Module:Cronos
This module uses TemplateStyles: |
The Module:Cronos is the core of the Meta:Cronos project and it's the Lua module implementing these templates:
- {{Cronos month}}
- {{Cronos list}}
See their related documentation for normal usage from wikitext.
Proceed in this page to discover how to read events using Cronos' public Lua APIs.
Overview
editThe source code of Module:Cronos is object-oriented and with lot of in-line documentation. Read-it.
In short there are some classes:
- CronosEvent
- whatever event (party, session, hackaton, etc.)
- CronosDay
- collector of events happening in a specific day
- CronosCategory
- one for each Event, this is its icon (see phab:T254586)
- CronosCalendarContext
- unuseful utility class used to store preferences and filters (e.g. Tags)
CronosDay API
editYou can access the data of a single Event from other Lua modules:
-- load the module
local Cronos = require( 'Module:Cronos' )
-- load a single day
local day = Cronos._day( '2019-03-25' )
-- load related events sorted by time
local events = day:getEventsSortedByTime()
-- loop these events
for i, event in pairs( events ) do
-- examine start date
mw.logObject( event:getStartDate() )
-- examine end date
mw.logObject( event:getEndDate() )
-- examine its category icon
mw.logObject( event:getCategory():getIconWikitext() )
-- examine some other data available in the CronosEvent object
mw.logObject( event )
end
CronosCategory API
editYou can also obtain other information like a known Category:
-- load the module
local Cronos = require( 'Module:Cronos' )
-- load a single category
local category = Cronos._category( 'libre' )
-- examine its icon
mw.logObject( category:getIconWikitext() )
-- examine its user identifier (e.g. 'libre')
mw.logObject( category.uid )
-- examine its name (e.g. 'Free Software initiatives')
mw.logObject( category.name )
-- examine some other data available in the CronosCategory object
mw.logObject( category )
Other APIs
editPlease look at the source code for everything exported in the p
-ackage. The source code is intensively in-line documented.
Questions / Issues
editFor any question or idea feel free to write in the talk page pinging the current maintainer who is User:Valerio Bozzolan.
For any issue feel free to open a new Task in Wikimedia Phabricator with the #WMCH-Cronos Tag and assign it to valerio.bozzolan: Create a Task.
See also
edit
---
-- This is the module that auto-generates the [[Meta:Cronos]] monthly calendar
--
-- Thanks to this module we do not need a bot to keep the calendar
-- updated.
--
-- This module does not have any dependency. Please keep this feature.
--
-- Note that this module needs to read some Event-related pages from your wiki.
-- This increases the "Expensive parser function count" by ~40. Anyway, the
-- global limit actually is 500.
--
-- Happy hacking!
--
-- @author [[User:Valerio Bozzolan]]
-- @creation 2019-04-13
-- @see https://phabricator.wikimedia.org/tag/wmch-cronos/
---
---
-- List of well-known categories
--
-- If you add a new Category please add a note in the talk page in order to
-- update the related website.
--
-- Note that the 'com' is expressed as default category in the configuration.
--
-- See:
-- https://phabricator.wikimedia.org/T254586
--
local CRONOS_CATEGORIES = {
['com'] = { uid = 'com', name = 'Community initiatives', filename = 'File:Wikimedia Community Logo.svg' },
['dat'] = { uid = 'dat', name = 'Wikidata initiatives', filename = 'File:Wikidata Favicon color.svg' },
['edu'] = { uid = 'edu', name = 'Wikimedia Education Program', filename = 'File:WikipediaEduBelow.svg' },
['libre'] = { uid = 'libre', name = 'Free Software and Open Source initiatives', filename = 'File:Heckert GNU white.svg' },
['osm'] = { uid = 'osm', name = 'OpenStreetMap', filename = 'File:Openstreetmap logo.svg' },
['glam'] = { uid = 'glam', name = 'Wikimedia GLAM Program', filename = 'File:GLAM logo.png' },
['wmch'] = { uid = 'wmch', name = 'Wikimedia CH initiatives', filename = 'File:WikimediaCHLogo.svg' },
['wbg'] = { uid = 'wbg', name = 'West Bengal Wikimedians initiatives', filename = 'File:Logo of West Bengal Wikimedians User Group.svg' },
['wmf'] = { uid = 'wmf', name = 'Wikimedia Foundation initiatives', filename = 'File:Wikimedia-logo black.svg' },
['wmno'] = { uid = 'wmno', name = 'Wikimedia Norge initiatives', filename = 'File:Wikimedia Norge-logo svart nb.svg' },
}
-- append a default dummy category
-- please keep this separated from the others for more visibility
CRONOS_CATEGORIES.default = { uid = 'default', name = 'Event', filename = 'File:Wikimedia Community Logo.svg' }
-- Default configuration
local DEFAULT_CONFIG = {
-- Namespace name for the event pages
-- e.g. 'Meta' for [[Meta:Cronos/Events/2000-12-31]
['event_ns'] = 'Meta',
-- Expected prefix for every Cronos event page
-- e.g. 'Meta:Cronos/Event/' for [[Meta:Cronos/Events/2000-12-31]
['event_prefix'] = 'Meta:Cronos/Events/',
-- Expected French Calendar dataset
-- See [[Events calendar/events.json]] in Meta-Wikusa i
-- Set to nil to disable this feature.
['french_dataset_page'] = 'Events calendar/events.json',
-- French Calendar home URL or page title
-- the French calendar has not a permalink for each event
-- so these events just link to this homepage
['french_dataset_url'] = 'Events calendar',
-- If you have another cute web-Cronos frontend, put the URL here
['cute_form_url'] = 'https://wmch.toolforge.org/cronos/',
-- Default timezone
-- '0': use local wiki timezone
-- '1': use UTC timezone
['utc'] = '0',
-- Default start of the week
-- '1': week start from Monday
-- '0': week start from Sunday
['start_from_monday'] = '1',
-- How much weeks to be displayed in the calendar as default
-- Note that if a week is splitted in two Months,
-- you may get more lines in order to display them.
['weeks'] = '4',
-- How much months to shift in the past or in the future as default
-- '0': Display the current month
-- '1': Display the next month
-- '-1': Display the previous month
-- etc.
['month_shift'] = '0',
-- Maximum length of an Event title in bytes before truncating it
['max_title_len'] = '30',
-- Choose to have a shorter week name as default
['short_weekname'] = '0',
-- Template used to briefly list some events
-- As default: [[Template:Cronos day brief]]
-- Arguments:
-- yyyy year in 4 digits
-- mm month in 2 digits
-- dd day in 2 digits
['template_day_brief'] = 'Cronos day brief',
-- Template used to briefly tell something about an event
-- As default: [[Template:Cronos event brief]]
-- Arguments:
-- yyyy
-- mm
-- dd
-- title
-- when
-- end
-- url
-- editurl
['template_event_brief'] = 'Cronos event brief',
-- Template used to display an event in a single line
-- As default: [[Template:Cronos event brief line]]
-- Arguments:
-- yyyy
-- mm
-- dd
-- title
-- when
-- end
-- url
-- editurl
['template_event_brief_line'] = 'Cronos event brief line',
-- how much columns has the above template
-- this is used to create the last line with some merged-columns
-- with your tags
['template_event_brief_line_columns'] = '5',
-- Template used as header to briefly tell something about an event
-- As default: [[Template:Cronos event brief line/Head]]
['template_event_brief_line_head'] = 'Cronos event brief line/Head',
-- Chips stylesheet
-- Default to [[Template:Cronos event/chips.css]]
['chips_stylesheet'] = 'Cronos event/chips.css',
-- Default Category
-- See https://phabricator.wikimedia.org/T254586
['default_category'] = 'default',
}
-- list of all 'MediaWiki:Sunday' etc.
local MW_WEEK_NAMES = {
'Sunday',
'Monday',
'Thursday',
'Wednesday',
'Thursday',
'Friday',
'Saturday',
}
-- list of all 'MediaWiki:January' etc.
local MW_MONTH_NAMES = {
'January',
'February',
'March',
'April',
'May',
'June',
'July',
'August',
'September',
'October',
'November',
'December',
}
-- this Lua package
local p = {}
-- constants
local SECONDS_IN_DAY = 86400;
local SECONDS_IN_MONTH = SECONDS_IN_DAY * 28 -- Not really :^) it's a lazy hack
-- template used for the heading of the calendar
-- default to [[Template:Cronos month/Head]]
local HEADING_TEMPLATE = 'Cronos month/Head'
-- template used to save/display data about a CronosEvent
-- default to [[Template:Cronos event]]
local EVENT_TEMPLATE = 'Cronos event'
-- this Lua pattern distinguish single events, and the known template arguments
-- name of the [[Template:Cronos event]]
local EVENT_PATTERN = '{{ *[Cc]ronos event'
--- Name of some expected {{Cronos event}} template arguments
-- title: Event title
-- when: Event start time
-- tags: Space separated list of Event tags
local EVENT_ARG_ID = 'id'
local EVENT_ARG_TITLE = 'title'
local EVENT_ARG_WHEN = 'when'
local EVENT_ARG_TAGS = 'tags'
local EVENT_ARG_URL = 'url'
local EVENT_ARG_WHEN_END = 'end'
local EVENT_ARG_CATEGORY = 'category'
local EVENT_ARG_WHERE = 'where'
local EVENT_ARG_ABSTRACT = 'abstract'
-- external identifiers
-- https://phabricator.wikimedia.org/T268213
local EVENT_ARG_ID_METAFR = 'id meta-fr'
local EVENT_ARG_ID_WMF_PHAB = 'id wmf-phab'
-- all the damn external ids
local EVENT_ARG_EXTERNAL_IDS = {
EVENT_ARG_ID_METAFR,
EVENT_ARG_ID_WMF_PHAB,
}
-- current date in raw format
-- to speed up the EventDay:isToday()
local TODAY_RAW = os.date( '%Y-%m-%d' )
---
-- BASE FUNCTIONS
---
---
-- Check if a value is inside an array
--
-- @param string needle String to be searched
-- @param string haystack Array of strings
-- @return boolean True if the needle is in the string
local function inArray( needle, haystack )
-- search the needle in the haystack
for i, v in pairs( haystack ) do
if needle == v then
return true
end
end
return false
end
---
-- Array merge
--
-- Merge the second array in the first one
--
-- @param table first
-- @param table second
local function arrayMerge( first, second )
for k, v in pairs( second ) do
first[ #first + 1 ] = v
end
end
---
-- Array replace
--
-- This somehow emulates the PHP array_replace()
--
-- @param table..
-- @return table
--
local function arrayReplace( ... )
-- final table
local complete = {}
-- table with all the arguments
local args = { ... }
-- for each table
for _, arg in pairs( args ) do
-- merge all the consecutive tables in the complete one
for k, v in pairs( arg ) do
-- the most left value takes precedence
complete[ k ] = v
end
end
return complete
end
--- Parse a single line of a template argument in wikitext
--
-- @param string s Wikitext
-- @param string|nil arg Argument name
local function parseTemplateArgument( s, arg )
-- in Lua the '-' is a special character and should be escaped with '%'
arg = string.gsub( arg, '%-', '%%-' )
local pattern = '|%s*' .. arg .. '%s*=(.-)\n'
local capture = mw.ustring.match( s, pattern )
if capture ~= nil then
capture = mw.text.trim( capture )
end
return capture
end
--- Parse a single heading from wikitext
--
-- @param string s Wikitext
local function parseSectionHeading( s )
local pattern = '(.+)=%s*\n'
local capture = mw.ustring.match( s, pattern )
if capture ~= nil then
capture = mw.text.trim( capture, " =" )
end
return capture
end
---
-- Remove empty tags from an array of Tags
--
-- If nothing interesting was found, it returns nil.
--
-- @param tags table Array of strings
-- @return table Array of strings without empty elements or nil if empty
local function arrayCleanTags( tags )
local cleanArray = {}
local founds = 0
-- for each tags
for i, v in pairs( tags ) do
-- trim
v = mw.text.trim( v )
-- no value no party
if v ~= '' then
founds = founds + 1
cleanArray[ founds ] = v
end
end
-- no founds no party
if founds == 0 then
cleanArray = nil
end
return cleanArray
end
---
-- Remove empty elements from a table
--
-- @param args table
-- @return table
local function cleanWikitextArguments( args )
local result = {}
-- clean each argument
for k, v in pairs( args ) do
-- eventually trim
if type( v ) == 'string' then
v = mw.text.trim( v )
-- promote to nil
if v == '' then
v = nil
end
end
if v then
result[ k ] = v
end
end
return result
end
--- Parse some comma separated words
--
-- @param string s Comma-separated tags
-- @return table Array of tags (without empty tags etc. or nil of no tag was OK)
local function parseTags( s )
return arrayCleanTags( mw.text.split( s or '', ',', true ) )
end
---
-- Check if a string starts with something
--
-- @param string haystack
-- @param string needle
-- @return boolean
local function stringStartsWith( haystack, needle )
local len = mw.ustring.len( needle )
local firstPiece = mw.ustring.sub( haystack, 1, len )
return needle == firstPiece
end
---
-- Check if something is a complete URL
--
-- @param string s Your string to be checked
-- @return boolean
local function isURL( s )
return stringStartsWith( s, 'https://' )
or stringStartsWith( s, 'http://' )
or stringStartsWith( s, '//' ) -- [[rfc:3986]]
end
---
-- Adapt something to an URL
--
-- @param rawTitle Your page title or a full URL
-- @return A full URL
local function title2url( rawTitle )
local url = nil
-- check if this is already a good URL
if isURL( rawTitle ) then
url = rawTitle
else
-- check if it's a valid MediaWiki page title
local title = mw.title.new( rawTitle )
if title ~= nil then
url = title:fullUrl()
end
end
return url
end
---
-- Parse a raw date / datetime / time string and obtain a Lua date object
--
-- It accepts:
-- yyyy-mm-dd
-- yyyy-mm-dd HH:ii
-- HH:ii
-- Unix timestamp
--
-- @param rawDateTime Raw date time in string format as 'YYYY-MNN-DD HH:ii' or just 'HH:ii' or Unix timestamp
-- @param date Optional object date useful to enrich the rawDateTime if it only consists in a time
-- @return Date object
local function parseDateOrTime( rawDateTime, date )
local result
local y, m, d, h, i
-- maybe the result is a timestamp
if type( rawDateTime ) == 'number' then
return os.date( '*t', rawDateTime )
end
-- try to parse a complete date
y, m, d, h, i = string.match(
rawDateTime,
'(%d%d%d%d)%-(%d%d)%-(%d%d) +(%d%d):(%d%d)'
)
-- otherwise try to parse just a time and inherit the date
if not y then
y = date.year
m = date.month
d = date.day
h, i = string.match( rawDateTime, '(%d%d):(%d%d)' )
end
-- only return the time object if we was able to parse something
if h then
result = os.time{ year=y, month=m, day=d, hour=h, min=i }
result = os.date( '*t', result )
end
return result
end
---
-- Render whatever as a time
--
-- If the input is a Unix timestamp, return 'HH:MM'
-- If the input is a complete date, return just the 'HH:MM'
-- If the input is a string, return a string.
--
-- @param rawTime Your time that can be a numeric timestamp or a 'HH:MM' string
-- @return string|nil
local function renderTime( rawTime )
local time
-- check the input type
local type = type( rawTime )
if type == 'number' then
-- convert a unix timestamp to 'HH:MM'
time = os.date( '%H:%M', rawTime )
elseif type == 'table' then
-- if it's a Lua date object, prints a time
time = rawTime.hour .. ':' .. rawTime.min
else
-- this may be an 'HH:MM' or 'YYYY-mm-dd HH:ii:ss'
hhii = string.match( rawTime, '%d%d%d%d%-%d%d%-%d%d +(%d%d:%d%d)' )
if hhii ~= nil then
time = hhii
else
-- just return the raw string otherwise
time = rawTime
end
end
return time
end
---
-- Index a single CronosEvent into a table of CronosEvent(s) indexed by raw date
--
-- It does not return anything.
-- It changes your 'groups' directly.
--
-- @param table Table of CronosEvent(s) indexed by raw date
-- @param table CronosEvent
local function indexCronosEventByDate( events, event )
local rawDate = event.day:getRawDate()
-- eventually create the group if it does not exist
if events[ rawDate ] == nil then
events[ rawDate ] = {}
end
-- append the CronosEvent in the group
local group = events[ rawDate ]
group[ #group + 1 ] = event
end
---
-- Index some CronosEvent(s) into a table of CronosEvent(s) indexed by raw date
--
-- It does not return anything.
-- It changes your 'groups' directly.
--
-- @param events Array of CronosEvent(s)
-- @return Associative array of CronosEvent(s) indexed by 'yyyy-mm-dd'
local function indexCronosEventsByDate( groups, events )
for i, event in pairs( events ) do
indexCronosEventByDate( groups, event )
end
end
---
-- Group a collection of CronosEvent by date
--
-- @param events Array of CronosEvent(s)
-- @return Associative array of CronosEvent(s) indexed by 'yyyy-mm-dd'
local function groupCronosEventsByDate( events )
local groups = {}
indexCronosEventsByDate( groups, events )
return groups
end
---
-- Enqueue a stylesheet
--
-- It creates a valid and generic <templatestyles src=""> tag.
--
-- See https://www.mediawiki.org/wiki/Extension:TemplateStyles
--
-- @param title
-- @return string
local function templateStyles( title )
return mw.getCurrentFrame():extensionTag{
name = 'templatestyles',
args = { src = title },
}
end
---
-- Merge some frame arguments
--
-- @return table
local function frameArguments( frame )
local argsParent = cleanWikitextArguments( frame:getParent().args or {} )
local args = cleanWikitextArguments( frame.args )
return arrayReplace( args, argsParent )
end
---
-- Get a complete configuration inheriting default options
--
-- @param table|nil config
-- @return table
local function getConfig( config )
return arrayReplace( DEFAULT_CONFIG, config )
end
---
-- Get a table of week names
--
-- @param boolean short Set to true to have short week names
-- @return table
local function weekNamesLocalized( short )
-- localized week names starting from Sunday
local weekNames = {}
for k, v in pairs( MW_WEEK_NAMES ) do
-- localize the week name thanks to MediaWiki messages ([[MediaWiki:Monday]] etc.)
v = mw.message.new( v ):plain()
-- eventually short the week names
if short then
v = mw.ustring.sub( v, 1, 3 )
end
weekNames[ k ] = v
end
return weekNames
end
---
-- Get a list of week names starting from monday or sunday
--
-- @param table week_names
-- @param boolean from_monday
-- @return table
local function weekNamesFrom( weekNames, fromMonday )
local ordered = {}
local i = fromMonday and 1 or 0
local days = 1
while days < 8 do
ordered[ days ] = weekNames[ i % 7 + 1 ]
i = i + 1
days = days + 1
end
return ordered
end
---
-- Giving a number, print some empty cells
--
-- @param int cells
-- @return string
local function printEmptyColumns( cells )
s = ''
while cells > 0 do
s = s .. '\n|'
cells = cells - 1
end
return s
end
---
-- Compare two events by time
--
-- This method is useful for table.sort().
--
-- @param one Event
-- @param two Event
--
local function compareTwoEventsByTime( one, two )
return one:getStartDateTimestamp()
< two:getStartDateTimestamp()
end
---
-- CLASSES
---
---
-- Simple class describing a single CronosEvent
---
local CronosEvent = {}
CronosEvent.__index = CronosEvent
---
-- Simple class collecting events
--
local CronosDay = {}
CronosDay.__index = CronosDay
---
-- Simple class organizing the Calendar
--
local CronosCalendarContext = {}
CronosCalendarContext.__index = CronosCalendarContext
---
-- Simple class organizing a CronosEvent's Category
--
local CronosCategory = {}
CronosCategory.__index = CronosCategory
-- all the Cronos Event(s) Categories indexed by UID
CronosCategory.all = {}
---
-- CLASS METHODS
---
---
-- Construct a clean CronosEvent object
--
-- @param event table Raw Event object to be incapsulated into a CronosEvent class.
-- Some of the supported arguments:
--
-- day CronosDay object
-- when Event start time hh:ii (or date and time yyyy-mm-dd hh:ii, or numeric UNIX timestamp)
-- whenEnd Event end time hh:ii (or date and time yyyy-mm-dd hh:ii, or numeric UNIX timestamp)
-- category Category identifier
-- tags Table of Event tags
-- url External URL
-- editurl Edit URL (or default to Day's title)
-- context Calendar context
--
-- @return CronosEvent
function CronosEvent:new( event )
setmetatable( event, CronosEvent )
-- the edit URL sometime can be guessed from the daily page title
event.editurl = event.editurl or event.day.title
return event
end
--- Construct a CronosEvent parsing a block of wikitext
--
-- @param table day The day this event belongs to
-- @param string wikitext Wikitext that contains part of the arguments
-- @param string sectionTitle Title of the current section
-- @return table|nil
-- @return CronosEvent
function CronosEvent:createParsingDayBlock( day, wikitext, sectionTitle )
local event = nil
-- try to parse the Event title
local title = parseTemplateArgument( wikitext, EVENT_ARG_TITLE ) or sectionTitle
-- no title no party
if title ~= nil then
-- try to parse the Event start time
local when = parseTemplateArgument( wikitext, EVENT_ARG_WHEN )
-- no Event time no party
if when ~= nil then
-- allow empty arguments
local args = {}
-- try to parse the Event tags
local tags = nil
local tagsLine = parseTemplateArgument( wikitext, EVENT_ARG_TAGS )
if tagsLine ~= nil then
tags = parseTags( tagsLine )
end
-- try to parse the Event URL
-- https://phabricator.wikimedia.org/T254160
args.url = parseTemplateArgument( wikitext, EVENT_ARG_URL )
-- try to parse the Event end date
-- https://phabricator.wikimedia.org/T254333
args.whenEnd = parseTemplateArgument( wikitext, EVENT_ARG_WHEN_END )
-- try to parse the category
-- https://phabricator.wikimedia.org/T254586
args.category = parseTemplateArgument( wikitext, EVENT_ARG_CATEGORY )
-- try to parse the location
args.where = parseTemplateArgument( wikitext, EVENT_ARG_WHERE )
-- try to parse the abstract
args.abstract = parseTemplateArgument( wikitext, EVENT_ARG_ABSTRACT )
-- create the Event object
args.day = day
args.title = title
args.when = when
args.tags = tags
-- mark this Event as local
-- this distinguish a CronosEvent from an imported one
-- https://phabricator.wikimedia.org/T268213
args.source = 'local'
-- check federated identifiers
-- https://phabricator.wikimedia.org/T268213
args.ids = {
[ 'local' ] = parseTemplateArgument( wikitext, EVENT_ARG_ID ),
[ 'meta-fr' ] = parseTemplateArgument( wikitext, EVENT_ARG_ID_METAFR ),
[ 'wmf-phab' ] = parseTemplateArgument( wikitext, EVENT_ARG_ID_WMF_PHAB ),
}
-- create an Event with all the needed arguments
event = CronosEvent:new( args )
end
end
return event
end
---
-- Construct a CronosEvent from a French "Events calendar" data block
--
-- See https://meta.wikimedia.org/wiki/Events_calendar/events.json
-- See https://phabricator.wikimedia.org/T254264
--
-- @param data Single French Calendar event data block
-- @return CronosEvent
function CronosEvent:createFromFrenchData( calendarContext, data )
-- Adapt French arguments
-- note: their dates are not strings, but numeric UNIX timestamps
-- initialize a dummy CronosDay for this event
local day = CronosDay:createFromTimestamp( calendarContext, data.dtstart )
-- external url
local url = data.link and title2url( data.link )
-- the French calendar has not a permalink for each event
-- so just link to the home
local editurl = DEFAULT_CONFIG[ 'french_dataset_url' ]
-- return the CronosEvent object
return CronosEvent:new( {
day = day,
title = data.title,
when = data.dtstart,
whenEnd = data.dtend,
tags = data.tags,
where = data.location,
url = url,
editurl = editurl,
-- https://phabricator.wikimedia.org/T268213
ids = {
[ 'meta-fr' ] = data.id,
},
source = 'meta-fr',
} )
end
---
-- Get the start date as Lua date object
--
-- @return table Lua date object
function CronosEvent:getStartDate()
return parseDateOrTime( self.when, self.day:getDate() )
end
---
-- Get the end date
--
-- If the end date is not specified, the default is the start date.
--
-- @return table Lua date object
function CronosEvent:getEndDate()
return parseDateOrTime( self.whenEnd or self.when, self.day:getDate() )
end
---
-- Get the start date as Unix timestamp
--
-- @return int Unix timestamp
function CronosEvent:getStartDateTimestamp()
return os.time( self:getStartDate() )
end
---
-- Get the end date as Unix timestamp
--
-- @return int Unix timestamp
function CronosEvent:getEndDateTimestamp()
return os.time( self:getEndDate() )
end
---
-- Check if this Cronos event has a specific tag name
--
-- @param string tag Tag name
-- @See https://phabricator.wikimedia.org/T253074
-- @return boolean
--
function CronosEvent:hasTag( tag )
return self.tags ~= nil and inArray( tag, self.tags )
end
---
-- Check if it exists an external identifier
--
-- @return boolean
--
function CronosEvent:hasExternalId()
-- stop when reaching the first external id
for i, argId in pairs( EVENT_ARG_EXTERNAL_IDS ) do
if self.ids[ argId ] ~= nil then
return true
end
end
return false
end
---
-- Check if this CronosEvent is mirrored somewhere
--
-- @See https://phabricator.wikimedia.org/T268213
-- @return boolean
--
function CronosEvent:isMirror()
return self.source == 'local' and self:hasExternalId()
end
---
-- Print the where location
--
-- @return string
function CronosEvent:getHumanWhere()
local where = self.where
if type( where ) == 'table' then
where = mw.text.listToText( self.where )
end
return where
end
---
-- Check if this Cronos event has at least one of the specified Tags
--
-- @See https://phabricator.wikimedia.org/T253074
--
-- @param object tags Array of tags
-- @return boolean True if one of the tags was associated to this Event
function CronosEvent:hasOneTag( tags )
-- check if this Event as at least one of these Tags
for i, tag in pairs( tags ) do
if( self:hasTag( tag ) ) then
return true
end
end
-- not found
return false
end
---
-- Return a table with a CronosEvent for each day of duration
--
-- When an event last
--
-- @return table
--
function CronosEvent:createEventsLastingMultipleDays()
local events = {}
-- if this has an original Event, it is a child
-- expanding this will break everything
if self.original == nil then
local start = self:getStartDateTimestamp()
local stop = self:getEndDateTimestamp()
local startHour = renderTime( start )
local lastRawDate = self.day:getRawDate()
local day = self.day
local child
-- well, the event may have no sense
if start and stop then
-- next day
day = day:getNextDay()
-- for each following day
while day:getTimestamp() <= stop do
-- produce a child
child = {}
for k, v in pairs( self ) do
child[ k ] = v
end
-- set this new day but set a time (without full-date)
child.day = day
child.when = startHour
-- remember my father
child.original = self
-- append this event
events[ #events + 1 ] = CronosEvent:new( child )
-- next day please
day = day:getNextDay()
end
end
end
return events
end
---
-- Expand extra events lasting multiple days
--
function CronosEvent:expandEventsLastingMultipleDays()
-- execute only once
if self._expandedEventsLastingMultipleDays == nil then
-- remember we have executed this
self._expandedEventsLastingMultipleDays = self:createEventsLastingMultipleDays()
-- publish each Event in its day
for i, event in pairs( self._expandedEventsLastingMultipleDays ) do
event:addToDay()
end
end
return self._expandedEventsLastingMultipleDays
end
---
-- Get the duration of this Event in days
--
-- @return int
function CronosEvent:getDurationDays()
return #self:expandEventsLastingMultipleDays()
end
---
-- Get a truncated title with eventually a tooltip
--
-- @return string
function CronosEvent:truncatedTitle()
-- eventually truncate (but put the complete title inside a tooltip)
local title = self.title
local title_is_pure_text = mw.ustring.find( title, '[', 1, true ) == nil
local maxlen = tonumber( self.day.context.config.max_title_len )
if mw.ustring.len( title ) > maxlen and title_is_pure_text then
local truncated = mw.ustring.sub( title, 1, maxlen ) .. '…'
title = tostring( mw.html.create( 'span' )
:attr( 'title', title )
:wikitext( truncated ) )
end
return title
end
---
-- Parameters useful for some Event templates
--
-- @return table
--
function CronosEvent:briefTemplateParameters()
return {
['yyyy'] = self.day.yyyy,
['mm'] = self.day.mm,
['dd'] = self.day.dd,
['title'] = self:truncatedTitle(),
['when'] = renderTime( self.when ),
['end'] = self.whenEnd and renderTime( self.whenEnd ),
['url'] = self.url and title2url( self.url ),
['editurl'] = self.editurl,
['category'] = self.category,
['where'] = self:getHumanWhere(),
}
end
---
-- Generate a brief abstract of this specific event
--
function CronosEvent:renderBriefCell( frame )
frame = frame or mw.getCurrentFrame()
-- this as default is [[Template:Cronos event brief]]
return frame:expandTemplate{
title = DEFAULT_CONFIG[ 'template_event_brief' ],
args = self:briefTemplateParameters(),
}
end
---
-- Generate a brief line for this specific event
--
-- @return string
function CronosEvent:renderBriefLine()
-- this as default is [[Template:Cronos event brief line]]
return mw.getCurrentFrame():expandTemplate{
title = DEFAULT_CONFIG[ 'template_event_brief_line' ],
args = self:briefTemplateParameters(),
}
end
---
-- Get some information about the Category of this CronosEvent (if any)
--
-- It always return a Category. Always
--
-- See https://phabricator.wikimedia.org/T254586
--
-- @return table CronosCategory
function CronosEvent:getCategory()
-- if the argument is not present assume a default one
return CronosCategory.createFromUID( self.category )
end
---
-- Shortcut to add this CronosEvent to the related CronosDay
--
-- @param table CronosEvent
function CronosEvent:addToDay()
self.day:addEvent( self )
end
---
-- Construct an empty CronosDay table
--
-- This entity was created to describe a 'Meta:Something/yyyy-mm-dd' page
-- but it also describe a generic date.
--
-- @param table context Optional Calendar Context
function CronosDay:new( context )
-- instantiate a CronosDay object
local day = {}
setmetatable( day, CronosDay )
-- keep the configuration as dependency injection
day.context = context or CronosCalendarContext:new()
-- initialize events
day.events = {}
return day
end
---
-- Construct a CronosDay table
--
-- This entity was created to describe a 'Meta:Something/yyyy-mm-dd' page
-- but it also describe a generic date.
--
-- @param date Full date formatted in 'yyyy-mm-dd'
-- @param table Calendar context
function CronosDay:createFromRawDate( date, calendarContext )
-- initialize a new day with this configuration
local day = CronosDay:new( calendarContext )
-- remember the raw date formatted in yyyy-mm-dd
day.rawdate = date
-- extract single date members
local dmy = mw.text.split( date, '-', true )
day.yyyy = dmy[ 1 ]
day.mm = dmy[ 2 ]
day.dd = dmy[ 3 ]
day.m = tonumber( day.mm )
day.timestamp = os.time( day:getDate() )
day.week = os.date( '%w', day.timestamp ) -- sunday is 0
day.title = calendarContext.config.event_prefix .. date
-- assure to recycle an existing day
return calendarContext:getUniqueDay( day )
end
---
-- Create a Cronos Day from a timestamp
--
-- @return CronosDay
--
function CronosDay:createFromTimestamp( calendarContext, timestamp )
local day = CronosDay:new( calendarContext )
-- basic information
day.yyyy = os.date( '%Y', timestamp ) -- full year
day.mm = os.date( '%m', timestamp ) -- full month 01-12
day.dd = os.date( '%d', timestamp ) -- full day 01-31
day.week = os.date( '%w', timestamp ) -- sunday is 0
day.timestamp = timestamp
-- assure to recycle an existing day
return calendarContext:getUniqueDay( day )
end
---
-- Get a Lua date object indicating this date
--
-- @return object Luda date object
function CronosDay:getDate()
return {
year = self.yyyy,
month = self.mm,
day = self.dd,
}
end
---
-- Get the date in format 'yyyy-mm-dd'
--
-- @return string
--
function CronosDay:getRawDate()
-- eventually build it
if self.rawdate == nil then
self.rawdate = self.yyyy .. '-' .. self.mm .. '-' .. self.dd
end
return self.rawdate
end
---
-- Get the date as Unix timestamp
--
-- @return integer
--
function CronosDay:getTimestamp()
return self.timestamp
end
---
-- Check if this CronosDay is today!
--
-- @return boolean
--
function CronosDay:isToday()
return self:getRawDate() == TODAY_RAW
end
---
-- Get the wikitext of this event page
--
-- @return string|nil
function CronosDay:wikitext()
local content = nil
-- no title, no party
if self.title then
content = mw.title.new( self.title )
:getContent()
end
return content
end
--- Add a CronosEvent to the collection
--
-- @param table CronosEvent
function CronosDay:addEvent( event )
self.events[ #self.events + 1 ] = event
end
---
-- Parse this day page looking for events
--
function CronosDay:parse()
-- save resources and parse only once
if self._parsed == nil then
-- this should be not initialized to do not overwrite some events
-- self.events = {}
-- shortcut
local context = self.context
-- requested Tags
local filterTags = context.filters.tags or nil
-- try to parse local events
local wikitext = self:wikitext()
if wikitext ~= nil then
-- split the page in sections
local sections = mw.text.split( wikitext, "\n=" )
for _, section in pairs( sections ) do
-- parse the section title
local sectionTitle = parseSectionHeading( section )
-- split the section content in event blocks
local blocks = mw.text.split( section, EVENT_PATTERN )
for i, block in pairs( blocks ) do
-- parse each Event template
local event = CronosEvent:createParsingDayBlock( self, block, sectionTitle )
if event ~= nil then
-- check if the Event matches filtersthe event must match filters
-- and should not be a local mirror (to avoid duplicates)
if context:canAccomodate( event ) and not event:isMirror() then
event:addToDay()
end
end
end
end
end
-- find the French Calendar events related to this day
local todayFrenchEvents = p._getFrenchCalendarEventsInDate( self.context, self:getRawDate() )
if todayFrenchEvents ~= nil then
for i, event in pairs( todayFrenchEvents ) do
-- check if the Event matches filtersthe event must match filters
-- and should not be a local mirror (to avoid duplicates)
if context:canAccomodate( event ) and not event:isMirror() then
event:addToDay()
end
end
end
-- mark this day as parsed
self._parsed = true
end
end
---
-- Get the CronosEvent(s) related to this day (if any)
--
-- @return CronosEvent[]
--
function CronosDay:getEvents()
-- parse this Event
-- this is safe to be called twice
-- this populates self.events
self:parse()
-- expand Event(s) lasting multiple days
-- this is safe to be called twice
-- this populates self.events
self:expandEventsLastingMultipleDays()
-- table of CronosEvent(s)
return self.events
end
---
-- Get the CronosEvent(s) related to this day (if any) ordered by time
--
-- @return CronosEvent[]
--
function CronosDay:getEventsSortedByTime()
local events = self:getEvents()
table.sort( events, compareTwoEventsByTime )
return events
end
---
-- Get the CronosEvent identified with the provided source and id
--
-- If in this day there is not such event, it returns false.
--
-- @param string source Federation source identifier e.g. 'local' for 'this wiki'
-- @param string id
-- @return CronosEvent|false
--
function CronosDay:getEventBySourceId( source, id )
-- find it by id
for i, event in pairs( self:getEvents() ) do
if event.ids[ source ] == id then
return event
end
end
return false
end
---
-- Shortcut to expand events lasting multiple days
--
function CronosDay:expandEventsLastingMultipleDays()
for i, event in pairs( self.events ) do
event:expandEventsLastingMultipleDays()
end
end
---
-- Generate a brief header of this day
--
-- @return string
function CronosDay:renderBrief( frame )
frame = frame or mw.getCurrentFrame()
return frame:expandTemplate{
title = DEFAULT_CONFIG[ 'template_day_brief' ],
args = { self.yyyy, self.mm, self.dd },
}
end
---
-- Generate a list of all the events in this day for a calendar cell
--
-- This builds the event list in a Calendar cell
--
-- @return string
function CronosDay:renderEventsForCell( frame )
local s = ''
frame = frame or mw.getCurrentFrame()
for _, event in pairs( self:getEventsSortedByTime() ) do
s = s .. "\n" .. event:renderBriefCell( frame )
end
return s
end
---
-- Generate a calendar cell with a list of all the events in this day
--
-- @return string
function CronosDay:renderCalendarCell( frame )
frame = frame or mw.getCurrentFrame()
return self:renderBrief( frame )
.. self:renderEventsForCell( frame )
end
---
-- Get the next day
--
-- @return CronosDay
--
function CronosDay:getNextDay()
return self.context:getDayFromTimestamp( self:getTimestamp() + SECONDS_IN_DAY )
end
---
-- Get the previous day
--
-- @return CronosDay
--
function CronosDay:getPreviusDay()
return self.context:getDayFromTimestamp( self:getTimestamp() - SECONDS_IN_DAY )
end
---
-- Preload some days before this one
--
-- @param integer days Number of days to be preloaded
--
function CronosDay:preloadPreviusDays( days )
local day
local i = 1
while i < days do
day = CronosDay:getPreviusDay()
-- this will trigger the cache
day:getEvents()
i = i + 1
end
end
---
-- Constructor for a CronosEvent's Category
--
-- @param category Category arguments
-- Some of them:
-- uid: string Category identifier
-- name: string Category name
-- filename: string Category filename
--
-- See https://phabricator.wikimedia.org/T254586
--
function CronosCategory:new( category )
setmetatable( category, CronosCategory )
return category
end
---
-- Construct/find a CronosEvent's Category
--
-- It assures that you obtain the same category each time you call this
--
-- @param uid string Category UID like 'com' for 'community' or nil for the default
--
-- See https://phabricator.wikimedia.org/T254586
--
function CronosCategory.createFromUID( uid )
local defaultUID = DEFAULT_CONFIG.default_category
-- if the UID is missing assume the default category
uid = uid or defaultUID
-- check if this category was already generated
if not CronosCategory.all[ uid ] then
-- check if this category is known
local categoryData = CRONOS_CATEGORIES[ uid ]
if categoryData then
-- instantiate this category for the first time
CronosCategory.all[ uid ] = CronosCategory:new( categoryData )
else
-- in this case the Category is not known
-- eventually throw an error if you have not the default
if uid == defaultUID then
error( 'whaat? missing default category with UID: ' .. defaultUID )
end
-- try with the default one
-- the second parameter avoids any recursion
-- that may happen if someone declares by mistake an
-- unexisting default category
return CronosCategory.createFromUID( defaultUID )
end
end
-- just return the already existing Category instance
return CronosCategory.all[ uid ]
end
---
-- Get the Cronos Event Category icon as wikitext
--
-- See https://phabricator.wikimedia.org/T254586
--
-- @param args Arguments
-- Accepted arguments:
-- size: string Size in pixels
-- link: string Link URL
-- @return string
function CronosCategory:getIconWikitext( args )
local link = args and args.link or ''
local size = args and args.size or '18px'
return '[[' .. self.filename .. '|' .. size .. '|link=' .. link .. '|' .. self.name .. ']]'
end
---
-- Create a Calendar context
--
-- It returns some useful information acting as a middleware for the data needed
-- by all the available visualizations (calendar visualization and list).
--
-- @param config Optional complete configuration
-- @return table
function CronosCalendarContext:new( config )
-- instantiate a CronosCalendarContext object
local calendarContext = {}
setmetatable( calendarContext, CronosCalendarContext )
-- expose the configuration
calendarContext.config = getConfig( config or {} )
-- localized week names starting from Sunday
calendarContext.weekNames = weekNamesLocalized( calendarContext.config.short_weekname ~= '0' )
-- number of week lines to be displayed
calendarContext.weeks = tonumber( calendarContext.config.weeks )
-- maximum number of days to be displayed
-- do not confuse with the 'dayList' attribute
calendarContext.days = calendarContext.weeks * 7
-- start from Monday or from Sunday
calendarContext.startFromMonday = calendarContext.config.start_from_monday == '1'
-- start the week names from the correct day (Monday or Sunday)
calendarContext.weekNamesFrom = weekNamesFrom( calendarContext.weekNames, calendarContext.startFromMonday )
-- the user may want to filter by some factors
calendarContext.filters = {
tags = nil,
}
-- eventually filter by some Tags
if calendarContext.config.tags ~= nil then
calendarContext.filters.tags = parseTags( calendarContext.config.tags )
end
-- shift the current time month forward or backward by some months
calendarContext.monthShift = tonumber( calendarContext.config.month_shift )
-- unix time starting from now (eventually shifting)
calendarContext.startWeekTime = os.time() + calendarContext.monthShift * SECONDS_IN_MONTH
-- build the format date
-- see the documentation of os.date() about UTC or local time
local format_prefix = ''
if calendarContext.config.utc == '1' then
format_prefix = '!'
end
-- os.date() formats to obtain a date table or a date in Y-m-d
local format_date = format_prefix .. '*t'
local format_ymd = format_prefix .. '%F'
-- today's date
calendarContext.today = os.date( format_date, calendarContext.startWeekTime )
-- prepare week indexes
calendarContext.weekStart = 1
calendarContext.weekEnd = 8
if calendarContext.startFromMonday then
calendarContext.weekStart = calendarContext.weekStart + 1
calendarContext.weekEnd = calendarContext.weekEnd + 1
end
-- how much days since the start of this week
-- note that the week day starts from sunday = 1, monday = 2, etc.
-- so as default if it's Monday should be considered "1 days" since start of week
-- so as default if it's Sunday should be considered "0 days" since start of week
calendarContext.daysSinceStartWeek = calendarContext.today.wday - 1
-- eventually consider Monday as start of the week
-- so as default if it's Monday should be considered "0 days" since start of week
-- so as default if it's Sunday should be considered "6 days" since start of week
if calendarContext.startFromMonday then
calendarContext.daysSinceStartWeek = calendarContext.daysSinceStartWeek - 1
if calendarContext.daysSinceStartWeek == -1 then
calendarContext.daysSinceStartWeek = 6
end
end
-- CronosDay(s) indexed by raw date
calendarContext.dayByDate = {}
return calendarContext
end
---
-- Check if the current context "can accomodate" the current Event
--
-- In short it checks if the Event matches current filters (Tags etc.)
--
-- @param event table Event
-- @return boolean
--
function CronosCalendarContext:canAccomodate( event )
-- eventually filter by Tag
-- https://phabricator.wikimedia.org/T253074
local tags = self.filters.tags
if tags ~= nil and not event:hasOneTag( tags ) then
return false
end
-- as default the event matches the context
return true
end
---
-- Get an unique day
--
-- This function assures that your day is unique for your calendar context.
-- In this way you do not create multiple different days rappresenting the same day.
--
-- @param day CronosDay
-- @return CronosDay
function CronosCalendarContext:getUniqueDay( day )
-- the date in 'yyyy-mm-dd'
local rawDate = day:getRawDate()
-- eventually initialize the singleton
if self.dayByDate[ rawDate ] == nil then
self.dayByDate[ rawDate ] = day
end
-- returns the singleton
return self.dayByDate[ rawDate ]
end
---
-- Shortcut to get a CronosDay from a raw date
--
-- @param string rawDate
-- @return CronosDay
function CronosCalendarContext:getDayFromRawDate( rawDate )
return CronosDay:createFromRawDate( rawDate, self )
end
---
-- Shortcut to get a CronosDay from a Unix timestamp
--
-- @param integer timestamp
-- @return CronosDay
function CronosCalendarContext:getDayFromTimestamp( timestamp )
return CronosDay:createFromTimestamp( self, timestamp )
end
---
-- Get a table of empty days starting from this week
--
-- @return table Table of CronosDay(s)
function CronosCalendarContext:getDays()
-- list of days to be returned
local dayList = {}
-- calculate the date when this week started
local seconds_since_start_week = self.daysSinceStartWeek * SECONDS_IN_DAY
local time_start_of_week = self.startWeekTime - seconds_since_start_week
-- see the documentation of os.date() about UTC or local time
local format_prefix = ''
if self.config.utc == '1' then
format_prefix = '!'
end
-- os.date() formats to obtain a date table or a date in Y-m-d
local format_ymd = format_prefix .. '%F'
-- generate the list of the days
local i = 0
while i < self.days do
-- prepare the CronosDay object
local event_time = time_start_of_week + i * SECONDS_IN_DAY
local event_ymd = os.date( format_ymd, event_time )
-- add this well-known CronosDay to the list
i = i + 1
dayList[ i ] = self:getDayFromRawDate( event_ymd )
end
return dayList
end
---
-- Read the French Calendar dataset (raw format)
--
-- You should not call this twice without any kind of cache.
--
-- See https://meta.wikimedia.org/wiki/Events_calendar/events.json
-- See https://phabricator.wikimedia.org/T254264
--
-- @return Table of raw events
function p._parseFrenchCalendarDatasetRawContent()
-- load the page
local page = mw.title.new( DEFAULT_CONFIG.french_dataset_page )
-- load the JSON
local content = page:getContent()
-- @TODO: replace with mw.loadJsonData() when will be available (after 18 October 2022)
-- return an array of events
return mw.text.jsonDecode( content )
end
---
-- ENDPOINTS
---
---
-- Convert a title or an URL... to an URL
--
-- This is intended to be called from Wikitext.
--
-- @return string
--
function p.title2url( frame )
local args = frame.args
-- take the first argument from the parent template of from the direct one
local titleOrURL = args[1]
return titleOrURL and title2url( titleOrURL )
end
---
-- Create a CalendarContext object
--
-- This is intended to be called from Lua.
--
-- @param table config Optional configuration
-- @return CalendarContext
function p._createCalendarContext( config )
return CronosCalendarContext:new( config )
end
---
-- Read the French Calendar dataset and convert them to CronosEvent(s)
--
-- This is intended to be called from Lua.
--
-- Note that this method does not have any kind of cache.
--
-- @param table Calendar Context
-- @return Table of events
function p._parseFrenchCalendarCronosEvents( calendarContext )
-- events to be returned
local events = {}
-- increment the expensive function call another time
-- parsing all of this stuff should be considered expensive
-- because they are so much:
-- june 2020: 505 (!)
mw.incrementExpensiveFunctionCount()
-- for each raw event
for i, rawEvent in ipairs( p._parseFrenchCalendarDatasetRawContent() ) do
-- append this structured event
events[ i ] = CronosEvent:createFromFrenchData( calendarContext, rawEvent )
end
return events
end
---
-- Get the French Calendar Dataset grouped by date
--
-- This function uses a cache to allow multiple calls.
--
-- See https://meta.wikimedia.org/wiki/Events_calendar/events.json
-- See https://phabricator.wikimedia.org/T254264
--
-- @param table Calendar context
function p._getFrenchCalendarEventsGroupedByDate( calendarContext )
-- assure that this is initialized only once
if calendarContext.frenchEventsByDate == nil then
calendarContext.frenchEventsByDate = groupCronosEventsByDate( p._parseFrenchCalendarCronosEvents( calendarContext ) )
end
return calendarContext.frenchEventsByDate
end
---
-- Get the French Calendar Dataset of a single day
--
-- If nothing is present it returns nil.
--
-- See https://meta.wikimedia.org/wiki/Events_calendar/events.json
-- See https://phabricator.wikimedia.org/T254264
--
-- @param object Calendar context
-- @param date Raw date formatted as 'yyyy-mm-dd'
-- @return table or nil
function p._getFrenchCalendarEventsInDate( context, rawDate )
local eventsByDate = p._getFrenchCalendarEventsGroupedByDate( context )
return eventsByDate[ rawDate ]
end
---
-- Show some Tag "chips"
--
-- This should be used from Lua and not from wikitext.
--
-- @param table Tags
-- @return string Wikitext
--
function p._tagChips( tags )
-- create a chip for each Tag
local chips = {}
for i, tag in pairs( tags ) do
chips[ i ] = '<span class="cronos-tag-chip">' .. tag .. '</span>'
end
-- include the stylesheet for the Tag chips
local s = templateStyles( DEFAULT_CONFIG[ 'chips_stylesheet' ] )
-- append stylesheet and tags
s = s .. table.concat( chips, ' ' )
return s
end
---
-- Show some Tag "chips" (from wikitext)
--
-- This should be used from wikitext and not from Lua.
--
-- @return string Wikitext
--
function p.tagChips( frame )
-- merge parent args etc.
local args = frameArguments( frame )
-- take the first argument from the parent template of from the direct one
local tagsRaw = args[1] or args.tags
-- parse the tags
local tags = parseTags( tagsRaw )
-- create the chips
return tags and p._tagChips( tags )
end
---
-- Get a CronosCategory object from its UID
--
-- This should be used from Lua and not from wikitext.
--
-- @param string uid Category UID
-- @return CronosCategory
function p._category( uid )
return CronosCategory.createFromUID( uid )
end
---
-- Print a CronosCategory icon from its UID
--
-- This should be used from wikitext and not from Lua.
--
-- @param string uid Category UID
function p.categoryIcon( frame )
local args = frameArguments( frame )
local uid = args[1] or args.uid
return p._category( uid ):getIconWikitext( {
link = args.link,
size = args.size,
} )
end
---
-- Print a CronosCategory name from its UID
--
-- This should be used from wikitext and not from Lua.
--
-- @param string uid Category UID
function p.categoryName( frame )
local args = frameArguments( frame )
local uid = args[1] or args.uid
return p._category( uid ).name
end
---
-- Get an URL to add an event
--
-- This method should be called from Lua and not from wikitext
--
-- @param table args Query string arguments
-- @return string
function p._getAddEventURL( args )
---
-- As default this is something like:
-- 'https://wmch.toolforge.org/cronos/'
local url = DEFAULT_CONFIG[ 'cute_form_url' ]
-- eventually append the query string, if any
if args ~= nil then
local queryString = mw.uri.buildQueryString( args )
if queryString ~= '' then
url = url .. '?' .. queryString
end
end
return url
end
---
-- Get an URL to add an event
--
-- This method should be called from Wikitext and not from Lua
--
-- @param frame
-- @return string
function p.getAddEventURL( frame )
local args = frameArguments( frame )
local query = {}
-- eventually append tags
local tags = args['tags']
if tags ~= nil then
local tagsTable = parseTags( tags )
if #tagsTable > 0 then
query.tags = table.concat( tagsTable, ',' )
end
end
return p._getAddEventURL( query )
end
---
-- Generate the monthly calendar (Lua API)
--
-- This should be used from Lua and not from wikitext.
--
-- @param object userConfig
-- @return string
function p._main( userConfig )
-- output string
local s = ''
-- get everything I need
local calendarContext = CronosCalendarContext:new( userConfig )
-- local configuration
local config = calendarContext.config
-- frame of the current page
local frame = mw.getCurrentFrame()
-- append heading template
s = s .. frame:expandTemplate{ title = HEADING_TEMPLATE }
-- append start of the table
s = s .. '\n{| class="wikitable cronos-month-table"'
-- list of days
local dayList = calendarContext:getDays()
-- generate the table body
local lastMonth = -1
for i, day in pairs( dayList ) do
-- print a newline?
local newline = false
-- domain: 0-6
local column = ( i - 1 ) % 7
if day.m == lastMonth then
-- new row
if column == 0 then
s = s .. '\n|-\n'
end
else
-- print week labels
lastMonth = day.m
-- print a new row if the month is changed
if column > 0 then
s = s .. printEmptyColumns( 7 - column )
s = s .. '\n|-\n'
end
-- add row with month name
local monthName = mw.message.new( MW_MONTH_NAMES[ lastMonth ] ):plain()
-- eventually separate the month name from the above lines
if i ~= 1 then
s = s .. '\n|-\n'
end
s = s .. '\n!colspan="7" class="cronos-month-name"|' .. monthName
s = s .. '\n|-\n'
-- add row with week names
for k, v in pairs( calendarContext.weekNamesFrom ) do
s = s .. '\n!' .. v
end
s = s .. '\n|-\n'
-- eventually print some empty columns
if column > 0 then
s = s .. printEmptyColumns( column )
end
end
-- allow to style in a different way the current day cell
if day:isToday() then
s = s .. '\n|class="cronos-day-current"|'
else
s = s .. '\n|'
end
-- put the day in the cell
s = s .. day:renderCalendarCell( frame )
-- next day
i = i + 1
end
-- eventually cite our filters
if calendarContext.filters.tags ~= nil then
s = s .. '\n|-\n'
s = s .. '\n|colspan="7|' .. p._tagChips( calendarContext.filters.tags )
end
return s .. '\n|}'
end
---
-- Try to obtain all the used Tags
--
-- The Tags will be returned as an array of objects like:
-- {
-- { tag = 'foo', count = 2 },
-- { tag = 'bar', count = 1 },
-- ...
-- }
--
--
-- @see https://phabricator.wikimedia.org/T276350
-- @return table
--
function p._tags( userConfig )
-- output
local tags = {}
-- get everything I need
local calendarContext = CronosCalendarContext:new( userConfig )
-- for each Day
for i, day in pairs( calendarContext:getDays() ) do
-- for each Event
for k, event in pairs( day:getEvents() ) do
-- for each Tag
if event.tags ~= nil then
for j, tag in pairs( event.tags ) do
-- eventually initialize
if tags[ tag ] == nil then
tags[ tag ] = {
tag = tag,
count = 0
}
end
-- increase count
tags[ tag ].count = tags[ tag ].count + 1
end
end
end
end
return tags
end
---
-- Generate a cute Tag Cloud
--
-- [[phab:T276666]]
-- https://phabricator.wikimedia.org/T276666
--
-- This should be used from wikitext and not from Lua.
--
function p.tagCloud( frame )
-- see [[Module:TagCloud]]
local moduleTagCloud = require( 'Module:TagCloud' )
-- parse user config
local userConfig = frameArguments( frame )
-- find the Tags in use in the calendar
local tags = p._tags( userConfig )
-- print the tag cloud
return moduleTagCloud._main( {
tags = tags,
} )
end
---
-- Generate the monthly calendar
--
-- This should be used from Lua and not from wikitext.
--
-- @param object userConfig Optional configuration
-- @return string
--
-- @see https://phabricator.wikimedia.org/T262016
function p._list( userConfig )
-- output string
local s = ''
-- get everything I need
local calendarContext = CronosCalendarContext:new( userConfig )
-- prepared configuration
local config = calendarContext.config
-- frame of the current page
local frame = mw.getCurrentFrame()
-- columns of the table
local columns = config.template_event_brief_line_columns
-- append heading template with stylesheet
s = s .. frame:expandTemplate{ title = HEADING_TEMPLATE }
-- append table header
-- as default [[Template:Event brief line/Head]]
s = s .. '\n'
s = s .. frame:expandTemplate{ title = config.template_event_brief_line_head }
-- days involved in this list
local dayList = calendarContext:getDays()
-- generate the table body
local lastMonth = -1
local todayEvents
for i, day in pairs( dayList ) do
-- events related to this day
todayEvents = day:getEventsSortedByTime()
-- plot every single event of this day
for _, event in pairs( todayEvents ) do
-- new row
s = s .. '\n|-\n'
-- render the event line (some cells)
s = s .. event:renderBriefLine()
end
-- next day
i = i + 1
end
-- eventually cite our filters
if calendarContext.filters.tags ~= nil then
s = s .. '\n|-'
s = s .. '\n|colspan="' .. columns .. '"|' .. p._tagChips( calendarContext.filters.tags )
end
return s .. '\n|}'
end
---
-- Generate the monthly calendar
--
-- This should be used from wikitext and not from Lua.
--
function p.main( frame )
local args = frameArguments( frame )
return p._main( args )
end
---
-- Generate the list calendar
--
-- This should be used from wikitext and not from Lua.
--
-- See https://phabricator.wikimedia.org/T262016
--
function p.list( frame )
local args = frameArguments( frame )
return p._list( args )
end
---
-- Get a single CronosDay object
--
-- This should be used from Lua and not from wikitext.
--
-- You can use this in your Lua console e.g.:
-- =mw.logObject( p._day( '2019-03-25' ) )
--
-- @param date Date formatted as 'yyyy-mm-dd'
-- @param config Optional configuration
--
function p._day( date, config )
-- no date no party
if not date then
error( 'missing date' )
end
-- create a Calendar context
local calendarContext = CronosCalendarContext:new( config )
-- create from the raw date
return CronosDay:createFromRawDate( date, calendarContext )
end
---
-- Print the events of a single CronosDay
--
-- This should be used from wikitext and not from Lua.
--
-- @param object config
--
function p.day( frame )
local args = frameArguments( frame )
local date = frame.args[ 1 ]
local day = p._day( date, frame.args )
return day:renderEvents( frame )
end
---
-- Get a single CronosEvent object from a date and an ID
--
-- The date is very important because there is not a central database,
-- so we start from a day, and then we filter its events by the provided ID
-- to keep this earch reliable and scalable and also to prevent any kind of
-- potential duplicates from external sources.
--
-- This should be used from Lua and not from wikitext.
--
-- You can use this in your Lua console e.g.:
-- = mw.logObject( p._event( {
-- date = '2019-03-25',
-- source = 'local',
-- id = 'cronos-2019-03-25-asd1',
-- ) )
--
-- @param args table Arguents. Some of them:
-- date (string formatted as 'yyyy-mm-dd')
-- source (string like 'local')
-- id (string like '123-asd')
-- @param config Optional configuration
--
function p._event( args )
local date = args.date
-- create a generic day
local day = p._day( args.date, args.config )
-- no source no party
if not args.source then
error( 'missing source' )
end
-- no id no party
if not args.id then
error( 'missing id' )
end
-- return the event or nil
return day:getEventBySourceId( args.source, args.id )
end
---
-- Display a single Event
--
-- The date is very important because there is not a central database,
-- so we start from a day, and then we filter its events by the provided ID
-- to keep this earch reliable and scalable and also to prevent any kind of
-- potential duplicates from external sources.
--
-- This should be used from wikitext and not from Lua.
--
-- Parameters:
-- date (string formatted as 'yyyy-mm-dd')
-- source (string like 'local')
-- id (string like '123-asd')
--
-- @param date Date formatted as 'yyyy-mm-dd'
-- @param id Event identifier as 'cronos-asd'
-- @param config Optional configuration
--
function p.event( frame )
local args = frameArguments( frame )
local date = args.date
local source = args.source
local id = args.id
local config = nil
-- find the event by this id
local event = p._event{
date = date,
source = source,
id = id,
config = config,
}
local tagList = ''
if event.tags ~= nil then
tagList = table.concat( event.tags, ', ' )
end
-- fill the arguments for the template
local args = {
[ EVENT_ARG_TITLE ] = event.title,
[ EVENT_ARG_WHERE ] = event.where,
[ EVENT_ARG_WHEN ] = event.when,
[ EVENT_ARG_WHEN_END ] = event.whenEnd,
[ EVENT_ARG_TAGS ] = tagList,
[ EVENT_ARG_URL ] = event.url,
[ EVENT_ARG_CATEGORY ] = event.category,
[ EVENT_ARG_ABSTRACT ] = event.abstract,
[ EVENT_ARG_ID ] = event.ids.id,
}
-- pass each external identifiers
for i, argId in pairs( EVENT_ARG_EXTERNAL_IDS ) do
args[ argId ] = event.ids[ argId ]
end
return frame:expandTemplate{ title = EVENT_TEMPLATE, args = args }
end
return p