Skip to content

Instantly share code, notes, and snippets.

@wtsnjp wtsnjp/sctexdoc.lua
Last active Apr 1, 2019

Embed
What would you like to do?
画期的な Texdoc
#!/usr/bin/env texlua
--
-- This is file 'sctexdoc.lua'.
--
-- Copyright 2017-2019 Takuto ASAKURA (wtsnjp)
-- GitHub: https://github.com/wtsnjp
-- Twitter: @wtsnjp
--
-- This package is distributed under the MIT License.
--
-- program information
prog_name = 'sctexdoc'
version = '0.5'
release_date = '2019-04-01'
author = 'Takuto ASAKURA (wtsnjp)'
-- option flags (default)
mode = 'view'
interaction = true
machine = false
debug = {
['config'] = false,
['score'] = false,
['snowman'] = false,
['version'] = false,
}
first_debug = true
verbosity_level = 0
-- config
config = {}
config_color = 'black'
----------------------------------------
-- library
require 'lfs'
-- global functions
function log(label, msg, ...)
io.stderr:write(prog_name .. ' ' .. label .. ': ' .. msg:format(...) .. '\n')
end
function err_print(err_type, msg, ...)
if (verbosity_level > 0) or (err_type == 'error')
or (verbosity_level > -1 and err_type == 'warning') then
log(err_type, msg, ...)
end
end
function dbg_print(dbg_type, msg, ...)
if debug[dbg_type] then
log('debug-' .. dbg_type, msg, ...)
end
end
-- short function names
cd = lfs.chdir
pwd = lfs.currentdir
mv = os.rename
rm = os.remove
mkdir = lfs.mkdir
rmdir = lfs.rmdir
random = math.random
----------------------------------------
do
-- aprilfool
local today = os.date('*t')
local aprilfool = (today.month == 4 and today.day == 1)
-- types of options
local bool = {'true', 'false'}
local colors = {
'black',
'red',
'green',
'blue',
'cyan',
'magenta',
'yellow',
'brown'
}
local mouthshape = {'smile', 'tight', 'frown'}
-- know options
local scsnowman_opts = {
['body'] = bool,
['mouthshape'] = mouthshape,
['nose'] = colors,
['sweat'] = bool,
['hat'] = colors,
['arms'] = colors,
['muffler'] = colors,
['buttons'] = colors,
['note'] = colors,
['broom'] = colors,
['snow'] = bool,
}
local function check_exists(val, tab)
for _, v in pairs(tab) do
if v == val then return true end
end
return false
end
local function count_table(tab)
local cnt = 0
for _, _ in pairs(tab) do
cnt = cnt + 1
end
return cnt
end
function set_config(item, value)
-- check the item
if item ~= 'color' and scsnowman_opts[item] == nil then
err_print('warning', 'Unknown configuration item "%s". Skipping.', item)
return
end
-- check the value
local ival = false
if item == 'mouthshape' then
if not check_exists(value, mouthshape) then
ival = true
end
else
if not (check_exists(value, colors) or check_exists(value, bool)) then
ival = true
end
end
if ival then
err_print('warning', 'Illegal value "%s" for option "%s". Skipping.',
value, item)
return
end
-- set config
dbg_print('config',
'Setting "%s=%s" from command line option "-c".', item, value)
if item == 'color' then
config_color = value
else
config[item] = value
end
end
local function run_luatex(fn)
local tex_cmd = 'lualatex --halt-on-error "' .. fn .. '"'
err_print('info', 'TeX command: ' .. tex_cmd)
if verbosity_level < 2 then
tex_cmd = tex_cmd .. ' >' .. fn .. '.out'
end
os.execute(tex_cmd)
end
local function rm_all(dir)
for f in lfs.dir(dir) do
rm(dir .. '/' .. f)
end
rmdir(dir)
end
local function scsnowman_src()
-- the template
local template = [[
\RequirePackage{luatex85}
\documentclass{standalone}
\usepackage{scsnowman}
\begin{document}
\color{%s}
\scsnowman[%s]
\end{document}
]]
local config_exists = false
local opts = 'scale=50'
local color
-- apply config, if specified
for k, v in pairs(config) do
config_exists = true
opts = opts .. ', ' .. k .. '=' .. v
end
-- otherwise, select randomly
if config_exists then
color = config_color
dbg_print('snowman', 'Using manually specified configuration.')
else
color = colors[random(#colors)]
dbg_print('snowman', 'Color "%s" is selected.', color)
local cnt = 0
local limit = random(count_table(scsnowman_opts))
for k, v in pairs(scsnowman_opts) do
cnt = cnt + 1
local tmp = ', ' .. k .. '=' .. v[random(#v)]
if v ~= colors or random(2) == 1 then
opts = opts .. tmp
dbg_print('snowman', 'Option "%s" is selected.', tmp:sub(3, -1))
end
if cnt >= limit then
break
end
end
end
return template:format(color, opts)
end
local function tikzducks_src()
-- the template
local template = [[
\RequirePackage{luatex85}
\documentclass[tikz,border=20pt]{standalone}
\usepackage{tikzducks}
\begin{document}
\begin{tikzpicture}
\duck[scale=5]
\end{tikzpicture}
\end{document}
]]
return template
end
local function generate_latex_src()
if aprilfool then
return tikzducks_src()
else
return scsnowman_src()
end
end
function generate_doc(keyword)
doc_name = keyword .. '-' .. prog_name
local latex_fn = doc_name .. '.tex'
local pdf_fn = doc_name .. '.pdf'
local sandbox = doc_name .. '-sandbox'
local origin = pwd()
-- sandbox
mkdir(sandbox)
cd(sandbox)
-- luatex
local latex_src = generate_latex_src()
local f = io.open(latex_fn, 'w')
f:write(latex_src)
f:close()
run_luatex(latex_fn)
mv(pdf_fn, origin .. '/' .. pdf_fn)
-- clean up
cd(origin)
rm_all(sandbox)
return pdf_fn
end
end
----------------------------------------
do
-- exit codes
local exit_ok = 0
local exit_error = 1
local exit_usage = 2
-- help texts
local usage_text = [[
Usage: sctexdoc[.lua] [OPTION...] NAME...
or: sctexdoc[.lua] ACTION
Try to find `ESSENTIAL' TeX documentation for the specified NAME(s).
Alternatively, perform the given ACTION and exit.
Options:
-w, --view Use view mode: start a viewer. (default)
-m, --mixed Use mixed mode (view or list).
-l, --list Use list mode: show a list of results.
-s, --showall Use showall mode: show also "bad" results.
-i, --interact Use interactive menus. (default)
-I, --nointeract Use plain lists, no interaction required.
-M, --machine Machine-readable output for lists (implies -I).
-q, --quiet Suppress warnings and most error messages.
-v, --verbose Print additional information (e.g., viewer command).
-D, --debug Activate all debug output (equal to "--debug=all").
-d LIST, --debug=LIST
Activate debug output restricted to LIST.
-c NAME=VALUE Set configuration item NAME to VALUE.
Actions:
-h, --help Print this help message.
-V, --version Print the version number.
-f, --files Print the list of configuration files used.
--just-view FILE Display file, given with full path (no searching).
An `ESSENTIAL' manual available via `sctexdoc sctexdoc'.
Source: <https://gist.github.com/wtsnjp/3bfcdb32420fa591c9fe641dbe932d38>
Please report bugs to <wtsnjp@gmail.com>.
]]
local version_text = [[
%s %s (%s)
Copyright 2017 %s.
License: The MIT License <https://opensource.org/licenses/mit-license.php>.
This is free software: you are free to change and redistribute it.
]]
local error_msg = "Try `sctexdoc[.lua] --help' for a short help."
-- show uasage / help
local function show_usage(out)
out:write(usage_text)
end
-- open document
local function open_doc(fn)
local view_cmd
if os.name == 'macosx' then
view_cmd = 'open "' .. fn .. '"'
elseif os.name == 'windows' then
view_cmd = 'start "' .. fn:gsub('/', '\\') .. '"'
else
view_cmd = '(xdg-open "' .. fn .. '") &'
end
err_print('info', 'View command: ' .. view_cmd)
os.execute(view_cmd)
end
-- execution functions
local function read_options()
if #arg == 0 then
show_usage(io.stderr)
os.exit(exit_usage)
end
local curr_arg
local action = false
-- modified Alternative Get Opt
-- cf. http://lua-users.org/wiki/AlternativeGetOpt
local function getopt(arg, options)
local tmp
local tab = {}
local saved_arg = { table.unpack(arg) }
for k, v in ipairs(saved_arg) do
if string.sub(v, 1, 2) == "--" then
table.remove(arg, 1)
local x = string.find(v, "=", 1, true)
if x then
table.insert(tab, { string.sub(v, 3, x-1), string.sub(v, x+1) })
else
table.insert(tab, { string.sub(v, 3), true })
end
elseif string.sub(v, 1, 1) == "-" then
table.remove(arg, 1)
local y = 2
local l = string.len(v)
local jopt
while (y <= l) do
jopt = string.sub(v, y, y)
if string.find(options, jopt, 1, true) then
if y < l then
tmp = string.sub(v, y+1)
y = l
else
table.remove(arg, 1)
tmp = saved_arg[k + 1]
end
if string.match(tmp, '^%-') then
table.insert(tab, { jopt, false })
else
table.insert(tab, { jopt, tmp })
end
else
table.insert(tab, { jopt, true })
end
y = y + 1
end
end
end
return tab
end
opts = getopt(arg, 'cd')
for _, tp in ipairs(opts) do
k, v = tp[1], tp[2]
if #k == 1 then
curr_arg = '-' .. k
else
curr_arg = '--' .. k
end
-- action
if (curr_arg == '-h') or (curr_arg == '--help') then
action = 'help'
elseif (curr_arg == '-V') or (curr_arg == '--version') then
action = 'version'
elseif (curr_arg == '-f') or (curr_arg == '--files') then
action = 'file'
elseif curr_arg == '--just-view' then
action = 'view'
-- mode
elseif (curr_arg == '-w') or (curr_arg == '--view') then
mode = 'view'
elseif (curr_arg == '-m') or (curr_arg == '--mixed') then
mode = 'view'
elseif (curr_arg == '-l') or (curr_arg == '--list') then
mode = 'list'
elseif (curr_arg == '-s') or (curr_arg == '--showall') then
mode = 'list'
-- interaction
elseif (curr_arg == '-I') or (curr_arg == '--nointeract') then
interaction = false
elseif (curr_arg == '-i') or (curr_arg == '--interact') then
interaction = true
elseif (curr_arg == '-M') or (curr_arg == '--machine') then
machine = true
-- config
elseif curr_arg == '-c' then
local item, value = string.match(v, '^([%a%d_]+)%s*=%s*(.+)')
set_config(item, value)
-- debug
elseif (curr_arg == '-D') or (curr_arg == '--debug' and v == 'all') then
if verbosity_level < 1 then verbosity_level = 1 end
for c, _ in pairs(debug) do
debug[c] = true
end
if first_debug then
first_debug = false
dbg_print('version', curr_file .. ' version ' .. version)
end
elseif (curr_arg == '-d') or (curr_arg == 'debug') then
if verbosity_level < 1 then verbosity_level = 1 end
if debug[v] == nil then
err_print('warning', 'unknown debug category: ' .. v)
else
debug[v] = true
end
debug['version'] = true
if first_debug then
first_debug = false
dbg_print('version', curr_file .. ' version ' .. version)
end
-- verbosity
elseif (curr_arg == '-q') or (curr_arg == '--quiet') then
verbosity_level = 0
elseif (curr_arg == '-v') or (curr_arg == '--verbose') then
verbosity_level = 2
-- problem
else
err_print('error', 'unknown option: ' .. curr_arg)
err_print('error', error_msg)
os.exit(exit_error)
end
end
return action
end
local function collect_info()
local absolute = arg[0]:match("^/") or arg[0]:match("^.:[/\\]")
local curr_file = absolute and arg[0] or (pwd() .. '/' .. arg[0])
return curr_file
end
local function do_action()
if action == 'help' then
show_usage(io.stdout)
elseif action == 'version' then
io.stdout:write(version_text:format(
prog_name, version, release_date, author))
elseif action == 'file' then
print(curr_file .. ' ' .. version)
elseif action == 'view' then
if arg[1] then
open_doc(arg[1])
else
show_usage(io.stderr)
os.exit(exit_usage)
end
end
end
local function show_doc()
local curr_kw
local pdf_fn
local pdf_path
local score
local doc_type = 'Essential documentation'
local prompt = [[
Enter number of file to view, RET to view 1, anything else to skip: ]]
-- make sure we actually have argument(s)
if not arg[1] then
show_usage(io.stderr)
os.exit(exit_usage)
end
local mul = #arg > 1
-- set randomseed once
math.randomseed(os.time())
while arg[1] do
curr_kw = table.remove(arg, 1)
pdf_fn = generate_doc(curr_kw)
pdf_path = pwd() .. '/' .. pdf_fn
-- set score (fake)
dbg_print('score', '----------')
dbg_print('score', 'Start scoring ' .. pdf_path)
dbg_print('score', 'New heuristic score: 88. Reason: essential')
score = 88
dbg_print('score', 'Final score: 88')
-- show
if mode == 'list' then
if mul then
print('*** Results for: ' .. curr_kw .. ' ***')
end
if machine then
print(curr_kw .. '\t' .. score .. '\t' .. pdf_path
.. '\t' .. doc_type)
else
print(' 1 ' .. pdf_path)
print(' = ' .. doc_type)
if interaction then
io.stdout:write(prompt)
local input = io.read()
if input == '1' or input == '' then
open_doc(pdf_path)
end
end
end
elseif mode == 'view' then
open_doc(pdf_path)
else
err_print('error', 'unknown mode: ' .. mode)
os.exit(exit_error)
end
end
end
function main()
curr_file = collect_info()
action = read_options()
if action then
do_action()
os.exit(exit_ok)
end
show_doc()
os.exit(exit_ok)
end
end
----------------------------------------
main()
-- EOF
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.