There are three files attached to this posting:txtmacs.py, txtmacs_help.py -- a script implementing a system of text macros in LyX, and its help file pLyXTexTMacros(compressed).lyx -- an explanatory document (with nearly 670 text macros just waiting to be expanded)
The explanatory document must be saved in *uncompressed* format for the examples in it to work. It explains where to put the python scripts and then launches into an extensive explanation of how to use the system.
While I was working on a table sorting script in September, there was a brief exchange on the Developer's list about text macros. It occurred to me that the scheme of exporting from .lyx to .lyx then doing a buffer-reload might lend itself to a text macro implementation. This started out as a system of expanding abbreviations (which could be nested one within another). Almost immediately I found that an 'abbreviation' could be essentially any LyX construct -- a word, a phrase, a paragraph, a number of paragraphs, an equation, inline or display, a picture, an inset, essentially anything.
Then the code generalised so that the abbreviations could take arguments. Thus a macro [hg] might, by itself, expand to 'hyperbolic geometry' but presented with an argument, [hg](H), would expand to 'Hyperbolic geometry', meaning it could be used within and at the start of sentences. With 3 well-chosen keyboard shortcuts, I found macros provided a more convenient way of inserting character styles than presently available (in my opinion) in LyX. Macros also provide the easy insertion of LyX menu selections (as in the LyX manuals) with their sans serif formatting and menu separation character. And they enable the insertion of a specified number of any given character -- 47 asterisks perhaps, or 34 smileys.
There are conditionals available which enlarge the scope of what is possible. In fact it is all very new to me and I don't know what is possible. But the basic system has proved undeniably useful, and I wouldn't consider undertaking any substantial writing task in LyX now without this facility to hand.
Andrew
# Define and expand text macros in a LyX document. # Part of the pLyX.py system; not an independent script # # txtmacs.py # # Andrew Parsloe ([email protected]) # ###################################################### import re, argparse bs1 = '\n\\backslash\n1\n' macdict = {'toggle': [[], ''], 'if0': [[''], bs1], 'if1': [[''], bs1], 'peel': [[''], bs1]} def main(infl, outfl, options, guff): '''Expand text macros.''' flex_arg = r'\begin_inset Flex .[argument]' begin_layout = r'\begin_layout' end_layout = r'\end_layout' begin_inset = r'\begin_inset' end_inset = r'\end_inset' begin_note = r'\begin_inset Note Note' bkslash = '\\' backslash = '\\backslash\n' status_open = 'status open\n' status_coll = 'status collapsed\n' re_macro = re.compile(r'(\w+)\n*(\w*)\s*(\d*)\s*$') re_lyxcmds = re.compile(r'(\\\w+) \w+') underscores = set([begin_layout, begin_inset, end_layout, end_inset]) charstyles = ['\\family', '\\series', '\\shape', '\\size', '\\emph', \ '\\noun', '\\underbar', '\\strikeout', '\\uuline', \ '\\uwave', '\\no_emph', '\\no_noun', '\\no_strikeout', \ '\\no_bar', '\\no_uuline', '\\no_uwave'] def output(depth, L): # write to file only at top level if depth == 0: outfl.write(L) return '' else: return L def defaults(s): '''Return used charstyles to default states.''' temp = r'' for y in (set(re_lyxcmds.findall(s)) - underscores): if y == r'\color': temp += y + ' inherit\n' elif y == r'\lang': temp += y + ' english\n' elif y.lower() in charstyles: temp += y + ' default\n' return temp def expand(mname, sup_args, depth): '''Expand the macro (recursively if necessary).''' # substitute values for placeholders macexp = macdict.get(mname)[1] macprms = macdict.get(mname)[0] num_params = len(macprms) macprms = ['\n'] + macprms[:num_params - len(sup_args)] + sup_args for i in range(num_params, -1, -1): macexp = macexp.replace('\n\\backslash\n' + str(i), macprms[i]) # replace macro & inset with its expansion # & expand included macros store = scan(iter(macexp.splitlines(True)), depth + 1) # return char styles to defaults store += output(depth + 1, defaults(macexp)) return store def strip_outers(stuff): '''Strip enclosing layout statements.''' stuff = stuff.partition('\n')[2] stuff = stuff.rpartition(end_layout)[0] return stuff def inset_contents(iterable, keepouters): '''Get contents of inset +/- outer layout statements.''' contents = lines = '' insets = 1 status = True bslash = False for line in iterable: lines += line # lose empties & status line if line == '\n': continue elif status: if status_open == line or status_coll == line: status = False continue elif begin_inset in line: insets += 1 bslash = True contents += line elif end_inset in line: insets -= 1 bslash = True if insets == 0: if keepouters: return contents, lines else: # strip outermost layout statements temp = strip_outers(contents) return temp, lines else: contents += line elif backslash == line: if bslash: contents += '\n' bslash = False contents += line elif bkslash == line[0]: bslash = True contents += line else: contents += line bslash = False def indx(look_for, given): '''Return index of look_for in given.''' if look_for in given: return given.index(look_for) else: return len(given) def scan(iterable, depth): '''Parse the iterable line by line & resolve into output.''' # the 2nd is needed for the "toggle" macro flex_macro = r'\begin_inset Flex .expand macro' flex_macr0 = r'\begin_inset Flex .expand macro' lines = store = '' status = 0 pending = starting = False params = supplied_args = [] for line in iterable: if line in '\n': continue # scanning text, looking for macro & note insets elif status == 0: # a macro inset? if flex_macro in line: status += 1 lines = line # store the line defining = False # a note inset? elif begin_note in line and not starting: store += output(depth, line) if not scanotes: # don't expand macros in notes toss, temp = inset_contents(iterable, True) store += output(depth, temp) # looking for toggle macro elif starting: lines += line if 'toggle\n' == line: # found it! flex_macro = flex_macr0 status += 1 defining = False starting = False if toggle_keep: store += output(depth, lines) lines = '' store += output(depth, end_layout + '\n' \ + end_inset + '\n') # not the toggle macro elif end_inset in line: starting = False store += output(depth, lines) lines = '' # macro expansion off; is this the toggle macro? elif flex_macr0 in line: lines = line starting = True # otherwise write to file else: store += output(depth, line) # in a macro inset elif status == 1: # get macro if re_macro.match(line): m = re_macro.match(line) macname = m.group(1) + m.group(2) if macname not in macdict: # a defining inset if m.group(3) != '': nparams = int(m.group(3)) else: nparams = 0 lines += line store += output(depth, lines) lines = line = '' defining = True ndflts = 0 params = [globdef for x in range(nparams)] elif macname == 'toggle': # turn off macro expansion by misnaming (a hack) flex_macro = r'\penguin_insect .expand macro' if toggle_keep: defining = True lines += line store += output(depth, lines) lines = line = '' elif m.group(3) == '0': # delete macro from dictionary macdict.pop(macname) lines += line store += output(depth, lines) lines = line = '' defining = True else: # a 'using' use; expansion looming pending = True elif begin_note in line: store += output(depth, line) toss, temp = inset_contents(iterable, True) store += output(depth, temp) elif end_inset in line: if defining: defining = False store += output(depth, end_layout + '\n') store += output(depth, line) status -= 1 else: # get arguments supplied_args = [] status += 1 # get default parameters elif flex_arg in line: store += output(depth, line) params[ndflts], temp = inset_contents(iterable, False) ndflts += 1 store += output(depth, temp) # get macro expansion elif flex_macro in line: store += output(depth, line) expansion, temp = inset_contents(iterable, False) macdict[macname] = [params, expansion] params = [] store += output(depth, temp) else: lines += line # get arguments (if any); expand macro elif status == 2: # get arguments (but not too many!) if flex_arg in line and \ len(supplied_args) < len(macdict.get(macname)[0]): arg, toss = inset_contents(iterable, False) # check if arg contains a top-level macro if indx(flex_macro, arg) < indx(flex_arg, arg): arg = scan(iter(arg.splitlines(True)), depth + 1) # strip nested argument insets (if they exist) if macname == 'peel': if flex_arg in arg[:29]: arg, toss = inset_contents(arg.splitlines(True)[1:], False) arg = arg.rstrip() # block (some) args of the conditionals if (macname == 'if0' and arg != '') or \ (macname == 'if1' and arg == ''): continue else: supplied_args.append(arg) # macro following hard on another elif flex_macro in line: # expand previous macro store += output(depth, expand(macname, supplied_args, depth)) pending = False # now parse this one status = 1 defining = False lines = line else: # all args found; expand store += output(depth, expand(macname, supplied_args, depth)) store += output(depth, line) pending = False status = 0 if pending: store += output(depth, expand(macname, supplied_args, depth)) return store ###################################################### # write the prelims outfl.write(guff) # get the options parser = argparse.ArgumentParser(description='Expand text macros') parser.add_argument('-n', action ='store_true', default = False, \ help='Expand macros in notes') parser.add_argument('-t', action ='store_true', default = False, \ help='Retain toggle macros in document') parser.add_argument('-g', action ='store', default = '', \ help='Set global default value') scanotes = parser.parse_args(options).n toggle_keep = parser.parse_args(options).t globdef = parser.parse_args(options).g depth = 0 scan(infl, depth) return 1
def helpnote(hv):
if hv > 1:
return header + version
else:
return header + tail
header = r'''\begin_layout LyX-Code
\family roman
\series bold
.expand macros
\end_layout
'''
version = r'''\begin_layout LyX-Code
\family roman
Version 1.0 (30 December 2012)
'invisible punctuator'; reversal of substitution order; built-in 'peel' macro.
\end_layout
\begin_layout LyX-Code
\family roman
Version 0.6 (20 December 2012)
expand arguments containing top-level macros before passing to the parent macro.
\end_layout
\begin_layout LyX-Code
\family roman
Version 0.5 (c.1 December 2012)
built-in 'if0' and 'if1' macros.
\end_layout
\begin_layout LyX-Code
\family roman
Version 0.4 (10 November 2012)
built-in 'toggle' macro.
\end_layout
\begin_layout LyX-Code
\family roman
Version 0.3 (21 October 2012)
first version for the pLyX system.
\end_layout
\begin_layout LyX-Code
\family roman
Version 0.2 (18 October 2012)
first functioning script using insets instead of markers.
\end_layout
\begin_layout LyX-Code
\family roman
Version 0.1 (13 October 2012)
first script for expanding abbreviations.
\end_layout
'''
tail = r'''\begin_layout LyX-Code
\family roman
Define and expand text macros.
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
\family roman
\series bold
Global options
\end_layout
\begin_layout LyX-Code
\family roman
\series bold
-h --help
\series default
show this help note.
\end_layout
\begin_layout LyX-Code
\family roman
\series bold
-v --version
\series default
show version information.
\end_layout
\begin_layout LyX-Code
\family roman
\series bold
-g
\series default
set global default value for arguments; the default is the empty string.
E.g.
\series bold
-g *
\series default
sets the global default to *.
\end_layout
\begin_layout LyX-Code
\family roman
\series bold
-n
\series default
make macros within (yellow) notes expandable; default
\series bold
False
\series default
.
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
\family roman
\series bold
Defining macros
\end_layout
\begin_layout LyX-Code
\family roman
\emph on
Example 1:
\end_layout
\begin_layout LyX-Code
\family roman
\begin_inset Flex .expand macro|txtmacs
status open
\begin_layout Plain Layout
\family roman
Lp
\begin_inset Flex .expand macro|txtmacs
status open
\begin_layout Plain Layout
\begin_inset Formula ${\displaystyle \left(\frac{\partial^{2}}{\partial x^{2}}+\frac{\partial^{2}}{\partial y^{2}}+\frac{\partial^{2}}{\partial z^{2}}\right)\phi=0}$
\end_inset
\end_layout
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout LyX-Code
\family roman
defines a
\begin_inset Quotes els
\end_inset
pure abbreviation
\begin_inset Quotes ers
\end_inset
macro
\series bold
Lp
\series default
(one with no parameters) which expands to Laplace's equation in mathematical
display format:
\family default
\begin_inset Flex .expand macro|txtmacs
status open
\begin_layout Plain Layout
\family roman
Lp
\end_layout
\end_inset
\end_layout
\begin_layout LyX-Code
\family roman
\emph on
Example 2:
\end_layout
\begin_layout LyX-Code
\family roman
\begin_inset Flex .expand macro|txtmacs
status open
\begin_layout Plain Layout
\family roman
hg 1
\begin_inset Flex .[argument]
status collapsed
\begin_layout Plain Layout
\family roman
h
\end_layout
\end_inset
\begin_inset Flex .expand macro|txtmacs
status open
\begin_layout Plain Layout
\family roman
\backslash
1yperbolic geometry
\end_layout
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout LyX-Code
\family roman
shows the definition of a macro
\series bold
hg
\series default
with one parameter.
The default value of the parameter is
\begin_inset Quotes els
\end_inset
h
\begin_inset Quotes ers
\end_inset
. Using
\begin_inset Quotes els
\end_inset
=>
\begin_inset Quotes ers
\end_inset
to mean
\begin_inset Quotes els
\end_inset
expands to
\begin_inset Quotes ers
\end_inset
,
\end_layout
\begin_layout LyX-Code
\family roman
\begin_inset Flex .expand macro|txtmacs
status collapsed
\begin_layout Plain Layout
\family roman
hg
\end_layout
\end_inset
=> hyperbolic geometry
\end_layout
\begin_layout LyX-Code
\family roman
\begin_inset Flex .expand macro|txtmacs
status collapsed
\begin_layout Plain Layout
\family roman
hg
\end_layout
\end_inset
\begin_inset Flex .[argument]
status collapsed
\begin_layout Plain Layout
\family roman
H
\end_layout
\end_inset
=> Hyperbolic geometry
\end_layout
\begin_layout LyX-Code
\family roman
so that the latter is appropriate for use at the start of a sentence.
\end_layout
\begin_layout LyX-Code
\family roman
\emph on
Example 3:
\end_layout
\begin_layout LyX-Code
\family roman
\begin_inset Flex .expand macro|txtmacs
status open
\begin_layout Plain Layout
\family roman
tp 2
\family default
\begin_inset Flex .[argument]
status collapsed
\begin_layout Plain Layout
File Handling
\end_layout
\end_inset
\family roman
\begin_inset Flex .[argument]
status collapsed
\begin_layout Plain Layout
File Formats
\end_layout
\end_inset
\family sans
\begin_inset Flex .expand macro|txtmacs
status open
\begin_layout Plain Layout
\family sans
Tools \SpecialChar \menuseparator
Preferences \SpecialChar \menuseparator
\backslash
1 \SpecialChar \menuseparator
\backslash
2
\end_layout
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout LyX-Code
\family roman
defines the macro
\series bold
tp
\series default
with two parameters, for both of which default values are given (but specifying
default values for some or all parameters is not essential).
Thus
\end_layout
\begin_layout LyX-Code
\family roman
\begin_inset Flex .expand macro|txtmacs
status collapsed
\begin_layout Plain Layout
\family roman
tp
\end_layout
\end_inset
=>
\family sans
Tools \SpecialChar \menuseparator
Preferences \SpecialChar \menuseparator
File Handling \SpecialChar \menuseparator
File Formats
\end_layout
\begin_layout LyX-Code
\family roman
\begin_inset Flex .expand macro|txtmacs
status collapsed
\begin_layout Plain Layout
\family roman
tp
\end_layout
\end_inset
\family default
\begin_inset Flex .[argument]
status collapsed
\begin_layout Plain Layout
\family roman
Converters
\end_layout
\end_inset
\family roman
=>
\family sans
Tools \SpecialChar \menuseparator
Preferences \SpecialChar \menuseparator
File Handling \SpecialChar \menuseparator
Converters
\end_layout
\begin_layout LyX-Code
\family roman
\begin_inset Flex .expand macro|txtmacs
status collapsed
\begin_layout Plain Layout
\family roman
tp
\end_layout
\end_inset
\begin_inset Flex .[argument]
status collapsed
\begin_layout Plain Layout
Editing
\end_layout
\end_inset
\family default
\begin_inset Flex .[argument]
status collapsed
\begin_layout Plain Layout
Shortcuts
\end_layout
\end_inset
\family roman
=>
\family sans
Tools \SpecialChar \menuseparator
Preferences \SpecialChar \menuseparator
Editing \SpecialChar \menuseparator
Shortcuts
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
\family roman
\emph on
Parameters
\end_layout
\begin_layout LyX-Code
\family roman
Parameters are entered in an
\family sans
.[argument]
\family roman
inset: the contents of an
\family sans
.[argument]
\family roman
inset are invisible to LaTeX and have no effect on the pdf.
Parameters are numbered from
\backslash
1,
\backslash
2, ...
\end_layout
\begin_layout Itemize
The contents of both macro and argument insets are invisible to LaTeX.
\end_layout
\begin_layout Itemize
\family roman
More than one macro may be defined in a macro inset, but each must start
on a new line in native LyX format (check
\family sans
View Source
\family roman.
\end_layout
\begin_layout Itemize
\family roman
An expansion may include other macros.
\end_layout
\begin_layout Itemize
\family roman
An expansion may be a word or phrase, or a multi-paragraph passage, containing
text and character formatting, an equation, a graphic, indeed anything
that can be displayed in LyX. It may contain parameters and other macros.
\end_layout
\begin_layout Itemize
A macro must be defined
\emph on
before
\emph default
its first use in the text (but the order of definition within an inset
is immaterial).
\end_layout
\begin_layout Itemize
Use the built-in macro
\series bold
toggle
\series default
to stop macro expansion thereafter or, used again, to start it again.
\end_layout
\begin_layout LyX-Code
\family roman
\series bold
Shortcuts
\end_layout
\begin_layout LyX-Code
\family roman
Convenient keyboard shortcuts are needed for the
\family sans
Macro
\family roman
and
\family sans
.[argument]
\family roman
insets, and also for
\begin_inset Quotes els
\end_inset
jumping
\begin_inset Quotes ers
\end_inset
out of an inset (to the right), so that the cursor is correctly placed
for further input.
Perhaps (after removing the current assignment of
\family sans
Ctrl+K
\family roman
):
\end_layout
\begin_layout Itemize
\series bold
flex-insert
\begin_inset Quotes eld
\end_inset
.expand macro|txtmacs
\begin_inset Quotes erd
\end_inset
\series default
<=>
\family sans
Ctrl+K
\family roman
(or
\family sans
Ctrl+H
\family roman
)
\end_layout
\begin_layout Itemize
\series bold
flex-insert .[argument]
\series default
<=>
\family sans
Ctrl+;
\end_layout
\begin_layout Itemize
\series bold
command-sequence line-end; char-right; toggle-inset;
\series default
<=>
\family sans
Ctrl+J
\family default
(J as in Jump)
\end_layout
'''
pLyXTextMacros(compressed).lyx
Description: Binary data
