From a464915f581caa27af8041c6726c128f1f352d12 Mon Sep 17 00:00:00 2001 From: Thibaut Cuvelier Date: Mon, 27 Sep 2021 00:42:08 +0200 Subject: [PATCH] DocBook copy: large refactoring to improve readability. --- autotests/export/docbook/ff/lily-3ed27d76.png | Bin 0 -> 1798 bytes lib/scripts/docbook_copy.py | 157 +++++++++++------- 2 files changed, 98 insertions(+), 59 deletions(-) create mode 100644 autotests/export/docbook/ff/lily-3ed27d76.png diff --git a/autotests/export/docbook/ff/lily-3ed27d76.png b/autotests/export/docbook/ff/lily-3ed27d76.png new file mode 100644 index 0000000000000000000000000000000000000000..870394e344610bd493355466722624ac34791f71 GIT binary patch literal 1798 zcmV+h2l@DkP)AT%wi*TLLkLe2P8nmj>CYGA+doWNPzksPCqMZ8pnA~(xB!uRIdHr`Qm%; z?%q9n?)dl^W}GSi8#t3Yns%mJ_VzHQ9;CW;2 zI9WXKeV^mFAPDf^OeW*HZW4j#kfiaTTrQ&l2qBK+uq<0Hmq(%4Bv2H^vaGY#BC!oB zl?nh5LM+STIF9Fe(==3*dR0j{C`@^7>ZbVk!{U0q!rmrjESg5b)PEAw%havZm{wRP$x&+`BPukP*b0l>|h zH~TdnD~E@Np(Zi$JdfvjLde*Z8bGO3IyyRvWdoLFMNvHUl5N|LeZ|Lw5nFC0Lg0Cl}d$S7!+q128D!Tu?PU!Y&JZjsw!Ra)ih06mWS#h%d%;j z6!(gt-EQ|LT9rzrp8|p)#LA0K#rOSOE*BQ;C{|~#>uzsv0|5R70Nb{U#bT{i6Gf5Z zIBMO_5JgciG{%QM9!QdeWeHi9pFMj~we39?_CvN=w`EvkBr_+xfJ;F!1zP`S*vvc?E-8ce~ zu(}r-gXj7E{e8USuv6ji@X+(Tp?Z0q7wtFp_V#)kynp}R_x*l45JGCTTBFgpc=6)q z=H}C&CShs@7|$WuIs*d@uE~JrPJw+jg2r? z$D!Ac)$8^6-4U!84RnbI2M3=&fBydc`{LpvW<;%4t6$^Z$$k0q<$Ar|Pd(4`{{H>@ z`0?ZL3_hO-oqzoJL6a9{vsnNT1fgFV0P!}KGnouF?!DwV&ahey%!^J{b$@?9Lg3=! zVs~SPVftk_F?Bcr=0!B|0R0jsGpefMY!w6n0Ky-+y}doq5B4k6SeAu3>u5POW7*i) zi0Ja*!2=v948s%(1=n@Q5~$)EEu_06bd3%|h-sRYN(C*E$z-DCIF5s&P9*8sY&PaR zkYzc1M)`a`dJOiu$7l^H795kC2MojDsK-#FX&R0Wp66}b=6T+-EQVofwOVw9gO!z)wY9Z{ zg@uS)P!vCg?LuFxP*pXY#eyJ&v(Ge54AgiG0II4|$dALYj)j7$_C=B;g*mULX}YfC z7AE>4Y=ar}#I{GK$FWdAV*{3DB}u|nJ9>Y^Fnr%fSESJk@20Skaf^FNb>z2#qvDSQYxm+%n8=w41LRqT-fP6k*DwVEXyY}$m z!*C3K{rXjwisHwxU9eV>^xc+5qw(p}r)INRuh*|%za9iZI-S0M z|Neyw7vifvolv|iGi(=pw`DZnA9M%$VRMP8WdeB-Lcf3Dx~`%qvkm0rP{w52wnb6I zO*F@GFgKk+2jx643?uyRqf{z|=ld1bXGB21pBxgFpaH> Given arguments:') + print('>> LilyPond: ' + ('present' if self.has_lilypond else 'not found') + '.') + print('>> LilyPond callable as: ' + self.lilypond_command + '.') + print('>> LilyPond path: ' + self.lilypond_folder + '.') + print('>> Input file: ' + self.in_file + '.') + print('>> Output file: ' + self.out_file + '.') + print('>> Input folder: ' + self.in_folder + '.') + print('>> Output folder: ' + self.out_folder + '.') - # Guess the path for LilyPond. - lilypond_folder = os.path.split(lilypond_command)[0] if has_lilypond else '' - - # Help debugging. - print('>> Given arguments:') - print('>> LilyPond: ' + ('present' if has_lilypond else 'not found') + '.') - print('>> LilyPond callable as: ' + lilypond_command + '.') - print('>> LilyPond path: ' + lilypond_folder + '.') - print('>> Input file: ' + in_file + '.') - print('>> Input folder: ' + in_folder + '.') - print('>> Output file: ' + out_file + '.') - - # Apply LilyPond to the original file if available and needed. - if has_lilypond and need_lilypond(in_file): - in_lily_file = in_file.replace(".xml", ".lyxml") - print('>> The input file needs a LilyPond pass and LilyPond is available.') - print('>> Rewriting ' + in_file) - print('>> as ' + in_lily_file + '.') + def in_file_needs_lilypond(self): + # Really tailored to the kind of output lilypond.module makes (in lib/layouts). + with open(self.in_file, 'r') as f: + return "language='lilypond'" in f.read() + def preprocess_input_for_lilypond(self): # LilyPond requires that its input file has the .lyxml extension. Due to a bug in LilyPond, # use " instead of ' to encode XML attributes. # https://lists.gnu.org/archive/html/bug-lilypond/2021-09/msg00039.html # Typical transformation: # FROM: language='lilypond' role='fragment verbatim staffsize=16 ragged-right relative=2' # TO: language="lilypond" role="fragment verbatim staffsize=16 ragged-right relative=2" - with open(in_file, 'r', encoding='utf-8') as f, open(in_lily_file, 'w', encoding='utf-8') as f_lily: + with open(self.in_file, 'r', encoding='utf-8') as f, open(self.in_lily_file, 'w', encoding='utf-8') as f_lily: for line in f: if "language='lilypond'" in line: line = re.sub( @@ -79,50 +74,94 @@ def copy_docbook(args): line ) f_lily.write(line) - os.unlink(in_file) + os.unlink(self.in_file) + + def postprocess_output_for_lilypond(self): + # TODO. + pass + + def call_lilypond(self): + # LilyPond requires that its input file has the .lyxml extension (plus bugs in LilyPond). + print('>> Rewriting ' + self.in_file) + print('>> as ' + self.in_lily_file + '.') + self.preprocess_input_for_lilypond() # Add LilyPond to the PATH. lilypond-book uses a direct call to lilypond from the PATH. - if os.path.isdir(lilypond_folder): - os.environ['PATH'] += os.pathsep + lilypond_folder + if os.path.isdir(self.lilypond_folder): + os.environ['PATH'] += os.pathsep + self.lilypond_folder # Make LilyPond believe it is working from the temporary LyX directory. Otherwise, it tries to find files - # starting from LyX's working directory... - os.chdir(in_folder) + # starting from LyX's working directory... LilyPond bug. + os.chdir(self.in_folder) # Start LilyPond on the copied file. First test the binary, then check if adding Python helps. - command_args = ['--format=docbook', '--output=' + in_folder, in_lily_file] - command_raw = [lilypond_command] + command_args - command_python = ['python', lilypond_command] + command_args + command_args = ['--format=docbook', '--output=' + self.in_folder, self.in_lily_file] + command_raw = [self.lilypond_command] + command_args + command_python = ['python', self.lilypond_command] + command_args print('>> Running LilyPond.') sys.stdout.flush() # So that the LilyPond output is at the right place in the logs. - failed = False - try: - subprocess.check_call(command_raw, stdout=sys.stdout.fileno(), stderr=sys.stdout.fileno()) - print('>> Success running LilyPond with ') - print('>> ' + str(command_raw)) - except (subprocess.CalledProcessError, OSError) as e1: + failed = True + exceptions = [] + for cmd in [command_raw, command_python]: try: - subprocess.check_call(command_python, stdout=sys.stdout.fileno(), stderr=sys.stdout.fileno()) + subprocess.check_call(cmd, stdout=sys.stdout.fileno(), stderr=sys.stdout.fileno()) print('>> Success running LilyPond with ') - print('>> ' + str(command_python) + '.') - except (subprocess.CalledProcessError, OSError) as e2: - print('>> Error from LilyPond. The successive calls were:') - print('>> (1) Error from trying ' + str(command_raw) + ':') - print('>> (1) ' + str(e1)) - print('>> (2) Error from trying ' + str(command_python) + ':') - print('>> (2) ' + str(e2)) - failed = True + print('>> ' + str(cmd)) + failed = False + except (subprocess.CalledProcessError, OSError) as e: + exceptions.append((cmd, e)) + + if failed: + print('>> Error from LilyPond. The successive calls were:') + for (i, pair) in enumerate(exceptions): + exc = pair[0] + cmd = pair[1] + + print('>> (' + i + ') Error from trying ' + str(cmd) + ':') + print('>> (' + i + ') ' + str(exc)) if failed: sys.exit(1) # Now, in_file should have the LilyPond-processed contents. + # LilyPond has a distressing tendency to leave the raw LilyPond code in the new file. + self.postprocess_output_for_lilypond() - # Perform the final copy. - shutil.copyfile(in_file, out_file, follow_symlinks=False) + def copy_lilypond_generated_images(self): + # LilyPond generates a lot of files in LyX' temporary folder, within the ff folder: source LilyPond files + # for each snippet to render, images in several formats. + in_generated_images_folder = os.path.join(self.in_folder, 'ff') + out_generated_images_folder = os.path.join(self.out_folder, 'ff') + + os.mkdir(out_generated_images_folder) + + for img in os.listdir(in_generated_images_folder): + if not img.endswith('.png') and not img.endswith('.pdf'): + continue + + shutil.copyfile( + os.path.join(in_generated_images_folder, img), + os.path.join(out_generated_images_folder, img), + follow_symlinks=False, + ) + + def copy(self): + # Apply LilyPond to the original file if available and needed. + if self.do_lilypond_processing: + print('>> The input file needs a LilyPond pass and LilyPond is available.') + self.call_lilypond() + + # Perform the actual copy: both the modified XML file and the generated images, if LilyPond is used. + shutil.copyfile(self.in_file, self.out_file, follow_symlinks=False) + if self.do_lilypond_processing: + self.copy_lilypond_generated_images() if __name__ == '__main__': - copy_docbook(sys.argv) + if len(sys.argv) != 4: + print('Exactly four arguments are expected, only %s found: %s.' % (len(sys.argv), sys.argv)) + sys.exit(1) + + DocBookCopier(sys.argv).copy()