#!/usr/bin/guile3.0 \
-e main
!#

;;; dsv.in -- Get various information from DSV files.

;; Copyright (C) 2015-2025 Artyom V. Poptsov <poptsov.artyom@gmail.com>
;;
;; This program 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 of the License, or
;; (at your option) any later version.
;;
;; The program 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 the program.  If not, see <http://www.gnu.org/licenses/>.


;;; Commentary:

;; A handy tool to get various information from DSV files.


;;; Code:

(use-modules (ice-9 getopt-long)
             (scheme documentation)
             (logging logger)
             (srfi  srfi-26)
             (dsv)
             (dsv common)
             (dsv table)
             (dsv table-preset)
             (dsv config)
             (dsv version)
             (dsv cli common)
             (dsv fsm context))



(define (print-help-and-exit table-presets-path)
    (display
     (string-append "\
Usage: dsv [options] [file]

The default behavior of the program is to print a formatted table from a
<file> to stdout.  The options listed below can be used to change or modify
this behavior.

When no <file> is provided, dsv reads data from stdin.

Options:
  --help, -h                 Print this message and exit.
  --summary, -s              Print summary information for a file.
  --delimiter, -D <delim>    Set a delimiter.
  --guess-delimiter, -d      Guess a file delimiter and print the result.
  --number, -n               Number rows and columns.
  --width, -w <width>        Wrap long lines of text inside cells to fit the table
                             into the specified width.  If with is specified as
                             \"auto\" (default value) then current terminal width
                             is used.
                             When the required width is too small for the table
                             wrapping, an error will be issued.
                             Zero width means no wrapping so the table might not
                             fit into the screen.
  --map-cell, -m <code>      Apply an arbitrary Scheme code on each cell value
                             before printing.
                             There are three variables that can be used in the code:
                             - $value -- current cell value.
                             - $row   -- current row number
                             - $col   -- current column number.

                             Code examples:
                             '(if (> $value 0) $value 0)'
                             '(string-append \"\\\"\" $value \"\\\"\")'

                             Note that the code must return a string, that in turn
                             will be printed in a cell.

  --filter-row, -f <code>    Keep only rows for which CODE returns #t.
                             There are two variables that can be used in the code:
                             - $value -- current row content.
                             - $row   -- current row number.

                             For example with this code Guile-DSV keeps only rows
                             that are 5 columns in length:
                             '(= (length $value) 5)'

  --filter-column, -c <procedure>
                             Keep only columns for which PROCEDURE returns #t.
                             There are two variables that can be used in the code:
                             - $value -- current column content as a list.
                             - $row   -- current column number.

                             For example with this code Guile-DSV keeps only the 2nd
                             column from the input data:
                              '(= $col 2)'

  --file-format, -F <fmt>    Set a file format.  Possible formats are:
                             \"unix\" (default), \"rfc4180\"
  --with-header, -H          Use the first row of a table as a header when
                             printing the table to the screen.
  --table-borders, -b <spec> Set table borders for printing.  The value can be
                             either a borders specification or a preset name.

                             Spec can be a comma-separated list of key=value
                             pairs that specify the table style.  The list of
                             possible keys can be found below
                             (see \"Table parameters\".)

                             Also a table preset name can be used as the value.
                             See \"Table presets\" below.

                             Table preset parameters can be overridden by specifying
                             extra parameters after the preset name.  E.g.:
                               \"graphic,bs=3;31\"

                             Example values:
                               - \"v=|,h=-,j=+\"
                               - org

  --table-presets-path <path>
                             Set the table preset path.
                             This option can be also set by
                              \"GUILE_DSV_TABLE_PRESETS_PATH\" environment
                             variable.
                             Default value: " %table-presets-path "
  --to, -t <fmt>             Convert a file to a specified format, write
                             the result to stdout.
  --to-delimiter, -T <delim> Convert delimiters to the specified variant.
                             When this option is not used, default delimiters
                             for the chosen output format will be used.
  --log-driver <driver>      Set the logging driver.
                             Supported values:
                             - \"syslog\" -- use syslog as the logging driver.
                             - \"file\" -- log to a specified file. Output files
                               are rotated as needed.
                               Options:
                                 \"file\" -- the full path to the log file.
                             - \"null\" -- disable logging (discard all the
                               messages.)

                             Default value is \"syslog\"
  --log-opt <options>        Set the logging options.
                             The set of options depends on the logging driver.
                             Format:
                               \"key1=value1,key2=value2\"
                             Example:
                               \"file=/tmp/smc.log\"

                             There's an option \"stderr\" that is handled
                             independently from the log driver that allows
                             to configure stderr logging.
                             Example values:
                               \"stderr=true\"
                               \"stderr=false\"

  --version                  Print information about Guile-DSV version.
  --debug                    Enable state machine debugging.

Table parameters:
"))
    (print-table-parameters (current-output-port))
    (display "\nTable presets:\n")
    (print-table-presets (current-output-port)
                         #:table-presets-path table-presets-path)
  (exit))

(define (print-version)
  (format #t "dsv (Guile-DSV) ~a~%" (dsv-version/string))
  (display "\
Copyright (C) 2015-2025 Artyom V. Poptsov <poptsov.artyom@gmail.com>
License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Written by Artyom V. Poptsov.
"))

(define (main args)
  (let* ((option-spec '((help            (single-char #\h) (value #f))
                        (summary         (single-char #\s) (value #f))
                        (number          (single-char #\n) (value #f))
                        (width           (single-char #\w) (value #t))
                        (map-cell        (single-char #\m) (value #t))
                        (filter-row      (single-char #\f) (value #t))
                        (filter-column   (single-char #\c) (value #t))
                        (delimiter       (single-char #\D) (value #t))
                        (to-delimiter    (single-char #\T) (value #t))
                        (file-format     (single-char #\F) (value #t))
                        (log-driver                        (value #t))
                        (log-opt                           (value #t))
                        (with-header     (single-char #\H) (value #f))
                        (table-borders   (single-char #\b) (value #t))
                        (guess-delimiter (single-char #\d) (value #f))
                        (table-presets-path                (value #t))
                        (to              (single-char #\t) (value #t))
                        (version                           (value #f))
                        (debug                             (value #f))))
         (options (getopt-long args option-spec))
         ;; Options
         (help-needed?      (option-ref options 'help            #f))
         (summary-needed?   (option-ref options 'summary         #f))
         (numbers-needed?   (option-ref options 'number          #f))
         (width             (option-ref options 'width           "auto"))
         (guess-delimiter?  (option-ref options 'guess-delimiter #f))
         (file-format       (option-ref options 'file-format     "unix"))
         (map-cell-proc     (option-ref options 'map-cell         #f))
         (filter-row-proc   (option-ref options 'filter-row       #f))
         (filter-col-proc   (option-ref options 'filter-column    #f))
         (delimiter         (and=> (option-ref options 'delimiter #f)
                                   (cut string-ref <> 0)))
         (to-delimiter      (and=> (option-ref options 'to-delimiter #f)
                                   (cut string-ref <> 0)))
         (with-header?      (option-ref options 'with-header     #f))
         (table-borders     (option-ref options 'table-borders   ""))
         (table-presets-path (option-ref options
                                         'table-presets-path
                                         (if (getenv "GUILE_DSV_TABLE_PRESETS_PATH")
                                             (getenv "GUILE_DSV_TABLE_PRESETS_PATH")
                                             %table-presets-path)))
         (to                (option-ref options 'to              #f))
         (version?          (option-ref options 'version         #f))
         (debug?            (option-ref options 'debug           #f))
         (log-driver        (option-ref options 'log-driver      "syslog"))
         (log-opt           (option-ref options 'log-opt         #f))
         (args              (option-ref options '()              #f)))

    (set-debug! debug?)

    (when debug?
      (enable-log-level! (smc-log-default-logger) 'debug))
    (smc-log-init! log-driver
                   (cond
                    ((and (string? log-opt)
                          (not (string-null? log-opt)))
                     (map (lambda (e)
                            (let ((kv (string-split e #\=)))
                              (cons (string->symbol (car kv))
                                    (cadr kv))))
                          (string-split log-opt #\,)))
                    (else
                     '())))

    (when help-needed?
      (print-help-and-exit table-presets-path))

    (when version?
      (print-version)
      (exit))

    (let ((input-port (if (null? args)
                          (current-input-port)
                          (open-input-file (car args)))))
      (cond
       (summary-needed?
        (print-summary input-port (string->dsv-format file-format) delimiter))
       (guess-delimiter?
        (print-delimiter input-port (string->dsv-format file-format)))
       (to
        (convert input-port
                 #:source-delimiter delimiter
                 #:target-delimiter to-delimiter
                 #:source-format (string->dsv-format file-format)
                 #:target-format (string->symbol to)
                 #:filter-col-proc (and filter-col-proc
                                        (eval-string
                                         (string-append
                                          "(lambda ($value $col) "
                                          filter-col-proc
                                          ")")))
                 #:filter-row-proc (and filter-row-proc
                                        (eval-string
                                         (string-append
                                          "(lambda ($value $row) "
                                          filter-row-proc
                                          ")")))
                 #:proc (and map-cell-proc
                             (eval-string
                              (string-append "(lambda ($value $row $col) "
                                             map-cell-proc
                                             ")")))
                 #:debug-mode? debug?))
       (else
        (print-file input-port
                    (string->dsv-format file-format) table-borders
                    delimiter
                    #:debug-mode? debug?
                    #:numbering? numbers-needed?
                    #:width (cond
                             ((string=? width "auto")
                              (let ((size (terminal-size)))
                                (and size
                                     (cdr size))))
                             (else
                              (string->number width)))
                    #:table-presets-path table-presets-path
                    #:with-header? with-header?
                    #:filter-col-proc (and filter-col-proc
                                           (eval-string
                                            (string-append
                                             "(lambda ($value $col) "
                                             filter-col-proc
                                             ")")))
                    #:filter-row-proc (and filter-row-proc
                                           (eval-string
                                            (string-append
                                             "(lambda ($value $row) "
                                             filter-row-proc
                                             ")")))
                    #:proc (and map-cell-proc
                                (eval-string
                                 (string-append "(lambda ($value $row $col) "
                                                map-cell-proc
                                                ")"))))))

      (close input-port))
    (exit)))


;;;

;; Local Variables:
;; mode: Scheme
;; End:

;;; dsv.in ends here
