]> git.lyx.org Git - features.git/blob - development/tools/gen_lfuns.py
LFUNs.lyx improvements
[features.git] / development / tools / gen_lfuns.py
1 #! /usr/bin/env python
2 # -*- coding: utf-8 -*-
3
4 # file gen_lfuns.py
5 # This file is part of LyX, the document processor.
6 # Licence details can be found in the file COPYING.
7
8 # author Ewan Davies
9
10 # Full author contact details are available in file CREDITS
11
12 # Usage:
13 # gen_lfuns.py <path/to/LyXAction.cpp> <where/to/save/LFUNs.lyx>
14
15 import sys,re,os.path
16 from datetime import date
17
18 def error(message):
19     sys.stderr.write(message + '\n')
20     sys.exit(1)
21
22 def usage(prog_name):
23     return "Usage: %s <path/to/LyXAction.cpp> [<where/to/save/LFUNs.lyx>]" % prog_name
24
25 DOXYGEN_START = "/*!"
26 DOXYGEN_END = "},"
27
28 LYX_NEWLINE = "\n\\begin_inset Newline newline\n\\end_inset\n\n"
29 LYX_BACKSLASH = "\n\\backslash\n"
30
31 HTMLONLY_START = "\\htmlonly"
32 HTMLONLY_END = "\\endhtmlonly"
33 LFUN_NAME_ID = "\\var lyx::FuncCode lyx::"
34 LFUN_ACTION_ID = "\\li Action: "
35 LFUN_NOTION_ID = "\\li Notion: "
36 LFUN_SYNTAX_ID = "\\li Syntax: "
37 LFUN_PARAMS_ID = "\\li Params: "
38 LFUN_SAMPLE_ID = "\\li Sample: "
39 LFUN_ORIGIN_ID = "\\li Origin: "
40 LFUN_ENDVAR = "\\endvar"
41
42 ID_DICT = dict(name=LFUN_NAME_ID, action=LFUN_ACTION_ID, notion=LFUN_NOTION_ID, 
43                 syntax=LFUN_SYNTAX_ID, params=LFUN_PARAMS_ID, sample=LFUN_SAMPLE_ID, origin=LFUN_ORIGIN_ID)
44
45 LFUNS_HEADER = """# gen_lfuns.py generated this file. For more info see http://www.lyx.org/
46 \\lyxformat 474
47 \\begin_document
48 \\begin_header
49 \\textclass article
50 \\begin_preamble
51 \\renewcommand{\\descriptionlabel}[1]{\\hspace\\labelsep\\upshape\\bfseries #1:}
52 \\renewenvironment{description}{\\list{}{%
53   \\setlength{\\itemsep}{-2pt}
54   \\advance\\leftmargini6\\p@ \\itemindent-12\\p@
55   \\labelwidth\\z@ \\let\\makelabel\\descriptionlabel}%
56 }{
57   \\endlist
58 }
59 \\end_preamble
60 \\use_default_options false
61 \\maintain_unincluded_children false
62 \\language english
63 \\language_package default
64 \\inputencoding auto
65 \\fontencoding global
66 \\font_roman default
67 \\font_sans default
68 \\font_typewriter default
69 \\font_math auto
70 \\font_default_family default
71 \\use_non_tex_fonts false
72 \\font_sc false
73 \\font_osf false
74 \\font_sf_scale 100
75 \\font_tt_scale 100
76 \\graphics default
77 \\default_output_format default
78 \\output_sync 0
79 \\bibtex_command default
80 \\index_command default
81 \\paperfontsize default
82 \\spacing single
83 \\use_hyperref false
84 \\papersize default
85 \\use_geometry true
86 \\use_package amsmath 1
87 \\use_package amssymb 1
88 \\use_package cancel 0
89 \\use_package esint 1
90 \\use_package mathdots 0
91 \\use_package mathtools 0
92 \\use_package mhchem 1
93 \\use_package stackrel 0
94 \\use_package stmaryrd 0
95 \\use_package undertilde 0
96 \\cite_engine basic
97 \\cite_engine_type default
98 \\biblio_style plain
99 \\use_bibtopic false
100 \\use_indices false
101 \\paperorientation portrait
102 \\suppress_date false
103 \\justification true
104 \\use_refstyle 0
105 \\index Index
106 \\shortcut idx
107 \\color #008000
108 \\end_index
109 \\leftmargin 2.5cm
110 \\topmargin 2cm
111 \\rightmargin 3cm
112 \\bottommargin 2.5cm
113 \\secnumdepth 3
114 \\tocdepth 3
115 \\paragraph_separation indent
116 \\paragraph_indentation default
117 \\quotes_language english
118 \\papercolumns 1
119 \\papersides 1
120 \\paperpagestyle default
121 \\tracking_changes false
122 \\output_changes false
123 \\html_math_output 0
124 \\html_css_as_file 0
125 \\html_be_strict false
126 \\end_header
127
128 \\begin_body
129
130 \\begin_layout Title
131 LyX Functions (LFUNs)
132 \\end_layout
133
134 \\begin_layout Author
135 The LyX Team
136 \\end_layout
137
138 \\begin_layout Date""" + "\n" + str(date.today()) + """
139 \\end_layout
140
141 """
142
143 LFUNS_INTRO ="""\\begin_layout Section*
144 About this manual
145 \\end_layout
146
147 \\begin_layout Standard
148 This manual documents the 
149 \\begin_inset Quotes eld
150 \\end_inset
151
152 LyX Functions
153 \\begin_inset Quotes erd
154 \\end_inset
155
156  (abbreviated LFUNs).
157  These are commands that are used to make LyX perform specific actions.
158  LyX itself uses these functions internally, and every internal action is
159  bound to an LFUN.
160 \\end_layout
161
162 \\begin_layout Standard
163 LFUNs are also used in the files that define keyboard shortcuts, menu or
164  toolbar items.
165  So if you want to change\\SpecialChar \\slash{}
166 customize the user interface, you need to deal
167  with LFUNs.
168  Furthermore, external programs can use LFUNs to communicate with and 
169 \\begin_inset Quotes eld
170 \\end_inset
171
172 remote-control
173 \\begin_inset Quotes erd
174 \\end_inset
175
176  LyX.
177  Finally, you can also issue LFUNs directly via the so called mini-buffer
178  which can be opened via 
179 \\begin_inset Info
180 type  "shortcuts"
181 arg   "command-execute"
182 \\end_inset
183
184 .
185 \\end_layout
186
187 \\begin_layout Standard
188 In the following, all LFUNs are listed, categorized by function.
189 \\end_layout
190
191 """
192
193
194 LFUNS_FOOTER = """\\end_body
195 \\end_document
196 """
197
198 def parse_lfun(str):
199     """Takes a comment block (str) and parses it for fields describing the LFUN. Returns a dict containing the fields."""
200     
201     lfun = dict(action="", notion="", syntax="", params="", sample="", origin="")
202     field = ""
203     lines = str.splitlines()
204     # strip leading whitespace and * from the lines of the comment to get 
205     # rid of unimportant characters
206     for i in range(0, len(lines)):
207         lines[i] = lines[i].strip(" *")
208     
209     for i in range(0, len(lines) - 1):
210         # work out what field is being read if none of these is found, the line will be added
211         #     to the last field edited
212         # since the field identifier is not included skip it out if it's found, otherwise skip
213         #     nothing as an existing field is being added to
214         # if a field id is found, then its the first line of the field so set the pre_space to ""
215         #     so that the first line isn't prespaced
216         if lines[i].startswith(LFUN_ACTION_ID):
217             field = "action"
218             pre_space = ""
219             skip = len(ID_DICT[field])
220         elif lines[i].startswith(LFUN_NOTION_ID):
221             field = "notion"
222             pre_space = ""
223             skip = len(ID_DICT[field])
224         elif lines[i].startswith(LFUN_SYNTAX_ID):
225             field = "syntax"
226             pre_space = ""
227             skip = len(ID_DICT[field])
228         elif lines[i].startswith(LFUN_PARAMS_ID):
229             field = "params"
230             pre_space = ""
231             skip = len(ID_DICT[field])
232         elif lines[i].startswith(LFUN_SAMPLE_ID):
233             field = "sample"
234             pre_space = ""
235             skip = len(ID_DICT[field])
236         elif lines[i].startswith(LFUN_ORIGIN_ID):
237             field = "origin"
238             pre_space = ""
239             skip = len(ID_DICT[field])
240         elif lines[i].startswith(LFUN_ENDVAR):
241             break
242         else:
243             skip = 0
244             # if a manual line break was found last line, don't prespace this line
245             if i > 1 and lines[i-1].endswith("\\n"):
246                 pre_space = ""
247             else:
248                 pre_space = " "
249         
250         # add the line to the field, processing it for \ characters and \n
251         # which, if occurring at the end of a line, must become a LYX_NEWLINE
252         line = lines[i][skip:]
253         
254         # deal with \htmlonly
255         # TODO: convert chars found in htmlonly to unicode
256         start = line.find(HTMLONLY_START)
257         if start > 0:
258             # if removing the htmlonly element leaves a double space, go back one to remove it
259             if line[start-1] == " ":
260                 start = start - 1
261             end = line.find(HTMLONLY_END)
262             if end > start:
263                 end = line.find(HTMLONLY_END) + len(HTMLONLY_END)
264                 line = line[:start] + line[end:]
265             #else:
266             # TODO: if HTMLONLY_END is not found, look on the next line
267             # TODO: in the current LyXAction.cpp there are no htmlonly fields which go over a line break
268         
269         # deal with \ but leave \n if at the end of the line
270         slash_idx = line.find("\\")
271         while slash_idx >= 0:
272             if slash_idx < len(line)-2 \
273             or slash_idx == len(line)-1:
274                 # true when the \ is not the last or second last char
275                 #      or when the slash is the last char of the line
276                 
277                 # slash must be interpreted literaly so swap it for a LYX_BACKSLASH
278                 line = line[:slash_idx] + LYX_BACKSLASH + line[slash_idx+1:]
279                 # skip the index ahead beyond the added text
280                 slash_idx = slash_idx + len(LYX_BACKSLASH)
281             elif line[slash_idx+1] != "n": # only evaluated if the line ends "\x" where 'x' != 'n'
282                 line = line[:slash_idx] + LYX_BACKSLASH + line[slash_idx+1:]
283                 # skip the index ahead beyond the added text
284                 slash_idx = slash_idx + len(LYX_BACKSLASH) 
285             # look for the next \
286             slash_idx = line.find("\\", slash_idx+1)
287             
288         # \n at the end of lines will not be processed by the above while loop
289         # so sort those out now
290         # sometime lines end " \n" so chop the space if its there
291         if line.endswith(" \\n"):
292             line = line[:len(line)-3] + LYX_NEWLINE
293         elif line.endswith("\\n"):
294             line = line[:len(line)-2] + LYX_NEWLINE
295         
296         # any references to other LFUNs need the # removing
297         # TODO: actually insert a cross-reference here
298         line = line.replace("#LFUN", "LFUN")
299         
300         # handle the few #lyx:: cases
301         line = line.replace("#lyx::", "lyx::")
302
303         # the first line might not have a field in it in which
304         # case the variable field won't have a value, so check
305         # to avoid an error
306         if field != "":
307             lfun[field] = lfun[field] + pre_space + line
308         
309         # TODO: sort out chopping lines of more that 80 chars in length
310         
311     return lfun
312
313 def write_fields(file, lfun):
314     """Writes the LFUN contained in the dict lfun to the file. Does not write a the file header or footer"""
315     # add lfun to LFUNs.lyx
316     file.write("\\begin_layout Subsection*\n")
317     file.write(lfun["name"] + "\n")
318     file.write("\\end_layout\n")
319     #file.write("\n")
320     if lfun["action"] != "":
321         file.write("\\begin_layout Description\n")
322         file.write("Action " + lfun["action"] + "\n")
323         #file.write("\n")
324         file.write("\\end_layout\n")
325         #file.write("\n")
326     if lfun["notion"] != "":
327         file.write("\\begin_layout Description\n")
328         file.write("Notion " + lfun["notion"] + "\n")
329         file.write("\\end_layout\n")
330         #file.write("\n")
331     if lfun["syntax"] != "":
332         file.write("\\begin_layout Description\n")
333         file.write("Syntax " + lfun["syntax"] + "\n")
334         file.write("\\end_layout\n")
335         #file.write("\n")
336     if lfun["params"] != "":
337         file.write("\\begin_layout Description\n")
338         file.write("Params " + lfun["params"] + "\n")
339         file.write("\\end_layout\n")
340         #file.write("\n")
341     if lfun["sample"] != "":
342         file.write("\\begin_layout Description\n")
343         file.write("Sample " + lfun["sample"] + "\n")
344         file.write("\\end_layout\n")
345         #file.write("\n")
346     if lfun["origin"] != "":
347         file.write("\\begin_layout Description\n")
348         file.write("Origin " + lfun["origin"] + "\n")
349         file.write("\\end_layout\n")
350         #file.write("\n")
351     file.write("\n")
352
353 def write_sections(file,lfuns):
354     """Write sections of LFUNs"""
355     sections = ["Layout", "Edit", "Math", "Buffer", "System", "Hidden"]
356     section_headings = {
357         "Layout":  "Layout Functions (Font, Layout and Textclass related)",
358         "Edit":  "Editing Functions (Cursor and Mouse Movement, Copy/Paste etc.)",
359         "Math":  "Math Editor Functions",
360         "Buffer":  "Buffer Fuctions (File and Window related)",
361         "System":  "System Funtions (Preferences, LyX Server etc.)",
362         "Hidden":  "Hidden Functions (not listed for configuration)"
363         }
364         # write the lfuns to the file
365     for val in sections:
366         file.write("\\begin_layout Section\n")
367         file.write(section_headings[val] + "\n")
368         file.write("\\end_layout\n")
369         for lf in lfuns:
370             if lf["type"] == val:
371                 write_fields(file, lf)
372     
373 def main(argv):
374     # parse command line arguments   
375     script_path, script_name = os.path.split(argv[0])
376     if len(argv) < 2:
377         error(usage(script_name))
378     # input file
379     lyxaction_path = argv[1]
380     if not os.path.exists(lyxaction_path):
381         error(script_name + ": %s is not a valid path" % lyxaction_path, usage(argv[0]))
382
383     # output file
384     if len(argv) == 3:
385         lfuns_path = argv[2]
386         if os.path.isdir(lfuns_path):
387             lfuns_path = lfuns_path + "LFUNs.lyx"
388         elif os.path.exists(lfuns_path):
389             error(script_name + ": %s already exists, delete it and rerun the script" % lfuns_path)
390         lfuns_file = open(lfuns_path, 'wb')
391     else:
392         lfuns_file = sys.stdout
393
394     sys.stderr.write(script_name + ": Start processing " + argv[1] + '\n')
395     # Read the input file and write the output file
396     lyxaction_file = open(lyxaction_path, 'rb')
397
398     lyxaction_text = lyxaction_file.read()
399
400     lfuns_file.write(LFUNS_HEADER)
401     
402     # An introductory section
403     lfuns_file.write(LFUNS_INTRO)
404
405     # seek to the important bit of LyXAction.cpp
406     try:
407         start = lyxaction_text.index("ev_item const items[] = {")
408     except ValueError:
409         lyxaction_file.close()
410         lfuns_file.close()
411         error(script_name + ": LFUNs not found in " + lyxaction_file)
412
413     done = count = 0
414
415     lfun_list_unsorted = []
416
417     while done == 0:
418         # look for a doxygen comment
419         start = lyxaction_text.find(DOXYGEN_START, start)
420         end = lyxaction_text.find(DOXYGEN_END, start) + len(DOXYGEN_END)
421         name = ""
422         atype = ""
423         snippet = lyxaction_text[start:end]
424         defline = snippet.replace("\n", "")
425         match = re.match(r'.*\s*\{\s*(.+),\s*"(.*)",\s*([\w\|\s]+),\s*(\w+)\s*\},.*$', defline)
426         if match:
427             name = match.group(2)
428             atype = match.group(4)
429         # parse the lfun if it is found
430         if start > 0:
431             if name:
432                 count = count + 1
433                 lfun = parse_lfun(snippet)
434                 lfun["name"] = name
435                 lfun["type"] = atype
436                 # save the lfun (we sort it before writing)
437                 lfun_list_unsorted.append(lfun)
438             # get the next one
439             start = end
440         else:
441             # if no more lfuns are found, EOF reached
442             done = 1
443
444     lfun_list = sorted(lfun_list_unsorted, key=lambda k: k['name'])
445     
446     # write the lfuns to the file
447     write_sections(lfuns_file, lfun_list)
448
449     sys.stderr.write(script_name + ": Created documentation for " + str(count) + " LFUNs\n")
450
451     # write the last part of LFUNs.lyx
452     lfuns_file.write(LFUNS_FOOTER)
453     
454     lyxaction_file.close()
455     lfuns_file.close()
456     
457     sys.stderr.write(script_name + ": Finished\n")
458     
459 if __name__ == "__main__":
460     main(sys.argv)