diff options
author | Karl Berry <karl@freefriends.org> | 2017-08-25 22:26:15 +0000 |
---|---|---|
committer | Karl Berry <karl@freefriends.org> | 2017-08-25 22:26:15 +0000 |
commit | 02f125ba4b59f889282c5aeaf16b1f38f057325e (patch) | |
tree | 997c5a7620b82c850ca011455a1f3b8c76a899bf | |
parent | 892337e471766e84277aa5c476152506d0cd3aa0 (diff) |
checkcites (25aug17)
git-svn-id: svn://tug.org/texlive/trunk@45131 c570f23f-e606-0410-a88d-b1316a301751
-rwxr-xr-x | Build/source/texk/texlive/linked_scripts/checkcites/checkcites.lua | 1275 | ||||
-rw-r--r-- | Master/texmf-dist/doc/support/checkcites/README | 88 | ||||
-rw-r--r-- | Master/texmf-dist/doc/support/checkcites/checkcites-doc.pdf | bin | 219274 -> 218420 bytes | |||
-rw-r--r-- | Master/texmf-dist/doc/support/checkcites/checkcites-doc.tex | 400 | ||||
-rwxr-xr-x | Master/texmf-dist/scripts/checkcites/checkcites.lua | 1275 |
5 files changed, 1663 insertions, 1375 deletions
diff --git a/Build/source/texk/texlive/linked_scripts/checkcites/checkcites.lua b/Build/source/texk/texlive/linked_scripts/checkcites/checkcites.lua index 69d79e172da..cdd0df14ef9 100755 --- a/Build/source/texk/texlive/linked_scripts/checkcites/checkcites.lua +++ b/Build/source/texk/texlive/linked_scripts/checkcites/checkcites.lua @@ -1,684 +1,721 @@ #!/usr/bin/env texlua --- ****************************************************************** +-- ----------------------------------------------------------------- -- checkcites.lua --- Copyright 2012 Enrico Gregorio, Paulo Roberto Massa Cereda +-- Copyright 2012, 2017, Enrico Gregorio, Paulo Roberto Massa Cereda -- --- This work may be distributed and/or modified under the --- conditions of the LaTeX Project Public License, either version 1.3 --- of this license or (at your option) any later version. +-- This work may be distributed and/or modified under the conditions +-- of the LaTeX Project Public License, either version 1.3 of this +-- license or (at your option) any later version. -- -- The latest version of this license is in --- http://www.latex-project.org/lppl.txt --- and version 1.3 or later is part of all distributions of LaTeX --- version 2005/12/01 or later. -- --- This work has the LPPL maintenance status `maintained'. --- --- The Current Maintainers of this work are the original authors. +-- http://www.latex-project.org/lppl.txt +-- +-- and version 1.3 or later is part of all distributions of LaTeX +-- version 2005/12/01 or later. -- --- This work consists of the file checkcites.lua. +-- This work has the LPPL maintenance status `maintained'. the +-- current maintainers of this work are the original authors. This +-- work consists of the file checkcites.lua. -- --- Project page: http://github.com/cereda/checkcites --- ****************************************************************** - --- version and date, to be updated on each release/commit -VERSION = "1.0i" -DATE = "December 18, 2012" - --- globals --- warning about \citation{*} -globalAsteriskWarning = true - --- The following code adds a 'split' function to the string type, --- thanks to the codebase available here: --- http://lua-users.org/wiki/SplitJoin -string.split = function(str, pattern) - - pattern = pattern or "[^%s]+" - - if pattern:len() == 0 then - - pattern = "[^%s]+" - +-- Project repository: http://github.com/cereda/checkcites +-- ----------------------------------------------------------------- + +-- Checks if the table contains the element. +-- @param a Table. +-- @param hit Element. +-- @return Boolean value if the table contains the element. +local function exists(a, hit) + for _, v in ipairs(a) do + if v == hit then + return true end - - local parts = {__index = table.insert} - - setmetatable(parts, parts) - - str:gsub(pattern, parts) - - setmetatable(parts, nil) - - parts.__index = nil - - return parts - + end + return false end --- In order to make our lives easier, we borrowed the following --- codes for implementing common set operations, available here: --- http://www.phailed.me/2011/02/common-set-operations-in-lua/ -function setFind(a, tbl) - - for _,a_ in ipairs(tbl) do - - if a_==a then - - return true +-- Parses the list of arguments based on a configuration map. +-- @param map Configuration map. +-- @param args List of command line arguments. +-- @return Table containing the valid keys and entries. +-- @return Table containing the invalid keys. +local function parse(map, args) + local keys, key, unknown = {}, 'unpaired', {} + local a, b + for _, v in ipairs(args) do + a, _, b = string.find(v, '^%-(%w)$') + if a then + for _, x in ipairs(map) do + key = 'unpaired' + if x['short'] == b then + key = x['long'] + break end - - end - -end - --- This code returns a table containing the difference between --- the two other tables, also available here: --- http://www.phailed.me/2011/02/common-set-operations-in-lua/ -function setDifference(a, b) - - local ret = {} - - for _,a_ in ipairs(a) do - - if not setFind(a_,b) then - - table.insert(ret, a_) - + end + if key == 'unpaired' then + table.insert(unknown, '-' .. b) + end + if not keys[key] then + keys[key] = {} + end + else + a, _, b = string.find(v, '^%-%-([%w-]+)$') + if a then + for _, x in ipairs(map) do + key = 'unpaired' + if x['long'] == b then + key = b + break + end end - - end - - return ret - -end - --- Code to remove duplicates from a table array. -function removeDuplicates(tb) - - -- new table - local ret = {} - - -- flag to spot new insertions - local flag - - -- set local variables - local i - local k - local j - local l - - -- iterate through the original table - for i, k in pairs(tb) do - - -- at first, insert element - flag = true - - -- iterate through the new table - for j, l in pairs(ret) do - - -- if the element already exists - if k == l then - - -- set flag to false, so the - -- new element won't be inserted - flag = false - - end - + if key == 'unpaired' then + if not exists(unknown, '--' .. b) then + table.insert(unknown, '--' .. b) + end end - - -- if it's ok to insert - if flag then - - -- insert new element - table.insert(ret, k) - + if not keys[key] then + keys[key] = {} end - - end - - -- return new table - return ret -end - --- This function opens and gets all data from the provided --- aux file. --- Return: --- * list of citations --- * list of bibfiles -function getDataFromAuxFile(theAuxFile) - - -- open a file handler - local fileHandler = io.open(theAuxFile,"r") - - -- check if the handler is valid - if fileHandler then - - -- create a local table for citations - local theCitations = {} - - -- and a local reference to the bib file - local theBibFiles = {} - - -- define local variables - local currentLine - local citation - local index - local theCitation - local theBibFile - local entry - - -- now let's iterate through the lines - for currentLine in fileHandler:lines() do - - -- if the citation matches, extract it - for citation in string.gmatch(currentLine, '\\citation{(.+)}') do - - -- sanity check, in case it's an '*' - if citation ~= "*" then - - -- break the citations list, in case of multiple - -- citations in the same \citation{} - local parts = string.split(citation, "[^,%s]+" ) - - -- for every entry in the citations list - for index, theCitation in pairs(parts) do - - -- insert the reference - table.insert(theCitations, theCitation) - - end - - -- found a '*' - else - - -- check if warning is still valid, that is, - -- if not displayed yet - if globalAsteriskWarning then - - -- show message - print("Warning: '\\nocite{*}' found, I'll do the check nonetheless.\n") - - -- warning already displayed, so - -- set flag to false - globalAsteriskWarning = false - - end - - end - - -- end of citation in current line - end - - -- in the same current line, check if there's the - -- bibdata entry and extract it - for entry in string.gmatch(currentLine, '\\bibdata{(.+)}') do - - -- break the bib files list, in case of multiple - -- files in the same \bibdata{} - local parts = string.split(entry, "[^,%s]+" ) - - -- for every entry in the bib files list - for index, theBibFile in pairs(parts) do - - -- insert the file - table.insert(theBibFiles, theBibFile) - - end - - -- end of bib files in the current line - end - - -- end of current line + else + if not keys[key] then + keys[key] = {} end - - -- close the file handler - fileHandler:close() - - -- remove duplicated citations - theCitations = removeDuplicates(theCitations) - - -- print a message about the citations - print("I found " .. #theCitations .. " citation(s).") - - -- remove possible duplicated files - theBibFiles = removeDuplicates(theBibFiles) - - -- if there are no bib files - if #theBibFiles == 0 then - - -- show message - print("I couldn't find any bibliography files.\nI'm afraid I have nothing to do now.") - - -- and abort the script - os.exit() - - -- if there is only one bib file - elseif #theBibFiles == 1 then - - -- show message - print("Great, there's only one 'bib' file. Let me check it.") - - -- there are more bib files + if key ~= 'unpaired' then + for _, x in ipairs(map) do + if x['long'] == key then + if not (x['argument'] and + #keys[key] == 0) then + key = 'unpaired' + end + break + end + end + if not keys[key] then + keys[key] = {} + end + table.insert(keys[key], v) else - - -- show message - print("Oh no, I have to check more than one 'bib' file. Please wait.") - + if not keys[key] then + keys[key] = {} + end + table.insert(keys[key], v) end - - -- return both citations and bib files - return theCitations, theBibFiles - - -- the file handler is invalid - else - - -- print an error message - print("File '" .. theAuxFile .. "' does not exist or is unavailable. Aborting script.") - - -- and abort the script - os.exit() - + end end - --- end of function + end + return keys, unknown end --- This function opens and gets all data from all the available --- bib files. -function getDataFromBibFiles(theBibFiles) - - -- create a table to store the citations - local theReferences = {} - - -- set local variables - local index - local theBibFile - local currentLine - local reference - - -- iterate through all bib files - for index, theBibFile in pairs(theBibFiles) do - - -- open the bib file - local fileHandler = io.open(theBibFile .. ".bib","r") - - -- check if the handler is valid - if fileHandler then - - -- iterate through every line of the bib file - for currentLine in fileHandler:lines() do - - -- if a reference is found - for reference in string.gmatch(currentLine, '@%w+{%s*(.+),') do - - -- insert the reference - table.insert(theReferences, reference) - - end - - -- end current line - end - - -- close the file handler - fileHandler:close() - - -- bib file does not exist - else - - -- error message - print("File '" .. theBibFile .. ".bib' does not exist. Aborting.") - - -- abort script - os.exit() - - end - - -- end iteration through the bib files +-- Calculates the difference between two tables. +-- @param a First table. +-- @param b Second table. +-- @return Table containing the difference between two tables. +local function difference(a, b) + local result = {} + for _, v in ipairs(a) do + if not exists(b, v) then + table.insert(result, v) end - - -- remove duplicated references - theReferences = removeDuplicates(theReferences) - - -- print message - print("I found " .. #theReferences .. " reference(s).") - - -- return references - return theReferences - --- end of function + end + return result end --- This function show all the undefined references. It's very --- simple, it's a difference between two sets. -function showUndefinedReferences(citations, references) - - -- get all undefined references - local undefined = setDifference(citations, references) - - -- print message - print("\nUndefined reference(s) in your TeX file: " .. #undefined) - - -- if there are undefined references - if #undefined ~= 0 then - - -- local variables - local index - local reference - - -- iterate - for index, reference in pairs(undefined) do - - -- and print - print("- " .. reference) - - end +-- Splits the string based on a pattern. +-- @param str String. +-- @param pattern Pattern. +local function split(str, pattern) + local result = {} + string.gsub(str, pattern, function(a) + table.insert(result, a) end) + return result +end +-- Reads lines from a file. +-- @param file File. +-- @returns Table representing the lines. +local function read(file) + local handler = io.open(file, 'r') + local lines = {} + if handler then + for line in handler:lines() do + table.insert(lines, line) end - --- end of function + handler:close() + end + return lines end --- This function show all the unused references. It's very --- simple, it's a difference between two sets. -function showUnusedReferences(citations, references) - --- get all undefined references - local unused = setDifference(references, citations) - - -- print message - print("\nUnused reference(s) in your bibliography file(s): " .. #unused) - - -- if there are unused references - if #unused ~= 0 then - - -- local variables - local index - local reference - - -- iterate - for index, reference in pairs(unused) do - - -- and print - print("- " .. reference) +-- Normalizes the string, removing leading and trailing spaces. +-- @param str String. +-- @return Normalized string without leading and trailing spaces. +local function normalize(str) + local result, _ = string.gsub(str, '^%s', '') + result, _ = string.gsub(result, '%s$', '') + return result +end - end - +-- Checks if the element is in a blacklist. +-- @param a Element. +-- @return Boolean value if the element is blacklisted. +local function blacklist(a) + local list = {} + for _, v in ipairs(list) do + if v == a then + return true end - --- end of function + end + return false end --- This function parses the command line arguments and returns a --- bunch of info for us to use. --- Return: --- * an argument code --- * the filename --- * an action code -function parseArguments(theArgs) - - -- check if there are no arguments - if #theArgs == 0 then - - -- return usage code - return 0, nil, nil - - -- there is one argument - elseif #theArgs == 1 then - - -- check if it's help - if theArgs[1] == "--help" then - - -- return help code - return 1, nil, nil - - -- check if it's version - elseif theArgs[1] == "--version" then - - -- return version code - return 2, nil, nil - - -- check if it's invalid - elseif string.sub(theArgs[1], 1, 1) == "-" then - - -- return invalid code - return 3, nil, nil - - -- it seems a clean argument - else +-- Extracts the biblographic key. +-- @param lines Lines of a file. +-- @return Table containing bibliographic keys. +local function extract(lines) + local result = {} + for _, line in ipairs(lines) do + local hit = string.match(line, + '^%s*%@%w+%s*{%s*(.+),') + if hit then + if not exists(result, hit) then + hit = normalize(hit) + table.insert(result, hit) + end + end + end + return result +end - -- return it as a valid argument - return 4, theArgs[1], nil +-- Gets a pluralized word based on a counter. +-- @param i Counter. +-- @param a Word in singular. +-- @param b Word in plural. +-- @return Either the first or second word based on the counter. +local function plural(i, a, b) + if i == 1 then + return a + else + return b + end +end - -- end for one parameter +-- Backend namespace +local backends = {} + +-- Gets data from auxiliary files (BibTeX). +-- @param lines Lines of a file. +-- @return Boolean indicating if an asterisk was found. +-- @return Table containing the citations. +-- @return Table containing the bibliography files. +backends.bibtex = function(lines) + local citations, bibliography = {}, {} + local asterisk, parts, hit = false + for _, line in ipairs(lines) do + hit = string.match(line, '^%s*\\citation{(.+)}$') + if hit then + if hit ~= '*' then + parts = split(hit, '[^,%s]+') + for _, v in ipairs(parts) do + v = normalize(v) + if not exists(citations, v) then + table.insert(citations, v) + end end - - -- there are two arguments - elseif #theArgs == 2 then - - -- check if both are valid - if ((theArgs[1] == "--all" or theArgs[1] == "--unused" or theArgs[1] == "--undefined") and string.sub(theArgs[2], 1, 1) ~= "-") or ((theArgs[2] == "--all" or theArgs[2] == "--unused" or theArgs[2] == "--undefined") and string.sub(theArgs[1], 1, 1) ~= "-") then - - -- create an action code - local actionCode - - -- check which one is the file name - if string.sub(theArgs[1], 1, 1) ~= "-" then - - -- check for --all - if theArgs[2] == "--all" then - - -- set the action code - actionCode = 0 - - -- check for --unused - elseif theArgs[2] == "--unused" then - - -- set the action code - actionCode = 1 - - -- it's --undefined - else - - -- set the action code - actionCode = 2 - - end - - -- it's the first - return 4, theArgs[1], actionCode - - else - - -- check for --all - if theArgs[1] == "--all" then - - -- set the action code - actionCode = 0 - - -- check for --unused - elseif theArgs[1] == "--unused" then - - -- set the action code - actionCode = 1 - - else - - -- it's --undefined - actionCode = 2 - - end - - -- it's the second - return 4, theArgs[2], actionCode - - end - - else - - -- return invalid code - return 3, nil, nil - + else + asterisk = true + end + else + hit = string.match(line, '^%s*\\bibdata{(.+)}$') + if hit then + parts = split(hit, '[^,%s]+') + for _, v in ipairs(parts) do + v = normalize(v) + if not exists(bibliography, v) and + not blacklist(v) then + table.insert(bibliography, v) + end end + end + end + end + return asterisk, citations, bibliography +end +-- Gets data from auxiliary files (Biber). +-- @param lines Lines of a file. +-- @return Boolean indicating if an asterisk was found. +-- @return Table containing the citations. +-- @return Table containing the bibliography files. +backends.biber = function(lines) + local citations, bibliography = {}, {} + local asterisk, parts, hit = false + for _, line in ipairs(lines) do + hit = string.match(line, '^%s*<bcf:citekey order="%d+">' .. + '(.+)</bcf:citekey>$') + if hit then + if hit ~= '*' then + parts = split(hit, '[^,%s]+') + for _, v in ipairs(parts) do + v = normalize(v) + if not exists(citations, v) then + table.insert(citations, v) + end + end + else + asterisk = true + end else - - -- more than two arguments, return usage code - return 0, nil, nil - + hit = string.match(line, '^%s*<bcf:datasource type="file" ' .. + 'datatype="%w+">(.+)</bcf:datasource>$') + if hit then + parts = split(hit, '[^,%s]+') + for _, v in ipairs(parts) do + v = normalize(v) + if not exists(bibliography, v) and + not blacklist(v) then + table.insert(bibliography, v) + end + end + end end - --- end of function + end + return asterisk, citations, bibliography end --- This function prints the script header. -function printHeader() - - -- print message - print("checkcites.lua -- a reference checker script (v" .. VERSION .. ")") - print("Copyright (c) 2012 Enrico Gregorio, Paulo Roberto Massa Cereda\n") - --- end of function +-- Counts the number of elements of a nominal table. +-- @param t Table. +-- @return Table size. +local function count(t) + local counter = 0 + for _, _ in pairs(t) do + counter = counter + 1 + end + return counter end --- This function prints the script usage -function printUsage() - - -- show message - print("Usage: " .. arg[0] .. " [--all | --unused | --undefined] file.aux\n") - print("--all Lists all unused and undefined references.") - print("--unused Lists only unused references in your 'bib' file.") - print("--undefined Lists only undefined references in your 'tex' file.\n") - print("If no flag is provided, '--all' is set by default.") - print("Be sure to have all your 'bib' files in the same directory.") - --- end of function +-- Repeats the provided char a certain number of times. +-- @param c Char. +-- @param w Number of times. +-- @return String with a char repeated a certain number of times. +local function pad(c, w) + local r = c + while #r < w do + r = r .. c + end + return r end -function printHelp() - - -- show message - print("checkcites.lua is a Lua script written for the sole purpose of") - print("detecting undefined/unused references from LaTeX auxiliary or") - print("bibliography files. It's very easy to use!\n") - - -- print usage - printUsage() - --- end of function +-- Adds the extension if the file does not have it. +-- @param file File. +-- @param extension Extension. +-- @return File with proper extension. +local function sanitize(file, extension) + extension = '.' .. extension + if string.sub(file, -#extension) ~= extension then + file = file .. extension + end + return file end --- This function prints the script version. -function printVersion() - - -- print message - print("checkcites.lua version " .. VERSION .. " (dated " .. DATE .. ")") - print("You can find more information about this script in the official") - print("source code repository:\n") - print("http://github.com/cereda/checkcites\n") - print("checkcites.lua is licensed under the LaTeX Project Public License.") - --- end of function +-- Flattens a table of tables into only one table. +-- @param t Table. +-- @return Flattened table. +local function flatten(t) + local result = {} + for _, v in ipairs(t) do + for _, k in ipairs(v) do + if not exists(result, k) then + table.insert(result, k) + end + end + end + return result end --- This function prints a message for invalid parameters. -function printInvalid() - - -- print message - print("Oh no, it seems you used an invalid argument.\n") - - -- print usage - printUsage() - --- end of function +-- Applies a function to elements of a table. +-- @param c Table. +-- @param f Function. +-- @return A new table. +local function apply(c, f) + local result = {} + for _, v in ipairs(c) do + table.insert(result, f(v)) + end + return result end --- This is our main function. -function runMain(theArgs) - - -- print the script header - printHeader() - - -- set local variables - local argCode - local fileName - local actionCode - - -- parse arguments and get the result - argCode, fileName, actionCode = parseArguments(theArgs) - - -- check for usage - if argCode == 0 then - - -- print usage - printUsage() - - -- it's help - elseif argCode == 1 then - - -- print help - printHelp() - - -- it's version - elseif argCode == 2 then - - -- print version - printVersion() - - -- it's an invalid parameter - elseif argCode == 3 then - - -- print invalid - printInvalid() - - -- it's a valid operation +-- Wraps a string based on a line width. +-- @param str String. +-- @param size Line width. +-- @return Wrapped string. +local function wrap(str, size) + local parts = split(str, '[^%s]+') + local r, l = '', '' + for _, v in ipairs(parts) do + if (#l + #v) > size then + r = r .. '\n' .. l + l = v else + l = normalize(l .. ' ' .. v) + end + end + r = normalize(r .. '\n' .. l) + return r +end - -- get data from aux - a, b = getDataFromAuxFile(fileName) - - -- get data from bib - c = getDataFromBibFiles(b) - - -- if there is an action code - if actionCode ~= nil then - - -- it's --all - if actionCode == 0 then - - -- do everything - showUnusedReferences(a,c) - showUndefinedReferences(a,c) - - -- it's --unused - elseif actionCode == 1 then +-- Prints the script header. +local function header() +print(" _ _ _ _") +print(" ___| |_ ___ ___| |_ ___|_| |_ ___ ___") +print("| _| | -_| _| '_| _| | _| -_|_ -|") +print("|___|_|_|___|___|_,_|___|_|_| |___|___|") +print() + print(wrap('checkcites.lua -- a reference ' .. + 'checker script (v2.0)', 74)) + print(wrap('Copyright (c) 2012, 2017, ' .. + 'Enrico Gregorio, Paulo ' .. + 'Roberto Massa Cereda', 74)) +end - -- only show unused - showUnusedReferences(a,c) +-- Operation namespace +local operations = {} + +-- Reports the unused references. +-- @param citations Citations. +-- @param references References. +-- @return Integer representing the status. +operations.unused = function(citations, references) + print() + print(pad('-', 74)) + print(wrap('Report of unused references in your TeX ' .. + 'document (that is, references present in ' .. + 'bibliography files, but not cited in ' .. + 'the TeX source file)', 74)) + print(pad('-', 74)) + local r = difference(references, citations) + print() + print(wrap('Unused references in your TeX document: ' .. + tostring(#r), 74)) + if #r == 0 then + return 0 + else + for _, v in ipairs(r) do + print('=> ' .. v) + end + return 1 + end +end - -- it's --undefined - else +-- Reports the undefined references. +-- @param citations Citations. +-- @param references References. +-- @return Integer value indicating the status. +operations.undefined = function(citations, references) + print() + print(pad('-', 74)) + print(wrap('Report of undefined references in your TeX ' .. + 'document (that is, references cited in the ' .. + 'TeX source file, but not present in the ' .. + 'bibliography files)', 74)) + print(pad('-', 74)) + local r = difference(citations, references) + print() + print(wrap('Undefined references in your TeX document: ' .. + tostring(#r), 74)) + if #r == 0 then + return 0 + else + for _, v in ipairs(r) do + print('=> ' .. v) + end + return 1 + end +end - -- only show undefined - showUndefinedReferences(a,c) +-- Reports both unused and undefined references. +-- @param citations Citations. +-- @param references References. +-- @return Integer value indicating the status. +operations.all = function(citations, references) + local x, y + x = operations.unused(citations, references) + y = operations.undefined(citations, references) + if x + y > 0 then + return 1 + else + return 0 + end +end - end +-- Checks if a file exists. +-- @param file File. +-- @return Boolean value indicating if the file exists. +local function valid(file) + local handler = io.open(file, 'r') + if handler then + handler:close() + return true + else + return false + end +end - -- there's only one parameter, the file name, - -- so we set --all - else +-- Filters a table of files, keeping the inexistent ones. +-- @param files Table. +-- @return Table of inexistent files. +local function validate(files) + local result = {} + for _, v in ipairs(files) do + if not valid(v) then + table.insert(result, v) + end + end + return result +end - -- show everything - showUnusedReferences(a,c) - showUndefinedReferences(a,c) +-- Main function. +-- @param args Command line arguments. +-- @return Integer value indicating the status +local function checkcites(args) + header() + + local parameters = { + { short = 'a', long = 'all', argument = false }, + { short = 'u', long = 'unused', argument = false }, + { short = 'U', long = 'undefined', argument = false }, + { short = 'v', long = 'version', argument = false }, + { short = 'h', long = 'help', argument = false }, + { short = 'b', long = 'backend', argument = true } + } + + local keys, err = parse(parameters, args) + local check, backend = 'all', 'bibtex' + + if #err ~= 0 then + print() + print(pad('-', 74)) + print(wrap('I am sorry, but I do not recognize ' .. + 'the following ' .. plural(#err, 'option', + 'options') .. ':', 74)) + for _, v in ipairs(err) do + print('=> ' .. v) + end - end + print() + print(wrap('Please make sure to use the correct ' .. + 'options when running this script. You ' .. + 'can also refer to the user documentation ' .. + 'for a list of valid options. The script ' .. + 'will end now.', 74)) + return 1 + end + + if count(keys) == 0 then + print() + print(pad('-', 74)) + print(wrap('I am sorry, but you have not provided ' .. + 'any command line argument, including ' .. + 'files to check and options. Make ' .. + 'sure to invoke the script with the actual ' .. + 'arguments. Refer to the user documentation ' .. + 'if you are unsure of how this tool ' .. + 'works. The script will end now.', 74)) + return 1 + end + + if keys['version'] or keys['help'] then + if keys['version'] then + print() + print(wrap('checkcites.lua, version 2.0 (dated August ' .. + '25, 2017)', 74)) + + print(pad('-', 74)) + print(wrap('You can find more details about this ' .. + 'script, as well as the user documentation, ' .. + 'in the official source code repository:', 74)) + + print() + print('https://github.com/cereda/checkcites') + + print() + print(wrap('The checkcites.lua script is licensed ' .. + 'under the LaTeX Project Public License, ' .. + 'version 1.3. The current maintainers ' .. + 'are the original authors.', 74)) + else + print() + print(wrap('Usage: ' .. args[0] .. ' [ [ --all | --unused | ' .. + '--undefined ] [ --backend <arg> ] <file> [ ' .. + '<file 2> ... <file n> ] | --help | --version ' .. + ']', 74)) + + print() + print('-a,--all list all unused and undefined references') + print('-u,--unused list only unused references in your bibliography files') + print('-U,--undefined list only undefined references in your TeX source file') + print('-b,--backend <arg> set the backend-based file lookup policy') + print('-h,--help print the help message') + print('-v,--version print the script version') + + print() + print(wrap('Unless specified, the script lists all unused and ' .. + 'undefined references by default. Also, the default ' .. + 'backend is set to "bibtex". Please refer to the user ' .. + 'documentation for more details.', 74)) + end + return 0 + end + + if not keys['unpaired'] then + print() + print(pad('-', 74)) + print(wrap('I am sorry, but you have not provided ' .. + 'files to process. The tool requires ' .. + 'least one file in order to properly ' .. + 'work. Make sure to invoke the script ' .. + 'with an actual file (or files). Refer ' .. + 'to the user documentation if you are ' .. + 'unsure of how this tool works. The ' .. + 'script will end now.', 74)) + return 1 + end + + if keys['backend'] then + if not exists({ 'bibtex', 'biber' }, keys['backend'][1]) then + print() + print(pad('-', 74)) + print(wrap('I am sorry, but you provided an ' .. + 'invalid backend. I know two: ' .. + '"bibtex" (which is the default ' .. + 'one) and "biber". Please make ' .. + 'sure to select one of the two. ' .. + 'Also refer to the user documentation ' .. + 'for more information on how these ' .. + 'backends work. The script will end ' .. + 'now.', 74)) + return 1 + else + backend = keys['backend'][1] + end + end + + if not keys['all'] then + if keys['unused'] and keys['undefined'] then + check = 'all' + elseif keys['unused'] or keys['undefined'] then + check = (keys['unused'] and 'unused') or + (keys['undefined'] and 'undefined') + end + end + + local auxiliary = apply(keys['unpaired'], function(a) + return sanitize(a, (backend == 'bibtex' + and 'aux') or 'bcf') end) + + local vld = validate(auxiliary) + if #vld ~= 0 then + print() + print(pad('-', 74)) + print(wrap('I am sorry, but I was unable to ' .. + 'locate ' .. plural(#vld, 'this file', + 'these files') .. ' (the extension ' .. + 'is automatically set based on the ' .. + '"' .. backend .. '" backend):', 74)) + for _, v in ipairs(vld) do + print('=> ' .. v) + end + print() + print(wrap('Selected backend: ' .. backend, 74)) + print(wrap('File lookup policy: add ".' .. + ((backend == 'bibtex' and 'aux') or 'bcf') .. + '" to files if not provided.', 74)) + + print() + print(wrap('Please make sure the ' .. plural(#vld, + 'path is', 'paths are') .. ' ' .. + 'correct and the ' .. plural(#vld, + 'file exists', 'files exist') .. '. ' .. + 'There is nothing I can do at the moment. ' .. + 'Refer to the user documentation for ' .. + 'details on the file lookup. If ' .. plural(#vld, + 'this is not the file', 'these are not the ' .. + 'files') .. ' you were expecting, ' .. + 'double-check your source file or ' .. + 'change the backend option when running ' .. + 'this tool. The script will end now.', 74)) + return 1 + end + + local lines = flatten(apply(auxiliary, read)) + local asterisk, citations, bibliography = backends[backend](lines) + + print() + print(wrap('Great, I found ' .. tostring(#citations) .. ' ' .. + plural(#citations, 'citation', 'citations') .. ' in ' .. + tostring(#auxiliary) .. ' ' .. plural(#auxiliary, 'file', + 'files') ..'. I also found ' .. tostring(#bibliography) .. + ' ' .. 'bibliography ' .. plural(#bibliography, 'file', + 'files') .. '. Let me check ' .. plural(#bibliography, + 'this file', 'these files') .. ' and extract the ' .. + 'references. Please wait a moment.', 74)) + + if asterisk then + print() + print(wrap('Also, it is worth noticing that I found a mention to ' .. + 'a special "*" when retrieving citations. That means ' .. + 'your TeX document contains "\\nocite{*}" somewhere in ' .. + 'the source code. I will continue with the check ' .. + 'nonetheless.', 74)) + end + + bibliography = apply(bibliography, function(a) + return sanitize(a, 'bib') end) + + vld = validate(bibliography) + if #vld ~= 0 then + print() + print(pad('-', 74)) + print(wrap('I am sorry, but I was unable to locate ' .. + plural(#vld, 'this file', 'these files') .. ' ' .. + '(the extension is automatically set to ' .. + '".bib", if not provided):', 74)) + for _, v in ipairs(vld) do + print('=> ' .. v) end --- end of function + print() + print(wrap('Please make sure the ' .. plural(#vld, + 'path is', 'paths are') .. ' ' .. + 'correct and the ' .. plural(#vld, + 'file exists', 'files exist') .. '. ' .. + 'There is nothing I can do at the moment. ' .. + 'Refer to to the user documentation ' .. + 'for details on bibliography lookup. If ' .. + plural(#vld, 'this is not the file', + 'these are not the files') .. ' you were ' .. + 'expecting (wrong bibliography), double-check ' .. + 'your source file. The script will end ' .. + 'now.', 74)) + return 1 + end + + local references = flatten(apply(bibliography, function(a) + return extract(read(a)) end)) + + print() + print(wrap('Fantastic, I found ' .. tostring(#references) .. + ' ' .. plural(#references, 'reference', + 'references') .. ' in ' .. tostring(#bibliography) .. + ' bibliography ' .. plural(#bibliography, 'file', + 'files') .. '. Please wait a moment while the ' .. + plural(((check == 'all' and 2) or 1), 'report is', + 'reports are') .. ' generated.', 74)) + + return operations[check](citations, references) end --- run the main function -runMain(arg) +-- Call and exit +os.exit(checkcites(arg)) + +-- EOF + diff --git a/Master/texmf-dist/doc/support/checkcites/README b/Master/texmf-dist/doc/support/checkcites/README index 461a08370fc..c2a62c38c10 100644 --- a/Master/texmf-dist/doc/support/checkcites/README +++ b/Master/texmf-dist/doc/support/checkcites/README @@ -1,10 +1,10 @@ -checkcites.lua -- Version 1.0i from December 18, 2012. -====================================================== +checkcites.lua -- Version 2.0 from August 25, 2017. +=================================================== License ------- -Copyright 2012 Enrico Gregorio, Paulo Roberto Massa Cereda +Copyright (c) 2012, 2017 Enrico Gregorio, Paulo Roberto Massa Cereda - Enrico dot Gregorio at univr dot it - cereda at users dot sf dot net @@ -17,18 +17,18 @@ About ----- checkcites is a Lua script written for the sole purpose of detecting -unused or undefined references from both LaTeX auxiliary or -bibliography files. We use 'unused reference' to refer to the -reference present the bibliography file -- with the '.bib' -extension -- but not cited in the '.tex' file. The 'undefined -reference' is exactly the opposite, that is, the items cited in the -'.tex' file, but not present in the '.bib' file. - -The original idea came from a question posted in the TeX.sx community -about how to check which bibliography entries were not used. We -decided to write a script to check references. We opted for Lua, -since it's a very straightforward language and it has an interpreter -available on every modern TeX distribution. +unused or undefined references from both LaTeX auxiliary or bibliography +files. We use the term *unused reference* to refer to the reference +present the bibliography file -- with the '.bib' extension -- but not +cited in the '.tex' file. The term *undefined reference* is exactly the +opposite, i.e, the item cited in the '.tex' file, but not present in the +'.bib' file. + +The original idea came from a question posted in the TEX community at +Stack Exchange about how to check which bibliography entries were not +used. We decided to write a script to check references. We opted for +Lua, since it's a very straightforward language and it has an +interpreter available on every modern TEX distribution. Installation ------------ @@ -62,14 +62,50 @@ Usage ----- The script is pretty simple to use. The only requirement is a recent -TeX distribution, such as TeX Live. Then run checkcites: +TeX distribution, such as TeX Live. + +checkcites uses the generated auxiliary files to start the analysis. +From version 2.0 on, the scripts supports two backends: + +-> bibtex + +Default behavior, the script checks '.aux' files looking for citations, +in the form of '\citation{a}'. For every \citation line found, checkcites +will extract the citations and add them to a table, even for multiple +citations separated by commas, like '\citation{a,b,c}'. The citation +table contains no duplicate values. At the same time checkcites also +looks for bibliography data, in the form of '\bibdata{a}'. Similarly, +for every '\bibdata' line found, the script will extract the bibliography +data and add them to a table, even if they are separated by commas, like +'\bibdata{d,e,f}'. Again, no duplicate values are allowed. Stick with this +backend if you are using BibTeX or BibLaTeX with the 'backend=bibtex' +package option. + +-> biber + +With this backend, the script checks '.bcf files' (which are XML-based) +looking for citations, in the form of 'bcf:citekey' tags. For every tag +found, checkcites will extract the corresponding values and add them to +a table. The citation table contains no duplicate values. At the same +time checkcites also looks for bibliography data, in the form of +'bcf:datasource' tags. Similarly, for every tag found, the script will +extract the bibliography data and add them to a table. Again, no duplicate +values are allowed. Stick with this backend if you are using BibLaTeX with +the default options or with the 'backend=biber' option explicitly set. + +Open a terminal and run checkcites: $ checkcites -It will print the script usage. The only required argument is the -'.aux' file which is generated when you compile your '.tex' file. -If your main document is named 'foo.tex', you will have a 'foo.aux' -file too. To run the script on that file, go with: +When you run checkcites without providing any argument to it, the a message +error will appear. Do not panic! Try again with the --help flag: + +$ checkcites --help + +If you are using BibTeX, simply provide the auxiliary file -- the one with +the '.aux' extension -- which is generated when you compile your main '.tex' +file. For example, if your main document is named 'foo.tex', you probably +have a 'foo.aux' file too. Then simply invoke $ checkcites foo.aux @@ -95,6 +131,18 @@ $ checkcites --all foo.aux If no special argument is provided, --all is set as default. +If you are using BibLaTeX, we need to inspect '.bcf' files instead. For +example, if your main document is named 'foo.tex', you probably have a +'foo.bcf' file too. Then invoke + +$ checkcites foo.aux --backend biber + +Note the --backend flag used for BibLaTeX support. We can even omit the +file extension, the script will automatically assign one based on the +current backend. + +That is it, folks! + Official code repository ------------------------ diff --git a/Master/texmf-dist/doc/support/checkcites/checkcites-doc.pdf b/Master/texmf-dist/doc/support/checkcites/checkcites-doc.pdf Binary files differindex 61c412726da..5f7c5f1212f 100644 --- a/Master/texmf-dist/doc/support/checkcites/checkcites-doc.pdf +++ b/Master/texmf-dist/doc/support/checkcites/checkcites-doc.pdf diff --git a/Master/texmf-dist/doc/support/checkcites/checkcites-doc.tex b/Master/texmf-dist/doc/support/checkcites/checkcites-doc.tex index afc5e2629f0..5a50af1f1c8 100644 --- a/Master/texmf-dist/doc/support/checkcites/checkcites-doc.tex +++ b/Master/texmf-dist/doc/support/checkcites/checkcites-doc.tex @@ -4,6 +4,7 @@ \usepackage[utf8]{inputenc} \usepackage[english]{babel} +\usepackage{tgpagella} \usepackage[margin=1in]{geometry} @@ -15,7 +16,7 @@ \newcommand{\checkcites}{\texttt{checkcites}} \newcommand{\email}[1]{\small\texttt{#1}} -\newcommand{\version}{Version 1.0i from December 18, 2012.} +\newcommand{\version}{Version 2.0 from August 25, 2017.} \newenvironment{infoblock}[1] {\par\addvspace{\medskipamount} @@ -43,25 +44,25 @@ \section{Introduction} \label{sec:intro} -\checkcites\ is a Lua script written for the sole purpose of detecting -unused or undefined references from both \LaTeX{} auxiliary or -bibliography files. We use \emph{unused reference} to refer to the -reference present the bibliography file --~with the \verb|.bib| -extension~-- but not cited in the \verb|.tex| file. The -\emph{undefined reference} is exactly the opposite, that is, the items -cited in the \texttt{.tex} file, but not present in the \verb|.bib| -file. - -The original idea came from a question posted in the -\href{http://tex.stackexchange.com}{TeX.sx community} about -\href{http://tex.stackexchange.com/questions/43276}{how to check which -bibliography entries were not used}. We decided to write a script to -check references. We opted for Lua, since it's a very straightforward -language and it has an interpreter available on every modern \TeX\ -distribution. +\checkcites\ is a Lua script written for the sole purpose of +detecting unused or undefined references from both \LaTeX\ auxiliary +or bibliography files. We use the term \emph{unused reference} to +refer to the reference present the bibliography file~--~with the +\verb|.bib| extension~--~but not cited in the \verb|.tex| file. +The term \emph{undefined reference} is exactly the opposite, i.e, +the item cited in the \verb|.tex| file, but not present in the +\verb|.bib| file. + +The original idea came from a question posted +in the \TeX\ community at Stack Exchange about +\href{http://tex.stackexchange.com/questions/43276}{how to check +which bibliography entries were not used}. We decided to write a +script to check references. We opted for Lua, since it is a very +straightforward language and it has an interpreter available on every +modern \TeX\ distribution. \begin{infoblock}{Attention!} -\checkcites\ is known to run with the most recent \verb|texlua| and +\checkcites\ is known to run with the most recent \verb|texlua| and \verb|lua| interpreters. Unfortunately, the code is incompatible with interpreters prior to the Lua 5.1 language specification. \end{infoblock} @@ -69,68 +70,79 @@ interpreters prior to the Lua 5.1 language specification. \section{How the script works} \label{sec:howto} -\checkcites\ uses the generated \verb|.aux| file to start the -analysis. The first step is to extract all citations found, in the -form of \verb|\citation{a}|. For every \verb|\citation| line found, -\checkcites\ will extract the citations and add them to a table, even -for multiple citations separated by commas, like -\verb|\citation{a,b,c}|. Then the citations table has all duplicate -values removed --~in other words, the table becomes a set. Let's call -$A$ the set of citations. +\checkcites\ uses the generated auxiliary files to start the +analysis. From version 2.0 on, the scripts supports two backends: + +\begin{description} +\item[\texttt{bibtex}] Default behavior, the script checks +\verb|.aux| files looking for citations, in the form of +\verb|\citation{a}|. For every \verb|\citation| line found, +\checkcites\ will extract the citations and add them to a +table, even for multiple citations separated by commas, like +\verb|\citation{a,b,c}|. The citation table contains no duplicate +values. At the same time \checkcites\ also looks for bibliography +data, in the form of \verb|\bibdata{a}|. Similarly, for every +\verb|\bibdata| line found, the script will extract the bibliography +data and add them to a table, even if they are separated by commas, +like \verb|\bibdata{d,e,f}|. Again, no duplicate values are allowed. +Stick with this backend if you are using Bib\TeX\ or Bib\LaTeX\ with +the \verb|backend=bibtex| package option. + +\item[\texttt{biber}] With this backend, the script checks +\verb|.bcf| files (which are XML-based) looking for citations, in the +form of \verb|bcf:citekey| tags. For every tag found, \checkcites\ +will extract the corresponding values and add them to a table. The +citation table contains no duplicate values. At the same time +\checkcites\ also looks for bibliography data, in the form of +\verb|bcf:datasource| tags. Similarly, for every tag found, the +script will extract the bibliography data and add them to a table. +Again, no duplicate values are allowed. Stick with this backend if +you are using Bib\LaTeX\ with the default options or with the +\verb|backend=biber| option explicitly set. +\end{description} \begin{infoblock}{Attention!} -If \verb|\citation{*}| is found, \checkcites\ will issue a message -telling that \verb|\nocite{*}| is in the \verb|.tex| document, but -the script will do the check nonetheless. +If \verb|\citation{*}| (Bib\TeX) or simply \verb|*| (Bib\LaTeX) +is found, \checkcites\ will issue a message telling that +\verb|\nocite{*}| is in the \verb|.tex| document, but the script will +do the check nonetheless. \end{infoblock} -At the same time \checkcites\ also looks for bibliography data, in the -form of \verb|\bibdata{a}|. Similarly, for every \verb|\bibdata| line -found, the script will extract the bibliography data and add them to a -table, even if they are separated by commas, like -\verb|\bibdata{d,e,f}|. The table has all duplicate values removed. +Now, \checkcites\ will extract all entries from the bibliography +files found in the previous steps, regardless of which backend was +used. For every element in the bibliography data table, the script +will look for entries like \verb|@BOOK|, \verb|@ARTICLE| and so +forth~--~we actually use pattern matching for this~-- and add their +identifiers to a table. No duplicate values are allowed. \begin{infoblock}{Attention!} -If no \verb|\bibdata| command is found, the script ends. There's -nothing to do in this case. +If \checkcites\ cannot find a certain bibliography file, the script +ends. Make sure to put the correct name of the bibliography file in +your \verb|.tex| file. \end{infoblock} -Now, \checkcites\ will extract all entries from the bibliography files -found in the previous step. For every element in the bibliography data -table, the script will look for entries like \verb|@BOOK|, -\verb|@ARTICLE| and so forth --~we actually use pattern matching for -this~-- and add their identifiers to a table. The script treats all -\verb|.bib| files as if they were only one. After all files have been -analyzed and all entries' identifiers extracted, the table has all -duplicate values removed. Let's call $B$ the set of bibliography -entries. - -\begin{infoblock}{Attention!} -If \checkcites\ cannot find a certain bibliography file --~that is, a -\verb|.bib| file~-- the script ends. Make sure to put the correct -name of the bibliography file in your \verb|.tex| file. -\end{infoblock} - -Now we have both sets $A$ and $B$. In order to get all unused -references in the \verb|.bib| files, we compute the set difference +Let there be $A$ and $B$ the sets of citations and references, +respectively. In order to get all unused references in the +\verb|.bib| files, we compute the set difference: \[ B - A = \{ x : x \in B, x \notin A \}. -\] -Similarly, in order to get all undefined references in the \verb|.tex| -file, we compute the set difference +\] +Similarly, in order to get all undefined references in the +\verb|.tex| file, we compute the set difference: \[ A - B = \{ x : x \in A, x \notin B \}. \] If there are either unused or undefined references, \checkcites\ will -print them in a list format. In Section~\ref{sec:usage} there's a more -complete explanation on how to use the script. +print them in a list format. In Section~\ref{sec:usage} there is a +more complete explanation on how to use the script. \section{Usage} \label{sec:usage} -\checkcites\ is very easy to use. First of all, let's define two files -that will be used here to explain the script usage. Here's our sample -bibliography file \verb|example.bib|, with five fictional entries. +\checkcites\ is very easy to use. First of all, let us define two +files that will be used here to explain the script usage. Here is our +sample bibliography file \verb|example.bib|, with five fictional +entries. \begin{tcblisting}{colframe=DarkTurquoise,coltitle=black,listing only, title=Bibliography file, fonttitle=\bfseries, @@ -176,7 +188,9 @@ bibliography file \verb|example.bib|, with five fictional entries. } \end{tcblisting} -The second file is our main \LaTeX{} document, \verb|document.tex|. +The second file is our main \LaTeX{} document, \verb|document.tex|. +Observe that we will stick with Bib\TeX\ for now and check Bib\LaTeX\ +later on. \begin{tcblisting}{colframe=DarkTurquoise,coltitle=black,listing only, title=Main document, fonttitle=\bfseries, @@ -185,7 +199,8 @@ The second file is our main \LaTeX{} document, \verb|document.tex|. \begin{document} -Hello world \cite{foo:2012a,foo:2012c}, how are you \cite{foo:2012f}, +Hello world \cite{foo:2012a,foo:2012c}, +how are you \cite{foo:2012f}, and goodbye \cite{foo:2012d,foo:2012a}. \bibliographystyle{plain} @@ -200,46 +215,81 @@ Open a terminal and run \verb|checkcites|: \begin{verbatim} $ checkcites \end{verbatim} -\tcblower +\tcblower\small \begin{verbatim} -checkcites.lua -- a reference checker script (v1.0i) -Copyright (c) 2012 Enrico Gregorio, Paulo Roberto Massa Cereda - -Usage: checkcites.lua [--all | --unused | --undefined] file.aux - ---all Lists all unused and undefined references. ---unused Lists only unused references in your 'bib' file. ---undefined Lists only undefined references in your 'tex' file. - -If no flag is provided, '--all' is set by default. -Be sure to have all your 'bib' files in the same directory. + _ _ _ _ + ___| |_ ___ ___| |_ ___|_| |_ ___ ___ +| _| | -_| _| '_| _| | _| -_|_ -| +|___|_|_|___|___|_,_|___|_|_| |___|___| + +checkcites.lua -- a reference checker script (v2.0) +Copyright (c) 2012, 2017, Enrico Gregorio, Paulo Roberto Massa Cereda + +-------------------------------------------------------------------------- +I am sorry, but you have not provided any command line argument, including +files to check and options. Make sure to invoke the script with the actual +arguments. Refer to the user documentation if you are unsure of how this +tool works. The script will end now. \end{verbatim} \end{terminal} -If you don't have \checkcites\ installed with your \TeX\ distribution, -you can run the standalone script \verb|checkcites.lua| with either -\verb|texlua| or \verb|lua|. We recommend to use \verb|texlua|, as -it's shipped with all the modern \TeX\ distributions: +If you do not have \checkcites\ installed with your \TeX\ +distribution, you can run the standalone script \verb|checkcites.lua| +with either \verb|texlua| or \verb|lua|. We recommend to use +\verb|texlua|, as it is shipped with all the modern \TeX\ +distributions: \begin{terminal} \begin{verbatim} $ texlua checkcites.lua \end{verbatim} \end{terminal} -When you run \checkcites\ without providing any argument to it, the -script usage will be printed, as seen in the previous output. The only -required argument is the auxiliary file --~with the \verb|.aux| -extension~-- which is generated when you compile your main \verb|.tex| -file. For example, if your main document is named \verb|foo.tex|, you -probably have a \verb|foo.aux| file too. Let's compile our sample -document \verb|document.tex|: +When you run \checkcites\ without providing any argument to it, the a +message error will appear. Do not panic! Try again with the +\verb|--help| flag: +\begin{terminal} +\begin{verbatim} +$ checkcites --help +\end{verbatim} +\tcblower\small +\begin{verbatim} + _ _ _ _ + ___| |_ ___ ___| |_ ___|_| |_ ___ ___ +| _| | -_| _| '_| _| | _| -_|_ -| +|___|_|_|___|___|_,_|___|_|_| |___|___| + +checkcites.lua -- a reference checker script (v2.0) +Copyright (c) 2012, 2017, Enrico Gregorio, Paulo Roberto Massa Cereda + +Usage: checkcites.lua [ [ --all | --unused | --undefined ] [ --backend +<arg> ] <file> [ <file 2> ... <file n> ] | --help | --version ] + +-a,--all list all unused and undefined references +-u,--unused list only unused references in your bibliography files +-U,--undefined list only undefined references in your TeX source file +-b,--backend <arg> set the backend-based file lookup policy +-h,--help print the help message +-v,--version print the script version + +Unless specified, the script lists all unused and undefined references by +default. Also, the default backend is set to "bibtex". Please refer to the +user documentation for more details. +\end{verbatim} +\end{terminal} + +Since we are using Bib\TeX, we do not need to set up the +backend! Simply provide the auxiliary file~--~the one with the +\verb|.aux| extension~--~which is generated when you compile your +main \verb|.tex| file. For example, if your main document is named +\verb|foo.tex|, you probably have a \verb|foo.aux| file too. Let us +compile our sample document \verb|document.tex|: \begin{terminal} \begin{verbatim} $ pdflatex document.tex \end{verbatim} \end{terminal} -After running \verb|pdflatex| on our \verb|.tex| file, there's now a +After running \verb|pdflatex| on our \verb|.tex| file, there is now a \verb|document.aux| file in our work directory. \begin{tcblisting}{colframe=DarkTurquoise,coltitle=black,listing only, @@ -261,31 +311,49 @@ Now we can run \checkcites\ on the \verb|document.aux| file: \begin{verbatim} $ checkcites document.aux \end{verbatim} -\tcblower +\tcblower\small \begin{verbatim} -checkcites.lua -- a reference checker script (v1.0i) -Copyright (c) 2012 Enrico Gregorio, Paulo Roberto Massa Cereda + _ _ _ _ + ___| |_ ___ ___| |_ ___|_| |_ ___ ___ +| _| | -_| _| '_| _| | _| -_|_ -| +|___|_|_|___|___|_,_|___|_|_| |___|___| + +checkcites.lua -- a reference checker script (v2.0) +Copyright (c) 2012, 2017, Enrico Gregorio, Paulo Roberto Massa Cereda -I found 4 citation(s). -Great, there's only one 'bib' file. Let me check it. -I found 5 reference(s). +Great, I found 4 citations in 1 file. I also found 1 bibliography file. Let +me check this file and extract the references. Please wait a moment. -Unused reference(s) in your bibliography file(s): 2 -- foo:2012b -- foo:2012e +Fantastic, I found 5 references in 1 bibliography file. Please wait a +moment while the reports are generated. -Undefined reference(s) in your TeX file: 1 -- foo:2012f +-------------------------------------------------------------------------- +Report of unused references in your TeX document (that is, references +present in bibliography files, but not cited in the TeX source file) +-------------------------------------------------------------------------- + +Unused references in your TeX document: 2 +=> foo:2012b +=> foo:2012e + +-------------------------------------------------------------------------- +Report of undefined references in your TeX document (that is, references +cited in the TeX source file, but not present in the bibliography files) +-------------------------------------------------------------------------- + +Undefined references in your TeX document: 1 +=> foo:2012f \end{verbatim} \end{terminal} -As we can see in the script output, \checkcites\ analyzed both -\verb|.aux| and \verb|.bib| files and found two unused references in -the bibliography file --~\verb|foo:2012b| and \verb|foo:2012e|~-- and -one undefined reference in the document --~\verb|foo:2012f|. +As we can see in the script output, \checkcites\ analyzed +both \verb|.aux| and \verb|.bib| files and managed to find two +unused references in the bibliography file~--~\verb|foo:2012b| +and \verb|foo:2012e|~--~and one undefined reference in the +document~--~\verb|foo:2012f|. -\checkcites\ allows a command line switch that will tell it how to -behave. For example, +\checkcites\ allows a couple of command line flags that will tell it +how to behave. For example, check this command line: \begin{terminal} \begin{verbatim} @@ -293,9 +361,9 @@ $ checkcites --unused document.aux \end{verbatim} \end{terminal} -The \verb|--unused| flag will make the script only look for unused -references in the \verb|.bib| file. The argument order doesn't matter, -you can also run +The \verb|--unused| flag will make the script only look for unused +references in the \verb|.bib| file. The argument order does not +matter, you can also run: \begin{terminal} \begin{verbatim} @@ -303,7 +371,7 @@ $ checkcites document.aux --unused \end{verbatim} \end{terminal} -The script will behave the same. Similarly, you can use +The script will behave the same. Similarly, you can use: \begin{terminal} \begin{verbatim} @@ -311,9 +379,9 @@ $ checkcites --undefined document.aux \end{verbatim} \end{terminal} -The \verb|--undefined| flag will make the script only look for -undefined references in the \verb|.tex| file. If you want \checkcites\ -to look for both unused and undefined references, run: +The \verb|--undefined| flag will make the script only look +for undefined references in the \verb|.tex| file. If you want +\checkcites\ to look for both unused and undefined references, run: \begin{terminal} \begin{verbatim} @@ -321,15 +389,113 @@ $ checkcites --all document.aux \end{verbatim} \end{terminal} -If no special argument is provided, the \verb|--all| flag is set as default. +If no special argument is provided, the \verb|--all| flag is set as +default. + +Observe that our example relied on the default backend, which +uses Bib\TeX. Let us change our document a bit to make it +Bib\LaTeX-compliant: + +\begin{tcblisting}{colframe=DarkTurquoise,coltitle=black,listing only, + title=Main document, fonttitle=\bfseries, + listing options={columns=fullflexible,basicstyle=\ttfamily}} +\documentclass{article} + +\usepackage{biblatex} +\addbibresource{example.bib} + +\begin{document} + +Hello world \cite{foo:2012a,foo:2012c}, +how are you \cite{foo:2012f}, +and goodbye \cite{foo:2012d,foo:2012a}. + +\printbibliography + +\end{document} +\end{tcblisting} + +As usual, let's compile our sample document \verb|document.tex|: + +\begin{terminal} +\begin{verbatim} +$ pdflatex document.tex +\end{verbatim} +\end{terminal} + +After running \verb|pdflatex| on our \verb|.tex| file, there is now a +\verb|document.aux| file in our work directory, as expected. However, +since we are using Bib\LaTeX\ as well, there is another file of +interest in our working directory, one that has a \verb|.bcf| +extension! In order to run \checkcites\ on that specific file, we +need to provide the \verb|biber| backend: + +\begin{terminal} +\begin{verbatim} +$ checkcites --backend biber document.bcf +\end{verbatim} +\end{terminal} + +We can even omit the file extension, the script will automatically +assign one based on the current backend: + +\begin{terminal} +\begin{verbatim} +$ checkcites --backend biber document +\end{verbatim} +\end{terminal} + +Now, let us run \checkcites\ on the \verb|.bcf| file, providing the +\verb|biber| backend: + +\begin{terminal} +\begin{verbatim} +$ checkcites --backend biber document.bcf +\end{verbatim} +\tcblower\small +\begin{verbatim} + _ _ _ _ + ___| |_ ___ ___| |_ ___|_| |_ ___ ___ +| _| | -_| _| '_| _| | _| -_|_ -| +|___|_|_|___|___|_,_|___|_|_| |___|___| + +checkcites.lua -- a reference checker script (v2.0) +Copyright (c) 2012, 2017, Enrico Gregorio, Paulo Roberto Massa Cereda + +Great, I found 4 citations in 1 file. I also found 1 bibliography file. Let +me check this file and extract the references. Please wait a moment. + +Fantastic, I found 5 references in 1 bibliography file. Please wait a +moment while the reports are generated. + +-------------------------------------------------------------------------- +Report of unused references in your TeX document (that is, references +present in bibliography files, but not cited in the TeX source file) +-------------------------------------------------------------------------- + +Unused references in your TeX document: 2 +=> foo:2012b +=> foo:2012e + +-------------------------------------------------------------------------- +Report of undefined references in your TeX document (that is, references +cited in the TeX source file, but not present in the bibliography files) +-------------------------------------------------------------------------- + +Undefined references in your TeX document: 1 +=> foo:2012f +\end{verbatim} +\end{terminal} + +That is all, folks! \section{License} \label{sec:license} -This script is licensed under the -\href{http://www.latex-project.org/lppl/}{LaTeX Project Public -License}. If you want to support \LaTeX{} development by a donation, -the best way to do this is donating to the +This script is licensed under the +\href{http://www.latex-project.org/lppl/}{\LaTeX\ Project Public +License}. If you want to support \LaTeX{} development by a +donation, the best way to do this is donating to the \href{http://www.tug.org/}{TeX Users Group}. \begin{infoblock}{Official code repository} diff --git a/Master/texmf-dist/scripts/checkcites/checkcites.lua b/Master/texmf-dist/scripts/checkcites/checkcites.lua index 69d79e172da..cdd0df14ef9 100755 --- a/Master/texmf-dist/scripts/checkcites/checkcites.lua +++ b/Master/texmf-dist/scripts/checkcites/checkcites.lua @@ -1,684 +1,721 @@ #!/usr/bin/env texlua --- ****************************************************************** +-- ----------------------------------------------------------------- -- checkcites.lua --- Copyright 2012 Enrico Gregorio, Paulo Roberto Massa Cereda +-- Copyright 2012, 2017, Enrico Gregorio, Paulo Roberto Massa Cereda -- --- This work may be distributed and/or modified under the --- conditions of the LaTeX Project Public License, either version 1.3 --- of this license or (at your option) any later version. +-- This work may be distributed and/or modified under the conditions +-- of the LaTeX Project Public License, either version 1.3 of this +-- license or (at your option) any later version. -- -- The latest version of this license is in --- http://www.latex-project.org/lppl.txt --- and version 1.3 or later is part of all distributions of LaTeX --- version 2005/12/01 or later. -- --- This work has the LPPL maintenance status `maintained'. --- --- The Current Maintainers of this work are the original authors. +-- http://www.latex-project.org/lppl.txt +-- +-- and version 1.3 or later is part of all distributions of LaTeX +-- version 2005/12/01 or later. -- --- This work consists of the file checkcites.lua. +-- This work has the LPPL maintenance status `maintained'. the +-- current maintainers of this work are the original authors. This +-- work consists of the file checkcites.lua. -- --- Project page: http://github.com/cereda/checkcites --- ****************************************************************** - --- version and date, to be updated on each release/commit -VERSION = "1.0i" -DATE = "December 18, 2012" - --- globals --- warning about \citation{*} -globalAsteriskWarning = true - --- The following code adds a 'split' function to the string type, --- thanks to the codebase available here: --- http://lua-users.org/wiki/SplitJoin -string.split = function(str, pattern) - - pattern = pattern or "[^%s]+" - - if pattern:len() == 0 then - - pattern = "[^%s]+" - +-- Project repository: http://github.com/cereda/checkcites +-- ----------------------------------------------------------------- + +-- Checks if the table contains the element. +-- @param a Table. +-- @param hit Element. +-- @return Boolean value if the table contains the element. +local function exists(a, hit) + for _, v in ipairs(a) do + if v == hit then + return true end - - local parts = {__index = table.insert} - - setmetatable(parts, parts) - - str:gsub(pattern, parts) - - setmetatable(parts, nil) - - parts.__index = nil - - return parts - + end + return false end --- In order to make our lives easier, we borrowed the following --- codes for implementing common set operations, available here: --- http://www.phailed.me/2011/02/common-set-operations-in-lua/ -function setFind(a, tbl) - - for _,a_ in ipairs(tbl) do - - if a_==a then - - return true +-- Parses the list of arguments based on a configuration map. +-- @param map Configuration map. +-- @param args List of command line arguments. +-- @return Table containing the valid keys and entries. +-- @return Table containing the invalid keys. +local function parse(map, args) + local keys, key, unknown = {}, 'unpaired', {} + local a, b + for _, v in ipairs(args) do + a, _, b = string.find(v, '^%-(%w)$') + if a then + for _, x in ipairs(map) do + key = 'unpaired' + if x['short'] == b then + key = x['long'] + break end - - end - -end - --- This code returns a table containing the difference between --- the two other tables, also available here: --- http://www.phailed.me/2011/02/common-set-operations-in-lua/ -function setDifference(a, b) - - local ret = {} - - for _,a_ in ipairs(a) do - - if not setFind(a_,b) then - - table.insert(ret, a_) - + end + if key == 'unpaired' then + table.insert(unknown, '-' .. b) + end + if not keys[key] then + keys[key] = {} + end + else + a, _, b = string.find(v, '^%-%-([%w-]+)$') + if a then + for _, x in ipairs(map) do + key = 'unpaired' + if x['long'] == b then + key = b + break + end end - - end - - return ret - -end - --- Code to remove duplicates from a table array. -function removeDuplicates(tb) - - -- new table - local ret = {} - - -- flag to spot new insertions - local flag - - -- set local variables - local i - local k - local j - local l - - -- iterate through the original table - for i, k in pairs(tb) do - - -- at first, insert element - flag = true - - -- iterate through the new table - for j, l in pairs(ret) do - - -- if the element already exists - if k == l then - - -- set flag to false, so the - -- new element won't be inserted - flag = false - - end - + if key == 'unpaired' then + if not exists(unknown, '--' .. b) then + table.insert(unknown, '--' .. b) + end end - - -- if it's ok to insert - if flag then - - -- insert new element - table.insert(ret, k) - + if not keys[key] then + keys[key] = {} end - - end - - -- return new table - return ret -end - --- This function opens and gets all data from the provided --- aux file. --- Return: --- * list of citations --- * list of bibfiles -function getDataFromAuxFile(theAuxFile) - - -- open a file handler - local fileHandler = io.open(theAuxFile,"r") - - -- check if the handler is valid - if fileHandler then - - -- create a local table for citations - local theCitations = {} - - -- and a local reference to the bib file - local theBibFiles = {} - - -- define local variables - local currentLine - local citation - local index - local theCitation - local theBibFile - local entry - - -- now let's iterate through the lines - for currentLine in fileHandler:lines() do - - -- if the citation matches, extract it - for citation in string.gmatch(currentLine, '\\citation{(.+)}') do - - -- sanity check, in case it's an '*' - if citation ~= "*" then - - -- break the citations list, in case of multiple - -- citations in the same \citation{} - local parts = string.split(citation, "[^,%s]+" ) - - -- for every entry in the citations list - for index, theCitation in pairs(parts) do - - -- insert the reference - table.insert(theCitations, theCitation) - - end - - -- found a '*' - else - - -- check if warning is still valid, that is, - -- if not displayed yet - if globalAsteriskWarning then - - -- show message - print("Warning: '\\nocite{*}' found, I'll do the check nonetheless.\n") - - -- warning already displayed, so - -- set flag to false - globalAsteriskWarning = false - - end - - end - - -- end of citation in current line - end - - -- in the same current line, check if there's the - -- bibdata entry and extract it - for entry in string.gmatch(currentLine, '\\bibdata{(.+)}') do - - -- break the bib files list, in case of multiple - -- files in the same \bibdata{} - local parts = string.split(entry, "[^,%s]+" ) - - -- for every entry in the bib files list - for index, theBibFile in pairs(parts) do - - -- insert the file - table.insert(theBibFiles, theBibFile) - - end - - -- end of bib files in the current line - end - - -- end of current line + else + if not keys[key] then + keys[key] = {} end - - -- close the file handler - fileHandler:close() - - -- remove duplicated citations - theCitations = removeDuplicates(theCitations) - - -- print a message about the citations - print("I found " .. #theCitations .. " citation(s).") - - -- remove possible duplicated files - theBibFiles = removeDuplicates(theBibFiles) - - -- if there are no bib files - if #theBibFiles == 0 then - - -- show message - print("I couldn't find any bibliography files.\nI'm afraid I have nothing to do now.") - - -- and abort the script - os.exit() - - -- if there is only one bib file - elseif #theBibFiles == 1 then - - -- show message - print("Great, there's only one 'bib' file. Let me check it.") - - -- there are more bib files + if key ~= 'unpaired' then + for _, x in ipairs(map) do + if x['long'] == key then + if not (x['argument'] and + #keys[key] == 0) then + key = 'unpaired' + end + break + end + end + if not keys[key] then + keys[key] = {} + end + table.insert(keys[key], v) else - - -- show message - print("Oh no, I have to check more than one 'bib' file. Please wait.") - + if not keys[key] then + keys[key] = {} + end + table.insert(keys[key], v) end - - -- return both citations and bib files - return theCitations, theBibFiles - - -- the file handler is invalid - else - - -- print an error message - print("File '" .. theAuxFile .. "' does not exist or is unavailable. Aborting script.") - - -- and abort the script - os.exit() - + end end - --- end of function + end + return keys, unknown end --- This function opens and gets all data from all the available --- bib files. -function getDataFromBibFiles(theBibFiles) - - -- create a table to store the citations - local theReferences = {} - - -- set local variables - local index - local theBibFile - local currentLine - local reference - - -- iterate through all bib files - for index, theBibFile in pairs(theBibFiles) do - - -- open the bib file - local fileHandler = io.open(theBibFile .. ".bib","r") - - -- check if the handler is valid - if fileHandler then - - -- iterate through every line of the bib file - for currentLine in fileHandler:lines() do - - -- if a reference is found - for reference in string.gmatch(currentLine, '@%w+{%s*(.+),') do - - -- insert the reference - table.insert(theReferences, reference) - - end - - -- end current line - end - - -- close the file handler - fileHandler:close() - - -- bib file does not exist - else - - -- error message - print("File '" .. theBibFile .. ".bib' does not exist. Aborting.") - - -- abort script - os.exit() - - end - - -- end iteration through the bib files +-- Calculates the difference between two tables. +-- @param a First table. +-- @param b Second table. +-- @return Table containing the difference between two tables. +local function difference(a, b) + local result = {} + for _, v in ipairs(a) do + if not exists(b, v) then + table.insert(result, v) end - - -- remove duplicated references - theReferences = removeDuplicates(theReferences) - - -- print message - print("I found " .. #theReferences .. " reference(s).") - - -- return references - return theReferences - --- end of function + end + return result end --- This function show all the undefined references. It's very --- simple, it's a difference between two sets. -function showUndefinedReferences(citations, references) - - -- get all undefined references - local undefined = setDifference(citations, references) - - -- print message - print("\nUndefined reference(s) in your TeX file: " .. #undefined) - - -- if there are undefined references - if #undefined ~= 0 then - - -- local variables - local index - local reference - - -- iterate - for index, reference in pairs(undefined) do - - -- and print - print("- " .. reference) - - end +-- Splits the string based on a pattern. +-- @param str String. +-- @param pattern Pattern. +local function split(str, pattern) + local result = {} + string.gsub(str, pattern, function(a) + table.insert(result, a) end) + return result +end +-- Reads lines from a file. +-- @param file File. +-- @returns Table representing the lines. +local function read(file) + local handler = io.open(file, 'r') + local lines = {} + if handler then + for line in handler:lines() do + table.insert(lines, line) end - --- end of function + handler:close() + end + return lines end --- This function show all the unused references. It's very --- simple, it's a difference between two sets. -function showUnusedReferences(citations, references) - --- get all undefined references - local unused = setDifference(references, citations) - - -- print message - print("\nUnused reference(s) in your bibliography file(s): " .. #unused) - - -- if there are unused references - if #unused ~= 0 then - - -- local variables - local index - local reference - - -- iterate - for index, reference in pairs(unused) do - - -- and print - print("- " .. reference) +-- Normalizes the string, removing leading and trailing spaces. +-- @param str String. +-- @return Normalized string without leading and trailing spaces. +local function normalize(str) + local result, _ = string.gsub(str, '^%s', '') + result, _ = string.gsub(result, '%s$', '') + return result +end - end - +-- Checks if the element is in a blacklist. +-- @param a Element. +-- @return Boolean value if the element is blacklisted. +local function blacklist(a) + local list = {} + for _, v in ipairs(list) do + if v == a then + return true end - --- end of function + end + return false end --- This function parses the command line arguments and returns a --- bunch of info for us to use. --- Return: --- * an argument code --- * the filename --- * an action code -function parseArguments(theArgs) - - -- check if there are no arguments - if #theArgs == 0 then - - -- return usage code - return 0, nil, nil - - -- there is one argument - elseif #theArgs == 1 then - - -- check if it's help - if theArgs[1] == "--help" then - - -- return help code - return 1, nil, nil - - -- check if it's version - elseif theArgs[1] == "--version" then - - -- return version code - return 2, nil, nil - - -- check if it's invalid - elseif string.sub(theArgs[1], 1, 1) == "-" then - - -- return invalid code - return 3, nil, nil - - -- it seems a clean argument - else +-- Extracts the biblographic key. +-- @param lines Lines of a file. +-- @return Table containing bibliographic keys. +local function extract(lines) + local result = {} + for _, line in ipairs(lines) do + local hit = string.match(line, + '^%s*%@%w+%s*{%s*(.+),') + if hit then + if not exists(result, hit) then + hit = normalize(hit) + table.insert(result, hit) + end + end + end + return result +end - -- return it as a valid argument - return 4, theArgs[1], nil +-- Gets a pluralized word based on a counter. +-- @param i Counter. +-- @param a Word in singular. +-- @param b Word in plural. +-- @return Either the first or second word based on the counter. +local function plural(i, a, b) + if i == 1 then + return a + else + return b + end +end - -- end for one parameter +-- Backend namespace +local backends = {} + +-- Gets data from auxiliary files (BibTeX). +-- @param lines Lines of a file. +-- @return Boolean indicating if an asterisk was found. +-- @return Table containing the citations. +-- @return Table containing the bibliography files. +backends.bibtex = function(lines) + local citations, bibliography = {}, {} + local asterisk, parts, hit = false + for _, line in ipairs(lines) do + hit = string.match(line, '^%s*\\citation{(.+)}$') + if hit then + if hit ~= '*' then + parts = split(hit, '[^,%s]+') + for _, v in ipairs(parts) do + v = normalize(v) + if not exists(citations, v) then + table.insert(citations, v) + end end - - -- there are two arguments - elseif #theArgs == 2 then - - -- check if both are valid - if ((theArgs[1] == "--all" or theArgs[1] == "--unused" or theArgs[1] == "--undefined") and string.sub(theArgs[2], 1, 1) ~= "-") or ((theArgs[2] == "--all" or theArgs[2] == "--unused" or theArgs[2] == "--undefined") and string.sub(theArgs[1], 1, 1) ~= "-") then - - -- create an action code - local actionCode - - -- check which one is the file name - if string.sub(theArgs[1], 1, 1) ~= "-" then - - -- check for --all - if theArgs[2] == "--all" then - - -- set the action code - actionCode = 0 - - -- check for --unused - elseif theArgs[2] == "--unused" then - - -- set the action code - actionCode = 1 - - -- it's --undefined - else - - -- set the action code - actionCode = 2 - - end - - -- it's the first - return 4, theArgs[1], actionCode - - else - - -- check for --all - if theArgs[1] == "--all" then - - -- set the action code - actionCode = 0 - - -- check for --unused - elseif theArgs[1] == "--unused" then - - -- set the action code - actionCode = 1 - - else - - -- it's --undefined - actionCode = 2 - - end - - -- it's the second - return 4, theArgs[2], actionCode - - end - - else - - -- return invalid code - return 3, nil, nil - + else + asterisk = true + end + else + hit = string.match(line, '^%s*\\bibdata{(.+)}$') + if hit then + parts = split(hit, '[^,%s]+') + for _, v in ipairs(parts) do + v = normalize(v) + if not exists(bibliography, v) and + not blacklist(v) then + table.insert(bibliography, v) + end end + end + end + end + return asterisk, citations, bibliography +end +-- Gets data from auxiliary files (Biber). +-- @param lines Lines of a file. +-- @return Boolean indicating if an asterisk was found. +-- @return Table containing the citations. +-- @return Table containing the bibliography files. +backends.biber = function(lines) + local citations, bibliography = {}, {} + local asterisk, parts, hit = false + for _, line in ipairs(lines) do + hit = string.match(line, '^%s*<bcf:citekey order="%d+">' .. + '(.+)</bcf:citekey>$') + if hit then + if hit ~= '*' then + parts = split(hit, '[^,%s]+') + for _, v in ipairs(parts) do + v = normalize(v) + if not exists(citations, v) then + table.insert(citations, v) + end + end + else + asterisk = true + end else - - -- more than two arguments, return usage code - return 0, nil, nil - + hit = string.match(line, '^%s*<bcf:datasource type="file" ' .. + 'datatype="%w+">(.+)</bcf:datasource>$') + if hit then + parts = split(hit, '[^,%s]+') + for _, v in ipairs(parts) do + v = normalize(v) + if not exists(bibliography, v) and + not blacklist(v) then + table.insert(bibliography, v) + end + end + end end - --- end of function + end + return asterisk, citations, bibliography end --- This function prints the script header. -function printHeader() - - -- print message - print("checkcites.lua -- a reference checker script (v" .. VERSION .. ")") - print("Copyright (c) 2012 Enrico Gregorio, Paulo Roberto Massa Cereda\n") - --- end of function +-- Counts the number of elements of a nominal table. +-- @param t Table. +-- @return Table size. +local function count(t) + local counter = 0 + for _, _ in pairs(t) do + counter = counter + 1 + end + return counter end --- This function prints the script usage -function printUsage() - - -- show message - print("Usage: " .. arg[0] .. " [--all | --unused | --undefined] file.aux\n") - print("--all Lists all unused and undefined references.") - print("--unused Lists only unused references in your 'bib' file.") - print("--undefined Lists only undefined references in your 'tex' file.\n") - print("If no flag is provided, '--all' is set by default.") - print("Be sure to have all your 'bib' files in the same directory.") - --- end of function +-- Repeats the provided char a certain number of times. +-- @param c Char. +-- @param w Number of times. +-- @return String with a char repeated a certain number of times. +local function pad(c, w) + local r = c + while #r < w do + r = r .. c + end + return r end -function printHelp() - - -- show message - print("checkcites.lua is a Lua script written for the sole purpose of") - print("detecting undefined/unused references from LaTeX auxiliary or") - print("bibliography files. It's very easy to use!\n") - - -- print usage - printUsage() - --- end of function +-- Adds the extension if the file does not have it. +-- @param file File. +-- @param extension Extension. +-- @return File with proper extension. +local function sanitize(file, extension) + extension = '.' .. extension + if string.sub(file, -#extension) ~= extension then + file = file .. extension + end + return file end --- This function prints the script version. -function printVersion() - - -- print message - print("checkcites.lua version " .. VERSION .. " (dated " .. DATE .. ")") - print("You can find more information about this script in the official") - print("source code repository:\n") - print("http://github.com/cereda/checkcites\n") - print("checkcites.lua is licensed under the LaTeX Project Public License.") - --- end of function +-- Flattens a table of tables into only one table. +-- @param t Table. +-- @return Flattened table. +local function flatten(t) + local result = {} + for _, v in ipairs(t) do + for _, k in ipairs(v) do + if not exists(result, k) then + table.insert(result, k) + end + end + end + return result end --- This function prints a message for invalid parameters. -function printInvalid() - - -- print message - print("Oh no, it seems you used an invalid argument.\n") - - -- print usage - printUsage() - --- end of function +-- Applies a function to elements of a table. +-- @param c Table. +-- @param f Function. +-- @return A new table. +local function apply(c, f) + local result = {} + for _, v in ipairs(c) do + table.insert(result, f(v)) + end + return result end --- This is our main function. -function runMain(theArgs) - - -- print the script header - printHeader() - - -- set local variables - local argCode - local fileName - local actionCode - - -- parse arguments and get the result - argCode, fileName, actionCode = parseArguments(theArgs) - - -- check for usage - if argCode == 0 then - - -- print usage - printUsage() - - -- it's help - elseif argCode == 1 then - - -- print help - printHelp() - - -- it's version - elseif argCode == 2 then - - -- print version - printVersion() - - -- it's an invalid parameter - elseif argCode == 3 then - - -- print invalid - printInvalid() - - -- it's a valid operation +-- Wraps a string based on a line width. +-- @param str String. +-- @param size Line width. +-- @return Wrapped string. +local function wrap(str, size) + local parts = split(str, '[^%s]+') + local r, l = '', '' + for _, v in ipairs(parts) do + if (#l + #v) > size then + r = r .. '\n' .. l + l = v else + l = normalize(l .. ' ' .. v) + end + end + r = normalize(r .. '\n' .. l) + return r +end - -- get data from aux - a, b = getDataFromAuxFile(fileName) - - -- get data from bib - c = getDataFromBibFiles(b) - - -- if there is an action code - if actionCode ~= nil then - - -- it's --all - if actionCode == 0 then - - -- do everything - showUnusedReferences(a,c) - showUndefinedReferences(a,c) - - -- it's --unused - elseif actionCode == 1 then +-- Prints the script header. +local function header() +print(" _ _ _ _") +print(" ___| |_ ___ ___| |_ ___|_| |_ ___ ___") +print("| _| | -_| _| '_| _| | _| -_|_ -|") +print("|___|_|_|___|___|_,_|___|_|_| |___|___|") +print() + print(wrap('checkcites.lua -- a reference ' .. + 'checker script (v2.0)', 74)) + print(wrap('Copyright (c) 2012, 2017, ' .. + 'Enrico Gregorio, Paulo ' .. + 'Roberto Massa Cereda', 74)) +end - -- only show unused - showUnusedReferences(a,c) +-- Operation namespace +local operations = {} + +-- Reports the unused references. +-- @param citations Citations. +-- @param references References. +-- @return Integer representing the status. +operations.unused = function(citations, references) + print() + print(pad('-', 74)) + print(wrap('Report of unused references in your TeX ' .. + 'document (that is, references present in ' .. + 'bibliography files, but not cited in ' .. + 'the TeX source file)', 74)) + print(pad('-', 74)) + local r = difference(references, citations) + print() + print(wrap('Unused references in your TeX document: ' .. + tostring(#r), 74)) + if #r == 0 then + return 0 + else + for _, v in ipairs(r) do + print('=> ' .. v) + end + return 1 + end +end - -- it's --undefined - else +-- Reports the undefined references. +-- @param citations Citations. +-- @param references References. +-- @return Integer value indicating the status. +operations.undefined = function(citations, references) + print() + print(pad('-', 74)) + print(wrap('Report of undefined references in your TeX ' .. + 'document (that is, references cited in the ' .. + 'TeX source file, but not present in the ' .. + 'bibliography files)', 74)) + print(pad('-', 74)) + local r = difference(citations, references) + print() + print(wrap('Undefined references in your TeX document: ' .. + tostring(#r), 74)) + if #r == 0 then + return 0 + else + for _, v in ipairs(r) do + print('=> ' .. v) + end + return 1 + end +end - -- only show undefined - showUndefinedReferences(a,c) +-- Reports both unused and undefined references. +-- @param citations Citations. +-- @param references References. +-- @return Integer value indicating the status. +operations.all = function(citations, references) + local x, y + x = operations.unused(citations, references) + y = operations.undefined(citations, references) + if x + y > 0 then + return 1 + else + return 0 + end +end - end +-- Checks if a file exists. +-- @param file File. +-- @return Boolean value indicating if the file exists. +local function valid(file) + local handler = io.open(file, 'r') + if handler then + handler:close() + return true + else + return false + end +end - -- there's only one parameter, the file name, - -- so we set --all - else +-- Filters a table of files, keeping the inexistent ones. +-- @param files Table. +-- @return Table of inexistent files. +local function validate(files) + local result = {} + for _, v in ipairs(files) do + if not valid(v) then + table.insert(result, v) + end + end + return result +end - -- show everything - showUnusedReferences(a,c) - showUndefinedReferences(a,c) +-- Main function. +-- @param args Command line arguments. +-- @return Integer value indicating the status +local function checkcites(args) + header() + + local parameters = { + { short = 'a', long = 'all', argument = false }, + { short = 'u', long = 'unused', argument = false }, + { short = 'U', long = 'undefined', argument = false }, + { short = 'v', long = 'version', argument = false }, + { short = 'h', long = 'help', argument = false }, + { short = 'b', long = 'backend', argument = true } + } + + local keys, err = parse(parameters, args) + local check, backend = 'all', 'bibtex' + + if #err ~= 0 then + print() + print(pad('-', 74)) + print(wrap('I am sorry, but I do not recognize ' .. + 'the following ' .. plural(#err, 'option', + 'options') .. ':', 74)) + for _, v in ipairs(err) do + print('=> ' .. v) + end - end + print() + print(wrap('Please make sure to use the correct ' .. + 'options when running this script. You ' .. + 'can also refer to the user documentation ' .. + 'for a list of valid options. The script ' .. + 'will end now.', 74)) + return 1 + end + + if count(keys) == 0 then + print() + print(pad('-', 74)) + print(wrap('I am sorry, but you have not provided ' .. + 'any command line argument, including ' .. + 'files to check and options. Make ' .. + 'sure to invoke the script with the actual ' .. + 'arguments. Refer to the user documentation ' .. + 'if you are unsure of how this tool ' .. + 'works. The script will end now.', 74)) + return 1 + end + + if keys['version'] or keys['help'] then + if keys['version'] then + print() + print(wrap('checkcites.lua, version 2.0 (dated August ' .. + '25, 2017)', 74)) + + print(pad('-', 74)) + print(wrap('You can find more details about this ' .. + 'script, as well as the user documentation, ' .. + 'in the official source code repository:', 74)) + + print() + print('https://github.com/cereda/checkcites') + + print() + print(wrap('The checkcites.lua script is licensed ' .. + 'under the LaTeX Project Public License, ' .. + 'version 1.3. The current maintainers ' .. + 'are the original authors.', 74)) + else + print() + print(wrap('Usage: ' .. args[0] .. ' [ [ --all | --unused | ' .. + '--undefined ] [ --backend <arg> ] <file> [ ' .. + '<file 2> ... <file n> ] | --help | --version ' .. + ']', 74)) + + print() + print('-a,--all list all unused and undefined references') + print('-u,--unused list only unused references in your bibliography files') + print('-U,--undefined list only undefined references in your TeX source file') + print('-b,--backend <arg> set the backend-based file lookup policy') + print('-h,--help print the help message') + print('-v,--version print the script version') + + print() + print(wrap('Unless specified, the script lists all unused and ' .. + 'undefined references by default. Also, the default ' .. + 'backend is set to "bibtex". Please refer to the user ' .. + 'documentation for more details.', 74)) + end + return 0 + end + + if not keys['unpaired'] then + print() + print(pad('-', 74)) + print(wrap('I am sorry, but you have not provided ' .. + 'files to process. The tool requires ' .. + 'least one file in order to properly ' .. + 'work. Make sure to invoke the script ' .. + 'with an actual file (or files). Refer ' .. + 'to the user documentation if you are ' .. + 'unsure of how this tool works. The ' .. + 'script will end now.', 74)) + return 1 + end + + if keys['backend'] then + if not exists({ 'bibtex', 'biber' }, keys['backend'][1]) then + print() + print(pad('-', 74)) + print(wrap('I am sorry, but you provided an ' .. + 'invalid backend. I know two: ' .. + '"bibtex" (which is the default ' .. + 'one) and "biber". Please make ' .. + 'sure to select one of the two. ' .. + 'Also refer to the user documentation ' .. + 'for more information on how these ' .. + 'backends work. The script will end ' .. + 'now.', 74)) + return 1 + else + backend = keys['backend'][1] + end + end + + if not keys['all'] then + if keys['unused'] and keys['undefined'] then + check = 'all' + elseif keys['unused'] or keys['undefined'] then + check = (keys['unused'] and 'unused') or + (keys['undefined'] and 'undefined') + end + end + + local auxiliary = apply(keys['unpaired'], function(a) + return sanitize(a, (backend == 'bibtex' + and 'aux') or 'bcf') end) + + local vld = validate(auxiliary) + if #vld ~= 0 then + print() + print(pad('-', 74)) + print(wrap('I am sorry, but I was unable to ' .. + 'locate ' .. plural(#vld, 'this file', + 'these files') .. ' (the extension ' .. + 'is automatically set based on the ' .. + '"' .. backend .. '" backend):', 74)) + for _, v in ipairs(vld) do + print('=> ' .. v) + end + print() + print(wrap('Selected backend: ' .. backend, 74)) + print(wrap('File lookup policy: add ".' .. + ((backend == 'bibtex' and 'aux') or 'bcf') .. + '" to files if not provided.', 74)) + + print() + print(wrap('Please make sure the ' .. plural(#vld, + 'path is', 'paths are') .. ' ' .. + 'correct and the ' .. plural(#vld, + 'file exists', 'files exist') .. '. ' .. + 'There is nothing I can do at the moment. ' .. + 'Refer to the user documentation for ' .. + 'details on the file lookup. If ' .. plural(#vld, + 'this is not the file', 'these are not the ' .. + 'files') .. ' you were expecting, ' .. + 'double-check your source file or ' .. + 'change the backend option when running ' .. + 'this tool. The script will end now.', 74)) + return 1 + end + + local lines = flatten(apply(auxiliary, read)) + local asterisk, citations, bibliography = backends[backend](lines) + + print() + print(wrap('Great, I found ' .. tostring(#citations) .. ' ' .. + plural(#citations, 'citation', 'citations') .. ' in ' .. + tostring(#auxiliary) .. ' ' .. plural(#auxiliary, 'file', + 'files') ..'. I also found ' .. tostring(#bibliography) .. + ' ' .. 'bibliography ' .. plural(#bibliography, 'file', + 'files') .. '. Let me check ' .. plural(#bibliography, + 'this file', 'these files') .. ' and extract the ' .. + 'references. Please wait a moment.', 74)) + + if asterisk then + print() + print(wrap('Also, it is worth noticing that I found a mention to ' .. + 'a special "*" when retrieving citations. That means ' .. + 'your TeX document contains "\\nocite{*}" somewhere in ' .. + 'the source code. I will continue with the check ' .. + 'nonetheless.', 74)) + end + + bibliography = apply(bibliography, function(a) + return sanitize(a, 'bib') end) + + vld = validate(bibliography) + if #vld ~= 0 then + print() + print(pad('-', 74)) + print(wrap('I am sorry, but I was unable to locate ' .. + plural(#vld, 'this file', 'these files') .. ' ' .. + '(the extension is automatically set to ' .. + '".bib", if not provided):', 74)) + for _, v in ipairs(vld) do + print('=> ' .. v) end --- end of function + print() + print(wrap('Please make sure the ' .. plural(#vld, + 'path is', 'paths are') .. ' ' .. + 'correct and the ' .. plural(#vld, + 'file exists', 'files exist') .. '. ' .. + 'There is nothing I can do at the moment. ' .. + 'Refer to to the user documentation ' .. + 'for details on bibliography lookup. If ' .. + plural(#vld, 'this is not the file', + 'these are not the files') .. ' you were ' .. + 'expecting (wrong bibliography), double-check ' .. + 'your source file. The script will end ' .. + 'now.', 74)) + return 1 + end + + local references = flatten(apply(bibliography, function(a) + return extract(read(a)) end)) + + print() + print(wrap('Fantastic, I found ' .. tostring(#references) .. + ' ' .. plural(#references, 'reference', + 'references') .. ' in ' .. tostring(#bibliography) .. + ' bibliography ' .. plural(#bibliography, 'file', + 'files') .. '. Please wait a moment while the ' .. + plural(((check == 'all' and 2) or 1), 'report is', + 'reports are') .. ' generated.', 74)) + + return operations[check](citations, references) end --- run the main function -runMain(arg) +-- Call and exit +os.exit(checkcites(arg)) + +-- EOF + |