├── .gitignore ├── README.md ├── Cargo.toml ├── examples └── basic.rs ├── LICENSE └── src └── lib.rs /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rust-osascript 2 | 3 | This library implements a simplified wrapper around the OSA system on OS X 4 | to remote control applications with JavaScript. 5 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "osascript" 3 | version = "0.3.0" 4 | authors = ["Armin Ronacher "] 5 | keywords = ["applescript", "osascript", "javascript"] 6 | description = "Provides simplified access to JavaScript via OSA on macOS." 7 | homepage = "https://github.com/mitsuhiko/rust-osascript" 8 | documentation = "https://docs.rs/osascript/" 9 | license = "BSD-3-Clause" 10 | readme = "README.md" 11 | 12 | [dependencies] 13 | serde = "1" 14 | serde_json = "1" 15 | serde_derive = "1" 16 | -------------------------------------------------------------------------------- /examples/basic.rs: -------------------------------------------------------------------------------- 1 | extern crate osascript; 2 | #[macro_use] extern crate serde_derive; 3 | 4 | use osascript::JavaScript; 5 | 6 | #[derive(Serialize)] 7 | struct AlertParams { 8 | title: String, 9 | message: String, 10 | alert_type: String, 11 | buttons: Vec, 12 | } 13 | 14 | #[derive(Deserialize)] 15 | struct AlertResult { 16 | #[serde(rename="buttonReturned")] 17 | button: String, 18 | } 19 | 20 | fn main() { 21 | let script = JavaScript::new(" 22 | var App = Application('Finder'); 23 | App.includeStandardAdditions = true; 24 | return App.displayAlert($params.title, { 25 | message: $params.message, 26 | 'as': $params.alert_type, 27 | buttons: $params.buttons, 28 | }); 29 | "); 30 | 31 | let rv: AlertResult = script.execute_with_params(AlertParams { 32 | title: "Shit is on fire!".into(), 33 | message: "What is happening".into(), 34 | alert_type: "critical".into(), 35 | buttons: vec![ 36 | "Show details".into(), 37 | "Ignore".into(), 38 | ] 39 | }).unwrap(); 40 | 41 | println!("You clicked '{}'", rv.button); 42 | } 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 by Armin Ronacher. 2 | 3 | Some rights reserved. 4 | 5 | Redistribution and use in source and binary forms of the software as well 6 | as documentation, with or without modification, are permitted provided 7 | that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright 10 | notice, this list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above 13 | copyright notice, this list of conditions and the following 14 | disclaimer in the documentation and/or other materials provided 15 | with the distribution. 16 | 17 | * The names of the contributors may not be used to endorse or 18 | promote products derived from this software without specific 19 | prior written permission. 20 | 21 | THIS SOFTWARE AND DOCUMENTATION IS PROVIDED BY THE COPYRIGHT HOLDERS AND 22 | CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT 23 | NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 24 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER 25 | OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 26 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 27 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 28 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 29 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 30 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 31 | SOFTWARE AND DOCUMENTATION, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 32 | DAMAGE. 33 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This library implements limited functionality for the OSA System on macOS. 2 | //! In particular it allows you to execute JavaScript via the OSA system to 3 | //! script applications. It's particularly useful if you need to tell other 4 | //! applications to execute certain functionality. 5 | //! 6 | //! Currently only JavaScript is supported. Parameters passed to it show up 7 | //! as `$params` and the return value from the script (as returned with the 8 | //! `return` keyword) is deserialized later. 9 | //! 10 | //! # Example 11 | //! 12 | //! ``` 13 | //! extern crate osascript; 14 | //! #[macro_use] extern crate serde_derive; 15 | //! 16 | //! #[derive(Serialize)] 17 | //! struct AlertParams { 18 | //! title: String, 19 | //! message: String, 20 | //! alert_type: String, 21 | //! buttons: Vec, 22 | //! } 23 | //! 24 | //! #[derive(Deserialize)] 25 | //! struct AlertResult { 26 | //! #[serde(rename="buttonReturned")] 27 | //! button: String, 28 | //! } 29 | //! 30 | //! fn main() { 31 | //! let script = osascript::JavaScript::new(" 32 | //! var App = Application('Finder'); 33 | //! App.includeStandardAdditions = true; 34 | //! return App.displayAlert($params.title, { 35 | //! message: $params.message, 36 | //! 'as': $params.alert_type, 37 | //! buttons: $params.buttons, 38 | //! }); 39 | //! "); 40 | //! 41 | //! let rv: AlertResult = script.execute_with_params(AlertParams { 42 | //! title: "Shit is on fire!".into(), 43 | //! message: "What is happening".into(), 44 | //! alert_type: "critical".into(), 45 | //! buttons: vec![ 46 | //! "Show details".into(), 47 | //! "Ignore".into(), 48 | //! ] 49 | //! }).unwrap(); 50 | //! 51 | //! println!("You clicked '{}'", rv.button); 52 | //! } 53 | //! ``` 54 | use std::process; 55 | use std::io; 56 | use std::fmt; 57 | use std::string::FromUtf8Error; 58 | use std::io::Write; 59 | use std::error; 60 | 61 | extern crate serde; 62 | extern crate serde_json; 63 | #[macro_use] extern crate serde_derive; 64 | 65 | use serde::Serialize; 66 | use serde::de::DeserializeOwned; 67 | 68 | /// The error from the script system 69 | #[derive(Debug)] 70 | pub enum Error { 71 | Io(io::Error), 72 | Json(serde_json::Error), 73 | Script(String), 74 | } 75 | 76 | /// Holds an apple flavoured JavaScript 77 | pub struct JavaScript { 78 | code: String, 79 | } 80 | 81 | impl From for Error { 82 | fn from(err: io::Error) -> Error { 83 | Error::Io(err) 84 | } 85 | } 86 | 87 | impl From for Error { 88 | fn from(err: serde_json::Error) -> Error { 89 | Error::Json(err) 90 | } 91 | } 92 | 93 | impl From for Error { 94 | fn from(err: FromUtf8Error) -> Error { 95 | Error::Script(format!("UTF-8 Error: {}", err)) 96 | } 97 | } 98 | 99 | impl error::Error for Error { 100 | fn description(&self) -> &str { 101 | match *self { 102 | Error::Io(ref err) => err.description(), 103 | Error::Json(ref err) => err.description(), 104 | Error::Script(..) => "script error", 105 | } 106 | } 107 | } 108 | 109 | impl fmt::Display for Error { 110 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 111 | match *self { 112 | Error::Io(ref err) => write!(f, "script io error: {}", err), 113 | Error::Json(ref err) => write!(f, "script json error: {}", err), 114 | Error::Script(ref msg) => write!(f, "script error: {}", msg), 115 | } 116 | } 117 | } 118 | 119 | #[derive(Serialize)] 120 | struct EmptyParams {} 121 | 122 | fn wrap_code(code: &str, params: S) -> Result { 123 | let mut buf: Vec = vec![]; 124 | write!(&mut buf, "var $params = ")?; 125 | serde_json::to_writer(&mut buf, ¶ms)?; 126 | write!(&mut buf, ";JSON.stringify((function() {{{};return null;}})());", code)?; 127 | Ok(String::from_utf8(buf)?) 128 | } 129 | 130 | impl JavaScript { 131 | /// Creates a new script from the given code. 132 | pub fn new(code: &str) -> JavaScript { 133 | JavaScript { 134 | code: code.to_string(), 135 | } 136 | } 137 | 138 | /// Executes the script and does not pass any arguments. 139 | pub fn execute<'a, D: DeserializeOwned>(&self) -> Result { 140 | self.execute_with_params(EmptyParams {}) 141 | } 142 | 143 | /// Executes the script and passes the provided arguments. 144 | pub fn execute_with_params<'a, S: Serialize, D: DeserializeOwned>(&self, params: S) 145 | -> Result 146 | { 147 | let wrapped_code = wrap_code(&self.code, params)?; 148 | let output = process::Command::new("osascript") 149 | .arg("-l") 150 | .arg("JavaScript") 151 | .arg("-e") 152 | .arg(&wrapped_code) 153 | .output()?; 154 | if output.status.success() { 155 | Ok(serde_json::from_slice(&output.stdout)?) 156 | } else { 157 | Err(Error::Script(String::from_utf8(output.stderr)?)) 158 | } 159 | } 160 | } 161 | --------------------------------------------------------------------------------