1#!/usr/bin/lua5.2
2
3-- CivetWeb command line completion for bash
4
5--[[ INSTALLING:
6
7To use auto-completion for bash, you need to define a command for the bash built-in
8[*complete*](https://www.gnu.org/software/bash/manual/html_node/Programmable-Completion-Builtins.html).
9
10Create a file called "civetweb" in the completion folder.
11Depending on Linux distribution and version, this might be
12/usr/share/bash-completion/completions/, /etc/bash_completion or another folder.
13
14The file has to contain just one line:
15complete -C /path/to/civetweb/resources/complete.lua civetweb
16
17The complete command script is implemented in this file.
18It needs Lua 5.2 to be installed (for Debian based systems: "sudo apt-get install lua5.2").
19In case lua5.2 is not located in /usr/bin/lua5.2 (see "which lua5.2"),
20the first line (#!) needs to be adapted accordingly.
21
22--]]
23
24---------------------------------------------------------------------------------------------------
25
26-- The bash "complete -C" has an awkward interface:
27-- see https://unix.stackexchange.com/questions/250262/how-to-use-bashs-complete-or-compgen-c-command-option
28-- Command line arguments
29cmd = arg[1] -- typically "./civetweb" or whatever path was used
30this = arg[2] -- characters already typed for the next options
31last = arg[3] -- option before this one
32-- Environment variables
33comp_line = os.getenv("COMP_LINE") -- entire command line
34comp_point = os.getenv("COMP_POINT") -- position of cursor (index)
35comp_type = os.getenv("COMP_TYPE") -- type:
36-- 9 for normal completion
37-- 33 when listing alternatives on ambiguous completions
38-- 37 for menu completion
39-- 63 when tabbing between ambiguous completions
40-- 64 to list completions after a partial completion
41
42
43-- Debug-Print function (must use absolute path for log file)
44function dp(txt)
45  --[[ comment / uncomment to enable debugging
46  local f = io.open("/tmp/complete.log", "a");
47  f:write(txt .. "\n")
48  f:close()
49  --]]
50end
51
52-- Helper function: Check if files exist
53function fileexists(name)
54  local f = io.open(name, "r")
55  if f then
56    f:close()
57    return true
58  end
59  return false
60end
61
62
63-- Debug logging
64dp("complete: cmd=" .. cmd .. ", this=" .. this .. ", last=" .. last .. ", type=" .. comp_type)
65
66
67-- Trim command line (remove spaces)
68trim_comp_line = string.match(comp_line, "^%s*(.-)%s*$")
69
70if (trim_comp_line == cmd) then
71  -- this is the first option
72  dp("Suggest --help argument")
73  print("--help ")
74  os.exit(0)
75end
76
77is_h = string.find(comp_line, "^%s*" .. cmd .. "%s+%-h%s")
78is_h = is_h or string.find(comp_line, "^%s*" .. cmd .. "%s+%--help%s")
79is_h = is_h or string.find(comp_line, "^%s*" .. cmd .. "%s+%-H%s")
80
81if (is_h) then
82  dp("If --help is used, no additional argument is allowed")
83  os.exit(0)
84end
85
86is_a = string.find(comp_line, "^%s*" .. cmd .. "%s+%-A%s")
87is_c = string.find(comp_line, "^%s*" .. cmd .. "%s+%-C%s")
88is_i = string.find(comp_line, "^%s*" .. cmd .. "%s+%-I%s")
89is_r = string.find(comp_line, "^%s*" .. cmd .. "%s+%-R%s")
90
91if (is_i) then
92  dp("If --I is used, no additional argument is allowed")
93  os.exit(0)
94end
95
96-- -A and -R require the password file as second argument
97htpasswd_r = ".htpasswd <mydomain.com> <username>"
98htpasswd_a = htpasswd_r .. " <password>"
99if (last == "-A") and (this == htpasswd_a:sub(1,#this)) then
100  dp("Fill with option template for -A")
101  print(htpasswd_a)
102  os.exit(0)
103end
104if (last == "-R") and (this == htpasswd_r:sub(1,#this)) then
105  dp("Fill with option template for -R")
106  print(htpasswd_r)
107  os.exit(0)
108end
109if (is_a or is_r) then
110  dp("Options -A and -R have a fixed number of arguments")
111  os.exit(0)
112end
113
114-- -C requires an URL, usually starting with http:// or https://
115http = "http://"
116if (last == "-C") and (this == http:sub(1,#this)) then
117  print(http)
118  print(http.. "localhost/")
119  os.exit(0)
120end
121http = "https://"
122if (last == "-C") and (this == http:sub(1,#this)) then
123  print(http)
124  print(http.. "localhost/")
125  os.exit(0)
126end
127if (is_c) then
128  dp("Option -C has just one argument")
129  os.exit(0)
130end
131
132
133-- Take options directly from "--help" output of executable
134optfile = "/tmp/civetweb.options"
135if not fileexists(optfile) then
136  dp("options file " .. optfile .. " missing")
137  os.execute(cmd .. " --help > " .. optfile .. " 2>&1")
138else
139  dp("options file " .. optfile .. " found")
140end
141
142for l in io.lines(optfile) do
143  local lkey, lval = l:match("^%s+(%-[^%s]*)%s*([^%s]*)%s*$")
144  if lkey then
145    local thislen = string.len(this)
146    if (thislen>0) and (this == lkey:sub(1,thislen)) then
147      print(lkey)
148      dp("match: " .. lkey)
149      keymatch = true
150    end
151    if last == lkey then
152      valmatch = lval
153    end
154  end
155end
156
157if keymatch then
158  -- at least one options matches
159  os.exit(0)
160end
161
162if valmatch then
163  -- suggest the default value
164  print(valmatch)
165  os.exit(0)
166end
167
168-- No context to determine next argument
169dp("no specific option")
170os.exit(0)
171
172