Merge branch 'development' into production

This commit is contained in:
contrapunctus 2021-03-29 12:00:34 +05:30
commit f07bdcee68
5 changed files with 358 additions and 193 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*.html
*~

View File

@ -1,153 +1,110 @@
* History
** Misadventures with make
*** ##emacs.de
#+BEGIN_EXAMPLE
[2016-06-02T21:38:49+0530]
<contrapunctus> Make sagt, "<Datei> ist bereits aktuell", aber ich habe sie gleich geändert :\
#+END_EXAMPLE
#+TITLE: mkly
#+SUBTITLE: a Guile build script for Lilypond (and other) projects
* README
** Features
1. Provides a default set of rules, so you don't have to.
2. Provides a full-blown programming language for writing rules.
3. Tiny (< 150 lines); trivial to hack on.
4. Implemented and configured using the only reasonable language (a Lisp 😏)
*** PM
#+BEGIN_EXAMPLE
[2016-06-03T20:03:45+0530]
<contrapunctus> hmf :\
[2016-06-03T20:07:02+0530]
<contrapunctus> I have files whose output names don't necessarily correspond to the input file names. (e.g. main.ly -> output/songname.pdf) Also, entering the output file name to specify which file to compile isn't convenient.
[2016-06-03T20:10:26+0530]
<contrapunctus> so I can have the pattern rule and correct dependency-modified checking (although that's completely useless for Lilypond, as here the smallest of changes in any source file will always trigger the whole output file to be recompiled), and do without good names; or I can have the good names and always run make with -B :\
[2016-06-03T20:16:11+0530]
<contrapunctus> I have half a mind to just write a friggin shell script.
#+END_EXAMPLE
** Non-goals
1. Incremental builds
** Bumbling with bash
#+BEGIN_EXAMPLE
[2016-06-03T20:33:02+0530]
<contrapunctus> and now...time to learn bash
[...]
[2016-06-03T20:34:53+0530]
<contrapunctus> and they say irregular/complex/rich syntax is not an issue - http://mywiki.wooledge.org/BashGuide/CommandsAndArguments
[2016-06-03T20:35:50+0530]
<contrapunctus> I even took the simple evaluation of lisps for granted until I came to makefiles...I didn't read all that stuff about how makefiles are evaluated, but the fact that it takes up a section instead of a friggin' paragraph oughta tell one something.
[2016-06-03T20:36:39+0530]
<contrapunctus> I suppose this is the time a programmer goes out for a stiff drink
[2016-06-03T20:37:35+0530]
<contrapunctus> "<contrapunctus> I remember fuming over having to learn ASDF when learning CL, but even that was easier than this :\" - #learnprogramming
[2016-06-03T20:37:58+0530]
<wasamasa> :D
[2016-06-03T20:38:11+0530]
<wasamasa> ASDF is after all, designed
#+END_EXAMPLE
** Installation
Guile must be available at =/usr/bin/guile=
1. Place =mkly= in your $PATH or your project root
2. Make it executable -
: chmod u+x mkly
3. In your project root, run -
: ./mkly
** Simply stellar Scheme!
#+BEGIN_EXAMPLE
[2016-06-04T09:08:01+0530]
<contrapunctus> so I have some trouble with the bash script I make
[2016-06-04T09:08:11+0530]
<contrapunctus> (mostly because I tried to avoid arg parsing)
[2016-06-04T09:08:36+0530]
<contrapunctus> someone in #learnprogramming says "personally, i would do this in python or ruby or lua or something, where you have a ton of nice libraries to help with that stuff. for me personally, bash always seems like more trouble than its worth"
[2016-06-04T09:09:04+0530]
<contrapunctus> I express my desire to do it in Scheme or Lisp, but I 'can't count on those being there on every system'
[2016-06-04T09:09:20+0530]
<contrapunctus> other person - 'isn't lilypond written in scheme?'
[2016-06-04T09:09:32+0530]
<contrapunctus> lightbulb moment - I could just write the script in guile :D
[2016-06-04T09:09:40+0530]
<contrapunctus> (= dependency of Lilypond)
[2016-06-04T22:27:05+0530]
<contrapunctus> also, schnell zusammengehackt (lol) - http://ix.io/OJA/scm
[2016-06-04T22:30:35+0530]
<contrapunctus> ich habe die getopts-long Bibliothek benutzt können, aber...hm, ich kann mich an den Grund nicht mehr erinnern o_O
[2016-06-04T22:33:04+0530]
<contrapunctus> (vielleicht sehen die *ohrschützer*mäßige Variablen nicht schön und Common-Lisp-artig aus?)
[2016-06-04T22:34:16+0530]
<wasamasa> mein aktueller gesichtsausdruck: http://brause.cc/srs/neeein.jpg
[2016-06-04T22:51:02+0530]
<contrapunctus> was o_O
[2016-06-04T22:51:25+0530]
<contrapunctus> so schrecklich ist es?
[2016-06-04T22:53:06+0530]
<contrapunctus> was soll ich darin verbessern?
[2016-06-04T23:11:31+0530]
<wasamasa> dieses deutsch ist einfach fürchterlich
#+END_EXAMPLE
** Invocation and default rules
=mkly= can be run without arguments -
: mkly
This will compile =main.ly= and =part-*.ly=.
* TODO
1. Make it declarative - define options and their effect, and the structure of the command, separately from the code that makes it happen.
2. Use regexps to define target names and how the output file name(s) derive from them.
3. Also, add support for part-<instrument>.ly -> <project-name>-<instrument>.pdf files.
You can pass one or more /targets/ -
: mkly <target>*
Each target will be checked against the /patterns/ defined in the rules, and the actions for all matching patterns will be run.
Hypothetical config in that style -
The default targets are
1. =main.ly= - compiles the file without point-and-click. Emits a file to =output/<project-name>=.
2. a file name matching =part-*.ly=, e.g. =part-foo.ly= - compiles the file without point-and-click. Emits a file to =output/<project-name>-part-foo=.
3. =dev= - compiles =main.ly= with point-and-click enabled. Emits a file to =output/<project-name>-pacON=.
4. =all= - compiles =main.ly= and all =part-*.ly= files, without point-and-click.
#+BEGIN_SRC scheme
(options
(pac ("off" "-dno-point-and-click")
(_ ""))
(size ("a4" "-a4")
(_ "")))
If a subdirectory is supplied, =mkly= will descend to it for all following targets (until another subdirectory on the command line is encountered) -
: mkly main foo/ dev bar/ main dev
Here, target "main" will be run in the project root, target "dev" will be run in the subdirectory "foo/", and target "main" and "dev" will be run in subdirectory "bar/".
;; TARGETS defines build targets for the project, in the form
;; (targets ENTRY*)
;; Each ENTRY is in the form -
;; (TARGET-NAME INPUT-FILE OUTPUT-FILE)
;; TARGET-NAME is the target name, specified by the user at the
;; command line while calling the script.
;; INPUT-FILE is the stem name of the file to be compiled.
;; OUTPUT-FILE is the stem name of the file to be passed as output
;; file name to the compiler command
** Defining rules
Rules are returned as a list by the procedure =rules=. This procedure can be redefined by loading your own Scheme file, by running =mkly= with the =--load= / =-l= command line option.
;; Inside a target entry, the keyword 'target' is automatically bound
;; to the target name - here, "main". The first entry given here means
;; - "specifying 'main' as target (on the CLI) will compile a file
;; called 'main' (the extension is added in the command stage) and
;; will have <project-name> as output stem."
(targets
("main" target project-name)
;; for target "band", compile "band", output "<project-name>-band"
("band" target (string-append project-name target))
("tags" (non-ly "etags -l none -r '/^[a-zA-Z]*\ *=/' *ly"))
("clean" (non-ly "rm -r output/"))
;; for target "foo", compile "part-foo", output "<project-name>-foo"
(_ (string-append "part-" target)
(string-append project-name "-" target)))
Some default variables are defined for use in defining rules -
1. =project-root= - the project root directory
2. =project-name= - by default, the basename of the parent directory
3. =shell-path= - path to the shell you want to use for running your commands
;; input-file and output-file are taken from the first and second args
;; of the target as defined in TARGETS
(command "lilypond" pac "-o output/" output-file size " " input-file size ".ly")
#+END_SRC
** Built-in helper functions
1. =(use-dir! DIR)= - creates DIR if it does not exist. DIR must be a string. Raises an error if DIR exists and is not a directory. Returns DIR.
2. =(vcs-current-branch)=
3. =(parent PATH)= - returns the parent directory component of PATH, or #f if none is present.
4. =(getcwd-base)= - returns the basename of the current directory.
Alternative way?
1. Instead regexps. If the supplied target matches a regexp for a
** TODO
*** Certain [50%]
1. [X] Make it declarative - define options and their effect, and the structure of the command, separately from the code that makes it happen.
2. [X] Use regexps to define target names and how the output file name(s) derive from them.
3. [X] Name files differently if PAC is on - the upside of this is, you don't accidentally send someone the point-and-click-enabled version of the file.
The downside is, it's easy to have file-pacON.pdf open during editing, while the script is actually compiling to file.pdf (or vice-versa) [fn:1], and wonder why your score isn't updating.
4. [X] Create output directory if it doesn't exist!
5. [X] Remove duplicate layer of CLI options - currently there's a needless extra layer of CLI options for options which already exist. Should make code simpler, UI familiar.
+ But the Lilypond CLI is ugly - what's better, "pac=off", or "-dno-point-and-click"? :\
- After the revamp, the script doesn't (yet) aim to want to support options like this; the idea is that the user specifies a /situation/ as a target, and all options relevant to that situation are passed (as specified in the rules by the user). It's a "maybe" to-do.
6. [ ] Tab completion of specified target names
7. [ ] Multiple targets in one command e.g. to compile both the main score and the parts in one command.
8. [X] Targets containing other targets.
+ I've tried calling the script itself with the required targets - not sure if that actually works. Especially considering that #7 - multiple targets in one command - isn't implemented yet.
+ If =mkly= is not in the user's $PATH and is invoked as =./mkly=, the shell won't be able to find it if it calls itself. We can try constructing the path to the script (using =getcwd= - if =(first (command-line))= is "./mkly", it's in the current working directory)
9. [-] Shell globs, both in targets and in actions.
* [X] Document shell-path as a user variable
* [ ] Use shell-path to run commands
10. [-] Sub-project operations
1. [X] Specify targets for sub*-project(s), instead of project in current working directory.
* The current way is somewhat inelegant, though - you either modify all rules to also work in sub-projects, or you make new rules for sub-projects...why can't the same rule work for both? What if we could specify a subdirectory, followed by the usual targets, resulting in us =cd= ing into that directory and running the usual targets? Same rules working in both situations!
2. [ ] Use sub-project-specific mkly-rules.scm if present, else use project-root mkly-rules.scm
3. [ ] I want to compile a particular target in every subproject. (use globs)
4. Tab completion
5. Recursive operations. Sometimes I have large projects with subprojects (and even sub-subprojects) in them. Usually, I want to compile a particular target in every subproject.
- Maybe...if the input file in a target entry is just a "/" (or maybe "->"?), the target name will be understood as being the target to run in either
1. all subdirectories directly below the directory containing the current build.scm (in this case, it is an error if any of these subdirectories do not contain a build.scm)
2. all subdirectories in which a build.scm can be found, directly below the directory containg the current build.scm
6. Create output directory if it doesn't exist!
7. Targets containing other targets.
(Old) implementation notes
1. Maybe...if the input file in a target entry is just a "/" (or maybe "->"?), the target name will be understood as being the target to run in either
1. all subdirectories directly below the directory containing the current build.scm (in this case, it is an error if any of these subdirectories do not contain a build.scm)
2. all subdirectories in which a build.scm can be found, directly below the directory containg the current build.scm
11. [ ] Expand branch detection to include more VCSs.
12. [ ] Add -h/--help, and -d/--debug or -v/--verbose
13. [ ] Warn users about ignored =mkly-rules.scm=, if detected
14. [ ] Add command line option to load a Scheme file, serving as an extension point (e.g. to define custom rules)
It would also be useful to compile the part-<instrument>.ly file if I'm editing an <instrument-class>/<instrument>.ly file - useful when working with orchestral files and wanting to put the generated MIDI in qtractor (importing a single track is easier than importing a multi-track MIDI just because you changed music on one track). That's more of an Emacs-side thing, though.
[fn:1] These are likely to happen if you have a compile-with-last-command-on-save setup, like in my Emacs.
...hell, one could define all kinds of rules - "when I'm working with such-and-such files, compile such-and-such targets."
8. Multiple targets in one command e.g. to compile both the main score and the parts in one command.
** VCS branch integration (2018-11-22T19:43:42+0530)
Check what branch you're on. If it's "master", do nothing. If it's something else, add the name to the output file.
** (2018-11-15T00:44:46+0530)
Currently there's a needless extra layer of arguments for arguments which already exist. Just accept compilation target(s) and pass all of the rest to lilypond(1). Should make code simpler, UI familiar.
- But the Lilypond CLI is ugly - what's better, "pac=off", or "-dno-point-and-click"? :\
** lack of tab completion sucks (2016-09-29T02:18:01+0530)
I can't even use tab completion from Emacs because the target name has to be "part-<instrument>", not "part-instrument.ly" (so I have to keep deleting the .ly bit)
** naming files differently if PAC is on (2018-08-30T03:50:37+0530)
The upside of this is, you don't accidentally send someone the point-and-click-enabled version of the file.
The downside is, it's easy to have file-pacON.pdf open during editing, while the script is actually compiling to file.pdf (or vice-versa) [1], and wonder why your score isn't updating.
[1] These are likely to happen if you have a compile-with-last-command-on-save setup, like in my Emacs.
* Bugs
1. Barfs when placed in and run from directories with a path containing non-ASCII characters.
*** Maybe [0%]
1. [ ] Allow users to define (command-line) options and their effect.
2. [ ] VCS branch integration - check what branch you're on. If it's branch X (e.g. "main"), do nothing. If it's something else, add the name to the output file.
+ Or to the output directory name, e.g. files from the main branch go to "output/", files from branch "foo" go to "output-foo/", etc.
3. [ ] Default rules for orchestral projects - for target =part-<instrument>.ly=, compile =<instrument class>/<instrument>.ly=
4. [ ] Make =rules= into a macro
5. [ ] Implement Scheme expressions as an action.
* one possible way - all actions are Scheme expressions - for shell commands, create a =($ ...)= form (with =$= doing what =run= does now) which is =eval=-uated if it matches a target. (see branch "$-syntax")
6. [ ] Replace regexp patterns with glob patterns?
* more consistent (since targets and actions use globs too), but possibly less powerful.
*** Bugs
1. [X] Barfs when placed in and run from directories with a path containing non-ASCII characters.
This is a [[http://lists.gnu.org/archive/html/guile-user/2015-12/msg00054.html][Guile]] [[https://debbugs.gnu.org/cgi/bugreport.cgi?bug%3D22229][bug]].
2. [ ] =./mkly= -> =sh: 1: main.ly: not found=
* [ ] Default rule (no pattern provided) and =else= seem to conflict - do we want to do different things when there's no pattern provided and when a provided pattern does not match, or the same thing?
* [ ] Meta-targets - if the command does not match another rule, run as shell command; otherwise treat as list of targets
3. [ ] chdir to project root before descending into subdir
4. [ ] Error on unmatched pattern
* License
I dream of a world where all software is liberated - transparent, trustable, and accessible for anyone to use or improve. But I don't want to make demands or threats (e.g. via legal conditions) to get there.

58
build
View File

@ -1,58 +0,0 @@
#!/usr/bin/guile -s
!#
(use-modules
(ice-9 match)
(srfi srfi-1)
(ice-9 format)
(ice-9 regex))
;; (format #t "~2%")
(define *project-name*
(regexp-substitute #f (string-match ".*/" (getcwd)) 'post))
(define (get-valid-args cli-args)
;; (format (current-error-port) "cli-args - ~s~%" cli-args)
(find (lambda (cli-word)
(string-match
(string-append "^" opt) cli-word))
cli-args))
(if (< (length (command-line)) 2)
(format #t "~a~%" "Usage: build.scm TARGET [ARG=VALUE]*")
(let* ((cli-args (command-line))
(target (cadr cli-args))
(valid-args (get-valid-args cli-args))
(args-alist (valid-args->alist valid-args))
(size (match (assoc-ref args-alist "size")
("a4" "-a4")
(_ "")))
(pac (match (assoc-ref args-alist "pac")
("off" "-dno-point-and-click ")
(_ "")))
(ly-command (string-append "lilypond " pac
"-o \"output/"
*project-name*
(match target
("main" "")
(_ (string-append "-" target)))
(match pac
("" "-pacON")
(_ ""))
(match size
("" "")
(_ "-"))
size
"\" "
target size ".ly")))
;; (format (current-error-port)
;; (string-append
;; "cli-args - ~s~%"
;; "target - ~s~%"
;; "valid-args - ~s~%"
;; "args-alist - ~s~%"
;; "size - ~s~%"
;; "pac - ~s~%")
;; cli-args target valid-args args-alist size pac)
(format #t "~2%~a~2%" ly-command)
(system ly-command)))

235
mkly Executable file
View File

@ -0,0 +1,235 @@
#!/usr/bin/guile -s
!#
(use-modules
(ice-9 match)
(srfi srfi-1)
(ice-9 format)
(ice-9 regex)
(ice-9 popen)
(ice-9 rdelim)
(ice-9 getopt-long))
(define rest cdr)
;; (define (debug . args)
;; (format
;; (current-error-port) "~%~{~a - ~s~%~}~%"
;; (list 'command-line (command-line)
;; 'target target
;; 'rules rules
;; 'selected-rule selected-rule
;; 'action action)))
(define option-spec
'((version (single-char #\v))
(help (single-char #\h))
(debug (single-char #\d))
(load (single-char #\l) (value #t) ;; (predicate file-exists?)
)))
(define options (getopt-long (command-line) option-spec))
(format (current-error-port)
"debug ~s~%"
(option-ref options 'debug #f))
(define (debug format-string . args)
(when (option-ref options 'debug #f)
(apply format (current-error-port) format-string args)))
;; Return the parent directory component of PATH, or #f if none is present.
(define (parent path)
(if path
(let ((match (string-match (format #f "/~a$" (basename path)) path)))
(if match (regexp-substitute #f match 'pre "/") #f))
#f))
;; Return the basename of the current working directory.
(define (getcwd-base) (basename (getcwd)))
;; If ARG is truthy, return "-<ARG>", else return "".
(define (file-name-part arg)
(if arg (format #f "-~a" arg) ""))
;; Return FILE name without the extension, or FILE if there is no extension.
(define (file-name-no-extension file)
(let ((rindex (string-rindex file #\.)))
(if rindex
(xsubstring file 0 rindex)
file)))
;; Create DIR if it does not exist.
;; Raise an error if DIR does exist, but is not a directory.
;; Return DIR.
(define (use-dir! dir)
(unless (file-exists? dir)
(mkdir dir))
(unless (file-is-directory? dir)
(error (format #f "File ~s already exists and is not a directory" dir)))
dir)
;; Return the output of running COMMAND with ARGS in the system shell.
(define (shell-result command . args)
(let* ((port (apply open-pipe* (append `("open_read" ,command) args)))
(output (read-line port)))
(close-pipe port)
output))
;; Return the current branch of the project's version control system,
;; or #f if no version control system was detected.
(define (vcs-current-branch)
(cond ((file-exists? ".git")
;; we were using `git rev-parse --abbrev-ref HEAD' before,
;; but that sometimes resulted in an error - `fatal:
;; ambiguous argument 'HEAD': unknown revision or path not in
;; the working tree.'
(shell-result "git" "symbolic-ref" "--short" "HEAD"))
((file-exists? ".hg")
(shell-result "hg" "identify" "-b"))
;; ((or (file-exists? ".fslckout")
;; (file-exists? "_FOSSIL_")))
;; ((file-exists? ".bzr"))
;; ((file-exists? "_darcs"))
;; ((file-exists? ".svn"))
(else #f)))
(define (flatten seq)
(cond ((null? seq) '())
((not (pair? seq)) (list seq))
(else (append (flatten (car seq))
(flatten (rest seq))))))
;; Flatten and convert list ARG to a string, with each element
;; separated by spaces.
(define (list->command-line arg)
(format #f "~{~s ~}" (flatten arg)))
;; Run ARG in the system shell. ARG must be a list with elements of
;; any type. Return the exit status.
(define (run arg)
;; (format (current-error-port) "~%mkly: run: ~s~2%" (list->command-line arg))
(system (list->command-line arg)))
(let ((load-values (option-ref options 'load #f)))
(cond ((not load-values) #f)
((pair? load-values)
(debug "loading files ~s~%" load-values)
(map load load-values))
(else
(load load-values)
(debug "loading file ~s~%" load-values))))
(define shell-path "/usr/bin/bash")
(define project-root (getcwd))
(define project-name (getcwd-base))
;; Return a list of build rules.
;;
;; TARGET is a single target passed on the command line, or "" if none
;; was supplied. (If multiple targets are passed on the command line,
;; this function will be called once with each target.)
;;
;; Return value is a list of rules, where each rule is a list in
;; the form (PATTERN COMMAND [ARG ...])
;;
;; PATTERN can be
;; * a symbol - COMMAND will be run when the target passed on the
;; command line matches this symbol;
;; * a string - treated as a regular expression; COMMAND will be run
;; when it matches the target passed on the command line
;;
;; COMMAND and arguments can be any value - lists will be flattened,
;; all values will be converted to strings, and spaces will be added.
;; String values will be quoted, which is useful for escaping file
;; names in the final command to be run.
(define (rules target)
;; if TARGET is a subdirectory in the project root, descend to it
;; before executing the action
(let* ((subdir (parent target))
(branch (vcs-current-branch))
(dest (begin
(when subdir
(chdir subdir)
(set! project-name (getcwd-base)))
(use-dir! (format #f "~:[output-~a~;output/~]" branch branch)))))
;; A main.ly could also exist both in the root and in the
;; sub-directories, but this will just compile the root main.ly regardless.
`((all main.ly part-*.ly)
(dev lilypond
-o ,(string-append dest project-name "-pacON")
main.ly)
(main.ly lilypond -dno-point-and-click
-o ,(string-append dest project-name)
,target)
("part-.*\\.ly" lilypond -dno-point-and-click
-o ,(string-append
dest project-name "-" (file-name-no-extension target))
,target))))
;; Return a rule from RULES which matches TARGET
;;
;; RULES must be a list (see procedure `rules'), where each element is
;; in the form (PATTERN COMMAND [ARGS ...])
;;
;; TARGET must be a string.
;; If it is empty, return the first rule in RULES.
;; If it is `equal?' to a PATTERN string or to the name of a PATTERN
;; symbol, or if TARGET is matched by a PATTERN regular expression,
;; return the rule whose TARGET matched.
(define (select-rule rules target)
(if (null? rules)
(begin (debug "mkly: no matching rule") #f)
(let* ((rule (first rules))
(pattern (first rule))
(rule-command (rest rule)))
;; (format (current-error-port) "~%rule - ~s~%pattern - ~s~%" rule target)
(cond ((equal? "" target) rule)
((and (string? pattern)
(string-match pattern target))
;; (format #t "~%mkly: running ~s~%" rule-command)
rule)
((and (symbol? pattern)
(equal? target (symbol->string pattern)))
;; (format #t "~%mkly: running ~s~%" rule-command)
rule)
(else (select-rule (rest rules) target))))))
;; target ::= action -> run
;; | target -> for each target, recurse with new target
(define (run-rule-for target)
(let* ((rules (rules target))
(selected-rule (flatten (select-rule rules target)))
;; Support for actions which call mkly itself. Do we really want this?
(action (if (equal? "./mkly" (first (command-line)))
(map (lambda (it)
(if (or (eq? it 'mkly)
(equal? it "mkly"))
(string-append (getcwd) "/mkly")
it))
(rest selected-rule))
(rest selected-rule))))
(debug "~%~{~a - ~s~%~}~%"
(list 'command-line (command-line)
'rules rules
'selected-rule selected-rule
'action action))
(system (list->command-line action))))
;; Run the associated action for each element of TARGETS.
;;
;; TARGETS must be a list of strings, representing all non-option
;; arguments passed by the user to the script on the command line.
(define (main targets)
(debug "targets - ~s~%" targets)
(let loop ((targets targets))
(let ((target (if (null? targets) "" (first targets))))
(debug "running rule ~a for target ~s~%"
(select-rule (rules target) target) target)
(if (and (file-exists? target)
(file-is-directory? target))
(begin (chdir project-root)
(chdir target))
(run-rule-for target))
(when (pair? targets)
(loop (rest targets))))))
(main (option-ref options '() #f))

29
mkly-tests.scm Normal file
View File

@ -0,0 +1,29 @@
(load "mkly")
(define (mkly-tests)
(list
#:parent
(equal? (parent "foo/bar") "foo/")
(equal? (parent "foo") #f)
#:file-name-part
(equal? "-foo" (file-name-part "foo"))
(equal? "" (file-name-part #f))
;; #:use-dir!
;; #:vcs-current-branch
#:flatten
(equal? (flatten '(1 "a" b (c "d" 3)))
'(1 "a" b c "d" 3))
#:list->command-line
(equal? (list->command-line '(1 "a" b (c "d" 3)))
"1 \"a\" b c \"d\" 3 ")
#:select-rule
(let ((rules '((all 1) (dev 2) (main.ly 3) ("part-.*\\.ly" 4) (else 5))))
(list
(equal? (select-rule rules "") '(all 1))
(equal? (select-rule rules "all") '(all 1))
(equal? (select-rule rules "dev") '(dev 2))
(equal? (select-rule rules "main.ly") '(main.ly 3))
(equal? (select-rule rules "part-flute.ly") '("part-.*\\.ly" 4))
(equal? (select-rule rules "frobnicate") #f)))))
(mkly-tests)