Difference between revisions of "Module:Card gallery"

From Yugipedia
Jump to: navigation, search
(Trying to deal with JP images with DT edition.)
m (Fix dumb mistakes.)
Line 239: Line 239:
  
 
if not self.flags.isOP and rarity == '' then
 
if not self.flags.isOP and rarity == '' then
return self:errors( 'rarity' );
+
return self:error( 'rarity' );
 
end
 
end
  
Line 257: Line 257:
  
 
if not self.flags.noEdition and not self.edition then
 
if not self.flags.noEdition and not self.edition then
return self:errors( 'edition' );
+
return self:error( 'edition' );
 
end
 
end
  
Line 267: Line 267:
 
function File:initAlt()
 
function File:initAlt()
 
local index = 5;
 
local index = 5;
if self.flags.isOP   then index = index - 1 end
+
if self.flags.isOP then index = index - 1 end
if not self.flags.ed then index = index - 1 end
+
if not self.ed     then index = index - 1 end
  
 
self.alt = self._standard[ index ];
 
self.alt = self._standard[ index ];

Revision as of 22:03, 6 May 2018

-- <pre>
-- NOTES:
-- input in the form of:
-- «
-- <card number>; <set>; <rarity>; <edition>; <alt> :: <modifier> // <option1>::<value1>; <optionN>::<valueN>
-- »
-- Where "rarity" and "edition" can either be in full form or abbreviated.
-- Example:
-- «
-- {{Card gallery|region=EN|
-- TLM-EN012; The Lost Millennium; SR;  1E // extension::jpg
-- TLM-EN012; The Lost Millennium; UtR; 1E // extension::jpg
-- TLM-EN012; The Lost Millennium; SR;  UE
-- TLM-EN012; The Lost Millennium; UtR; UE
-- SDJ-035; Starter Deck: Joey; C; UE; Reprint // description::Spell
-- S1; Yu-Gi-Oh! True Duel Monsters: Sealed Memories promotional cards; UR
-- »

-- Ideas:
-- * propagate:
-- «
-- TLM-EN012; The Lost Millennium; SR;  1E
--         *;                   *; UtR;  *
--         *;                   *; SR;  UE
--         *;                   *; UtR;  *
-- »
-- Avoids repitition. Harder with a file replace script.

-- TODO: Check edition and alt (mind cases where JP images have an edition).

--------------------
-- Global vairables:
--------------------
local CardGallery = {}; -- Export variable.
local _D          = {}; -- Global data object.

----------------
-- Load modules:
----------------
local getArgs    = require( 'Module:Arguments' ).getArgs;
local getName    = require( 'Module:Name' ).main;
local getImgName = require( 'Module:Card image name' ).main;
local DATA       = require( 'Module:Data' );
local UTIL       = require( 'Module:Util' );

---------------------
-- Utility functions:
---------------------
-- mw functions:
local  split = mw.text.split;
local gsplit = mw.text.gsplit;
local HTML   = mw.html.create;

-- @name _error
-- @description Generates an error and places it on the error table.
local function _error( message, default, category )
	_D.errors.exists = true;
	local err = HTML( 'div' ):css( 'padding-left', '1.6em' )
		:tag( 'strong' ):addClass( 'error' )
			:wikitext( ('Error: %s'):format( message ) )
		:done()
	:allDone();
	local cat = category and ('[[Category:%s]]'):format( category ) or '';

	table.insert(
		_D.errors, table.concat( {
			tostring( err ), cat
		} )
	);

	return default or '';
end

--------------
-- File class:
--------------
-- @name File
-- @classAttr counter -> [static] Counts the number of File instances.
local File   = {};
File.__index = File;
File.counter = 0;

-- @name new          -> File constructor.
-- @attr flags        -> Control flags:
-- -- exists    => denotes if a file is to be printed.
-- -- noEdition => if it is a Japanese or Chinese print; thus, no edition (except DT ones).
-- -- isOP      => if it is an Official Proxy.
-- @attr number       -> The card number.
-- @attr rg           -> The region index.
-- @attr setEn        -> The English name for the set.
-- @attr setLn        -> The localized name for the set.
-- @attr setAbbr      -> The set abbreviation.
-- @attr rarity       -> The rarity name.
-- @attr r            -> The rarity abbreviation.
-- @attr edition      -> The full edition.
-- @attr ed           -> The edition abbreviation.
-- @attr modifier     -> The card modifier.
-- @attr mod          -> The card modifier abbreviation (OP|GC|CT|RP).
-- @attr alt          -> The alt value.
-- @attr extension    -> The file extension.
-- @attr description  -> A short file description.
function File.new( std, mod, opt )
	-- @attr _standard -> Contains the trimmed input args for the standard input {enum-like}.
	-- @attr _modifier -> Contains the trimmed input arg for the modifier (OP|GC|CT|RP).
	-- @attr _options  -> Contains the trimmed input args for the options {map-like}.
	File.counter       = File.counter + 1;
	local fileData     = {};
	fileData.flags     = {
		exists    = true,
		noEdition = false,
		isOP      = false
	};
	fileData._standard = std or {};
	fileData._modifier = mod or '';
	fileData._options  = opt or {};

	return setmetatable( fileData, File );
end

-- @name __tostring
-- @description [metamethod] Renders the File.
function File:__tostring()
	if not self.flags.exists then
		return '';
	end

	-- Build file:
	local file    = {
		getImgName(), self.setAbbr, self.rg
	};
	if self.r   then table.insert( file, self.r   ) end
	if self.ed  then table.insert( file, self.ed  ) end
	if self.mod then table.insert( file, self.mod ) end
	if self.alt then table.insert( file, self.alt ) end

	-- Build caption: (this might get better implementation in the future)
	local caption = {};
	local temp1st = {};
	if self.number then table.insert( temp1st, UTIL.link( self.number ) ) end
	if self.rarity then
		table.insert(
			temp1st,
			('(%s)'):format( UTIL.link( self.rarity, self.r ) )
		);
	end
	if UTIL.count( temp1st ) ~= 0 then
		table.insert( caption, table.concat( temp1st, ' ' ) );
	end
	if self.edition  then table.insert( caption, UTIL.link( self.edition  ) ) end
	if self.modifier then table.insert( caption, UTIL.link( self.modifier ) ) end
	table.insert(
		caption,
		UTIL.italics(
			UTIL.link(
				self.setEn,
				self.setEn:match( '%(2011%)' ) and self.setEn -- or self.setEn:match( '%(series%)' )
			)
		)
	);
	if self.description then table.insert( caption, self.description ) end

	-- Stringify:
	local fileString    = ('%s.%s'):format( table.concat( file, '-' ), self.extension );
	local captionString = table.concat( caption, '<br />' );
	
	-- Return concatenation:
	return ('%s | %s'):format( fileString, captionString );
end

-- @name error
-- @description Generate consistent error messages.
function File:error( parameter )
	self.flags.exists = false;
	_error( ('No %s given for file input number %d!'):format( parameter, File.counter ) );

	return self;
end

-- @name initRegion
-- @description Sets the «rg» attribute.
function File:initRg()
	self.rg = _D.rg:upper();
	self.flags.noEdition = self.rg == 'JP' or self.rg == 'JA' or self.rg == 'TC';

	return self;
end

-- @name initNumber
-- @description Sets the «number» and «setAbbr» attributes.
function File:initNumber()
	local cardNumber = self._standard[ 1 ];

	if cardNumber == '' then
		return self:error( 'set abbreviation' );
	end

	if cardNumber and cardNumber:match( '^%w-%-%w-$' ) then
		-- Input like «TLM-EN012».
		self.number  = cardNumber:upper();
		self.setAbbr = self.number:match( '^(%w-)%-%w-$' );
	else
		-- Input like «S1».
		self.number  = nil;
		self.setAbbr = cardNumber:upper();
	end

	return self;
end

-- @name initSet
-- @description Sets the «setEn» and «setLn» attributes.
function File:initSet()
	local set = self._standard[ 2 ];

	if set == '' then
		return self:error( 'set name' );
	end

	self.setEn = getName( set ) or set;
	self.setLn = getName( set, _D.language );

	return self;
end

-- @name initModifier
-- @description Sets the «modifier» and «mod» attributes.
function File:initModifier()
	self.modifier   = DATA.getModifier( self._modifier );
	self.mod        = self.modifier and DATA.getMod( self.modifier ):upper();
	self.flags.isOP = self.mod == 'OP';

	return self;
end

-- @name initRarity
-- @description Sets the «rarity» and «r» attributes.
function File:initRarity()
	local rarity = not self.flags.isOP and self._standard[ 3 ];

	if not self.flags.isOP and rarity == '' then
		return self:error( 'rarity' );
	end

	self.rarity = DATA.getRarity( rarity );
	self.r      = self.rarity and DATA.getR( self.rarity ):upper();

	return self;
end

-- @name initEdtion
-- @description Sets the «edition» and «ed» attributes.
function File:initEdition()
	local edition = self._standard[ self.flags.isOP and 3 or 4 ];

	self.edition = DATA.getEdition( edition );
	self.ed      = self.edition and DATA.getEd( self.edition ):upper();

	if not self.flags.noEdition and not self.edition then
		return self:error( 'edition' );
	end

	return self;
end

-- @name initAlt
-- @description Set the «alt» attribute.
function File:initAlt()
	local index = 5;
	if self.flags.isOP then index = index - 1 end
	if not self.ed     then index = index - 1 end

	self.alt = self._standard[ index ];

	return self;
end

-- @name initOptions
-- @description Sets the file extension.
function File:initOptions()
	-- Extension:
	local extension = self._options[ 'extension' ];
	self.extension  = UTIL.isString( extension ) and extension:lower() or 'png';

	-- Description:
	self.description = self._options[ 'description' ];

	return self;
end

-- @name init
-- @description Initializes the attributes of the File instance.
function File:init()
	return self
		:initRg()
		:initNumber()
		:initSet()
		:initModifier()
		:initRarity()
		:initEdition()
		:initAlt()
		:initOptions()
	;
end

--------------------
-- Module functions:
--------------------
-- @name getInfo
-- @description Handles generic info.
local function getInfo()
	-- Region and language:
	_D.rg       = DATA.getRg( _D.args[ 'region' ] ) or _error(
		('Invalid «region»: %s!'):format( _D.args[ 'region' ] or '(no region given)' ), DATA.getRg( 'en' )
	);
	_D.region   = DATA.getRegion( _D.rg );
	_D.ln       = DATA.getLn( _D.rg );
	_D.language = DATA.getLanguage( _D.rg );
	
	-- Flags:
	_D.flags            = {};
	_D.flags.notEnglish = _D.ln ~= 'en'; -- TODO may not be needed!
	_D.flags.italics    = not ((_D.ln == 'ja') or (_D.ln == 'zh') or (_D.ln == 'ko')) --[[and 'normal' or 'italic']];
	
	-- Medium:
	_D.cg = _D.flags.italics and 'TCG' or 'OCG'; -- TODO Probably not gonna use that.
	
	-- Card and page:
	local mwTitle = mw.title.getCurrentTitle();
	_D.PAGENAME   = mwTitle.text;
	_D.NAMESPACE  = mwTitle.nsText;
	_D.name       = getName( _D.PAGENAME, _D.language );

end

-- @name wrapInQuotes
-- @description Wraps «name» in proper quotation marks.
local function wrapInQuotes( name, std )
	if not UTIL.trim( name ) then
		return '';  --  Return empty string.
	end
	return (std or (_D.ln ~= 'ja' and _D.ln ~= 'zh'))
		and table.concat( { '"', name, '"' } )
		or table.concat( { '「', name, '」' } )
	;
end

-- @name printErrors
-- @dascription Stringifies the errors table.
local function printErrors()
	local category = '[[Category:((Card gallery)) transclusion to be checked]]';

	if not _D.errors.exists then
		return '';
	end

	table.insert( _D.errors, category );

	return table.concat( _D.errors, '\n' );
end

-- @name buildHeader
-- @description Builds the gallery header.
local function buildHeader()
	return HTML( 'div' ):addClass( 'gallery-header' ):attr( 'id', ('%s_section'):format( _D.rg ) )
		:tag( 'div' )
			:wikitext( wrapInQuotes( _D.name ) )
		:done()
	:allDone();
end


local function _buildStandard( arg )
	local standard = {};

	if not UTIL.trim( arg ) then
		return standard;
	end

	for value in gsplit( arg, '%s*;%s*' ) do
		if value then
			table.insert( standard, UTIL.trim( value ) or '' );
		end
	end

	return standard;
end

local function _buildOptions( arg )
	local options = {};

	if not UTIL.trim( arg ) then
		return options;
	end

	for option in gsplit( arg, '%s*;%s*' ) do
		local opt = UTIL.trim( option );
		if opt then
			local optTemp = split( opt, '%s*::%s*' );
			local optName  = UTIL.trim( optTemp[ 1 ] );
			local optValue = UTIL.trim( optTemp[ 2 ] );
			if optName and optValue then
				options[ optName ] = optValue;
			end
		end
	end

	return options;
end

-- @name splitEntry
-- @description Splits an input entry into the standard values, the modifier and the options.
local function splitEntry( entry )
	if not UTIL.trim( entry ) then
		return {}, '', {};
	end

	local temp1   = split( entry, '%s*//%s*' );
	local tempOpt = temp1[ 2 ] or '';
	local temp2   = split( temp1[ 1 ], '%s*::%s*' );
	local tempMod = temp2[ 2 ];
	local tempStd = temp2[ 1 ];

	local standard = _buildStandard( tempStd );
	local modifier = UTIL.trim( tempMod );
	local options  = _buildOptions( tempOpt );

	return standard, modifier, options;
end

-- @name process
-- @description Processes a gallery input entry and converts it into a File object («nil» if there's no valid info).
local function process( entry )
	local standard, modifier, options = splitEntry( entry );

	if UTIL.count( standard ) == 0 and UTIL.count( options ) == 0 then
		-- Not enough info to even try to build.
		return nil;
	end

	local fileObject = File.new( standard, modifier, options ):init();

	return fileObject.flags.exists and fileObject;
end

-- @name buildInnerGallery
-- @description Build the inner part of the gallery (between the <gallery> tags).
local function buildInnerGallery()
	if not _D.args[ 1 ] then
		return _error( 'Empty or no input provided for the gallery!', '' );
	end

	local galleryEntries = {};
	for inputEntry in gsplit( _D.args[ 1 ], '\n' ) do
		local entry = UTIL.trim( inputEntry ) and process( inputEntry );
		if entry then
			table.insert( galleryEntries, tostring( entry ) );
		end
	end

	return table.concat( galleryEntries, '\n' );
end

-- @name buildGallery
-- @description Builds the full gallery part.
local function buildGallery()
	local inner = buildInnerGallery();

	return table.concat( {
		'<gallery heights="175px" position="center" captionalign="center">',
		inner,
		'</gallery>'
	}, '\n' );
end

local function buildAll( frame, errors, header, gallery )
	local contatiner = HTML( 'div' ):addClass( 'card-galleries' )
		:node( tostring( header ) )
		:newline()
		:node( errors )
		:newline()
		:node( frame:preprocess( gallery ) )
	:allDone();

	return table.concat( {
		('== %s =='):format( _D.region ),
		tostring( contatiner )
	}, '\n' );
end

-- @name main
-- @notes exportable 
-- @description To be called through #invoke.
function CardGallery.main( frame )
	_D.errors = {};
	_D.args   = getArgs( frame, {
		trim         = true,
		removeBlanks = true,
		parentOnly   = true
	} );

	getInfo();

	local errors  = printErrors();
	local header  = buildHeader();
	local gallery = buildGallery();

	return buildAll( frame, errors, header, gallery );
end

----------
-- Return:
----------
return CardGallery;
-- </pre>