]> git.lyx.org Git - lyx.git/blob - development/tools/gen_lfuns.py
Add stats updating script to development/tools/.
[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 345
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 \\language english
63 \\inputencoding auto
64 \\font_roman default
65 \\font_sans default
66 \\font_typewriter default
67 \\font_default_family default
68 \\font_sc false
69 \\font_osf false
70 \\font_sf_scale 100
71 \\font_tt_scale 100
72
73 \\graphics default
74 \\paperfontsize default
75 \\spacing single
76 \\use_hyperref false
77 \\papersize default
78 \\use_geometry true
79 \\use_amsmath 1
80 \\use_esint 1
81 \\cite_engine basic
82 \\use_bibtopic false
83 \\paperorientation portrait
84 \\leftmargin 2.5cm
85 \\topmargin 2cm
86 \\rightmargin 3cm
87 \\bottommargin 1cm
88 \\secnumdepth 3
89 \\tocdepth 3
90 \\paragraph_separation indent
91 \\defskip medskip
92 \\quotes_language english
93 \\papercolumns 1
94 \\papersides 1
95 \\paperpagestyle default
96 \\tracking_changes false
97 \\output_changes false
98 \\author "" 
99 \\author "" 
100 \\end_header
101
102 \\begin_body
103
104 \\begin_layout Section*""" + "\nLFUNs documentation automatically generated " + str(date.today()) + """
105 \\end_layout
106
107 \\begin_layout Standard
108 \\begin_inset ERT
109 status collapsed
110
111 \\begin_layout Plain Layout
112
113
114 \\backslash
115 thispagestyle{empty}
116 \\end_layout
117
118 \\end_inset
119
120
121 \\begin_inset VSpace 1cm
122 \\end_inset
123
124
125 \\end_layout
126 """
127 LFUNS_FOOTER = """\\end_body
128 \\end_document
129 """
130
131 def parse_lfun(str):
132     """Takes a comment block (str) and parses it for fields describing the LFUN. Returns a dict containing the fields."""
133     
134     lfun = dict(name="", action="", notion="", syntax="", params="", sample="", origin="")
135     field = ""
136     lines = str.splitlines()
137     # strip leading whitespace and * from the lines of the comment to get 
138     # rid of unimportant characters
139     for i in range(0, len(lines)):
140         lines[i] = lines[i].strip(" *")
141     
142     for i in range(0, len(lines) - 1):
143         # work out what field is being read if none of these is found, the line will be added
144         #     to the last field edited
145         # since the field identifier is not included skip it out if it's found, otherwise skip
146         #     nothing as an existing field is being added to
147         # if a field id is found, then its the first line of the field so set the pre_space to ""
148         #     so that the first line isn't prespaced
149         if lines[i].startswith(LFUN_NAME_ID):
150             field = "name"
151             pre_space = ""
152             skip = len(ID_DICT[field])
153         elif lines[i].startswith(LFUN_ACTION_ID):
154             field = "action"
155             pre_space = ""
156             skip = len(ID_DICT[field])
157         elif lines[i].startswith(LFUN_NOTION_ID):
158             field = "notion"
159             pre_space = ""
160             skip = len(ID_DICT[field])
161         elif lines[i].startswith(LFUN_SYNTAX_ID):
162             field = "syntax"
163             pre_space = ""
164             skip = len(ID_DICT[field])
165         elif lines[i].startswith(LFUN_PARAMS_ID):
166             field = "params"
167             pre_space = ""
168             skip = len(ID_DICT[field])
169         elif lines[i].startswith(LFUN_SAMPLE_ID):
170             field = "sample"
171             pre_space = ""
172             skip = len(ID_DICT[field])
173         elif lines[i].startswith(LFUN_ORIGIN_ID):
174             field = "origin"
175             pre_space = ""
176             skip = len(ID_DICT[field])
177         elif lines[i].startswith(LFUN_ENDVAR):
178             break
179         else:
180             skip = 0
181             # if a manual line break was found last line, don't prespace this line
182             if i > 1 and lines[i-1].endswith("\\n"):
183                 pre_space = ""
184             else:
185                 pre_space = " "
186         
187         # add the line to the field, processing it for \ characters and \n
188         # which, if occurring at the end of a line, must become a LYX_NEWLINE
189         line = lines[i][skip:]
190         
191         # deal with \htmlonly
192         # TODO: convert chars found in htmlonly to unicode
193         start = line.find(HTMLONLY_START)
194         if start > 0:
195             # if removing the htmlonly element leaves a double space, go back one to remove it
196             if line[start-1] == " ":
197                 start = start - 1
198             end = line.find(HTMLONLY_END)
199             if end > start:
200                 end = line.find(HTMLONLY_END) + len(HTMLONLY_END)
201                 line = line[:start] + line[end:]
202             #else:
203             # TODO: if HTMLONLY_END is not found, look on the next line
204             # TODO: in the current LyXAction.cpp there are no htmlonly fields which go over a line break
205         
206         # deal with \ but leave \n if at the end of the line
207         slash_idx = line.find("\\")
208         while slash_idx >= 0:
209             if slash_idx < len(line)-2 \
210             or slash_idx == len(line)-1:
211                 # true when the \ is not the last or second last char
212                 #      or when the slash is the last char of the line
213                 
214                 # slash must be interpreted literaly so swap it for a LYX_BACKSLASH
215                 line = line[:slash_idx] + LYX_BACKSLASH + line[slash_idx+1:]
216                 # skip the index ahead beyond the added text
217                 slash_idx = slash_idx + len(LYX_BACKSLASH)
218             elif line[slash_idx+1] != "n": # only evaluated if the line ends "\x" where 'x' != 'n'
219                 line = line[:slash_idx] + LYX_BACKSLASH + line[slash_idx+1:]
220                 # skip the index ahead beyond the added text
221                 slash_idx = slash_idx + len(LYX_BACKSLASH) 
222             # look for the next \
223             slash_idx = line.find("\\", slash_idx+1)
224             
225         # \n at the end of lines will not be processed by the above while loop
226         # so sort those out now
227         # sometime lines end " \n" so chop the space if its there
228         if line.endswith(" \\n"):
229             line = line[:len(line)-3] + LYX_NEWLINE
230         elif line.endswith("\\n"):
231             line = line[:len(line)-2] + LYX_NEWLINE
232         
233         # any references to other LFUNs need the # removing
234         # TODO: actually insert a cross-reference here
235         line = line.replace("#LFUN", "LFUN")
236         
237         # handle the few #lyx:: cases
238         line = line.replace("#lyx::", "lyx::")
239
240         # the first line might not have a field in it in which
241         # case the variable field won't have a value, so check
242         # to avoid an error
243         if field != "":
244             lfun[field] = lfun[field] + pre_space + line
245         
246         # TODO: sort out chopping lines of more that 80 chars in length
247         
248     return lfun
249
250 def write_fields(file, lfun):
251     """Writes the LFUN contained in the dict lfun to the file. Does not write a the file header or footer"""
252     # add lfun to LFUNs.lyx
253     file.write("\\begin_layout Subsection*\n")
254     file.write(lfun["name"] + "\n")
255     file.write("\\end_layout\n")
256     #file.write("\n")
257     if lfun["action"] != "":
258         file.write("\\begin_layout Description\n")
259         file.write("Action " + lfun["action"] + "\n")
260         #file.write("\n")
261         file.write("\\end_layout\n")
262         #file.write("\n")
263     if lfun["notion"] != "":
264         file.write("\\begin_layout Description\n")
265         file.write("Notion " + lfun["notion"] + "\n")
266         file.write("\\end_layout\n")
267         #file.write("\n")
268     if lfun["syntax"] != "":
269         file.write("\\begin_layout Description\n")
270         file.write("Syntax " + lfun["syntax"] + "\n")
271         file.write("\\end_layout\n")
272         #file.write("\n")
273     if lfun["params"] != "":
274         file.write("\\begin_layout Description\n")
275         file.write("Params " + lfun["params"] + "\n")
276         file.write("\\end_layout\n")
277         #file.write("\n")
278     if lfun["sample"] != "":
279         file.write("\\begin_layout Description\n")
280         file.write("Sample " + lfun["sample"] + "\n")
281         file.write("\\end_layout\n")
282         #file.write("\n")
283     if lfun["origin"] != "":
284         file.write("\\begin_layout Description\n")
285         file.write("Origin " + lfun["origin"] + "\n")
286         file.write("\\end_layout\n")
287         #file.write("\n")
288     file.write("\n")        
289     
290 def main(argv):
291     # parse command line arguments   
292     script_path, script_name = os.path.split(argv[0])
293     if len(argv) < 2:
294         error(usage(script_name))
295     # input file
296     lyxaction_path = argv[1]
297     if not os.path.exists(lyxaction_path):
298         error(script_name + ": %s is not a valid path" % lyxaction_path, usage(argv[0]))
299
300     # output file
301     if len(argv) == 3:
302         lfuns_path = argv[2]
303         if os.path.isdir(lfuns_path):
304             lfuns_path = lfuns_path + "LFUNs.lyx"
305         elif os.path.exists(lfuns_path):
306             error(script_name + ": %s already exists, delete it and rerun the script" % lfuns_path)
307         lfuns_file = open(lfuns_path, 'wb')
308     else:
309         lfuns_file = sys.stdout
310
311     sys.stderr.write(script_name + ": Start processing " + argv[1] + '\n')
312     # Read the input file and write the output file
313     lyxaction_file = open(lyxaction_path, 'rb')
314         
315     lyxaction_text = lyxaction_file.read()
316         
317     lfuns_file.write(LFUNS_HEADER)
318         
319         # seek to the important bit of LyXAction.cpp
320     try:
321         start = lyxaction_text.index("ev_item const items[] = {")
322     except ValueError:
323         lyxaction_file.close()
324         lfuns_file.close()
325         error(script_name + ": LFUNs not found in " + lyxaction_file)
326
327     done = count = 0
328
329     while done == 0:
330         # look for a doxygen comment
331         start = lyxaction_text.find(DOXYGEN_START, start)
332         end = lyxaction_text.find(DOXYGEN_END, start) + len(DOXYGEN_END)
333         # parse the lfun if it is found
334         if start > 0:
335             count = count + 1
336             lfun = parse_lfun(lyxaction_text[start:end])
337             # write the lfun to the file
338             write_fields(lfuns_file, lfun)
339             # done adding current lfun to LFUNs.lyx so get the next one
340             start = end
341         else:
342             # if no more lfuns are found, EOF reached
343             done = 1
344             
345     sys.stderr.write(script_name + ": Created documentation for " + str(count) + " LFUNs\n")
346     
347     # write the last part of LFUNs.lyx
348     lfuns_file.write(LFUNS_FOOTER)
349     
350     lyxaction_file.close()
351     lfuns_file.close()
352     
353     sys.stderr.write(script_name + ": Finished\n")
354     
355 if __name__ == "__main__":
356     main(sys.argv)