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
|
-- A class to store transient file contents and manage the creation of
-- Manuscript objects.
local Manuscript = require "digestif.Manuscript"
local lpeg = require "lpeg"
local util = require "digestif.util"
local S, C = lpeg.S, lpeg.C
local sequence, gobble_until = util.sequence, util.gobble_until
local path_split, path_join = util.path_split, util.path_join
local path_normalize = util.path_normalize
local weak_values = {__mode = "v"}
local io_open = io.open
local Cache = util.class()
function Cache:__init(tbl)
self.store = setmetatable({}, weak_values)
self.persist = {} -- a place to keep an extra reference to things
-- that shouldn't vanish
if tbl then
for name, src in pairs(tbl) do
self:put(name, src)
end
end
end
-- Set the contents of a file in the cache.
function Cache:put(filename, str)
local props = self.store[filename]
if not props then
props = {}
self.store[filename] = props
else
props.rootname = nil
end
props.contents = str
self.persist[filename] = props
end
-- Return the contents of a file, as stored with the put method, or
-- attempt to read the file from disk. If neither succeeds, return
-- nil. The second return value is a cookie. Values read from disk
-- will be garbage collected when no reference to the cookie exists.
-- Values stored by the put method are not subject to garbage
-- collection until an explicit call to Cache.forget.
--
function Cache:__call(filename)
local props = self.store[filename]
if not props then
local file, str = io_open(filename), nil
if file then
str = file:read("*all")
file:close()
end
props = {contents = str}
self.store[filename] = props
end
return props.contents, props
end
-- Remove a file from the cache.
function Cache:forget(filename)
self.store[filename] = nil
self.persist[filename] = nil
end
local space = S" \t\r"
local magic_comment_patt = sequence(
(space^0 * "%")^1,
space^0 * "!" * util.case_fold("tex"),
space^1 * "root",
space^0 * "=",
space^0 * C(gobble_until(space^0 * "\n")))
local search_magic_comment_patt = util.choice(
magic_comment_patt,
util.search("\n" * magic_comment_patt))
-- Determine the root path of a document from magic comments.
function Cache:rootname(filename)
local _, cookie = self(filename) -- warm up
local props = assert(self.store[filename])
local rootname = props.rootname
if rootname == nil then
local src = self(filename)
local val = search_magic_comment_patt:match(src:sub(1, 1000))
if val then
rootname = path_normalize(path_join(path_split(filename), val))
else
rootname = false
end
props.rootname = rootname
end
return rootname
end
-- Produce a Manuscript object. This method ensures that the result
-- belongs to a tree of Manuscript objects representing a multi-file
-- TeX document. If the rootname argument is omitted, try to infer it
-- using magic comments.
--
-- This method reuses as much information as possible from the cache.
--
-- Arguments:
-- filename (string): the path of the manuscript
-- format (string): the TeX format
-- rootname (string, optional): the root path of the TeX document
--
-- Returns:
-- a Manuscript object
--
function Cache:manuscript(filename, format, rootname)
rootname = rootname or self:rootname(filename) or filename
local root = self.store[rootname] and self.store[rootname][format]
if not root or not root:is_current() then
root = Manuscript{
filename = rootname,
format = format,
files = self
}
self.store[rootname][format] = root
end
local script = root:find_manuscript(filename)
or self:manuscript(filename, format, filename) -- root does not refer back
self.store[filename][format] = script
return script
end
return Cache
|