Module:Weather/sandbox explained

local p =

require('strict')

local degree = "°" -- used by addUnitNameslocal minus = "−" -- used by makeRow and makeTablelocal thinSpace = mw.ustring.char(0x2009) -- used by makeCell

local precision, decimals

-- if not emptylocal function ine(var) var = tostring(var) if var

"" then return nil else return var endend

-- Error message handlinglocal message = ""

local function addMessage(newMessage) if ine(message) then message = message .. " " .. newMessage else message = "Notices: " .. newMessage endend

local function monospace(str) return '

' .. str .. ''end

-- Input and output parameterslocal function getFormat(inputParameter, outputParameter, palette, messages) local length, inputUnit, outputUnit, palette, show, cellFormat if inputParameter

nil then error('Please provide the number of values and a unit in the input parameter') else -- Find as many as two digits in the input parameter. length = tonumber(string.match(inputParameter, "(%d%d?)")) if not length then length = 13 addMessage('getFormat has not found a length value in the input parameter; length defaults to "13"') end -- Find C or F, but not both if string.find(inputParameter, "C") and string.find(inputParameter, "F") then error("Input unit must be either C (Celsius) or F (Fahrenheit)") else inputUnit = string.match(inputParameter, "([CF])") or error("Please provide an input unit in the input parameter: F for Fahrenheit or C for Celsius", 0) end if inputUnit

"C" then outputUnit = "F" else outputUnit = "C" end -- Make sure nothing except C, F, numbers, or spaces is in the input parameter. if string.find(inputParameter, "[^CF%d%s]") then addMessage("There are extraneous characters in the " .. monospace("output") .. " parameter.") end end if outputParameter

nil then -- Since there are default values, the module will still generate output with an empty output parameter. addMessage("No output format has been provided in the " .. monospace("output") .. " parameter, so default values will be used.") else cellFormat = for i, unit in require("Module:StringTools").imatch(outputParameter, "[CF]") do cellFormat[i] = unit if i > 2 then break end end local function setFormat(key, variable, value) if string.find(outputParameter, key) then cellFormat[variable] = value else cellFormat[variable] = not value end end if cellFormat[1] then cellFormat.first = cellFormat[1] else error('C or F not found in output parameter') end if cellFormat[2]

nil then cellFormat["convertUnits"] = false else if cellFormat[2]

cellFormat[1] then error('There should not be two of the same unit name in the output parameter.') else cellFormat["convertUnits"] = true end end setFormat("unit", "unitNames", true) setFormat("no ?color", "color", false) setFormat("sort", "sortable", true) setFormat("full ?size", "smallFont", false) setFormat("no ?brackets", "brackets", false) setFormat("round", "decimals", "0", "") if string.find(outputParameter, "line break") then cellFormat["lineBreak"] = true elseif string.find(outputParameter, "one line") then cellFormat["lineBreak"] = false else cellFormat["lineBreak"] = "auto" end if string.find(outputParameter, "one line") and string.find(outputParameter, "line break") then error('Place either "one line" or "line break" in the output parameter, not both') end end palette = palette or "cool2avg" show = messages

"show" return end

-- Math functions

local function round(value, decimals) value = tonumber(value) if type(value)

"number" then return string.format("%." .. decimals .. "f", value) else error("Format was asked to operate on " .. tostring(value) .. ", which cannot be converted to a number.", 2) return "" endend

local function convert(value, unit, decimals) -- Unit is the unit being converted from. if not unit then error("No unit supplied to convert.", 2) end if tonumber(value) then local value = tonumber(value) if unit

"C" then return round(value * 9/5 + 32, decimals) elseif unit

"F" then return round((value - 32) * 5/9, decimals) else error("Input unit not recognized", 2) end else -- to avoid concatenation errors return "" endend

-- Stick numbers into array. Find out if any have decimals.-- Throw an error if any are invalid.local function _makeArray(format) return function(parameter) if not parameter then return nil end local array = -- If there are multiple parameters for numbers, and the first doesn't have -- decimals, the rest will have their decimals rounded off. format.precision = format.precision or parameter:find("%d%.%d") and "1" or "0" local numbers = mw.text.split(parameter, "%s+") if #numbers ~= format.length then addMessage('There are not ' .. format.length .. ' values in the ' .. parameter .. ' parameter.') end for i, number in ipairs(numbers) do if not number:find("^%-?%d%d?%d?.?(%d?)$") then error('The number "' .. number .. '" does not fit the expected pattern.') end table.insert(array, number) end return array endend

-- Color generation

p.palettes =

--Return style for a table cell based on the given value which should be a temperature in °C. local function temperatureColor(palette, value, outRGB) local backgroundColor, textColor value = tonumber(value) if not value then backgroundColor, textColor = 'FFF', '000' addMessage("Value supplied to " .. monospace("temperatureColor") .. " is not recognized.") else local min, max = unpack(palette.white or) if value < min or value >= max then textColor = 'FFF' -- Else nil. -- This assumes that black text color is the default for most readers. end

local backgroundRGB = outRGB or for i, v in ipairs(palette) do local a, b, c, d = unpack(v) if value <= a then backgroundRGB[i] = 0 elseif value < b then backgroundRGB[i] = (value - a) * 255 / (b - a) elseif value <= c then backgroundRGB[i] = 255 elseif value < d then backgroundRGB[i] = 255 - ((value - c) * 255 / (d - c)) else backgroundRGB[i] = 0 end end backgroundColor = string.format('%02X%02X%02X', unpack(backgroundRGB)) end return backgroundColor, textColorend

local function colorCSS(backgroundColor, textColor) if backgroundColor and textColor then return 'background: #' .. backgroundColor .. '; color: #' .. textColor .. ';' elseif backgroundColor then return 'background: #' .. backgroundColor .. ';' else return endend

local function temperatureColorCSS(palette, value, outRGB) return colorCSS(temperatureColor(palette, value, outRGB))end

local function temperatureCSS(value, unit, palette) local palette = p.palettes[palette] or p.palettes.cool local value = tonumber(value) if value

nil then error("The function " .. monospace("temperatureCSS") .. " is receiving a nil value") else if unit

'F' then value = convert(value, 'F', decimals) elseif unit ~= 'C' then unitError(unit or "nil") end return colorCSS(temperatureColor(palette, value)) endend

local function styleAttribute(palette, value, outRGB) local fontSize = "font-size: 85%;" local color = temperatureColorCSS(palette, value, outRGB) return 'style=\"' .. color .. ' ' .. fontSize .. '\"'end

local style_attribute = styleAttribute

--[=[ Used by {{Average temperature table/row/C/sandbox}}, {{Average temperature table/row/F/sandbox}}, {{Average temperature table/row/C/sandbox}}, {{Template:Avg temp row F/sandbox2}}, {{Template:Avg temp row C/sandbox2}}. ]=]function p.temperatureStyle(frame) local palette = p.palettes[frame.args.palette] or p.palettes.cool local unit = frame.args.unit or 'C' local value = tonumber(frame.args[1]) if unit

'F' then value = convert(value, 'F', 1) elseif unit ~= 'C' then error('Unrecognized unit: ' .. unit) end return styleAttribute(palette, value)end

p.temperature_style = p.temperatureStyle

--

Cell, row, table generation

local outputFormats =

local outputFormat

local function addUnitNames(value, yesOrNo, unit) if not unit then error("No unit supplied as argument 3 to addUnitNames", 2) end -- Don't add a unit name to an empty string value = yesOrNo

true and ine(value) and value .. " " .. degree .. unit or value return valueend

local function ifYes(parameter, realization1, realization2) local result if realization1 then if realization2 then result = parameter

true and or else result = parameter

true and realization1 or "" end else result = "" addMessage(monospace("ifYes") .. " needs at least one realization.") end return resultend

local function makeCell(outputFormat, a, b, c, format) local cell, cellContent = "", "" local colorCSS, otherCSS, titleAttribute, sortkey, attributeSeparator, convertedUnitsSeparator = "", "", "", "", "", "", "" -- Distinguish styleAttribute variable from styleAttribute function above. local styleAttribute, highLowSeparator, brackets, values, convertedUnits = ,,,, -- Precision is 1 if any number has one or more decimals. decimals = tonumber(outputFormat.decimals) and outputFormat.decimals or format.precision if tonumber(b) and tonumber(a) then values, highLowSeparator =, elseif tonumber(a) then values = elseif tonumber(c) then values = end if outputFormat.first

format.inputUnit then if outputFormat.convertUnits

true then convertedUnits = end values = elseif outputFormat.first

"C" or outputFormat.first

"F" then if outputFormat.convertUnits

true then convertedUnits = end values = else addMessage(monospace(tostring(outputFormat.first)) .. ", the value for " .. monospace("first") .. " in " .. monospace("outputFormat") .. " is not recognized.") end -- if outputFormat.convertUnits

true then brackets = outputFormat.brackets

true and or if outputFormat.lineBreak

"auto" then convertedUnitsSeparator = (ine(values[2]) or decimals ~= "0" or outputFormat.showUnits

true) and "
" or " " else convertedUnitsSeparator = outputFormat.lineBreak

true and "
" or outputFormat.lineBreak

false and " " or error('Value for lineBreak not recognized') end end cellContent = values[1] .. highLowSeparator[1] .. values[2] .. convertedUnitsSeparator .. brackets[1] .. convertedUnits[1] .. highLowSeparator[2] .. convertedUnits[2] .. brackets[2] if tonumber(c) then colorCSS = outputFormat.color

true and temperatureCSS(c, format.inputUnit, format.palette, format.inputUnit) or "" if tonumber(b) and tonumber(a) then local attributeValue = outputFormat.first

format.inputUnit and c or convert(c, format.inputUnit, decimals) sortkey = outputFormat.sortable

true and " data-sort-value=\"" .. attributeValue .. "\"" or "" titleAttribute = " title=\"Average temperature: " .. attributeValue .. " " .. degree .. outputFormat.first .. "\"" end elseif tonumber(b) then colorCSS = "" elseif tonumber(a) then colorCSS = outputFormat.color

true and temperatureCSS(a, format.inputUnit, format.palette) or "" else addMessage('Neither a nor b nor c are strings.') end otherCSS = outputFormat.smallFont

true and "font-size: 85%;" or "" if ine(colorCSS) or ine(otherCSS) then styleAttribute = end if ine(otherCSS) or ine(colorCSS) or ine(titleAttribute) or ine(sortkey) then attributeSeparator = " | " end cell = "\n| " .. styleAttribute[1] .. colorCSS .. otherCSS .. styleAttribute[2] .. titleAttribute .. sortkey .. attributeSeparator .. cellContent return cellend

--Replaces hyphens that have a punctuation or space character before them and a number after them, making sure that hyphens in "data-sort-type" are not replaced with minuses. If Lua had (?<=), a capture would not be necessary. local function hyphenToMinus(str) return str:gsub("([%p%s])-(%d)", "%1" .. minus .. "%2")end

function p.makeRow(frame) local args = frame.args local format = getFormat(args.input, args.output, args.palette, args.messages) local makeArray = _makeArray(format) local a, b, c = makeArray(args.a), makeArray(args.b), makeArray(args.c) local output = if args[1] then table.insert(output, "\n|-") table.insert(output, "\n! " .. args[1]) if args[2] then table.insert(output, " !! " .. args[2]) end end if format.cellFormat then outputFormat = format.cellFormat end -- Assumes that if c is defined, b and a are, and if b is defined, a is. if c then if not outputFormat then outputFormat = outputFormats.high_low_average_F end for i = 1, format.length do table.insert(output, makeCell(outputFormat, a[i], b[i], c[i], format)) end elseif b then if not outputFormat then outputFormat = outputFormats.high_low_F end for i = 1, format.length do table.insert(output, makeCell(outputFormat, a[i], b[i], nil, format)) end elseif a then if not outputFormat then outputFormat = outputFormats.average_F end for i = 1, format.length do table.insert(output, makeCell(outputFormat, a[i], nil, nil, format)) end end output = table.concat(output) output = hyphenToMinus(output) return outputend

function p.makeTable(frame) local args = frame.args local format = getFormat(args.input, args.output, args.palette, args.messages) local makeArray = _makeArray(format) local a, b, c = makeArray(args.a), makeArray(args.b), makeArray(args.c) local output = ") if format.show then table.insert(output, "\n\n

" .. message .. "") end output = table.concat(output) output = hyphenToMinus(output) return outputend

local chart = width=600|height=180|xAxisTitle=Celsius|yAxisTitle=__COLOR|type=line|x=__XVALUES|y=__YVALUES|colors=__COLOR}}

function p.show(frame) -- For testing, return wikitext to show graphs of how the red/green/blue colors -- vary with temperature, and a table of the resulting colors. local function collection -- Return a table to hold items. return end local function make_chart(result, color, xvalues, yvalues) result:add('\n') result:add(frame:preprocess((chart:gsub('__[A-Z]+',)))) end local function with_minus(value) if value < 0 then return minus .. tostring(-value) end return tostring(value) end local args = frame.args local first = args[1] or -90 local last = args[2] or 59 local palette = p.palettes[args.palette] or p.palettes.cool local xvals, reds, greens, blues = collection, collection, collection, collection local wikitext = collection wikitext:add('

-\n') local columns = 0 for celsius = first, last do local backgroundRGB = local style = styleAttribute(palette, celsius, backgroundRGB) local R = math.floor(backgroundRGB[1]) local G = math.floor(backgroundRGB[2]) local B = math.floor(backgroundRGB[3]) xvals:add(celsius) reds:add(R) greens:add(G) blues:add(B) wikitext:add('' .. style .. ' ' .. with_minus(celsius) .. '\n') columns = columns + 1 if columns >= 10 then columns = 0 wikitext:add('-\n') end end wikitext:add('
\n') make_chart(wikitext, 'Red', xvals, reds) make_chart(wikitext, 'Green', xvals, greens) make_chart(wikitext, 'Blue', xvals, blues) return wikitext:joinend

return p