]> git.lyx.org Git - lyx.git/blob - lib/scripts/layout2layout.py
e93966a81478736e790d85f68628576774680101
[lyx.git] / lib / scripts / layout2layout.py
1 #! /usr/bin/env python
2 # -*- coding: utf-8 -*-
3
4 # file layout2layout.py
5 # This file is part of LyX, the document processor.
6 # Licence details can be found in the file COPYING.
7
8 # author Georg Baum
9
10 # Full author contact details are available in file CREDITS
11
12 # This script will update a .layout file to current format
13
14
15 import os, re, string, sys
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 # Do not forget to document format change in Customization
95 # Manual (section "Declaring a new text class").
96
97 # You might also want to consider running the
98 # development/tools/updatelayouts.sh script to update all
99 # layout files to the new format.
100
101 currentFormat = 25
102
103
104 def usage(prog_name):
105     return ("Usage: %s inputfile outputfile\n" % prog_name +
106             "or     %s <inputfile >outputfile" % prog_name)
107
108
109 def error(message):
110     sys.stderr.write(message + '\n')
111     sys.exit(1)
112
113
114 def trim_bom(line):
115     " Remove byte order mark."
116     if line[0:3] == "\357\273\277":
117         return line[3:]
118     else:
119         return line
120
121
122 def read(source):
123     " Read input file and strip lineendings."
124     lines = source.read().splitlines()
125     lines[0] = trim_bom(lines[0])
126     return lines
127
128
129 def write(output, lines):
130     " Write output file with native lineendings."
131     output.write(os.linesep.join(lines) + os.linesep)
132
133
134 # Concatenates old and new in an intelligent way:
135 # If old is wrapped in ", they are stripped. The result is wrapped in ".
136 def concatenate_label(old, new):
137     # Don't use strip as long as we support python 1.5.2
138     if old[0] == '"':
139         return old[0:-1] + new + '"'
140     else:
141         return '"' + old + new + '"'
142
143 # appends a string to a list unless it's already there
144 def addstring(s, l):
145     if l.count(s) > 0:
146         return
147     l.append(s)
148
149
150 def convert(lines):
151     " Convert to new format."
152     re_Comment = re.compile(r'^(\s*)#')
153     re_Counter = re.compile(r'\s*Counter\s*', re.IGNORECASE)
154     re_Name = re.compile(r'\s*Name\s+(\S+)\s*', re.IGNORECASE)
155     re_UseMod = re.compile(r'^\s*UseModule\s+(.*)', re.IGNORECASE)
156     re_Empty = re.compile(r'^(\s*)$')
157     re_Format = re.compile(r'^(\s*)(Format)(\s+)(\S+)', re.IGNORECASE)
158     re_Preamble = re.compile(r'^(\s*)Preamble', re.IGNORECASE)
159     re_EndPreamble = re.compile(r'^(\s*)EndPreamble', re.IGNORECASE)
160     re_LangPreamble = re.compile(r'^(\s*)LangPreamble', re.IGNORECASE)
161     re_EndLangPreamble = re.compile(r'^(\s*)EndLangPreamble', re.IGNORECASE)
162     re_BabelPreamble = re.compile(r'^(\s*)BabelPreamble', re.IGNORECASE)
163     re_EndBabelPreamble = re.compile(r'^(\s*)EndBabelPreamble', re.IGNORECASE)
164     re_MaxCounter = re.compile(r'^(\s*)(MaxCounter)(\s+)(\S+)', re.IGNORECASE)
165     re_LabelType = re.compile(r'^(\s*)(LabelType)(\s+)(\S+)', re.IGNORECASE)
166     re_LabelString = re.compile(r'^(\s*)(LabelString)(\s+)(("[^"]+")|(\S+))', re.IGNORECASE)
167     re_LabelStringAppendix = re.compile(r'^(\s*)(LabelStringAppendix)(\s+)(("[^"]+")|(\S+))', re.IGNORECASE)
168     re_LatexType = re.compile(r'^(\s*)(LatexType)(\s+)(\S+)', re.IGNORECASE)
169     re_Style = re.compile(r'^(\s*)(Style)(\s+)(\S+)', re.IGNORECASE)
170     re_CopyStyle = re.compile(r'^(\s*)(CopyStyle)(\s+)(\S+)', re.IGNORECASE)
171     re_NoStyle = re.compile(r'^(\s*)(NoStyle)(\s+)(\S+)', re.IGNORECASE)
172     re_End = re.compile(r'^(\s*)(End)(\s*)$', re.IGNORECASE)
173     re_Provides = re.compile(r'^(\s*)Provides(\S+)(\s+)(\S+)', re.IGNORECASE)
174     re_CharStyle = re.compile(r'^(\s*)CharStyle(\s+)(\S+)$', re.IGNORECASE)
175     re_AMSMaths = re.compile(r'^\s*Input ams(?:math|def)s.inc\s*')
176     re_AMSMathsPlain = re.compile(r'^\s*Input amsmaths-plain.inc\s*')
177     re_AMSMathsSeq = re.compile(r'^\s*Input amsmaths-seq.inc\s*')
178     re_TocLevel = re.compile(r'^(\s*)(TocLevel)(\s+)(\S+)', re.IGNORECASE)
179     re_I18nPreamble = re.compile(r'^(\s*)I18nPreamble', re.IGNORECASE)
180     re_EndI18nPreamble = re.compile(r'^(\s*)EndI18nPreamble', re.IGNORECASE)
181     re_Float = re.compile(r'^\s*Float\s*$', re.IGNORECASE)
182     re_Type = re.compile(r'\s*Type\s+(\w+)', re.IGNORECASE)
183     re_Builtin = re.compile(r'^(\s*)LaTeXBuiltin\s+(\w*)', re.IGNORECASE)
184     re_True = re.compile(r'^\s*(?:true|1)\s*$', re.IGNORECASE)
185
186     # counters for sectioning styles (hardcoded in 1.3)
187     counters = {"part"          : "\\Roman{part}",
188                 "chapter"       : "\\arabic{chapter}",
189                 "section"       : "\\arabic{section}",
190                 "subsection"    : "\\arabic{section}.\\arabic{subsection}",
191                 "subsubsection" : "\\arabic{section}.\\arabic{subsection}.\\arabic{subsubsection}",
192                 "paragraph"     : "\\arabic{section}.\\arabic{subsection}.\\arabic{subsubsection}.\\arabic{paragraph}",
193                 "subparagraph"  : "\\arabic{section}.\\arabic{subsection}.\\arabic{subsubsection}.\\arabic{paragraph}.\\arabic{subparagraph}"}
194
195     # counters for sectioning styles in appendix (hardcoded in 1.3)
196     appendixcounters = {"chapter"       : "\\Alph{chapter}",
197                         "section"       : "\\Alph{section}",
198                         "subsection"    : "\\arabic{section}.\\arabic{subsection}",
199                         "subsubsection" : "\\arabic{section}.\\arabic{subsection}.\\arabic{subsubsection}",
200                         "paragraph"     : "\\arabic{section}.\\arabic{subsection}.\\arabic{subsubsection}.\\arabic{paragraph}",
201                         "subparagraph"  : "\\arabic{section}.\\arabic{subsection}.\\arabic{subsubsection}.\\arabic{paragraph}.\\arabic{subparagraph}"}
202
203     # Value of TocLevel for sectioning styles
204     toclevels = {"part"          : 0,
205                  "chapter"       : 0,
206                  "section"       : 1,
207                  "subsection"    : 2,
208                  "subsubsection" : 3,
209                  "paragraph"     : 4,
210                  "subparagraph"  : 5}
211
212     i = 0
213     only_comment = 1
214     counter = ""
215     toclevel = ""
216     label = ""
217     labelstring = ""
218     labelstringappendix = ""
219     space1 = ""
220     labelstring_line = -1
221     labelstringappendix_line = -1
222     labeltype_line = -1
223     latextype = ""
224     latextype_line = -1
225     style = ""
226     maxcounter = 0
227     format = 1
228     formatline = 0
229     usemodules = []
230
231     while i < len(lines):
232         # Skip comments and empty lines
233         if re_Comment.match(lines[i]) or re_Empty.match(lines[i]):
234             i += 1
235             continue
236
237         # insert file format if not already there
238         if (only_comment):
239             match = re_Format.match(lines[i])
240             if match:
241                 formatline = i
242                 format = int(match.group(4))
243                 if format > 1 and format < currentFormat:
244                     lines[i] = "Format %d" % (format + 1)
245                     only_comment = 0
246                 elif format == currentFormat:
247                     # nothing to do
248                     return format
249                 else:
250                     error('Cannot convert file format %s' % format)
251             else:
252                 lines.insert(i, "Format 2")
253                 only_comment = 0
254                 continue
255
256         # Don't get confused by LaTeX code
257         if re_Preamble.match(lines[i]):
258             i += 1
259             while i < len(lines) and not re_EndPreamble.match(lines[i]):
260                 i += 1
261             continue
262         if re_LangPreamble.match(lines[i]):
263             i += 1
264             while i < len(lines) and not re_EndLangPreamble.match(lines[i]):
265                 i += 1
266             continue
267         if re_BabelPreamble.match(lines[i]):
268             i += 1
269             while i < len(lines) and not re_EndBabelPreamble.match(lines[i]):
270                 i += 1
271             continue
272         
273         # Only new features
274         if format == 24:
275             i += 1
276             continue
277
278         if format == 23:
279           match = re_Float.match(lines[i])
280           i += 1
281           if not match:
282             continue
283           # we need to do two things:
284           # (i)  Convert Builtin to NeedsFloatPkg
285           # (ii) Write ListCommand lines for the builtin floats table and figure
286           builtin = False
287           cmd = ""
288           while True and i < len(lines):
289             m1 = re_End.match(lines[i])
290             if m1:
291               if builtin and cmd:
292                 line = "    ListCommand " + cmd
293                 lines.insert(i, line)
294                 i += 1
295               break
296             m2 = re_Builtin.match(lines[i])
297             if m2:
298               builtin = True
299               ws1 = m2.group(1)
300               arg = m2.group(2)
301               newarg = ""
302               if re_True.match(arg):
303                 newarg = "false"
304               else:
305                 newarg = "true"
306               lines[i] = ws1 + "NeedsFloatPkg " + newarg
307             m3 = re_Type.match(lines[i])
308             if m3:
309               fltype = m3.group(1)
310               fltype = fltype.lower()
311               if fltype == "table":
312                 cmd = "listoftables"
313               elif fltype == "figure":
314                 cmd = "listoffigures"
315               # else unknown, which is why we're doing this
316             i += 1
317           continue              
318           
319         # This just involved new features, not any changes to old ones
320         if format >= 14 and format <= 22:
321           i += 1
322           continue
323
324         # Rename I18NPreamble to BabelPreamble
325         if format == 13:
326             match = re_I18nPreamble.match(lines[i])
327             if match:
328                 lines[i] = match.group(1) + "BabelPreamble"
329                 i += 1
330                 match = re_EndI18nPreamble.match(lines[i])
331                 while i < len(lines) and not match:
332                     i += 1
333                     match = re_EndI18nPreamble.match(lines[i])
334                 lines[i] = match.group(1) + "EndBabelPreamble"
335                 i += 1
336                 continue
337
338         # These just involved new features, not any changes to old ones
339         if format == 11 or format == 12:
340           i += 1
341           continue
342
343         if format == 10:
344             match = re_UseMod.match(lines[i])
345             if match:
346                 module = match.group(1)
347                 lines[i] = "DefaultModule " + module
348             i += 1
349             continue
350
351         if format == 9:
352             match = re_Counter.match(lines[i])
353             if match:
354                 counterline = i
355                 i += 1
356                 while i < len(lines):
357                     namem = re_Name.match(lines[i])
358                     if namem:
359                         name = namem.group(1)
360                         lines.pop(i)
361                         lines[counterline] = "Counter %s" % name
362                         # we don't need to increment i
363                         continue
364                     endem = re_End.match(lines[i])
365                     if endem:
366                         i += 1
367                         break
368                     i += 1
369             i += 1
370             continue
371
372         if format == 8:
373             # We want to scan for ams-type includes and, if we find them,
374             # add corresponding UseModule tags to the layout.
375             match = re_AMSMaths.match(lines[i])
376             if match:
377                 addstring("theorems-ams", usemodules)
378                 addstring("theorems-ams-extended", usemodules)
379                 addstring("theorems-sec", usemodules)
380                 lines.pop(i)
381                 continue
382             match = re_AMSMathsPlain.match(lines[i])
383             if match:
384                 addstring("theorems-starred", usemodules)
385                 lines.pop(i)
386                 continue
387             match = re_AMSMathsSeq.match(lines[i])
388             if match:
389                 addstring("theorems-ams", usemodules)
390                 addstring("theorems-ams-extended", usemodules)
391                 lines.pop(i)
392                 continue
393             i += 1
394             continue
395
396         # These just involved new features, not any changes to old ones
397         if format >= 5 and format <= 7:
398           i += 1
399           continue
400
401         if format == 4:
402             # Handle conversion to long CharStyle names
403             match = re_CharStyle.match(lines[i])
404             if match:
405                 lines[i] = "InsetLayout CharStyle:%s" % (match.group(3))
406                 i += 1
407                 lines.insert(i, "\tLyXType charstyle")
408                 i += 1
409                 lines.insert(i, "")
410                 lines[i] = "\tLabelString %s" % (match.group(3))
411             i += 1
412             continue
413
414         if format == 3:
415             # convert 'providesamsmath x',  'providesmakeidx x',  'providesnatbib x',  'providesurl x' to
416             #         'provides amsmath x', 'provides makeidx x', 'provides natbib x', 'provides url x'
417             # x is either 0 or 1
418             match = re_Provides.match(lines[i])
419             if match:
420                 lines[i] = "%sProvides %s%s%s" % (match.group(1), match.group(2).lower(),
421                                                   match.group(3), match.group(4))
422             i += 1
423             continue
424
425         if format == 2:
426             caption = []
427
428             # delete caption styles
429             match = re_Style.match(lines[i])
430             if match:
431                 style = string.lower(match.group(4))
432                 if style == "caption":
433                     del lines[i]
434                     while i < len(lines) and not re_End.match(lines[i]):
435                         caption.append(lines[i])
436                         del lines[i]
437                     if i == len(lines):
438                         error('Incomplete caption style.')
439                     else:
440                         del lines[i]
441                         continue
442
443             # delete undefinition of caption styles
444             match = re_NoStyle.match(lines[i])
445             if match:
446                 style = string.lower(match.group(4))
447                 if style == "caption":
448                     del lines[i]
449                     continue
450
451             # replace the CopyStyle statement with the definition of the real
452             # style. This may result in duplicate statements, but that is OK
453             # since the second one will overwrite the first one.
454             match = re_CopyStyle.match(lines[i])
455             if match:
456                 style = string.lower(match.group(4))
457                 if style == "caption":
458                     if len(caption) > 0:
459                         lines[i:i+1] = caption
460                     else:
461                         # FIXME: This style comes from an include file, we
462                         # should replace the real style and not this default.
463                         lines[i:i+1] = ['       Margin                First_Dynamic',
464                                         '       LatexType             Command',
465                                         '       LatexName             caption',
466                                         '       NeedProtect           1',
467                                         '       LabelSep              xx',
468                                         '       ParSkip               0.4',
469                                         '       TopSep                0.5',
470                                         '       Align                 Center',
471                                         '       AlignPossible         Center',
472                                         '       LabelType             Sensitive',
473                                         '       LabelString           "Senseless!"',
474                                         '       OptionalArgs          1',
475                                         '       LabelFont',
476                                         '         Series              Bold',
477                                         '       EndFont']
478
479             i += 1
480             continue
481
482         # Delete MaxCounter and remember the value of it
483         match = re_MaxCounter.match(lines[i])
484         if match:
485             level = match.group(4)
486             if string.lower(level) == "counter_chapter":
487                 maxcounter = 0
488             elif string.lower(level) == "counter_section":
489                 maxcounter = 1
490             elif string.lower(level) == "counter_subsection":
491                 maxcounter = 2
492             elif string.lower(level) == "counter_subsubsection":
493                 maxcounter = 3
494             elif string.lower(level) == "counter_paragraph":
495                 maxcounter = 4
496             elif string.lower(level) == "counter_subparagraph":
497                 maxcounter = 5
498             elif string.lower(level) == "counter_enumi":
499                 maxcounter = 6
500             elif string.lower(level) == "counter_enumii":
501                 maxcounter = 7
502             elif string.lower(level) == "counter_enumiii":
503                 maxcounter = 8
504             del lines[i]
505             continue
506
507         # Replace line
508         #
509         # LabelType Counter_EnumI
510         #
511         # with two lines
512         #
513         # LabelType Counter
514         # LabelCounter EnumI
515         #
516         match = re_LabelType.match(lines[i])
517         if match:
518             label = match.group(4)
519             # Remember indenting space for later reuse in added lines
520             space1 = match.group(1)
521             # Remember the line for adding the LabelCounter later.
522             # We can't do it here because it could shift latextype_line etc.
523             labeltype_line = i
524             if string.lower(label[:8]) == "counter_":
525                 counter = string.lower(label[8:])
526                 lines[i] = re_LabelType.sub(r'\1\2\3Counter', lines[i])
527
528         # Remember the LabelString line
529         match = re_LabelString.match(lines[i])
530         if match:
531             labelstring = match.group(4)
532             labelstring_line = i
533
534         # Remember the LabelStringAppendix line
535         match = re_LabelStringAppendix.match(lines[i])
536         if match:
537             labelstringappendix = match.group(4)
538             labelstringappendix_line = i
539
540         # Remember the LatexType line
541         match = re_LatexType.match(lines[i])
542         if match:
543             latextype = string.lower(match.group(4))
544             latextype_line = i
545
546         # Remember the TocLevel line
547         match = re_TocLevel.match(lines[i])
548         if match:
549             toclevel = string.lower(match.group(4))
550
551         # Reset variables at the beginning of a style definition
552         match = re_Style.match(lines[i])
553         if match:
554             style = string.lower(match.group(4))
555             counter = ""
556             toclevel = ""
557             label = ""
558             space1 = ""
559             labelstring = ""
560             labelstringappendix = ""
561             labelstring_line = -1
562             labelstringappendix_line = -1
563             labeltype_line = -1
564             latextype = ""
565             latextype_line = -1
566
567         if re_End.match(lines[i]):
568
569             # Add a line "LatexType Bib_Environment" if LabelType is Bibliography
570             # (or change the existing LatexType)
571             if string.lower(label) == "bibliography":
572                 if (latextype_line < 0):
573                     lines.insert(i, "%sLatexType Bib_Environment" % space1)
574                     i += 1
575                 else:
576                     lines[latextype_line] = re_LatexType.sub(r'\1\2\3Bib_Environment', lines[latextype_line])
577
578             # Change "LabelType Static" to "LabelType Itemize" for itemize environments
579             if latextype == "item_environment" and string.lower(label) == "static":
580                 lines[labeltype_line] = re_LabelType.sub(r'\1\2\3Itemize', lines[labeltype_line])
581
582             # Change "LabelType Counter_EnumI" to "LabelType Enumerate" for enumerate environments
583             if latextype == "item_environment" and string.lower(label) == "counter_enumi":
584                 lines[labeltype_line] = re_LabelType.sub(r'\1\2\3Enumerate', lines[labeltype_line])
585                 # Don't add the LabelCounter line later
586                 counter = ""
587
588             # Replace
589             #
590             # LabelString "Chapter"
591             #
592             # with
593             #
594             # LabelString "Chapter \arabic{chapter}"
595             #
596             # if this style has a counter. Ditto for LabelStringAppendix.
597             # This emulates the hardcoded article style numbering of 1.3
598             #
599             if counter != "":
600                 if counters.has_key(style):
601                     if labelstring_line < 0:
602                         lines.insert(i, '%sLabelString "%s"' % (space1, counters[style]))
603                         i += 1
604                     else:
605                         new_labelstring = concatenate_label(labelstring, counters[style])
606                         lines[labelstring_line] = re_LabelString.sub(
607                                 r'\1\2\3%s' % new_labelstring.replace("\\", "\\\\"),
608                                 lines[labelstring_line])
609                 if appendixcounters.has_key(style):
610                     if labelstringappendix_line < 0:
611                         lines.insert(i, '%sLabelStringAppendix "%s"' % (space1, appendixcounters[style]))
612                         i += 1
613                     else:
614                         new_labelstring = concatenate_label(labelstring, appendixcounters[style])
615                         lines[labelstringappendix_line] = re_LabelStringAppendix.sub(
616                                 r'\1\2\3%s' % new_labelstring.replace("\\", "\\\\"),
617                                 lines[labelstringappendix_line])
618
619                 # Now we can safely add the LabelCounter line
620                 lines.insert(labeltype_line + 1, "%sLabelCounter %s" % (space1, counter))
621                 i += 1
622
623             # Add the TocLevel setting for sectioning styles
624             if toclevel == "" and toclevels.has_key(style) and maxcounter <= toclevels[style]:
625                 lines.insert(i, '%s\tTocLevel %d' % (space1, toclevels[style]))
626                 i += 1
627
628         i += 1
629
630     if usemodules:
631         i = formatline + 1
632         for mod in usemodules:
633             lines.insert(i, "UseModule " + mod)
634             i += 1
635
636     return format + 1
637
638
639 def main(argv):
640
641     # Open files
642     if len(argv) == 1:
643         source = sys.stdin
644         output = sys.stdout
645     elif len(argv) == 3:
646         source = open(argv[1], 'rb')
647         output = open(argv[2], 'wb')
648     else:
649         error(usage(argv[0]))
650
651     # Do the real work
652     lines = read(source)
653     format = 1
654     while (format < currentFormat):
655         format = convert(lines)
656     write(output, lines)
657
658     # Close files
659     if len(argv) == 3:
660         source.close()
661         output.close()
662
663     return 0
664
665
666 if __name__ == "__main__":
667     main(sys.argv)