diff options
-rw-r--r-- | NEWS | 74 | ||||
-rw-r--r-- | doc/idest.texi | 4 | ||||
-rw-r--r-- | scheme/Makefile.am | 4 | ||||
-rw-r--r-- | scheme/batch.scm | 46 | ||||
-rw-r--r-- | scheme/idest/batch/help.scm | 25 | ||||
-rw-r--r-- | scheme/idest/format/help.scm | 82 | ||||
-rw-r--r-- | scheme/idest/format/shortlist.scm | 2 | ||||
-rw-r--r-- | scheme/idest/list-modules.scm | 100 | ||||
-rw-r--r-- | src/cmdline.opt | 17 | ||||
-rw-r--r-- | src/guile.c | 55 | ||||
-rw-r--r-- | src/idest.h | 3 | ||||
-rw-r--r-- | src/main.c | 43 |
12 files changed, 297 insertions, 158 deletions
@@ -1,4 +1,4 @@ -IdEst -- history of user-visible changes. 2011-07-17 +IdEst -- history of user-visible changes. 2011-07-25 Copyright (C) 2009-2011 Sergey Poznyakoff See the end of file for copying conditions. @@ -64,6 +64,51 @@ only if the input file originally had no ID3 tags. Prints verbose frame descriptions instead of their short names. +* The `--script' option. + +The `--script' (`-S') option stops further argument processing and +passes the rest of command line as argument to the script. The script +can modify the arguments (e.g. by removing its command-line options). +The modified arguments are then returned to idest and processed as +a list of input file names. + +* Argument to the --script option is searched in the %load-path. + +This feature is disabled if the argument contains directory separators +(/). + +* New option --dry-run + +The `--dry-run' (`-n') option can be used together with `--script' +or `--batch' options. It instructs idest to print all the +modifications the script produced without actually applying them +to the file(s). Use it to check whether your scripts work in +the expected way and correctly modify the frames. For example, +to test the script `modify.scm' run + + idest --dry-run --script modify.scm *.mp3 + +* New option --format + +The `--format=NAME' (`-H NAME') option instructs idest to run a +user-defined format NAME. This option stops further argument +processing and passes the rest of command line as argument to +the format script. To obtain a list of available formats along +with short descriptions of them, run `idest --format=help'. + +See also `User-defined formats and batch scripts', below. + +* New option --batch + +The `--batch=NAME' (`-B NAME') option instructs idest to run a +user-defined batch modification module NAME. This option stops +further argument processing and passes the rest of command line as +argument to the batch script. To obtain a list of available batch +scripts along with short descriptions of them, run +`idest --batch=help'. + +See also `User-defined formats and batch scripts', below. + * The --delete option takes optional argument The argument, if supplied, is a comma-separated list of the @@ -108,7 +153,7 @@ The full syntax is: (See above for a description of the optional QUAL part). For example: - idest --query Title%TIT2,Artist%TPE1,Author%TOLY,Composer%TCOM *.mp3 + idest --query=Title%TIT2,Artist%TPE1,Author%TOLY,Composer%TCOM *.mp3 * Changes in Scheme representation of frame lists. @@ -146,25 +191,22 @@ $HOME/.idest.scm and $GUILE_SITE/idest/idest.scm (where HOME is the user home directory and GUILE_SITE is Guile site-wide directory). The first of them which is found is loaded as a Scheme source file. -* Argument to the --script option is searched in the %load-path. - -This feature is disabled if the argument contains directory separators -(/). - * Guile scripts can access and modify command line arguments. -* Test script `dry-run.scm' - -The test script `dry-run.scm' is installed in the program script -directory. It allows you to check whether your scripts work in -the expected way and correctly modify the frames. For example, -to test the script `modify.scm' run +See "The `--script' option" above. - idest --script dry-run modify.scm *.mp3 +* User-defined formats and batch scripts. -The program will print input file names and frames produced by -`modify.scm' without actually touching your data. +User-defined formats provide a convenient way to extend `idest' +functionality. A format is a Guile script. It differs from the +usual scripts in that it is written as a module and stored in +directory "idest/format", somewhere in the %load-path. Formats are +invoked using the `--format' (`-H') option (see above). Several +formats are shipped with the idest. +Batch scripts are similar to formats, except that they provide a way +to modify tags. Batch scripts are stored in "idest/format" directory +and are called via the `--batch' (`-B') option (see above). Version 1.2, 2011-04-10 diff --git a/doc/idest.texi b/doc/idest.texi index bd6bad9..fa73ad4 100644 --- a/doc/idest.texi +++ b/doc/idest.texi @@ -1146,10 +1146,6 @@ are deleted. @xref{Delete}. Print verbose frame descriptions instead of short names. @xref{describe}. -@item -f -@itemx --function=@var{name} -Guile function to call. @xref{Scripting}. - @item -F @var{flist} @itemx --filter=@var{flist} Operate only on frames from @var{flist}. This option affects the diff --git a/scheme/Makefile.am b/scheme/Makefile.am index a71eb52..b8eefe7 100644 --- a/scheme/Makefile.am +++ b/scheme/Makefile.am @@ -15,17 +15,19 @@ # along with Idest. If not, see <http://www.gnu.org/licenses/>. EXTRA_DIST=\ + batch.scm\ dry-run.scm\ format.scm sitedir = @GUILE_SITE@/$(PACKAGE) site_DATA=\ + batch.scm\ dry-run.scm\ format.scm dist-hook: - tar -C $(srcdir) -c -f - --exclude-vcs idest | \ + tar -C $(srcdir) -c -f - --exclude-vcs --exclude-backups idest | \ tar -C $(distdir) -x -f - versionsitedir = $(sitedir)/$(VERSION) diff --git a/scheme/batch.scm b/scheme/batch.scm new file mode 100644 index 0000000..5fa368c --- /dev/null +++ b/scheme/batch.scm @@ -0,0 +1,46 @@ +;; This file is part of Idest +;; Copyright (C) 2011 Sergey Poznyakoff +;; Idest is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation; either version 3, or (at your option) +;; any later version. +;; +;; Idest is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. +;; +;; You should have received a copy of the GNU General Public License +;; along with Idest. If not, see <http://www.gnu.org/licenses/>. + +(let ((cmd (command-line)) + (mod-pathname #f) + (saved-load-hook %load-hook)) + + (set! %load-hook (lambda (filename) + (set! mod-pathname filename) + (set! %load-hook saved-load-hook))) + + (let ((mod-name (list-ref cmd 1))) + (set-program-arguments (list-tail cmd 1)) + + (catch 'misc-error + (lambda () + (set! idest-main + (module-ref + (resolve-module + (list 'idest 'batch (string->symbol mod-name))) + 'idest-main))) + (lambda (key port message args sys-error) + (with-output-to-port + (current-error-port) + (cond + ((not mod-pathname) + (format #t + "idest: no such batch: ~A~%" mod-name) + (exit 1)) + (else + (apply format #t (string-append "idest: " message) args) + (newline) + (exit 1)))))) + (set! idest-readonly #f))) diff --git a/scheme/idest/batch/help.scm b/scheme/idest/batch/help.scm new file mode 100644 index 0000000..48891ce --- /dev/null +++ b/scheme/idest/batch/help.scm @@ -0,0 +1,25 @@ +;; This file is part of Idest +;; Copyright (C) 2011 Sergey Poznyakoff +;; +;; Idest is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation; either version 3, or (at your option) +;; any later version. +;; +;; Idest is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. +;; +;; You should have received a copy of the GNU General Public License +;; along with Idest. If not, see <http://www.gnu.org/licenses/>. + +(define-module (idest batch help)) + +(use-modules (idest list-modules)) + +(idest-list-modules 'batch) +(exit 0) + + + diff --git a/scheme/idest/format/help.scm b/scheme/idest/format/help.scm index 4f739e6..e55dda1 100644 --- a/scheme/idest/format/help.scm +++ b/scheme/idest/format/help.scm @@ -16,86 +16,10 @@ (define-module (idest format help)) -(use-modules (ice-9 getopt-long) - (srfi srfi-1)) +(use-modules (idest list-modules)) -(define (strip-suffix name) - (call-with-current-continuation - (lambda (return) - (for-each - (lambda (suf) - (if (and (not (string-null? suf)) (string-suffix? suf name)) - (return - (substring name 0 (- (string-length name) (string-length suf)))))) - %load-extensions) - (return #f)))) +(idest-list-modules 'format) +(exit 0) -(let* ((saved-load-hook %load-hook) - (cmd (command-line)) - (progname (car cmd)) - ;; Collect a list of possible modules. List elements are conses: - ;; (basename . dir) - ;; where basename is the module name and dir is the directory where - ;; it is found. Make sure only one entry for each basename exists. - ;; Sort the list alphabetically on basename. - (candidates - (sort - (fold - (lambda (elt prev) - (catch 'misc-error - (lambda () - (let ((dir (string-append elt "/idest/format"))) - (if (and dir - (file-exists? dir) - (eq? (stat:type (stat dir)) 'directory)) - (let ((d (opendir dir))) - (let loop ((file (readdir d))) - (cond - ((not (eof-object? file)) - (if (eq? (stat:type - (stat (string-append dir "/" file))) - 'regular) - (let ((base (strip-suffix file))) - (if (and base - (not (assoc-ref prev base))) - (set! prev (cons (cons base dir) - prev))))) - (loop (readdir d))))))))) - (lambda (key . args) - #f)) - prev) - '() - %load-path) - (lambda (a b) - (string<? (car a) (car b)))))) - ;; Try out each candidate and print ist name, directory and description - ;; if it happens to be a valid idest format module. - ;; Take care not to bail out on errors. Disable %load-hook as it migh - ;; clobber the output. - (set! %load-hook #f) - (for-each - (lambda (candidate) - (catch 'misc-error - (lambda () - (let ((mod (resolve-module - (list 'idest 'format - (string->symbol (car candidate)))))) - ; Check if it defines idest-main - (module-ref mod 'idest-main) - (format #t "~A (from ~A): ~A~%" - (car candidate) (cdr candidate) - (catch #t - (lambda () - (module-ref mod 'description)) - (lambda (key . args) - "no description"))))) - (lambda (key . args) - #f))) - candidates) - (newline) - (set! %load-hook saved-load-hook) - (exit 0)) - - diff --git a/scheme/idest/format/shortlist.scm b/scheme/idest/format/shortlist.scm index 4dd418b..a3d5bed 100644 --- a/scheme/idest/format/shortlist.scm +++ b/scheme/idest/format/shortlist.scm @@ -8,7 +8,7 @@ (define-module (idest format shortlist)) (define-public description - "display title, artist name and year, on a signle line") + "display title, artist name and year, on a single line") (define (get-frame code frames) (or (assoc-ref diff --git a/scheme/idest/list-modules.scm b/scheme/idest/list-modules.scm new file mode 100644 index 0000000..bd538ea --- /dev/null +++ b/scheme/idest/list-modules.scm @@ -0,0 +1,100 @@ +;; This file is part of Idest +;; Copyright (C) 2011 Sergey Poznyakoff +;; +;; Idest is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation; either version 3, or (at your option) +;; any later version. +;; +;; Idest is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. +;; +;; You should have received a copy of the GNU General Public License +;; along with Idest. If not, see <http://www.gnu.org/licenses/>. + +(define-module (idest list-modules)) + +(use-modules (ice-9 getopt-long) + (srfi srfi-1)) + +(define (strip-suffix name) + (call-with-current-continuation + (lambda (return) + (for-each + (lambda (suf) + (if (and (not (string-null? suf)) (string-suffix? suf name)) + (return + (substring name 0 (- (string-length name) (string-length suf)))))) + %load-extensions) + (return #f)))) + +(define-public (idest-list-modules type) + (let ((saved-load-hook %load-hook) + ;; Collect a list of possible modules. List elements are conses: + ;; (basename . dir) + ;; where basename is the module name and dir is the directory where + ;; it is found. Make sure only one entry for each basename exists. + ;; Sort the list alphabetically on basename. + (candidates + (sort + (fold + (lambda (elt prev) + (catch 'misc-error + (lambda () + (let ((dir (string-append elt "/idest/" + (symbol->string type)))) + (if (and dir + (file-exists? dir) + (eq? (stat:type (stat dir)) 'directory)) + (let ((d (opendir dir))) + (let loop ((file (readdir d))) + (cond + ((not (eof-object? file)) + (if (eq? (stat:type + (stat (string-append dir "/" file))) + 'regular) + (let ((base (strip-suffix file))) + (if (and base + (not (assoc-ref prev base))) + (set! prev (cons (cons base dir) + prev))))) + (loop (readdir d))))))))) + (lambda (key . args) + #f)) + prev) + '() + %load-path) + (lambda (a b) + (string<? (car a) (car b)))))) + + ;; Try out each candidate and print ist name, directory and description + ;; if it happens to be a valid idest format module. + ;; Take care not to bail out on errors. Disable %load-hook as it migh + ;; clobber the output. + (set! %load-hook #f) + (for-each + (lambda (candidate) + (catch 'misc-error + (lambda () + (let ((mod (resolve-module + (list 'idest type + (string->symbol (car candidate)))))) + ; Check if it defines idest-main + (module-ref mod 'idest-main) + (format #t "~A (from ~A): ~A~%" + (car candidate) (cdr candidate) + (catch #t + (lambda () + (module-ref mod 'description)) + (lambda (key . args) + "no description"))))) + (lambda (key . args) + #f))) + candidates) + (newline) + (set! %load-hook saved-load-hook))) + + + diff --git a/src/cmdline.opt b/src/cmdline.opt index f2d891d..0043164 100644 --- a/src/cmdline.opt +++ b/src/cmdline.opt @@ -184,8 +184,8 @@ GROUP([<Scripting options>]) OPTION(script,S,FILE, [<read Guile script from FILE; this stops further argument processing>]) BEGIN - guile_script = optarg; - guile_argv = argv + optind; /* Save rest of arguments */ + set_guile_argv(argc - optind, argv + optind); /* Save rest of arguments */ + *--guile_argv = optarg; optind = argc; /* Stop argument processing */ END @@ -196,10 +196,17 @@ BEGIN stop = 1; /* Stop argument processing */ END -OPTION(function,f,NAME, - [<guile function to call>]) +OPTION(batch,B,NAME, + [<apply batch modification module NAME; this stops further argument processing>]) BEGIN - guile_function = optarg; + batch_name = optarg; + stop = 1; /* Stop argument processing */ +END + +OPTION(dry-run,n,, + [<run the script, print modified frames but do not write them to the file>]) +BEGIN + dry_run_option = 1; END OPTION(trace,,[LEVEL], diff --git a/src/guile.c b/src/guile.c index b75b674..2de2fe2 100644 --- a/src/guile.c +++ b/src/guile.c @@ -22,8 +22,6 @@ int guile_inited = 0; int guile_debug = 1; -char *guile_script; -char *guile_function; char **guile_argv; SCM_GLOBAL_VARIABLE_INIT(sym_idest_main, "idest-main", SCM_EOL); @@ -81,49 +79,31 @@ guile_safe_exec(SCM (*handler)(void *data), void *data, SCM *result) return 0; } -struct load_closure { - char *filename; - int argc; - char **argv; -}; - static SCM load_handler(void *data) { - struct load_closure *lp = data; + char **argv = data; - scm_set_program_arguments(lp->argc, lp->argv, lp->filename); - scm_primitive_load(scm_from_locale_string(lp->filename)); + scm_set_program_arguments(-1, argv, NULL); + scm_primitive_load(scm_from_locale_string(argv[0])); return SCM_UNDEFINED; } static SCM load_handler_path(void *data) { - struct load_closure *lp = data; - - scm_set_program_arguments(lp->argc, lp->argv, lp->filename); - scm_primitive_load_path(scm_from_locale_string(lp->filename)); + char **argv = data; + + scm_set_program_arguments(-1, argv, NULL); + scm_primitive_load_path(scm_from_locale_string(argv[0])); return SCM_UNDEFINED; } static int -guile_load(char *filename, int use_path, char **argv) +guile_load(char **argv, int use_path) { - struct load_closure lc; - if (argv) { - lc.argc = -1; - lc.argv = argv; - } else { - char *s_argv[2]; - s_argv[0] = filename; - s_argv[1] = NULL; - lc.argc = 1; - lc.argv = s_argv; - } - lc.filename = filename; if (guile_safe_exec(use_path ? load_handler_path : load_handler, - &lc, NULL)) + argv, NULL)) exit(1); } @@ -639,7 +619,7 @@ load_startup_file() for (i = 0; argv[i]; i++) { if (access(argv[i], R_OK) == 0) { - if (guile_load(argv[i], 0, argv + i + 1)) + if (guile_load(argv + i, 0)) error(1, 0, "cannot load startup script %s", argv[i]); break; @@ -658,7 +638,7 @@ guile_init(int *pargc, char ***pargv) int argc, i; char **argv; - if (!guile_script) + if (!guile_argv) return; scm_init_guile(); @@ -678,7 +658,7 @@ guile_init(int *pargc, char ***pargv) load_startup_file(); - guile_load(guile_script, !strchr(guile_script, '/'), guile_argv); + guile_load(guile_argv, !strchr(guile_argv[0], '/')); /* Read command line arguments */ args = scm_program_arguments(); @@ -696,13 +676,8 @@ guile_init(int *pargc, char ***pargv) *pargv = argv; proc = SCM_VARIABLE_REF(sym_idest_main); - if (proc == SCM_EOL) { - if (guile_function) { - proc = SCM_VARIABLE_REF(scm_c_lookup(guile_function)); - SCM_VARIABLE_SET(sym_idest_main, proc); - } else - error(1, 0, "idest-main not defined"); - } + if (proc == SCM_EOL) + error(1, 0, "idest-main not defined"); if (scm_procedure_p(proc) != SCM_BOOL_T) error(1, 0, @@ -716,7 +691,7 @@ guile_init(int *pargc, char ***pargv) else error(1, 0, "script %s set non-boolean value of idest-readonly", - guile_script); + guile_argv[0]); guile_inited = 1; } #else diff --git a/src/idest.h b/src/idest.h index 83b1d9d..b23a02f 100644 --- a/src/idest.h +++ b/src/idest.h @@ -75,13 +75,10 @@ extern char *source_file; extern struct id3_tag *source_tag; extern enum backup_type backup_type; extern char *backup_dir; -extern gl_list_t ed_list; extern unsigned convert_version; extern unsigned version_option; extern unsigned default_version_option; extern int guile_debug; -extern char *guile_script; -extern char *guile_function; extern char **guile_argv; /* idop.c */ @@ -24,12 +24,14 @@ enum backup_type backup_type = no_backups; char *backup_dir; int verbose_option = 0; int describe_option = 0; +int dry_run_option = 0; int all_frames = 0; char *source_file; struct id3_tag *source_tag; char *format_name; +char *batch_name; struct item_info { @@ -164,6 +166,20 @@ const char *mode_option_str[] = { "--list-frames" }; +char **guile_argv_ptr; + +void +set_guile_argv(int argc, char **av) +{ + int i; + + guile_argv_ptr = xcalloc(argc + 4, sizeof(guile_argv_ptr[0])); + guile_argv = guile_argv_ptr + 3; + for (i = 0; i < argc; i++) + guile_argv[i] = av[i]; + guile_argv[i] = NULL; +} + #include "cmdline.h" int @@ -181,21 +197,30 @@ main(int argc, char **argv) argv += optind; if (format_name) { - int i; - if (mode_set) error(1, 0, "--format cannot be used with %s", mode_option_str[mode]); - if (guile_script) + if (guile_argv) error(1, 0, "--format cannot be used with --script"); - - guile_script = "format"; - guile_argv = xcalloc(argc + 2, sizeof(guile_argv[0])); - guile_argv[0] = format_name; - for (i = 0; i <= argc; i++) - guile_argv[i + 1] = argv[i]; + if (batch_name) + error(1, 0, "--format cannot be used with --batch"); + set_guile_argv(argc, argv); + *--guile_argv = format_name; + *--guile_argv = "format"; + } else if (batch_name) { + if (mode_set) + error(1, 0, "--batch cannot be used with %s", + mode_option_str[mode]); + if (guile_argv) + error(1, 0, "--batch cannot be used with --script"); + set_guile_argv(argc, argv); + *--guile_argv = batch_name; + *--guile_argv = "batch"; } + if (dry_run_option) + *--guile_argv = "dry-run"; + guile_init(&argc, &argv); if (mode == MODE_LIST) { |