]> git.lyx.org Git - features.git/blob - lib/scripts/layout2layout.py
Add NewlineCmd InsetLayout and Argument option
[features.git] / lib / scripts / layout2layout.py
1 # -*- coding: utf-8 -*-
2
3 # file layout2layout.py
4 # This file is part of LyX, the document processor.
5 # Licence details can be found in the file COPYING.
6
7 # author Georg Baum
8
9 # Full author contact details are available in file CREDITS
10
11 # This script will update a .layout file to current format
12
13 # The latest layout format is also defined in src/TextClass.cpp
14 currentFormat = 74
15
16
17 # Incremented to format 4, 6 April 2007, lasgouttes
18 # Introduction of generic "Provides" declaration
19
20 # Incremented to format 5, 22 August 2007 by vermeer
21 # InsetLayout material
22
23 # Incremented to format 6, 7 January 2008 by spitz
24 # Requires tag added to layout files
25
26 # Incremented to format 7, 24 March 2008 by rgh
27 # AddToPreamble tag added to layout files
28
29 # Incremented to format 8, 25 July 2008 by rgh
30 # UseModule tag added to layout files
31 # CopyStyle added to InsetLayout
32
33 # Incremented to format 9, 5 October 2008 by rgh
34 # ForcePlain and CustomPars tags added to InsetLayout
35
36 # Incremented to format 10, 6 October 2008 by rgh
37 # Change format of counters
38
39 # Incremented to format 11, 14 October 2008 by rgh
40 # Add ProvidesModule, ExcludesModule tags
41
42 # Incremented to format 12, 10 January 2009 by gb
43 # Add I18NPreamble tag
44
45 # Incremented to format 13, 5 February 2009 by rgh
46 # Add InToc tag for InsetLayout
47
48 # Incremented to format 14, 14 February 2009 by gb
49 # Rename I18NPreamble to BabelPreamble and add LangPreamble
50
51 # Incremented to format 15, 28 May 2009 by lasgouttes
52 # Add new tag OutputFormat; modules can be conditioned on feature
53 # "from->to".
54
55 # Incremented to format 16, 5 June 2009 by rgh
56 # Add new tags for Text Class:
57 #   HTMLPreamble, HTMLAddToPreamble
58 # For Layout:
59 #   HTMLTag, HTMLAttr, HTMLLabel, HTMLLabelAttr, HTMLItem, HTMLItemAttr
60 #   HTMLStyle, and HTMLPreamble
61 # For InsetLayout:
62 #   HTMLTag, HTMLAttr, HTMLStyle, and HTMLPreamble
63 # For Floats:
64 #   HTMLType, HTMLClass, HTMLStyle
65
66 # Incremented to format 17, 12 August 2009 by rgh
67 # Add IfStyle and IfCounter tags for layout.
68
69 # Incremented to format 18, 27 October 2009 by rgh
70 # Added some new tags for HTML output.
71
72 # Incremented to format 19, 17 November 2009 by rgh
73 # Added InPreamble tag.
74
75 # Incremented to format 20, 17 December 2009 by rgh
76 # Added ContentAsLabel tag.
77
78 # Incremented to format 21, 12 January 2010 by rgh
79 # Added HTMLTocLayout and HTMLTitle tags.
80
81 # Incremented to format 22, 20 January 2010 by rgh
82 # Added HTMLFormat tag to Counters.
83
84 # Incremented to format 23, 13 February 2010 by spitz
85 # Added Spellcheck tag.
86
87 # Incremented to format 24, 5 March 2010 by rgh
88 # Changed LaTeXBuiltin tag to NeedsFloatPkg and
89 # added new tag ListCommand.
90
91 # Incremented to format 25, 12 March 2010 by rgh
92 # Added RefPrefix tag for layouts and floats.
93
94 # Incremented to format 26, 29 March 2010 by rgh
95 # Added CiteFormat.
96
97 # Incremented to format 27, 4 June 2010 by rgh
98 # Added RequiredArgs tag.
99
100 # Incremented to format 28, 6 August 2010 by lasgouttes
101 # Added ParbreakIsNewline tag for Layout and InsetLayout.
102
103 # Incremented to format 29, 10 August 2010 by rgh
104 # Changed Custom:Style, CharStyle:Style, and Element:Style
105 # uniformly to Flex:Style.
106
107 # Incremented to format 30, 13 August 2010 by rgh
108 # Introduced ResetsFont tag for InsetLayout.
109
110 # Incremented to format 31, 12 January 2011 by rgh
111 # Introducted NoCounter tag.
112
113 # Incremented to format 32, 30 January 2011 by forenr
114 # Added Display tag for InsetLayout
115
116 # Incremented to format 33, 2 February 2011 by rgh
117 # Changed NeedsFloatPkg to UsesFloatPkg
118
119 # Incremented to format 34, 28 March 2011 by rgh
120 # Remove obsolete Fill_(Top|Bottom) tags
121
122 # Incremented to format 35, 28 March 2011 by rgh
123 # Try to add "Flex:" to any flex insets that don't have it.
124
125 # Incremented to format 36, 7 December 2011, by rgh
126 # Added HTMLStyles and AddToHTMLStyles tags.
127
128 # Incremented to format 37, 29 February 2012 by jrioux
129 # Implement the citation engine machinery in layouts.
130 # Change CiteFormat to CiteFormat (default|authoryear|numerical).
131
132 # Incremented to format 38, 08 April 2012 by gb
133 # Introduce LangPreamble and BabelPreamble for InsetLayout.
134
135 # Incremented to format 39, 15 April 2012 by sanda
136 # Introduce styling of branches via "InsetLayout Branch:".
137
138 # Incremented to format 40, 10 October 2012 by rgh
139 # Re-do layout names for layout categories
140
141 # Incremented to format 41, 20 November 2012 by spitz
142 # New Argument syntax
143
144 # Incremented to format 42, 22 December 2012 by spitz
145 # New Style tag "ItemCommand"
146
147 # Incremented to format 43, 30 December 2012 by spitz
148 # Extended InsetCaption format
149
150 # Incremented to format 44, 9 February 2013 by rgh
151 # Remove COUNTER label style; rename as STATIC
152 # Rename TOP_ENVIRONMENT to ABOVE and CENTERED_TOP_ENVIRONMENT to CENTERED
153
154 # Incremented to format 45, 12 February 2013 by rgh
155 # New Tag "NoInsetLayout"
156
157 # Incremented to format 46, 15 May 2013 by gb
158 # New Tag "ForceLocal"
159
160 # Incremented to format 47, 23 May 2013 by rgh
161 # Add PackageOptions tag
162
163 # Incremented to format 48, 31 May 2013 by rgh
164 # Add InitialValue tag for counters
165
166 # Incremented to format 49, 10 Feb 2014 by gb
167 # Change default of "ResetsFont" tag to false
168
169 # Incremented to format 50, 9 May 2014 by forenr
170 # Removal of "Separator" layouts
171
172 # Incremented to format 51, 29 May 2014 by spitz
173 # New Style tag "ToggleIndent"
174
175 # Incremented to format 52, 1 December 2014 by spitz
176 # New InsetLayout tag "ForceOwnlines"
177
178 # Incremented to format 53, 7 December 2014 by spitz
179 # New InsetLayout tag "ObsoletedBy"
180
181 # Incremented to format 54, 11 Jan 2014 by gb
182 # New InsetLayout tag "FixedWidthPreambleEncoding"
183
184 # Incremented to format 55, 20 April 2015 by spitz
185 # New InsetLayout and Layout tags "PassThruChars"
186
187 # Incremented to format 56, 20 May 2015 by spitz
188 # New Float tags "AllowedPlacement", "AllowsWide", "AllowsSideways"
189
190 # Incremented to format 57, 30 May 2015 by spitz
191 # New Layout tag "ParagraphGroup"
192
193 # Incremented to format 58, 5 December 2015, by rgh
194 # New Layout tag "ProvideStyle"
195 # Change "IfStyle" to "ModifyStyle"
196
197 # Incremented to format 59, 22 November 2015 by gm
198 # New Tag "OutlinerName"
199 # New Layout tags "AddToToc", "IsTocCaption"
200 # New Layout argument tag "IsTocCaption"
201
202 # Incremented to format 60, 25 March 2016 by lasgouttes
203 # Rename caption subtype LongTableNoNumber to Unnumbered
204
205 # Incremented to format 61, 14 October 2016 by spitz
206 # New Layout tags "ResumeCounter", "StepMasterCounter"
207
208 # Incremented to format 62, 21 October 2016 by spitz
209 # New Layout argument tag "PassThru"
210
211 # Incremented to format 63, 7 January 2017 by spitz
212 # - New textclass tags CiteFramework, MaxCiteNames (for cite engines)
213 # - Extended InsetCite syntax.
214
215 # Incremented to format 64, 30 August 2017 by rgh
216 # Strip leading and trailing spaces from LabelString,
217 # LabelStringAppendix, and EndLabelString, and LabelCounter,
218 # to conform to what we used to do.
219
220 # Incremented to format 65, 16 October 2017 by spitz
221 # Color collapsable -> collapsible
222
223 # Incremented to format 66, 28 December 2017 by spitz
224 # New Layout tags "AutoNests ... EndAutoNests" and
225 # "IsAutoNestedBy ... EndIsAutoNestedBy"
226
227 # Incremented to format 67, 14 April 2018 by spitz
228 # New Layout tag "NeedsCProtect"
229
230 # Incremented to format 68, 21 May 2018 by spitz
231 # New Layout tag "AddToCiteEngine"
232
233 # Incremented to format 69, 16 August 2018 by spitz
234 # New argument type "listpreamble"
235
236 # Incremented to format 70, 5 June 2018 by rkh
237 # New InsetLayout tag EditExternal
238
239 # Incremented to format 71, 12 March 2019 by spitz
240 # New [Inset]Layout tag NeedMBoxProtect
241
242 # Incremented to format 72, 26 March 2019 by spitz
243 # New TextClass tag TableStyle
244
245 # Incremented to format 73, 18 April 2019 by spitz
246 # New InsetLayout tag MenuString
247
248 # Incremented to format 74, 18 April 2019 by spitz
249 # New InsetLayout and Argument tag NewlineCmd
250
251 # Do not forget to document format change in Customization
252 # Manual (section "Declaring a new text class").
253
254 # You might also want to consider running the
255 # development/tools/updatelayouts.py script to update all
256 # layout files to the new format.
257
258
259 import os, re, sys
260 import argparse
261
262 # Provide support for both python 2 and 3
263 # (copied from lyx2lyx)
264 PY2 = sys.version_info[0] == 2
265 if PY2:
266     # argparse returns strings in the commandline encoding, we need to convert.
267     # sys.getdefaultencoding() would not always be correct, see
268     # http://legacy.python.org/dev/peps/pep-0383/
269     def cmd_arg(arg):
270         return arg.decode(sys.getfilesystemencoding())
271 else:
272     cmd_arg = str
273 # End of code to support for both python 2 and 3
274
275
276 def error(message):
277     sys.stderr.write(message + '\n')
278     sys.exit(1)
279
280
281 def trim_bom(line):
282     " Remove byte order mark."
283     if line[0:3] == "\357\273\277":
284         return line[3:]
285     else:
286         return line
287
288
289 def read(source):
290     " Read input file and strip lineendings."
291     lines = source.read().splitlines() or ['']
292     lines[0] = trim_bom(lines[0])
293     return lines
294
295
296 def write(output, lines):
297     " Write output file with native lineendings."
298     output.write(os.linesep.encode('ascii').join(lines)
299                  + os.linesep.encode('ascii'))
300
301
302 # Concatenates old and new in an intelligent way:
303 # If old is wrapped in ", they are stripped. The result is wrapped in ".
304 def concatenate_label(old, new):
305     # Don't use strip as long as we support python 1.5.2
306     if old[0] == b'"':
307         return old[0:-1] + new + b'"'
308     else:
309         return b'"' + old + new + b'"'
310
311 # appends a string to a list unless it's already there
312 def addstring(s, l):
313     if l.count(s) > 0:
314         return
315     l.append(s)
316
317
318 def convert(lines, end_format):
319     " Convert to new format."
320     re_Comment = re.compile(b'^(\\s*)#')
321     re_Counter = re.compile(b'\\s*Counter\\s*', re.IGNORECASE)
322     re_Name = re.compile(b'\\s*Name\\s+(\\S+)\\s*', re.IGNORECASE)
323     re_UseMod = re.compile(b'^\\s*UseModule\\s+(.*)', re.IGNORECASE)
324     re_Empty = re.compile(b'^(\\s*)$')
325     re_Format = re.compile(b'^(\\s*)(Format)(\\s+)(\\S+)', re.IGNORECASE)
326     re_Preamble = re.compile(b'^(\\s*)Preamble', re.IGNORECASE)
327     re_EndPreamble = re.compile(b'^(\\s*)EndPreamble', re.IGNORECASE)
328     re_LangPreamble = re.compile(b'^(\\s*)LangPreamble', re.IGNORECASE)
329     re_EndLangPreamble = re.compile(b'^(\\s*)EndLangPreamble', re.IGNORECASE)
330     re_BabelPreamble = re.compile(b'^(\\s*)BabelPreamble', re.IGNORECASE)
331     re_EndBabelPreamble = re.compile(b'^(\\s*)EndBabelPreamble', re.IGNORECASE)
332     re_MaxCounter = re.compile(b'^(\\s*)(MaxCounter)(\\s+)(\\S+)', re.IGNORECASE)
333     re_LabelType = re.compile(b'^(\\s*)(LabelType)(\\s+)(\\S+)', re.IGNORECASE)
334     re_LabelString = re.compile(b'^(\\s*)(LabelString)(\\s+)(("[^"]+")|(\\S+))', re.IGNORECASE)
335     re_LabelStringAppendix = re.compile(b'^(\\s*)(LabelStringAppendix)(\\s+)(("[^"]+")|(\\S+))', re.IGNORECASE)
336     re_LatexType = re.compile(b'^(\\s*)(LatexType)(\\s+)(\\S+)', re.IGNORECASE)
337     re_Style = re.compile(b'^(\\s*)(Style)(\\s+)(\\S+)', re.IGNORECASE)
338     re_IfStyle = re.compile(b'^(\\s*)IfStyle(\\s+\\S+)', re.IGNORECASE)
339     re_CopyStyle = re.compile(b'^(\\s*)(CopyStyle)(\\s+)(\\S+)', re.IGNORECASE)
340     re_NoStyle = re.compile(b'^(\\s*)(NoStyle)(\\s+)(\\S+)', re.IGNORECASE)
341     re_End = re.compile(b'^(\\s*)(End)(\\s*)$', re.IGNORECASE)
342     re_Provides = re.compile(b'^(\\s*)Provides(\\S+)(\\s+)(\\S+)', re.IGNORECASE)
343     re_CharStyle = re.compile(b'^(\\s*)CharStyle(\\s+)(\\S+)$', re.IGNORECASE)
344     re_CiteFormat = re.compile(b'^(\\s*)(CiteFormat)(?:(\\s*)()|(\\s+)(default|authoryear|numerical))', re.IGNORECASE)
345     re_AMSMaths = re.compile(b'^\\s*Input ams(?:math|def)s.inc\\s*')
346     re_AMSMathsPlain = re.compile(b'^\\s*Input amsmaths-plain.inc\\s*')
347     re_AMSMathsSeq = re.compile(b'^\\s*Input amsmaths-seq.inc\\s*')
348     re_TocLevel = re.compile(b'^(\\s*)(TocLevel)(\\s+)(\\S+)', re.IGNORECASE)
349     re_I18nPreamble = re.compile(b'^(\\s*)I18nPreamble', re.IGNORECASE)
350     re_EndI18nPreamble = re.compile(b'^(\\s*)EndI18nPreamble', re.IGNORECASE)
351     re_Float = re.compile(b'^\\s*Float\\s*$', re.IGNORECASE)
352     re_Type = re.compile(b'\\s*Type\\s+(\\w+)', re.IGNORECASE)
353     re_Builtin = re.compile(b'^(\\s*)LaTeXBuiltin\\s+(\\w*)', re.IGNORECASE)
354     re_True = re.compile(b'^\\s*(?:true|1)\\s*$', re.IGNORECASE)
355     re_InsetLayout = re.compile(b'^\\s*InsetLayout\\s+(?:Custom|CharStyle|Element):(\\S+)\\s*$', re.IGNORECASE)
356     re_ResetsFont = re.compile(b'^(\\s*)ResetsFont(\\s+)(\\S+)$', re.IGNORECASE)
357     # with quotes
358     re_QInsetLayout = re.compile(b'^\\s*InsetLayout\\s+"(?:Custom|CharStyle|Element):([^"]+)"\\s*$', re.IGNORECASE)
359     re_InsetLayout_CopyStyle = re.compile(b'^\\s*CopyStyle\\s+(?:Custom|CharStyle|Element):(\\S+)\\s*$', re.IGNORECASE)
360     re_QInsetLayout_CopyStyle = re.compile(b'^\\s*CopyStyle\\s+"(?:Custom|CharStyle|Element):([^"]+)"\\s*$', re.IGNORECASE)
361     re_NeedsFloatPkg = re.compile(b'^(\\s*)NeedsFloatPkg\\s+(\\w+)\\s*$', re.IGNORECASE)
362     re_Fill = re.compile(b'^\\s*Fill_(?:Top|Bottom).*$', re.IGNORECASE)
363     re_InsetLayout2 = re.compile(b'^\\s*InsetLayout\\s+(\\S+)\\s*$', re.IGNORECASE)
364     # with quotes
365     re_QInsetLayout2 = re.compile(b'^\\s*InsetLayout\\s+"([^"]+)"\\s*$', re.IGNORECASE)
366     re_IsFlex = re.compile(b'\\s*LyXType.*$', re.IGNORECASE)
367     re_CopyStyle2 = re.compile(b'(\\s*CopyStyle\\s+)"?([^"]+)"?\\s*$')
368     re_Separator = re.compile(b'^(?:(-*)|(\\s*))(Separator|EndOfSlide)(?:(-*)|(\\s*))$', re.IGNORECASE)
369     # for categories
370     re_Declaration = re.compile(b'^#\\s*\\Declare\\w+Class.*$')
371     re_ExtractCategory = re.compile(b'^(#\\s*\\Declare\\w+Class(?:\\[[^]]*?\\])?){([^(]+?)\\s+\\(([^)]+?)\\)\\s*}\\s*$')
372     ConvDict = {"article": "Articles", "book" : "Books", "letter" : "Letters", "report": "Reports", \
373                 "presentation" : "Presentations", "curriculum vitae" : "Curricula Vitae", "handout" : "Handouts"}
374     # Arguments
375     re_OptArgs = re.compile(b'^(\\s*)OptionalArgs(\\s+)(\\d+)\\D*$', re.IGNORECASE)
376     re_ReqArgs = re.compile(b'^(\\s*)RequiredArgs(\\s+)(\\d+)\\D*$', re.IGNORECASE)
377
378     # various changes associated with changing how chapters are handled
379     re_LabelTypeIsCounter = re.compile(b'^(\\s*)LabelType(\\s*)Counter\\s*$', re.IGNORECASE)
380     re_TopEnvironment = re.compile(b'^(\\s*)LabelType(\\s+)Top_Environment\\s*$', re.IGNORECASE)
381     re_CenteredEnvironment = re.compile(b'^(\\s*)LabelType(\\s+)Centered_Top_Environment\\s*$', re.IGNORECASE)
382     re_ChapterStyle = re.compile(b'^\\s*Style\\s+Chapter\\s*$', re.IGNORECASE)
383     re_InsetLayout_CaptionLTNN = re.compile(b'^(\\s*InsetLayout\\s+)(Caption:LongTableNonumber)', re.IGNORECASE)
384     # for format 64
385     re_trimLabelString = re.compile(b'^(\\s*LabelString\s+)"\\s*(.*?)\\s*"\\s*$')
386     re_trimLabelStringAppendix  = re.compile(b'^(\\s*LabelStringAppendix\s+)"\\s*(.*?)\\s*"\\s*$')
387     re_trimEndLabelString = re.compile(b'^(\\s*EndLabelString\s+)"\\s*(.*?)\\s*"\\s*$')
388     re_trimLabelCounter = re.compile(b'^(\\s*LabelCounter\s+)"\\s*(.*?)\\s*"\\s*$')
389
390
391     # counters for sectioning styles (hardcoded in 1.3)
392     counters = {b"part"          : b"\\Roman{part}",
393                 b"chapter"       : b"\\arabic{chapter}",
394                 b"section"       : b"\\arabic{section}",
395                 b"subsection"    : b"\\arabic{section}.\\arabic{subsection}",
396                 b"subsubsection" : b"\\arabic{section}.\\arabic{subsection}.\\arabic{subsubsection}",
397                 b"paragraph"     : b"\\arabic{section}.\\arabic{subsection}.\\arabic{subsubsection}.\\arabic{paragraph}",
398                 b"subparagraph"  : b"\\arabic{section}.\\arabic{subsection}.\\arabic{subsubsection}.\\arabic{paragraph}.\\arabic{subparagraph}"}
399
400     # counters for sectioning styles in appendix (hardcoded in 1.3)
401     appendixcounters = {b"chapter"       : b"\\Alph{chapter}",
402                         b"section"       : b"\\Alph{section}",
403                         b"subsection"    : b"\\arabic{section}.\\arabic{subsection}",
404                         b"subsubsection" : b"\\arabic{section}.\\arabic{subsection}.\\arabic{subsubsection}",
405                         b"paragraph"     : b"\\arabic{section}.\\arabic{subsection}.\\arabic{subsubsection}.\\arabic{paragraph}",
406                         b"subparagraph"  : b"\\arabic{section}.\\arabic{subsection}.\\arabic{subsubsection}.\\arabic{paragraph}.\\arabic{subparagraph}"}
407
408     # Value of TocLevel for sectioning styles
409     toclevels = {b"part"          : -1,
410                  b"chapter"       : 0,
411                  b"section"       : 1,
412                  b"subsection"    : 2,
413                  b"subsubsection" : 3,
414                  b"paragraph"     : 4,
415                  b"subparagraph"  : 5}
416
417     i = 0
418     only_comment = 1
419     counter = b""
420     toclevel = b""
421     label = b""
422     labelstring = b""
423     labelstringappendix = b""
424     space1 = b""
425     labelstring_line = -1
426     labelstringappendix_line = -1
427     labeltype_line = -1
428     latextype = b""
429     latextype_line = -1
430     style = b""
431     maxcounter = 0
432     format = 1
433     formatline = 0
434     usemodules = []
435     flexstyles = []
436     opts = 0
437     reqs = 0
438     inchapter = False
439     isflexlayout = False         # only used for 48 -> 49
440     # Whether a style is inherited (works only for CopyStyle currently,
441     # not for true inherited styles, see bug 8920
442     inherited = False        # only used for 48 -> 49
443     resetsfont_found = False # only used for 48 -> 49
444
445     while i < len(lines):
446         # Skip comments and empty lines
447         if (re_Comment.match(lines[i]) or re_Empty.match(lines[i])):
448           # We need to deal with this conversion here, because it happens
449           # inside the initial comment block.
450           if only_comment and format == 39:
451               match = re_ExtractCategory.match(lines[i])
452               if match:
453                   lpre = match.group(1)
454                   lcat = match.group(2)
455                   lnam = match.group(3)
456                   if lcat in ConvDict:
457                       lcat = ConvDict[lcat]
458                   lines[i] = lpre + b"{" + lnam + b"}"
459                   lines.insert(i+1, b"#  \\DeclareCategory{" + lcat + b"}")
460                   i += 1
461           i += 1
462           continue
463
464         # insert file format if not already there
465         if (only_comment):
466             match = re_Format.match(lines[i])
467             if match:
468                 formatline = i
469                 format = int(match.group(4))
470                 if format > 1 and format < end_format:
471                     lines[i] = b"Format %d" % (format + 1)
472                     only_comment = 0
473                 elif format == end_format:
474                     # nothing to do
475                     return format
476                 else:
477                     error('Cannot convert file format %s to %s' % (format, end_format))
478             else:
479                 lines.insert(i, b"Format 2")
480                 only_comment = 0
481                 continue
482
483         # Don't get confused by LaTeX code
484         if re_Preamble.match(lines[i]):
485             i += 1
486             while i < len(lines) and not re_EndPreamble.match(lines[i]):
487                 i += 1
488             continue
489         if re_LangPreamble.match(lines[i]):
490             i += 1
491             while i < len(lines) and not re_EndLangPreamble.match(lines[i]):
492                 i += 1
493             continue
494         if re_BabelPreamble.match(lines[i]):
495             i += 1
496             while i < len(lines) and not re_EndBabelPreamble.match(lines[i]):
497                 i += 1
498             continue
499
500         if format >= 65 and format <= 73:
501             # nothing to do.
502             i += 1
503             continue
504
505         if format == 64:
506             match = re.compile(b'(\\s*Color\\s+)(\\w+)', re.IGNORECASE).match(lines[i])
507             if not match:
508                 i += 1
509                 continue
510             col  = match.group(2)
511             if col == "collapsable":
512                 lines[i] = match.group(1) + "collapsible"
513             i += 1
514             continue
515
516         if format == 63:
517             for r in (re_trimLabelString, re_trimLabelStringAppendix,\
518               re_trimEndLabelString, re_trimLabelCounter):
519                 m = r.match(lines[i])
520                 if m:
521                     lines[i] = m.group(1) + b'"' + m.group(2) + b'"'
522             i += 1
523             continue
524
525         if format >= 60 and format <= 62:
526             # nothing to do.
527             i += 1
528             continue
529
530         if format == 59:
531             match = re_InsetLayout_CaptionLTNN.match(lines[i])
532             if not match:
533                 i += 1
534                 continue
535             # '^(\s*InsetLayout\s+)(Caption:LongTableNonumber)'
536             lead  = match.group(1)
537             lines[i] = lead + b"Caption:Unnumbered"
538             i += 1
539             continue
540
541         if format == 58:
542             # nothing to do.
543             i += 1
544             continue
545
546         if format == 57:
547             match = re_IfStyle.match(lines[i])
548             if not match:
549                 i += 1
550                 continue
551             # b'^(\\s*)IfStyle(\\s+\\S+)
552             lead  = match.group(1)
553             trail = match.group(2)
554             lines[i] = lead + b"ModifyStyle" + trail
555             i += 1
556             continue
557
558         if format >= 50 and format <= 56:
559             # nothing to do.
560             i += 1
561             continue
562
563         if format == 49:
564             separator = []
565
566             # delete separator styles
567             match = re_Style.match(lines[i])
568             if match:
569                 style = match.group(4).lower()
570                 if re_Separator.match(style):
571                     del lines[i]
572                     while i < len(lines) and not re_End.match(lines[i]):
573                         separator.append(lines[i])
574                         del lines[i]
575                     if i == len(lines):
576                         error('Incomplete separator style.')
577                     else:
578                         del lines[i]
579                         continue
580
581             # delete undefinition of separator styles
582             match = re_NoStyle.match(lines[i])
583             if match:
584                 style = match.group(4).lower()
585                 if re_Separator.match(style):
586                     del lines[i]
587                     continue
588
589             # replace the CopyStyle statement with the definition of the real
590             # style. This may result in duplicate statements, but that is OK
591             # since the second one will overwrite the first one.
592             match = re_CopyStyle.match(lines[i])
593             if match:
594                 style = match.group(4).lower()
595                 if re_Separator.match(style):
596                     if len(separator) > 0:
597                         lines[i:i+1] = separator
598                     else:
599                         # FIXME: If this style was redefined in an include file,
600                         # we should replace the real style and not this default.
601                         lines[i:i+1] = [b'      Category              MainText',
602                                         b'      KeepEmpty             1',
603                                         b'      Margin                Dynamic',
604                                         b'      LatexType             Paragraph',
605                                         b'      LatexName             dummy',
606                                         b'      ParIndent             MM',
607                                         b'      Align                 Block',
608                                         b'      LabelType             Static',
609                                         b'      LabelString           "--- Separate Environment ---"',
610                                         b'      LabelFont',
611                                         b'        Family              Roman',
612                                         b'        Series              Medium',
613                                         b'        Size                Normal',
614                                         b'        Color               Blue',
615                                         b'      EndFont',
616                                         b'      HTMLLabel             NONE']
617             i += 1
618             continue
619
620         if format == 48:
621             # The default of ResetsFont in LyX changed from true to false,
622             # because it is now used for all InsetLayouts, not only flex ones.
623             # Therefore we need to set it to true for all flex insets which do
624             # do not already have a ResetsFont.
625             match = re_InsetLayout2.match(lines[i])
626             if not match:
627                 i += 1
628                 continue
629
630             name = match.group(1).lower()
631             if name != b"flex" and name != b"\"flex\"" and name[0:5] != b"flex:" and name [0:6] != b"\"flex:":
632                 i += 1
633                 continue
634
635             resetsfont_found = False
636             inherited = False
637             notdone = True
638             while i < len(lines):
639               match = re_ResetsFont.match(lines[i])
640               if match:
641                   resetsfont_found = True
642               else:
643                 match = re_CopyStyle.match(lines[i])
644                 if match:
645                   inherited = True
646                 else:
647                   match = re_End.match(lines[i])
648                   if match:
649                     break
650               i += 1
651             if not resetsfont_found and not inherited:
652               lines.insert(i, b"\tResetsFont true")
653
654             continue
655
656         if format >= 44 and format <= 47:
657             # nothing to do.
658             i += 1
659             continue
660
661         if format == 43:
662           match = re_LabelTypeIsCounter.match(lines[i])
663           if match:
664             if inchapter:
665              lines[i] = match.group(1) + b"LabelType" + match.group(2) + b"Above"
666             else:
667               lines[i] = match.group(1) + b"LabelType" + match.group(2) + b"Static"
668
669           match = re_TopEnvironment.match(lines[i])
670           if match:
671             lines[i] = match.group(1) + b"LabelType" + match.group(2) + b"Above"
672
673           match = re_CenteredEnvironment.match(lines[i])
674           if match:
675             lines[i] = match.group(1) + b"LabelType" + match.group(2) + b"Centered"
676
677           if inchapter:
678             match = re_Style.match(lines[i])
679             if match:
680               inchapter = False
681           else:
682             match = re_ChapterStyle.match(lines[i])
683             if match:
684               inchapter = True
685
686           i += 1
687           continue
688
689         if format == 42:
690           if lines[i] == b"InsetLayout Caption":
691             lines[i] = b"InsetLayout Caption:Standard"
692           i += 1
693           continue
694
695         if format == 41:
696             # nothing to do.
697             i += 1
698             continue
699
700         if format == 40:
701             # reset counters on Style beginning
702             match = re_Style.match(lines[i])
703             if match:
704                 opts = 0
705                 reqs = 0
706                 i += 1
707                 continue
708             match = re_OptArgs.match(lines[i])
709             if match:
710                 # Save number of optional arguments
711                 space1 = match.group(1)
712                 opts = int(match.group(3))
713                 # OptionalArgs 0 > ResetArgs 1
714                 if opts == 0:
715                     lines[i] = space1 + b"ResetArgs\t1"
716                     i += 1
717                 else:
718                     del lines[i]
719                 continue
720             match = re_ReqArgs.match(lines[i])
721             if match:
722                 # Save number of required arguments
723                 space1 = match.group(1)
724                 reqs = int(match.group(3))
725                 del lines[i]
726                 continue
727             # Insert the required number of arguments at the end of the style definition
728             match = re_End.match(lines[i])
729             if match:
730                 newarg = ['']
731                 # First the optionals (this is the required order pre 2.1)
732                 if opts > 0:
733                     if opts == 1:
734                         newarg = [ b'%sArgument 1' % (space1),
735                                    b'%s\tLabelString\t\"Optional Layout Argument\"' % (space1),
736                                    b'%sEndArgument' % (space1)]
737                     elif opts > 1:
738                         actopt = 1
739                         while actopt < (opts + 1):
740                             newarg += [ b'%sArgument %d' % (space1, actopt),
741                                b'%s\tLabelString\t\"Optional Layout Argument %d\"' % (space1, actopt),
742                                b'%sEndArgument' % (space1)]
743                             actopt += 1
744                 # Now the mandatories
745                 if reqs > 0:
746                     actopt = opts + 1
747                     while actopt < (opts +  reqs + 1):
748                         newarg += [ b'%sArgument %d' % (space1, actopt),
749                            b'%s\tLabelString\t"Required Layout Argument %d"' % (space1, actopt - opts),
750                            b'%s\tMandatory\t1' % (space1),
751                            b'%sEndArgument' % (space1)]
752                         actopt += 1
753                 # Since we replace the "End" line, re-add this line
754                 if len(newarg) > 1:
755                     newarg += [b'End']
756                     lines[i:i+1] = newarg
757                     i += len(newarg)
758                 # Reset the counters
759                 opts = 0
760                 reqs = 0
761             i += 1
762             continue
763
764         if format == 39:
765             # There is a conversion with format 40, but it is done within the
766             # initial comment block and so is above.
767             i += 1
768             continue
769
770         if format == 37 or format == 38:
771             i += 1
772             continue
773
774         if format == 36:
775             match = re_CiteFormat.match(lines[i]);
776             if match and match.group(4) == b"":
777                 lines[i] = match.group(0) + b" default"
778             i += 1
779             continue
780
781         if format == 35:
782           i += 1
783           continue
784
785         if format == 34:
786           match = re_QInsetLayout2.match(lines[i])
787           if not match:
788             match = re_InsetLayout2.match(lines[i])
789           if not match:
790             match = re_CopyStyle2.match(lines[i])
791             if not match:
792               i += 1
793               continue
794             style = match.group(2)
795
796             if flexstyles.count(style):
797               lines[i] = match.group(1) + b"\"Flex:" + style + b"\""
798             i += 1
799             continue
800
801           name = match.group(1)
802           names = name.split(b":", 1)
803           if len(names) > 1 and names[0] == b"Flex":
804             i += 1
805             continue
806
807           isflex = False
808           for j in range(i + 1, len(lines)):
809             if re_IsFlex.match(lines[j]):
810               isflex = True
811               break
812             if re_End.match(lines[j]):
813               break
814
815           if isflex:
816             flexstyles.append(name)
817             lines[i] = b"InsetLayout \"Flex:" + name + b"\""
818
819           i += 1
820           continue
821
822         if format == 33:
823           m = re_Fill.match(lines[i])
824           if m:
825             lines[i] = b""
826           i += 1
827           continue
828
829         if format == 32:
830           match = re_NeedsFloatPkg.match(lines[i])
831           if match:
832             space = match.group(1)
833             val = match.group(2)
834             lines[i] = space + b"UsesFloatPkg " + val
835             newval = b'true'
836             if val == b'1' or val.lower() == b'true':
837               newval = b'false'
838             lines.insert(i, space + b"IsPredefined " + newval)
839             i += 1
840           i += 1
841           continue
842
843         # Only new features
844         if format >= 29 and format <= 31:
845           i += 1
846           continue
847
848         if format == 28:
849           match = re_InsetLayout.match(lines[i])
850           if match:
851             lines[i] = b"InsetLayout Flex:" + match.group(1)
852           else:
853             match = re_QInsetLayout.match(lines[i])
854             if match:
855               lines[i] = b"InsetLayout \"Flex:" + match.group(1) + b"\""
856             else:
857               match = re_InsetLayout_CopyStyle.match(lines[i])
858               if match:
859                 lines[i] = b"\tCopyStyle Flex:" + match.group(1)
860               else:
861                 match = re_QInsetLayout_CopyStyle.match(lines[i])
862                 if match:
863                   lines[i] = b"\tCopyStyle \"Flex:" + match.group(1) + b"\""
864           i += 1
865           continue
866
867         # Only new features
868         if format >= 24 and format <= 27:
869           i += 1
870           continue
871
872         if format == 23:
873           match = re_Float.match(lines[i])
874           i += 1
875           if not match:
876             continue
877           # we need to do two things:
878           # (i)  Convert Builtin to NeedsFloatPkg
879           # (ii) Write ListCommand lines for the builtin floats table and figure
880           builtin = False
881           cmd = b""
882           while True and i < len(lines):
883             m1 = re_End.match(lines[i])
884             if m1:
885               if builtin and cmd:
886                 line = b"    ListCommand " + cmd
887                 lines.insert(i, line)
888                 i += 1
889               break
890             m2 = re_Builtin.match(lines[i])
891             if m2:
892               builtin = True
893               ws1 = m2.group(1)
894               arg = m2.group(2)
895               newarg = b""
896               if re_True.match(arg):
897                 newarg = b"false"
898               else:
899                 newarg = b"true"
900               lines[i] = ws1 + b"NeedsFloatPkg " + newarg
901             m3 = re_Type.match(lines[i])
902             if m3:
903               fltype = m3.group(1)
904               fltype = fltype.lower()
905               if fltype == b"table":
906                 cmd = b"listoftables"
907               elif fltype == b"figure":
908                 cmd = b"listoffigures"
909               # else unknown, which is why we're doing this
910             i += 1
911           continue
912
913         # This just involved new features, not any changes to old ones
914         if format >= 14 and format <= 22:
915           i += 1
916           continue
917
918         # Rename I18NPreamble to BabelPreamble
919         if format == 13:
920             match = re_I18nPreamble.match(lines[i])
921             if match:
922                 lines[i] = match.group(1) + b"BabelPreamble"
923                 i += 1
924                 match = re_EndI18nPreamble.match(lines[i])
925                 while i < len(lines) and not match:
926                     i += 1
927                     match = re_EndI18nPreamble.match(lines[i])
928                 lines[i] = match.group(1) + b"EndBabelPreamble"
929                 i += 1
930                 continue
931
932         # These just involved new features, not any changes to old ones
933         if format == 11 or format == 12:
934           i += 1
935           continue
936
937         if format == 10:
938             match = re_UseMod.match(lines[i])
939             if match:
940                 module = match.group(1)
941                 lines[i] = b"DefaultModule " + module
942             i += 1
943             continue
944
945         if format == 9:
946             match = re_Counter.match(lines[i])
947             if match:
948                 counterline = i
949                 i += 1
950                 while i < len(lines):
951                     namem = re_Name.match(lines[i])
952                     if namem:
953                         name = namem.group(1)
954                         lines.pop(i)
955                         lines[counterline] = b"Counter %s" % name
956                         # we don't need to increment i
957                         continue
958                     endem = re_End.match(lines[i])
959                     if endem:
960                         i += 1
961                         break
962                     i += 1
963             i += 1
964             continue
965
966         if format == 8:
967             # We want to scan for ams-type includes and, if we find them,
968             # add corresponding UseModule tags to the layout.
969             match = re_AMSMaths.match(lines[i])
970             if match:
971                 addstring(b"theorems-ams", usemodules)
972                 addstring(b"theorems-ams-extended", usemodules)
973                 addstring(b"theorems-sec", usemodules)
974                 lines.pop(i)
975                 continue
976             match = re_AMSMathsPlain.match(lines[i])
977             if match:
978                 addstring(b"theorems-starred", usemodules)
979                 lines.pop(i)
980                 continue
981             match = re_AMSMathsSeq.match(lines[i])
982             if match:
983                 addstring(b"theorems-ams", usemodules)
984                 addstring(b"theorems-ams-extended", usemodules)
985                 lines.pop(i)
986                 continue
987             i += 1
988             continue
989
990         # These just involved new features, not any changes to old ones
991         if format >= 5 and format <= 7:
992           i += 1
993           continue
994
995         if format == 4:
996             # Handle conversion to long CharStyle names
997             match = re_CharStyle.match(lines[i])
998             if match:
999                 lines[i] = b"InsetLayout CharStyle:%s" % (match.group(3))
1000                 i += 1
1001                 lines.insert(i, b"\tLyXType charstyle")
1002                 i += 1
1003                 lines.insert(i, b"")
1004                 lines[i] = b"\tLabelString %s" % (match.group(3))
1005             i += 1
1006             continue
1007
1008         if format == 3:
1009             # convert 'providesamsmath x',  'providesmakeidx x',  'providesnatbib x',  'providesurl x' to
1010             #         'provides amsmath x', 'provides makeidx x', 'provides natbib x', 'provides url x'
1011             # x is either 0 or 1
1012             match = re_Provides.match(lines[i])
1013             if match:
1014                 lines[i] = b"%sProvides %s%s%s" % (match.group(1), match.group(2).lower(),
1015                                                   match.group(3), match.group(4))
1016             i += 1
1017             continue
1018
1019         if format == 2:
1020             caption = []
1021
1022             # delete caption styles
1023             match = re_Style.match(lines[i])
1024             if match:
1025                 style = match.group(4).lower()
1026                 if style == b"caption":
1027                     del lines[i]
1028                     while i < len(lines) and not re_End.match(lines[i]):
1029                         caption.append(lines[i])
1030                         del lines[i]
1031                     if i == len(lines):
1032                         error('Incomplete caption style.')
1033                     else:
1034                         del lines[i]
1035                         continue
1036
1037             # delete undefinition of caption styles
1038             match = re_NoStyle.match(lines[i])
1039             if match:
1040                 style = match.group(4).lower()
1041                 if style == b"caption":
1042                     del lines[i]
1043                     continue
1044
1045             # replace the CopyStyle statement with the definition of the real
1046             # style. This may result in duplicate statements, but that is OK
1047             # since the second one will overwrite the first one.
1048             match = re_CopyStyle.match(lines[i])
1049             if match:
1050                 style = match.group(4).lower()
1051                 if style == b"caption":
1052                     if len(caption) > 0:
1053                         lines[i:i+1] = caption
1054                     else:
1055                         # FIXME: This style comes from an include file, we
1056                         # should replace the real style and not this default.
1057                         lines[i:i+1] = [b'      Margin                First_Dynamic',
1058                                         b'      LatexType             Command',
1059                                         b'      LatexName             caption',
1060                                         b'      NeedProtect           1',
1061                                         b'      LabelSep              xx',
1062                                         b'      ParSkip               0.4',
1063                                         b'      TopSep                0.5',
1064                                         b'      Align                 Center',
1065                                         b'      AlignPossible         Center',
1066                                         b'      LabelType             Sensitive',
1067                                         b'      LabelString           "Senseless!"',
1068                                         b'      OptionalArgs          1',
1069                                         b'      LabelFont',
1070                                         b'        Series              Bold',
1071                                         b'      EndFont']
1072
1073             i += 1
1074             continue
1075
1076         # Delete MaxCounter and remember the value of it
1077         match = re_MaxCounter.match(lines[i])
1078         if match:
1079             level = match.group(4).lower()
1080             if level == b"counter_chapter":
1081                 maxcounter = 0
1082             elif level == b"counter_section":
1083                 maxcounter = 1
1084             elif level == b"counter_subsection":
1085                 maxcounter = 2
1086             elif level == b"counter_subsubsection":
1087                 maxcounter = 3
1088             elif level == b"counter_paragraph":
1089                 maxcounter = 4
1090             elif level == b"counter_subparagraph":
1091                 maxcounter = 5
1092             elif level == b"counter_enumi":
1093                 maxcounter = 6
1094             elif level == b"counter_enumii":
1095                 maxcounter = 7
1096             elif level == b"counter_enumiii":
1097                 maxcounter = 8
1098             del lines[i]
1099             continue
1100
1101         # Replace line
1102         #
1103         # LabelType Counter_EnumI
1104         #
1105         # with two lines
1106         #
1107         # LabelType Counter
1108         # LabelCounter EnumI
1109         #
1110         match = re_LabelType.match(lines[i])
1111         if match:
1112             label = match.group(4)
1113             # Remember indenting space for later reuse in added lines
1114             space1 = match.group(1)
1115             # Remember the line for adding the LabelCounter later.
1116             # We can't do it here because it could shift latextype_line etc.
1117             labeltype_line = i
1118             if label[:8].lower() == b"counter_":
1119                 counter = label[8:].lower()
1120                 lines[i] = re_LabelType.sub(b'\\1\\2\\3Counter', lines[i])
1121
1122         # Remember the LabelString line
1123         match = re_LabelString.match(lines[i])
1124         if match:
1125             labelstring = match.group(4)
1126             labelstring_line = i
1127
1128         # Remember the LabelStringAppendix line
1129         match = re_LabelStringAppendix.match(lines[i])
1130         if match:
1131             labelstringappendix = match.group(4)
1132             labelstringappendix_line = i
1133
1134         # Remember the LatexType line
1135         match = re_LatexType.match(lines[i])
1136         if match:
1137             latextype = match.group(4).lower()
1138             latextype_line = i
1139
1140         # Remember the TocLevel line
1141         match = re_TocLevel.match(lines[i])
1142         if match:
1143             toclevel = match.group(4).lower()
1144
1145         # Reset variables at the beginning of a style definition
1146         match = re_Style.match(lines[i])
1147         if match:
1148             style = match.group(4).lower()
1149             counter = b""
1150             toclevel = b""
1151             label = b""
1152             space1 = b""
1153             labelstring = b""
1154             labelstringappendix = b""
1155             labelstring_line = -1
1156             labelstringappendix_line = -1
1157             labeltype_line = -1
1158             latextype = b""
1159             latextype_line = -1
1160
1161         if re_End.match(lines[i]):
1162
1163             # Add a line "LatexType Bib_Environment" if LabelType is Bibliography
1164             # (or change the existing LatexType)
1165             if label.lower() == b"bibliography":
1166                 if (latextype_line < 0):
1167                     lines.insert(i, b"%sLatexType Bib_Environment" % space1)
1168                     i += 1
1169                 else:
1170                     lines[latextype_line] = re_LatexType.sub(b'\\1\\2\\3Bib_Environment', lines[latextype_line])
1171
1172             # Change "LabelType Static" to "LabelType Itemize" for itemize environments
1173             if latextype == b"item_environment" and label.lower() == b"static":
1174                 lines[labeltype_line] = re_LabelType.sub(b'\\1\\2\\3Itemize', lines[labeltype_line])
1175
1176             # Change "LabelType Counter_EnumI" to "LabelType Enumerate" for enumerate environments
1177             if latextype == b"item_environment" and label.lower() == b"counter_enumi":
1178                 lines[labeltype_line] = re_LabelType.sub(b'\\1\\2\\3Enumerate', lines[labeltype_line])
1179                 # Don't add the LabelCounter line later
1180                 counter = ""
1181
1182             # Replace
1183             #
1184             # LabelString "Chapter"
1185             #
1186             # with
1187             #
1188             # LabelString "Chapter \arabic{chapter}"
1189             #
1190             # if this style has a counter. Ditto for LabelStringAppendix.
1191             # This emulates the hardcoded article style numbering of 1.3
1192             #
1193             if counter != b"":
1194                 if style in counters:
1195                     if labelstring_line < 0:
1196                         lines.insert(i, b'%sLabelString "%s"' % (space1, counters[style]))
1197                         i += 1
1198                     else:
1199                         new_labelstring = concatenate_label(labelstring, counters[style])
1200                         lines[labelstring_line] = re_LabelString.sub(
1201                                 b'\\1\\2\\3%s' % new_labelstring.replace(b"\\", b"\\\\"),
1202                                 lines[labelstring_line])
1203                 if style in appendixcounters:
1204                     if labelstringappendix_line < 0:
1205                         lines.insert(i, b'%sLabelStringAppendix "%s"' % (space1, appendixcounters[style]))
1206                         i += 1
1207                     else:
1208                         new_labelstring = concatenate_label(labelstring, appendixcounters[style])
1209                         lines[labelstringappendix_line] = re_LabelStringAppendix.sub(
1210                                 b'\\1\\2\\3%s' % new_labelstring.replace(b"\\", b"\\\\"),
1211                                 lines[labelstringappendix_line])
1212
1213                 # Now we can safely add the LabelCounter line
1214                 lines.insert(labeltype_line + 1, b"%sLabelCounter %s" % (space1, counter))
1215                 i += 1
1216
1217             # Add the TocLevel setting for sectioning styles
1218             if toclevel == b"" and style in toclevels and maxcounter <= toclevels[style]:
1219                 lines.insert(i, b'%s\tTocLevel %d' % (space1, toclevels[style]))
1220                 i += 1
1221
1222         i += 1
1223
1224     if only_comment:
1225         lines.insert(i, b"Format 2")
1226     if usemodules:
1227         i = formatline + 1
1228         for mod in usemodules:
1229             lines.insert(i, b"UseModule " + mod)
1230             i += 1
1231
1232     return format + 1
1233
1234
1235 def main(argv):
1236     args = {}
1237     args["description"] = "Convert layout file <inputfile> to a newer format."
1238
1239     parser = argparse.ArgumentParser(**args)
1240
1241     parser.add_argument("-t", "--to", type=int, dest="format", default= currentFormat,
1242                         help=("destination layout format, default %i (latest)") % currentFormat)
1243     parser.add_argument("input_file", nargs='?', type=cmd_arg, default=None,
1244                         help="input file (default stdin)")
1245     parser.add_argument("output_file", nargs='?', type=cmd_arg, default=None,
1246                         help="output file (default stdout)")
1247
1248     options = parser.parse_args(argv[1:])
1249
1250     # Open files
1251     if options.input_file:
1252         source = open(options.input_file, 'rb')
1253     else:
1254         source = sys.stdin
1255
1256     if options.output_file:
1257         output = open(options.output_file, 'wb')
1258     else:
1259         output = sys.stdout
1260
1261     if options.format > currentFormat:
1262         error("Format %i does not exist" % options.format);
1263
1264     # Do the real work
1265     lines = read(source)
1266     format = 1
1267     while (format < options.format):
1268         format = convert(lines, options.format)
1269     write(output, lines)
1270
1271     # Close files
1272     if options.input_file:
1273         source.close()
1274     if options.output_file:
1275         output.close()
1276
1277     return 0
1278
1279
1280 if __name__ == "__main__":
1281     main(sys.argv)