if not modules then modules = { } end modules ['buff-ini'] = { version = 1.001, comment = "companion to buff-ini.mkiv", author = "Hans Hagen, PRAGMA-ADE, Hasselt NL", copyright = "PRAGMA ADE / ConTeXt Development Team", license = "see context related readme files" } local trace_run = false trackers.register("buffers.run", function(v) trace_run = v end) local trace_grab = false trackers.register("buffers.grab", function(v) trace_grab = v end) local trace_visualize = false trackers.register("buffers.visualize", function(v) trace_visualize = v end) local report_buffers = logs.reporter("buffers","usage") local report_grabbing = logs.reporter("buffers","grabbing") local context, commands = context, commands local concat = table.concat local type, next, load = type, next, load local sub, format = string.sub, string.format local splitlines, validstring = string.splitlines, string.valid local P, Cs, patterns, lpegmatch = lpeg.P, lpeg.Cs, lpeg.patterns, lpeg.match local variables = interfaces.variables local settings_to_array = utilities.parsers.settings_to_array local formatters = string.formatters local v_yes = variables.yes local catcodenumbers = catcodes.numbers local ctxcatcodes = catcodenumbers.ctxcatcodes local txtcatcodes = catcodenumbers.txtcatcodes buffers = buffers or { } local buffers = buffers local cache = { } local function erase(name) cache[name] = nil end local function assign(name,str,catcodes) cache[name] = { data = str, catcodes = catcodes } end local function append(name,str) local buffer = cache[name] if buffer then buffer.data = buffer.data .. str else cache[name] = { data = str } end end local function exists(name) return cache[name] end local function getcontent(name) local buffer = name and cache[name] return buffer and buffer.data or "" end local function getlines(name) local buffer = name and cache[name] return buffer and splitlines(buffer.data) end local function collectcontent(names,separator) -- no print if type(names) == "string" then names = settings_to_array(names) end local nnames = #names if nnames == 0 then return getcontent("") -- default buffer elseif nnames == 1 then return getcontent(names[1]) else local t, n = { }, 0 for i=1,nnames do local c = getcontent(names[i]) if c ~= "" then n = n + 1 t[n] = c end end return concat(t,separator or "\n") -- was \r end end local function loadcontent(names) -- no print if type(names) == "string" then names = settings_to_array(names) end local nnames = #names local ok = false if nnames == 0 then ok = load(getcontent("")) -- default buffer elseif nnames == 1 then ok = load(getcontent(names[1])) else -- lua 5.2 chunked load local i = 0 ok = load(function() while true do i = i + 1 if i > nnames then return nil end local c = getcontent(names[i]) if c == "" then -- would trigger end of load else return c end end end) end if ok then return ok() elseif nnames == 0 then report_buffers("invalid lua code in default buffer") else report_buffers("invalid lua code in buffer %a",concat(names,",")) end end buffers.raw = getcontent buffers.erase = erase buffers.assign = assign buffers.append = append buffers.exists = exists buffers.getcontent = getcontent buffers.getlines = getlines buffers.collectcontent = collectcontent buffers.loadcontent = loadcontent -- the context interface commands.erasebuffer = erase commands.assignbuffer = assign local anything = patterns.anything local alwaysmatched = patterns.alwaysmatched local function countnesting(b,e) local n local g = P(b) / function() n = n + 1 end + P(e) / function() n = n - 1 end + anything local p = alwaysmatched / function() n = 0 end * g^0 * alwaysmatched / function() return n end return p end local counters = { } local nesting = 0 local autoundent = true local continue = false -- Beware: the first character of bufferdata has to be discarded as it's there to -- prevent gobbling of newlines in the case of nested buffers. The last one is -- a newlinechar and is removed too. -- -- An \n is unlikely to show up as \r is the endlinechar but \n is more generic -- for us. -- This fits the way we fetch verbatim: the indentatio before the sentinel -- determines the stripping. -- str = [[ -- test test test test test test test -- test test test test test test test -- test test test test test test test -- -- test test test test test test test -- test test test test test test test -- test test test test test test test -- ]] -- local function undent(str) -- local margin = match(str,"[\n\r]( +)[\n\r]*$") or "" -- local indent = #margin -- if indent > 0 then -- local lines = splitlines(str) -- local ok = true -- local pattern = "^" .. margin -- for i=1,#lines do -- local l = lines[i] -- if find(l,pattern) then -- lines[i] = sub(l,indent+1) -- else -- ok = false -- break -- end -- end -- if ok then -- return concat(lines,"\n") -- end -- end -- return str -- end -- how about tabs local getmargin = (Cs(P(" ")^1)*P(-1)+1)^1 local eol = patterns.eol local whatever = (P(1)-eol)^0 * eol^1 local strippers = { } local function undent(str) -- new version, needs testing local margin = lpegmatch(getmargin,str) if type(margin) ~= "string" then return str end local indent = #margin if indent == 0 then return str end local stripper = strippers[indent] if not stripper then stripper = Cs((P(margin)/"" * whatever + eol^1)^1) strippers[indent] = stripper end return lpegmatch(stripper,str) or str end function commands.grabbuffer(name,begintag,endtag,bufferdata,catcodes) -- maybe move \\ to call local dn = getcontent(name) if dn == "" then nesting = 0 continue = false end if trace_grab then if #bufferdata > 30 then report_grabbing("%s => |%s..%s|",name,sub(bufferdata,1,10),sub(bufferdata,-10,#bufferdata)) else report_grabbing("%s => |%s|",name,bufferdata) end end local counter = counters[begintag] if not counter then counter = countnesting(begintag,endtag) counters[begintag] = counter end nesting = nesting + lpegmatch(counter,bufferdata) local more = nesting > 0 if more then dn = dn .. sub(bufferdata,2,-1) .. endtag nesting = nesting - 1 continue = true else if continue then dn = dn .. sub(bufferdata,2,-2) -- no \r, \n is more generic elseif dn == "" then dn = sub(bufferdata,2,-2) else dn = dn .. "\n" .. sub(bufferdata,2,-2) -- no \r, \n is more generic end local last = sub(dn,-1) if last == "\n" or last == "\r" then -- \n is unlikely as \r is the endlinechar dn = sub(dn,1,-2) end if autoundent then dn = undent(dn) end end assign(name,dn,catcodes) commands.doifelse(more) end -- The optional prefix hack is there for the typesetbuffer feature and -- in mkii we needed that (this hidden feature is used in a manual). local function prepared(name,list,prefix) -- list is optional if not list or list == "" then list = name end if not name or name == "" then name = list end local content = collectcontent(list,nil) or "" if content == "" then content = "empty buffer" end if prefix then local name = file.addsuffix(name,"tmp") return tex.jobname .. "-" .. name, content else return name, content end end local capsule = "\\starttext\n%s\n\\stoptext\n" local command = "context %s" function commands.runbuffer(name,list,encapsulate) local name, content = prepared(name,list) if encapsulate then content = format(capsule,content) end local data = io.loaddata(name) if data ~= content then if trace_run then report_buffers("changes in %a, processing forced",name) end io.savedata(name,content) os.execute(format(command,name)) elseif trace_run then report_buffers("no changes in %a, not processed",name) end end function commands.savebuffer(list,name,prefix) -- name is optional local name, content = prepared(name,list,prefix==v_yes) io.savedata(name,content) end function commands.getbuffer(name) local str = getcontent(name) if str ~= "" then context.viafile(str,formatters["buffer.%s"](validstring(name,"noname"))) end end function commands.getbuffermkvi(name) -- rather direct ! context.viafile(resolvers.macros.preprocessed(getcontent(name)),formatters["buffer.%s.mkiv"](validstring(name,"noname"))) end function commands.gettexbuffer(name) local buffer = name and cache[name] if buffer and buffer.data ~= "" then context.pushcatcodetable() if buffer.catcodes == txtcatcodes then context.setcatcodetable(txtcatcodes) else context.setcatcodetable(ctxcatcodes) end -- context(function() context.viafile(buffer.data) end) context.getbuffer { name } -- viafile flushes too soon context.popcatcodetable() end end commands.getbufferctxlua = loadcontent function commands.doifelsebuffer(name) commands.doifelse(exists(name)) end -- This only used for mp buffers and is a kludge. Don't change the -- texprint into texsprint as it fails because "penddef" becomes -- "penddef" then. -- function commands.feedback(names) -- texprint(ctxcatcodes,splitlines(collectcontent(names))) -- end function commands.feedback(names) -- bad name, maybe rename to injectbuffercontent context.printlines(collectcontent(names)) end