I've attached a Python script that takes a LilyPond source file and prints
all the include file names it finds, recursively.  It keeps track of the
current working directory.  It does *not* reeport LilyPond include files,
but I'm guessing that's not what's wanted.  Every include file is reported
once and printed sorted.  It's pretty rough, but there it is.

--
Knute Snortum



On Sat, Mar 15, 2025 at 4:12 PM Saul Tobin <saul.james.to...@gmail.com>
wrote:

> These are questions I've had to consider as I work on Emacs
> lilypond-ts-mode, and I suspect are relevant to lots of tooling projects.
>
> Since output file names and locations are potentially set via arbitrary
> Scheme code, it's not feasible to do this reliably without actually running
> the LilyPond binary itself to parse the input file. It's certainly possible
> to do that without actually running translation. The easiest way would be
> to make a custom init file that defines `default-toplevel-book-handler` to
> a function that just calls `get-current-filename` but doesn't actually call
> into ly:book-process, then pass the custom init file to LilyPond via `-i`.
> For reference, look at the definitions of the built-in toplevel handlers in
> lily-library.scm. Note that `get-current-filename` is not exported from
> `lily` into the session module. Also note that this will only give you the
> filename, not the output directory if one was set via `-o`. I believe you
> can get that by simply checking the current Guile working directory.
>
> The complexity of retrieving output file names is, I believe, the reason
> that Frescobaldi just requires users to specify non-standard output files
> via a file-local variable.
>
> Includes I suspect can't easily be retrieved via Scheme, since they
> literally just insert file contents verbatim. But just grep-ing for
> \include probably gets you far enough to be useful in 99% of LilyPond
> projects. Technically, it's possible for a LilyPond project to load Scheme
> files that construct music data, but users with such complicated projects
> can't reasonably expect tooling to simply work.
>
> Saul
>
> On Sat, Mar 15, 2025 at 5:11 PM YTG 1234 <ytg1234.pm...@gmail.com> wrote:
>
>> I've been wondering what the equivalent of cpp -M for Lilypond is, if
>> there is such a thing.
>>
>> It wouldn't be so simple to implement; as well as tracking \includes
>> recursively, tracking the expected names of output files (as there can
>> be multiple per source file) is also required. There's probably some
>> Guile interface for getting all the books generated by a file, but there
>> would have to be a way to evaluate it without compiling the entire score…
>>
>> Any ideas?
>>
>>
>>
#!/usr/bin/env python3

# Prints all non-LilyPond include file name in a source file.
# Execute it in the directory your source file is in.

import sys

def main():
    if len(sys.argv) != 2:
        print("Usage: python find-includes.py <filename>")
        sys.exit(1)

    filename = sys.argv[1]
    includes = set()
    cwd = []

    def find_includes(filename):
        try:
            with open(filename, 'r') as file:
                for line in file:
                    pos = line.find("\\include")
                    if pos != -1:
                        include_name = line[pos+9:].strip()
                        include_name = include_name[1:-1] # remove quotes
                        includes.add("/".join(cwd) + include_name)
                        d = include_name.rfind('/') # directory in name?
                        if d != -1:
                            cwd.append(include_name[:d+1])
                            include_name = include_name[d+1:]
                        find_includes("/".join(cwd) + include_name)
                        if d != -1:
                            cwd.pop()
        except FileNotFoundError:
            print(f"File {filename} not found (may be a LilyPond file).", file=sys.stderr)
            includes.discard(filename)
        except Exception as e:
            print(f"An error occurred: {e}", file=sys.stderr)

    find_includes(filename)
    print(*sorted(includes), sep='\n')

if __name__ == "__main__":
    main()

Reply via email to