From d717897ee6cdbb9158659fad7f2c0e407e1aab13 Mon Sep 17 00:00:00 2001 From: mainzelm Date: Mon, 29 Mar 2004 08:10:25 +0000 Subject: [PATCH] Removed files from install-lib. --- install-lib-module.scm | 48 --- install-lib.scm | 707 ----------------------------------------- install-pkg | 3 - 3 files changed, 758 deletions(-) delete mode 100644 install-lib-module.scm delete mode 100644 install-lib.scm delete mode 100755 install-pkg diff --git a/install-lib-module.scm b/install-lib-module.scm deleted file mode 100644 index ca17e83..0000000 --- a/install-lib-module.scm +++ /dev/null @@ -1,48 +0,0 @@ -;;; Installation library for scsh modules. -;;; $Id: install-lib-module.scm,v 1.4 2004/02/01 23:12:26 frese Exp $ - -;;; Interfaces - -(define-interface install-interface - (export tmpl-libtool-la-reader - - version->string - string->version - version-compare - version? - version=? - - (define-package :syntax) - - install-file - install-files - install-directory - install-directories - install-directory-contents - - identity - parse-boolean - show-boolean - - get-directory - get-option-value - with-output-to-load-script* - (with-output-to-load-script :syntax) - write-to-load-script - - install-main)) - -;;; Structures - -(define-structure install install-interface - (open scheme-with-scsh - fluids - let-opt - srfi-1 - srfi-9 - srfi-13 - srfi-37 - configure - pp) - (files install-lib)) diff --git a/install-lib.scm b/install-lib.scm deleted file mode 100644 index 369a977..0000000 --- a/install-lib.scm +++ /dev/null @@ -1,707 +0,0 @@ -;;; Installation library for scsh modules. -;;; $Id: install-lib.scm,v 1.4 2004/02/01 23:12:26 frese Exp $ - -;; TODO -;; - add support for communication between configure and pkg-def.scm -;; - add support for image creation, -;; - add support to maintain a documentation index, -;; - maybe add a "--force" option to overwrite existing files - -;; -;; Support code templates -;; -;; These templates are meant to be inserted in package-loading -;; scripts. - -;; Template to parse libtool's ".la" files. -(define tmpl-libtool-la-reader - '((define (normalize-la-entry key val) - (let ((left-quotes-rx (rx (: bos #\'))) - (right-quotes-rx (rx (: #\' eos))) - (kill-matches - (lambda (rx str) - (regexp-substitute/global #f rx str 'pre 'post)))) - (cons (string->symbol key) - (kill-matches left-quotes-rx - (kill-matches right-quotes-rx val))))) - (define add-la-entry - (let ((splitter (infix-splitter (rx #\=))) - (comment-rx (rx (: bos #\#)))) - (lambda (line alist) - (cond - ((and (not (regexp-search? comment-rx line)) - (string-index line #\=)) - (let ((lst (splitter line))) - (if (= 2 (length lst)) - (cons (apply normalize-la-entry lst) alist) - (error "Could not read la entry" line list)))) - (else alist))))) - (define (read-libtool-la file-name) - (call-with-input-file - file-name - (lambda (port) - (let lp ((line (read-line port)) (alist '())) - (if (eof-object? line) - alist - (lp (read-line port) (add-la-entry line alist))))))))) - -;; -;; Utilities -;; - -(define default-perms-fn - (lambda (name) #o755)) - -;; Return the name of the parent directory of FNAME. -(define (parent-directory fname) - (file-name-directory (directory-as-file-name fname))) - -;; Create directory FNAME and all its parents, as needed. -(define (create-directory&parents fname . rest) - (let-optionals rest ((perms-fn default-perms-fn)) - (let ((parent (parent-directory fname))) - (if (not (file-exists? parent)) - (apply create-directory&parents parent rest)) - (if (not (file-exists? fname)) - (-create-directory fname - (perms-fn (absolute-file-name fname))))))) - -;; Return the length of the longest prefix common to lists L1 and L2, -;; by comparing elements using PRED (defaults to EQUAL?). -(define (common-prefix-length l1 l2 . rest) - (let-optionals rest ((pred equal?)) - (if (or (null? l1) (null? l2) (not (pred (first l1) (first l2)))) - 0 - (+ 1 (apply common-prefix-length (cdr l1) (cdr l2) rest))))) - -;; Return the name of file NAME relative to DIR (defaults to current -;; directory). -(define (relative-file-name name . rest) - (let-optionals rest ((dir (cwd))) - (let* ((abs-pl (split-file-name (absolute-file-name name))) - (dir-pl (split-file-name (directory-as-file-name dir))) - (cp-len (common-prefix-length abs-pl dir-pl))) - (path-list->file-name (append (make-list (- (length dir-pl) cp-len) "..") - (drop abs-pl cp-len)))))) - -;; Return the name of FNAME, which must be absolute, with NEW-ROOT as -;; root. -(define (re-root-file-name fname new-root) - (let ((fname-pl (split-file-name fname)) - (new-root-pl (split-file-name new-root))) - (if (string=? (first fname-pl) "") - (path-list->file-name (append new-root-pl (cdr fname-pl))) - (error "no root to replace in relative file name" fname)))) - -;; Copy file/symlink SOURCE to TARGET. TARGET must be the name of a -;; non-existing file (i.e. it cannot be the name of a directory). -(define (copy-file source target) - (if (file-exists? target) - (error "copy-file: target file exists" target)) - (if (file-symlink? source) - (create-symlink (read-symlink source) target) - (begin - (run (cp ,source ,target)) - (set-file-mode target (file-mode source))))) - -;; Like "load" but without printing anything. -(define load-quietly - (let ((eval (lambda (expr t) (eval expr (interaction-environment))))) - (lambda (file-name) - (call-with-input-file file-name - (lambda (port) (port-fold port read eval #f)))))) - -(define (permissions->string perms) - (let ((decode (lambda (mask str) - (if (zero? (bitwise-and perms mask)) "-" str)))) - (string-append (decode #o400 "r") (decode #o200 "w") (decode #o100 "x") - (decode #o040 "r") (decode #o020 "w") (decode #o010 "x") - (decode #o004 "r") (decode #o002 "w") (decode #o001 "x")))) - -;; Replace all bindings of KEY in ALIST with one binding KEY to DATUM. -(define (alist-replace key datum alist) - (alist-cons key datum (alist-delete key alist))) - -;; Return the value associated with KEY in ALIST. If none exists, -;; return DEFAULT, or signal an error if no DEFAULT was given. -(define (alist-get key alist . rest) - (cond ((assoc key alist) => cdr) - ((not (null? rest)) (first rest)) - (else (error "internal error: cannot find key in alist" key alist)))) - -;; Convert all arguments to strings using DISPLAY and concatenate the -;; result in a single string which is returned. -(define (as-string . args) - (call-with-string-output-port - (lambda (port) (for-each (lambda (arg) (display arg port)) args)))) - -;; Return a string of max(M,N) white spaces. -(define (spaces m n) (make-string (max m n) #\space)) - -;; -;; Support for dry runs / verbose operation. -;; - -(define (wrap real-fn info-fn) - (lambda args - (if (or (get-option-value 'verbose) (get-option-value 'dry-run)) - (begin (display (apply info-fn args)) (newline))) - (if (not (get-option-value 'dry-run)) - (apply real-fn args)))) - -(define -create-directory - (wrap create-directory - (lambda (fname . rest) - (let-optionals rest ((perms #o777)) - (as-string "creating directory " fname - " (perms: " (permissions->string perms) ")"))))) - -(define -create-symlink - (wrap create-symlink - (lambda (old-name new-name) - (as-string "creating symbolic link " new-name - " pointing to " old-name)))) - -(define -copy-file - (wrap copy-file - (lambda (source target) - (as-string "copying file " source " to " target)))) - -(define -delete-file - (wrap delete-file - (lambda (fname) (as-string "deleting file " fname)))) - -;; -;; Versions -;; -;; Versions are represented as lists of integers, the most significant -;; being at the head. - -;; Return the printed representation of VERSION. -(define (version->string version) - (string-join (map number->string version) ".")) - -;; Convert the printed representation of a version found in -;; VERSION-STRING to the version it represents. -(define string->version - (let ((split-version (infix-splitter "."))) - (lambda (version-string) - (map string->number (split-version version-string))))) - -;; Compare two versions lexicographically and return the symbol -;; 'smaller if the first is strictly smaller than the second, 'equal -;; if both are equal, and 'greater otherwise. -(define (version-compare v1 v2) - (cond ((and (null? v1) (null? v2)) 'equal) - ((null? v1) 'smaller) - ((null? v2) 'greater) - (else (let ((v1h (car v1)) (v2h (car v2))) - (cond ((< v1h v2h) 'smaller) - ((> v1h v2h) 'greater) - (else (version-compare (cdr v1) (cdr v2)))))))) - -(define (version? v1 v2) (eq? (version-compare v1 v2) 'greater)) -(define (version=? v1 v2) (eq? (version-compare v1 v2) 'equal)) - -;; -;; Layouts -;; - -;; Names of all shared locations (i.e. the ones which do not depend on -;; the platform). -(define shared-locations - '(active base misc-shared scheme doc)) - -;; Names of all non-shared (i.e. platform-dependent) locations. -(define non-shared-locations - '(lib)) - -;; All locations defined for a layout. -(define all-locations (append shared-locations non-shared-locations)) - -;; Return true iff the given location is "active", that is if files -;; should be installed in it. -(define (active-location? location) - (member location (if (get-option-value 'non-shared-only) - non-shared-locations - all-locations))) - -;; Parse a layout given as a string of comma-separated bindings. A -;; binding consists of the name of a location, followed by an equal -;; sign and the name of the directory to associate to the location. -(define parse-layout - (let ((split-defs (infix-splitter ",")) - (split-sides (infix-splitter "="))) - (lambda (str) - (map (lambda (name&value) - (let ((name/value (split-sides name&value))) - (cons (string->symbol (first name/value)) (second name/value)))) - (split-defs str))))) - -;; Combine layouts L1 and L2 by adding to L1 all the additional -;; mappings found in L2. -(define (combine-layouts l1 l2) - (fold (lambda (key/value layout) - (if (assoc (car key/value) layout) layout (cons key/value layout))) - l1 - l2)) - -;; Return an absolute version of LAYOUT by prepending PREFIX to all -;; its components (which must be relative). -(define (absolute-layout layout prefix) - (map (lambda (key/value) - (cons (car key/value) (absolute-file-name (cdr key/value) prefix))) - layout)) - -;; Return the directory associated with the LOCATION in LAYOUT. -(define (layout-dir layout location) - (alist-get location layout #f)) - -;; Predefined layouts - -(define (scsh-layout platform base) - `((base . ,base) - (misc-shared . ,base) - (scheme . ,(absolute-file-name "scheme" base)) - (lib . ,(absolute-file-name "lib" base)) - (doc . ,(absolute-file-name "doc" base)))) - -(define (scsh-layout-1 platform pkg) - (combine-layouts '((active . ".")) - (scsh-layout platform (package-full-name pkg)))) - -(define (scsh-layout-2 platform pkg) - (combine-layouts - '((active . "active")) - (scsh-layout platform - (path-list->file-name - (list "installed" - (package-name pkg) - (version->string (package-version pkg))))))) - -(define (fhs-layout platform pkg) - (let ((base (absolute-file-name (package-full-name pkg) - "share/scsh/modules"))) - `((base . ,base) - (misc-shared . ,base) - (scheme . ,(absolute-file-name "scheme" base)) - (lib . ,(absolute-file-name (package-full-name pkg) - "lib/scsh/modules")) - (doc . ,(absolute-file-name (package-full-name pkg) "share/doc")) - (active . "share/scsh/modules")))) - -(define predefined-layouts - `(("scsh" . ,scsh-layout-1) - ("scsh-alt" . ,scsh-layout-2) - ("fhs" . ,fhs-layout))) - -;; -;; Packages -;; - -(define-record-type package - (make-package name version extensions install-thunk) - package? - (name package-name) - (version package-version) - (extensions package-extensions) - (install-thunk package-install-thunk)) - -;; Return the full name of PKG. -(define (package-full-name pkg) - (string-append - (package-name pkg) "-" (version->string (package-version pkg)))) - -;; Return the value of extension called EXT for PKG. If such an -;; extension doesn't exist, return #f. -(define (package-extension pkg ext) - (alist-get ext (package-extensions pkg) #f)) - -;; List of all defined packages -(define packages '()) - -;; Add PKG to the above list of all defined packages. -(define (add-package pkg) - (set! packages (cons pkg packages))) - -(define-syntax define-package - (syntax-rules () - ((define-package name version extensions body ...) - (add-package (make-package name - (quote version) - (quasiquote extensions) - (lambda () body ...)))))) - -;; -;; Package options -;; - -(define-record-type pkg-opt - (really-make-pkg-opt key - help - arg-help - required-arg? - optional-arg? - default - parse - show - transform) - pkg-opt? - (key pkg-opt-key) - (help pkg-opt-help) - (arg-help pkg-opt-arg-help) - (required-arg? pkg-opt-required-arg?) - (optional-arg? pkg-opt-optional-arg?) - (default pkg-opt-default) - (parse pkg-opt-parse) - (show pkg-opt-show) - (transform pkg-opt-transform)) - -(define (make-pkg-opt key help arg-help req-arg? opt-arg? default . rest) - (let-optionals rest ((parse identity) - (show identity) - (transform (lambda (old new) new))) - (really-make-pkg-opt key - help - arg-help - req-arg? - opt-arg? - default - parse - show - transform))) - -;; Return the name of PKG-OPT -(define (pkg-opt-name pkg-opt) - (symbol->string (pkg-opt-key pkg-opt))) - -;; Convert PKG-OPT into an SRFI-37 option. -(define (pkg-opt->option pkg-opt) - (let ((key (pkg-opt-key pkg-opt)) - (transform (pkg-opt-transform pkg-opt)) - (parse (pkg-opt-parse pkg-opt))) - (option (list (pkg-opt-name pkg-opt)) - (pkg-opt-required-arg? pkg-opt) - (pkg-opt-optional-arg? pkg-opt) - (lambda (opt name arg alist) - (alist-replace key - (transform (alist-get key alist) (parse arg)) - alist))))) - -;; Return a pair (key, default) which associates the default value of -;; PKG-OPT to its key. -(define (pkg-opt-key&default pkg-opt) - (cons (pkg-opt-key pkg-opt) (pkg-opt-default pkg-opt))) - -;; Return the list of all package options of the PACKAGES. -(define (all-package-options packages) - (append-map - (lambda (pkg) - (cond ((package-extension pkg 'options) - => (lambda (opts) - (map (lambda (args) (apply make-pkg-opt args)) opts))) - (else '()))) - packages)) - -;; -;; Load script handling -;; - -;; Evaluate THUNK with CURRENT-OUTPUT-PORT opened on the current -;; package's loading script (in the install directory). During a dry -;; run, or when only non-shared data has to be installed, do nothing. -(define (with-output-to-load-script* thunk) - (let ((dir (get-directory 'base #t))) - (create-directory&parents dir) - (if (not (or (get-option-value 'dry-run) (get-option-value 'non-shared-only))) - (with-output-to-file (absolute-file-name "load.scm" dir) - thunk)))) - -;; Sugar for with-output-to-load-script*. -(define-syntax with-output-to-load-script - (syntax-rules () - ((with-output-to-load-script body ...) - (with-output-to-load-script* (lambda () body ...))))) - -;; Pretty-print all the elements of s-exps, one after the other, to -;; the current package's loading script (in the install directory). -(define (write-to-load-script s-exps) - (with-output-to-load-script (for-each p s-exps))) - -;; -;; Actions -;; - -;; Perform all actions required to make the given version of the -;; package active (i.e. the default version for that package). -(define (activate-package layout pkg) - (let ((lnk-name (absolute-file-name (package-name pkg) - (layout-dir layout 'active)))) - (if (and (file-exists? lnk-name) (file-symlink? lnk-name)) - (-delete-file lnk-name)) - (-create-symlink (relative-file-name (layout-dir layout 'base) - (file-name-directory lnk-name)) - lnk-name))) - -(define (install-thing% layout name-or-pair location target-rel-dir perms-fn) - (let* ((target-dir (absolute-file-name target-rel-dir - (layout-dir layout location))) - (source (if (pair? name-or-pair) (car name-or-pair) name-or-pair)) - (target-name (file-name-nondirectory (if (pair? name-or-pair) - (cdr name-or-pair) - name-or-pair))) - (target (absolute-file-name target-name target-dir))) - (create-directory&parents target-dir perms-fn) - (cond ((or (file-regular? source) (file-symlink? source)) - (-copy-file source target)) - ((file-directory? source) - (-create-directory target (file-mode source)) - (install-directory-contents% layout - source - location - (absolute-file-name target-name - target-rel-dir) - perms-fn)) - (else (error "cannot install file-system object" source))))) - -(define (install-directory-contents% layout - name - location - target-rel-dir - perms-fn) - (for-each (lambda (thing) - (install-thing% layout thing location target-rel-dir perms-fn)) - (map (lambda (f) (absolute-file-name f name)) - (directory-files name #t)))) - -(define (install-thing name-or-pair location . rest) - (if (active-location? location) - (let-optionals rest ((target-rel-dir ".") (perms-fn default-perms-fn)) - (install-thing% (fluid *install-layout*) - name-or-pair - location - target-rel-dir - perms-fn)))) - -(define (install-things names-or-pairs . rest) - (for-each (lambda (name-or-pair) - (apply install-thing name-or-pair rest)) - names-or-pairs)) - -(define install-file install-thing) -(define install-files install-things) -(define install-directory install-thing) -(define install-directories install-things) - -(define (install-directory-contents name location . rest) - (if (active-location? location) - (let-optionals rest ((target-rel-dir ".") (perms-fn default-perms-fn)) - (install-directory-contents% (fluid *install-layout*) - name - location - target-rel-dir - perms-fn)))) - -(define *layout* (make-fluid #f)) -(define *install-layout* (make-fluid #f)) - -;; Return the directory identified by LOCATION in the current layout. -;; If INSTALL? is true, return the directory valid during the -;; installation of the package, otherwise return the directory valid -;; after installation (i.e. during package use). -(define (get-directory location install?) - (layout-dir (fluid (if install? *install-layout* *layout*)) location)) - -;; Perform all actions to install PKG in INSTALL-LAYOUT. If LAYOUT is -;; not the same as INSTALL-LAYOUT, assume that some external tool will -;; move the installed files so that they are laid out according to -;; LAYOUT. -(define (install-package layout install-layout pkg) - (let-fluids *layout* layout - *install-layout* install-layout - (package-install-thunk pkg))) - -;; -;; Error handling -;; - -;; Display MSG (a format string with ARGS as arguments) on the error -;; port, then exit with an error code of 1. -(define (display-error-and-exit msg . args) - (apply format (current-error-port) (string-append "Error: " msg) args) - (newline) - (exit 1)) - -(define usage #< specify directory where files are installed - --layout specify layout of installation directory - (predefined: ~a) - --dry-run don't do anything, print what would have been done - --verbose print messages about what is being done - --inactive don't activate package after installing it - --non-shared-only only install platform-dependent files, if any - -advanced options: - --build name of platform for which to build - --layout-from load layout of installation directory from file - --layout-to output layout to given file - --install-prefix specify prefix to used during installation - (to be used only during staged installations) - -END -) - -(define usage-descr-col 26) - -;; Complete the above USAGE string to include information about the -;; package options PKG-OPTS. -(define (complete-usage! pkg-opts) - (let ((usage-port (make-string-output-port))) - (write-string usage usage-port) - (write-string "\npackage-specific options:\n" usage-port) - (for-each - (lambda (pkg-opt) - (let ((option/arg (format #f "--~a ~a" - (pkg-opt-name pkg-opt) - (pkg-opt-arg-help pkg-opt)))) - (format usage-port - " ~a~a~a [~a]\n" - option/arg - (spaces 2 (- usage-descr-col (string-length option/arg))) - (pkg-opt-help pkg-opt) - ((pkg-opt-show pkg-opt) (pkg-opt-default pkg-opt))))) - pkg-opts) - (set! usage (string-output-port-output usage-port)))) - -;; Display the usage string, then MSG (a format string with ARGS as -;; arguments) on the standard output port, then exit with an error -;; code of 1. -(define (display-usage-and-exit msg . args) - (format #t - usage - (car (command-line)) - (string-join (map car predefined-layouts) ", ")) - (if msg (begin (apply format #t msg args) (newline))) - (exit 1)) - -;; -;; Command line parsing -;; - -;; Predefined parsers/unparsers -(define (parse-boolean s) - (cond ((string=? s "yes") #t) - ((string=? s "no") #f) - (else (display-error-and-exit - "unknown boolean value '~a'. Use 'yes' or 'no'." s)))) - -(define (show-boolean b) - (if b "yes" "no")) - -;; The identity function, sometimes useful for parsers/unparsers. -(define (identity x) x) - -;; Fluid containing the value of all options. -(define *options-values* (make-fluid #f)) - -(define package-definition-file "pkg-def.scm") - -(define (get-option-value key) - (alist-get key (fluid *options-values*))) - -(define options - (let ((alist-arg-updater (lambda (key) - (lambda (opt name arg alist) - (alist-replace key arg alist)))) - (alist-boolean-updater (lambda (key) - (lambda (opt name arg alist) - (alist-replace key #t alist))))) - (list - (option '(#\h "help") #f #f - (lambda args (display-usage-and-exit #f))) - (option '("prefix") #t #f (alist-arg-updater 'prefix)) - (option '("dest-dir") #t #f (alist-arg-updater 'dest-dir)) - (option '("layout") #t #f - (lambda (opt name arg alist) - (alist-replace 'layout - (cond ((assoc arg predefined-layouts) => cdr) - (else (parse-layout arg))) - alist))) - (option '("layout-from") #t #f - (lambda (opt name arg alist) - (alist-replace 'layout - (let ((layout (call-with-input-file arg read))) - (lambda args layout)) - alist))) - (option '("layout-to") #t #f (alist-arg-updater 'layout-to)) - (option '("build") #t #f (alist-arg-updater 'build)) - (option '("non-shared-only") #f #f - (alist-boolean-updater 'non-shared-only)) - (option '("inactive") #f #f (alist-boolean-updater 'inactive)) - (option '("dry-run") #f #f (alist-boolean-updater 'dry-run)) - (option '("verbose") #f #f (alist-boolean-updater 'verbose))))) - -(define options-defaults - `((prefix . #f) - (dest-dir . "/") - (layout . ,scsh-layout-1) - (layout-to . #f) - (build . ,(host)) - (non-shared-only . #f) - (inactive . #f) - (dry-run . #f) - (verbose . #f))) - -(define (parse-options args options defaults) - (args-fold args - options - (lambda (option name . rest) - (display-usage-and-exit "Unknown option ~a" name)) - (lambda (operand . rest) - (display-usage-and-exit "Don't know what to do with ~a" - operand)) - defaults)) - -(define (install-main cmd-line) - (if (not (file-exists? package-definition-file)) - (display-error-and-exit "cannot find package definition file (~a)" - package-definition-file)) - (load-quietly package-definition-file) - (let ((all-pkg-opts (all-package-options packages))) - (if (not (null? all-pkg-opts)) - (complete-usage! all-pkg-opts)) - (let* ((all-opts (append options (map pkg-opt->option all-pkg-opts))) - (all-dfts (append options-defaults - (map pkg-opt-key&default all-pkg-opts))) - (options-values (parse-options (cdr cmd-line) all-opts all-dfts)) - (prefix (alist-get 'prefix options-values)) - (dest-dir (alist-get 'dest-dir options-values)) - (dest-prefix (and prefix (re-root-file-name prefix dest-dir))) - (layout-fn (alist-get 'layout options-values)) - (layout-to (alist-get 'layout-to options-values)) - (build (alist-get 'build options-values)) - (non-shared-only? (alist-get 'non-shared-only options-values)) - (activate? (not (alist-get 'inactive options-values)))) - (if (not prefix) - (display-error-and-exit "no prefix specified (use --prefix option)")) - (let-fluids *options-values* options-values - (lambda () - (for-each - (lambda (pkg) - (let* ((rel-layout (layout-fn build pkg)) - (layout (absolute-layout rel-layout prefix)) - (i-layout (absolute-layout rel-layout dest-prefix))) - (if layout-to - (call-with-output-file - (string-append layout-to "_" (package-full-name pkg)) - (lambda (port) - (write rel-layout port) (newline port)))) - (install-package layout i-layout pkg) - (if (and activate? (not non-shared-only?)) - (activate-package i-layout pkg)))) - packages)))))) diff --git a/install-pkg b/install-pkg deleted file mode 100755 index 8edafc6..0000000 --- a/install-pkg +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh -exec scsh -lm install-lib-module.scm -o pp -o configure -o install -e install-main -s "$0" "$@" -!#