mcron: Handle command line arguments in C with argp

'argp' is a convenient and maintainable way to parse command line arguments.
Guile doesn't offer an equivalent of this, so the command line handling has
been moved to C.

* src/mcron.c (parse_args, parse_opt): New functions.
(inner_main): Call 'parse_args'.
* src/mcron/scripts/mcron.scm (show-help, %options): Delete.
(main): Remove command line handling.
This commit is contained in:
Mathieu Lirzin 2017-04-25 16:32:49 +02:00
commit d011957843
No known key found for this signature in database
GPG key ID: 0ADEE10094604D37
2 changed files with 108 additions and 61 deletions

View file

@ -18,10 +18,13 @@
along with GNU Mcron. If not, see <http://www.gnu.org/licenses/>. */ along with GNU Mcron. If not, see <http://www.gnu.org/licenses/>. */
#include "utils.h" #include "utils.h"
#include <argp.h>
#include <libguile.h> #include <libguile.h>
/* Forward declarations. */ /* Forward declarations. */
static void inner_main (void *closure, int argc, char *argv[]); static void inner_main (void *closure, int argc, char *argv[]);
static SCM parse_args (int argc, char *argv[]);
static error_t parse_opt (int key, char *arg, struct argp_state *state);
int int
main (int argc, char *argv[]) main (int argc, char *argv[])
@ -44,6 +47,83 @@ main (int argc, char *argv[])
static void static void
inner_main (void *closure, int argc, char *argv[]) inner_main (void *closure, int argc, char *argv[])
{ {
SCM config = parse_args (argc, argv);
scm_set_current_module (scm_c_resolve_module ("mcron scripts mcron")); scm_set_current_module (scm_c_resolve_module ("mcron scripts mcron"));
scm_call_0 (scm_variable_ref (scm_c_lookup ("main"))); scm_call_1 (scm_variable_ref (scm_c_lookup ("main")), config);
}
/* Handle command line arguments. */
static SCM
parse_args (int argc, char *argv[])
{
static struct argp_option options[] = {
{"schedule", 's', "N", 0,
"Display the next N jobs that will be run"},
{"daemon", 'd', 0, 0,
"Run as a daemon process"},
{"stdin", 'i', "FORMAT", 0,
"Format of data passed as standard input or file arguments (default guile)"},
{0, 0, 0, 0, 0}
};
static struct argp argp = {
.options = options,
.parser = parse_opt,
.args_doc = "[FILE...]",
.doc = "Run an mcron process according to the specifications in the "
"FILE... (`-' for standard input), or use all the files in "
"~/.config/cron (or the deprecated ~/.cron) with .guile or "
".vixie extensions."
};
SCM config = SCM_EOL;
argp_program_version = PACKAGE_STRING;
argp_program_bug_address = PACKAGE_BUGREPORT;
argp_parse (&argp, argc, argv, 0, NULL, &config);
return config;
}
static error_t
parse_opt (int key, char *arg, struct argp_state *state)
{
SCM *config = state->input;
switch (key)
{
case 's':
*config = scm_assq_set_x (*config, scm_from_utf8_symbol ("schedule"),
scm_from_int (atoi (arg)));
break;
case 'd':
*config = scm_assq_set_x (*config, scm_from_utf8_symbol ("daemon"),
SCM_BOOL_T);
break;
case 'i':
if (strncmp (arg, "vixie", 6) == 0)
*config = scm_assq_set_x (*config, scm_from_utf8_symbol ("vixie"),
SCM_BOOL_T);
break;
case ARGP_KEY_NO_ARGS:
*config = scm_assq_set_x (*config, scm_from_utf8_symbol ("files"),
SCM_EOL);
break;
case ARGP_KEY_ARGS:
{
SCM lst = SCM_EOL;
int filec = state->argc - state->next;
char **filev = state->argv + state->next;
for (int i = filec - 1; i >= 0; i--)
lst = scm_cons (scm_from_locale_string (filev[i]), lst);
*config = scm_assq_set_x (*config, scm_from_utf8_symbol ("files"),
lst);
break;
}
case ARGP_KEY_ARG:
default:
return ARGP_ERR_UNKNOWN;
}
return 0;
} }

View file

@ -25,34 +25,6 @@
#:use-module (mcron vixie-specification) #:use-module (mcron vixie-specification)
#:export (main)) #:export (main))
(define (show-help)
(display "Usage: mcron [OPTIONS] [FILES]
Run an mcron process according to the specifications in the FILES (`-' for
standard input), or use all the files in ~/.config/cron (or the
deprecated ~/.cron) with .guile or .vixie extensions.
-v, --version Display version
-h, --help Display this help message
-sN, --schedule[=]N Display the next N jobs that will be run by mcron
-d, --daemon Immediately detach the program from the terminal
and run as a daemon process
-i, --stdin=(guile|vixie) Format of data passed as standard input or
file arguments (default guile)")
(newline)
(show-package-information))
(define %options
`((schedule (single-char #\s) (value #t)
(predicate ,(λ (str) (string->number str))))
(daemon (single-char #\d) (value #f))
(noetc (single-char #\n) (value #f))
(stdin (single-char #\i) (value #t)
(predicate ,(λ (val)
(or (string=? val "guile")
(string=? val "vixie")))))
(version (single-char #\v) (value #f))
(help (single-char #\h) (value #f))))
(define process-user-file (define process-user-file
(let ((guile-regexp (make-regexp "\\.gui(le)?$")) (let ((guile-regexp (make-regexp "\\.gui(le)?$"))
(vixie-regexp (make-regexp "\\.vix(ie)?$"))) (vixie-regexp (make-regexp "\\.vix(ie)?$")))
@ -102,35 +74,30 @@ $XDG_CONFIG_HOME is not defined uses ~/.config/cron instead)."
;;; Entry point. ;;; Entry point.
;;; ;;;
(define* (main #:optional (args (command-line))) (define* (main #:optional (opts '()))
(let ((opts (parse-args args %options))) (when config-debug
(when config-debug (debug-enable 'backtrace))
(debug-enable 'backtrace))
(cond ((option-ref opts 'help #f) (%process-files (or (assq-ref opts 'files) '())
(show-help) (if (assq-ref opts 'vixie) "vixie" "guile"))
(exit 0))
((option-ref opts 'version #f) (cond ((assq-ref opts 'schedule) ;display jobs schedule
(show-version "mcron") => (λ (count)
(exit 0)) (display (get-schedule (max 1 count)))
(else (exit 0)))
(%process-files (option-ref opts '() '()) ((assq-ref opts 'daemon) ;run mcron as a daemon
(option-ref opts 'stdin "guile")) (case (primitive-fork)
(cond ((option-ref opts 'schedule #f) ;display jobs schedule ((0) (setsid))
=> (λ (count) (else (exit 0)))))
(display (get-schedule (max 1 (string->number count))))
(exit 0))) ;; Forever execute the 'run-job-loop', and when it drops out (can
((option-ref opts 'daemon #f) ;run mcron as a daemon ;; only be because a message has come in on the socket) we process
(case (primitive-fork) ;; the socket request before restarting the loop again.
((0) (setsid)) (catch-mcron-error
(else (exit 0))))) (let ((fdes-list '()))
;; Forever execute the 'run-job-loop', and when it drops out (can (while #t
;; only be because a message has come in on the socket) we process (run-job-loop fdes-list)
;; the socket request before restarting the loop again. ;; we can also drop out of run-job-loop because of a SIGCHLD,
(catch-mcron-error ;; so must test FDES-LIST.
(let ((fdes-list '())) (unless (null? fdes-list)
(while #t (process-update-request fdes-list))))))
(run-job-loop fdes-list)
;; we can also drop out of run-job-loop because of a SIGCHLD,
;; so must test FDES-LIST.
(unless (null? fdes-list)
(process-update-request fdes-list)))))))))