Module:User:SuggestBot/WikiProjects

local wp = {}

-- BTW: Lua allows literal strings declarations [[ ]], but the syntax
-- highlighter doesn't support them properly, fails if the string contains
-- another set of square brackets...

-- Number of articles in each list if the page invoking this module is transcluded
local inc_limit = 2

-- Mapping of article assessment ratings to low/medium/high as numbers
local rating_numbers = {FA=3, A=3, GA=3, B=2, C=2, Start=1, Stub=1, NA=0}
local rating_names = {FA='High', A='High', GA='High', B='Medium', C='Medium',
	Start='Low', Stub='Low', NA='Unassessed'}

-- Mapping of SuggestBot's task categories (see [[User:SuggestBot/Documentation/Task categories]])
-- to section titles and descriptions, and the order in which they will be presented
local taskcat_order = {'source', 'cleanup', 'expand', 'unenc',
	'merge', 'wikify', 'orphan', 'stub'}
local taskcat_map = {
	source={
		title='Add sources',
		descr='See [[Wikipedia:Citing sources]] for more information.'
	},
	cleanup={
		title='Cleanup',
		descr='Improve the presentation of content.'
	},
	expand={
		title='Expand',
		descr='Add more information to these articles.'
	},
	unenc={
		title='Unencyclopaedic',
		descr='Remove content that is not relevant for an encyclopaedia.'
	},
	merge={
		title='Merge',
		descr='Should this article be merged with another article?'
	},
	wikify={
		title='Wikify',
		descr='Improve the wiki-formatting on this article.'
	},
	orphan={
		title='Orphan',
		descr='Few or no articles link to this article.'
	},
	stub={
		title='Stub',
		descr='Expand on this brief article.'
	}
}

-- Mapping of needs (specific tasks to improve an article) to descriptions
local needs_list = {'content', 'headings', 'links', 'images', 'sources'}
local needs_map = {content='more content',
	headings='proper section headings',
	links='more wikilinks',
	images='more images',
	sources='more sources'}

-- Local support functions follow below

local function has_key(tbl, key)
	-- Check if the given table has the given key
	return tbl[key] ~= nil	
end

local function sort_articles(arglist, task_cats)
	-- Sort the given argument list into a table mapping task categories
	-- to a list of articles in that task category.
	local sorted_arts = {}
	for i, task_cat in pairs(task_cats) do
		sorted_arts[task_cat] = {}
	end

	-- 0-based index of the current article being processed	
	local cur_art = 0
	while arglist[(cur_art * 6) + 1] do
		local cur_idx = cur_art * 6
		local task_cat = arglist[cur_idx + 1]
		-- Do we have this task category in our table?
		if sorted_arts[task_cat] then
			table.insert(sorted_arts[task_cat], {
				tag=task_cat, title=arglist[cur_idx + 2],
				views=arglist[cur_idx + 3],
				cur_qual=arglist[cur_idx + 4],
				pred_qual=arglist[cur_idx + 5],
				tasks=arglist[cur_idx + 6]
				})
		end
		cur_art = cur_art + 1
	end
	return sorted_arts
end

local function randomizeArray(t, limit)
	-- Randomizes an array. It works by iterating through the list backwards, each time swapping the entry
	-- "i" with a random entry. Courtesy of Xinhuan at http://forums.wowace.com/showthread.php?p=279756
	-- If the limit parameter is set, the array is shortened to that many elements after being randomized.
	-- The lowest possible value is 0, and the highest possible is the length of the array.
	local len = #t
	for i = len, 2, -1 do
		local r = math.random(i)
		t[i], t[r] = t[r], t[i]
	end
	if limit and limit < len then
		local ret = {}
		for i, v in ipairs(t) do
			if i > limit then
				break
			end
			ret[i] = v
		end
		return ret
	else
		return t
	end
end

local function make_ratingdesc(assessment, prediction)
	-- Make a string that describes the article's quality
	-- Quality: Low, Assessed class: Stub, Predicted class: Stub
	return "Quality: " .. rating_names[prediction] ..
	", Assessed class: " .. assessment ..
	", Predicted class: " .. prediction
end

local function make_needs(needs)
	-- From the given comma-separated list of needs, make a string that
	-- describes those needs based on needs_map
	local result = ''
	for i, task in ipairs(needs_list) do
		if string.find(needs, task) then
			result = result .. ", " .. needs_map[task]
		end
	end
	-- Remove the first two characters (", ")
	result = mw.ustring.sub(result, 3)
	-- Return with the first character in upper-case, from http://lua-users.org/wiki/StringRecipes
	return result:gsub("^%l", string.upper)
end

-- Globally exposed functions follow below
function wp.suggestions(frame)
	-- Process the suggested articles given in the frame's arguments
	-- and build lists of articles for each task category defined in the global
	-- list of categories above.
	
	-- Keyword arguments: 
	-- * is_included=yes: passed in to limit lists to two items each when
	--                    the page of the call is transcluded
	-- * list_col=[colour value]: defines the CSS colour value of list headers
	-- * item_col=[colour value]: defines the CSS colour value of list items
	--
	-- The rest of the arguments are sets of six parameters describing each
	-- suggested article, as follows:
	-- 1: task category
	-- 2: article title
	-- 3: number of average views/day (14 days prior to the update)
	-- 4: assessed quality
	-- 5: predicted quality
	-- 6: comma-separated list of specific tasks for improving this article
	
	local list_col = '#086'
	local item_col = '#37f'
	if has_key(frame, 'list_col') then
		list_col = frame['list_col']
	end
	if has_key(frame, 'item_col') then
		item_col = frame['item_col']
	end
	
	local is_included = false
	if has_key(frame.args, 'is_included') then
		is_included = true
	end
	
	-- When was the page this is invoked from last updated?
	local last_update = frame:expandTemplate{title='WPX last updated',
		args={frame:getParent():getTitle()}}
	
	local result = ''
	
	-- sort the supplied suggested articles and process into lists…
	local sorted_articles = sort_articles(frame.args, taskcat_order)
	for i, task_cat in pairs(taskcat_order) do
		-- write list start
		local list_params = {color=list_col,
			title=taskcat_map[task_cat]['title'],
			intro=taskcat_map[task_cat]['descr'] .. "<br />" .. last_update
		}
		if is_included then
			list_params['constrained'] = 'yes'
		end
		result = result .. frame:expandTemplate{title='WPX list start',
								args=list_params}
		
		-- write some articles
		local articles = sorted_articles[task_cat]
		if is_included then
			articles = randomizeArray(articles, inc_limit)
		end
		
		for j, article in pairs(articles) do
			result = result .. frame:expandTemplate{title='WPX article recommendation',
				args={
					color=item_col,
					title=article['title'],
					pageviews=article['views'],
					rating=rating_numbers[article['pred_qual']],
					rating_description=make_ratingdesc(article['cur_qual'],
						article['pred_qual']),
					needs=make_needs(article['tasks'])
				}
			}
		end
		
		-- write list end
		result = result .. frame:expandTemplate{title='WPX list end',
			args={more=frame:getParent():getTitle(), section=string.gsub(taskcat_map[task_cat]['title'], ' ', '_')}}
	end
	return result
end

function wp.hello(frame)
	return "Hello World!"	
end

function wp.test_args(frame)
	result = "The method got the following arguments:"
	if has_key(frame, 'is_included') then
		result = result .. "\n* is_included" .. frame.args['is_included']
	end
	for k, v in pairs(frame.args) do
		result = result .. "\n* " .. k .. "=" .. v				
	end
	return result
end

return wp