mtx-fcd.lua /size: 10 Kb    last modification: 2020-07-01 14:35
1if not modules then modules = { } end modules ['mtx-fcd'] = {
2    version   = 1.002,
3    comment   = "companion to mtxrun.lua",
4    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
5    copyright = "PRAGMA ADE / ConTeXt Development Team",
6    license   = "see context related readme files",
7    comment   = "based on the ruby version from 2005",
8}
9
10-- This is a kind of variant of the good old ncd (norton change directory) program. This
11-- script uses the same indirect cmd trick as Erwin Waterlander's wcd program.
12--
13-- The program is called via the stubs fcd.cmd or fcd.sh. On unix one should probably source
14-- the file: ". fcd args" in order to make the chdir persistent.
15--
16-- You need to create a stub with:
17--
18--   mtxrun --script fcd --stub > fcd.cmd
19--   mtxrun --script fcd --stub > fcd.sh
20--
21-- The stub starts this script and afterwards runs the created directory change script as
22-- part if the same run, so that indeed we change.
23
24local helpinfo = [[
25<?xml version="1.0"?>
26<application>
27 <metadata>
28  <entry name="name">mtx-fcd</entry>
29  <entry name="detail">Fast Directory Change</entry>
30  <entry name="version">1.00</entry>
31 </metadata>
32 <flags>
33  <category name="basic">
34   <subcategory>
35    <flag name="clear"><short>clear the cache</short></flag>
36    <flag name="clear"><short><ref name="history"/> [entry] clear the history</short></flag>
37    <flag name="scan"><short>clear the cache and add given path(s)</short></flag>
38    <flag name="add"><short>add given path(s)</short></flag>
39    <flag name="find"><short>find given path (can be substring)</short></flag>
40    <flag name="find"><short><ref name="nohistory"/> find given path (can be substring) but don't use history</short></flag>
41    <flag name="stub"><short>print platform stub file</short></flag>
42    <flag name="list"><short>show roots of cached dirs</short></flag>
43    <flag name="list"><short><ref name="history"/> show history of chosen dirs</short></flag>
44    <flag name="help"><short>show this help</short></flag>
45   </subcategory>
46  </category>
47 </flags>
48 <examples>
49  <category>
50   <title>Example</title>
51   <subcategory>
52    <example><command>fcd --scan t:\</command></example>
53    <example><command>fcd --add f:\project</command></example>
54    <example><command>fcd [--find] whatever</command></example>
55    <example><command>fcd --list</command></example>
56   </subcategory>
57  </category>
58 </examples>
59</application>
60]]
61
62local application = logs.application {
63    name     = "mtx-fcd",
64    banner   = "Fast Directory Change 1.00",
65    helpinfo = helpinfo,
66}
67
68local report  = application.report
69local writeln = (logs and logs.writer) or (texio and texio.write_nl) or print
70
71local find, char, byte, lower, gsub, format = string.find, string.char, string.byte, string.lower, string.gsub, string.format
72
73local mswinstub = [[@echo off
74
75rem this is: fcd.cmd
76
77@echo off
78
79if not exist "%HOME%" goto homepath
80
81:home
82
83mtxrun --script mtx-fcd.lua %1 %2 %3 %4 %5 %6 %7 %8 %9
84
85if exist "%HOME%\mtx-fcd-goto.cmd" call "%HOME%\mtx-fcd-goto.cmd"
86
87goto end
88
89:homepath
90
91if not exist "%HOMEDRIVE%\%HOMEPATH%" goto end
92
93mtxrun --script mtx-fcd.lua %1 %2 %3 %4 %5 %6 %7 %8 %9
94
95if exist "%HOMEDRIVE%\%HOMEPATH%\mtx-fcd-goto.cmd" call "%HOMEDRIVE%\%HOMEPATH%\mtx-fcd-goto.cmd"
96
97goto end
98
99:end
100]]
101
102local unixstub = [[#!/usr/bin/env sh
103
104# this is: fcd.sh
105
106# mv fcd.sh fcd
107# chmod fcd 755
108# . fcd [args]
109
110ruby -S fcd_start.rb $1 $2 $3 $4 $5 $6 $7 $8 $9
111
112if test -f "$HOME/fcd_stage.sh" ; then
113  . $HOME/fcd_stage.sh ;
114fi;
115
116]]
117
118local gotofile
119local datafile
120local stubfile
121local stubdata
122local stubdummy
123local stubchdir
124
125
126if os.type == 'windows' then
127    local shell = "cmd"
128--     local shell = "powershell"
129    if shell == "powershell" then
130        gotofile  = 'mtx-fcd-goto.ps1'
131        datafile  = 'mtx-fcd-data.lua'
132        stubfile  = 'fcd.cmd'
133        stubdata  = mswinstub
134        stubdummy = '# no dir to change to'
135        stubchdir = '. Set-Location %s' -- powershell
136    else
137        gotofile  = 'mtx-fcd-goto.cmd'
138        datafile  = 'mtx-fcd-data.lua'
139        stubfile  = 'fcd.cmd'
140        stubdata  = mswinstub
141        stubdummy = 'rem no dir to change to'
142        stubchdir = 'cd /d "%s"' -- cmd
143    end
144else
145    gotofile  = 'mtx-fcd-goto.sh'
146    datafile  = 'mtx-fcd-data.lua'
147    stubfile  = 'fcd.sh'
148    stubdata  = unixstub
149    stubdummy = '# no dir to change to'
150    stubchdir = '# cd "%s"'
151end
152
153local homedir = os.env["HOME"] or "" -- no longer TMP etc
154
155if homedir == "" then
156    homedir = format("%s/%s",os.env["HOMEDRIVE"] or "",os.env["HOMEPATH"] or "")
157end
158
159if homedir == "/" or not lfs.isdir(homedir) then
160    os.exit()
161end
162
163local datafile = file.join(homedir,datafile)
164local gotofile = file.join(homedir,gotofile)
165local hash     = nil
166local found    = { }
167local pattern  = ""
168local version  = modules['mtx-fcd'].version
169
170io.savedata(gotofile,stubdummy)
171
172if not lfs.isfile(gotofile) then
173    -- write error
174    os.exit()
175end
176
177local function fcd_clear(onlyhistory,what)
178    if onlyhistory and hash and hash.history then
179        if what and what ~= "" then
180            hash.history[what] = nil
181        else
182            hash.history = { }
183        end
184    else
185        hash = {
186            name    = "fcd cache",
187            comment = "generated by mtx-fcd.lua",
188            created = os.date(),
189            version = version,
190            paths   = { },
191            history = { },
192        }
193    end
194end
195
196local function fcd_changeto(dir)
197    if dir and dir ~= "" then
198        io.savedata(gotofile,format(stubchdir,dir,dir))
199    end
200end
201
202local function fcd_load(forcecreate)
203    if lfs.isfile(datafile) then
204        hash = dofile(datafile)
205    end
206    if not hash or hash.version ~= version then
207        if forcecache then
208            fcd_clear()
209        else
210            writeln("empty dir cache")
211            fcd_clear()
212            os.exit()
213        end
214    end
215end
216
217local function fcd_save()
218    if hash then
219        io.savedata(datafile,table.serialize(hash,true))
220    end
221end
222
223local function fcd_list(onlyhistory)
224    if hash then
225        writeln("")
226        if onlyhistory then
227            if next(hash.history) then
228                for k, v in table.sortedhash(hash.history) do
229                    writeln(format("%s => %s",k,v))
230                end
231            else
232                writeln("no history")
233            end
234        else
235            local paths = hash.paths
236            if #paths > 0 then
237                for i=1,#paths do
238                    local path = paths[i]
239                    writeln(format("%4i  %s",#path[2],path[1]))
240                end
241            else
242                writeln("empty cache")
243            end
244        end
245    end
246end
247
248local function fcd_find()
249    found = { }
250    pattern = lower(environment.files[1] or "")
251    if pattern ~= "" then
252        pattern = string.escapedpattern(pattern)
253        local paths = hash.paths
254        for i=1,#paths do
255            local paths = paths[i][2]
256            for i=1,#paths do
257                local path = paths[i]
258                if find(lower(path),pattern) then
259                    found[#found+1] = path
260                end
261            end
262        end
263    end
264end
265
266local function fcd_choose(new)
267    if pattern == "" then
268        writeln(format("staying in dir %q",(gsub(lfs.currentdir(),"\\","/"))))
269        return
270    end
271    if #found == 0 then
272        writeln(format("dir %q not found",pattern))
273        return
274    end
275    local okay = #found == 1 and found[1] or (not new and hash.history[pattern])
276    if okay then
277        writeln(format("changing to %q",okay))
278        fcd_changeto(okay)
279        return
280    end
281    local offset = 0
282    while true do
283        if not found[offset] then
284            offset = 0
285        end
286        io.write("\n")
287        for i=1,26 do
288            local v = found[i+offset]
289            if v then
290                writeln(format("%s  %3i  %s",char(i+96),offset+i,v))
291            else
292                break
293            end
294        end
295        offset = offset + 26
296        if found[offset+1] then
297            io.write("\n[press enter for more or select letter]\n\n>> ")
298        else
299            io.write("\n[select letter]\n\n>> ")
300        end
301        local answer = lower(io.read() or "")
302        if not answer or answer == 'quit' then
303            break
304        elseif #answer > 0 then
305            local choice = tonumber(answer)
306            if not choice then
307                if answer >= "a" and answer <= "z" then
308                    choice = byte(answer) - 96 + offset - 26
309                end
310            end
311            local newdir = found[choice]
312            if newdir then
313                hash.history[pattern] = newdir
314                writeln(format("changing to %q",newdir))
315                fcd_changeto(newdir)
316                fcd_save()
317                return
318            end
319        else
320            -- try again
321        end
322    end
323end
324
325local function globdirs(path,dirs)
326    local dirs = dirs or { }
327    for name in lfs.dir(path) do
328        if not find(name,"%.$") then
329            local fullname = path .. "/" .. name
330            if lfs.isdir(fullname) and not find(fullname,"/%.") then
331                dirs[#dirs+1] = fullname
332                globdirs(fullname,dirs)
333            end
334        end
335    end
336    return dirs
337end
338
339local function fcd_scan()
340    if hash then
341        local paths = hash.paths
342        for i=1,#environment.files do
343            local name = environment.files[i]
344            local name = gsub(name,"\\","/")
345            local name = gsub(name,"/$","")
346            local list = globdirs(name)
347            local done = false
348            for i=1,#paths do
349                if paths[i][1] == name then
350                    paths[i][2] = list
351                    done = true
352                    break
353                end
354            end
355            if not done then
356                paths[#paths+1] = { name, list }
357            end
358        end
359    end
360end
361
362local argument = environment.argument
363
364if argument("clear") then
365    if argument("history") then
366        fcd_load()
367        fcd_clear(true)
368    else
369        fcd_clear()
370    end
371    fcd_save()
372elseif argument("scan") then
373    fcd_clear()
374    fcd_scan()
375    fcd_save()
376elseif argument("add") then
377    fcd_load(true)
378    fcd_scan()
379    fcd_save()
380elseif argument("stub") then
381    writeln(stubdata)
382elseif argument("list") then
383    fcd_load()
384    if argument("history") then
385        fcd_list(true)
386    else
387        fcd_list()
388    end
389elseif argument("help") then
390    application.help()
391elseif argument("exporthelp") then
392    application.export(argument("exporthelp"),environment.files[1])
393else -- also argument("find")
394    fcd_load()
395    fcd_find()
396    fcd_choose(argument("nohistory"))
397end
398
399