Skip to content

Instantly share code, notes, and snippets.

@phi-gamma
Created May 5, 2012 11:30
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save phi-gamma/2601712 to your computer and use it in GitHub Desktop.
Save phi-gamma/2601712 to your computer and use it in GitHub Desktop.
\unprotect
\let\@EA\expandafter%% Precaution in case Hans removes it.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% implementation
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% We-F¢ll have a minimal setup macro as an interface.
\def\setupsortstructure{%
\dosingleempty\do_setup_sort_structure%
}
\def\do_setup_sort_structure[#1]{%
\doifassignmentelse{#1}{\getparameters[sortstructure][#1]}{}%
}
\def\register_structure_at_depth#1#2{%
\ctxlua{thirddata.sort_structure.register("#1", \number#2)}%
}
%%% Interception layer. This will define fake sectioning commands from
%%% the specification given in \setupsortstructure.
\def\intercept_structure{%
%%% The redefinition list is assumed to represent the actual nesting
%%% hierarchy. The counter \redefined_structure will be incremented
%%% for every item in the list, yielding the nesting depth.
\newcount\redefined_structures
%%% This macro will be used to iterate over the list of structure
%%% elements.
\def\redefine_structure_element##1{%
%%% Calculate current depth.
\advance\redefined_structures\plusone
\register_structure_at_depth{##1}{\the\redefined_structures}%
%%% We will keep the definition of the original sectioning macros
%%% intact, i.e. the optional first argument (a comma separated
%%% list) will be respected. To this end we define ordinary
%%% two-stage macros and store the arguments along with the actual
%%% heading text.
\@EA\def\csname##1\endcsname{%
\edef\current_depth{\the\redefined_structures}%
%%% 1stly define a wrapper so we can store the optional first arg.
\@EA\dosingleempty\csname do_##1\endcsname%
}
%%% The items we get will be passed through to the Lua end.
\@EA\def\csname do_##1\endcsname[####1]####2{
\ctxlua{
thirddata.sort_structure.collect(
\!!bs##1\!!es, %% type
\!!bs\detokenize{####2}\!!es, %% label
\!!bs####1\!!es %% args (optional)
)
}
}%
}
%%% Now the entry macro. Will be sorted as well but lacks both a
%%% definite depth and optional arguments (the latter could be
%%% implemented but that was not part of the question.
\def\define_entry_command{%
\def\entry####1{%
\ctxlua{
thirddata.sort_structure.collect(
\!!bs entry\!!es,
\!!bs\detokenize{####1}\!!es
)
}%
}%
}
%%% Of course, at some point we¢ll have to revert the interception and
%%% reinstate the real commands.
\prependtoks
%%% End group with \stoptext.
\endgroup
%%% Now that we¢re done collecting, we can sort the items
%%% recursively and push the content according to the restructured
%%% order.
\ctxlua{thirddata.sort_structure.process_tree()}
\to \everystoptext%
%%% This will activat the interception layer.
\begingroup %%% The parallelism between TEX and Lua scoping is great.
\@EA\processcommalist\@EA[\sortstructuresections]%
\redefine_structure_element
}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Lua internals
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\startluacode
--- Pro tip: uncomment the print statements to watch what¢s going on.
--- Namespacing adheres to the guidelines for modules.
thirddata = thirddata or { }
thirddata.sort_structure = { }
local sort = thirddata.sort_structure
sort.args = { }
sort.depth_to_id = { [0] = "root" }
sort.id_to_depth = { }
sort.labels = { }
--- Compare function. You will have to modify it to suit your needs.
local nodecmp = function (a, b)
return string.lower(a.label) < string.lower(b.label)
end
--- The tree will be sorted recursively. Once we arrive at max depth
--- we expect only nodes of type -Y´entry¡ to follow.
local do_process_tree do_process_tree = function (tree)
local depth, label, nodes = tree.depth, tree.label, tree.nodes
-- print(string.rep("*", depth)..">", "["..sort.depth_to_id[depth].."]", label)
if nodes then
table.sort(nodes, nodecmp)
for i=1, #nodes do
local node = nodes[i]
if node.id == "entry" then
-- print(string.format([=[\letterbackslash entry{%s}]=], node.label))
context.sortstructureentrycommand(node.label)
else
context(string.format([=[\letterbackslash%s[%s]{%s}]=],
node.id,
node.args,
node.label))
do_process_tree(node)
end
end
end
end
--- Wrapper for the sorting function.
sort.process_tree = function ()
do_process_tree(sort.doctree)
end
--- This is called when the structure is defined initially. It maps
--- element names to their respective levels.
sort.register = function (id, depth)
-- print("Registering structure", id, "=>", depth)
sort.id_to_depth[id] = depth
sort.depth_to_id[depth] = id
end
--- Root node. Poor thing ain-F¢t got no parents.
sort.doctree = {
parent = false,
nodes = { },
depth = 0,
label = "root",
}
local currentnode = sort.doctree
--- This function does the main work of the interception layer. It
--- ensures that entries are inserted in the correct depth.
sort.collect = function (id, label, args)
if id == "entry" then
local parent = currentnode.nodes[#currentnode.nodes]
-- print("Collecting entry", parent.id, parent.depth, label)
parent.nodes[#parent.nodes+1] = {
args = args,
depth = parent.depth + 1,
id = "entry",
label = label,
nodes = false,
}
return
end
local depth = sort.id_to_depth[id]
local indepth = sort.id_to_depth[id] - 1
-- print("Collecting element", id, depth, label, args)
local newnode = {
args = args, --- <string>
depth = depth, --- <int>
id = id, --- <string>
label = label, --- <string>
nodes = { }, --- <node array>
}
if indepth > currentnode.depth then
local parent = currentnode.nodes[#currentnode.nodes]
newnode.parent = parent
parent.nodes[#parent.nodes+1] = newnode
currentnode = parent
elseif indepth == currentnode.depth then
local parent = currentnode
newnode.parent = parent
parent.nodes[#parent.nodes+1] = newnode
else -- less
local diff = currentnode.depth - indepth
for i=1, diff do
currentnode = currentnode.parent
end
local parent = currentnode
newnode.parent = parent
parent.nodes[#parent.nodes+1] = newnode
end
end
\stopluacode
%%% Insert the interception layer. The -Y´real¡ heading commands will be
%%% restored once we arrive at \stoptext.
\appendtoks
\intercept_structure
\define_entry_command
\to \everystarttext
\protect
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% setups
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% Demo layout.
\setuppapersize[A6][A6]
\setuplayout [width=middle,height=middle,backspace=1cm,topspace=1cm,header=\zeropoint,footer=\zeropoint]
%%% Demo heading style.
\setuphead [part,chapter,section] [placehead=yes,page=no]%,number=no,align=middle]
\setuphead [part] [style=\WORD\tfa]
\setuphead [chapter] [style=\WORD\tf]
\setuphead [section] [style=\word\tf\sc]
%%% The macro for entries.
\definehighlight [entryhighlight] [style=italic]
\define[1]\entry{%
\leavevmode
\noindentation
\hbox to2em{\hfill-A·}\hskip.5em
\entryhighlight{#1}%
\blank[line]%
}
%%% Configuration.
\setupsortstructure[
%%% The structure elements to intercept.
sections={part,chapter,section,subsection},
%%% Hook your self-defined macro into this here.
entrycommand=\entry,
]
\starttext
%%% Here we are already using the fake commands ...
\part{Birds}
\chapter{North America}
\section{West Coast}
\entry{Bald Eagle}
\entry{Spotted Owl}
\section{East Coast}
\section{Alaska}
\chapter{South America}
\section{Peru}
\section{Brazil}
\part{Bears}
\chapter{Canada}
\chapter{Greenland}
%%% Only now will the actual typesetting take place.
\stoptext
\endinput
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment