アクトインディ開発者ブログ

子供とお出かけ情報「いこーよ」を運営する、アクトインディ株式会社の開発者ブログです

CoffeeScript を使って Titanium でアプリを作る (Common Lisp バージョン)

こんにちは、tahara です。

CoffeeScript を使って Titanium でアプリを作るにはいくつか方法があるようです。

弊社ではこのような場合 Common Lisp を使います(個人的に)。

コードは下記のとおり。 もし動かしてみたいという方がいらっしゃるようでしたら、 defparameter しているものを環境に合わせて変更してください。 あと Quicklisp でインストールできるものの他に https://github.com/quek/info.read-eval-print.series-ext も必要になります。

エラーがあれば repl に表示されます。 一度 Titanium Studio から Emulator でアプリを起動していれば、 CoffeeScript の保存で自動的に Eumlator での実行まで行います。 快適です。

;;;; CoffeeScript を使って Titanium で Android アプリを作る
;;;;
;;;; 参考にしたサイト
;;;; http://a-h.parfe.jp/einfach/archives/2011/0106235955.html

(eval-when (:compile-toplevel :load-toplevel :execute)
  (require :alexandria)
  (require :bordeaux-threads)
  (require :cl-ppcre)
  (require :info.read-eval-print.series-ext))

(info.read-eval-print.series-ext:sdefpackage
 :compile-coffee
 (:use :cl))

(in-package :compile-coffee)

(defparameter *builder.py*
  "~/.titanium/mobilesdk/linux/1.8.1/android/builder.py"
  "Titanium のビルドコマンド
iPhone で動かす場合は s/android/iphone/ でいいかもしれない")

(defparameter *project-dir*
  "~/Titanium\\ Studio\\ Workspace/outing-app"
  "Titanium のプロジェクトディレクトリ")

(defparameter *android-sdk*
  "~/local/opt/android-sdk-linux"
  "Android SDKディレクトリ")

(defparameter *interval* 0.3
  "ファイルの変更監視間隔")

(defvar *compile-titanium-process* nil)

(defun escape-sh-arg (arg)
  (ppcre:regex-replace-all " " arg "\\ "))

(defun sh-async (control-string &rest format-arguments)
  (let* ((command (apply #'format nil control-string format-arguments))
         (process (progn (format *terminal-io* "~&~a" command)
                         (sb-ext:run-program "/bin/sh"
                                             (list "-c" command)
                                             :wait nil
                                             :output :stream
                                             :error :stream)))
         (streams (list (sb-ext:process-output process)
                        (sb-ext:process-error process)))
         (threads (labels ((cat (stream)
                             (bordeaux-threads:make-thread
                              (lambda ()
                                (collect-stream
                                 *terminal-io*
                                 (delete #\Return
                                         (scan-stream stream #'read-line))
                                 #'write-line)))))
                    (collect (cat (scan 'list streams))))))
    (bordeaux-threads:make-thread
     (lambda ()
       (let ((exit-code (sb-ext:process-exit-code (sb-ext:process-wait process))))
         (collect-ignore (progn
                           (bordeaux-threads:join-thread (scan 'list threads))
                           (close (scan 'list streams))))
         (if (zerop exit-code)
             (prog1 t (format *terminal-io* "~&ok"))
             nil))))
    process))

(defun sh (control-string &rest format-arguments)
  (let* ((command (apply #'format nil control-string format-arguments))
         (process (progn (format *terminal-io* "~&~a" command)
                         (sb-ext:run-program "/bin/sh"
                                             (list "-c" command)
                                             :wait nil
                                             :output :stream
                                             :error :stream)))
         (streams (list (sb-ext:process-output process)
                        (sb-ext:process-error process)))
         (threads (labels ((cat (stream)
                             (bordeaux-threads:make-thread
                              (lambda ()
                                (collect-stream
                                 *terminal-io*
                                 (delete #\Return
                                         (scan-stream stream #'read-line))
                                 #'write-line)))))
                    (collect (cat (scan 'list streams)))))
         (exit-code (sb-ext:process-exit-code (sb-ext:process-wait process))))
    (collect-ignore (progn
                      (bordeaux-threads:join-thread (scan 'list threads))
                      (close (scan 'list streams))))
    (if (zerop exit-code)
        (prog1 t (format *terminal-io* "~&ok"))
        nil)))

(defun compile-titanium ()
  (when *compile-titanium-process*
    (sb-ext:process-kill *compile-titanium-process*
                         sb-posix:sigterm)
    (setf *compile-titanium-process* nil))
  (setf *compile-titanium-process*
        (sh-async "~a run ~a ~a" *builder.py* *project-dir* *android-sdk*)))

(defun compile-coffee (file)
  (when (sh "coffee -o ~a -c ~a"
            (escape-sh-arg (directory-namestring file))
            (escape-sh-arg (namestring file)))
    (compile-titanium)))

(defun run ()
  (sb-cltl2:compiler-let ((series::*suppress-series-warnings* t))
    (collect-ignore
     (compile-coffee
      (choose-if (complement
                  (lambda (x)
                    (alexandria:starts-with-subseq ".#" (file-namestring x))))
                 (scan-file-change
                  (format nil "~a/**/*.coffee" *project-dir*)
                  :interval *interval*))))))

;; 実行
;; (bordeaux-threads:make-thread #'run :name "compile-coffee")