├── package.json ├── package.yml ├── Test ├── Safari.reloadCSS.js ├── Safari.demo.node.js ├── Safari.setCSS.js └── Safari.exec.js ├── README.md ├── lib ├── escape.js └── cli-app.js └── Source ├── AppleScriptApp-node.js └── Safari.js /package.json: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /package.yml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Test/Safari.reloadCSS.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node #// -*- Mode: Node.js JavaScript; tab-width: 4; -*- 2 | (new (require('AppleScript/Source/Safari').Safari)).exec("reloadCSS"); 3 | -------------------------------------------------------------------------------- /Test/Safari.demo.node.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node #// -*- Mode: Node.js JavaScript; tab-width: 4; -*- 2 | 3 | require.paths.unshift('../Source'); 4 | var sys = require('sys'); 5 | var Safari = new (require('Safari').Safari); 6 | 7 | 8 | 9 | Safari.exec("document.getElementsByTagName('div').length"); 10 | 11 | 12 | 13 | Safari.exec(function(){ 14 | return [].map.call(document.querySelectorAll('[href]'),function(a){return a.href}).sort().join('\n'); 15 | },function(error,stdout,stderr){ 16 | sys.print(stdout); 17 | }); 18 | 19 | 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | AppleScript for JavaScript *(Node.js)* 2 | ========================== 3 | #### FIXME: Make it work with Narwhal too! 4 | 5 | 6 | Install like a nerd 7 | ------------------- 8 | 9 | mkdir ~/.node_libraries 10 | cd !$ 11 | git clone git://github.com/subtleGradient/AppleScript.js.git AppleScript 12 | 13 | 14 | Try it out 15 | ---------- 16 | 17 | open http://subtlegradient.com 18 | ~/.node_libraries/AppleScript/Test/Safari.reloadCSS.js 19 | 20 | 21 | How do I make my own awesome AppleScript Apps for Node.js?! 22 | ----------------------------------------------------------- 23 | 24 | 1. Read the source code of `AppleScript/Source/Safari.js` 25 | 2. Do stuff like that 26 | 3. … 27 | 4. *Profit!* 28 | -------------------------------------------------------------------------------- /Test/Safari.setCSS.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node #// -*- Mode: Node.js JavaScript; tab-width: 4; -*- 2 | // 3 | // REPL 4 | // Safari.exec 5 | // document.documentElement.outerHTML 6 | // document.documentElement.childNodes.length 7 | // ^D 8 | // Bye! 9 | // 10 | // STDIN 11 | // echo 'document.documentElement.outerHTML'|Safari.exec 12 | // 13 | // ARGV 14 | // Safari.exec document.documentElement.outerHTML 15 | // 16 | 17 | var Safari = new (require('AppleScript/Source/Safari').Safari); 18 | 19 | var CSS = ''; 20 | 21 | var stdin = process.openStdin(); 22 | stdin.setEncoding('utf8'); 23 | stdin.addListener('data', function(chunk){ 24 | CSS += chunk; 25 | }); 26 | stdin.addListener('end', function(){ 27 | 28 | Safari.setCSS(CSS); 29 | 30 | require('sys').debug('\ 31 | \n\ 32 | Setting the CSS…\ 33 | \n\ 34 | '); 35 | }); 36 | -------------------------------------------------------------------------------- /Test/Safari.exec.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node #// -*- Mode: Node.js JavaScript; tab-width: 4; -*- 2 | // 3 | // REPL 4 | // Safari.exec 5 | // document.documentElement.outerHTML 6 | // document.documentElement.childNodes.length 7 | // ^D 8 | // Bye! 9 | // 10 | // STDIN 11 | // echo 'document.documentElement.outerHTML'|Safari.exec 12 | // 13 | // ARGV 14 | // Safari.exec document.documentElement.outerHTML 15 | // 16 | 17 | var Safari = new (require('AppleScript/Source/Safari').Safari); 18 | 19 | var commands = process.ARGV.slice(2); 20 | if (commands && commands.length) 21 | commands.forEach(Safari.execSync); 22 | 23 | else { 24 | var stdin = process.openStdin(); 25 | stdin.setEncoding('utf8'); 26 | stdin.addListener('data', Safari.execSync); 27 | stdin.addListener('end', function(){ 28 | process.stdout.write('\nBye!\n'); 29 | }); 30 | } 31 | -------------------------------------------------------------------------------- /lib/escape.js: -------------------------------------------------------------------------------- 1 | /* 2 | --- 3 | name : escape 4 | description : escape escapes strings 5 | 6 | authors : Thomas Aylott 7 | copyright : © 2010 Thomas Aylott 8 | license : MIT 9 | 10 | provides: 11 | - escapeShell 12 | - escapeAppleScript 13 | ... 14 | */ 15 | 16 | (function(exports){ 17 | 18 | 19 | var String = exports.String || (exports.String = function(){}); 20 | 21 | // escape text to make it usable in a shell script as one “word” (string) 22 | // Based on TextMate.app/Contents/SharedSupport/Support/lib/escape.rb e_sh 23 | String.escapeShell = function(str){ 24 | // Ruby: str.to_s.gsub(/(?=[^a-zA-Z0-9_.\/\-\x7F-\xFF\n])/n, '\\').gsub(/\n/, "'\n'").sub(/^$/, "''") 25 | return (''+str).replace(/(?=[^a-zA-Z0-9_.\/\-\x7F-\xFF\n])/gm, '\\').replace(/\n/g, "'\n'").replace(/^$/, "''"); 26 | } 27 | 28 | // escape text for use in an AppleScript string 29 | // Based on TextMate.app/Contents/SharedSupport/Support/lib/escape.rb e_as 30 | String.escapeAppleScript = function(str){ 31 | // Ruby: str.to_s.gsub(/(?=["\\])/, '\\') 32 | return (''+str).replace(/(?=["\\])/g, '\\'); 33 | } 34 | 35 | String.escapeJS = function(str){ 36 | return (''+str).replace(/(?=["\\])/g, '\\').replace(/\n/g, '\\n'); 37 | } 38 | 39 | 40 | }(typeof exports != 'undefined' ? exports : this)); 41 | -------------------------------------------------------------------------------- /lib/cli-app.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node #// -*- Mode: Node.js JavaScript; tab-width: 4; -*- 2 | 3 | /* 4 | --- 5 | name : cli-app 6 | description : cli-app lets you convert a standard JavaScript object or class into a CLI app 7 | 8 | authors : Thomas Aylott 9 | copyright : © 2010 Thomas Aylott 10 | license : MIT 11 | 12 | provides : CLIApp 13 | requires : Node.js/sys 14 | ... 15 | */ 16 | 17 | var CLIApp = (function(exports){ 18 | 19 | exports.CLIApp = CLIApp; 20 | 21 | function CLIApp(cliMe){ 22 | if (!(this instanceof CLIApp)) return CLIApp.from(cliMe); 23 | } 24 | CLIApp.from = function(cliMe){ 25 | 26 | }; 27 | CLIApp.fromClass = function(cliMe){ 28 | 29 | }; 30 | CLIApp.fromObject = function(cliMe){ 31 | 32 | }; 33 | 34 | 35 | return exports; 36 | }(typeof exports != 'undefined' ? exports : CLIApp)) 37 | .CLIApp; 38 | 39 | if (require.main == module) { 40 | 41 | function CLIAppDemo(){ 42 | 43 | } 44 | CLIAppDemo.prototype = new CLIApp; 45 | if (require.main == module) new CLIAppDemo; 46 | 47 | } 48 | 49 | 50 | if (require.main == module) { 51 | 52 | function Person(){} 53 | 54 | Person.help = "Person totally rocks socks, bro!"; 55 | 56 | Person.prototype = 57 | 58 | { 'jump help':"Command your Person to jump!" 59 | , jump: function(){ 60 | this. 61 | return this; 62 | } 63 | , 'dance help':"Command your Person to dance!" 64 | , dance: function(){ 65 | return this; 66 | } 67 | }; 68 | 69 | Person.prototype.jumpSuccess = "jumped"; 70 | Person.prototype.jumpFail = "failed jumping"; 71 | 72 | 73 | } -------------------------------------------------------------------------------- /Source/AppleScriptApp-node.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node #// -*- Mode: Node.js JavaScript; tab-width: 4; -*- 2 | /* 3 | --- 4 | name : AppleScriptApp.node 5 | description : AppleScriptApp.node lets you control AppleScript apps using osascript using Node.js 6 | 7 | authors : Thomas Aylott 8 | copyright : © 2010 Thomas Aylott 9 | license : MIT 10 | 11 | provides: 12 | - AppleScriptApp 13 | 14 | requires: 15 | - Node.js/sys.p 16 | - Node.js/sys.print 17 | - Node.js/sys.debug 18 | - Node.js/child_process.exec 19 | - escapeShell 20 | 21 | environment: 22 | - osx 23 | - osascript 24 | - Safari.app 25 | ... 26 | */ 27 | (function(exports){ 28 | var sys = require('sys'); 29 | var escape = require('../lib/escape'); 30 | 31 | 32 | exports.AppleScriptApp = AppleScriptApp; 33 | 34 | function AppleScriptApp(appName){ 35 | if (!(this instanceof AppleScriptApp)) throw new Error("Usage: `new AppleScriptApp(appName:String)`"); 36 | this.appName = ''+appName; 37 | } 38 | 39 | AppleScriptApp.prototype.__exec__ = require('child_process').exec; 40 | // AppleScriptApp.prototype.__exec__ = require('sys').print; 41 | 42 | AppleScriptApp.prototype.tell = function(appleScript, callback){ 43 | var command = 'osascript -e ' + 44 | [ escape.String.escapeShell('tell application "' + this.appName + '"') 45 | , escape.String.escapeShell(appleScript) 46 | , 'end tell' 47 | ].join(' -e ') 48 | ; 49 | 50 | if (this.debugLevel == 'debug') sys.debug(command); 51 | 52 | return this.__exec__( 53 | 54 | command 55 | , 56 | { encoding: 'utf8' 57 | , timeout: 120 *1000 58 | , maxBuffer: 10240 *1024 59 | //, killSignal: 'SIGKILL' 60 | } 61 | , 62 | callback || this.handleExec 63 | 64 | ); 65 | } 66 | 67 | AppleScriptApp.prototype.handleExec = function(error, stdout, stderr){ 68 | sys.print(stdout); 69 | if (this.debugLevel == 'debug') sys.debug(stderr); 70 | if (error !== null) sys.p(error); 71 | }; 72 | 73 | 74 | }(typeof exports != 'undefined' ? exports : this)); 75 | -------------------------------------------------------------------------------- /Source/Safari.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node #// -*- Mode: Node.js JavaScript; tab-width: 4; -*- 2 | 3 | var Safari = 4 | (function(exports){ 5 | 6 | 7 | var hasOwnProperty = {}.hasOwnProperty; 8 | var escape = require('../lib/escape'); 9 | var AppleScriptApp = require('./AppleScriptApp-node').AppleScriptApp; 10 | 11 | 12 | function uneval(fn){ 13 | fn = ''+fn; 14 | var string = ''; 15 | 16 | if (/^function\b/.test(fn)) string += '(' + fn + ')()'; 17 | else return fn; 18 | return string; 19 | } 20 | 21 | 22 | 23 | exports.Safari = Safari; 24 | 25 | function Safari(){ 26 | if (!(this instanceof Safari)) throw new Error("Usage: `new Safari`"); 27 | var self = this; 28 | this.execSync = function(js){ 29 | return self.exec(js); 30 | }; 31 | } 32 | 33 | Safari.prototype.debugLevel = ''; 34 | 35 | Safari.prototype = new AppleScriptApp('Safari'); 36 | 37 | Safari.prototype.exec = 38 | Safari.prototype.evalJS = 39 | Safari.prototype.doJavaScript = function(js, callback){ 40 | js = uneval(Safari.command[js] || js); 41 | 42 | return this.tell 43 | ( 'tell the first document to do javascript "' 44 | + escape.String.escapeAppleScript(js) 45 | + '"' 46 | , callback 47 | ); 48 | } 49 | 50 | Safari.prototype.get = function(propertyName){ 51 | return this.tell('get '+ propertyName +' of the first document'); 52 | } 53 | 54 | Safari.prototype.getHTML = function(){ 55 | var HTML = ''; 56 | HTML += docType; 57 | HTML += this.doJavaScript('getHTML'); 58 | return HTML; 59 | }; 60 | 61 | 62 | 63 | Safari.command = {}; 64 | 65 | Safari.command.getHTML = function(){ 66 | return document.documentElement.outerHTML; 67 | }; 68 | 69 | Safari.command.reload = function(){ 70 | return document.location.reload(); 71 | }; 72 | 73 | Safari.command.reloadCSS = function(){ 74 | return Array.prototype.map.call(document.querySelectorAll('link[rel=stylesheet][href]'),function(link){ 75 | var href = link.href; 76 | href = href.replace(/(&|\?)forceReload=\d+/g, '$1'); 77 | href += (1+href.indexOf('?') ? '&' : '?') + 'forceReload=' + (+new Date) 78 | link.href = href; 79 | return href; 80 | }).join('\n'); 81 | }; 82 | 83 | Safari.command.removeCSS = function(){ 84 | return Array.prototype.map.call(document.querySelectorAll('link[rel=stylesheet][href],style'),function(css){ 85 | css.disabled = true; 86 | css.parentNode.removeChild(css); 87 | return css; 88 | }).join('\n'); 89 | }; 90 | Safari.command.addCSS = function(){ 91 | 92 | /* 93 | --- 94 | name : Sheet.DOM 95 | description : Sheet.DOM adds some handy stuff for working with the browser's native CSS capabilities. 96 | 97 | authors : Thomas Aylott 98 | copyright : © 2010 Thomas Aylott 99 | license : MIT 100 | 101 | provides : Sheet.DOM 102 | ... 103 | */ 104 | 105 | if (typeof Sheet == 'undefined') var Sheet = {}; 106 | if (Sheet.DOM == null) Sheet.DOM = {}; 107 | 108 | window.Sheet = Sheet; 109 | 110 | Sheet.DOM.createSheet = function(raw){ 111 | var oldLength = document.styleSheets.length; 112 | 113 | if (document.createStyleSheet){ 114 | document.createStyleSheet(); 115 | document.styleSheets[document.styleSheets.length - 1].cssText = raw; 116 | } 117 | 118 | if (oldLength >= document.styleSheets.length){ 119 | var style = document.createElement('style'); 120 | style.setAttribute('type','text/css'); 121 | style.appendChild(document.createTextNode(raw)); 122 | document.getElementsByTagName('head')[0].appendChild(style); 123 | } 124 | 125 | if (oldLength >= document.styleSheets.length){ 126 | var style = document.createElement('div'); 127 | style.innerHTML = ''; 128 | document.getElementsByTagName('head')[0].appendChild(style); 129 | } 130 | 131 | if (oldLength >= document.styleSheets.length) 132 | throw new Error('no styleSheet added :('); 133 | 134 | var sheet = document.styleSheets[document.styleSheets.length - 1]; 135 | sheet.cssText = raw; 136 | 137 | return sheet; 138 | }; 139 | 140 | // Sheet.DOM.createSheet = function(raw){ 141 | // var style = document.createElement('style'); 142 | // style.setAttribute('type','text/css'); 143 | // style.appendChild(document.createTextNode(raw)); 144 | // document.getElementsByTagName('head')[0].appendChild(style); 145 | // 146 | // return document.styleSheets[document.styleSheets.length - 1]; 147 | // }; 148 | 149 | Sheet.DOM.createStyle = function(raw){ 150 | var div = document.createElement('div'); 151 | div.innerHTML = '
'; 152 | return { style:div.firstChild.style }; 153 | }; 154 | }; 155 | Safari.prototype.setCSS = function(css){ 156 | this.exec([uneval(Safari.command.addCSS), uneval(Safari.command.removeCSS), uneval('Sheet.DOM.createSheet("' + escape.String.escapeJS(css) + '")')].join(';')); 157 | }; 158 | 159 | 160 | return exports; 161 | }(typeof exports != 'undefined' ? exports : this)) 162 | .Safari; 163 | 164 | 165 | if (require.main == module) { 166 | var safari = new Safari; 167 | 168 | //require('cli-app') 169 | 170 | safari.doJavaScript(function(){ 171 | alert("Hello Safari! — Love, Node.js"); 172 | }); 173 | //safari.doJavaScript('reload'); 174 | 175 | } 176 | --------------------------------------------------------------------------------