Module:Version: Difference between revisions
		
		
		
		Jump to navigation
		Jump to search
		
|  (Removed unused code) | m (Only use the first two components of the version as the third one can vary) | ||
| (5 intermediate revisions by one other user not shown) | |||
| Line 1: | Line 1: | ||
| ------------------------------------------------------------------------------- | |||
| --  | |||
| --                                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_util = require('Module:Util') | ||
| local m_cargo = require('Module:Cargo') | local m_cargo = require('Module:Cargo') | ||
| local  | 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 | ||
| -- --------------------------------------------------------------------- | -- --------------------------------------------------------------------- | ||
| Line 39: | Line 26: | ||
| local h = {} | local h = {} | ||
| function h.date(value, args) | function h.date(value, args) | ||
| Line 92: | Line 50: | ||
|          }, |          }, | ||
|      } |      } | ||
|      local lang = mw.getContentLanguage() | |||
|      local date_format = arg_list['format']['default'] |      local date_format = arg_list['format']['default'] | ||
|      local timestamp =  |      local timestamp = lang:formatDate(date_format, value) | ||
|      -- If the time is 00:00:00 then assume that the time isn't defined: |      -- If the time is 00:00:00 then assume that the time isn't defined: | ||
|      if  |      if lang:formatDate('H:i:s', timestamp) == '00:00:00' then   | ||
|          date_format = arg_list['format']['no_time'] |          date_format = arg_list['format']['no_time'] | ||
|      end |      end | ||
| Line 111: | Line 70: | ||
|      local out |      local out | ||
|      if value ~= nil then |      if value ~= nil then | ||
|          out =  |          out = lang:formatDate(date_format, timestamp) | ||
|      end |      end | ||
| Line 117: | Line 76: | ||
| end | end | ||
| function h.validate_version(value) | |||
|      if value == nil then |      if value == nil then | ||
|          return value |          return value | ||
|      end |      end | ||
|     return m_util.cast.version(value, {return_type='string'}) | |||
| end | end | ||
| function h.show_date(args) | |||
|      return function( |      return function(targs) | ||
|          local version =  |          local version = targs[args.key] | ||
|          local date =  |          local date = targs[string.format('%s_date', args.key)] | ||
|          if version and date then |          if version and date then | ||
|              date = h.date(date) or '' |              date = h.date(date) or '' | ||
|              if args.key == 'before' then |              if args.key == 'before' then | ||
|                  return  |                  return string.format(i18n.show_date.before, version, version, date) | ||
|              elseif args.key == 'after' then |              elseif args.key == 'after' then | ||
|                  return  |                  return string.format(i18n.show_date.after, version, version, date) | ||
|              end |              end | ||
|          else |          else | ||
| Line 148: | Line 100: | ||
| end | end | ||
| -- ---------------------------------------------------------------------------- | |||
| -- Cargo tables | |||
| -- ---------------------------------------------------------------------------- | |||
| local  | local tables = {} | ||
| tables.versions ={ | |||
|      table = 'versions', |      table = 'versions', | ||
|      fields = { |      fields = { | ||
| Line 155: | Line 112: | ||
|              field = 'version', |              field = 'version', | ||
|              type = 'String', |              type = 'String', | ||
|              func = h.validate_version, | |||
|          }, |          }, | ||
|          patchdate = { |          patchdate = { | ||
|              field = 'release_date', |              field = 'release_date', | ||
|              type = 'Datetime', |              type = 'Datetime', | ||
|              func = tostring, | |||
|          }, |          }, | ||
|          major_part = { |          major_part = { | ||
| Line 181: | Line 138: | ||
|              field = 'previous', |              field = 'previous', | ||
|              type = 'String', |              type = 'String', | ||
|              func = h.validate_version, | |||
|              show = show_date{key='before'}, |              show = h.show_date{key='before'}, | ||
|          }, |          }, | ||
|          after = { |          after = { | ||
|              field = 'after', |              field = 'after', | ||
|              type = 'String', |              type = 'String', | ||
|              func = h.validate_version, | |||
|              show = show_date{key='after'}, |              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', |          before = '2.4.1', | ||
|          patch = '2.4.1b', |          patch = '2.4.1b', | ||
| Line 208: | Line 167: | ||
|      --]] |      --]] | ||
|      -- Unpack args and validate | |||
|      for k, arg_def in pairs(tables.versions.fields) do | |||
|          if arg_def.func ~= nil then | |||
|      for k,  |              args[k] = arg_def.func(args[k]) | ||
|          if  | |||
|          end |          end | ||
|      end |      end | ||
|      if not args.patch or not args.patchdate then | |||
|      if not  | |||
|          error(i18n.version.required_args) |          error(i18n.version.required_args) | ||
|      end |      end | ||
|      local version_parts = m_util.cast.version( |      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 |      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] | |||
|          local  |          if version_number then | ||
|          if  |              local results = m_cargo.query( | ||
|              local results =  |                  {'versions'}, | ||
|                  'versions',   |                  {'versions.release_date=date'}, | ||
|                  'versions.release_date',   | |||
|                  { |                  { | ||
|                      where=string.format('version="%s"',  |                      where = string.format('versions.version="%s"', version_number) | ||
|                  } |                  } | ||
|              ) |              ) | ||
|              if #results == 1 then |              if #results == 1 then | ||
|                  args[string.format('%s_date', key)] = results[1].date | |||
|              elseif #results > 1 then |              elseif #results > 1 then | ||
|                  error(i18n.version.multiple_versions) |                  error(i18n.version.multiple_versions) | ||
| Line 252: | Line 203: | ||
|          end |          end | ||
|      end |      end | ||
|      --  |      -- Store cargo data | ||
|      local  |      local data = { | ||
|          _table =  |          _table = tables.versions.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 |      end | ||
|      m_cargo.store(data) | |||
|      m_cargo.store( | |||
|      mw.getCurrentFrame():expandTemplate{ | |||
|          title =  |          title = 'Template:Version/cargo/versions/attach' | ||
|      } |      } | ||
|      -- Generate output |      -- Generate output | ||
|      local release_date = h.date( |      local release_date = h.date(args.patchdate) | ||
|      local tbl =  |      local tbl = mw.html.create('table') | ||
|      tbl |      tbl | ||
|          :addClass('wikitable successionbox') |          :addClass('wikitable successionbox') | ||
| Line 285: | 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) .. m_util.misc.add_category({i18n.categories.versions}) | |||
|      return tostring(tbl) .. m_util.misc.add_category( | |||
| end | end | ||
| local function _timeline(args) | |||
| function  | |||
|      --[[   |      --[[   | ||
|      Creates a version timeline and optionally lists items added to the game for each version | |||
|      Examples: |      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', | |||
|      local  |         '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 | ||
|      local results =  |         -- 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 out = {} | ||
|      local last_main_version   |      local last_main_version   | ||
|      local last_minor_version |      local last_minor_version | ||
|      local current_version |      local current_version | ||
|      local  |      local list | ||
|      -- Loop through all the results from the query |      -- Loop through all the results from the query | ||
|      for i,  |      for i, row in ipairs(results) do | ||
|          local release_version =  |          local release_version = row['versions.version'] | ||
|          local v = m_util.cast.version(release_version) |          local v = m_util.cast.version(release_version) | ||
|          local version_h2 = table.concat({v[1], v[2]}, '.') |          local version_h2 = table.concat({v[1], v[2]}, '.') | ||
|          if release_version ~= last_minor_version then |          if release_version ~= last_minor_version then | ||
|              if version_h2 ~= last_main_version then   |              if version_h2 ~= last_main_version then   | ||
|                  if current_version ~= nil then |                  if current_version ~= nil then | ||
| Line 363: | Line 318: | ||
|                      '===%s %s===',   |                      '===%s %s===',   | ||
|                      i18n.timeline.version,   |                      i18n.timeline.version,   | ||
|                      table.concat({v[1], v[2] |                      table.concat({v[1], v[2]}, '.') | ||
|                  )   |                  )   | ||
|                  current_version = mw.html.create('ul') |                  current_version = mw.html.create('ul') | ||
|              end |              end | ||
|              current_version |              current_version | ||
|                  :tag('li') |                  :tag('li') | ||
|                      :wikitext(string.format( |                      :wikitext(string.format( | ||
|                          '%s - [[%s %s]]',   |                          '%s - [[%s %s]]', | ||
|                          h.date( |                          h.date(row['versions.release_date']), | ||
|                          i18n.timeline.version,   |                          i18n.timeline.version, | ||
|                          release_version,   |                          release_version, | ||
|                          row['versions.version']) | |||
|                      ) |                      ) | ||
|              list = current_version:tag('ol') | |||
|          end   |          end | ||
|          -- List items | |||
|          --  |          if args.list_items then | ||
|              list | |||
|          if  |                 :tag('li') | ||
|                     :wikitext(m_util.html.wikilink(row['items._pageName'], row['items.name'])) | |||
|          end |          end | ||
|          -- Save the last list |          -- Save the last list | ||
|          if  |          if i == #results and current_version ~= nil then   | ||
|              out[#out + 1] = tostring(current_version) |              out[#out + 1] = tostring(current_version) | ||
|          end |          end | ||
| Line 408: | Line 349: | ||
|          last_minor_version = release_version |          last_minor_version = release_version | ||
|      end |      end | ||
|      return table.concat(out, '\n') .. m_util.misc.add_category({i18n.categories.timelines}) | |||
|      return table.concat(out, '\n') .. m_util.misc.add_category( | |||
| 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 09:42, 1 April 2025
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]}, '.')
                ) 
                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

