1 # -*- coding: utf-8 -*-
4 # This file is part of LyX, the document processor.
5 # Licence details can be found in the file COPYING.
7 # \author Thibaut Cuvelier
9 # Full author contact details are available in file CREDITS
12 # python docbook_copy.py lilypond_book_command in.docbook out.docbook
13 # This script copies the original DocBook file (directly produced by LyX) to the output DocBook file,
14 # potentially applying a post-processing step. For now, the only implemented post-processing step is
16 # lilypond_book_command is either directly the binary to call OR the equivalent Python script that is
17 # not directly executable.
18 # /!\ The original file may be modified by this script!
29 def need_lilypond(file):
30 # Really tailored to the kind of output lilypond.module makes (in lib/layouts).
31 with open(file, 'r') as f:
32 return "language='lilypond'" in f.read()
35 def copy_docbook(args):
37 print('Exactly four arguments are expected, only %s found: %s.' % (len(args), args))
40 # Parse the command line.
41 lilypond_command = args[1]
45 has_lilypond = lilypond_command not in {'', 'none'}
46 in_folder = os.path.split(in_file)[0]
48 # Guess the path for LilyPond.
49 lilypond_folder = os.path.split(lilypond_command)[0] if has_lilypond else ''
52 print('>> Given arguments:')
53 print('>> LilyPond: ' + ('present' if has_lilypond else 'not found') + '.')
54 print('>> LilyPond callable as: ' + lilypond_command + '.')
55 print('>> LilyPond path: ' + lilypond_folder + '.')
56 print('>> Input file: ' + in_file + '.')
57 print('>> Input folder: ' + in_folder + '.')
58 print('>> Output file: ' + out_file + '.')
60 # Apply LilyPond to the original file if available and needed.
61 if has_lilypond and need_lilypond(in_file):
62 in_lily_file = in_file.replace(".xml", ".lyxml")
63 print('>> The input file needs a LilyPond pass and LilyPond is available.')
64 print('>> Rewriting ' + in_file)
65 print('>> as ' + in_lily_file + '.')
67 # LilyPond requires that its input file has the .lyxml extension. Due to a bug in LilyPond,
68 # use " instead of ' to encode XML attributes.
69 # https://lists.gnu.org/archive/html/bug-lilypond/2021-09/msg00039.html
70 # Typical transformation:
71 # FROM: language='lilypond' role='fragment verbatim staffsize=16 ragged-right relative=2'
72 # TO: language="lilypond" role="fragment verbatim staffsize=16 ragged-right relative=2"
73 with open(in_file, 'r', encoding='utf-8') as f, open(in_lily_file, 'w', encoding='utf-8') as f_lily:
75 if "language='lilypond'" in line:
77 '<programlisting\\s+language=\'lilypond\'.*?(role=\'(?P<options>.*?)\')?>',
78 '<programlisting language="lilypond" role="\\g<options>">',
84 # Add LilyPond to the PATH. lilypond-book uses a direct call to lilypond from the PATH.
85 if os.path.isdir(lilypond_folder):
86 os.environ['PATH'] += os.pathsep + lilypond_folder
88 # Make LilyPond believe it is working from the temporary LyX directory. Otherwise, it tries to find files
89 # starting from LyX's working directory...
92 # Start LilyPond on the copied file. First test the binary, then check if adding Python helps.
93 command_args = ['--format=docbook', '--output=' + in_folder, in_lily_file]
94 command_raw = [lilypond_command] + command_args
95 command_python = ['python', lilypond_command] + command_args
97 print('>> Running LilyPond.')
98 sys.stdout.flush() # So that the LilyPond output is at the right place in the logs.
102 subprocess.check_call(command_raw, stdout=sys.stdout.fileno(), stderr=sys.stdout.fileno())
103 print('>> Success running LilyPond with ')
104 print('>> ' + str(command_raw))
105 except (subprocess.CalledProcessError, OSError) as e1:
107 subprocess.check_call(command_python, stdout=sys.stdout.fileno(), stderr=sys.stdout.fileno())
108 print('>> Success running LilyPond with ')
109 print('>> ' + str(command_python) + '.')
110 except (subprocess.CalledProcessError, OSError) as e2:
111 print('>> Error from LilyPond. The successive calls were:')
112 print('>> (1) Error from trying ' + str(command_raw) + ':')
113 print('>> (1) ' + str(e1))
114 print('>> (2) Error from trying ' + str(command_python) + ':')
115 print('>> (2) ' + str(e2))
121 # Now, in_file should have the LilyPond-processed contents.
123 # Perform the final copy.
124 shutil.copyfile(in_file, out_file, follow_symlinks=False)
127 if __name__ == '__main__':
128 copy_docbook(sys.argv)