[NEW] Initial commit of mojo.el package.

This commit is contained in:
Sami Samhuri 2009-11-21 19:24:23 -08:00
commit 027a60f0f4
6 changed files with 1296 additions and 0 deletions

51
CHANGELOG Normal file
View file

@ -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

14
LICENSE Normal file
View file

@ -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

118
README Normal file
View file

@ -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 <jonnay@jonnay.net>
Sami Samhuri <sami.samhuri@gmail.com>
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

6
TODO Normal file
View file

@ -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

530
json.el Normal file
View file

@ -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 <ted@oconnor.cx>
;; 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 <http://www.gnu.org/licenses/>.
;;; Commentary:
;; This is a library for parsing and generating JSON (JavaScript Object
;; Notation).
;; Learn all about JSON here: <URL:http://json.org/>.
;; 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 <kehoea@parhasard.net>.
;; 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

577
mojo.el Normal file
View file

@ -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 <jonnay@jonnay.net>
;; Sami Samhuri <sami.samhuri@gmail.com>
;;
;; 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