LISPUSER

Common Lisp – 小物プログラムLisp isn't a language, it's a building material.

(Top Page) (Lisp Memo)

はじめに

日常的に使っている小物 Lisp プログラムを紹介します.

CVS や Subversion や darcs の更新

まぁ,shell でかけば一瞬ですが.

for i in `find . -name "_darcs"; do (cd $i/..; darcs pull); done

とかと同等なんですがね….最近は Subversion リポジトリで公開されている ものは svk でローカルにミラーしていじってます.

#!/usr/bin/env clisp

#+clisp
(eval-when (:execute :load-toplevel) (setf *load-compiling* t))
#-clisp
(error "sorry. this script need clisp 2.36 or later.")

(defpackage :script.vc-auto-update (:use :cl :fad))
(in-package :script.vc-auto-update)

(defparameter *dirs* '(#p"~/lisp" #p"~/local/darcs" #p"~/local/site-lisp"))
(defparameter *hold* '(;; obsolete
                       "odcl" "imho" "uncommonsql" "uncommonxml"
                       "osicat"
                       ;; hold
                       "maxima" "lispuser" "blog"))

(defun check (path)
  (let ((name (car (last (pathname-directory path)))))
    (loop for e in *hold*
          never (string= e name))))

(defun guess-vc-type (path)
  (cond ((fad:directory-exists-p (merge-pathnames "CVS/" path))
         :cvs)
        ((fad:directory-exists-p (merge-pathnames ".svn/" path))
         :svn)
        ((fad:directory-exists-p (merge-pathnames "_darcs/" path))
         :darcs)))

(defun auto-update-p (path)
  (guess-vc-type path))
  
(defun auto-update (path &key (dry-run nil))
  (format *standard-output* "update: ~A~%" path)
  (let ((type (guess-vc-type path)))
    (ext:cd path)
    (multiple-value-bind (cmd args)
        (case type
          (:cvs
            (values "cvs" '("-z9" "update" "-dP")))
          (:svn
            (values "svn" '("update")))
          (:darcs
            (values "darcs" '("pull"))))
      (format *standard-output* " => ~A: ~A, ~A~%" type cmd args)
      (unless dry-run
        (ext:run-program cmd :arguments args)))))

(defun walk-vc-directory (path fn &key test)
  (cond ((funcall test path)
         (when (check path) (funcall fn path)))
        (t
         (dolist (e (fad:list-directory path))
           (when (fad:directory-pathname-p e)
             (walk-vc-directory e fn :test test))))))

(defun main (*args*)
  (let ((fn (cond ((find "print" *args* :test #'string=)
                   (lambda (path) (auto-update path :dry-run t)))
                  (t
                   (lambda (path) (auto-update path))))))
    (dolist (d *dirs*)
      (walk-vc-directory d fn :test #'auto-update-p))))
  
(main ext:*args*)

PDF へ書き込み

CL-PDF には PDF 生成機能のほかに pdf-parser がついており,PDF ファイルに書き込みができます.

;; 最小の例
(pdf:with-existing-document (#P"/tmp/original.pdf")
  (pdf:with-existing-page (0) ;; 最初のページ
    (let ((font (pdf:get-font "Helvetica")))
      (pdf:insert-original-page-content)
      (pdf:set-font font 16.0) ;; サイズ指定
      (pdf:move-text 100 100)  ;; 位置指定
      (pdf:draw-text "Comment Text")))
  (pdf:write-document #P"/tmp/modified.pdf"))

これは,たとえばコメント文字列をあとで PDF に反映したり,オーダーフォー ムに書き込んだりと時々役に立ちます.↓の URL にページ番号を振る例があり ます.

http://foldr.org/~michaelw/log/programming/lisp/add-pdf-page-numbers

ドキュメント目録 HTML 作成

ローカルに置いてあるドキュメントのインデックス HTML 作成用.

mkindex.lisp

スクリプトファイル本体.ライブラリはイメージに取り込んであります. clisp 専用なのは,cron で呼び出すスクリプティング用途がメインなのでフッ トプリントの軽い clisp のみとしてあるだけで,深い意図はありません.

#!/usr/bin/env clisp

#+clisp
(eval-when (:execute :load-toplevel) (setf *load-compiling* t))
#-clisp
(error "sorry. this script need clisp 2.35 or later.")

(defpackage :mkindex (:use :cl :indexer))
(in-package :mkindex)

(define-root (merge-pathnames "doc" (user-homedir-pathname)))

(define-category "Lisp Resources"
    :link '(("Common Lisp Hyper Spec" "./HyperSpec/Front/index.htm")
          ("Common Lisp The Language 2nd Edition" "./cltl/cltl2.html")
          ("Practical Common Lisp" "./web/www.gigamonkeys.com/book/index.html")
          ("Paul Graham" "./web/www.paulgraham.com/index.html")
          ("Dream Songs" "./web/www.dreamsongs.com/index.html")
          ("Pascal Contanza" "./web/p-cos.net/index.html")
          ("Structure and Interpretation of Computer Programms" "./web/www-mitpress.mit.edu/sicp/index.html")
          ("SICM" "web/swiss.csail.mit.edu/~gjs/6946/sicm-html/index.html")
          ))

(define-category "Other Documents"
  :link '(("PostgreSQL 8.1 (ja)" "./postgres/index.html")
        ("Statistics with R" "./web/zoonek2.free.fr/UNIX/48_R/all.html")
        ("Perl and Shift_JIS" "http://homepage1.nifty.com/nomenclator/perl/index.htm")
        ("Perl Hints" "http://www.geocities.co.jp/SiliconValley-Oakland/4080/")
        ("Perl and Unicode" "http://www.lr.pi.titech.ac.jp/~abekawa/perl/index.html")
        ))

(define-category "Maxima Documents"
    :pred (filter :directory "lisp" :filetype "pdf"))

(define-category "PDF Documents"
    :pred (filter :directory "ebook" :filetype "pdf"))

(define-category "CHM Documents"
    :pred (filter :directory "ebook" :filetype "chm"))

(generate-index)

indexer.lisp

ドキュメント数が増えてきたので JavaScript でソート機能をつけました.

#!/usr/bin/env clisp

#+clisp
(eval-when (:execute :load-toplevel) (setf *load-compiling* t))
#-clisp
(error "sorry. this script need clisp 2.35 or later.")

(defpackage :indexer (:use :cl :cl-who :fad :json)
  (:export #:define-root #:define-category #:generate-index #:filter))
(in-package :indexer)

(defclass <node> ()
  ((name :accessor name-of :initarg :name :initform nil)
   (path :accessor path-of :initarg :path)))

(defclass <file-node> (<node>) ())
(defclass <uri-node> (<node>) ())

(defclass <category> ()
  ((name  :accessor name-of  :initarg :name  :initform nil)
   (nodes :accessor nodes-of :initarg :nodes :initform nil)
   (pred  :accessor pred-of  :initarg :pred  :initform nil)))

(defparameter *categories* nil)
(defparameter *nodes* nil)
(defparameter *root* nil)

(defmethod make-instance :around ((class (eql '<node>)) &rest initargs &key name path &allow-other-keys)
  "指定されたパスが URI スキーマか,ファイルパスかで実際に生成するクラスの型を替える"
  (declare (ignorable initargs))
  (cond ((and (stringp path) (search "://" path))
       (make-instance '<uri-node> :name name :path path))
      (t
       (make-instance '<file-node> :name name :path (merge-pathnames path *root*)))))

(defmethod href-of ((self <file-node>))
  "ハイパーリンク用の文字列を構築する"
  (format nil "./~A" 
        (cl-who:escape-string-minimal-plus-quotes
         (subseq (format nil "~A" (path-of self)) (length (format nil "~A" *root*))))))

(defmethod href-of ((self <node>))
  "ハイパーリンク用の文字列を構築する"
  (format nil "~A" 
        (cl-who:escape-string-minimal-plus-quotes
         (subseq (format nil "~A" (path-of self)) (length (format nil "~A" *root*))))))

(defmethod title-of ((self <node>))
  (or (name-of self) (pathname-name (path-of self))))

(defmethod mtime-of ((self <file-node>))
  "ファイルノードは mtime を表示する"
  ;; (format *error-output* "path: ~A~%" (path-of self))
  (multiple-value-bind (sec min hour day month year _ _ _)
      (decode-universal-time (ext:file-stat-mtime (ext:file-stat (path-of self))))
    (declare (ignorable _))
    (format nil "~4,'0D/~2,'0D/~2,'0D ~2,'0D:~2,'0D:~2,'0D" year month day hour min sec)))

(defmethod mtime-of ((self <node>))
  "ファイル以外のノードは時間を持たない"
  "none")

(defmethod render-nodes (name lst stream)
  "ノードを表示する"
  (format stream "contents[~S] = " name)
  (json:encode-json
   (mapcar #'(lambda (self)
             (let ((h (make-hash-table)))
               (setf (gethash "title" h) (title-of self)
                     (gethash "link" h) (href-of self)
                     (gethash "time" h) (mtime-of self))
               h))
         lst)
   stream)
  (write-line ";" stream))

(defmethod render ((self <category>) stream)
  "カテゴリを表示する"
  (cl-who:with-html-output (s stream :indent t)
    (:div
     (:script (render-nodes (name-of self) (nodes-of self) s))
     (:h2 (str (name-of self)))
     (:table :id (name-of self) :width "100%" :align "center"
       (:tr
      (:th :width "80%" :onclick "by_name(this)" "Title") (:th :onclick "by_date(this)" "Date"))))))

(defmethod sort-nodes ((self <category>))
  (let ((nodes (sort (nodes-of self)
                   #'string-lessp
                   :key #'(lambda (node)
                            (pathname-name (path-of node))))))
    (setf (nodes-of self) nodes)))

(defun define-root (root)
  "ルート項目のパスを設定する"
  (setf *root* (fad:pathname-as-directory root)))

(defun define-category (name &key link pred)
  ;; (assert (or link pred) "must be specified :link or :pred")
  (flet ((link->node (link)
         (loop for l in link
                 collect (make-instance '<node>
                                      :name (first l)
                                      :path (second l)))))
    (let ((category
         (cond (pred  (make-instance '<category> :name name :pred pred))
               (link  (make-instance '<category> :name name :nodes (link->node link))))))
      (pushnew category *categories*))))

(defun register-node (path)
  (let ((node (make-instance '<node> :path path)))
    (push node *nodes*)
    (dolist (category *categories*)
      (when (and (pred-of category) (funcall (pred-of category) path))
      (pushnew node (nodes-of category))))))

(defun generate-index ()
  (fad:walk-directory *root* #'register-node)
  (loop for category in (reverse *categories*) do (sort-nodes category))
  (with-open-file (css (merge-pathnames "pdms.css" *root*) :direction :output :if-exists :supersede)
    (write-line "h1 { border: gray 3px solid; background: #D0D0D0; padding: 5px; text-align: center; }" css)
    (write-line "h2 { border-bottom: black 1px solid }" css)
    (write-line "div#footer { border-top: black 1px solid; border-bottom: black 1px solid; }" css)
    (write-line "div#footer p { text-align: right; }" css))
  (with-open-file (index (merge-pathnames "index.html" *root*) :direction :output :if-exists :supersede)
    (cl-who:with-html-output (s index :prologue t :indent t)
      (:html
       (:head
      (:title "PDMS - Personal Documents Management System")
      (:link :rel "stylesheet" :href "pdms.css" :type "text/css")
      (:script :language "JavaScript" "
    var contents = Object();
    function build (name) {
        var data   = contents[name];
        var table  = document.all(name);
        var tbody  = table.firstChild;
        var header = table.rows[0];

        for (var i = 0; i < data.length; i++) {
            var d = data[i];
            row = tbody.insertRow();
            for (var j=0; j < header.cells.length; j++) {
                var txt  = header.cells[j].innerText
                var cell = row.insertCell();
                if (/^Title/.test(txt)) {
                    cell.innerHTML = '<a href=\"'+d['link']+'\">'+d['title']+'</a>';
                } else {
                    cell.innerText = d['time'];
                }
            }
        }
    }
    function by_name (obj) {
        var check = function (table) {
            var compare;
            if (table.sort_by_name) {
               table.sort_by_name = false;
               compare = function (a, b) { return (a.title == b.title) ? 0 : (a.title > b.title) ? 1 : -1; }
            } else {
               table.sort_by_name = true;
               compare = function (a, b) { return (a.title == b.title) ? 0 : (a.title < b.title) ? 1 : -1; }
            }
            return compare;
        }
        sort_table(obj, check);
    }
    function by_date (obj) {
        var check = function (table) {
            var compare;
            if (table.sort_by_date) {
               table.sort_by_date = false;
               compare = function (a, b) { return (a.time == b.time) ? 0 : (a.time > b.time) ? 1 : -1; }
            } else {
               table.sort_by_date = true;
               compare = function (a, b) { return (a.time == b.time) ? 0 : (a.time < b.time) ? 1 : -1; }
            }
            return compare;
        }
        sort_table(obj, check);
    }
    function sort_table (obj, check) {
        var table  = obj;
        for (; table.tagName != 'TABLE'; table = table.parentNode)
               ;
        var tbody  = table.firstChild;
        var header = table.rows[0];
        var data   = contents[table.id];
        var comp   = check(table);
        data = data.sort(comp);

        for (var i = 1; i < table.rows.length; i++) {
            var d   = data[i-1];
            var row = table.rows[i].cell;
            for (var j=0; j < header.cells.length; j++) {
                var txt  = header.cells[j].innerText;
                var cell = table.rows[i].cells[j];
                if (/^Title/.test(txt)) {
                    cell.innerHTML = '<a href=\"'+d['link']+'\">'+i+'. '+d['title']+'</a>';
                } else {
                    cell.innerText = d['time'];
                }
            }
        }     
    }
    function init () { for (name in contents) { build(name); }}
"))
       (:body :onLoad "init();"
      (:div :id "header"
            (:h1 "PDMS - Personal Documents Management System"))
      (:div :id "contents"
            (loop for category in (reverse *categories*) do (render category s)))
      (:div :id "footer"
            (:p "Powered by LISP")))))))

(defun filter (&key directory filetype (test #'string-equal))
  (lambda (p)
    (and (find directory (pathname-directory p) :test test)
       (funcall test filetype (pathname-type p)))))

indexer.lisp (旧バージョン)

#!/usr/bin/env clisp

#+clisp
(eval-when (:execute :load-toplevel) (setf *load-compiling* t))
#-clisp
(error "sorry. this script need clisp 2.35 or later.")

(defpackage :indexer (:use :cl :cl-who :fad)
  (:export #:define-root #:define-category #:generate-index #:filter))
(in-package :indexer)

(defclass <node> ()
  ((name :accessor name-of :initarg :name :initform nil)
   (path :accessor path-of :initarg :path)))

(defclass <file-node> (<node>) ())
(defclass <uri-node> (<node>) ())

(defclass <category> ()
  ((name  :accessor name-of  :initarg :name  :initform nil)
   (nodes :accessor nodes-of :initarg :nodes :initform nil)
   (pred  :accessor pred-of  :initarg :pred  :initform nil)))

(defparameter *categories* nil)
(defparameter *nodes* nil)
(defparameter *root* nil)

(defmethod make-instance :around ((class (eql '<node>)) &rest initargs &key name path &allow-other-keys)
  "指定されたパスが URI スキーマか,ファイルパスかで実際に生成するクラスの型を替える"
  (declare (ignorable initargs))
  (cond ((and (stringp path) (search "://" path))
       (make-instance '<uri-node> :name name :path path))
      (t
       (make-instance '<file-node> :name name :path (merge-pathnames path *root*)))))

(defmethod href-of ((self <file-node>))
  "ハイパーリンク用の文字列を構築する"
  (format nil "./~A" 
        (cl-who:escape-string-minimal-plus-quotes
         (subseq (format nil "~A" (path-of self)) (length (format nil "~A" *root*))))))

(defmethod href-of ((self <node>))
  "ハイパーリンク用の文字列を構築する"
  (format nil "~A" 
        (cl-who:escape-string-minimal-plus-quotes
         (subseq (format nil "~A" (path-of self)) (length (format nil "~A" *root*))))))

(defmethod title-of ((self <node>))
  (or (name-of self) (pathname-name (path-of self))))

(defmethod mtime-of ((self <file-node>))
  "ファイルノードは mtime を表示する"
  ;; (format *error-output* "path: ~A~%" (path-of self))
  (multiple-value-bind (sec min hour day month year _ _ _)
      (decode-universal-time (ext:file-stat-mtime (ext:file-stat (path-of self))))
    (declare (ignorable _))
    (format nil " - ~4,'0D/~2,'0D/~2,'0D ~2,'0D:~2,'0D:~2,'0D" year month day hour min sec)))

(defmethod mtime-of ((self <node>))
  "ファイル以外のノードは時間を持たない"
  " - now")

(defmethod render ((self <node>) stream)
  "ノードを表示する"
  (cl-who:with-html-output (s stream :indent t)
    (:li
     (:a :href (href-of self) (str (title-of self)))
     (:small (str (mtime-of self))))))

(defmethod render ((self <category>) stream)
  "カテゴリを表示する"
  (cl-who:with-html-output (s stream :indent t)
    (:div :id (name-of self)
      (:h2 :name (name-of self) :id (name-of self) (str (name-of self)))
      (:ol
       (loop for node in (nodes-of self) do (render node s))))))

(defmethod sort-nodes ((self <category>))
  (let ((nodes (sort (nodes-of self)
                   #'string-lessp
                   :key #'(lambda (node)
                            (pathname-name (path-of node))))))
    (setf (nodes-of self) nodes)))

(defun define-root (root)
  "ルート項目のパスを設定する"
  (setf *root* (fad:pathname-as-directory root)))

(defun define-category (name &key link pred)
  ;; (assert (or link pred) "must be specified :link or :pred")
  (flet ((link->node (link)
         (loop for l in link
                 collect (make-instance '<node>
                                      :name (first l)
                                      :path (second l)))))
    (let ((category
         (cond (pred  (make-instance '<category> :name name :pred pred))
               (link  (make-instance '<category> :name name :nodes (link->node link))))))
      (pushnew category *categories*))))

(defun register-node (path)
  (let ((node (make-instance '<node> :path path)))
    (push node *nodes*)
    (dolist (category *categories*)
      (when (and (pred-of category) (funcall (pred-of category) path))
      (pushnew node (nodes-of category))))))

(defun generate-index ()
  (fad:walk-directory *root* #'register-node)
  (loop for category in (reverse *categories*) do (sort-nodes category))
  (with-open-file (css (merge-pathnames "pdms.css" *root*) :direction :output :if-exists :supersede)
    (write-line "h1 { border: gray 3px solid; background: #D0D0D0; padding: 5px; text-align: center; }" css)
    (write-line "h2 { border-bottom: black 1px solid }" css)
    (write-line "div#footer { border-top: black 1px solid; border-bottom: black 1px solid; }" css)
    (write-line "div#footer p { text-align: right; }" css))
  (with-open-file (index (merge-pathnames "index.html" *root*) :direction :output :if-exists :supersede)
    (cl-who:with-html-output (s index :prologue t :indent t)
      (:html
       (:head
      (:title "PDMS - Personal Documents Management System")
      (:link :rel "stylesheet" :href "pdms.css" :type "text/css"))
       (:body
      (:div :id "header"
            (:h1 "PDMS - Personal Documents Management System"))
      (:div :id "contents"
            (loop for category in (reverse *categories*) do (render category s)))
      (:div :id "footer"
            (:p "Copyright (&copy;) Masayuki Onjo")))))))

(defun filter (&key directory filetype (test #'string-equal))
  (lambda (p)
    (and (find directory (pathname-directory p) :test test)
       (funcall test filetype (pathname-type p)))))

$Last Update: 2006/08/20 3:16:22 $