diff --git a/emacs b/emacs index 2b3f3fe..9992c60 100644 --- a/emacs +++ b/emacs @@ -140,6 +140,15 @@ (require 'mojo) + +;;;;;;;;;;;;;;;; +;; javascript ;; +;;;;;;;;;;;;;;;; + +(autoload 'js2-mode "js2-mode" nil t) +(add-to-list 'auto-mode-alist '("\\.js$" . js2-mode)) + + ;;;;;;;;;;;; ;; markup ;; ;;;;;;;;;;;; @@ -168,11 +177,11 @@ (setq scheme-program-name "~/Projects/elschemo/elschemo") ;; setup slime -;; (require 'slime) -;; (add-hook 'lisp-mode-hook (lambda () (slime-mode t))) -;; (add-hook 'scheme-mode-hook (lambda () (slime-mode t))) -;; (add-hook 'inferior-lisp-mode-hook (lambda () (inferior-slime-mode t))) -;; (add-hook 'inferior-scheme-mode-hook (lambda () (inferior-slime-mode t))) +;(require 'slime) +;(add-hook 'lisp-mode-hook (lambda () (slime-mode t))) +;(add-hook 'scheme-mode-hook (lambda () (slime-mode t))) +;(add-hook 'inferior-lisp-mode-hook (lambda () (inferior-slime-mode t))) +;(add-hook 'inferior-scheme-mode-hook (lambda () (inferior-slime-mode t))) ;; use sbcl for lisp (setq inferior-lisp-program "/usr/bin/sbcl") @@ -247,6 +256,12 @@ (global-set-key "<" 'tagify-region-or-insert-self) (global-set-key "\C-zt" 'tagify-region-or-insert-tag) +;; mojo keyboard shortcuts +(global-set-key [f2] 'mojo-generate-scene) +(global-set-key [f3] 'mojo-emulate) +(global-set-key [f4] 'mojo-package) +(global-set-key [f5] 'mojo-package-install-and-inspect) + ;; XXX:todo need a version of this that inserts a line terminator as well ;; Use C-j! ;;(global-set-key [M-return] 'move-end-of-line-insert-newline) @@ -374,6 +389,8 @@ '(face-font-family-alternatives (quote (("bistream vera sans mono" "courier" "fixed") ("helv" "helvetica" "arial" "fixed")))) '(global-font-lock-mode t nil (font-lock)) '(icicle-reminder-prompt-flag 5) + '(mojo-build-directory "~/Projects/brighthouse/webOS/build") + '(mojo-project-directory "~/Projects/brighthouse/webOS") '(remote-shell-program "/usr/bin/ssh") '(save-place t nil (saveplace)) '(scroll-bar-mode nil) @@ -392,3 +409,9 @@ (put 'upcase-region 'disabled nil) (put 'downcase-region 'disabled nil) +(custom-set-faces + ;; custom-set-faces was added by Custom. + ;; If you edit it by hand, you could mess it up, so be careful. + ;; Your init file should contain only one such instance. + ;; If there is more than one, they won't work right. + ) diff --git a/emacs.d/json.el b/emacs.d/json.el new file mode 100644 index 0000000..8dee2b2 --- /dev/null +++ b/emacs.d/json.el @@ -0,0 +1,530 @@ +;;; json.el --- JavaScript Object Notation parser / generator + +;; Copyright (C) 2006, 2007, 2008, 2009 Free Software Foundation, Inc. + +;; Author: Edward O'Connor +;; Version: 1.2 +;; Keywords: convenience + +;; This file is part of GNU Emacs. + +;; GNU Emacs 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. + +;; GNU Emacs 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 GNU Emacs. If not, see . + +;;; Commentary: + +;; This is a library for parsing and generating JSON (JavaScript Object +;; Notation). + +;; Learn all about JSON here: . + +;; The user-serviceable entry points for the parser are the functions +;; `json-read' and `json-read-from-string'. The encoder has a single +;; entry point, `json-encode'. + +;; Since there are several natural representations of key-value pair +;; mappings in elisp (alist, plist, hash-table), `json-read' allows you +;; to specify which you'd prefer (see `json-object-type' and +;; `json-array-type'). + +;; Similarly, since `false' and `null' are distinct in JSON, you can +;; distinguish them by binding `json-false' and `json-null' as desired. + +;;; History: + +;; 2006-03-11 - Initial version. +;; 2006-03-13 - Added JSON generation in addition to parsing. Various +;; other cleanups, bugfixes, and improvements. +;; 2006-12-29 - XEmacs support, from Aidan Kehoe . +;; 2008-02-21 - Installed in GNU Emacs. + +;;; Code: + +(eval-when-compile (require 'cl)) + +;; Compatibility code + +(defalias 'json-encode-char0 'encode-char) +(defalias 'json-decode-char0 'decode-char) + + +;; Parameters + +(defvar json-object-type 'alist + "Type to convert JSON objects to. +Must be one of `alist', `plist', or `hash-table'. Consider let-binding +this around your call to `json-read' instead of `setq'ing it.") + +(defvar json-array-type 'vector + "Type to convert JSON arrays to. +Must be one of `vector' or `list'. Consider let-binding this around +your call to `json-read' instead of `setq'ing it.") + +(defvar json-key-type nil + "Type to convert JSON keys to. +Must be one of `string', `symbol', `keyword', or nil. + +If nil, `json-read' will guess the type based on the value of +`json-object-type': + + If `json-object-type' is: nil will be interpreted as: + `hash-table' `string' + `alist' `symbol' + `plist' `keyword' + +Note that values other than `string' might behave strangely for +Sufficiently Weird keys. Consider let-binding this around your call to +`json-read' instead of `setq'ing it.") + +(defvar json-false :json-false + "Value to use when reading JSON `false'. +If this has the same value as `json-null', you might not be able to tell +the difference between `false' and `null'. Consider let-binding this +around your call to `json-read' instead of `setq'ing it.") + +(defvar json-null nil + "Value to use when reading JSON `null'. +If this has the same value as `json-false', you might not be able to +tell the difference between `false' and `null'. Consider let-binding +this around your call to `json-read' instead of `setq'ing it.") + + + +;;; Utilities + +(defun json-join (strings separator) + "Join STRINGS with SEPARATOR." + (mapconcat 'identity strings separator)) + +(defun json-alist-p (list) + "Non-null if and only if LIST is an alist." + (or (null list) + (and (consp (car list)) + (json-alist-p (cdr list))))) + +(defun json-plist-p (list) + "Non-null if and only if LIST is a plist." + (or (null list) + (and (keywordp (car list)) + (consp (cdr list)) + (json-plist-p (cddr list))))) + +;; Reader utilities + +(defsubst json-advance (&optional n) + "Skip past the following N characters." + (forward-char n)) + +(defsubst json-peek () + "Return the character at point." + (let ((char (char-after (point)))) + (or char :json-eof))) + +(defsubst json-pop () + "Advance past the character at point, returning it." + (let ((char (json-peek))) + (if (eq char :json-eof) + (signal 'end-of-file nil) + (json-advance) + char))) + +(defun json-skip-whitespace () + "Skip past the whitespace at point." + (skip-chars-forward "\t\r\n\f\b ")) + + + +;; Error conditions + +(put 'json-error 'error-message "Unknown JSON error") +(put 'json-error 'error-conditions '(json-error error)) + +(put 'json-readtable-error 'error-message "JSON readtable error") +(put 'json-readtable-error 'error-conditions + '(json-readtable-error json-error error)) + +(put 'json-unknown-keyword 'error-message "Unrecognized keyword") +(put 'json-unknown-keyword 'error-conditions + '(json-unknown-keyword json-error error)) + +(put 'json-number-format 'error-message "Invalid number format") +(put 'json-number-format 'error-conditions + '(json-number-format json-error error)) + +(put 'json-string-escape 'error-message "Bad unicode escape") +(put 'json-string-escape 'error-conditions + '(json-string-escape json-error error)) + +(put 'json-string-format 'error-message "Bad string format") +(put 'json-string-format 'error-conditions + '(json-string-format json-error error)) + +(put 'json-object-format 'error-message "Bad JSON object") +(put 'json-object-format 'error-conditions + '(json-object-format json-error error)) + + + +;;; Keywords + +(defvar json-keywords '("true" "false" "null") + "List of JSON keywords.") + +;; Keyword parsing + +(defun json-read-keyword (keyword) + "Read a JSON keyword at point. +KEYWORD is the keyword expected." + (unless (member keyword json-keywords) + (signal 'json-unknown-keyword (list keyword))) + (mapc (lambda (char) + (unless (char-equal char (json-peek)) + (signal 'json-unknown-keyword + (list (save-excursion + (backward-word 1) + (thing-at-point 'word))))) + (json-advance)) + keyword) + (unless (looking-at "\\(\\s-\\|[],}]\\|$\\)") + (signal 'json-unknown-keyword + (list (save-excursion + (backward-word 1) + (thing-at-point 'word))))) + (cond ((string-equal keyword "true") t) + ((string-equal keyword "false") json-false) + ((string-equal keyword "null") json-null))) + +;; Keyword encoding + +(defun json-encode-keyword (keyword) + "Encode KEYWORD as a JSON value." + (cond ((eq keyword t) "true") + ((eq keyword json-false) "false") + ((eq keyword json-null) "null"))) + +;;; Numbers + +;; Number parsing + +(defun json-read-number (&optional sign) + "Read the JSON number following point. +The optional SIGN argument is for internal use. + +N.B.: Only numbers which can fit in Emacs Lisp's native number +representation will be parsed correctly." + ;; If SIGN is non-nil, the number is explicitly signed. + (let ((number-regexp + "\\([0-9]+\\)?\\(\\.[0-9]+\\)?\\([Ee][+-]?[0-9]+\\)?")) + (cond ((and (null sign) (char-equal (json-peek) ?-)) + (json-advance) + (- (json-read-number t))) + ((and (null sign) (char-equal (json-peek) ?+)) + (json-advance) + (json-read-number t)) + ((and (looking-at number-regexp) + (or (match-beginning 1) + (match-beginning 2))) + (goto-char (match-end 0)) + (string-to-number (match-string 0))) + (t (signal 'json-number-format (list (point))))))) + +;; Number encoding + +(defun json-encode-number (number) + "Return a JSON representation of NUMBER." + (format "%s" number)) + +;;; Strings + +(defvar json-special-chars + '((?\" . ?\") + (?\\ . ?\\) + (?/ . ?/) + (?b . ?\b) + (?f . ?\f) + (?n . ?\n) + (?r . ?\r) + (?t . ?\t)) + "Characters which are escaped in JSON, with their elisp counterparts.") + +;; String parsing + +(defun json-read-escaped-char () + "Read the JSON string escaped character at point." + ;; Skip over the '\' + (json-advance) + (let* ((char (json-pop)) + (special (assq char json-special-chars))) + (cond + (special (cdr special)) + ((not (eq char ?u)) char) + ((looking-at "[0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f]") + (let ((hex (match-string 0))) + (json-advance 4) + (json-decode-char0 'ucs (string-to-number hex 16)))) + (t + (signal 'json-string-escape (list (point))))))) + +(defun json-read-string () + "Read the JSON string at point." + (unless (char-equal (json-peek) ?\") + (signal 'json-string-format (list "doesn't start with '\"'!"))) + ;; Skip over the '"' + (json-advance) + (let ((characters '()) + (char (json-peek))) + (while (not (char-equal char ?\")) + (push (if (char-equal char ?\\) + (json-read-escaped-char) + (json-pop)) + characters) + (setq char (json-peek))) + ;; Skip over the '"' + (json-advance) + (if characters + (apply 'string (nreverse characters)) + ""))) + +;; String encoding + +(defun json-encode-char (char) + "Encode CHAR as a JSON string." + (setq char (json-encode-char0 char 'ucs)) + (let ((control-char (car (rassoc char json-special-chars)))) + (cond + ;; Special JSON character (\n, \r, etc.) + (control-char + (format "\\%c" control-char)) + ;; ASCIIish printable character + ((and (> char 31) (< char 161)) + (format "%c" char)) + ;; Fallback: UCS code point in \uNNNN form + (t + (format "\\u%04x" char))))) + +(defun json-encode-string (string) + "Return a JSON representation of STRING." + (format "\"%s\"" (mapconcat 'json-encode-char string ""))) + +;;; JSON Objects + +(defun json-new-object () + "Create a new Elisp object corresponding to a JSON object. +Please see the documentation of `json-object-type'." + (cond ((eq json-object-type 'hash-table) + (make-hash-table :test 'equal)) + (t + (list)))) + +(defun json-add-to-object (object key value) + "Add a new KEY -> VALUE association to OBJECT. +Returns the updated object, which you should save, e.g.: + (setq obj (json-add-to-object obj \"foo\" \"bar\")) +Please see the documentation of `json-object-type' and `json-key-type'." + (let ((json-key-type + (if (eq json-key-type nil) + (cdr (assq json-object-type '((hash-table . string) + (alist . symbol) + (plist . keyword)))) + json-key-type))) + (setq key + (cond ((eq json-key-type 'string) + key) + ((eq json-key-type 'symbol) + (intern key)) + ((eq json-key-type 'keyword) + (intern (concat ":" key))))) + (cond ((eq json-object-type 'hash-table) + (puthash key value object) + object) + ((eq json-object-type 'alist) + (cons (cons key value) object)) + ((eq json-object-type 'plist) + (cons key (cons value object)))))) + +;; JSON object parsing + +(defun json-read-object () + "Read the JSON object at point." + ;; Skip over the "{" + (json-advance) + (json-skip-whitespace) + ;; read key/value pairs until "}" + (let ((elements (json-new-object)) + key value) + (while (not (char-equal (json-peek) ?})) + (json-skip-whitespace) + (setq key (json-read-string)) + (json-skip-whitespace) + (if (char-equal (json-peek) ?:) + (json-advance) + (signal 'json-object-format (list ":" (json-peek)))) + (setq value (json-read)) + (setq elements (json-add-to-object elements key value)) + (json-skip-whitespace) + (unless (char-equal (json-peek) ?}) + (if (char-equal (json-peek) ?,) + (json-advance) + (signal 'json-object-format (list "," (json-peek)))))) + ;; Skip over the "}" + (json-advance) + elements)) + +;; Hash table encoding + +(defun json-encode-hash-table (hash-table) + "Return a JSON representation of HASH-TABLE." + (format "{%s}" + (json-join + (let (r) + (maphash + (lambda (k v) + (push (format "%s:%s" + (json-encode k) + (json-encode v)) + r)) + hash-table) + r) + ", "))) + +;; List encoding (including alists and plists) + +(defun json-encode-alist (alist) + "Return a JSON representation of ALIST." + (format "{%s}" + (json-join (mapcar (lambda (cons) + (format "%s:%s" + (json-encode (car cons)) + (json-encode (cdr cons)))) + alist) + ", "))) + +(defun json-encode-plist (plist) + "Return a JSON representation of PLIST." + (let (result) + (while plist + (push (concat (json-encode (car plist)) + ":" + (json-encode (cadr plist))) + result) + (setq plist (cddr plist))) + (concat "{" (json-join (nreverse result) ", ") "}"))) + +(defun json-encode-list (list) + "Return a JSON representation of LIST. +Tries to DWIM: simple lists become JSON arrays, while alists and plists +become JSON objects." + (cond ((null list) "null") + ((json-alist-p list) (json-encode-alist list)) + ((json-plist-p list) (json-encode-plist list)) + ((listp list) (json-encode-array list)) + (t + (signal 'json-error (list list))))) + +;;; Arrays + +;; Array parsing + +(defun json-read-array () + "Read the JSON array at point." + ;; Skip over the "[" + (json-advance) + (json-skip-whitespace) + ;; read values until "]" + (let (elements) + (while (not (char-equal (json-peek) ?\])) + (push (json-read) elements) + (json-skip-whitespace) + (unless (char-equal (json-peek) ?\]) + (if (char-equal (json-peek) ?,) + (json-advance) + (signal 'json-error (list 'bleah))))) + ;; Skip over the "]" + (json-advance) + (apply json-array-type (nreverse elements)))) + +;; Array encoding + +(defun json-encode-array (array) + "Return a JSON representation of ARRAY." + (concat "[" (mapconcat 'json-encode array ", ") "]")) + + + +;;; JSON reader. + +(defvar json-readtable + (let ((table + '((?t json-read-keyword "true") + (?f json-read-keyword "false") + (?n json-read-keyword "null") + (?{ json-read-object) + (?\[ json-read-array) + (?\" json-read-string)))) + (mapc (lambda (char) + (push (list char 'json-read-number) table)) + '(?- ?+ ?. ?0 ?1 ?2 ?3 ?4 ?5 ?6 ?7 ?8 ?9)) + table) + "Readtable for JSON reader.") + +(defun json-read () + "Parse and return the JSON object following point. +Advances point just past JSON object." + (json-skip-whitespace) + (let ((char (json-peek))) + (if (not (eq char :json-eof)) + (let ((record (cdr (assq char json-readtable)))) + (if (functionp (car record)) + (apply (car record) (cdr record)) + (signal 'json-readtable-error record))) + (signal 'end-of-file nil)))) + +;; Syntactic sugar for the reader + +(defun json-read-from-string (string) + "Read the JSON object contained in STRING and return it." + (with-temp-buffer + (insert string) + (goto-char (point-min)) + (json-read))) + +(defun json-read-file (file) + "Read the first JSON object contained in FILE and return it." + (with-temp-buffer + (insert-file-contents file) + (goto-char (point-min)) + (json-read))) + + + +;;; JSON encoder + +(defun json-encode (object) + "Return a JSON representation of OBJECT as a string." + (cond ((memq object (list t json-null json-false)) + (json-encode-keyword object)) + ((stringp object) (json-encode-string object)) + ((keywordp object) (json-encode-string + (substring (symbol-name object) 1))) + ((symbolp object) (json-encode-string + (symbol-name object))) + ((numberp object) (json-encode-number object)) + ((arrayp object) (json-encode-array object)) + ((hash-table-p object) (json-encode-hash-table object)) + ((listp object) (json-encode-list object)) + (t (signal 'json-error (list object))))) + +(provide 'json) + +;; arch-tag: 15f6e4c8-b831-4172-8749-bbc680c50ea1 +;;; json.el ends here diff --git a/emacs.d/mojo.el b/emacs.d/mojo.el index 1b57dc0..16125ea 100644 --- a/emacs.d/mojo.el +++ b/emacs.d/mojo.el @@ -1,7 +1,18 @@ -;;; mojo.el --- Interactive functions to aid the development of Palm Pre apps -(defconst mojo-version "0.2") +(require 'json) + +;;; mojo.el --- Interactive functions to aid the development of webOS apps +(defconst mojo-version "0.9") + ;; Copyright (c)2008 Jonathan Arkell. (by)(nc)(sa) Some rights reserved. -;; Author: Jonathan Arkell +;; 2009 Sami Samhuri +;; +;; Authors: Jonathan Arkell +;; Sami Samhuri +;; +;; Latest version is available on github: +;; http://github.com/samsonjs/config/blob/master/emacs.d/mojo.el +;; +;; With sufficient interest mojo.el will get its own repo. ;; This file is not part of GNU Emacs. @@ -20,19 +31,29 @@ ;;; Commentary: (defgroup mojo '() - "Interactive functions to aid the development of Palm Pre apps. + "Interactive functions to aid the development of webOS apps. -This package is in Early Beta, (they did just release the SDK). -I am open to any contributions or ideas. For now just post on -the Emacs Wiki, but soon there will be a spot on github for it.") +This package is in early beta. I am open to any contributions or +ideas. Send me a pull request on github if you hack on mojo.el.") ;;; Installation: -;; Put mojo.el somewhere in your load-path. +;; Put json.el and mojo.el somewhere in your load-path. ;; (Use M-x show-variable RET load-path to see what your load path is.) ;; Add this to your Emacs init file. ;(require 'mojo) +;; ;; Make sure you customize the variables: -;; `mojo-project-directory' and `mojo-sdk-directory' +;; `mojo-project-directory', `mojo-sdk-directory' and `mojo-build-directory' +;; +;; I recommend that you define a few keyboard shortcuts in your .emacs file. +;; Maybe something like this: +;; +;; (global-set-key [f2] 'mojo-generate-scene) +;; (global-set-key [f3] 'mojo-emulate) +;; (global-set-key [f4] 'mojo-package) +;; (global-set-key [f5] 'mojo-package-install-and-inspect) +;; +;; ;;; Commands: ;; @@ -40,24 +61,29 @@ the Emacs Wiki, but soon there will be a spot on github for it.") ;; ;; `mojo-generate' ;; Generate a new Mojo application in the `mojo-project-directory'. +;; `mojo-generate-scene' +;; Generate a new Mojo scene for the application in `mojo-root'. ;; `mojo-emulate' ;; Launch the palm emulator. ;; `mojo-package' -;; Package up an application inside of DIR. +;; Package the current application. ;; `mojo-install' -;; Install PACKAGE. The emulator needs to be running. +;; Install the specified package for the current application. +;; The emulator needs to be running. ;; `mojo-list' ;; List all installed packages. ;; `mojo-delete' ;; Remove application named APP-NAME. ;; `mojo-launch' -;; Launch application APP-NAME in an emulator. +;; Launch the current application in an emulator. ;; `mojo-close' -;; Close launched application APP-NAME. +;; Close launched application. ;; `mojo-inspect' -;; Run the dom inspector on APP-NAME. +;; Run the dom inspector on the current application. ;; `mojo-hard-reset' ;; Perform a hard reset, clearing all data. +;; `mojo-package-install-and-inspect' +;; Package, install, and launch the specified app for inspection. ;; ;;; Customizable Options: ;; @@ -69,6 +95,8 @@ the Emacs Wiki, but soon there will be a spot on github for it.") ;; `mojo-project-directory' ;; Directory where all your Mojo projects are located. ;; default = "" +;; `mojo-build-directory' +;; Directory to build Mojo applications in. ;; `mojo-debug' ;; Run Mojo in debug mode. Assumed true while in such an early version. ;; default = t @@ -77,6 +105,32 @@ the Emacs Wiki, but soon there will be a spot on github for it.") ;; ;;; CHANGELOG: +;; +;; v 0.9 - Automatically find Mojo project root by searching upwards +;; for appinfo.json. +;; +;; - Added command for generating new scenes, +;; mojo-generate-scene. +;; +;; - mojo-package now operates only on the current project. +;; +;; - Parse appinfo.json to get version, used for installing & +;; launching with less interaction. +;; +;; - mojo-install, mojo-launch, mojo-inspect, and mojo-delete +;; still read in arguments but have the current project/app as +;; the default values. +;; +;; - New convenience method: mojo-package-install-and-inspect +;; This function only operates on the active app and does not +;; read in any input. +;; +;; - Remembered filenames and app ids are cleared when the Mojo +;; project root changes. (DWIM) +;; +;; - Parse output of `palm-install --list` for app id +;; completion. App id completion was ported from cheat.el. +;; ;; v 0.2 - Fixed some minor bugs ;; v 0.1 - Initial release @@ -86,7 +140,7 @@ the Emacs Wiki, but soon there will be a spot on github for it.") (case system-type ((windows-nt) "c:/progra~1/palm/sdk") ((darwin) "/opt/PalmSDK/Current") - (t "/opt/PalmSDK/Current")) + (t "")) "Path to where the mojo SDK is. Note, using the old-school dos name of progra~1 was the only way i could make @@ -94,23 +148,16 @@ this work." :type 'directory :group 'mojo) -(defcustom mojo-project-directory "~/Projects/brighthouse/webOS" +(defcustom mojo-project-directory "" "Directory where all your Mojo projects are located." :type 'directory :group 'mojo) -(defcustom mojo-build-directory (expand-file-name "build" mojo-project-directory) - "Directory where builds are saved." +(defcustom mojo-build-directory "" + "Directory where built projects are saved." :type 'directory :group 'mojo) -;;* buffer const -(defconst mojo-buffer-name "*mojo*") - -;;* buffer var -(defvar mojo-buffer nil - "Buffer that spits out any mojo commandline messages.") - ;;* debug (defcustom mojo-debug t "Run Mojo in debug mode. Assumed true while in such an early version." @@ -118,41 +165,11 @@ this work." :group 'mojo) -(defun drop-last-path-component (path) - "Get the head of a path by dropping the last component." - (if (string= "/" path) - path - (substring path 0 (- (length path) - (length (last-path-component path)) - 1)))) ;; subtract one more for the trailing slash - -(defun last-path-component (path) - "Get the tail of a path, i.e. the last component." - (if (string= "/" path) - path - (let ((start -2)) - (while (not (string= "/" (substring path start (+ start 1)))) - (setq start (- start 1))) - (substring path (+ start 1))))) - -(defun find-project-subdirectory (path) - "Find a project's subdirectory under mojo-project-directory." - (let ((project-subdirectory (expand-file-name mojo-project-directory)) - (last-component (last-path-component path)) - (dir-prefix path)) - ;; remove last path element until we find the project subdir under mojo-project-directory - (while (and (not (string= project-subdirectory dir-prefix)) - (not (string= "/" dir-prefix))) - (setq last-component (last-path-component dir-prefix)) - (setq dir-prefix (drop-last-path-component dir-prefix))) - (concat dir-prefix "/" last-component))) - ;;* interactive generate (defun mojo-generate (title directory) "Generate a new Mojo application in the `mojo-project-directory'. TITLE is the name of the application. -ID is the id of the application. DIRECTORY is the directory where the files are stored." ;;TODO handle existing directories (use --overwrite) (interactive "sTitle: \nsDirectory Name (inside of mojo-project-directory): \n") @@ -160,9 +177,22 @@ DIRECTORY is the directory where the files are stored." (when (file-exists-p mojo-dir) (error "Cannot mojo-generate onto an existing directory! (%s)" mojo-dir)) (make-directory mojo-dir) - (mojo-cmd "palm-generate" (list "-p" (format "\"{'title':'%s'}\"" title) mojo-dir)) + (mojo-cmd "palm-generate" (list "-p" (format "\"{'title':'%s'}\"" title) + mojo-dir)) (find-file (concat mojo-dir "/appinfo.json")))) +;;* interactive +(defun mojo-generate-scene (name) + "Generate a new Mojo scene for the current application. + +NAME is the name of the scene." + (interactive "sScene Name: \n") + (let ((mojo-dir (mojo-root))) + (mojo-cmd "palm-generate" (list "-t" "new_scene" + "-p" (format "name=%s" name) mojo-dir)) + (find-file (format "%s/app/assistants/%s-assistant.js" mojo-dir name)) + (find-file (format "%s/app/views/%s/%s-scene.html" mojo-dir name name)))) + ;;* interactive (defun mojo-emulate () "Launch the palm emulator." @@ -171,15 +201,16 @@ DIRECTORY is the directory where the files are stored." ;;* interactive (defun mojo-package () - "Package up an application inside of DEFAULT-DIRECTORY for the current buffer." + "Package the current application into `MOJO-BUILD-DIRECTORY'." (interactive) - (mojo-cmd "palm-package" (list "-o" mojo-build-directory (find-project-subdirectory)))) + (mojo-cmd "palm-package" (list "-o" (expand-file-name mojo-build-directory) + (mojo-root)))) ;;* interactive -(defun mojo-install (package) - "Install PACKAGE. The emulator needs to be running." - (interactive "fInstall Package File: ") - (mojo-cmd "palm-install" (list (expand-file-name package)))) +(defun mojo-install () + "Install the package named by `MOJO-PACKAGE-FILENAME'. The emulator needs to be running." + (interactive) + (mojo-cmd "palm-install" (list (expand-file-name (mojo-read-package-filename))))) ;;* interactive (defun mojo-list () @@ -188,28 +219,28 @@ DIRECTORY is the directory where the files are stored." (mojo-cmd "palm-install" (list "--list"))) ;;* interactive -(defun mojo-delete (app-name) - "Remove application named APP-NAME." - (interactive "sDelete App: ") - (mojo-cmd "palm-install" (list "-r" app-name))) +(defun mojo-delete () + "Remove the current application using `MOJO-APP-ID'." + (interactive) + (mojo-cmd "palm-install" (list "-r" (mojo-read-app-id)))) ;;* interactive -(defun mojo-launch (app-name) - "Launch application APP-NAME in an emulator." - (interactive "sApp Name to Launch: ") - (mojo-cmd "palm-launch" (list app-name))) +(defun mojo-launch () + "Launch the current application in an emulator." + (interactive) + (mojo-cmd "palm-launch" (list (mojo-read-app-id)))) ;;* interactive -(defun mojo-close (app-name) - "Close launched application APP-NAME." - (interactive "sPackage Name:") - (mojo-cmd "palm-launch" (list "-c" app-name))) +(defun mojo-close () + "Close launched application." + (interactive) + (mojo-cmd "palm-launch" (list "-c" (mojo-read-app-id)))) ;;* launch interactive -(defun mojo-inspect (app-name) - "Run the dom inspector on APP-NAME." - (interactive "sPackage Name:") - (mojo-cmd "palm-launch" (list "-i" app-name))) +(defun mojo-inspect () + "Run the DOM inspector on the current application." + (interactive) + (mojo-cmd "palm-launch" (list "-i" (mojo-read-app-id)))) ;;* emulator interactive (defun mojo-hard-reset () @@ -221,6 +252,216 @@ DIRECTORY is the directory where the files are stored." "Use `browse-url' to visit your application with Palm Host." (browse-url "http://localhost:8888")) + +;;* interactive +(defun mojo-package-install-and-inspect () + "Package, install, and launch the current application for inspection." + (interactive) + (mojo-package) + (mojo-cmd "palm-install" (list (expand-file-name (mojo-package-filename)))) + (mojo-cmd "palm-launch" (list "-i" (mojo-app-id)))) + + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Some support functions that grok the basics of a Mojo project. ;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun drop-last-path-component (path) + "Get the head of a path by dropping the last component." + (if (< (length path) 2) + path + (substring path 0 (- (length path) + (length (last-path-component path)) + 1)))) ;; subtract one more for the trailing slash + +(defun last-path-component (path) + "Get the tail of a path, i.e. the last component." + (if (< (length path) 2) + path + (let ((start -2)) + (while (not (string= "/" (substring path start (+ start 1)))) + (setq start (- start 1))) + (substring path (+ start 1))))) + +(defvar *mojo-last-root* "" + "Last Mojo root found by `MOJO-ROOT'.") + +(defun mojo-root () + "Find a Mojo project's root directory starting with `DEFAULT-DIRECTORY'." + (let ((last-component (last-path-component default-directory)) + (dir-prefix default-directory)) + ;; remove last path element until we find appinfo.json + (while (and (not (file-exists-p (concat dir-prefix "/appinfo.json"))) + (not (< (length dir-prefix) 2))) + (setq last-component (last-path-component dir-prefix)) + (setq dir-prefix (drop-last-path-component dir-prefix))) + + ;; If no Mojo root found, ask for a directory. + (if (< (length dir-prefix) 2) + (setq dir-prefix (mojo-read-root))) + + ;; Invalidate cached values when changing projects. + (if (or (blank *mojo-last-root*) + (not (string= dir-prefix *mojo-last-root*))) + (progn + (setq *mojo-last-root* dir-prefix) + (setq *mojo-package-filename* nil) + (setq *mojo-app-id* nil))) + + dir-prefix)) + +(defun read-json-file (filename) + "Parse the JSON in FILENAME and return the result." + (let ((origbuffer (current-buffer)) + (filebuffer (find-file filename)) + (text)) + (goto-char (point-min)) ;; in case buffer already open + (let ((text (buffer-string))) + (switch-to-buffer origbuffer) + (json-read-from-string text)))) + +(defun mojo-app-version () + "Parse the project version from the appinfo.json file in `MOJO-ROOT'." + (let ((appinfo (read-json-file (concat (mojo-root) "/appinfo.json")))) + (cdr (assoc 'version appinfo)))) + +(defun mojo-app-id () + "Parse the project id from the appinfo.json file in `MOJO-ROOT'." + (let ((appinfo (read-json-file (concat (mojo-root) "/appinfo.json")))) + (cdr (assoc 'id appinfo)))) + +(defun mojo-package-filename () + "Get the package filename for the specified application." + (format "%s/%s_%s_all.ipk" (expand-file-name mojo-build-directory) + (mojo-app-id) (mojo-app-version))) + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; app listing and completion ;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun mojo-app-cache-file () + (concat (mojo-root) "/.applist")) + +(defvar *mojo-app-id* nil + "Most recently used application id.") + +(defvar *mojo-package-filename* nil + "Most recently used package file.") + +(defvar *mojo-app-history* nil + "List of the most recently used application ids.") + +;; cache expires hourly by default +(defvar *mojo-app-cache-ttl* 3600 + "The minimum age of a stale cache file, in seconds.") + +(defvar *mojo-package-history* nil + "List of the most recently used package filenames.") + +;; this is from rails-lib.el in the emacs-rails package +(defun string-join (separator strings) + "Join all STRINGS using SEPARATOR." + (mapconcat 'identity strings separator)) + +(defun blank (thing) + "Return T if THING is nil or an empty string, otherwise nil." + (or (null thing) + (and (stringp thing) + (= 0 (length thing))))) + +(defun mojo-read-root () + "Get the path to a Mojo application, prompting with completion and + history." + (read-file-name "Mojo project: " (expand-file-name (concat mojo-project-directory + "/")))) + +(defun mojo-read-package-filename () + "Get the filename of a packaged application, prompting with completion and + history. + +The app id is stored in *mojo-package-filename* unless it was blank." + (let* ((default (or *mojo-package-filename* + (mojo-package-filename))) + (package (read-file-name (format "Package file (default: %s): " default) + (concat mojo-build-directory "/") default t))) + (setq *mojo-package-filename* (last-path-component package)) + (expand-file-name package))) + +(defun mojo-read-app-id (&optional prompt) + "Get the id of an existing application, prompting with completion and + history. + +The app id is stored in *mojo-app-id* unless it was blank." + (let* ((default (or *mojo-app-id* (mojo-app-id))) + (prompt (or prompt + (format "App id (default: %s): " default))) + (app-id (completing-read prompt + (mojo-app-list t) + nil + t + nil + '*mojo-app-history* + default))) + (when (blank app-id) + (setq app-id (mojo-app-id))) + (setq *mojo-app-id* app-id) + app-id)) + +(defun mojo-app-list (&optional fetch-if-missing-or-stale) + "Get a list of installed Mojo applications." + (cond ((and (file-readable-p (mojo-app-cache-file)) + (not (mojo-app-cache-stale-p))) + (save-excursion + (let* ((buffer (find-file (mojo-app-cache-file))) + (apps (split-string (buffer-string)))) + (kill-buffer buffer) + apps))) + (fetch-if-missing-or-stale + (mojo-cache-app-list) + (mojo-app-list)) ;; guaranteed cache hit this time + (t nil))) + +(defun mojo-fetch-app-list () + "Fetch a fresh list of all applications." + (let* ((raw-list (nthcdr 7 (split-string (mojo-cmd-to-string "palm-install" (list "--list"))))) + (apps '()) + (n (length raw-list)) + (i 0)) + (while (< i n) + (if (= 0 (mod i 3)) + (push (pop raw-list) apps) + (pop raw-list)) + (incf i)) + (reverse apps))) + +(defun mojo-cache-app-list () + "Cache the list of applications in `MOJO-APP-CACHE-FILE'. Return the + list." + (save-excursion + (let ((buffer (find-file (mojo-app-cache-file))) + (apps (mojo-fetch-app-list))) + (insert (string-join "\n" apps)) + (basic-save-buffer) + (kill-buffer buffer) + apps))) + +(defun mojo-app-cache-stale-p () + "Non-nil if the cache in `MOJO-APP-CACHE-FILE' is more than + *mojo-app-cache-ttl* seconds old. + +If the cache file does not exist then it is considered stale." + (or (null (file-exists-p (mojo-app-cache-file))) + (let* ((now (float-time (current-time))) + (last-mod (float-time (sixth (file-attributes + (mojo-app-cache-file))))) + (age (- now last-mod))) + (> age *mojo-app-cache-ttl*)))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + ;;* lowlevel luna (defun mojo-luna-send (url data) "Send something through luna. @@ -232,57 +473,38 @@ This is a low level Emacs interface to luna-send. URL is the luna url, and DATA is the data." (mojo-cmd "luna-send" (list "-n" "1" url data))) -(when nil - (mojo-get-app-list)) - -;;* lowlevel app list -(defun mojo-get-app-list () - "Retrieve list of all installed applications. - -List is in the format of: - (id version name) -and stored inside of `mojo--app-list'" - (save-excursion - (set-buffer mojo-buffer) - (setq mojo--app-list (point)) - (set-process-sentinel (mojo-list) 'mojo--comint-list-sentinal))) - -;;* var list -(defvar mojo--app-list nil - "Variable for storing the current app list.") - -;;* hook list -(defun mojo--comint-process-filter-applist (output) - "Bunk function. Kept for reference. To Be Removed." - (if (string-match "\\([A-Za-z.]+\\) \\([0-9]+\\.[0-9.]+\\) \"\\(.+\\)\"") - (aput mojo--app-list (match-string 1) (list (match-string 2) (match-string 3))))) - -;;* hook list -(defun mojo--list-sentinal (proc state) - "Still in progress." - (if mojo-debug (message "Process got state %s" state)) - (if (integerp mojo--app-list) - (save-excursion - (set-buffer mojo-buffer) - (goto-char mojo-app-list) - (line-down 2)))) +(defun mojo-path-to-cmd (cmd) + "Return the absolute path to a Mojo SDK command line program." + ) ;;* lowlevel cmd (defun mojo-cmd (cmd args) - "General interface for running mojo-skd commands. + "General interface for running mojo-sdk commands. CMD is the name of the command (without path or extension) to execute. Automagically shell quoted. ARGS is a list of all arguments to the command. These arguments are NOT shell quoted." - (when (or (null mojo-buffer) - (not (buffer-live-p mojo-buffer))) - (setq mojo-buffer (get-buffer-create mojo-buffer-name))) (let ((cmd (case system-type ((windows-nt) (concat mojo-sdk-directory "/bin/" cmd ".bat")) (t (concat mojo-sdk-directory "/bin/" cmd))))) - (if mojo-debug (message "running %s with args %s " cmd args)) - (apply 'start-process "mojo" mojo-buffer cmd args))) + (if mojo-debug (message "running %s with args %s " cmd (string-join " " args))) + (shell-command (concat cmd " " (string-join " " args))))) + +;;* lowlevel cmd +(defun mojo-cmd-to-string (cmd args) + "General interface for running mojo-skd commands and capturing the output + to a string. + +CMD is the name of the command (without path or extension) to execute. + Automatically shell quoted. +ARGS is a list of all arguments to the command. + These arguments are NOT shell quoted." + (let ((cmd (case system-type + ((windows-nt) (concat mojo-sdk-directory "/bin/" cmd ".bat")) + (t (concat mojo-sdk-directory "/bin/" cmd))))) + (if mojo-debug (message "running %s with args %s " cmd (string-join " " args))) + (shell-command-to-string (concat cmd " " (string-join " " args))))) (provide 'mojo)