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 (apars...@clear.net.nz) # ###################################################### 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