Last active
August 10, 2020 13:12
-
-
Save wtsnjp/3bfcdb32420fa591c9fe641dbe932d38 to your computer and use it in GitHub Desktop.
画期的な Texdoc
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env texlua | |
-- | |
-- This is file 'sctexdoc.lua'. | |
-- | |
-- Copyright 2017-2020 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.6' | |
release_date = '2020-08-10' | |
author = 'Takuto ASAKURA (wtsnjp)' | |
-- option flags (default) | |
mode = 'view' | |
mixed = false | |
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 not mixed and 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] [OPTION]... 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 color mode (no sanity check for colors). | |
-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 <[email protected]>. | |
]] | |
local version_text = [[ | |
%s %s (%s) | |
Copyright 2017-2020 %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 | |
mixed = true | |
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