summaryrefslogtreecommitdiff
path: root/Master/texmf-dist/scripts/texdoc/view.tlu
blob: 15784ff739044bb2ecaac060ccd4a290e922082e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
-- view.tlu view a document and/or display the list of results in texdoc
--
-- Manuel Pégourié-Gonnard and the TeX Live team, GPLv3,
-- see texdoclib.tlu for details

-----------------------------   view a document   -----------------------------

-- view a document
-- see search.tlu for the structure of the argument
function view_doc(docfile)
    return view_file(docfile.realpath)
end

-- get viewer and viewer_replacement before calling try_viewing
-- returns false of failure, true on success
-- viewer_replacement is either:
-- 1. the filename, quoted with "
-- 2. the filename, quoted with " followed by some rm commands
-- The second case happens when the doc was zipped. In the case, this function
-- unzips it in a tempdir so that the viewer command can use the unzipped file.
function view_file (filename)
    local viewer, viewer_replacement
    -- check if the file is zipped
    local nozipname, zipext = parse_zip(filename)
    -- determine viewer_replacement
    if zipext then
        local unzip_cmd = config['unzip_'..zipext]
        if not unzip_cmd then
            err_print('error',
            "No unzip command for ."..zipext..' files, skipping '..filename)
            return false
        end
        local tmpdir = os.tmpdir("/tmp/texdoc.XXXXXX")
        if not tmpdir then
            err_print('error', 'Failed to create tempdir to unzip.')
            return false
        end
        local basename = string.match(nozipname, '.*/(.*)$') or nozipname
        local tmpfile = '"'..tmpdir..'/'..basename..'"'
        local unzip = unzip_cmd..' "'..filename..'">'..tmpfile
        deb_print('view', "Unzip command: "..unzip)
        if not os.execute(unzip) then
            err_print('error', "Failed to unzip '"..filename.."'")
            os.remove(tmpfile)
            os.remove(tmpdir)
            return false
        end
        viewer_replacement = ''..tmpfile..'; '
            ..config.rm_file..' '..tmpfile..'; '
            ..config.rm_dir..' '..tmpdir
        filename = nozipname
    else
        viewer_replacement = '"'..w32_path(filename)..'"'
    end
    -- files without extension are assumed to be text
    local viewext = (filename:match('.*%.([^/]*)$') or 'txt'):lower()
    -- special case : sty files use txt viewer
    if viewext == 'sty' then viewext = 'txt' end
    -- FIXME: hardcoding such cases this way is not very clean
    if viewext == 'texlive' then viewext = 'txt' end
    if viewext == 'htm' then viewext = 'html' end
    -- get a viewer from built-in defaults if not already set
    if not config['viewer_'..viewext] then
        get_default_viewers()
    end
    -- still no viewers? use txt as a fallback
    if not config['viewer_'..viewext] then
        err_print('warning',
            "No viewer_"..viewext.." defined, using viewer_txt.")
        viewext = 'txt'
    end
    assert(config['viewer_'..viewext], 'Internal error: no viewer found.')
    return try_viewing(config['viewer_'..viewext], viewer_replacement)
end

-- view a file, if possible
function try_viewing (view_command, viewer_replacement)
    if string.match (view_command, C.place_holder) then
        view_command = string.gsub(
        view_command, C.place_holder, viewer_replacement)
    else
        view_command = view_command..' '..viewer_replacement
    end

    -- try to catch problems with missing DISPLAY on Unix
    if os.type == 'unix' and not (os.name == 'macosx')
            and os.getenv('DISPLAY') == nil then
        err_print('warning',
        "DISPLAY is not set; your viewer will likely have problems.")
        err_print('warning',
        "Try --list to list results instead of displaying them.")
    end
    err_print('info', 'View command: '..view_command)
     
    -- See long comment below this function for the LC_CTYPE story.
    -- We only want to reset the environment if we have the value
    -- to reset it to. In older versions of luatex, status.lc_* will be nil.
    local env_lc_ctype = status.lc_ctype
    local luatex_lc_ctype = os.setlocale(nil, 'ctype')
    if (env_lc_ctype) then
      err_print('info', "Setting environment LC_CTYPE to: "..env_lc_ctype)
      os.setenv("LC_CTYPE", env_lc_ctype)
    end

    -- the big casino: run the external command.
    if not os.execute(view_command) then
        err_print('error', "Failed to execute: "..view_command)
        return false
    end
    
    -- reset back to "C" (should always be C and always happen, but in case...)
    if (luatex_lc_ctype) then
      os.setenv("LC_CTYPE", luatex_lc_ctype)
    end

    return true
end

-- Explanation of locale madness:
-- LuaTeX resets LC_CTYPE, LC_COLLATE, LC_NUMERIC to "C". That is good for
-- inside luatex, but when we run an external program, if the user's
-- environment is something else, we want to switch back to it. As of
-- TL 2017 LuaTeX, we can inspect the user's locale with status.lc_ctype, etc.
-- 
-- For texdoc purposes, what matters is LC_CTYPE (so we don't bother with
-- the others). For example, with the less pager, when LC_CTYPE=C,
-- non-ASCII bytes are displayed as "<xx>", where xx is the two hex
-- digits for the byte.
-- 
-- See <texdoc-dev-source>/tools/Makefile for a standalone test case.

-----------------------------   display results   -----------------------------

-- print a list of docfile objects (see search.tlu) as a menu
-- if showall is false, stop as soon as a bad result is encountered
function print_menu(name, doclist, showall)
    local max_lines = tonumber(config.max_lines)
    if config.interact_switch and doclist[max_lines+1] then
        -- there may be too many lines, count them
        local n
        if showall then
            n = #doclist
        else
            n = 0
            while doclist[n+1] and doclist[n+1].quality == 'good' do
                n = n + 1
            end
        end
        if n > max_lines then
            io.write (n, " results.  Display them all? (y/N) ")
            local ans = io.read('*line')
            if not ((ans == 'y') or (ans == 'Y')
                -- io.read had a bug wrt windows eol on some versions of texlua
                or (ans == '\ry') or (ans == '\rY')) then
                return
            end
        end
    end
    local i, doc, last_i
    for i, doc in ipairs (doclist) do
        if doc.quality == 'killed' then break end
        if doc.quality ~= 'good' and not showall then break end
        if config.machine_switch == true then
            print(name, doc.score, w32_path(doc.realpath),
            doc.lang or '', doc.details or '')
        else
            last_i = i -- save for test below
            print(string.format('%2d %s', i, w32_path(doc.realpath)))
            if doc.details or doc.lang then
                local line = '   = '
                if doc.lang then line = line..'['..doc.lang..'] ' end
                if doc.details then line = line..doc.details end
                print(line)
            end
        end
    end
    if config.interact_switch then
        io.write ("Enter number of file to view, ",
        "RET to view 1, anything else to skip: ")
        local num_str = io.read('*line')
        -- That returns the empty string on an empty line, nil on EOF.
        -- We only want to default to viewing 1 on an empty line.
        -- Use Lua's faked ternary operator for fun and brevity:
        num = (num_str == "" and 1 or tonumber(num_str))
        if num and doclist[num] and num <= last_i then
            view_doc(doclist[num])
        end
    end
end

-----------------------   deliver results based on mode   ---------------------

function deliver_results(name, doclist, many)
    -- ensure that results were found or apologize
    if not doclist[1] or doclist[1].quality == 'killed' then
        if not config.machine_switch then
            local msg = string.gsub(C.notfound_msg, C.notfound_msg_ph, name)
            print(msg) -- get rid of gsub's 2nd value
        end
        return
    end
    -- shall we show all of them or only the "good" ones?
    local showall = (config.mode == 'showall')
    if not showall and doclist[1].quality ~= 'good' then
        showall = true
        err_print('info', 'No good result found, showing all results.')
    end
    -- view result or show menu based on mode and number of results
    if (config.mode == 'view')
        or config.mode == 'mixed' and (not doclist[2]
            or (doclist[2].quality ~= 'good' and not showall)) then
        view_doc(doclist[1])
    else
        if many and not config.machine_switch then 
            print ("*** Results for: "..name.." ***")
        end
        print_menu(name, doclist, showall)
    end
end

return {
    view_file       = view_file,
    deliver_results = deliver_results,
}

-- vim: ft=lua: