Module:Version: Difference between revisions
Jump to navigation
Jump to search
>TheFrz m (Date type sucks) |
m (48 revisions imported) |
||
(34 intermediate revisions by 6 users not shown) | |||
Line 1: | Line 1: | ||
------------------------------------------------------------------------------- | |||
-- | |||
-- Module:Version | |||
-- | |||
-- This module implements Template:Version, Template:Version history list, and | |||
-- Template:Timeline of items | |||
------------------------------------------------------------------------------- | |||
local | require('Module:No globals') | ||
local | local m_util = require('Module:Util') | ||
local | local m_cargo = require('Module:Cargo') | ||
local m_item_util -- Lazy load require('Module:Item util') | |||
local | -- Should we use the sandbox version of our submodules? | ||
local use_sandbox = m_util.misc.maybe_sandbox('Version') | |||
local | -- The cfg table contains all localisable strings and configuration, to make it | ||
-- easier to port this module to another wiki. | |||
local cfg = use_sandbox and mw.loadData('Module:Version/config/sandbox') or mw.loadData('Module:Version/config') | |||
local | local i18n = cfg.i18n | ||
-- --------------------------------------------------------------------- | |||
-- Helper functions | |||
-- --------------------------------------------------------------------- | |||
local h = {} | |||
---------------------- | function h.date(value, args) | ||
--[[ | |||
Format dates in correct and useable form. | |||
Parameters | |||
---------- | |||
value : String, required | |||
Date | |||
args : Table | |||
Table with extra formatting args. | |||
]] | |||
local args = args or {} | |||
-- List of allowed extra arguments: | |||
local arg_list = { | |||
format = { | |||
default = 'F j, Y H:i:s', | |||
cargo = 'Y-m-d H:i:s', | |||
no_time = 'F j, Y', | |||
}, | |||
} | |||
local lang = mw.getContentLanguage() | |||
local date_format = arg_list['format']['default'] | |||
local timestamp = lang:formatDate(date_format, value) | |||
-- If the time is 00:00:00 then assume that the time isn't defined: | |||
if lang:formatDate('H:i:s', timestamp) == '00:00:00' then | |||
date_format = arg_list['format']['no_time'] | |||
end | |||
-- Add the extra arguments: | |||
for i,v in pairs(args) do | |||
if i == 'format' then | |||
date_format = arg_list[i][v] | |||
end | |||
end | |||
-- Return the final timestamp format: | |||
local out | |||
if value ~= nil then | |||
out = lang:formatDate(date_format, timestamp) | |||
end | |||
return out | |||
end | |||
function h.validate_version(value) | |||
if value == nil then | |||
return value | |||
end | |||
return m_util.cast.version(value, {return_type='string'}) | |||
end | |||
function h.show_date(args) | |||
return function(targs) | |||
local version = targs[args.key] | |||
local date = targs[string.format('%s_date', args.key)] | |||
if version and date then | |||
date = h.date(date) or '' | |||
if args.key == 'before' then | |||
return string.format(i18n.show_date.before, version, version, date) | |||
elseif args.key == 'after' then | |||
return string.format(i18n.show_date.after, version, version, date) | |||
return | |||
end | end | ||
end | else | ||
}, | return '' | ||
end | |||
end | |||
end | |||
-- ---------------------------------------------------------------------------- | |||
-- Cargo tables | |||
-- ---------------------------------------------------------------------------- | |||
local tables = {} | |||
tables.versions ={ | |||
table = 'versions', | |||
fields = { | |||
patch = { | |||
field = 'version', | |||
type = 'String', | |||
func = h.validate_version, | |||
}, | |||
patchdate = { | |||
field = 'release_date', | |||
type = 'Datetime', | |||
func = tostring, | |||
}, | |||
major_part = { | |||
field = 'major_part', | |||
type = 'Integer', | |||
}, | |||
minor_part = { | |||
field = 'minor_part', | |||
type = 'Integer', | |||
}, | |||
patch_part = { | |||
field = 'patch_part', | |||
type = 'Integer', | |||
}, | |||
revision_part = { | |||
field = 'revision_part', | |||
type = 'String', | |||
}, | |||
before = { | |||
field = 'previous', | |||
type = 'String', | |||
func = h.validate_version, | |||
show = h.show_date{key='before'}, | |||
}, | |||
after = { | |||
field = 'after', | |||
type = 'String', | |||
func = h.validate_version, | |||
show = h.show_date{key='after'}, | |||
}, | |||
}, | }, | ||
} | } | ||
-- ---------------------------------------------------------------------------- | |||
-- Main functions | |||
-- ---------------------------------------------------------------------------- | |||
local function _version(args) | |||
--[[ | --[[ | ||
Creates a version succession box and stores the data in a cargo table | |||
before = '2.4. | |||
Example: | |||
p.version{ | |||
before = '2.4.1', | |||
patch = '2.4.1b', | patch = '2.4.1b', | ||
patchdate = 'October 18, 2016', | patchdate = 'October 18, 2016', | ||
after = '2.4.2', | after = '2.4.2', | ||
} | } | ||
--]] | --]] | ||
-- Unpack args and validate | |||
for k, arg_def in pairs(tables.versions.fields) do | |||
if arg_def.func ~= nil then | |||
args[k] = arg_def.func(args[k]) | |||
end | |||
end | |||
if not args.patch or not args.patchdate then | if not args.patch or not args.patchdate then | ||
error( | error(i18n.version.required_args) | ||
end | end | ||
local | local version_parts = m_util.cast.version(args.patch, {return_type='table'}) | ||
args.major_part = tonumber(version_parts[1]) | |||
args.minor_part = tonumber(version_parts[2]) | |||
args.patch_part = tonumber(version_parts[3]) | |||
if version_parts[4] then | |||
args.revision_part = version_parts[4] | |||
end | end | ||
-- Validate 'before' and 'after' versions and query their release dates | |||
-- | for _, key in ipairs({'before', 'after'}) do | ||
local version_number = args[key] | |||
if version_number then | |||
local | local results = m_cargo.query( | ||
if | {'versions'}, | ||
{'versions.release_date=date'}, | |||
{ | |||
where = string.format('versions.version="%s"', version_number) | |||
} | |||
) | |||
if #results == 1 then | |||
args[string.format('%s_date', key)] = results[1].date | |||
elseif #results > 1 then | |||
error(i18n.version.multiple_versions) | |||
end | |||
end | end | ||
end | end | ||
-- Store cargo data | |||
-- | local data = { | ||
local | _table = tables.versions.table, | ||
_table = | |||
} | } | ||
for | for k, v in pairs(tables.versions.fields) do | ||
if | if args[k] ~= nil then | ||
data[v.field] = args[k] | |||
end | |||
end | end | ||
m_cargo.store(data) | |||
mw.getCurrentFrame():expandTemplate{ | |||
title = 'Template:Version/cargo/versions/attach' | |||
} | |||
-- Generate output | -- Generate output | ||
local release_date = h.date(args.patchdate) | |||
local release_date = | local tbl = mw.html.create('table') | ||
local tbl = | |||
tbl | tbl | ||
:addClass('wikitable successionbox') | :addClass('wikitable successionbox') | ||
Line 153: | Line 227: | ||
:tag('th') | :tag('th') | ||
:attr('colspan', 3) | :attr('colspan', 3) | ||
:wikitext( | :wikitext(i18n.version.header) | ||
:done() | :done() | ||
:done() | :done() | ||
Line 159: | Line 233: | ||
:tag('td') | :tag('td') | ||
:cssText('width: 30%') | :cssText('width: 30%') | ||
:wikitext( | :wikitext(tables.versions.fields.before.show(args)) | ||
:done() | :done() | ||
:tag('td') | :tag('td') | ||
:cssText('width: 40%') | :cssText('width: 40%') | ||
:wikitext( | :wikitext(string.format('<b>%s</b><br>%s', args.patch, release_date)) | ||
:done() | :done() | ||
:tag('td') | :tag('td') | ||
:cssText('width: 30%') | :cssText('width: 30%') | ||
:wikitext( | :wikitext(tables.versions.fields.after.show(args)) | ||
return tostring(tbl) .. | return tostring(tbl) .. m_util.misc.add_category({i18n.categories.versions}) | ||
end | end | ||
---- | local function _timeline(args) | ||
--[[ | |||
Creates a version timeline and optionally lists items added to the game for each version | |||
Examples: | |||
p.timeline{ | |||
where = 'versions.major_part = 0 AND versions.minor_part < 9', | |||
} | |||
p.timeline{ | |||
list_items = true | |||
where = 'items.class_id = "DivinationCard"', | |||
} | |||
--]] | |||
local tables = {'versions'} | |||
local fields = { | |||
local | 'versions.version', | ||
'versions.release_date', | |||
} | } | ||
local query = { | local query = { | ||
orderBy = 'versions.major_part DESC, versions.minor_part DESC, versions.patch_part DESC, versions.revision_part DESC' | |||
} | } | ||
args.list_items = m_util.cast.boolean(args.list_items) | |||
if args.list_items then | |||
m_item_util = m_item_util or require('Module:Item util') | |||
table.insert(tables, 'items') | |||
fields = m_util.table.merge(fields, {'items._pageName', 'items.name'}) | |||
query.join = 'versions.version=items.release_version' | |||
query.where = 'items.release_version IS NOT NULL' | |||
query.orderBy = query.orderBy .. ', items.name ASC' | |||
-- Namespace condition | |||
-- This is mainly to prevent items from user pages or other testing pages | |||
-- from being returned in the query results. | |||
if args.namespaces ~= 'any' then | |||
local namespaces = m_util.cast.table(args.namespaces, {callback=m_util.cast.number}) | |||
if #namespaces > 0 then | |||
namespaces = table.concat(namespaces, ',') | |||
else | |||
namespaces = m_item_util.get_item_namespaces{format = 'list'} | |||
end | |||
query.where = string.format('%s AND items._pageNamespace IN (%s)', query.where, namespaces) | |||
end | end | ||
end | |||
if args.where then | |||
-- m_util.table.merge rebuilds the table, which removes empty values | |||
-- TODO: Use a better function than m_util.table.merge | |||
query.where = table.concat(m_util.table.merge({query.where, args.where}), ' AND ') | |||
end | |||
local results = m_cargo.query(tables, fields, query) | |||
local out = {} | local out = {} | ||
local last_minor_version | local last_main_version | ||
local last_minor_version | |||
for i | local current_version | ||
local | local list | ||
-- Loop through all the results from the query | |||
for i, row in ipairs(results) do | |||
local v = | local release_version = row['versions.version'] | ||
local | local v = m_util.cast.version(release_version) | ||
local version_h2 = table.concat({v[1], v[2]}, '.') | |||
if | if release_version ~= last_minor_version then | ||
if | if version_h2 ~= last_main_version then | ||
out[#out + 1] = | if current_version ~= nil then | ||
out[#out + 1] = tostring(current_version) | |||
end | |||
out[#out+1] = string.format( | |||
'===%s %s===', | |||
i18n.timeline.version, | |||
table.concat({v[1], v[2], 0}, '.') | |||
) | |||
current_version = mw.html.create('ul') | |||
end | end | ||
current_version | |||
:tag('li') | |||
:wikitext(string.format( | |||
'%s - [[%s %s]]', | |||
h.date(row['versions.release_date']), | |||
i18n.timeline.version, | |||
release_version, | |||
row['versions.version']) | |||
) | |||
list = current_version:tag('ol') | |||
end | |||
-- List items | |||
if args.list_items then | |||
list | |||
:tag('li') | |||
:wikitext(m_util.html.wikilink(row['items._pageName'], row['items.name'])) | |||
end | end | ||
-- Save the last list | |||
if i == #results and current_version ~= nil then | |||
-- | out[#out + 1] = tostring(current_version) | ||
if i == #results and | |||
out[#out + 1] = tostring( | |||
end | end | ||
last_minor_version = | last_main_version = version_h2 | ||
last_minor_version = release_version | |||
end | end | ||
return | return table.concat(out, '\n') .. m_util.misc.add_category({i18n.categories.timelines}) | ||
end | end | ||
----- | -- ---------------------------------------------------------------------------- | ||
-- Exported functions | |||
-- ---------------------------------------------------------------------------- | |||
local p = {} | |||
p.table_versions = m_cargo.declare_factory{data=tables.versions} | |||
-- | |||
-- Template:Version | |||
-- | |||
p.version = m_util.misc.invoker_factory(_version, { | |||
wrappers = cfg.wrappers.version, | |||
}) | |||
----- | -- | ||
-- Template:Version history list, Template:Timeline of items | |||
-- | |||
p.timeline = m_util.misc.invoker_factory(_timeline) | |||
return p | return p |
Latest revision as of 22:01, 24 September 2024
The above documentation is transcluded from Module:Version/doc.
Editors can experiment in this module's sandbox and testcases pages.
Subpages of this module.
Editors can experiment in this module's sandbox and testcases pages.
Subpages of this module.
-------------------------------------------------------------------------------
--
-- Module:Version
--
-- This module implements Template:Version, Template:Version history list, and
-- Template:Timeline of items
-------------------------------------------------------------------------------
require('Module:No globals')
local m_util = require('Module:Util')
local m_cargo = require('Module:Cargo')
local m_item_util -- Lazy load require('Module:Item util')
-- Should we use the sandbox version of our submodules?
local use_sandbox = m_util.misc.maybe_sandbox('Version')
-- The cfg table contains all localisable strings and configuration, to make it
-- easier to port this module to another wiki.
local cfg = use_sandbox and mw.loadData('Module:Version/config/sandbox') or mw.loadData('Module:Version/config')
local i18n = cfg.i18n
-- ---------------------------------------------------------------------
-- Helper functions
-- ---------------------------------------------------------------------
local h = {}
function h.date(value, args)
--[[
Format dates in correct and useable form.
Parameters
----------
value : String, required
Date
args : Table
Table with extra formatting args.
]]
local args = args or {}
-- List of allowed extra arguments:
local arg_list = {
format = {
default = 'F j, Y H:i:s',
cargo = 'Y-m-d H:i:s',
no_time = 'F j, Y',
},
}
local lang = mw.getContentLanguage()
local date_format = arg_list['format']['default']
local timestamp = lang:formatDate(date_format, value)
-- If the time is 00:00:00 then assume that the time isn't defined:
if lang:formatDate('H:i:s', timestamp) == '00:00:00' then
date_format = arg_list['format']['no_time']
end
-- Add the extra arguments:
for i,v in pairs(args) do
if i == 'format' then
date_format = arg_list[i][v]
end
end
-- Return the final timestamp format:
local out
if value ~= nil then
out = lang:formatDate(date_format, timestamp)
end
return out
end
function h.validate_version(value)
if value == nil then
return value
end
return m_util.cast.version(value, {return_type='string'})
end
function h.show_date(args)
return function(targs)
local version = targs[args.key]
local date = targs[string.format('%s_date', args.key)]
if version and date then
date = h.date(date) or ''
if args.key == 'before' then
return string.format(i18n.show_date.before, version, version, date)
elseif args.key == 'after' then
return string.format(i18n.show_date.after, version, version, date)
end
else
return ''
end
end
end
-- ----------------------------------------------------------------------------
-- Cargo tables
-- ----------------------------------------------------------------------------
local tables = {}
tables.versions ={
table = 'versions',
fields = {
patch = {
field = 'version',
type = 'String',
func = h.validate_version,
},
patchdate = {
field = 'release_date',
type = 'Datetime',
func = tostring,
},
major_part = {
field = 'major_part',
type = 'Integer',
},
minor_part = {
field = 'minor_part',
type = 'Integer',
},
patch_part = {
field = 'patch_part',
type = 'Integer',
},
revision_part = {
field = 'revision_part',
type = 'String',
},
before = {
field = 'previous',
type = 'String',
func = h.validate_version,
show = h.show_date{key='before'},
},
after = {
field = 'after',
type = 'String',
func = h.validate_version,
show = h.show_date{key='after'},
},
},
}
-- ----------------------------------------------------------------------------
-- Main functions
-- ----------------------------------------------------------------------------
local function _version(args)
--[[
Creates a version succession box and stores the data in a cargo table
Example:
p.version{
before = '2.4.1',
patch = '2.4.1b',
patchdate = 'October 18, 2016',
after = '2.4.2',
}
--]]
-- Unpack args and validate
for k, arg_def in pairs(tables.versions.fields) do
if arg_def.func ~= nil then
args[k] = arg_def.func(args[k])
end
end
if not args.patch or not args.patchdate then
error(i18n.version.required_args)
end
local version_parts = m_util.cast.version(args.patch, {return_type='table'})
args.major_part = tonumber(version_parts[1])
args.minor_part = tonumber(version_parts[2])
args.patch_part = tonumber(version_parts[3])
if version_parts[4] then
args.revision_part = version_parts[4]
end
-- Validate 'before' and 'after' versions and query their release dates
for _, key in ipairs({'before', 'after'}) do
local version_number = args[key]
if version_number then
local results = m_cargo.query(
{'versions'},
{'versions.release_date=date'},
{
where = string.format('versions.version="%s"', version_number)
}
)
if #results == 1 then
args[string.format('%s_date', key)] = results[1].date
elseif #results > 1 then
error(i18n.version.multiple_versions)
end
end
end
-- Store cargo data
local data = {
_table = tables.versions.table,
}
for k, v in pairs(tables.versions.fields) do
if args[k] ~= nil then
data[v.field] = args[k]
end
end
m_cargo.store(data)
mw.getCurrentFrame():expandTemplate{
title = 'Template:Version/cargo/versions/attach'
}
-- Generate output
local release_date = h.date(args.patchdate)
local tbl = mw.html.create('table')
tbl
:addClass('wikitable successionbox')
:tag('tr')
:tag('th')
:attr('colspan', 3)
:wikitext(i18n.version.header)
:done()
:done()
:tag('tr')
:tag('td')
:cssText('width: 30%')
:wikitext(tables.versions.fields.before.show(args))
:done()
:tag('td')
:cssText('width: 40%')
:wikitext(string.format('<b>%s</b><br>%s', args.patch, release_date))
:done()
:tag('td')
:cssText('width: 30%')
:wikitext(tables.versions.fields.after.show(args))
return tostring(tbl) .. m_util.misc.add_category({i18n.categories.versions})
end
local function _timeline(args)
--[[
Creates a version timeline and optionally lists items added to the game for each version
Examples:
p.timeline{
where = 'versions.major_part = 0 AND versions.minor_part < 9',
}
p.timeline{
list_items = true
where = 'items.class_id = "DivinationCard"',
}
--]]
local tables = {'versions'}
local fields = {
'versions.version',
'versions.release_date',
}
local query = {
orderBy = 'versions.major_part DESC, versions.minor_part DESC, versions.patch_part DESC, versions.revision_part DESC'
}
args.list_items = m_util.cast.boolean(args.list_items)
if args.list_items then
m_item_util = m_item_util or require('Module:Item util')
table.insert(tables, 'items')
fields = m_util.table.merge(fields, {'items._pageName', 'items.name'})
query.join = 'versions.version=items.release_version'
query.where = 'items.release_version IS NOT NULL'
query.orderBy = query.orderBy .. ', items.name ASC'
-- Namespace condition
-- This is mainly to prevent items from user pages or other testing pages
-- from being returned in the query results.
if args.namespaces ~= 'any' then
local namespaces = m_util.cast.table(args.namespaces, {callback=m_util.cast.number})
if #namespaces > 0 then
namespaces = table.concat(namespaces, ',')
else
namespaces = m_item_util.get_item_namespaces{format = 'list'}
end
query.where = string.format('%s AND items._pageNamespace IN (%s)', query.where, namespaces)
end
end
if args.where then
-- m_util.table.merge rebuilds the table, which removes empty values
-- TODO: Use a better function than m_util.table.merge
query.where = table.concat(m_util.table.merge({query.where, args.where}), ' AND ')
end
local results = m_cargo.query(tables, fields, query)
local out = {}
local last_main_version
local last_minor_version
local current_version
local list
-- Loop through all the results from the query
for i, row in ipairs(results) do
local release_version = row['versions.version']
local v = m_util.cast.version(release_version)
local version_h2 = table.concat({v[1], v[2]}, '.')
if release_version ~= last_minor_version then
if version_h2 ~= last_main_version then
if current_version ~= nil then
out[#out + 1] = tostring(current_version)
end
out[#out+1] = string.format(
'===%s %s===',
i18n.timeline.version,
table.concat({v[1], v[2], 0}, '.')
)
current_version = mw.html.create('ul')
end
current_version
:tag('li')
:wikitext(string.format(
'%s - [[%s %s]]',
h.date(row['versions.release_date']),
i18n.timeline.version,
release_version,
row['versions.version'])
)
list = current_version:tag('ol')
end
-- List items
if args.list_items then
list
:tag('li')
:wikitext(m_util.html.wikilink(row['items._pageName'], row['items.name']))
end
-- Save the last list
if i == #results and current_version ~= nil then
out[#out + 1] = tostring(current_version)
end
last_main_version = version_h2
last_minor_version = release_version
end
return table.concat(out, '\n') .. m_util.misc.add_category({i18n.categories.timelines})
end
-- ----------------------------------------------------------------------------
-- Exported functions
-- ----------------------------------------------------------------------------
local p = {}
p.table_versions = m_cargo.declare_factory{data=tables.versions}
--
-- Template:Version
--
p.version = m_util.misc.invoker_factory(_version, {
wrappers = cfg.wrappers.version,
})
--
-- Template:Version history list, Template:Timeline of items
--
p.timeline = m_util.misc.invoker_factory(_timeline)
return p