emacs-diffs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Emacs-diffs] master 41b28de 4/5: Enable addition of local printers from


From: Vincent Belaïche
Subject: [Emacs-diffs] master 41b28de 4/5: Enable addition of local printers from a mode hook.
Date: Thu, 28 Jul 2016 16:13:04 +0000 (UTC)

branch: master
commit 41b28dea8587c13b0bc59c1ec70b65afab3aeeca
Author: Vincent Belaïche <address@hidden>
Commit: Vincent Belaïche <address@hidden>

    Enable addition of local printers from a mode hook.
    
    * doc/misc/ses.texi (Printer functions): Split the node into 5
    sub-nodes + add some extra documentation.
    (Various kinds of printer functions): Make an itemisation to
    disintguish better the 3 types of printers, give an example of
    lambda printer definition.
    (Standard printer functions): Add documentation for ses-prin1
    printer function.
    (Local printer functions): Add documentation for creating
    local printers programmatically from a hook.
    (Writing a lambda printer function): Add documentation about
    anti-stackoverflow precautions to take when you call the
    standard printer functions from inside a local printer.
    
    * lisp/ses.el (ses-standard-printer-functions): Add ses-prin1
    among standard printer function, and update docstring
    accordingly.
    (ses-call-printer, ses-export-tab): Call `ses-prin1' instead
    of prin1-to-string.
    (ses-define-local-printer): Add definition to arguments so
    that a local printer can be defined programmatically from a
    mode hook.  Make docstring more substantial.  Use completing
    read for local printer name input.  Plus some minor
    optimization.
    (ses-define-if-new-local-printer): New defsubst.
    (ses-center, ses-center-span, ses-dashfill)
    (ses-dashfill-span, ses-tildefill-span): Allow to pass printer
    as an optional argument to superseed column printer/default
    spreadsheet printer.
    (ses-prin1): New defun.
---
 doc/misc/ses.texi |  189 +++++++++++++++++++++++++++++++++++++++++++++--------
 lisp/ses.el       |  125 +++++++++++++++++++++++++----------
 2 files changed, 250 insertions(+), 64 deletions(-)

diff --git a/doc/misc/ses.texi b/doc/misc/ses.texi
index 8b0bb82..1c5070b 100644
--- a/doc/misc/ses.texi
+++ b/doc/misc/ses.texi
@@ -374,26 +374,62 @@ Undo previous action (@code{(undo)}).
 @cindex printer functions
 @cindex cell formatting
 @cindex formatting cells
address@hidden ses-read-cell-printer
address@hidden ses-read-column-printer
address@hidden ses-read-default-printer
address@hidden ses-define-local-printer
address@hidden ses-center
address@hidden ses-center-span
address@hidden ses-dashfill
address@hidden ses-dashfill-span
address@hidden ses-tildefill-span
-
 
 Printer functions convert binary cell values into the print forms that
 Emacs will display on the screen.
 
-A printer can be a format string, like @samp{"$%.2f"}.  The result
address@hidden
+* Various kinds of printer functions::
+* Configuring what printer function applies::
+* Standard printer functions::
+* Local printer functions::
+* Writing a lambda printer function::
address@hidden menu
+
address@hidden Various kinds of printer functions
address@hidden Various kinds of printer functions
+
+When configuring what printer function applies (@pxref{Configuring
+what printer function applies}), you can enter a printer function as
+one of the following:
+
address@hidden
address@hidden
+A format string, like @samp{"$%.2f"}.  The result
 string is right-aligned within the print cell.  To get left-alignment,
-use parentheses: @samp{("$%.2f")}.  A printer can also be a
-one-argument function (a symbol or a lambda), whose result is a string
-(right-aligned) or list of one string (left-aligned).  While typing in
-a lambda, you can use @address@hidden to complete the names of symbols.
+use parentheses: @samp{("$%.2f")}.
address@hidden
+A printer can also be a one-argument function, the result of which is
+a string (right-aligned) or list of one string (left-aligned). Such a
+function can be in turn configured as:
address@hidden
address@hidden
+A lambda expression, for instance:
+
address@hidden
+(lambda (x)
+  (cond
+     ((null x) "")
+     ((numberp x) (format "%.2f" x))
+     (t (ses-center-span x ?# 'ses-prin1))))
address@hidden lisp
+
+While typing in a lambda, you can use @address@hidden to complete
+the names of symbols.
address@hidden
+A symbol referring to a standard printer function (@pxref{Standard
+printer functions}).
address@hidden
+A symbol referring to a local printer function (@pxref{Local printer
+functions}).
address@hidden itemize
+
+
address@hidden itemize
+
+
address@hidden Configuring what printer function applies
address@hidden Configuring what printer function applies
 
 Each cell has a printer.  If @code{nil}, the column-printer for the cell's
 column is used.  If that is also @code{nil}, the default-printer for the
@@ -401,25 +437,35 @@ spreadsheet is used.
 
 @table @kbd
 @item p
address@hidden ses-read-cell-printer
 Enter a printer for current cell or range (@code{ses-read-cell-printer}).
 
 @item M-p
address@hidden ses-read-column-printer
 Enter a printer for the current column (@code{ses-read-column-printer}).
 
 @item C-c C-p
address@hidden ses-read-default-printer
 Enter the default printer for the spreadsheet
 (@code{ses-read-default-printer}).
 @end table
 
-The @address@hidden commands have their own minibuffer
-history, which is preloaded with the set of all printers used in this
-spreadsheet, plus the standard printers.
+The @address@hidden commands have their own
+minibuffer history, which is preloaded with the set of all printers
+used in this spreadsheet, plus the standard printers (@pxref{Standard
+printer functions}) and the local printers (@pxref{Local printer
+functions}).
 
-The standard printers are suitable only for cells, not columns or
-default, because they format the value using the column-printer (or
-default-printer if @code{nil}) and then center the result:
address@hidden Standard printer functions
address@hidden Standard printer functions
 
address@hidden @code
+
+Except for @code{ses-prin1}, the other standard printers are suitable
+only for cells, not columns or default, because they format the value
+using the column-printer (or default-printer if @code{nil}) and then
+center the result:
+
address@hidden @code
 @item ses-center
 Just centering.
 
@@ -434,8 +480,16 @@ Centering with dashes and spill-over.
 
 @item ses-tildefill-span
 Centering with tildes (~) and spill-over.
address@hidden table
 
address@hidden ses-prin1
+This is the fallback printer, used when calling the configured printer
+throws some error.
address@hidden ftable
+
address@hidden Local printer functions
address@hidden Local printer functions
+
address@hidden ses-define-local-printer
 You can define printer function local to a sheet with the command
 @code{ses-define-local-printer}.  For instance, define a printer
 @samp{foo} to @code{"%.2f"}, and then use symbol @samp{foo} as a
@@ -444,9 +498,50 @@ printer function.  Then, if you call again
 @code{"%.3f"}, all the cells using printer @samp{foo} will be
 reprinted accordingly.
 
-When you define a printer function with a lambda expression taking one
-argument, please take care that the returned value is a string, or a
-list containing a string, even when the input argument has an
+Sometimes there are local printers that you want to define or
+re-define automatically every time you open a sheet. For instance
+imagine that you want to define/re-define automatically a local
+printer @code{euro} to display a number like an amount of euros, that
+is to say number @code{3.1} would be displayed as
address@hidden@address@hidden To do so in any non read-only SES buffer,
+you can add some code like this to your @file{.emacs} init file:
+
address@hidden
+(defun my-ses-mode-hook ()
+  (unless buffer-read-only
+    (ses-define-local-printer
+     'euro
+     (lambda (x)
+       (cond
+       ((null x) "")
+       ((numberp x) (format "%.2f€" x))
+       (t (ses-center-span x ?# 'ses-prin1)))))))
+(add-hook 'ses-mode-hook 'my-ses-mode-hook)
address@hidden lisp
+
+If you replace command @code{ses-define-local-printer} by function
address@hidden
address@hidden ses-define-if-new-local-printer
+the definition will occur only if a local printer with the same name
+in not already defined.
+
+
address@hidden Writing a lambda printer function
address@hidden Writing a lambda printer function
+
+You can write a printer function with a lambda expression taking one
+argument in two cases:
+
address@hidden
address@hidden
+when you configure the printer function applying to a cell or column, or
address@hidden
+when you define a local printer function with command
address@hidden
address@hidden itemize
+
+When doing so, please take care that the returned value is a string,
+or a list containing a string, even when the input argument has an
 unexpected value. Here is an example:
 
 @example
@@ -454,10 +549,11 @@ unexpected value. Here is an example:
    (cond
       ((null val) "")
       ((and (numberp val) (>= val 0)) (format "%.1f" val))
-      (t (ses-center-span (format "%S" val) ?#))))
+      (t (ses-center-span val ?# 'ses-prin1))))
 @end example
 
 This example will:
+
 @itemize
 @item
 When the cell is empty (ie.@: when @code{val} is @code{nil}), print an
@@ -467,12 +563,47 @@ When the cell value is a non negative number, format the 
the value in
 fixed-point notation with one decimal after point
 @item
 Otherwise, handle the value as erroneous by printing it as an
-s-expression (using @code{prin1}), centered and surrounded by @code{#}
-filling.
+s-expression (using @code{ses-prin1}), centered and surrounded by
address@hidden filling.
 @end itemize
 
+Another precaution to take is to avoid stack-overflow (due to a
+printer function indefintely recursively re-calling itself). This can
+happen mistakenly when you use a local printer as a column printer,
+and this local printer implicitely call the current column printer, so
+will call itself recursively.  Imagine for instance that you want to
+create some local printer @code{=fill} that would center the content
+of a cell and surround it by equal signs @code{=}, and you do it this
+way:
+
address@hidden
+(lambda (x)
+  (cond
+   ((null x) "")
+   (t (ses-center x 0 ?=))))
address@hidden lisp
 
+Because @code{=fill} uses standard printer @code{ses-center} without
+passing explicitely any printer to it, @code{ses-center} will call the
+current column printer if any or the spreadsheet default printer
+otherwise. So using @code{=fill} as a column printer will result in a
+stack overflow in this column. SES does not make any check for that,
+you just have to be careful. For instance re-write @code{=fill} like
+this:
+
address@hidden
+(lambda (x)
+  (cond
+   ((null x) "")
+   ((stringp x) (ses-center x 0 ?= " %s "))
+   (t (ses-center-span x ?# 'ses-prin1))))
address@hidden lisp
 
+The code above applies the @code{=} filling only to strings, it also
+surrounds the string by one space on each side before filling with
address@hidden signs. So string @samp{Foo} will be displayed like 
@address@hidden
+Foo ===}} in an 11 character wide column. Anything else than empty cell
+or non string is displayed like errouneous by using @code{#} filling.
 
 @node Clearing cells
 @section Clearing cells
diff --git a/lisp/ses.el b/lisp/ses.el
index 305027c..9d278b6 100644
--- a/lisp/ses.el
+++ b/lisp/ses.el
@@ -275,12 +275,15 @@ Each function is called with ARG=1."
   "Display properties to create a raised box for cells in the header line.")
 
 (defconst ses-standard-printer-functions
-  '(ses-center ses-center-span ses-dashfill ses-dashfill-span
-    ses-tildefill-span)
-  "List of print functions to be included in initial history of printer
-functions.  None of these standard-printer functions is suitable for use as a
-column printer or a global-default printer because they invoke the column or
-default printer and then modify its output.")
+  '(ses-center
+    ses-center-span ses-dashfill ses-dashfill-span
+    ses-tildefill-span
+    ses-prin1)
+  "List of print functions to be included in initial history of
+printer functions.  None of these standard-printer functions,
+except function `ses-prin1', is suitable for use as a column
+printer or a global-default printer because they invoke the
+column or default printer and then modify its output.")
 
 
 ;;----------------------------------------------------------------------------
@@ -1328,7 +1331,7 @@ printer signaled one (and \"%s\" is used as the default 
printer), else nil."
          (car value))))
     (error
      (setq ses-call-printer-return signal)
-     (prin1-to-string value t))))
+     (ses-prin1 value))))
 
 (defun ses-adjust-print-width (col change)
   "Insert CHANGE spaces in front of column COL, or at end of line if
@@ -3232,7 +3235,7 @@ is non-nil.  Newlines and tabs in the export text are 
escaped."
       (when (eq (car-safe item) 'quote)
        (push "'" result)
        (setq item (cadr item)))
-      (setq item (prin1-to-string item t))
+      (setq item (ses-prin1 item))
       (setq item (replace-regexp-in-string "\t" "\\\\t" item))
       (push item result)
       (cond
@@ -3531,34 +3534,67 @@ Uses the value COMPILED-VALUE for this printer."
              (ses-begin-change))
            (ses-print-cell row col)))))))
 
-(defun ses-define-local-printer (name)
-  "Define a local printer with name NAME."
-  (interactive "*SEnter printer name: ")
+
+(defun ses-define-local-printer (name definition)
+  "Define a local printer with name NAME and definition DEFINITION.
+
+NAME shall be a symbol. Use TAB to complete over existing local
+printer names.
+
+DEFINITION shall be either a string formatter, e.g.:
+
+  \"%.2f\" or (\"%.2f\")  for left alignment.
+
+or a lambda expression, e.g. for formatting in ISO format dates
+created with a '(calcFunc-date YEAR MONTH DAY)' formula:
+
+  (lambda (x)
+     (cond
+      ((null val) \"\")
+      ((eq (car-safe x) 'date)
+       (let ((calc-format-date '(X YYYY \"-\" MM \"-\" DD)))
+         (math-format-date x)))
+      (t (ses-center-span val ?# 'ses-prin1))))
+
+If NAME is already used to name a local printer function, then
+the current definition is proposed as default value, and the
+function is redefined."
+  (interactive
+   (let (name def already-defined-names)
+     (maphash (lambda (key val) (push (symbol-name key) already-defined-names))
+              ses--local-printer-hashmap)
+     (setq name (completing-read    "Enter printer name: " 
already-defined-names))
+     (when (string= name "")
+       (error "Invalid printer name"))
+     (setq name (intern name))
+     (let* ((cur-printer (gethash name ses--local-printer-hashmap))
+            (default (and cur-printer (ses--locprn-def cur-printer))))
+            (setq def (ses-read-printer (format "Enter definition of printer 
%S: " name)
+                                        default)))
+            (list name def)))
+
   (let* ((cur-printer (gethash name ses--local-printer-hashmap))
-        (default (and (vectorp cur-printer) (ses--locprn-def cur-printer)))
-        create-printer
-        (new-def
-          (ses-read-printer (format "Enter definition of printer %S: " name)
-                            default)))
+        (default (and cur-printer (ses--locprn-def cur-printer)))
+        create-printer)
     (cond
      ;; cancelled operation => do nothing
-     ((eq new-def t))
+     ((eq definition t))
      ;; no change => do nothing
-     ((and (vectorp cur-printer) (equal new-def default)))
+     ((and cur-printer (equal definition default)))
      ;; re-defined printer
-     ((vectorp cur-printer)
+     (cur-printer
       (setq create-printer 0)
-      (setf (ses--locprn-def cur-printer) new-def)
+      (setf (ses--locprn-def cur-printer) definition)
       (ses-refresh-local-printer
        name
        (setf (ses--locprn-compiled cur-printer)
-             (ses-local-printer-compile new-def))))
+             (ses-local-printer-compile definition))))
      ;; new definition
      (t
       (setq create-printer 1)
       (puthash name
               (setq cur-printer
-                    (ses-make-local-printer-info new-def))
+                    (ses-make-local-printer-info definition))
               ses--local-printer-hashmap)))
     (when create-printer
       (let ((printer-def-text
@@ -3582,8 +3618,18 @@ Uses the value COMPILED-VALUE for this printer."
               (when (= create-printer 1)
                 (ses-file-format-extend-parameter-list 3)
                 (ses-set-parameter 'ses--numlocprn
-                                   (+ ses--numlocprn create-printer))))))))))
+                                   (1+ ses--numlocprn))))))))))
 
+(defsubst ses-define-if-new-local-printer (name def)
+  "Same as function `ses-define-if-new-local-printer', except
+that the definition occurs only when the local printer does not
+already exists.
+
+Function `ses-define-if-new-local-printer' is not interactive, it
+is intended for mode hooks to programatically automatically add
+local printers."
+  (unless  (gethash name ses--local-printer-hashmap)
+    (ses-define-local-printer name def)))
 
 ;;----------------------------------------------------------------------------
 ;; Checking formulas for safety
@@ -3794,13 +3840,16 @@ either (ses-range BEG END) or (list ...).  The TEST is 
evaluated."
 ;; Standard print functions
 ;;----------------------------------------------------------------------------
 
-(defun ses-center (value &optional span fill)
+(defun ses-center (value &optional span fill printer)
   "Print VALUE, centered within column.
 FILL is the fill character for centering (default = space).
 SPAN indicates how many additional rightward columns to include
-in width (default = 0)."
-  (let ((printer (or (ses-col-printer ses--col) ses--default-printer))
-       (width   (ses-col-width ses--col))
+in width (default = 0).
+PRINTER is the printer to use for printing the value, default is the
+column printer if any, or the spreadsheet the spreadsheet default
+printer otherwise."
+  (setq printer (or printer  (ses-col-printer ses--col) ses--default-printer))
+  (let ((width   (ses-col-width ses--col))
        half)
     (or fill (setq fill ?\s))
     (or span (setq span 0))
@@ -3815,7 +3864,7 @@ in width (default = 0)."
       (concat half value half
              (if (> (% width 2) 0) (char-to-string fill))))))
 
-(defun ses-center-span (value &optional fill)
+(defun ses-center-span (value &optional fill printer)
   "Print VALUE, centered within the span that starts in the current column
 and continues until the next nonblank column.
 FILL specifies the fill character (default = space)."
@@ -3823,22 +3872,28 @@ FILL specifies the fill character (default = space)."
     (while (and (< end ses--numcols)
                (memq (ses-cell-value ses--row end) '(nil *skip*)))
       (setq end (1+ end)))
-    (ses-center value (- end ses--col 1) fill)))
+    (ses-center value (- end ses--col 1) fill printer)))
 
-(defun ses-dashfill (value &optional span)
+(defun ses-dashfill (value &optional span printer)
   "Print VALUE centered using dashes.
 SPAN indicates how many rightward columns to include in width (default = 0)."
-  (ses-center value span ?-))
+  (ses-center value span ?- printer))
 
-(defun ses-dashfill-span (value)
+(defun ses-dashfill-span (value &optional printer)
   "Print VALUE, centered using dashes within the span that starts in the
 current column and continues until the next nonblank column."
-  (ses-center-span value ?-))
+  (ses-center-span value ?- printer))
 
-(defun ses-tildefill-span (value)
+(defun ses-tildefill-span (value &optional printer)
   "Print VALUE, centered using tildes within the span that starts in the
 current column and continues until the next nonblank column."
-  (ses-center-span value ?~))
+  (ses-center-span value ?~ printer))
+
+(defun ses-prin1 (value)
+  "Shorthand for  '(prin1-to-string VALUE t)'.
+Usefull to handle the default behaviour in custom lambda based
+printer functions."
+  (prin1-to-string value t))
 
 (defun ses-unsafe (_value)
   "Substitute for an unsafe formula or printer."



reply via email to

[Prev in Thread] Current Thread [Next in Thread]