Module:SOM.meta.SimpleAttribute

From SunshinePPS Wiki

Utilities to define a Simple Attribute class.

generate(args)
Generates a Simple Attribute class.
args:
class_name : the class name, e.g. SOM.Simple.HasSomeValue
docstring : description of the class
property_root : root of the SMW property used for attaching the value, e.g. Has some value
value_input_type : (nil, string) input type to use for a PageForms field
value_extra_args : (nil, string) extra args to use for a PageForms field
multivalued : (nil, boolean) if true, allow multiple (comma-separated) values
render_formatter : (nil, function) custom formatting function, to be invoked by the class' render() method. This function will be called with the same arguments passed to render(), and should return wikitext or nil.
returns:
Returns the new class (as a table).

(/doc page)


require("Module:No globals")

local base = require("Module:SOM.meta.AttributeBase")
local util = require("Module:SOM.util")
local StringBuffer = require("Module:SOM.util.StringBuffer")

--local function make_value_property(root)
--	return "SOM:" .. root
--end

local function make_missing_if_nil(record, key)
	if record[key] == nil then
		record[key] = util.MISSING_VALUE
	end
end

local p = { docs = {} }

function p.docs.docs()
	return {
		module_name = "SOM.meta.SimpleAttribute",
		docstring = "Utilities to define a Simple Attribute class."
	}
end

-- generate({
--    class_name = "SOM.Intrinsic.BelongsToSchoolDistrict",
--    property_root = "Belongs to school district",
--    value_arg = "district",
--    value_lua_type = "string",
--    value_label = "School District",
--    value_input_type = "combobox",
--    value_input_category = "School District"
--    attribute_label = "School District",
--    instance_template = "SOM.Intrinsic.BelongsToSchoolDistrict/Instance"
--    })
function p.docs.generate()
	return {
		desc = "Generates a Simple Attribute class.",
		needs_args = true,
		args = {
			{"class_name", "the class name, e.g. <code>SOM.Simple.HasSomeValue</code>"},
			{"docstring", "description of the class"},
			{"property_root", "root of the SMW property used for attaching the value, e.g. <code>Has some value</code>"},
			{"value_input_type", "(nil, string) input type to use for a PageForms field"},
			{"value_extra_args", "(nil, string) extra args to use for a PageForms field"},
			{"multivalued", "(nil, boolean) if true, allow multiple (comma-separated) values"},
			{"render_formatter", "(nil, function) custom formatting function, to be invoked by the class' <code>render()</code> method.  This function will be called with the same arguments passed to <code>render()</code>, and should return wikitext or nil."},
		},
		returns = "Returns the new class (as a table)."
	}
end
	
function p.generate(args)
	
	local class_name = args.class_name
	local docstring = args.docstring
----	local category_name = args.class_name
	local property_root = args.property_root

----	local value_arg = args.value_arg
----	local value_lua_type = args.value_lua_type
----	local value_label = args.value_label
	
	local value_input_type = args.value_input_type
	local value_extra_args = args.value_extra_args
	
	local multivalued = args.multivalued
	
	local render_formatter = args.render_formatter

----	local attribute_label = args.attribute_label
----	local value_formatter = args.value_formatter or ""

	local single_value_property = 
		property_root and base.make_value_property(property_root)
	
	local class = {}
	class.docs = {}
	
--	class.module_name = class_name
	function class.docs.docs()
		local buffer = StringBuffer.new()
		buffer:add_uformat("This module defines the %ssimple attribute class <code>%s</code>.",
			util.string_if(multivalued, "multivalued, "),
			class_name):nl()
		if docstring then
			buffer:nl():add(mw.text.trim(docstring)):nl()
		end
		if single_value_property then
			buffer:nl():add_uformat([=[
The value%s will be attached as the SMW property [[Property:%s|%s]].]=],
				util.string_if(multivalued, "(s)"),
				single_value_property, single_value_property):nl()
		end
		return {
			module_name = class_name,
			docstring = buffer:output(),
		}
	end
	
	------------------------------
	function class.is_composite()
		return false
	end
	
	function class.docs.is_composite()
		return { desc = "Returns false, indicating that this class is not composite." }
	end

	------------------------------
	function class.attach(args)
		local single_value = args.value
		local empty_as = args.empty_as or "error"
		
		util.assertf(
			(empty_as == "error") or
			(empty_as == "missing"),
			"Invalid value for empty_as: %s", empty_as)
		
		util.assertf((type(single_value) == "string") or (single_value == nil),
			"Value for %s must be string or nil, not %s.",
			class_name, type(single_value))
		if (single_value == nil) or (single_value == "") then
			if empty_as == "error" then	
				util.assertf(false, "Value for %s cannot be empty.", class_name)
			elseif empty_as == "missing" then
				if single_value_property then
					util.note_missing_field(single_value_property)
				end
				return nil
			else
				util.unreachable()
			end
		end
		
		local subvalues = {single_value}
		if multivalued then
			-- Split value at commas, ','
			subvalues = mw.text.split(single_value, ",%s*")
		end
		
		if single_value_property then
			local ds = {}
			if multivalued then
--				-- Split value at commas, ','
--				local subvalues = mw.text.split(single_value, ",%s*")
				ds[single_value_property] = subvalues
			else
				ds[single_value_property] = single_value
			end
			util.assert_smw_set(ds)
--			util.assert_smw_set({
--				[single_value_property] = single_value,
--				})
		end
		-- TODO maddog Should we simply always return the subvalues list???
		if multivalued then
			return { value = subvalues }
		else
			return { value = single_value }
		end
	end

	function class.docs.attach()
		local desc = StringBuffer.new()
		if multivalued then
			desc:add("Attaches attribute values to the page")
		else
			desc:add("Attaches an attribute value to the page")
		end
		if single_value_property then
			desc:add_uformat(", via the property [[Property:%s|%s]].",
				single_value_property, single_value_property)
		else
			desc:add(".")
		end
		desc:nl():nl()
		desc:add([[
<code>empty_as</code> specifies how a missing/empty <code>value</code> shall
be handled.  If <code>error</code> (the default), an error will be raised;
otherwise, ]])
		if single_value_property then
			desc:add([[
a missing field will be flagged on the page for the property.]])
		else
			desc:add([[the missing value will be ignored.]])
		end
		desc:nl():nl()
		if multivalued then
			desc:add([[
The <code>value</code> string may contain multiple comma-separated values.
The string will be split by commas, and whitespace will be trimmed from
each value.]]):nl():nl()
			desc:add([[
Returns a table with a single key <code>value</code> that resolves to the
list of split values.]])
		else
			desc:add([[
Returns a table with a single key <code>value</code> that resolves to the value.]])
		end
		desc:add([[  But, returns <code>nil</code> if <code>value</code> is empty/nil.]])
		
		return { 
			desc = desc:output(),
			needs_args = true,
			args = {
				{ "value", "(string or nil) the value for the attribute" },
				{ "empty_as", "(nil or 'missing' or 'error') how to handle a nil/empty value" },
			}
		}
	end


--	function class.note_as_missing()
--		util.note_missing_field(single_value_property)
--	end
	
	
	------------------------------
	function class.docs.render()
		local args
		if render_formatter then
			args = {
				{"value", "value instance (<code>nil</code> interpreted as missing)"},
				{"args", "additional parameter forwarded to the class' render_formatter"},
				}
		else
			args = {
				{"value", "(nil, string) value instance (<code>nil</code> interpreted as missing)"},
				{"ignored", "ignored parameter"},
				}
		end
		return {
			desc = "Render a value instance to wikitext.",
			returns = [[
Returns a wikitext string.  <code>nil</code> will be rendered as "missing" and
add a category tag for the missing-data category.]],
			args = args,
			}
	end
		
	function class.render(value, args)
		if render_formatter then
			value = render_formatter(value, args)
		end
		if multivalued and (value ~= nil) then
			return table.concat(value, ", ")
		end
		return util.show_nil_as_missing(value)
	end
	

--	function class.show_single (frame)
--		-- {{#ask:
--		-- [[{{FULLPAGENAME}}]]
--		-- [[Has website::+]]
--		-- |?Has website=
--		-- |mainlabel=-
--		-- }}
--		local query = {}
--		table.insert(query, 
--			mw.ustring.format("[[%s]][[%s::+]]",
--				mw.title.getCurrentTitle().fullText,
--				single_value_property))
--		table.insert(query, 
--			mw.ustring.format("?%s%s=", single_value_property, value_formatter))
------------				"?" .. single_value_property .. "=")
--		table.insert(query, "mainlabel=-")
--	
--		local result = mw.smw.ask(query)
--		if false then
--			return mw.dumpObject(result)
--		end
--		-- result should be a table with one subtable (corresponding to one
--		-- matched page).  Subtable should have a single string element, but if
--		-- there are multiple property values then the subtable will have a 
--		-- single sub-subtable with each string value.
--		if (result == nil) or (#result == 0) then
--			util.note_missing_field(single_value_property)
--			return util.MISSING_VALUE
--		end
--		-- This should always be true:
--		assert(#result == 1, "More than one matching page.")
--		-- TODO maddog Deal with multiple results.
--		assert((#result[1] == 1) and (type(result[1][1]) == value_lua_type),
--			"More than one single value.")
--		return result[1][1]
--	end
	
	
	
	------------------------------
	function class.form_field(args)
		local field_name = args.field_name
		local label = args.label  -- nil ok

		local result = StringBuffer.new()
			:add_uformat("{{{field|%s|input type=%s", field_name, value_input_type)
		if label then
			result:add_uformat("|label=%s", label)
		end
		for k, v in pairs(value_extra_args) do
			if type(k) == "number" then
				result:add_uformat("|%s", v)
			else
				result:add_uformat("|%s=%s", k, v)
			end
		end
		result:add("}}}")
		return result:output()	
	end
	
	function class.docs.form_field()
		return {
			desc = [[
Returns a PageForms field definition string for this attribute, to be used in
a PageForms form template.]],
			needs_args = true,
			args = {
				{"field_name", "(string) the name of the field in the form"},
				{"label", "(string, nil) optional label argument for the field"},
			}
		}
	end
	
	return class	
end


return p