2 -- ion/query/mod_query.lua -- Some common queries for Ion
4 -- Copyright (c) Tuomo Valkonen 2004-2006.
6 -- Ion is free software; you can redistribute it and/or modify it under
7 -- the terms of the GNU Lesser General Public License as published by
8 -- the Free Software Foundation; either version 2.1 of the License, or
9 -- (at your option) any later version.
13 -- This is a slight abuse of the package.loaded variable perhaps, but
14 -- library-like packages should handle checking if they're loaded instead of
15 -- confusing the user with require/include differences.
16 if package.loaded["mod_query"] then return end
18 if not ioncore.load_module("mod_query") then
22 local mod_query=_G["mod_query"]
27 local DIE_TIMEOUT_ERRORCODE=10 -- 10 seconds
28 local DIE_TIMEOUT_NO_ERRORCODE=2 -- 2 seconds
31 -- Generic helper functions {{{
34 function mod_query.make_completor(completefn)
35 local function completor(cp, str, point)
36 cp:set_completions(completefn(str, point))
43 -- Low-level query routine. \var{mplex} is the \type{WMPlex} to display
44 -- the query in, \var{prompt} the prompt string, and \var{initvalue}
45 -- the initial contents of the query box. \var{handler} is a function
46 -- that receives (\var{mplex}, result string) as parameter when the
47 -- query has been succesfully completed, \var{completor} the completor
48 -- routine which receives a (\var{cp}, \var{str}, \var{point}) as parameters.
49 -- The parameter \var{str} is the string to be completed and \var{point}
50 -- cursor's location within it. Completions should be eventually,
51 -- possibly asynchronously, set with \fnref{WComplProxy.set_completions}
53 function mod_query.query(mplex, prompt, initvalue, handler, completor,
55 local function handle_it(str)
58 local function cycle(wedln)
59 wedln:complete('next', 'normal')
62 -- Check that no other queries are open in the mplex.
63 local l=mplex:managed_list()
64 for i, r in pairs(l) do
65 if obj_is(r, "WEdln") then
69 local wedln=mod_query.do_query(mplex, prompt, initvalue,
70 handle_it, completor, cycle)
72 wedln:set_context(context)
80 -- This function query will display a query with prompt \var{prompt} in
81 -- \var{mplex} and if the user answers affirmately, call \var{handler}
82 -- with \var{mplex} as parameter.
83 function mod_query.query_yesno(mplex, prompt, handler)
84 local function handler_yesno(mplex, str)
85 if str=="y" or str=="Y" or str=="yes" then
89 return mod_query.query(mplex, prompt, nil, handler_yesno, nil,
96 local function maybe_finish(pid)
99 if t and t.closed and t.dietime then
101 local tmd=os.difftime(t.dietime, t.starttime)
102 --if tmd<DIE_TIMEOUT_ERRORCODE and t.signaled then
103 -- local msg=TR("Program received signal ")..t.termsig.."\n"
104 -- mod_query.warn(t.mplex, msg..(t.errs or ""))
106 if ((tmd<DIE_TIMEOUT_ERRORCODE and (t.hadcode or t.signaled)) or
107 (tmd<DIE_TIMEOUT_NO_ERRORCODE)) and t.errs then
108 mod_query.warn(t.mplex, t.errs)
114 local badsig_={4, 5, 6, 7, 8, 11}
116 for _, v in pairs(badsig_) do
120 local function chld_handler(p)
121 local t=errdata[p.pid]
124 t.signaled=(p.signaled and badsig[p.termsig])
126 t.hadcode=(p.exited and p.exitstatus~=0)
131 ioncore.get_hook("ioncore_sigchld_hook"):add(chld_handler)
133 function mod_query.exec_on_merr(mplex, cmd)
136 local function monitor(str)
141 t.errs=(t.errs or "")..str
150 local function timeout()
154 pid=ioncore.exec_on(mplex, cmd, monitor)
160 local tmr=ioncore.create_timer();
161 local tmd=math.max(DIE_TIMEOUT_NO_ERRORCODE, DIE_TIMEOUT_ERRORCODE)
163 tmr:set(tmd*1000, timeout)
165 errdata[pid]={tmr=tmr, mplex=mplex, starttime=now}
169 function mod_query.file_completor(wedln, str)
170 local ic=ioncore.lookup_script("ion-completefile")
172 mod_query.popen_completions(wedln,
173 ic.." "..string.shell_safe(str))
178 function mod_query.query_execfile(mplex, prompt, prog)
180 local function handle_execwith(mplex, str)
181 mod_query.exec_on_merr(mplex, prog.." "..string.shell_safe(str))
183 return mod_query.query(mplex, prompt, mod_query.get_initdir(mplex),
184 handle_execwith, mod_query.file_completor,
189 function mod_query.query_execwith(mplex, prompt, dflt, prog, completor,
191 local function handle_execwith(frame, str)
192 if not str or str=="" then
195 local args=(noquote and str or string.shell_safe(str))
196 mod_query.exec_on_merr(mplex, prog.." "..args)
198 return mod_query.query(mplex, prompt, nil, handle_execwith, completor,
203 function mod_query.get_initdir(mplex)
204 --if mod_query.last_dir then
205 -- return mod_query.last_dir
207 local wd=(ioncore.get_dir_for(mplex) or os.getenv("PWD"))
210 elseif string.sub(wd, -1)~="/" then
220 function mod_query.complete_from_list(list, str)
222 local len=string.len(str)
226 for _, m in pairs(list) do
227 if string.sub(m, 1, len)==str then
228 table.insert(results, m)
239 mod_query.COLLECT_THRESHOLD=2000
242 -- This function can be used to read completions from an external source.
243 -- The parameter \var{cp} is the completion proxy to be used,
244 -- and the string \var{cmd} the shell command to be executed. To its stdout,
245 -- the command should on the first line write the \var{common_beg}
246 -- parameter of \fnref{WComplProxy.set_completions} (which \var{fn} maybe used
247 -- to override) and a single actual completion on each of the successive lines.
248 -- The function \var{reshnd} may be used to override a result table
250 function mod_query.popen_completions(cp, cmd, fn, reshnd)
252 local pst={cp=cp, maybe_stalled=0}
255 reshnd = function(rs, a)
256 if not rs.common_beg then
264 local function rcv(str)
271 if pst.maybe_stalled>=2 then
277 totallen=totallen+string.len(str)
278 if totallen>ioncore.RESULT_DATA_LIMIT then
279 error(TR("Too much result data"))
283 data=string.gsub(data..str, "([^\n]*)\n",
290 if lines>mod_query.COLLECT_THRESHOLD then
295 str=coroutine.yield()
298 if not results.common_beg then
299 results.common_beg=beg
302 (fn or WComplProxy.set_completions)(cp, results)
310 local found_clean=false
312 for k, v in pairs(pipes) do
314 if v.maybe_stalled<2 then
315 v.maybe_stalled=v.maybe_stalled+1
321 if not found_clean then
323 ioncore.popen_bgread(cmd, coroutine.wrap(rcv))
331 -- Simple queries for internal actions {{{
334 function mod_query.call_warn(mplex, fn)
335 local err = collect_errors(fn)
337 mod_query.warn(mplex, err)
343 function mod_query.complete_name(str, list)
345 local l=string.len(str)
346 for i, reg in pairs(list) do
348 if nm and string.sub(nm, 1, l)==str then
349 table.insert(entries, nm)
353 for i, reg in pairs(list) do
355 if nm and string.find(nm, str, 1, true) then
356 table.insert(entries, nm)
363 function mod_query.complete_clientwin(str)
364 return mod_query.complete_name(str, ioncore.clientwin_list())
367 function mod_query.complete_workspace(str)
368 return mod_query.complete_name(str, ioncore.region_list("WGroupWS"))
371 function mod_query.complete_region(str)
372 return mod_query.complete_name(str, ioncore.region_list())
376 function mod_query.gotoclient_handler(frame, str)
377 local cwin=ioncore.lookup_clientwin(str)
380 mod_query.warn(frame, TR("Could not find client window %s.", str))
386 function mod_query.attachclient_handler(frame, str)
387 local cwin=ioncore.lookup_clientwin(str)
390 mod_query.warn(frame, TR("Could not find client window %s.", str))
394 local reg=cwin:manager()
397 if not obj_is(reg, "WGroupCW") then
403 managed = {{ reg = cwin, bottom = true }}
408 frame:attach(reg, { switchto = true })
412 if frame:rootwin_of()~=reg:rootwin_of() then
413 mod_query.warn(frame, TR("Cannot attach: different root windows."))
414 elseif reg:manager()==frame then
417 mod_query.call_warn(frame, attach)
422 function mod_query.workspace_handler(mplex, name)
423 local ws=ioncore.lookup_region(name, "WGroupWS")
429 local scr=mplex:screen_of()
431 local function mkws()
432 if not ioncore.create_ws(scr, {name=name}) then
433 error(TR("Unknown error"))
437 mod_query.call_warn(mplex, mkws)
442 -- This query asks for the name of a client window and attaches
443 -- it to the frame the query was opened in. It uses the completion
444 -- function \fnref{ioncore.complete_clientwin}.
445 function mod_query.query_gotoclient(mplex)
446 mod_query.query(mplex, TR("Go to window:"), nil,
447 mod_query.gotoclient_handler,
448 mod_query.make_completor(mod_query.complete_clientwin),
453 -- This query asks for the name of a client window and switches
454 -- focus to the one entered. It uses the completion function
455 -- \fnref{ioncore.complete_clientwin}.
456 function mod_query.query_attachclient(mplex)
457 mod_query.query(mplex, TR("Attach window:"), nil,
458 mod_query.attachclient_handler,
459 mod_query.make_completor(mod_query.complete_clientwin),
465 -- This query asks for the name of a workspace. If a workspace
466 -- (an object inheriting \type{WGroupWS}) with such a name exists,
467 -- it will be switched to. Otherwise a new workspace with the
468 -- entered name will be created and the user will be queried for
469 -- the type of the workspace.
470 function mod_query.query_workspace(mplex)
471 mod_query.query(mplex, TR("Go to or create workspace:"), nil,
472 mod_query.workspace_handler,
473 mod_query.make_completor(mod_query.complete_workspace),
479 -- This query asks whether the user wants to exit Ion (no session manager)
480 -- or close the session (running under a session manager that supports such
481 -- requests). If the answer is 'y', 'Y' or 'yes', so will happen.
482 function mod_query.query_shutdown(mplex)
483 mod_query.query_yesno(mplex, TR("Exit Ion/Shutdown session (y/n)?"),
489 -- This query asks whether the user wants restart Ioncore.
490 -- If the answer is 'y', 'Y' or 'yes', so will happen.
491 function mod_query.query_restart(mplex)
492 mod_query.query_yesno(mplex, TR("Restart Ion (y/n)?"), ioncore.restart)
497 -- This function asks for a name new for the frame where the query
499 function mod_query.query_renameframe(frame)
500 mod_query.query(frame, TR("Frame name:"), frame:name(),
501 function(frame, str) frame:set_name(str) end,
507 -- This function asks for a name new for the workspace on which the
509 function mod_query.query_renameworkspace(mplex)
510 local ws=ioncore.find_manager(mplex, "WGroupWS")
511 mod_query.query(mplex, TR("Workspace name:"), ws:name(),
512 function(mplex, str) ws:set_name(str) end,
524 -- Asks for a file to be edited. This script uses
525 -- \command{run-mailcap --mode=edit} by default, but you may provide an
526 -- alternative script to use. The default prompt is "Edit file:" (translated).
527 function mod_query.query_editfile(mplex, script, prompt)
528 mod_query.query_execfile(mplex,
529 prompt or TR("Edit file:"),
530 script or "run-mailcap --action=edit")
535 -- Asks for a file to be viewed. This script uses
536 -- \command{run-mailcap --action=view} by default, but you may provide an
537 -- alternative script to use. The default prompt is "View file:" (translated).
538 function mod_query.query_runfile(mplex, script, prompt)
539 mod_query.query_execfile(mplex,
540 prompt or TR("View file:"),
541 script or "run-mailcap --action=view")
546 local function isspace(s)
547 return string.find(s, "^%s*$")~=nil
551 local function break_cmdline(str, no_ws)
552 local st, en, beg, rest, ch, rem
555 local function ins(str)
557 if string.find(res[n], "^%s+$") then
558 table.insert(res, str)
564 local function ins_space(str)
568 table.insert(res, "")
571 if isspace(res[n]) then
574 table.insert(res, str)
579 -- Handle terminal startup syntax
580 st, en, beg, ch, rest=string.find(str, "^(%s*)(:+)(.*)")
582 if string.len(beg)>0 then
591 st, en, beg, rest, ch=string.find(str, "^(.-)(([%s'\"\\|])(.*))")
603 st, en, beg, rest=string.find(str, "^(\\.)(.*)")
605 st, en, beg, rest=string.find(str, "^(\".-[^\\]\")(.*)")
608 st, en, beg, rest=string.find(str, "^(\"\")(.*)")
611 st, en, beg, rest=string.find(str, "^('.-')(.*)")
619 st, en, beg, rest=string.find(str, "^.(%s*)(.*)")
640 local function unquote(str)
641 str=string.gsub(str, "^['\"]", "")
642 str=string.gsub(str, "([^\\])['\"]", "%1")
643 str=string.gsub(str, "\\(.)", "%1")
648 local function quote(str)
649 return string.gsub(str, "([%(%)\"'\\%*%?%[%]%| ])", "\\%1")
653 local function find_point(strs, point)
654 for i, s in ipairs(strs) do
655 point=point-string.len(s)
664 function mod_query.exec_completor(wedln, str, point)
665 local parts=break_cmdline(str)
666 local complidx=find_point(parts, point+1)
668 local s_compl, s_beg, s_end="", "", ""
670 if complidx==1 and string.find(parts[1], "^:+$") then
674 if string.find(parts[complidx], "[^%s]") then
675 s_compl=unquote(parts[complidx])
678 for i=1, complidx-1 do
679 s_beg=s_beg..parts[i]
682 for i=complidx+1, #parts do
683 s_end=s_end..parts[i]
687 if complidx==1 or (complidx==2 and isspace(parts[1])) then
689 elseif string.find(parts[1], "^:+$") then
692 elseif string.find(parts[2], "^%s*$") then
699 local function set_fn(cp, res)
700 res=table.map(quote, res)
701 res.common_beg=s_beg..(res.common_beg or "")
702 res.common_end=(res.common_end or "")..s_end
703 cp:set_completions(res)
706 local function filter_fn(res, s)
707 if not res.common_beg then
718 local ic=ioncore.lookup_script("ion-completefile")
720 mod_query.popen_completions(wedln,
721 ic..wp..string.shell_safe(s_compl),
727 local cmd_overrides={}
731 -- Define a command override for the \fnrefx{mod_query}{query_exec} query.
732 function mod_query.defcmd(cmd, fn)
733 cmd_overrides[cmd]=fn
737 function mod_query.exec_handler(mplex, cmdline)
738 local parts=break_cmdline(cmdline, true)
739 local cmd=table.remove(parts, 1)
741 if cmd_overrides[cmd] then
742 cmd_overrides[cmd](mplex, table.map(unquote, parts))
744 mod_query.exec_on_merr(mplex, cmdline)
750 -- This function asks for a command to execute with \file{/bin/sh}.
751 -- If the command is prefixed with a colon (':'), the command will
752 -- be run in an XTerm (or other terminal emulator) using the script
753 -- \file{ion-runinxterm}. Two colons ('::') will ask you to press
754 -- enter after the command has finished.
755 function mod_query.query_exec(mplex)
756 mod_query.query(mplex, TR("Run:"), nil, mod_query.exec_handler,
757 mod_query.exec_completor,
768 mod_query.known_hosts={}
771 function mod_query.get_known_hosts(mplex)
772 mod_query.known_hosts={}
774 local h=os.getenv("HOME")
776 f=io.open(h.."/.ssh/known_hosts")
779 warn(TR("Failed to open ~/.ssh/known_hosts"))
782 for l in f:lines() do
783 local st, en, hostname=string.find(l, "^([^%s,]+)")
785 table.insert(mod_query.known_hosts, hostname)
792 mod_query.hostnicks={}
794 function mod_query.get_hostnicks(mplex)
795 mod_query.hostnicks={}
797 local substr, pat, patterns
798 local h=os.getenv("HOME")
801 f=io.open(h.."/.ssh/config")
804 warn(TR("Failed to open ~/.ssh/config"))
808 for l in f:lines() do
809 _, _, substr=string.find(l, "^%s*[hH][oO][sS][tT](.*)")
811 _, _, pat=string.find(substr, "^%s*[=%s]%s*(%S.*)")
814 elseif string.find(substr, "^[nN][aA][mM][eE]")
816 for s in string.gfind(patterns, "%S+") do
817 if not string.find(s, "[*?]") then
818 table.insert(mod_query.hostnicks, s)
828 function mod_query.complete_ssh(str)
829 local st, en, user, at, host=string.find(str, "^([^@]*)(@?)(.*)$")
831 if string.len(at)==0 and string.len(host)==0 then
832 host = user; user = ""
841 if string.len(host)==0 then
842 if string.len(user)==0 then
843 return mod_query.ssh_completions
846 for _, v in ipairs(mod_query.ssh_completions) do
847 table.insert(res, user .. v)
852 for _, v in ipairs(mod_query.ssh_completions) do
853 local s, e=string.find(v, host, 1, true)
854 if s==1 and e>=1 then
855 table.insert(res, user .. v)
862 mod_query.ssh_completions={}
865 -- This query asks for a host to connect to with SSH.
866 -- Hosts to tab-complete are read from \file{\~{}/.ssh/known\_hosts}.
867 function mod_query.query_ssh(mplex, ssh)
868 mod_query.get_known_hosts(mplex)
869 mod_query.get_hostnicks(mplex)
871 for _, v in ipairs(mod_query.known_hosts) do
872 table.insert(mod_query.ssh_completions, v)
874 for _, v in ipairs(mod_query.hostnicks) do
875 table.insert(mod_query.ssh_completions, v)
880 local function handle_exec(mplex, str)
881 if not (str and string.find(str, "[^%s]")) then
885 mod_query.exec_on_merr(mplex, ssh.." "..string.shell_safe(str))
888 return mod_query.query(mplex, TR("SSH to:"), nil, handle_exec,
889 mod_query.make_completor(mod_query.complete_ssh),
899 function mod_query.man_completor(wedln, str)
900 local mc=ioncore.lookup_script("ion-completeman")
902 mod_query.popen_completions(wedln, (mc.." -complete "
903 ..string.shell_safe(str)))
909 -- This query asks for a manual page to display. By default it runs the
910 -- \command{man} command in an \command{xterm} using \command{ion-runinxterm},
911 -- but it is possible to pass another program as the \var{prog} argument.
912 function mod_query.query_man(mplex, prog)
913 local dflt=ioncore.progname()
914 mod_query.query_execwith(mplex, TR("Manual page (%s):", dflt),
915 dflt, prog or ":man",
916 mod_query.man_completor, "man",
917 true --[[ no quoting ]])
924 -- Lua code execution {{{
927 function mod_query.create_run_env(mplex)
928 local origenv=getfenv()
929 local meta={__index=origenv, __newindex=origenv}
932 _sub=mplex:current(),
935 setmetatable(env, meta)
939 function mod_query.do_handle_lua(mplex, env, code)
941 local function collect_print(...)
945 tmp=tmp..tostring(arg[i])..(i==l and "\n" or "\t")
947 print_res=(print_res and print_res..tmp or tmp)
950 local f, err=loadstring(code)
952 mod_query.warn(mplex, err)
956 env.print=collect_print
959 err=collect_errors(f)
961 mod_query.warn(mplex, err)
962 elseif print_res then
963 mod_query.message(mplex, print_res)
967 local function getindex(t)
968 local mt=getmetatable(t)
969 if mt then return mt.__index end
973 function mod_query.do_complete_lua(env, str)
974 -- Get the variable to complete, including containing tables.
975 -- This will also match string concatenations and such because
976 -- Lua's regexps don't support optional subexpressions, but we
977 -- handle them in the next step.
980 local _, _, tocomp=string.find(str, "([%w_.:]*)$")
982 -- Descend into tables
983 if tocomp and string.len(tocomp)>=1 then
984 for t in string.gfind(tocomp, "([^.:]*)[.:]") do
986 if string.len(t)==0 then
989 if type(comptab[t])=="table" then
991 elseif type(comptab[t])=="userdata" then
992 comptab=getindex(comptab[t])
1001 if not comptab then return {} end
1005 -- Get the actual variable to complete without containing tables
1006 _, _, compl.common_beg, tocomp=string.find(str, "(.-)([%w_]*)$")
1008 local l=string.len(tocomp)
1013 if type(tab) == "table" then
1014 for k in pairs(tab) do
1015 if type(k)=="string" then
1016 if string.sub(k, 1, l)==tocomp then
1017 table.insert(compl, k)
1023 -- We only want to display full list of functions for objects, not
1024 -- the tables representing the classes.
1025 --if not metas then break end
1029 if not tab or seen[tab] then break end
1032 -- If there was only one completion and it is a string or function,
1033 -- concatenate it with "." or "(", respectively.
1035 if type(comptab[compl[1]])=="table" then
1036 compl[1]=compl[1] .. "."
1037 elseif type(comptab[compl[1]])=="function" then
1038 compl[1]=compl[1] .. "("
1047 -- This query asks for Lua code to execute. It sets the variable '\var{\_}'
1048 -- in the local environment of the string to point to the mplex where the
1049 -- query was created. It also sets the table \var{arg} in the local
1050 -- environment to \code{\{_, _:current()\}}.
1051 function mod_query.query_lua(mplex)
1052 local env=mod_query.create_run_env(mplex)
1054 local function complete(cp, code)
1055 cp:set_completions(mod_query.do_complete_lua(env, code))
1058 local function handler(mplex, code)
1059 return mod_query.do_handle_lua(mplex, env, code)
1062 mod_query.query(mplex, TR("Lua code:"), nil, handler, complete, "lua")
1071 -- This query can be used to create a query of a defined menu.
1072 function mod_query.query_menu(mplex, themenu, prompt)
1073 local _sub=mplex:current()
1074 local menu=ioncore.evalmenu(themenu, {mplex, _sub})
1075 local menuname=(type(themenu)=="string" and themenu or "?")
1078 mod_query.warn(mplex, TR("Unknown menu %s.", tostring(themenu)))
1083 prompt=menuname..":"
1088 local function xform_name(n, is_submenu)
1089 return (string.lower(string.gsub(n, "[-%s]+", "-"))
1090 ..(is_submenu and "/" or ""))
1093 local function xform_menu(t, m, p)
1094 for _, v in ipairs(m) do
1096 local is_submenu=v.submenu_fn
1097 local n=p..xform_name(v.name, is_submenu)
1102 if is_submenu and not v.noautoexpand then
1103 local sm=v.submenu_fn()
1105 xform_menu(t, sm, n)
1107 ioncore.warn_traced(TR("Missing submenu ")
1116 local ntab=xform_menu({}, menu, "")
1118 local function complete(str)
1120 for s, e in pairs(ntab) do
1121 if string.find(s, str, 1, true) then
1122 table.insert(results, s)
1128 local function handle(mplex, str)
1132 local err=collect_errors(function()
1136 mod_query.warn(mplex, err)
1138 elseif e.submenu_fn then
1139 mod_query.query_menu(mplex, e.submenu_fn(),
1143 mod_query.warn(mplex, TR("No entry '%s'", str))
1147 mod_query.query(mplex, prompt, nil, handle,
1148 mod_query.make_completor(complete), "menu."..menuname)
1154 -- Miscellaneous {{{
1158 -- Display an "About Ion" message in \var{mplex}.
1159 function mod_query.show_about_ion(mplex)
1160 mod_query.message(mplex, ioncore.aboutmsg())
1165 -- Show information about a region tree
1166 function mod_query.show_tree(mplex, reg, max_depth)
1167 local function indent(s)
1169 return i..string.gsub(s, "\n", "\n"..i)
1172 local function get_info(reg, indent, d)
1174 return (indent .. "No region")
1177 local function n(s) return (s or "") end
1179 local s=string.format("%s%s \"%s\"", indent, obj_typename(reg),
1181 indent = indent .. " "
1182 if obj_is(reg, "WClientWin") then
1183 local i=reg:get_ident()
1184 s=s .. TR("\n%sClass: %s\n%sRole: %s\n%sInstance: %s\n%sXID: 0x%x",
1187 indent, n(i.instance),
1191 if (not max_depth or max_depth > d) and reg.managed_list then
1192 local mgd=reg:managed_list()
1194 s=s .. "\n" .. indent .. "---"
1195 for k, v in pairs(mgd) do
1196 s=s .. "\n" .. get_info(v, indent, d+1)
1204 mod_query.message(mplex, get_info(reg, "", 0))
1210 dopath('mod_query_chdir')
1212 -- Mark ourselves loaded.
1213 package.loaded["mod_query"]=true
1216 -- Load configuration file
1217 dopath('cfg_query', true)