commit 027a60f0f47c5fe756eaf5791ea1229e73b29235 Author: Sami Samhuri Date: Sat Nov 21 19:24:23 2009 -0800 [NEW] Initial commit of mojo.el package. diff --git a/CHANGELOG b/CHANGELOG new file mode 100644 index 0000000..e194baf --- /dev/null +++ b/CHANGELOG @@ -0,0 +1,51 @@ +CHANGELOG +========= + +sjs 2009-11-21 +v 0.9.2 (bug fixes) + + - reading json files no longer messes up your buffer history. + + - app list completion works now (caching bug) + +sjs 2009-11-21 +v 0.9.1 + + - Added mojo-package-install-and-launch. + + - New variable for specifying whether commands target the + device or emulator, *mojo-target*. Set it to 'usb' for a + real device and 'tcp' for the emulator. Defaults to 'tcp'. + To set the default target you can use the convenience + functions mojo-target-device and mojo-target-emulator. + +sjs 2009-11-20 +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 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..9a36135 --- /dev/null +++ b/LICENSE @@ -0,0 +1,14 @@ +This program 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 version 2. + +This program 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. + +For a copy of the GNU General Public License, see the GNU website[1], search +the Internet, or write to the Free Software Foundation, Inc., 59 Temple Place, +Suite 330, Boston, MA 02111-1307 USA + +[1] http://www.gnu.org/licenses/gpl-2.0.txt \ No newline at end of file diff --git a/README b/README new file mode 100644 index 0000000..01a3290 --- /dev/null +++ b/README @@ -0,0 +1,118 @@ +Copyright (c)2008 Jonathan Arkell. (by)(nc)(sa) Some rights reserved. + 2009 Sami Samhuri + +Distributed under the terms of the GNU Public License v2, see LICENSE. + +Authors: Jonathan Arkell + Sami Samhuri + + +Overview +======== +Mojo.el is an Emacs package that provides interactive functions to aid the +development of webOS apps. + +Latest version is available on github: + http://github.com/samsonjs/mojo.el + +And usually also on Emacs Wiki: + http://emacswiki.org/emacs/MojoSdk + http://emacswiki.org/emacs/mojo.el + + +Installation +============ + +1. 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.) + +2. Add this to your Emacs init file: (require 'mojo) + +3. Make sure you customize the variables: + mojo-project-directory, mojo-sdk-directory and mojo-build-directory + (Use M-x customize-group RET mojo RET) + +(optional) + +4. I recommend that you define a few keyboard shortcuts in your Emacs init + 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 +======== +The complete command list: + + mojo-generate + Generate a new Mojo application in the mojo-project-directory. + + mojo-generate-scene + Generate a new Mojo scene for the application found by mojo-root. + (a.k.a. the current application) + + mojo-emulate + Launch the palm emulator. + + mojo-package + Package the specified application (defaults to current app id). + + mojo-install + Install the specified package (defaults to current app id). + The emulator needs to be running. + + mojo-list + List all installed packages. + + mojo-delete + Remove the specified application. (defaults to current app id) + + mojo-launch + Launch the specified application in the emulator. (defaults to current app id) + + mojo-close + Close specified application. (defaults to current app id) + + mojo-inspect + Run the dom inspector on the specified application. (defaults to current app id) + + mojo-hard-reset + Perform a hard reset, clearing all data. + + mojo-package-install-and-launch + Package, install, and launch the current app. + + mojo-package-install-and-inspect + Package, install, and launch the current app for inspection. + + mojo-target-device + Set the target device to USB. + + mojo-target-emulator + Set the target device to the emulator. + + +Customizations +============== +Customizable options: + mojo-sdk-directory + Path to where the mojo SDK is. (default ok for windows and mac os x) + default = (case system-type + ((windows-nt) "c:/progra~1/palm/sdk") + ((darwin) "/opt/PalmSDK/Current") + (t "")) + + mojo-project-directory + Directory where all your Mojo projects are located. + default = "" + + mojo-build-directory + Directory to store packaged Mojo applications. + default = "" + + mojo-debug + Run Mojo in debug mode. Assumed true while in such an early version. + default = t diff --git a/TODO b/TODO new file mode 100644 index 0000000..ea10b16 --- /dev/null +++ b/TODO @@ -0,0 +1,6 @@ +TODO +==== + +* Jump to related files: to assistant from view and vice versa + +* detect if emulator is running and launch it if necessary \ No newline at end of file diff --git a/json.el b/json.el new file mode 100644 index 0000000..8dee2b2 --- /dev/null +++ b/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/mojo.el b/mojo.el new file mode 100644 index 0000000..772e9ef --- /dev/null +++ b/mojo.el @@ -0,0 +1,577 @@ +;;; mojo.el --- Interactive functions to aid the development of webOS apps +;; 2009-11-21 19:23:18 +(defconst mojo-version "0.9.2") + +(require 'json) + +;; Copyright (c)2008 Jonathan Arkell. (by)(nc)(sa) Some rights reserved. +;; 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. + +;; This program 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 version 2. + +;; This program 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. + +;; For a copy of the GNU General Public License, search the Internet, +;; or write to the Free Software Foundation, Inc., 59 Temple Place, +;; Suite 330, Boston, MA 02111-1307 USA + +;;; Commentary: +(defgroup mojo '() + "Interactive functions to aid the development of webOS apps. + +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: +;; +;; 1. 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.) +;; +;; 2. Add this to your Emacs init file: (require 'mojo) +;; +;; 3. Make sure you customize the variables: +;; mojo-project-directory, mojo-sdk-directory and mojo-build-directory +;; (Use M-x customize-group RET mojo RET) +;; +;; (optional) +;; +;; 4. I recommend that you define a few keyboard shortcuts in your Emacs init +;; 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: +;; +;; Below are complete command list: +;; +;; `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 the current application. +;; `mojo-install' +;; 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 the current application in an emulator. +;; `mojo-close' +;; Close launched application. +;; `mojo-inspect' +;; Run the dom inspector on the current application. +;; `mojo-hard-reset' +;; Perform a hard reset, clearing all data. +;; `mojo-package-install-and-launch' +;; Package, install, and launch the current app. +;; `mojo-package-install-and-inspect' +;; Package, install, and launch the current app for inspection. +;; `mojo-target-device' +;; Set the target to a USB device. +;; `mojo-target-emulator' +;; Set the target to the emulator. + +;;; Customizable Options: +;; +;; Below are customizable option list: +;; +;; `mojo-sdk-directory' +;; Path to where the mojo SDK is. +;; default = (case system-type +;; ((windows-nt) "c:/progra~1/palm/sdk") +;; ((darwin) "/opt/PalmSDK/Current") +;; (t "")) +;; `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 + +;; CHANGELOG +;; ========= +;; +;; sjs 2009-11-21 +;; v 0.9.2 (bug fixes) +;; +;; - reading json files no longer messes up your buffer history. +;; +;; - app list completion works now (caching bug) +;; +;; sjs 2009-11-21 +;; v 0.9.1 +;; +;; - Added mojo-package-install-and-launch. +;; +;; - New variable for specifying whether commands target the +;; device or emulator, *mojo-target*. Set it to 'usb' for a +;; real device and 'tcp' for the emulator. Defaults to 'tcp'. +;; To set the default target you can use the convenience +;; functions mojo-target-device and mojo-target-emulator. +;; +;; sjs 2009-11-20 +;; 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 + + +;;; Code: + + +(defcustom mojo-sdk-directory + (case system-type + ((windows-nt) "c:/progra~1/palm/sdk") + ((darwin) "/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 +this work." + :type 'directory + :group 'mojo) + +(defcustom mojo-project-directory "" + "Directory where all your Mojo projects are located." + :type 'directory + :group 'mojo) + +(defcustom mojo-build-directory "" + "Directory where built projects are saved." + :type 'directory + :group 'mojo) + +;;* debug +(defcustom mojo-debug t + "Run Mojo in debug mode. Assumed true while in such an early version." + :type 'boolean + :group 'mojo) + + +;;* interactive generate +(defun mojo-generate (title directory) + "Generate a new Mojo application in the `mojo-project-directory'. + +TITLE is the name 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") + (let ((mojo-dir (expand-file-name (concat mojo-project-directory "/" directory)))) + (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)) + (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." + (interactive) + (mojo-cmd "palm-emulator" nil)) + +;;* interactive +(defun mojo-package () + "Package the current application into `MOJO-BUILD-DIRECTORY'." + (interactive) + (mojo-cmd "palm-package" (list "-o" (expand-file-name mojo-build-directory) + (mojo-root)))) + +;;* interactive +(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)))) + (mojo-invalidate-app-cache)) + +;;* interactive +(defun mojo-list () + "List all installed packages." + (interactive) + (mojo-cmd "palm-install" (list "--list"))) + +;;* interactive +(defun mojo-delete () + "Remove the current application using `MOJO-APP-ID'." + (interactive) + (mojo-cmd "palm-install" (list "-r" (mojo-read-app-id))) + (mojo-invalidate-app-cache)) + +;;* interactive +(defun mojo-launch () + "Launch the current application in an emulator." + (interactive) + (mojo-cmd "palm-launch" (list (mojo-read-app-id)))) + +;;* interactive +(defun mojo-close () + "Close launched application." + (interactive) + (mojo-cmd "palm-launch" (list "-c" (mojo-read-app-id)))) + +;;* launch interactive +(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 () + "Perform a hard reset, clearing all data." + (interactive) + (mojo-cmd "palm-emulator" (list "--reset"))) + +(defun mojo-browse () + "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)))) + +;;* interactive +(defun mojo-package-install-and-launch () + "Package, install, and launch the current application." + (interactive) + (mojo-package) + (mojo-cmd "palm-install" (list (expand-file-name (mojo-package-filename)))) + (mojo-cmd "palm-launch" (list (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." + (save-excursion + (let ((origbuffer (current-buffer)) + (filebuffer (find-file-noselect filename))) + (set-buffer filebuffer) + (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 ;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defvar *mojo-app-cache-filename* nil) +(defun mojo-app-cache-file (&optional force-reload) + "Cache the list of applications in a temporary file. Return the filename." + (when (or force-reload + (not *mojo-app-cache-filename*)) + (setq *mojo-app-cache-filename* (make-temp-file "mojo-app-list-cache")) + (save-excursion + (let ((buffer (find-file-noselect *mojo-app-cache-filename*)) + (apps (mojo-fetch-app-list))) + (set-buffer buffer) + (insert (string-join "\n" apps)) + (basic-save-buffer) + (kill-buffer buffer)))) + *mojo-app-cache-filename*) + +(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-app-cache-file t) ;; force reload + (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 (list)) + (appname-regex "^[^0-9][^.]+\\(\\.[^.]+\\)+$") + (item (pop raw-list))) + (while item + (if (string-match appname-regex item) ;; liberal regex for simplicity + (push item apps) + (print (concat "discarding " item))) + (setq item (pop raw-list))) + (nreverse 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*)))) + +(defun mojo-invalidate-app-cache () + "Delete the app list cache." + (delete-file (mojo-app-cache-file))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;;* lowlevel luna +(defun mojo-luna-send (url data) + "Send something through luna. + +Luna-send is a program to send things like incoming calls, GPS status, SMS, +etc. to your emulator. + +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))) + +(defvar *mojo-target* "tcp" + "Used to specify the target platform, \"usb\" for the device + and \"tcp\" for the emulator. Deaults to \"tcp\".") + +(defun mojo-target-device () + "Specify that Mojo commands should target a real device. + +Sets `*mojo-target*' to \"usb\"." + (setq *mojo-target* "usb")) + +(defun mojo-target-emulator () + "Specify that Mojo commands should target a real device. + +Sets `*mojo-target*' to \"tcp\"." + (setq *mojo-target* "tcp")) + +(defun mojo-path-to-cmd (cmd) + "Return the absolute path to a Mojo SDK command line program." + (case system-type + ((windows-nt) (concat mojo-sdk-directory "/bin/" cmd ".bat")) + (t (concat mojo-sdk-directory "/bin/" cmd)))) + +;;* lowlevel cmd +(defun mojo-cmd (cmd args &optional target) + "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." + (let ((cmd (mojo-path-to-cmd cmd)) + (args (concat "-d " (or target *mojo-target*) " " + (string-join " " args)))) + (if mojo-debug (message "running %s with args %s " cmd args)) + (shell-command (concat cmd " " args)))) + +;;* lowlevel cmd +(defun mojo-cmd-to-string (cmd args &optional target) + "General interface for running mojo-sdk 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 (mojo-path-to-cmd cmd)) + (args (concat "-d " (or target *mojo-target*) " " + (string-join " " args)))) + (if mojo-debug (message "running %s with args %s " cmd args)) + (shell-command-to-string (concat cmd " " args)))) + +(provide 'mojo) + + +;;; mojo ends here