[CHANGED] created mojo-mode minor mode. prefix all functions with mojo-.

This commit is contained in:
Sami Samhuri 2009-12-01 08:30:09 -08:00
parent 5855f51326
commit 057120c5e6
5 changed files with 493 additions and 56 deletions

63
README
View file

@ -9,8 +9,10 @@ Authors: Jonathan Arkell <jonnay@jonnay.net>
Overview Overview
======== ========
Mojo.el is an Emacs package that provides interactive functions to aid the
development of webOS apps. Mojo.el is an Emacs package that provides interactive functions to aid
the development of webOS apps. There is a minor mode that can be
toggled with the command `mojo-mode'.
Latest version is available on github: Latest version is available on github:
http://github.com/samsonjs/mojo.el http://github.com/samsonjs/mojo.el
@ -32,21 +34,15 @@ Installation
mojo-project-directory, mojo-sdk-directory and mojo-build-directory mojo-project-directory, mojo-sdk-directory and mojo-build-directory
(Use M-x customize-group RET mojo RET) (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 Commands
======== ========
The complete command list: The complete command list:
Code generation
---------------
mojo-generate mojo-generate
Generate a new Mojo application in the mojo-project-directory. Generate a new Mojo application in the mojo-project-directory.
@ -54,6 +50,10 @@ The complete command list:
Generate a new Mojo scene for the application found by mojo-root. Generate a new Mojo scene for the application found by mojo-root.
(a.k.a. the current application) (a.k.a. the current application)
Packaging and device/emulator interactions
------------------------------------------
mojo-emulate mojo-emulate
Launch the palm emulator. Launch the palm emulator.
@ -71,13 +71,15 @@ The complete command list:
Remove the specified application. (defaults to current app id) Remove the specified application. (defaults to current app id)
mojo-launch mojo-launch
Launch the specified application in the emulator. (defaults to current app id) Launch the specified application in the emulator. (defaults to
current app id)
mojo-close mojo-close
Close specified application. (defaults to current app id) Close specified application. (defaults to current app id)
mojo-inspect mojo-inspect
Run the dom inspector on the specified application. (defaults to current app id) Run the dom inspector on the specified application. (defaults to
current app id)
mojo-hard-reset mojo-hard-reset
Perform a hard reset, clearing all data. Perform a hard reset, clearing all data.
@ -94,10 +96,43 @@ The complete command list:
mojo-target-emulator mojo-target-emulator
Set the target device to the emulator. Set the target device to the emulator.
mojo-toggle-target
Automatically change the target device from 'usb' to 'tcp' and vice
versa.
Quickly switch buffers
----------------------
mojo-switch-to-assistant
Switch to the corresponding assistant from any view file.
mojo-switch-to-view
Switch to the main view from an assistant.
mojo-switch-to-next-view
Switch to the next view file, alphabetically. Wraps around at the
end.
mojo-switch-to-appinfo
Switch to the appinfo.json file.
mojo-switch-to-sources
Switch to the sources.json file.
mojo-switch-to-index
Switch to the root index.html file.
mojo-switch-to-stylesheet
Switch to the main stylesheet.
Customizations Customizations
============== ==============
Customizable options: Customizable options:
mojo-sdk-directory mojo-sdk-directory
Path to where the mojo SDK is. (default ok for windows and mac os x) Path to where the mojo SDK is. (default ok for windows and mac os x)
default = (case system-type default = (case system-type

6
TODO
View file

@ -1,4 +1,8 @@
TODO TODO
==== ====
* Jump to related files: to assistant from view and vice versa * Detect when inside a mojo project and load mojo-mode
* turn on and off debugging & logging
* switch to last visited view
* inject Mojo.Ext
* convert assistants to inherit from SceneAssistantBase

241
mojo.el
View file

@ -1,6 +1,6 @@
;;; mojo.el --- Interactive functions to aid the development of webOS apps ;;; mojo.el --- Interactive functions to aid the development of webOS apps
;; 2009-11-26 14:12:59 ;; 2009-12-01 08:29:25
(defconst mojo-version "0.9.5") (defconst mojo-version "0.9.6")
(require 'json) (require 'json)
@ -118,6 +118,57 @@ ideas. Send me a pull request on github if you hack on mojo.el.")
;;; Code: ;;; Code:
(define-minor-mode mojo-mode
"Toggle Mojo mode.
With no argument, this command toggles the mode.
Non-null prefix argument turns on the mode.
Null prefix argument turns off the mode.
When Mojo mode is enabled various commands are enabled to
aid the development of Mojo applications.
Make sure you customize the variables
\\[mojo-project-directory], \\[mojo-sdk-directory] and
\\[mojo-build-directory].
Keybindings are:
* C-c a -- \\[mojo-switch-to-assistant]
* C-c i -- \\[mojo-switch-to-appinfo]
* C-c I -- \\[mojo-switch-to-index]
* C-c n -- \\[mojo-switch-to-next-view]
* C-c s -- \\[mojo-switch-to-sources]
* C-c S -- \\[mojo-switch-to-stylesheet]
* C-c v -- \\[mojo-switch-to-view]
* C-c SPC -- \\[mojo-switch-file-dwim]
* C-c C-e -- \\[mojo-emulate]
* C-c C-p -- \\[mojo-package]
* C-c C-r -- \\[mojo-package-install-and-inspect]
* C-c C-s -- \\[mojo-generate-scene]
* C-c C-t -- \\[mojo-toggle-target]
See the source code mojo.el or the README file for a list of
all of the interactive commands."
;; The initial value.
:init-value nil
;; The indicator for the mode line.
:lighter "-Mojo"
;; The minor mode bindings.
:keymap
'(("\C-ca" . mojo-switch-to-assistant)
("\C-ci" . mojo-switch-to-appinfo)
("\C-cI" . mojo-switch-to-index)
("\C-cn" . mojo-switch-to-next-view)
("\C-cs" . mojo-switch-to-sources)
("\C-cS" . mojo-switch-to-stylesheet)
("\C-cv" . mojo-switch-to-view)
("\C-c " . mojo-switch-file-dwim)
("\C-c\C-e" . mojo-emulate)
("\C-c\C-p" . mojo-package)
("\C-c\C-r" . mojo-package-install-and-inspect)
("\C-c\C-s" . mojo-generate-scene)
("\C-c\C-t" . mojo-toggle-target))
:group 'mojo)
(defcustom mojo-sdk-directory (defcustom mojo-sdk-directory
(case system-type (case system-type
@ -295,19 +346,28 @@ Sets `*mojo-target*' to \"tcp\"."
(setq *mojo-target* "tcp")) (setq *mojo-target* "tcp"))
;;* interactive
(defun mojo-toggle-target ()
"Automatically change the target device from 'usb' to 'tcp' and
vice versa."
(interactive)
(if (string= "usb" *mojo-target*)
(mojo-target-emulator)
(mojo-target-device)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Some support functions that grok the basics of a Mojo project. ;; ;; Some support functions that grok the basics of a Mojo project. ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defun drop-last-path-component (path) (defun mojo-drop-last-path-component (path)
"Get the head of a path by dropping the last component." "Get the head of a path by dropping the last component."
(if (< (length path) 2) (if (< (length path) 2)
path path
(substring path 0 (- (length path) (substring path 0 (- (length path)
(length (last-path-component path)) (length (mojo-last-path-component path))
1)))) ;; subtract one more for the trailing slash 1)))) ;; subtract one more for the trailing slash
(defun last-path-component (path) (defun mojo-last-path-component (path)
"Get the tail of a path, i.e. the last component." "Get the tail of a path, i.e. the last component."
(if (< (length path) 2) (if (< (length path) 2)
path path
@ -321,20 +381,20 @@ Sets `*mojo-target*' to \"tcp\"."
(defun mojo-root () (defun mojo-root ()
"Find a Mojo project's root directory starting with `DEFAULT-DIRECTORY'." "Find a Mojo project's root directory starting with `DEFAULT-DIRECTORY'."
(let ((last-component (last-path-component default-directory)) (let ((last-component (mojo-last-path-component default-directory))
(dir-prefix default-directory)) (dir-prefix default-directory))
;; remove last path element until we find appinfo.json ;; remove last path element until we find appinfo.json
(while (and (not (file-exists-p (concat dir-prefix "/appinfo.json"))) (while (and (not (file-exists-p (concat dir-prefix "/appinfo.json")))
(not (< (length dir-prefix) 2))) (not (< (length dir-prefix) 2)))
(setq last-component (last-path-component dir-prefix)) (setq last-component (mojo-last-path-component dir-prefix))
(setq dir-prefix (drop-last-path-component dir-prefix))) (setq dir-prefix (mojo-drop-last-path-component dir-prefix)))
;; If no Mojo root found, ask for a directory. ;; If no Mojo root found, ask for a directory.
(if (< (length dir-prefix) 2) (if (< (length dir-prefix) 2)
(setq dir-prefix (mojo-read-root))) (setq dir-prefix (mojo-read-root)))
;; Invalidate cached values when changing projects. ;; Invalidate cached values when changing projects.
(if (or (blank *mojo-last-root*) (if (or (mojo-blank *mojo-last-root*)
(not (string= dir-prefix *mojo-last-root*))) (not (string= dir-prefix *mojo-last-root*)))
(progn (progn
(setq *mojo-last-root* dir-prefix) (setq *mojo-last-root* dir-prefix)
@ -343,7 +403,14 @@ Sets `*mojo-target*' to \"tcp\"."
dir-prefix)) dir-prefix))
(defun read-json-file (filename) ;; foolproof?
(defun mojo-project-p ()
(and (file-exists-p (concat (mojo-root) "/appinfo.json"))
(file-exists-p (concat (mojo-root) "/sources.json"))
(file-exists-p (concat (mojo-root) "/app"))
(file-exists-p (concat (mojo-root) "/index.html"))))
(defun mojo-read-json-file (filename)
"Parse the JSON in FILENAME and return the result." "Parse the JSON in FILENAME and return the result."
(save-excursion (save-excursion
(let ((origbuffer (current-buffer)) (let ((origbuffer (current-buffer))
@ -355,13 +422,27 @@ Sets `*mojo-target*' to \"tcp\"."
(defun mojo-app-version () (defun mojo-app-version ()
"Parse the project version from the appinfo.json file in `MOJO-ROOT'." "Parse the project version from the appinfo.json file in `MOJO-ROOT'."
(let ((appinfo (read-json-file (concat (mojo-root) "/appinfo.json")))) (when (mojo-project-p)
(cdr (assoc 'version appinfo)))) (let ((appinfo (mojo-read-json-file (concat (mojo-root) "/appinfo.json"))))
(cdr (assoc 'version appinfo)))))
(defun mojo-app-id () (defun mojo-app-id ()
"Parse the project id from the appinfo.json file in `MOJO-ROOT'." "Parse the project id from the appinfo.json file in `MOJO-ROOT'."
(let ((appinfo (read-json-file (concat (mojo-root) "/appinfo.json")))) (when (mojo-project-p)
(cdr (assoc 'id appinfo)))) (let ((appinfo (mojo-read-json-file (concat (mojo-root) "/appinfo.json"))))
(cdr (assoc 'id appinfo)))))
(defun mojo-app-title ()
"Parse the project title from appinfo.json."
(when (mojo-project-p)
(let ((appinfo (mojo-read-json-file (concat (mojo-root) "/appinfo.json"))))
(cdr (assoc 'title appinfo)))))
(defun mojo-informal-app-id ()
"Parse the project title from appinfo.json and remove all non alphanumeric
characters."
(let ((title (downcase (mojo-app-title))))
(replace-regexp-in-string "[^a-z0-9]" "" title)))
(defun mojo-package-filename () (defun mojo-package-filename ()
"Get the package filename for the specified application." "Get the package filename for the specified application."
@ -383,7 +464,7 @@ Sets `*mojo-target*' to \"tcp\"."
(let ((buffer (find-file-noselect *mojo-app-cache-filename*)) (let ((buffer (find-file-noselect *mojo-app-cache-filename*))
(apps (mojo-fetch-app-list))) (apps (mojo-fetch-app-list)))
(set-buffer buffer) (set-buffer buffer)
(insert (string-join "\n" apps)) (insert (mojo-string-join "\n" apps))
(basic-save-buffer) (basic-save-buffer)
(kill-buffer buffer)))) (kill-buffer buffer))))
*mojo-app-cache-filename*) *mojo-app-cache-filename*)
@ -405,11 +486,11 @@ Sets `*mojo-target*' to \"tcp\"."
"List of the most recently used package filenames.") "List of the most recently used package filenames.")
;; this is from rails-lib.el in the emacs-rails package ;; this is from rails-lib.el in the emacs-rails package
(defun string-join (separator strings) (defun mojo-string-join (separator strings)
"Join all STRINGS using SEPARATOR." "Join all STRINGS using SEPARATOR."
(mapconcat 'identity strings separator)) (mapconcat 'identity strings separator))
(defun blank (thing) (defun mojo-blank (thing)
"Return T if THING is nil or an empty string, otherwise nil." "Return T if THING is nil or an empty string, otherwise nil."
(or (null thing) (or (null thing)
(and (stringp thing) (and (stringp thing)
@ -429,7 +510,7 @@ The app id is stored in *mojo-package-filename* unless it was blank."
(mojo-package-filename))) (mojo-package-filename)))
(package (read-file-name (format "Package file (default: %s): " default) (package (read-file-name (format "Package file (default: %s): " default)
(concat mojo-build-directory "/") default t))) (concat mojo-build-directory "/") default t)))
(setq *mojo-package-filename* (last-path-component package)) (setq *mojo-package-filename* (mojo-last-path-component package))
(expand-file-name package))) (expand-file-name package)))
(defun mojo-read-app-id (&optional prompt) (defun mojo-read-app-id (&optional prompt)
@ -447,7 +528,7 @@ The app id is stored in *mojo-app-id* unless it was blank."
nil nil
'*mojo-app-history* '*mojo-app-history*
default))) default)))
(when (blank app-id) (when (mojo-blank app-id)
(setq app-id (mojo-app-id))) (setq app-id (mojo-app-id)))
(setq *mojo-app-id* app-id) (setq *mojo-app-id* app-id)
app-id)) app-id))
@ -499,6 +580,124 @@ If the cache file does not exist then it is considered stale."
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; switch to files easily ;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defvar *mojo-switch-suffixes*
'(("-assistant.js" . mojo-switch-to-view)
("-scene.html" . mojo-switch-to-assistant)
(".html" . mojo-switch-to-next-view)
("" . mojo-switch-to-appinfo)))
;;* interactive
(defun mojo-switch-to-appinfo ()
(interactive)
(find-file (concat (mojo-root) "/appinfo.json")))
;;* interactive
(defun mojo-switch-to-index ()
(interactive)
(find-file (concat (mojo-root) "/index.html")))
;;* interactive
(defun mojo-switch-to-sources ()
(interactive)
(find-file (concat (mojo-root) "/sources.json")))
;;* interactive
(defun mojo-switch-to-stylesheet ()
(interactive)
(let* ((stylesheet-dir (concat (mojo-root) "/stylesheets"))
(path (concat stylesheet-dir "/"
(mojo-informal-app-id) ".css")))
(when (not (file-exists-p path))
(setq path (car (mojo-filter-paths (directory-files stylesheet-dir t)))))
(find-file path)))
(defun mojo-scene-name-from-assistant ()
(let ((path (buffer-file-name)))
(substring (mojo-last-path-component path) 0 (- 0 (length "-assistant.js")))))
(defun mojo-scene-name-from-view ()
(mojo-last-path-component (mojo-drop-last-path-component (buffer-file-name))))
;;* interactive
(defun mojo-switch-file-dwim ()
"Determine if the current buffer is visiting a file with known
relationships. Try to find the 'best' choice and switch to it.
This does what I (sjs) mean by default, so change
*mojo-switch-suffixes* if you want different behaviour."
(interactive)
(let* ((path (buffer-file-name))
(suffixes (copy-list *mojo-switch-suffixes*))
(switch-function
(catch 'break
(dolist (pair suffixes nil)
(let ((suffix (car pair))
(function (cdr pair)))
(when (string= suffix (substring path (- 0 (length suffix))))
(throw 'break function)))))))
(when switch-function
(call-interactively switch-function))))
;;* interactive
(defun mojo-switch-to-view ()
(interactive)
(when (mojo-project-p)
(let ((scene-name (mojo-scene-name-from-assistant)))
(find-file (concat (mojo-root)
"/app/views/" scene-name "/"
scene-name "-scene.html")))))
(defun mojo-ignored-path (path)
(let ((filename (mojo-last-path-component path)))
(or (string= (substring filename 0 1) ".")
(and (string= (substring filename 0 1) "#")
(string= (substring filename -1) "#")))))
(defun mojo-filter-paths (all-paths)
(let ((wanted-paths (list)))
(dolist (path all-paths wanted-paths)
(unless (mojo-ignored-path path)
(setq wanted-paths (cons path wanted-paths))))
(reverse wanted-paths)))
(defun mojo-index (elem list)
(catch 'break
(let ((index 0))
(dolist (path list index)
(incf index)
(when (string= path elem)
(throw 'break index))))))
;;* interactive
(defun mojo-switch-to-next-view ()
(interactive)
(when (mojo-project-p)
(let* ((scene-name (mojo-scene-name-from-view))
(view-dir (concat (mojo-root) "/app/views/" scene-name))
(view-files (mojo-filter-paths (directory-files view-dir t)))
(mojo-filter-paths (directory-files (concat (mojo-root) "/app/views/" (mojo-scene-name-from-view)) t))
(index (mojo-index (buffer-file-name) view-files))
(filename (nth (mod index (length view-files)) view-files)))
(find-file filename))))
;;* interactive
(defun mojo-switch-to-assistant ()
(interactive)
(let ((scene-name (mojo-scene-name-from-view)))
(when (and (mojo-project-p)
scene-name)
(find-file (concat (mojo-root)
"/app/assistants/" scene-name "-assistant.js")))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;* lowlevel luna ;;* lowlevel luna
(defun mojo-luna-send (url data) (defun mojo-luna-send (url data)
"Send something through luna. "Send something through luna.
@ -540,7 +739,7 @@ CMD is the name of the command (without path or extension) to execute.
ARGS is a list of all arguments to the command. ARGS is a list of all arguments to the command.
These arguments are NOT shell quoted." These arguments are NOT shell quoted."
(let ((cmd (mojo-path-to-cmd cmd)) (let ((cmd (mojo-path-to-cmd cmd))
(args (string-join " " args))) (args (mojo-string-join " " args)))
(if mojo-debug (message "running %s with args %s " cmd args)) (if mojo-debug (message "running %s with args %s " cmd args))
(shell-command (concat cmd " " args)))) (shell-command (concat cmd " " args))))
@ -566,7 +765,7 @@ ARGS is a list of all arguments to the command.
These arguments are NOT shell quoted." These arguments are NOT shell quoted."
(let ((cmd (mojo-path-to-cmd cmd)) (let ((cmd (mojo-path-to-cmd cmd))
(args (concat "-d " (or target *mojo-target*) " " (args (concat "-d " (or target *mojo-target*) " "
(string-join " " args)))) (mojo-string-join " " args))))
(if mojo-debug (message "running %s with args %s " cmd args)) (if mojo-debug (message "running %s with args %s " cmd args))
(shell-command-to-string (concat cmd " " args)))) (shell-command-to-string (concat cmd " " args))))

View file

@ -1,3 +1,54 @@
(define-minor-mode mojo-mode
"Toggle Mojo mode.
With no argument, this command toggles the mode.
Non-null prefix argument turns on the mode.
Null prefix argument turns off the mode.
When Mojo mode is enabled various commands are enabled to
aid the development of Mojo applications.
Make sure you customize the variables
\\[mojo-project-directory], \\[mojo-sdk-directory] and
\\[mojo-build-directory].
Keybindings are:
* C-c a -- \\[mojo-switch-to-assistant]
* C-c i -- \\[mojo-switch-to-appinfo]
* C-c I -- \\[mojo-switch-to-index]
* C-c n -- \\[mojo-switch-to-next-view]
* C-c s -- \\[mojo-switch-to-sources]
* C-c S -- \\[mojo-switch-to-stylesheet]
* C-c v -- \\[mojo-switch-to-view]
* C-c SPC -- \\[mojo-switch-file-dwim]
* C-c C-e -- \\[mojo-emulate]
* C-c C-p -- \\[mojo-package]
* C-c C-r -- \\[mojo-package-install-and-inspect]
* C-c C-s -- \\[mojo-generate-scene]
* C-c C-t -- \\[mojo-toggle-target]
See the source code mojo.el or the README file for a list of
all of the interactive commands."
;; The initial value.
:init-value nil
;; The indicator for the mode line.
:lighter "-Mojo"
;; The minor mode bindings.
:keymap
'(("\C-ca" . mojo-switch-to-assistant)
("\C-ci" . mojo-switch-to-appinfo)
("\C-cI" . mojo-switch-to-index)
("\C-cn" . mojo-switch-to-next-view)
("\C-cs" . mojo-switch-to-sources)
("\C-cS" . mojo-switch-to-stylesheet)
("\C-cv" . mojo-switch-to-view)
("\C-c " . mojo-switch-file-dwim)
("\C-c\C-e" . mojo-emulate)
("\C-c\C-p" . mojo-package)
("\C-c\C-r" . mojo-package-install-and-inspect)
("\C-c\C-s" . mojo-generate-scene)
("\C-c\C-t" . mojo-toggle-target))
:group 'mojo)
(defcustom mojo-sdk-directory (defcustom mojo-sdk-directory
(case system-type (case system-type
@ -175,19 +226,28 @@ Sets `*mojo-target*' to \"tcp\"."
(setq *mojo-target* "tcp")) (setq *mojo-target* "tcp"))
;;* interactive
(defun mojo-toggle-target ()
"Automatically change the target device from 'usb' to 'tcp' and
vice versa."
(interactive)
(if (string= "usb" *mojo-target*)
(mojo-target-emulator)
(mojo-target-device)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Some support functions that grok the basics of a Mojo project. ;; ;; Some support functions that grok the basics of a Mojo project. ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defun drop-last-path-component (path) (defun mojo-drop-last-path-component (path)
"Get the head of a path by dropping the last component." "Get the head of a path by dropping the last component."
(if (< (length path) 2) (if (< (length path) 2)
path path
(substring path 0 (- (length path) (substring path 0 (- (length path)
(length (last-path-component path)) (length (mojo-last-path-component path))
1)))) ;; subtract one more for the trailing slash 1)))) ;; subtract one more for the trailing slash
(defun last-path-component (path) (defun mojo-last-path-component (path)
"Get the tail of a path, i.e. the last component." "Get the tail of a path, i.e. the last component."
(if (< (length path) 2) (if (< (length path) 2)
path path
@ -201,20 +261,20 @@ Sets `*mojo-target*' to \"tcp\"."
(defun mojo-root () (defun mojo-root ()
"Find a Mojo project's root directory starting with `DEFAULT-DIRECTORY'." "Find a Mojo project's root directory starting with `DEFAULT-DIRECTORY'."
(let ((last-component (last-path-component default-directory)) (let ((last-component (mojo-last-path-component default-directory))
(dir-prefix default-directory)) (dir-prefix default-directory))
;; remove last path element until we find appinfo.json ;; remove last path element until we find appinfo.json
(while (and (not (file-exists-p (concat dir-prefix "/appinfo.json"))) (while (and (not (file-exists-p (concat dir-prefix "/appinfo.json")))
(not (< (length dir-prefix) 2))) (not (< (length dir-prefix) 2)))
(setq last-component (last-path-component dir-prefix)) (setq last-component (mojo-last-path-component dir-prefix))
(setq dir-prefix (drop-last-path-component dir-prefix))) (setq dir-prefix (mojo-drop-last-path-component dir-prefix)))
;; If no Mojo root found, ask for a directory. ;; If no Mojo root found, ask for a directory.
(if (< (length dir-prefix) 2) (if (< (length dir-prefix) 2)
(setq dir-prefix (mojo-read-root))) (setq dir-prefix (mojo-read-root)))
;; Invalidate cached values when changing projects. ;; Invalidate cached values when changing projects.
(if (or (blank *mojo-last-root*) (if (or (mojo-blank *mojo-last-root*)
(not (string= dir-prefix *mojo-last-root*))) (not (string= dir-prefix *mojo-last-root*)))
(progn (progn
(setq *mojo-last-root* dir-prefix) (setq *mojo-last-root* dir-prefix)
@ -223,7 +283,14 @@ Sets `*mojo-target*' to \"tcp\"."
dir-prefix)) dir-prefix))
(defun read-json-file (filename) ;; foolproof?
(defun mojo-project-p ()
(and (file-exists-p (concat (mojo-root) "/appinfo.json"))
(file-exists-p (concat (mojo-root) "/sources.json"))
(file-exists-p (concat (mojo-root) "/app"))
(file-exists-p (concat (mojo-root) "/index.html"))))
(defun mojo-read-json-file (filename)
"Parse the JSON in FILENAME and return the result." "Parse the JSON in FILENAME and return the result."
(save-excursion (save-excursion
(let ((origbuffer (current-buffer)) (let ((origbuffer (current-buffer))
@ -235,13 +302,27 @@ Sets `*mojo-target*' to \"tcp\"."
(defun mojo-app-version () (defun mojo-app-version ()
"Parse the project version from the appinfo.json file in `MOJO-ROOT'." "Parse the project version from the appinfo.json file in `MOJO-ROOT'."
(let ((appinfo (read-json-file (concat (mojo-root) "/appinfo.json")))) (when (mojo-project-p)
(cdr (assoc 'version appinfo)))) (let ((appinfo (mojo-read-json-file (concat (mojo-root) "/appinfo.json"))))
(cdr (assoc 'version appinfo)))))
(defun mojo-app-id () (defun mojo-app-id ()
"Parse the project id from the appinfo.json file in `MOJO-ROOT'." "Parse the project id from the appinfo.json file in `MOJO-ROOT'."
(let ((appinfo (read-json-file (concat (mojo-root) "/appinfo.json")))) (when (mojo-project-p)
(cdr (assoc 'id appinfo)))) (let ((appinfo (mojo-read-json-file (concat (mojo-root) "/appinfo.json"))))
(cdr (assoc 'id appinfo)))))
(defun mojo-app-title ()
"Parse the project title from appinfo.json."
(when (mojo-project-p)
(let ((appinfo (mojo-read-json-file (concat (mojo-root) "/appinfo.json"))))
(cdr (assoc 'title appinfo)))))
(defun mojo-informal-app-id ()
"Parse the project title from appinfo.json and remove all non alphanumeric
characters."
(let ((title (downcase (mojo-app-title))))
(replace-regexp-in-string "[^a-z0-9]" "" title)))
(defun mojo-package-filename () (defun mojo-package-filename ()
"Get the package filename for the specified application." "Get the package filename for the specified application."
@ -263,7 +344,7 @@ Sets `*mojo-target*' to \"tcp\"."
(let ((buffer (find-file-noselect *mojo-app-cache-filename*)) (let ((buffer (find-file-noselect *mojo-app-cache-filename*))
(apps (mojo-fetch-app-list))) (apps (mojo-fetch-app-list)))
(set-buffer buffer) (set-buffer buffer)
(insert (string-join "\n" apps)) (insert (mojo-string-join "\n" apps))
(basic-save-buffer) (basic-save-buffer)
(kill-buffer buffer)))) (kill-buffer buffer))))
*mojo-app-cache-filename*) *mojo-app-cache-filename*)
@ -285,11 +366,11 @@ Sets `*mojo-target*' to \"tcp\"."
"List of the most recently used package filenames.") "List of the most recently used package filenames.")
;; this is from rails-lib.el in the emacs-rails package ;; this is from rails-lib.el in the emacs-rails package
(defun string-join (separator strings) (defun mojo-string-join (separator strings)
"Join all STRINGS using SEPARATOR." "Join all STRINGS using SEPARATOR."
(mapconcat 'identity strings separator)) (mapconcat 'identity strings separator))
(defun blank (thing) (defun mojo-blank (thing)
"Return T if THING is nil or an empty string, otherwise nil." "Return T if THING is nil or an empty string, otherwise nil."
(or (null thing) (or (null thing)
(and (stringp thing) (and (stringp thing)
@ -309,7 +390,7 @@ The app id is stored in *mojo-package-filename* unless it was blank."
(mojo-package-filename))) (mojo-package-filename)))
(package (read-file-name (format "Package file (default: %s): " default) (package (read-file-name (format "Package file (default: %s): " default)
(concat mojo-build-directory "/") default t))) (concat mojo-build-directory "/") default t)))
(setq *mojo-package-filename* (last-path-component package)) (setq *mojo-package-filename* (mojo-last-path-component package))
(expand-file-name package))) (expand-file-name package)))
(defun mojo-read-app-id (&optional prompt) (defun mojo-read-app-id (&optional prompt)
@ -327,7 +408,7 @@ The app id is stored in *mojo-app-id* unless it was blank."
nil nil
'*mojo-app-history* '*mojo-app-history*
default))) default)))
(when (blank app-id) (when (mojo-blank app-id)
(setq app-id (mojo-app-id))) (setq app-id (mojo-app-id)))
(setq *mojo-app-id* app-id) (setq *mojo-app-id* app-id)
app-id)) app-id))
@ -379,6 +460,124 @@ If the cache file does not exist then it is considered stale."
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; switch to files easily ;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defvar *mojo-switch-suffixes*
'(("-assistant.js" . mojo-switch-to-view)
("-scene.html" . mojo-switch-to-assistant)
(".html" . mojo-switch-to-next-view)
("" . mojo-switch-to-appinfo)))
;;* interactive
(defun mojo-switch-to-appinfo ()
(interactive)
(find-file (concat (mojo-root) "/appinfo.json")))
;;* interactive
(defun mojo-switch-to-index ()
(interactive)
(find-file (concat (mojo-root) "/index.html")))
;;* interactive
(defun mojo-switch-to-sources ()
(interactive)
(find-file (concat (mojo-root) "/sources.json")))
;;* interactive
(defun mojo-switch-to-stylesheet ()
(interactive)
(let* ((stylesheet-dir (concat (mojo-root) "/stylesheets"))
(path (concat stylesheet-dir "/"
(mojo-informal-app-id) ".css")))
(when (not (file-exists-p path))
(setq path (car (mojo-filter-paths (directory-files stylesheet-dir t)))))
(find-file path)))
(defun mojo-scene-name-from-assistant ()
(let ((path (buffer-file-name)))
(substring (mojo-last-path-component path) 0 (- 0 (length "-assistant.js")))))
(defun mojo-scene-name-from-view ()
(mojo-last-path-component (mojo-drop-last-path-component (buffer-file-name))))
;;* interactive
(defun mojo-switch-file-dwim ()
"Determine if the current buffer is visiting a file with known
relationships. Try to find the 'best' choice and switch to it.
This does what I (sjs) mean by default, so change
*mojo-switch-suffixes* if you want different behaviour."
(interactive)
(let* ((path (buffer-file-name))
(suffixes (copy-list *mojo-switch-suffixes*))
(switch-function
(catch 'break
(dolist (pair suffixes nil)
(let ((suffix (car pair))
(function (cdr pair)))
(when (string= suffix (substring path (- 0 (length suffix))))
(throw 'break function)))))))
(when switch-function
(call-interactively switch-function))))
;;* interactive
(defun mojo-switch-to-view ()
(interactive)
(when (mojo-project-p)
(let ((scene-name (mojo-scene-name-from-assistant)))
(find-file (concat (mojo-root)
"/app/views/" scene-name "/"
scene-name "-scene.html")))))
(defun mojo-ignored-path (path)
(let ((filename (mojo-last-path-component path)))
(or (string= (substring filename 0 1) ".")
(and (string= (substring filename 0 1) "#")
(string= (substring filename -1) "#")))))
(defun mojo-filter-paths (all-paths)
(let ((wanted-paths (list)))
(dolist (path all-paths wanted-paths)
(unless (mojo-ignored-path path)
(setq wanted-paths (cons path wanted-paths))))
(reverse wanted-paths)))
(defun mojo-index (elem list)
(catch 'break
(let ((index 0))
(dolist (path list index)
(incf index)
(when (string= path elem)
(throw 'break index))))))
;;* interactive
(defun mojo-switch-to-next-view ()
(interactive)
(when (mojo-project-p)
(let* ((scene-name (mojo-scene-name-from-view))
(view-dir (concat (mojo-root) "/app/views/" scene-name))
(view-files (mojo-filter-paths (directory-files view-dir t)))
(mojo-filter-paths (directory-files (concat (mojo-root) "/app/views/" (mojo-scene-name-from-view)) t))
(index (mojo-index (buffer-file-name) view-files))
(filename (nth (mod index (length view-files)) view-files)))
(find-file filename))))
;;* interactive
(defun mojo-switch-to-assistant ()
(interactive)
(let ((scene-name (mojo-scene-name-from-view)))
(when (and (mojo-project-p)
scene-name)
(find-file (concat (mojo-root)
"/app/assistants/" scene-name "-assistant.js")))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;* lowlevel luna ;;* lowlevel luna
(defun mojo-luna-send (url data) (defun mojo-luna-send (url data)
"Send something through luna. "Send something through luna.
@ -420,7 +619,7 @@ CMD is the name of the command (without path or extension) to execute.
ARGS is a list of all arguments to the command. ARGS is a list of all arguments to the command.
These arguments are NOT shell quoted." These arguments are NOT shell quoted."
(let ((cmd (mojo-path-to-cmd cmd)) (let ((cmd (mojo-path-to-cmd cmd))
(args (string-join " " args))) (args (mojo-string-join " " args)))
(if mojo-debug (message "running %s with args %s " cmd args)) (if mojo-debug (message "running %s with args %s " cmd args))
(shell-command (concat cmd " " args)))) (shell-command (concat cmd " " args))))
@ -446,7 +645,7 @@ ARGS is a list of all arguments to the command.
These arguments are NOT shell quoted." These arguments are NOT shell quoted."
(let ((cmd (mojo-path-to-cmd cmd)) (let ((cmd (mojo-path-to-cmd cmd))
(args (concat "-d " (or target *mojo-target*) " " (args (concat "-d " (or target *mojo-target*) " "
(string-join " " args)))) (mojo-string-join " " args))))
(if mojo-debug (message "running %s with args %s " cmd args)) (if mojo-debug (message "running %s with args %s " cmd args))
(shell-command-to-string (concat cmd " " args)))) (shell-command-to-string (concat cmd " " args))))

View file

@ -2,6 +2,6 @@
"title": "mojo.el", "title": "mojo.el",
"filename": "../mojo.el", "filename": "../mojo.el",
"basename": "mojo.el", "basename": "mojo.el",
"version": "0.9.5", "version": "0.9.6",
"template": "template.el" "template": "template.el"
} }