Documentation for this module may be created at Module:Category handler/doc

  1. --------------------------------------------------------------------------------
  2. -- --
  3. -- CATEGORY HANDLER --
  4. -- --
  5. -- This module implements the {{category handler}} template in Lua, --
  6. -- with a few improvements: all namespaces and all namespace aliases --
  7. -- are supported, and namespace names are detected automatically for --
  8. -- the local wiki. This module requires [[Module:Namespace detect]] --
  9. -- and [[Module:Yesno]] to be available on the local wiki. It can be --
  10. -- configured for different wikis by altering the values in --
  11. -- [[Module:Category handler/config]], and pages can be blacklisted --
  12. -- from categorisation by using [[Module:Category handler/blacklist]]. --
  13. -- --
  14. --------------------------------------------------------------------------------
  15.  
  16. -- Load required modules
  17. local yesno = require('Module:Yesno')
  18.  
  19. -- Lazily load things we don't always need
  20. local mShared, mappings
  21.  
  22. local p = {}
  23.  
  24. --------------------------------------------------------------------------------
  25. -- Helper functions
  26. --------------------------------------------------------------------------------
  27.  
  28. local function trimWhitespace(s, removeBlanks)
  29. if type(s) ~= 'string' then
  30. return s
  31. end
  32. s = s:match('^%s*(.-)%s*$')
  33. if removeBlanks then
  34. if s ~= '' then
  35. return s
  36. else
  37. return nil
  38. end
  39. else
  40. return s
  41. end
  42. end
  43.  
  44. --------------------------------------------------------------------------------
  45. -- CategoryHandler class
  46. --------------------------------------------------------------------------------
  47.  
  48. local CategoryHandler = {}
  49. CategoryHandler.__index = CategoryHandler
  50.  
  51. function CategoryHandler.new(data, args)
  52. local obj = setmetatable({ _data = data, _args = args }, CategoryHandler)
  53. -- Set the title object
  54. do
  55. local pagename = obj:parameter('demopage')
  56. local success, titleObj
  57. if pagename then
  58. success, titleObj = pcall(mw.title.new, pagename)
  59. end
  60. if success and titleObj then
  61. obj.title = titleObj
  62. if titleObj == mw.title.getCurrentTitle() then
  63. obj._usesCurrentTitle = true
  64. end
  65. else
  66. obj.title = mw.title.getCurrentTitle()
  67. obj._usesCurrentTitle = true
  68. end
  69. end
  70.  
  71. -- Set suppression parameter values
  72. for _, key in ipairs{'nocat', 'categories'} do
  73. local value = obj:parameter(key)
  74. value = trimWhitespace(value, true)
  75. obj['_' .. key] = yesno(value)
  76. end
  77. do
  78. local subpage = obj:parameter('subpage')
  79. local category2 = obj:parameter('category2')
  80. if type(subpage) == 'string' then
  81. subpage = mw.ustring.lower(subpage)
  82. end
  83. if type(category2) == 'string' then
  84. subpage = mw.ustring.lower(category2)
  85. end
  86. obj._subpage = trimWhitespace(subpage, true)
  87. obj._category2 = trimWhitespace(category2) -- don't remove blank values
  88. end
  89. return obj
  90. end
  91.  
  92. function CategoryHandler:parameter(key)
  93. local parameterNames = self._data.parameters[key]
  94. local pntype = type(parameterNames)
  95. if pntype == 'string' or pntype == 'number' then
  96. return self._args[parameterNames]
  97. elseif pntype == 'table' then
  98. for _, name in ipairs(parameterNames) do
  99. local value = self._args[name]
  100. if value ~= nil then
  101. return value
  102. end
  103. end
  104. return nil
  105. else
  106. error(string.format(
  107. 'invalid config key "%s"',
  108. tostring(key)
  109. ), 2)
  110. end
  111. end
  112.  
  113. function CategoryHandler:isSuppressedByArguments()
  114. return
  115. -- See if a category suppression argument has been set.
  116. self._nocat == true
  117. or self._categories == false
  118. or (
  119. self._category2
  120. and self._category2 ~= self._data.category2Yes
  121. and self._category2 ~= self._data.category2Negative
  122. )
  123.  
  124. -- Check whether we are on a subpage, and see if categories are
  125. -- suppressed based on our subpage status.
  126. or self._subpage == self._data.subpageNo and self.title.isSubpage
  127. or self._subpage == self._data.subpageOnly and not self.title.isSubpage
  128. end
  129.  
  130. function CategoryHandler:shouldSkipBlacklistCheck()
  131. -- Check whether the category suppression arguments indicate we
  132. -- should skip the blacklist check.
  133. return self._nocat == false
  134. or self._categories == true
  135. or self._category2 == self._data.category2Yes
  136. end
  137.  
  138. function CategoryHandler:matchesBlacklist()
  139. if self._usesCurrentTitle then
  140. return self._data.currentTitleMatchesBlacklist
  141. else
  142. mShared = mShared or require('Module:Category handler/shared')
  143. return mShared.matchesBlacklist(
  144. self.title.prefixedText,
  145. mw.loadData('Module:Category handler/blacklist')
  146. )
  147. end
  148. end
  149.  
  150. function CategoryHandler:isSuppressed()
  151. -- Find if categories are suppressed by either the arguments or by
  152. -- matching the blacklist.
  153. return self:isSuppressedByArguments()
  154. or not self:shouldSkipBlacklistCheck() and self:matchesBlacklist()
  155. end
  156.  
  157. function CategoryHandler:getNamespaceParameters()
  158. if self._usesCurrentTitle then
  159. return self._data.currentTitleNamespaceParameters
  160. else
  161. if not mappings then
  162. mShared = mShared or require('Module:Category handler/shared')
  163. mappings = mShared.getParamMappings(true) -- gets mappings with mw.loadData
  164. end
  165. return mShared.getNamespaceParameters(
  166. self.title,
  167. mappings
  168. )
  169. end
  170. end
  171.  
  172. function CategoryHandler:namespaceParametersExist()
  173. -- Find whether any namespace parameters have been specified.
  174. -- We use the order "all" --> namespace params --> "other" as this is what
  175. -- the old template did.
  176. if self:parameter('all') then
  177. return true
  178. end
  179. if not mappings then
  180. mShared = mShared or require('Module:Category handler/shared')
  181. mappings = mShared.getParamMappings(true) -- gets mappings with mw.loadData
  182. end
  183. for ns, params in pairs(mappings) do
  184. for i, param in ipairs(params) do
  185. if self._args[param] then
  186. return true
  187. end
  188. end
  189. end
  190. if self:parameter('other') then
  191. return true
  192. end
  193. return false
  194. end
  195.  
  196. function CategoryHandler:getCategories()
  197. local params = self:getNamespaceParameters()
  198. local nsCategory
  199. for i, param in ipairs(params) do
  200. local value = self._args[param]
  201. if value ~= nil then
  202. nsCategory = value
  203. break
  204. end
  205. end
  206. if nsCategory ~= nil or self:namespaceParametersExist() then
  207. -- Namespace parameters exist - advanced usage.
  208. if nsCategory == nil then
  209. nsCategory = self:parameter('other')
  210. end
  211. local ret = {self:parameter('all')}
  212. local numParam = tonumber(nsCategory)
  213. if numParam and numParam >= 1 and math.floor(numParam) == numParam then
  214. -- nsCategory is an integer
  215. ret[#ret + 1] = self._args[numParam]
  216. else
  217. ret[#ret + 1] = nsCategory
  218. end
  219. if #ret < 1 then
  220. return nil
  221. else
  222. return table.concat(ret)
  223. end
  224. elseif self._data.defaultNamespaces[self.title.namespace] then
  225. -- Namespace parameters don't exist, simple usage.
  226. return self._args[1]
  227. end
  228. return nil
  229. end
  230.  
  231. --------------------------------------------------------------------------------
  232. -- Exports
  233. --------------------------------------------------------------------------------
  234.  
  235. local p = {}
  236.  
  237. function p._exportClasses()
  238. -- Used for testing purposes.
  239. return {
  240. CategoryHandler = CategoryHandler
  241. }
  242. end
  243.  
  244. function p._main(args, data)
  245. data = data or mw.loadData('Module:Category handler/data')
  246. local handler = CategoryHandler.new(data, args)
  247. if handler:isSuppressed() then
  248. return nil
  249. end
  250. return handler:getCategories()
  251. end
  252.  
  253. function p.main(frame, data)
  254. data = data or mw.loadData('Module:Category handler/data')
  255. local args = require('Module:Arguments').getArgs(frame, {
  256. wrappers = data.wrappers,
  257. valueFunc = function (k, v)
  258. v = trimWhitespace(v)
  259. if type(k) == 'number' then
  260. if v ~= '' then
  261. return v
  262. else
  263. return nil
  264. end
  265. else
  266. return v
  267. end
  268. end
  269. })
  270. return p._main(args, data)
  271. end
  272.  
  273. return p