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