From 2dd305e7e1dcd664747def1a7fb931fa957d50c1 Mon Sep 17 00:00:00 2001
From: Samuel Da Mota <da.mota.sam@gmail.com>
Date: Tue, 17 Jul 2012 22:35:08 +0200
Subject: [PATCH] Added: feature that let's attach the sources to pdf file.

RESTRICTIONS FOR NOW: this is a prototype. Must not be integrated as
is!!!

For now this is a shell script, thus not portable to windows target!
The integration with lilypond is really cheap.

Some code was added into lilypond so it writes on stdout the list of
files it loaded. The output must go into a file to be used be the
script. Thus lilypond must be called like this:

shell> lilypond "file.ly" > input_files

and then call the script giving it the input files and pdf one as
parameters. Thus calling the script like this:

shell> attach_sources_to_pdf.sh input_files pdf_files
---
 lily/sources.cc                  |   10 +-
 scripts/attach_sources_to_pdf.sh |  388 ++++++++++++++++++++++++++++++++++++++
 2 files changed, 395 insertions(+), 3 deletions(-)
 create mode 100755 scripts/attach_sources_to_pdf.sh

diff --git a/lily/sources.cc b/lily/sources.cc
index 2c0e665..bebfdb2 100644
--- a/lily/sources.cc
+++ b/lily/sources.cc
@@ -83,11 +83,15 @@ Sources::add (Source_file *sourcefile)
   sourcefiles_.push_back (sourcefile);
 }
 
+#include <iostream>
+
 Sources::~Sources ()
 {
+
   for (vsize i = 0; i < sourcefiles_.size (); i++)
-    {
-      sourcefiles_[i]->unprotect ();
-    }
+  {
+    std::cout << sourcefiles_[i]->name_ << std::endl;
+    sourcefiles_[i]->unprotect ();
+  }
 }
 
diff --git a/scripts/attach_sources_to_pdf.sh b/scripts/attach_sources_to_pdf.sh
new file mode 100755
index 0000000..2eec15c
--- /dev/null
+++ b/scripts/attach_sources_to_pdf.sh
@@ -0,0 +1,388 @@
+#! /usr/bin/env bash
+
+
+# $1 is is the list of input files used to build the pdf file
+# each filename is written on a single line
+
+# $2 is the pdf file into which sources should be added
+
+
+
+# The function below filters out the files that correspond to lilypond
+# internal files
+#
+# For exemple this function should say that the file "italiano.ly" is
+# not interesting, however the file named "Beethoven - Für Elise.ly" is.
+#
+# The separation interesting/not interesting is based on the full
+# file's path. There is a blacklist of lilypond installation
+# files. This function simply filters out these files from the input
+# files list.
+#
+function removes_lilypond_files()
+{
+# The blacklist should be automaticaly generated by make, or make
+# install.  Indeed, for developpers working on a lilypond which
+# install itself in
+# e.g. /home/<user_account>/lilypond/build/out/bin/lilypond, the list
+# of files below should start with
+# /home/<user_account>/lilypond/build/out/ instead os /usr.
+#
+# It may thus be interesting to replace /usr/ in the list below by a
+# replace pattern like @@@@__FILEPATH_BEGIN__@@@@ and then having a
+# sed command that automaticaly replaces it.
+   grep -v  -e '^/usr/share/lilypond/*/ly/deutsch.ly$'					\
+	    -e '^/usr/share/lilypond/*/ly/norsk.ly$'					\
+	    -e '^/usr/share/lilypond/*/ly/nederlands.ly$'				\
+	    -e '^/usr/share/lilypond/*/ly/suomi.ly$'					\
+	    -e '^/usr/share/lilypond/*/ly/svenska.ly$'					\
+	    -e '^/usr/share/lilypond/*/ly/festival.ly$'					\
+	    -e '^/usr/share/lilypond/*/ly/articulate.ly$'				\
+	    -e '^/usr/share/lilypond/*/ly/generate-interface-doc-init.ly$'		\
+	    -e '^/usr/share/lilypond/*/ly/predefined-ukulele-fretboards.ly$'		\
+	    -e '^/usr/share/lilypond/*/ly/vlaams.ly$'					\
+	    -e '^/usr/share/lilypond/*/ly/predefined-mandolin-fretboards.ly$'		\
+	    -e '^/usr/share/lilypond/*/ly/predefined-guitar-ninth-fretboards.ly$'	\
+	    -e '^/usr/share/lilypond/*/ly/scale-definitions-init.ly$'			\
+	    -e '^/usr/share/lilypond/*/ly/string-tunings-init.ly$'			\
+	    -e '^/usr/share/lilypond/*/ly/Welcome-to-LilyPond-MacOS.ly$'		\
+	    -e '^/usr/share/lilypond/*/ly/portugues.ly$'				\
+	    -e '^/usr/share/lilypond/*/ly/guile-debugger.ly$'				\
+	    -e '^/usr/share/lilypond/*/ly/lilypond-book-preamble.ly$'			\
+	    -e '^/usr/share/lilypond/*/ly/gregorian.ly$'				\
+	    -e '^/usr/share/lilypond/*/ly/engraver-init.ly$'				\
+	    -e '^/usr/share/lilypond/*/ly/espanol.ly$'					\
+	    -e '^/usr/share/lilypond/*/ly/init.ly$'					\
+	    -e '^/usr/share/lilypond/*/ly/grace-init.ly$'				\
+	    -e '^/usr/share/lilypond/*/ly/titling-init.ly$'				\
+	    -e '^/usr/share/lilypond/*/ly/dynamic-scripts-init.ly$'			\
+	    -e '^/usr/share/lilypond/*/ly/generate-documentation.ly$'			\
+	    -e '^/usr/share/lilypond/*/ly/toc-init.ly$'					\
+	    -e '^/usr/share/lilypond/*/ly/drumpitch-init.ly$'				\
+	    -e '^/usr/share/lilypond/*/ly/arabic.ly$'					\
+	    -e '^/usr/share/lilypond/*/ly/script-init.ly$'				\
+	    -e '^/usr/share/lilypond/*/ly/chord-modifiers-init.ly$'			\
+	    -e '^/usr/share/lilypond/*/ly/declarations-init.ly$'			\
+	    -e '^/usr/share/lilypond/*/ly/predefined-guitar-fretboards.ly$'		\
+	    -e '^/usr/share/lilypond/*/ly/midi-init.ly$'				\
+	    -e '^/usr/share/lilypond/*/ly/predefined-fretboards-init.ly$'		\
+	    -e '^/usr/share/lilypond/*/ly/english.ly$'					\
+	    -e '^/usr/share/lilypond/*/ly/catalan.ly$'					\
+	    -e '^/usr/share/lilypond/*/ly/makam.ly$'					\
+	    -e '^/usr/share/lilypond/*/ly/paper-defaults-init.ly$'			\
+	    -e '^/usr/share/lilypond/*/ly/italiano.ly$'					\
+	    -e '^/usr/share/lilypond/*/ly/spanners-init.ly$'				\
+	    -e '^/usr/share/lilypond/*/ly/bagpipe.ly$'					\
+	    -e '^/usr/share/lilypond/*/ly/chord-repetition-init.ly$'			\
+	    -e '^/usr/share/lilypond/*/ly/property-init.ly$'				\
+	    -e '^/usr/share/lilypond/*/ly/music-functions-init.ly$'			\
+	    -e '^/usr/share/lilypond/*/ly/graphviz-init.ly$'				\
+	    -e '^/usr/share/lilypond/*/ly/performer-init.ly$'				\
+	    -e '^/usr/share/lilypond/*/ly/Welcome_to_LilyPond.ly$'			\
+       "$1"
+}
+
+
+# Keeps only files that correspond to user supplied files.  It takes
+# one parameter: the filename of the file containing the list of
+# lilypond inputs file
+function get_interesing_files()
+{
+  removes_lilypond_files "$1" | while read line ; do
+    # keeps only existing files (removes '<included string>' from the
+    # list and other thing like this that could have been introduded
+    # by lilypond).
+    if test -e "$line" ; then
+	echo "$line"
+    fi
+
+  done
+}
+
+
+
+
+# This function is used to get full path for every files.  Indeed it
+# is easier to work with paths like
+#
+# /tmp/music/Bach/Praeludium_1.ly
+# /tmp/music/common.ly
+#
+# rather than
+# ./Praeludium_1.ly
+# ../../common.ly
+#
+# It takes one parameter: the filename of the file containing the list
+# of lilypond inputs file
+function canonize_path()
+{
+  get_interesing_files "$1" | while read line ; do
+    # Because of files' name or directories' name that may contain
+    # space characters, the use of the $line variable must be enclosed
+    # in quotes or double quotes
+
+    local filedir=$(cd $(dirname "$line")  && pwd )
+    local filename=$(basename "$line")
+
+    if test "$filedir" = "/" ; then
+      # if a file is used directly under / (though unlikely), it is
+      # prefered to have a filename of /file.ly, rather the //file.ly
+      # This is the reason of this dummy if.
+      echo "/${filename}"
+    else
+      echo "${filedir}/${filename}"
+    fi
+  done
+}
+
+
+
+INTERESTING_FILES=$(canonize_path "$1")
+NB_INTERESTING_FILES=$(echo "$INTERESTING_FILES" | wc -l)
+
+
+# This function is used to created the "smallest" tarball.  The goal
+# here is to avoid dummy directories in the tarball. E.G., in the case
+# of a .ly project composed of only two files, say "full_score.ly" and
+# "intro.ly", it is more usefull to create a tarball named
+# score_sources.tar.bz2 that looks like this:
+#
+# shell> tar tf score_sources.tar.bz2
+# score_sources/
+# score_sources/full_score.ly
+# score_sources/intro.ly
+#
+# Than having a tarball that looks like:
+#
+# shell> tar tf score_sources.tar.bz2
+# home/
+# home/<user_name>/
+# home/<user_name>/music
+# home/<user_name>/music/<music_author>/
+# home/<user_name>/music/<music_author>/<title_name>
+# home/<user_name>/music/<music_author>/<title_name>/full_score.ly
+# home/<user_name>/music/<music_author>/<title_name>/intro.ly
+#
+# Let's think about the detarring step and how annoying it is to have
+# a tarball looking like the second one.
+#
+# Thus the goal of this function is to look at each user supplied
+# source filename to extract the longest common prefix. For example if
+# there are three source files like this:
+#
+# /home/<user_name>/music/<music_author>/<title_name>/full_score.ly
+# /home/<user_name>/music/<music_author>/<title_name>/intro.ly
+# /home/<user_name>/music/common_template.ly
+#
+# the output of this function should be /home/<user_name>/music/
+function find_longest_prefix()
+{
+    local first_file=$(head -n 1 <<< "$INTERESTING_FILES")
+    local other_files=$(tail -n +2 <<< "$INTERESTING_FILES")
+    local longest_prefix=$(dirname "$first_file")
+
+
+    # while it exists one path that doesn't start by longest prefix,
+    # remove the last directory of longest prefix
+    while (test "$longest_prefix" != "/") &&
+          (grep -q -v "^$longest_prefix/" <<< "$other_files" )
+    do
+	longest_prefix=$(dirname "$longest_prefix")
+    done
+
+
+    # add a trailing / in path if necessary
+    if (test "$longest_prefix" != "/" ); then
+      longest_prefix="$longest_prefix/"
+    fi
+
+    echo "$longest_prefix"
+}
+
+
+
+
+# This function prepares the directories and files and for the *minimal*
+# tarball.
+#
+# For example, if the score is made of the following files:
+#
+# /home/<user_name>/music/<music_author>/<title_name>/full_score.ly
+# /home/<user_name>/music/<music_author>/<title_name>/intro.ly
+# /home/<user_name>/music/common_template.ly
+#
+# It first detects that all path starts by /home/<user_name>/music/
+# and thus that the only thing to keep is the rest of the path i.e.
+#
+# <music_author>/<title_name>/full_score.ly
+# <music_author>/<title_name>/intro.ly
+# common_template.ly
+#
+# Note that this function takes a folder name as parameter. This is
+# the folder in which the directories will be created.
+#
+# For example, if called with /tmp/dummy_dir as parameter, it will
+# create, in the case of this example the repertories
+# /tmp/dummy_dir/<music_author>/
+# /tmp/dummy_dir/<music_author>/<title_name>/
+#
+# then it will copy
+# <music_author>/<title_name>/full_score.ly to /tmp/dummy_dir/<music_author>/<title_name>/full_score.ly
+# <music_author>/<title_name>/intro.ly to /tmp/dummy_dir/<music_author>/<title_name>/intro.ly
+# common_template.ly /tmp/dummy_dir/common_template.ly
+function create_tarball_files()
+{
+  local dest_dir="${1}/"
+  local longest_prefix=$(find_longest_prefix)
+
+  echo "$INTERESTING_FILES" | while read line ; do
+    # create the directory in which the file should be copied (e.g
+    # /tmp/dummy_dir/<music_author>/<title_name>/)
+    local this_file_dest_dir=$(sed -e "s,$longest_prefix,$dest_dir," -e "s,/[^/]*$,," <<< "$line" )
+    mkdir -p "$this_file_dest_dir" 2>/dev/null
+    cp "$line" "$this_file_dest_dir"
+  done
+
+
+  if ( ! test -e "${dest_dir}/Makefile" ) ; then
+    # Let's create a Makefile.  When dealing with several files, the
+    # following question arises: "which file should I pass to lilypond
+    # as parameter?". Creating a Makefile will takes this away.  The
+    # file given as parameter to lilypond is the first file in the list
+    # of interesting ones.
+
+    local first_file=$(head -n 1 <<< "$INTERESTING_FILES")
+    # Unfortunately, first file is the full path to the file. Using
+    # this variable as is won't work after detarring the
+    # tarball. Indeed it will create a Makefile rule that calls
+    # lilypond with
+    # e.g. /home/<user_name>/musics/<music_author>/<title_name>/full_score.ly
+    # as parameter instead of just
+    # <music_author>/<title_name>/full_score.ly So the maximum prefix
+    # must be removed first
+    first_file=$(sed "s,${longest_prefix},," <<< "$first_file")
+    # However it *normally* is still not enough. Indeed, because of
+    # relatives includes, just removing the longest prefix assumes
+    # that to launch lilypond one has to be at the "root of the
+    # tarball".  However as stated in lilypond manual:
+    #
+    # "With relative-includes set, the path for each \include command
+    # will be taken relative to the file containing that command. This
+    # behavior is recommended and it will become the default behavior
+    # in a future version of lilypond. "
+    #
+    # Thus, one can be lazy in this script and assume lilypond will do
+    # the filepath includes resolution by itself.
+
+    cat > "${dest_dir}"/Makefile <<EOF
+all:
+	lilypond "$first_file"
+EOF
+
+  fi
+}
+
+
+
+# This function creates the minimal tarball
+#
+# It takes as parameter the desired tarball name without the extension
+# E.g, one who wants a tarball name "Bach_Praelidium_1.tar.bz2" should
+# call this function with Bach_Praelidium_1 as parameter.
+#
+# The function will return the full path of the tarball
+function create_tarball()
+{
+  local tmp_dir=$(mktemp -d /tmp/dummy_dir.XXXXXX)
+  local source_dir_name="$1"
+  local tarball_dir="${tmp_dir}/${source_dir_name}"
+
+  create_tarball_files "${tarball_dir}"
+  local this_path=$(pwd)
+  cd ${tmp_dir}
+
+  # TODO replace .tar.bz2 by .zip as many more users are familiar with
+  # .zip files
+  tar cjf "${source_dir_name}.tar.bz2" "${source_dir_name}"
+
+  cd "${this_path}"
+
+  # TODO acroread doesn't let me save .tar.bz2 as it is too
+  # "dangerous". Find a way to bypass it. Should we create a .zip_ly
+  # extension to fool acroread? and then tell users that .zip_ly files
+  # opens up with frescobaldi ? (requires asking frescobaldi author to
+  # implement a open .zip_ly file first). And then let also ask any
+  # lilypond based software to do the same ... just to fool
+  # acroread!!!
+
+  local dest_file="/tmp/${source_dir_name}.tar.bz2.to_be_renamed"
+  mv "${tarball_dir}.tar.bz2" "$dest_file"
+  rm -rf ${tmp_dir}
+  echo "$dest_file"
+}
+
+
+
+# This function attaches the given files to the pdf
+#
+# The first parameter is the pdf file, the other ones represent the
+# list of files to attach to.
+function attach_files_to_pdf()
+{
+  if test $# -lt 2 ; then
+      echo "Bad usage" >&2
+      echo "  the function \""${FUNCNAME[0]}"\" takes at least two parameters:" >&2
+      echo "  -  the pdf filename to attach files to" >&2
+      echo "  -  at least one file to attach to the pdf" >&2
+      exit 1
+  fi
+
+  local pdf_file="$1"
+  shift
+  local files_to_attach="$*"
+
+  local output_pdf_file=$(mktemp)
+
+  pdftk "$pdf_file" attach_files "$files_to_attach" output $output_pdf_file
+  if test $? -ne 0 ; then
+    echo "file attachment failed" >&2
+    rm -f $output_pdf_file
+    exit 1
+
+  else
+    mv $output_pdf_file "$pdf_file"
+
+  fi
+
+}
+
+
+
+# This function takes the pdf file as parameter
+function main()
+{
+  local pdf_file="$1"
+
+  if test 0 -eq $NB_INTERESTING_FILES ; then
+    echo "error no user input files found"  >&2
+    exit 1
+
+  elif test 1 -eq $NB_INTERESTING_FILES ; then
+    # only one source file found, don't bother creating a tarball that
+    # will only contain one file. Just attach it.
+    local source_file="$INTERESTING_FILES"
+    attach_files_to_pdf "$pdf_file" "$source_file"
+
+  else
+    # several source files found, so create a tarball containing:
+    # $INTERESTING_FILES
+    local file_to_attach=$(create_tarball "${pdf_file%%.pdf}")
+    attach_files_to_pdf "$pdf_file" "$file_to_attach"
+
+  fi
+
+
+}
+
+
+main "$2"
-- 
1.7.10.4

