1;;; cust-print.el --- handles print-level and print-circle 2 3;; Copyright (C) 1992, 2001, 2002, 2003, 2004, 2005, 4;; 2006, 2007 Free Software Foundation, Inc. 5 6;; Author: Daniel LaLiberte <liberte@holonexus.org> 7;; Adapted-By: ESR 8;; Keywords: extensions 9 10;; LCD Archive Entry: 11;; cust-print|Daniel LaLiberte|liberte@holonexus.org 12;; |Handle print-level, print-circle and more. 13 14;; This file is part of GNU Emacs. 15 16;; GNU Emacs is free software; you can redistribute it and/or modify 17;; it under the terms of the GNU General Public License as published by 18;; the Free Software Foundation; either version 2, or (at your option) 19;; any later version. 20 21;; GNU Emacs is distributed in the hope that it will be useful, 22;; but WITHOUT ANY WARRANTY; without even the implied warranty of 23;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 24;; GNU General Public License for more details. 25 26;; You should have received a copy of the GNU General Public License 27;; along with GNU Emacs; see the file COPYING. If not, write to the 28;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 29;; Boston, MA 02110-1301, USA. 30 31;;; Commentary: 32 33;; This package provides a general print handler for prin1 and princ 34;; that supports print-level and print-circle, and by the way, 35;; print-length since the standard routines are being replaced. Also, 36;; to print custom types constructed from lists and vectors, use 37;; custom-print-list and custom-print-vector. See the documentation 38;; strings of these variables for more details. 39 40;; If the results of your expressions contain circular references to 41;; other parts of the same structure, the standard Emacs print 42;; subroutines may fail to print with an untrappable error, 43;; "Apparently circular structure being printed". If you only use cdr 44;; circular lists (where cdrs of lists point back; what is the right 45;; term here?), you can limit the length of printing with 46;; print-length. But car circular lists and circular vectors generate 47;; the above mentioned error in Emacs version 18. Version 48;; 19 supports print-level, but it is often useful to get a better 49;; print representation of circular and shared structures; the print-circle 50;; option may be used to print more concise representations. 51 52;; There are three main ways to use this package. First, you may 53;; replace prin1, princ, and some subroutines that use them by calling 54;; install-custom-print so that any use of these functions in 55;; Lisp code will be affected; you can later reset with 56;; uninstall-custom-print. Second, you may temporarily install 57;; these functions with the macro with-custom-print. Third, you 58;; could call the custom routines directly, thus only affecting the 59;; printing that requires them. 60 61;; Note that subroutines which call print subroutines directly will 62;; not use the custom print functions. In particular, the evaluation 63;; functions like eval-region call the print subroutines directly. 64;; Therefore, if you evaluate (aref circ-list 0), where circ-list is a 65;; circular list rather than an array, aref calls error directly which 66;; will jump to the top level instead of printing the circular list. 67 68;; Uninterned symbols are recognized when print-circle is non-nil, 69;; but they are not printed specially here. Use the cl-packages package 70;; to print according to print-gensym. 71 72;; Obviously the right way to implement this custom-print facility is 73;; in C or with hooks into the standard printer. Please volunteer 74;; since I don't have the time or need. More CL-like printing 75;; capabilities could be added in the future. 76 77;; Implementation design: we want to use the same list and vector 78;; processing algorithm for all versions of prin1 and princ, since how 79;; the processing is done depends on print-length, print-level, and 80;; print-circle. For circle printing, a preprocessing step is 81;; required before the final printing. Thanks to Jamie Zawinski 82;; for motivation and algorithms. 83 84 85;;; Code: 86 87(defgroup cust-print nil 88 "Handles print-level and print-circle." 89 :prefix "print-" 90 :group 'lisp 91 :group 'extensions) 92 93;; If using cl-packages: 94 95'(defpackage "cust-print" 96 (:nicknames "CP" "custom-print") 97 (:use "el") 98 (:export 99 print-level 100 print-circle 101 102 custom-print-install 103 custom-print-uninstall 104 custom-print-installed-p 105 with-custom-print 106 107 custom-prin1 108 custom-princ 109 custom-prin1-to-string 110 custom-print 111 custom-format 112 custom-message 113 custom-error 114 115 custom-printers 116 add-custom-printer 117 )) 118 119'(in-package cust-print) 120 121;; Emacs 18 doesn't have defalias. 122;; Provide def for byte compiler. 123(eval-and-compile 124 (or (fboundp 'defalias) (fset 'defalias 'fset))) 125 126 127;; Variables: 128;;========================================================= 129 130;;(defvar print-length nil 131;; "*Controls how many elements of a list, at each level, are printed. 132;;This is defined by emacs.") 133 134(defcustom print-level nil 135 "*Controls how many levels deep a nested data object will print. 136 137If nil, printing proceeds recursively and may lead to 138max-lisp-eval-depth being exceeded or an error may occur: 139`Apparently circular structure being printed.' 140Also see `print-length' and `print-circle'. 141 142If non-nil, components at levels equal to or greater than `print-level' 143are printed simply as `#'. The object to be printed is at level 0, 144and if the object is a list or vector, its top-level components are at 145level 1." 146 :type '(choice (const nil) integer) 147 :group 'cust-print) 148 149 150(defcustom print-circle nil 151 "*Controls the printing of recursive structures. 152 153If nil, printing proceeds recursively and may lead to 154`max-lisp-eval-depth' being exceeded or an error may occur: 155\"Apparently circular structure being printed.\" Also see 156`print-length' and `print-level'. 157 158If non-nil, shared substructures anywhere in the structure are printed 159with `#N=' before the first occurrence (in the order of the print 160representation) and `#N#' in place of each subsequent occurrence, 161where N is a positive decimal integer. 162 163There is no way to read this representation in standard Emacs, 164but if you need to do so, try the cl-read.el package." 165 :type 'boolean 166 :group 'cust-print) 167 168 169(defcustom custom-print-vectors nil 170 "*Non-nil if printing of vectors should obey print-level and print-length. 171 172For Emacs 18, setting print-level, or adding custom print list or 173vector handling will make this happen anyway. Emacs 19 obeys 174print-level, but not for vectors." 175 :type 'boolean 176 :group 'cust-print) 177 178 179;; Custom printers 180;;========================================================== 181 182(defvar custom-printers nil 183 ;; e.g. '((symbolp . pkg::print-symbol)) 184 "An alist for custom printing of any type. 185Pairs are of the form (PREDICATE . PRINTER). If PREDICATE is true 186for an object, then PRINTER is called with the object. 187PRINTER should print to `standard-output' using cust-print-original-princ 188if the standard printer is sufficient, or cust-print-prin for complex things. 189The PRINTER should return the object being printed. 190 191Don't modify this variable directly. Use `add-custom-printer' and 192`delete-custom-printer'") 193;; Should cust-print-original-princ and cust-print-prin be exported symbols? 194;; Or should the standard printers functions be replaced by 195;; CP ones in Emacs Lisp so that CP internal functions need not be called? 196 197(defun add-custom-printer (pred printer) 198 "Add a pair of PREDICATE and PRINTER to `custom-printers'. 199Any pair that has the same PREDICATE is first removed." 200 (setq custom-printers (cons (cons pred printer) 201 (delq (assq pred custom-printers) 202 custom-printers))) 203 ;; Rather than updating here, we could wait until cust-print-top-level is called. 204 (cust-print-update-custom-printers)) 205 206(defun delete-custom-printer (pred) 207 "Delete the custom printer associated with PREDICATE." 208 (setq custom-printers (delq (assq pred custom-printers) 209 custom-printers)) 210 (cust-print-update-custom-printers)) 211 212 213(defun cust-print-use-custom-printer (object) 214 ;; Default function returns nil. 215 nil) 216 217(defun cust-print-update-custom-printers () 218 ;; Modify the definition of cust-print-use-custom-printer 219 (defalias 'cust-print-use-custom-printer 220 ;; We don't really want to require the byte-compiler. 221 ;; (byte-compile 222 `(lambda (object) 223 (cond 224 ,@(mapcar (function 225 (lambda (pair) 226 `((,(car pair) object) 227 (,(cdr pair) object)))) 228 custom-printers) 229 ;; Otherwise return nil. 230 (t nil) 231 )) 232 ;; ) 233 )) 234 235 236;; Saving and restoring emacs printing routines. 237;;==================================================== 238 239(defun cust-print-set-function-cell (symbol-pair) 240 (defalias (car symbol-pair) 241 (symbol-function (car (cdr symbol-pair))))) 242 243(defun cust-print-original-princ (object &optional stream)) ; dummy def 244 245;; Save emacs routines. 246(if (not (fboundp 'cust-print-original-prin1)) 247 (mapcar 'cust-print-set-function-cell 248 '((cust-print-original-prin1 prin1) 249 (cust-print-original-princ princ) 250 (cust-print-original-print print) 251 (cust-print-original-prin1-to-string prin1-to-string) 252 (cust-print-original-format format) 253 (cust-print-original-message message) 254 (cust-print-original-error error)))) 255 256 257(defun custom-print-install () 258 "Replace print functions with general, customizable, Lisp versions. 259The Emacs subroutines are saved away, and you can reinstall them 260by running `custom-print-uninstall'." 261 (interactive) 262 (mapcar 'cust-print-set-function-cell 263 '((prin1 custom-prin1) 264 (princ custom-princ) 265 (print custom-print) 266 (prin1-to-string custom-prin1-to-string) 267 (format custom-format) 268 (message custom-message) 269 (error custom-error) 270 )) 271 t) 272 273(defun custom-print-uninstall () 274 "Reset print functions to their Emacs subroutines." 275 (interactive) 276 (mapcar 'cust-print-set-function-cell 277 '((prin1 cust-print-original-prin1) 278 (princ cust-print-original-princ) 279 (print cust-print-original-print) 280 (prin1-to-string cust-print-original-prin1-to-string) 281 (format cust-print-original-format) 282 (message cust-print-original-message) 283 (error cust-print-original-error) 284 )) 285 t) 286 287(defalias 'custom-print-funcs-installed-p 'custom-print-installed-p) 288(defun custom-print-installed-p () 289 "Return t if custom-print is currently installed, nil otherwise." 290 (eq (symbol-function 'custom-prin1) (symbol-function 'prin1))) 291 292(put 'with-custom-print-funcs 'edebug-form-spec '(body)) 293(put 'with-custom-print 'edebug-form-spec '(body)) 294 295(defalias 'with-custom-print-funcs 'with-custom-print) 296(defmacro with-custom-print (&rest body) 297 "Temporarily install the custom print package while executing BODY." 298 `(unwind-protect 299 (progn 300 (custom-print-install) 301 ,@body) 302 (custom-print-uninstall))) 303 304 305;; Lisp replacements for prin1 and princ, and for some subrs that use them 306;;=============================================================== 307;; - so far only the printing and formatting subrs. 308 309(defun custom-prin1 (object &optional stream) 310 "Output the printed representation of OBJECT, any Lisp object. 311Quoting characters are printed when needed to make output that `read' 312can handle, whenever this is possible. 313Output stream is STREAM, or value of `standard-output' (which see). 314 315This is the custom-print replacement for the standard `prin1'. It 316uses the appropriate printer depending on the values of `print-level' 317and `print-circle' (which see)." 318 (cust-print-top-level object stream 'cust-print-original-prin1)) 319 320 321(defun custom-princ (object &optional stream) 322 "Output the printed representation of OBJECT, any Lisp object. 323No quoting characters are used; no delimiters are printed around 324the contents of strings. 325Output stream is STREAM, or value of `standard-output' (which see). 326 327This is the custom-print replacement for the standard `princ'." 328 (cust-print-top-level object stream 'cust-print-original-princ)) 329 330 331(defun custom-prin1-to-string (object &optional noescape) 332 "Return a string containing the printed representation of OBJECT, 333any Lisp object. Quoting characters are used when needed to make output 334that `read' can handle, whenever this is possible, unless the optional 335second argument NOESCAPE is non-nil. 336 337This is the custom-print replacement for the standard `prin1-to-string'." 338 (let ((buf (get-buffer-create " *custom-print-temp*"))) 339 ;; We must erase the buffer before printing in case an error 340 ;; occurred during the last prin1-to-string and we are in debugger. 341 (save-excursion 342 (set-buffer buf) 343 (erase-buffer)) 344 ;; We must be in the current-buffer when the print occurs. 345 (if noescape 346 (custom-princ object buf) 347 (custom-prin1 object buf)) 348 (save-excursion 349 (set-buffer buf) 350 (buffer-string) 351 ;; We could erase the buffer again, but why bother? 352 ))) 353 354 355(defun custom-print (object &optional stream) 356 "Output the printed representation of OBJECT, with newlines around it. 357Quoting characters are printed when needed to make output that `read' 358can handle, whenever this is possible. 359Output stream is STREAM, or value of `standard-output' (which see). 360 361This is the custom-print replacement for the standard `print'." 362 (cust-print-original-princ "\n" stream) 363 (custom-prin1 object stream) 364 (cust-print-original-princ "\n" stream)) 365 366 367(defun custom-format (fmt &rest args) 368 "Format a string out of a control-string and arguments. 369The first argument is a control string. It, and subsequent arguments 370substituted into it, become the value, which is a string. 371It may contain %s or %d or %c to substitute successive following arguments. 372%s means print an argument as a string, %d means print as number in decimal, 373%c means print a number as a single character. 374The argument used by %s must be a string or a symbol; 375the argument used by %d, %b, %o, %x or %c must be a number. 376 377This is the custom-print replacement for the standard `format'. It 378calls the Emacs `format' after first making strings for list, 379vector, or symbol args. The format specification for such args should 380be `%s' in any case, so a string argument will also work. The string 381is generated with `custom-prin1-to-string', which quotes quotable 382characters." 383 (apply 'cust-print-original-format fmt 384 (mapcar (function (lambda (arg) 385 (if (or (listp arg) (vectorp arg) (symbolp arg)) 386 (custom-prin1-to-string arg) 387 arg))) 388 args))) 389 390 391(defun custom-message (fmt &rest args) 392 "Print a one-line message at the bottom of the screen. 393The first argument is a control string. 394It may contain %s or %d or %c to print successive following arguments. 395%s means print an argument as a string, %d means print as number in decimal, 396%c means print a number as a single character. 397The argument used by %s must be a string or a symbol; 398the argument used by %d or %c must be a number. 399 400This is the custom-print replacement for the standard `message'. 401See `custom-format' for the details." 402 ;; It doesn't work to princ the result of custom-format as in: 403 ;; (cust-print-original-princ (apply 'custom-format fmt args)) 404 ;; because the echo area requires special handling 405 ;; to avoid duplicating the output. 406 ;; cust-print-original-message does it right. 407 (apply 'cust-print-original-message fmt 408 (mapcar (function (lambda (arg) 409 (if (or (listp arg) (vectorp arg) (symbolp arg)) 410 (custom-prin1-to-string arg) 411 arg))) 412 args))) 413 414 415(defun custom-error (fmt &rest args) 416 "Signal an error, making error message by passing all args to `format'. 417 418This is the custom-print replacement for the standard `error'. 419See `custom-format' for the details." 420 (signal 'error (list (apply 'custom-format fmt args)))) 421 422 423 424;; Support for custom prin1 and princ 425;;========================================= 426 427;; Defs to quiet byte-compiler. 428(defvar circle-table) 429(defvar cust-print-current-level) 430 431(defun cust-print-original-printer (object)) ; One of the standard printers. 432(defun cust-print-low-level-prin (object)) ; Used internally. 433(defun cust-print-prin (object)) ; Call this to print recursively. 434 435(defun cust-print-top-level (object stream emacs-printer) 436 ;; Set up for printing. 437 (let ((standard-output (or stream standard-output)) 438 ;; circle-table will be non-nil if anything is circular. 439 (circle-table (and print-circle 440 (cust-print-preprocess-circle-tree object))) 441 (cust-print-current-level (or print-level -1))) 442 443 (defalias 'cust-print-original-printer emacs-printer) 444 (defalias 'cust-print-low-level-prin 445 (cond 446 ((or custom-printers 447 circle-table 448 print-level ; comment out for version 19 449 ;; Emacs doesn't use print-level or print-length 450 ;; for vectors, but custom-print can. 451 (if custom-print-vectors 452 (or print-level print-length))) 453 'cust-print-print-object) 454 (t 'cust-print-original-printer))) 455 (defalias 'cust-print-prin 456 (if circle-table 'cust-print-print-circular 'cust-print-low-level-prin)) 457 458 (cust-print-prin object) 459 object)) 460 461 462(defun cust-print-print-object (object) 463 ;; Test object type and print accordingly. 464 ;; Could be called as either cust-print-low-level-prin or cust-print-prin. 465 (cond 466 ((null object) (cust-print-original-printer object)) 467 ((cust-print-use-custom-printer object) object) 468 ((consp object) (cust-print-list object)) 469 ((vectorp object) (cust-print-vector object)) 470 ;; All other types, just print. 471 (t (cust-print-original-printer object)))) 472 473 474(defun cust-print-print-circular (object) 475 ;; Printer for `prin1' and `princ' that handles circular structures. 476 ;; If OBJECT appears multiply, and has not yet been printed, 477 ;; prefix with label; if it has been printed, use `#N#' instead. 478 ;; Otherwise, print normally. 479 (let ((tag (assq object circle-table))) 480 (if tag 481 (let ((id (cdr tag))) 482 (if (> id 0) 483 (progn 484 ;; Already printed, so just print id. 485 (cust-print-original-princ "#") 486 (cust-print-original-princ id) 487 (cust-print-original-princ "#")) 488 ;; Not printed yet, so label with id and print object. 489 (setcdr tag (- id)) ; mark it as printed 490 (cust-print-original-princ "#") 491 (cust-print-original-princ (- id)) 492 (cust-print-original-princ "=") 493 (cust-print-low-level-prin object) 494 )) 495 ;; Not repeated in structure. 496 (cust-print-low-level-prin object)))) 497 498 499;;================================================ 500;; List and vector processing for print functions. 501 502(defun cust-print-list (list) 503 ;; Print a list using print-length, print-level, and print-circle. 504 (if (= cust-print-current-level 0) 505 (cust-print-original-princ "#") 506 (let ((cust-print-current-level (1- cust-print-current-level))) 507 (cust-print-original-princ "(") 508 (let ((length (or print-length 0))) 509 510 ;; Print the first element always (even if length = 0). 511 (cust-print-prin (car list)) 512 (setq list (cdr list)) 513 (if list (cust-print-original-princ " ")) 514 (setq length (1- length)) 515 516 ;; Print the rest of the elements. 517 (while (and list (/= 0 length)) 518 (if (and (listp list) 519 (not (assq list circle-table))) 520 (progn 521 (cust-print-prin (car list)) 522 (setq list (cdr list))) 523 524 ;; cdr is not a list, or it is in circle-table. 525 (cust-print-original-princ ". ") 526 (cust-print-prin list) 527 (setq list nil)) 528 529 (setq length (1- length)) 530 (if list (cust-print-original-princ " "))) 531 532 (if (and list (= length 0)) (cust-print-original-princ "...")) 533 (cust-print-original-princ ")")))) 534 list) 535 536 537(defun cust-print-vector (vector) 538 ;; Print a vector according to print-length, print-level, and print-circle. 539 (if (= cust-print-current-level 0) 540 (cust-print-original-princ "#") 541 (let ((cust-print-current-level (1- cust-print-current-level)) 542 (i 0) 543 (len (length vector))) 544 (cust-print-original-princ "[") 545 546 (if print-length 547 (setq len (min print-length len))) 548 ;; Print the elements 549 (while (< i len) 550 (cust-print-prin (aref vector i)) 551 (setq i (1+ i)) 552 (if (< i (length vector)) (cust-print-original-princ " "))) 553 554 (if (< i (length vector)) (cust-print-original-princ "...")) 555 (cust-print-original-princ "]") 556 )) 557 vector) 558 559 560 561;; Circular structure preprocessing 562;;================================== 563 564(defun cust-print-preprocess-circle-tree (object) 565 ;; Fill up the table. 566 (let (;; Table of tags for each object in an object to be printed. 567 ;; A tag is of the form: 568 ;; ( <object> <nil-t-or-id-number> ) 569 ;; The id-number is generated after the entire table has been computed. 570 ;; During walk through, the real circle-table lives in the cdr so we 571 ;; can use setcdr to add new elements instead of having to setq the 572 ;; variable sometimes (poor man's locf). 573 (circle-table (list nil))) 574 (cust-print-walk-circle-tree object) 575 576 ;; Reverse table so it is in the order that the objects will be printed. 577 ;; This pass could be avoided if we always added to the end of the 578 ;; table with setcdr in walk-circle-tree. 579 (setcdr circle-table (nreverse (cdr circle-table))) 580 581 ;; Walk through the table, assigning id-numbers to those 582 ;; objects which will be printed using #N= syntax. Delete those 583 ;; objects which will be printed only once (to speed up assq later). 584 (let ((rest circle-table) 585 (id -1)) 586 (while (cdr rest) 587 (let ((tag (car (cdr rest)))) 588 (cond ((cdr tag) 589 (setcdr tag id) 590 (setq id (1- id)) 591 (setq rest (cdr rest))) 592 ;; Else delete this object. 593 (t (setcdr rest (cdr (cdr rest)))))) 594 )) 595 ;; Drop the car. 596 (cdr circle-table) 597 )) 598 599 600 601(defun cust-print-walk-circle-tree (object) 602 (let (read-equivalent-p tag) 603 (while object 604 (setq read-equivalent-p 605 (or (numberp object) 606 (and (symbolp object) 607 ;; Check if it is uninterned. 608 (eq object (intern-soft (symbol-name object))))) 609 tag (and (not read-equivalent-p) 610 (assq object (cdr circle-table)))) 611 (cond (tag 612 ;; Seen this object already, so note that. 613 (setcdr tag t)) 614 615 ((not read-equivalent-p) 616 ;; Add a tag for this object. 617 (setcdr circle-table 618 (cons (list object) 619 (cdr circle-table))))) 620 (setq object 621 (cond 622 (tag ;; No need to descend since we have already. 623 nil) 624 625 ((consp object) 626 ;; Walk the car of the list recursively. 627 (cust-print-walk-circle-tree (car object)) 628 ;; But walk the cdr with the above while loop 629 ;; to avoid problems with max-lisp-eval-depth. 630 ;; And it should be faster than recursion. 631 (cdr object)) 632 633 ((vectorp object) 634 ;; Walk the vector. 635 (let ((i (length object)) 636 (j 0)) 637 (while (< j i) 638 (cust-print-walk-circle-tree (aref object j)) 639 (setq j (1+ j)))))))))) 640 641 642;; Example. 643;;======================================= 644 645'(progn 646 (progn 647 ;; Create some circular structures. 648 (setq circ-sym (let ((x (make-symbol "FOO"))) (list x x))) 649 (setq circ-list (list 'a 'b (vector 1 2 3 4) 'd 'e 'f)) 650 (setcar (nthcdr 3 circ-list) circ-list) 651 (aset (nth 2 circ-list) 2 circ-list) 652 (setq dotted-circ-list (list 'a 'b 'c)) 653 (setcdr (cdr (cdr dotted-circ-list)) dotted-circ-list) 654 (setq circ-vector (vector 1 2 3 4 (list 'a 'b 'c 'd) 6 7)) 655 (aset circ-vector 5 (make-symbol "-gensym-")) 656 (setcar (cdr (aref circ-vector 4)) (aref circ-vector 5)) 657 nil) 658 659 (install-custom-print) 660 ;; (setq print-circle t) 661 662 (let ((print-circle t)) 663 (or (equal (prin1-to-string circ-list) "#1=(a b [1 2 #1# 4] #1# e f)") 664 (error "circular object with array printing"))) 665 666 (let ((print-circle t)) 667 (or (equal (prin1-to-string dotted-circ-list) "#1=(a b c . #1#)") 668 (error "circular object with array printing"))) 669 670 (let* ((print-circle t) 671 (x (list 'p 'q)) 672 (y (list (list 'a 'b) x 'foo x))) 673 (setcdr (cdr (cdr (cdr y))) (cdr y)) 674 (or (equal (prin1-to-string y) "((a b) . #1=(#2=(p q) foo #2# . #1#))" 675 ) 676 (error "circular list example from CL manual"))) 677 678 (let ((print-circle nil)) 679 ;; cl-packages.el is required to print uninterned symbols like #:FOO. 680 ;; (require 'cl-packages) 681 (or (equal (prin1-to-string circ-sym) "(#:FOO #:FOO)") 682 (error "uninterned symbols in list"))) 683 (let ((print-circle t)) 684 (or (equal (prin1-to-string circ-sym) "(#1=FOO #1#)") 685 (error "circular uninterned symbols in list"))) 686 687 (uninstall-custom-print) 688 ) 689 690(provide 'cust-print) 691 692;;; arch-tag: 3a5a8650-622c-48c4-87d8-e01bf72ec580 693;;; cust-print.el ends here 694