PureScript で DOM を操作
PureScript の下記ライブラリを使って簡単な DOM 操作を試してみました。
ソースは http://github.com/fits/try_samples/tree/master/blog/20160125/
はじめに
PureScript を使って実装するものと同等の処理を JavaScript で書いてみました。 id で指定した DOM ノードの textContent を変更するだけの簡単な処理です。
sample.js
var Sample = { updateContent: (id, content) => { var node = document.getElementById(id); if (node) { node.textContent = content; } } };
下記の HTML で実行してみます。 (PureScript の方は updateContent
の呼び出し部分が少し異なります)
index.html
<!DOCTYPE html> <html> <body> <h2 id="d"></h2> <script src="sample.js"></script> <script> Sample.updateContent('d', 'sample javascript'); </script> </body> </html>
実行結果
Web ブラウザで表示した結果は以下の通りです。
purescript-dom の場合
pulp init
でプロジェクトを作成し、pulp dep install
で purescript-dom
をインストールします。
なお、pulp と gulp を事前にインストール (npm install
) しておきます。
purescript-dom インストール
> pulp init ・・・ > pulp dep install purescript-dom --save
src/Main.purs を編集し updateContent
関数を実装します。
以下のように型まわりに注意が必要です。
- (a) document 関数は Eff (dom :: DOM | eff) HTMLDocument を返す
- (b) getElementById 関数の引数は ElementId と NonElementParentNode
- (c) getElementById 関数は Eff (dom :: DOM | eff) (Nullable Element) を返す
- (d) setTextContent 関数の引数は String と Node
(a) の結果の HTMLDocument を getElementById の引数へそのまま使えなかったので htmlDocumentToNonElementParentNode
関数で変換しています。
(c) の結果の Nullable はそのままだと使い難いので toMaybe
関数で Maybe 化し、Element も setTextContent
の引数に使えなかったので elementToNode
関数で変換しています。
src/Main.purs
module Main where import Prelude import Control.Monad.Eff import Data.Maybe import Data.Nullable (toMaybe) import DOM import DOM.HTML.Types import DOM.Node.Types import DOM.HTML (window) import DOM.HTML.Window (document) import DOM.Node.NonElementParentNode (getElementById) import DOM.Node.Node (setTextContent) updateContent :: forall eff. String -> String -> Eff (dom :: DOM | eff) Unit updateContent id content = do win <- window doc <- document win node <- getElementById (ElementId id) $ htmlDocumentToNonElementParentNode doc case (toMaybe node) of Just x -> setTextContent content (elementToNode x) _ -> return unit
今回は、gulp を使って pulp browserify
を実行するように以下のような gulpfile.js を用意しました。
Sample.updateContent
で関数を実行できるように --standalone
を指定しています。
gulpfile.js
var gulp = require('gulp'); var child_process = require('child_process'); var pulpCmd = (process.platform == 'win32')? 'pulp.cmd': 'pulp'; var destFile = 'sample.js' gulp.task('pulp_package', () => { // pulp browserify の実行 var res = child_process.spawnSync(pulpCmd, ['browserify', '--standalone', 'Sample', '-t', destFile]); // 実行結果の出力 [res.stdin, res.stdout, res.stderr].forEach( x => { if (x) { console.log(x.toString()); } }); }); gulp.task('default', ['pulp_package']);
gulp
コマンドを実行すると sample.js が生成されます。
ビルド例 (gulp で pulp browserify を実行)
> gulp
下記の HTML で実行してみます。
ここで、Sample.updateContent
はカリー化されており function(id) { return function(content) { return function __do() { ・・・ } } }
となっている点に注意。
index.html
<!DOCTYPE html> <html> <body> <h2 id="d"></h2> <script src="sample.js"></script> <script> Sample.updateContent('d')('sample purescript-dom')(); </script> </body> </html>
実行結果
purescript-simple-dom の場合
同じ様にして purescript-simple-dom
をインストールします。
purescript-simple-dom インストール
> pulp init ・・・ > pulp dep install purescript-simple-dom --save
src/Main.purs を編集し updateContent
関数を実装します。
purescript-dom と比べると余計な型変換が不要なのでシンプルです。
src/Main.purs
module Main where import Prelude import Control.Monad.Eff import DOM import Data.Maybe import Data.DOM.Simple.Window (document, globalWindow) import Data.DOM.Simple.Element (getElementById, setTextContent) updateContent :: forall eff. String -> String -> Eff (dom :: DOM | eff) Unit updateContent id content = do doc <- document globalWindow node <- getElementById id doc case node of Just x -> setTextContent content x _ -> return unit
purescript-dom と同じ様に gulp で sample.js を生成しました。(gulpfile.js は同じ内容です)
HTML は以下の通りです。
index.html
<!DOCTYPE html> <html> <body> <h2 id="d"></h2> <script src="sample.js"></script> <script> Sample.updateContent('d')('sample purescript-simple-dom')(); </script> </body> </html>
実行結果
virtual-dom のイベント処理
仮想 DOM を扱うための JavaScript 用ライブラリ virtual-dom では ev-xxx (例. ev-click
) でイベント処理を扱えるようになっていますが、実際に機能させるには dom-delegator が必要なようです。
virtual-dom は bower install が可能で Web ブラウザ上で直接使えるファイル (dist/virtual-dom.js) を用意してくれていますが、今のところ dom-delegator はそうなっていません。(dom-delegator は npm install する)
名称 | bower install | Web ブラウザで直接使用 |
---|---|---|
virtual-dom | ○ | ○ |
dom-delegator | × | × |
そこで、今回は下記ツールを用いて virtual-dom で ev-click
が機能するようにしてみます。
事前準備
Gulp・Bower・Browserify をインストールします。
インストール例
> npm install -g gulp bower browserify
virtual-dom のイベント処理サンプル
virtual-dom で ev-click を実施するサンプルを作成します。
ソースは http://github.com/fits/try_samples/tree/master/blog/20160107/
(1) virtual-dom インストール
virtual-dom を Bower でインストールします。
bower init と virtual-dom インストール例
> bower init ・・・ > bower install virtual-dom --save ・・・
(2) dom-delegator インストール
dom-delegator は npm でインストールします。 (dom-delegator は Bower でインストールできないため ※)
※ dom-delegator の git リポジトリを bower install する事は可能ですが、 その場合は依存ライブラリを手動でインストールする事になります
npm init と dom-delegator インストール例
> npm init ・・・ > npm install dom-delegator --save ・・・
(3) gulp-flatten インストール
Gulp でファイルをコピーする際にディレクトリ構成をフラット化するため gulp-flatten パッケージをインストールしておきます。
gulp-flatten インストール例
> npm install gulp-flatten --save-dev
Gulp で以下のように bower_components/virtual-dom/dist/virtual-dom.js を js ディレクトリへコピーすると、js/virtual-dom/dist/virtual-dom.js が作られます。
Gulp によるコピー例
gulp.task('js-copy', () => { gulp.src('bower_components/*/dist/*.js') .pipe(gulp.dest('js')); });
今回は、js/virtual-dom/dist/virtual-dom.js では無く js/virtual-dom.js へコピーしたかったので gulp-flatten を使いました。
(4) Gulp ビルド定義作成と実行
以下のような処理を行う gulpfile.js を作成します
- (a) bower_components/virtual-dom/dist/virtual-dom.js を js/virtual-dom.js へコピー
- (b) dom-delegator を Browserify で処理して js/dom-delegator.js へ出力 (Web ブラウザ上で DOMDelegator という名称で扱えるように standalone も設定)
gulpfile.js
var fs = require('fs'); var gulp = require('gulp'); var browserify = require('browserify'); var flatten = require('gulp-flatten'); // (a) gulp.task('js-copy', () => { gulp.src('bower_components/*/dist/*.js') .pipe(flatten()) // gulp-flatten でディレクトリをフラット化 .pipe(gulp.dest('js')); }); // (b) gulp.task('browserify', () => { browserify({ require: 'dom-delegator', standalone: 'DOMDelegator' }).bundle().pipe(fs.createWriteStream('js/dom-delegator.js')); }); gulp.task('default', ['js-copy', 'browserify']);
js ディレクトリを手動で作成した後、gulp
コマンドを実行します。
gulp 実行例
> mkdir js > gulp [00:19:17] Using gulpfile ・・・\virtual-dom\gulpfile.js [00:19:17] Starting 'js-copy'... [00:19:17] Finished 'js-copy' after 15 ms [00:19:17] Starting 'browserify'... [00:19:17] Finished 'browserify' after 17 ms [00:19:17] Starting 'default'... [00:19:17] Finished 'default' after 6.16 μs
これで下記のファイルが作成されます。
- js/virtual-dom.js
- js/dom-delegator.js
(5) virtual-dom の ev-click サンプル作成と実行
Gulp で生成した js ファイルを使い virtual-dom で ev-click を扱う単純なサンプルを作成し動作確認してみます。
virtual-dom は概ね以下のようにして使います。
- (1) virtualDom.diff 関数で 2つの VNode (VirtualNode) の差分を VPatch (VirtualPatch) として抽出
- (2) virtualDom.patch 関数で VPatch の内容を実際の DOM へ反映
index.html
<!DOCTYPE html> <html> <script src="js/virtual-dom.js"></script> <script src="js/dom-delegator.js"></script> <body> <h1>virtual-dom click sample</h1> <div id="ct"></div> <script> (function() { // ev-xxx イベントの有効化 (dom-delegator) new DOMDelegator(); var h = virtualDom.h; var create = virtualDom.create; var diff = virtualDom.diff; var patch = virtualDom.patch; var index = 1; var tree = h(); var render = function(v) { // 仮想 DOM の作成 return h('div', [ h('button', { // click イベントの設定 'ev-click': function(ev) { update(index++) } }, ['countUp'] ), h('br'), 'counter: ' + String(v) ]); } var update = function(v) { var newTree = render(v); // tree と newTree の差分を実際の DOM へ反映 patch(document.getElementById('ct'), diff(tree, newTree)); tree = newTree; }; update(index++); })(); </script> </body> </html>
Web ブラウザで実行し countUp ボタンを押下すると counter の数値がカウントアップする事を確認できました。
Web ブラウザ表示例
virtual-dom イベント処理の仕組み
最後に、virtual-dom と dom-delegator がどのようにイベント処理を行っているのか簡単に書いておきます。
まず、VNode の作成時に ev- で始まるプロパティは EvHook へ変換されます。(virtual-hyperscript/index.js の transformProperties)
patch 関数で実際の DOM へ反映する際に、対象の DOM ノードへ特殊なプロパティ __EV_STORE_KEY@7
※ を追加して EvHook の内容 (イベント名とイベント処理関数) を設定します。 (ev-store の機能)
※ プロパティ名は ev-store の index.js で定義されており、 ev-store 7.0.0 では __EV_STORE_KEY@7 となっています
__EV_STORE_KEY@7 が設定されている DOM ノード例
dom-delegator は "blur", "change", "click" 等の多数のイベントを document.documentElement で listen するように処理し、イベント発生時に対象 DOM ノードの __EV_STORE_KEY@7
プロパティの内容をチェックして、該当するイベントが設定されていればその処理関数を実行します。
つまり、上記のようなイベント処理を自前で行えば dom-delegator を使わなくても ev-click を機能させる事が可能です。
index2.html (dom-delegator を使わずに ev-click を有効化する例)
<!DOCTYPE html> <html> <script src="js/virtual-dom.js"></script> <body> <h1>virtual-dom click sample2</h1> <div id="ct"></div> <script> (function() { // dom-delegator の代用処理 document.addEventListener('click', function(ev) { var store = ev.target['__EV_STORE_KEY@7']; if (store && store[ev.type]) { // 該当するイベント処理関数の実行 store[ev.type](ev); } }, true); var h = virtualDom.h; ・・・ var render = function(v) { return h('div', [ h('button', { 'ev-click': function(ev) { update(index++) } }, ['countUp'] ), h('br'), 'counter: ' + String(v) ]); } ・・・ })(); </script> </body> </html>
pulp を使った PureScript の開発
PureScript 用のビルドツールに pulp があります。
pulp を使えば PureScript v0.7 から多少面倒になったビルドや実行が比較的容易になります。
pulp インストール
Node.js の npm で purescript (コンパイラ) と pulp をインストールします。
pulp インストール例
> npm install -g purescript pulp
今回インストールした PureScript コンパイラ・pulp のバージョンは以下の通りです。
なお、PureScript コンパイラに関しては https://github.com/purescript/purescript/releases/ から各 OS 用のバイナリを直接取得する方法もあります。
npm でインストールしたものも実際は node_modules/purescript/vendor
ディレクトリへ配置された各 OS 用のバイナリファイル (例. psc.exe) を使っているようです。
pulp を使った開発
今回作成したソースは http://github.com/fits/try_samples/tree/master/blog/20160105/
プロジェクトの作成
任意のディレクトリ内で pulp init
を実行すると、必要最小限のファイルが生成されます。
その際に Bower を使って PureScript の主要ライブラリ (以下) を自動的に取得しますので、git コマンドを使えるようにしておく必要があります。
- purescript-console
- purescript-eff
- purescript-prelude
プロジェクト作成例
> pulp init * Generating project skeleton in ・・・ bower cached git://github.com/purescript/purescript-console.git#0.1.1 bower validate 0.1.1 against git://github.com/purescript/purescript-console.git#^0.1.0 bower cached git://github.com/purescript/purescript-eff.git#0.1.2 bower validate 0.1.2 against git://github.com/purescript/purescript-eff.git#^0.1.0 bower cached git://github.com/purescript/purescript-prelude.git#0.1.3 bower validate 0.1.3 against git://github.com/purescript/purescript-prelude.git#^0.1.0 bower install purescript-console#0.1.1 bower install purescript-eff#0.1.2 bower install purescript-prelude#0.1.3 purescript-console#0.1.1 bower_components\purescript-console └── purescript-eff#0.1.2 purescript-eff#0.1.2 bower_components\purescript-eff └── purescript-prelude#0.1.3
ディレクトリ・ファイル構成は以下のようになります。
- bower_components
- purescript-console
- purescript-eff
- purescript-prelude
- src/Main.purs
- test/Main.purs
- .gitignore
- bower.json
デフォルトで用意されている src/Main.purs の内容は以下の通りです。
src/Main.purs
module Main where import Prelude import Control.Monad.Eff import Control.Monad.Eff.Console main :: forall e. Eff (console :: CONSOLE | e) Unit main = do log "Hello sailor!"
ライブラリの追加には pulp dep install <ライブラリ名>
を実行します。
そうすると bower install が実施されます。
bower.json の依存パッケージ設定へエントリを追加するには --save オプションを付けます。
purescript-tuples の追加例
> pulp dep install purescript-tuples --save ・・・ bower install purescript-control#0.3.2 bower install purescript-invariant#0.3.0 purescript-tuples#0.4.0 bower_components\purescript-tuples └── purescript-foldable-traversable#0.4.2 purescript-foldable-traversable#0.4.2 bower_components\purescript-foldable-traversable ├── purescript-bifunctors#0.4.0 └── purescript-maybe#0.3.5 purescript-maybe#0.3.5 bower_components\purescript-maybe └── purescript-monoid#0.3.2 purescript-bifunctors#0.4.0 bower_components\purescript-bifunctors └── purescript-control#0.3.2 purescript-monoid#0.3.2 bower_components\purescript-monoid ├── purescript-control#0.3.2 └── purescript-invariant#0.3.0 purescript-control#0.3.2 bower_components\purescript-control └── purescript-prelude#0.1.3 purescript-invariant#0.3.0 bower_components\purescript-invariant └── purescript-prelude#0.1.3
ビルドと実行
Main.purs を以下のようにタプルを使った処理に書き換えて実行してみます。
src/Main.purs
module Main where import Prelude import Control.Monad.Eff import Control.Monad.Eff.Console import Data.Tuple main :: forall e. Eff (console :: CONSOLE | e) Unit main = do let t = Tuple "two" 2 print t let r = t >>= \x -> Tuple " + 1" (x + 1) print r
pulp run
を実行するとビルドした後に処理を実施します。
ビルドだけを実施したい場合は pulp build
を使います。
実行
> pulp run * Building project in ・・・\20160105\purescript psc: No files found using pattern: src/**/*.js * Build successful. Tuple ("two") (2) Tuple ("two + 1") (3)
ビルド結果は output ディレクトリへ生成されます。
パッケージング
pulp browserify
を実行すると Browserify を使って output ディレクトリ内のファイルをパッケージングしてくれます。
browserify によるパッケージング
> pulp browserify > sample.js * Browserifying project in ・・・\20160105\purescript * Project unchanged; skipping build step. * Browserifying...
パッケージングしたファイル (sample.js) の実行結果は以下の通りです。
実行結果
> node sample.js Tuple ("two") (2) Tuple ("two + 1") (3)
Web ブラウザで実行する事もできます。
index.html
<!DOCTYPE html> <html> <script src="sample.js"></script> </html>
備考 - pulp を使わない場合
最後に pulp を使わない場合のビルド・実行方法も書いておきます。
まずは、Bower を使って purescript-console 等の必要なライブラリを手動でインストールします。
ライブラリのインストール例
> bower install purescript-console --save
src/Main.purs を psc コマンドでビルドするには以下のようにします。
psc によるビルド例
> psc src/Main.purs bower_components/purescript-*/src/**/*.purs --ffi bower_components/purescript-*/src/**/*.js
bower_components 内の .purs ファイルと --ffi オプションで bower_components 内の .js ファイルを指定します。
ビルド結果はデフォルトで output ディレクトリへ生成されます。(-o オプションで変更する事も可能)
実行する場合は、NODE_PATH 環境変数へ output ディレクトリを設定し、node コマンドで require('Main/index.js').main()
を実行します。
実行例
> set NODE_PATH=output > node -e "require('Main/index.js').main()" Tuple ("two") (2) Tuple ("two + 1") (3)
Google Cloud Print を Web API で操作 - Unirest 使用
リフレッシュトークンを使って Google Cloud Print を Web API で操作してみます。
以前、「Google アカウントで Google API を利用 - google-api-services-gmail」 では Apache HTTPClient を使いましたが、今回は Unirest を使っています。
ソースは http://github.com/fits/try_samples/tree/master/blog/20151222/
はじめに
事前準備としてリフレッシュトークンを取得しておきます。
今回は、「Google アカウントで・・・」 にて発行したクライアント ID をそのまま使用します。
(1) コードの取得
下記パラメータを付け https://accounts.google.com/o/oauth2/auth
へ Web ブラウザ等でアクセスし API の利用を許可する事でコードを取得します。
パラメータ | 値 |
---|---|
response_type | code |
client_id | クライアント ID の client_id 値(※1) |
scope | https://www.googleapis.com/auth/cloudprint |
redirect_uri | oob (※2) |
(※1)クライアント ID 発行時にダウンロードした JSON ファイルの client_id 値 (※2)リダイレクトしない場合の設定。 何らかのアプリケーション内から実行する際は それに合わせたリダイレクト先を指定する
例えば、以下のような URL へアクセスします。
URL 例
https://accounts.google.com/o/oauth2/auth?redirect_uri=oob&response_type=code&client_id=xxx.apps.googleusercontent.com&scope=https://www.googleapis.com/auth/cloudprint
Google アカウントでログインすると以下のような画面が表示されますので、「許可」ボタンを押下します。
コードが表示されるのでコピーしておきます。
(2) リフレッシュトークンの取得
次に、下記パラメータを https://www.googleapis.com/oauth2/v3/token
へ POST し、リフレッシュトークンを含んだ JSON を取得します。
パラメータ | 値 |
---|---|
code | (1) で取得したコード |
client_id | クライアント ID の client_id 値 |
client_secret | クライアント ID の client_secret 値 |
grant_type | authorization_code |
redirect_uri | oob (※1) |
(※1)(1) の場合と同様です。 ただし、以下のスクリプトでは urn:ietf:wg:oauth:2.0:oob を設定しています
Groovy スクリプト化すると以下のようになります。
get_refresh-token.groovy
@Grab('com.mashape.unirest:unirest-java:1.4.9') import com.mashape.unirest.http.Unirest import groovy.json.JsonSlurper addShutdownHook { Unirest.shutdown() } def json = new JsonSlurper() def conf = json.parse(new File(args[0])).installed def code = args[1] def res = Unirest.post('https://www.googleapis.com/oauth2/v3/token') .field('code', code) .field('client_id', conf.client_id) .field('client_secret', conf.client_secret) .field('grant_type', 'authorization_code') .field('redirect_uri', conf.redirect_uris[0]) .asJson() println res.body
クライアント ID 発行時にダウンロードした JSON (ここでは client_secret.json) を第1引数、(1) で取得したコードを第2引数へ指定して実行します。
取得したリフレッシュトークン JSON は後で使うので保存しておきます。 (ここでは token.json へ保存)
実行例
> groovy get_refresh-token.groovy client_secret.json 4/9ic・・・ > token.json
プリンタ一覧の取得
リフレッシュトークンを使う場合は、以下のような手順で API を呼び出します。
- リフレッシュトークンからアクセストークンを取得
- アクセストークンを使って API を実行
リフレッシュトークンからアクセストークン取得
アクセストークンを取得するには、下記パラメータを https://www.googleapis.com/oauth2/v3/token
へ POST します。
パラメータ | 値 |
---|---|
client_id | クライアント ID の client_id 値 |
client_secret | クライアント ID の client_secret 値 |
grant_type | refresh_token |
refresh_token | リフレッシュトークンの値 (※1) |
(※1)リフレッシュトークン取得で保存した JSON の refresh_token 値
この処理は共通的に使用するため、Groovy の BaseScript として定義しました。
TokenScript.groovy
import com.mashape.unirest.http.Unirest import groovy.json.JsonSlurper abstract class TokenScript extends Script { // アクセストークンの取得 def accessToken(String clientId, String clientSecret, String refreshToken) { def res = Unirest.post('https://www.googleapis.com/oauth2/v3/token') .field('client_id', clientId) .field('client_secret', clientSecret) .field('grant_type', 'refresh_token') .field('refresh_token', refreshToken) .asJson() res.body.object } }
API 実行 (プリンタの一覧取得)
アクセストークンは HTTP ヘッダーへ Authorization: Bearer <アクセストークン>
のように設定して使います。(例. Authorization: Bearer ya26.pw・・・)
クラウドプリンタ一覧は https://www.google.com/cloudprint/search
へ GET すれば取得できます。 (q や type パラメータを付けて条件検索することも可能)
get_printers.groovy
@Grab('com.mashape.unirest:unirest-java:1.4.9') import com.mashape.unirest.http.Unirest import groovy.json.JsonSlurper import groovy.transform.BaseScript // TokenScript を BaseScript へ設定 @BaseScript TokenScript baseScript addShutdownHook { Unirest.shutdown() } def json = new JsonSlurper() def conf = json.parse(new File(args[0])).installed def token = json.parse(new File(args[1])) // アクセストークン取得 def newToken = accessToken( conf.client_id, conf.client_secret, token.refresh_token ) // API の実行 def printers = Unirest.get('https://www.google.com/cloudprint/search') .header('Authorization', "${newToken.token_type} ${newToken.access_token}") .asJson() println printers.body
実行結果は以下の通りです。
まだプリンタを登録していないためデフォルトの 「ドライブに保存」 しかありません。
実行結果 (出力結果は加工済、実際は改行・字下げは無し)
> groovy get_printers.groovy client_secret.json token.json { "request":{・・・}, "printers":[{ "isTosAccepted":false, "displayName":"ドライブに保存", "capsHash":"", "description":"ドキュメントを Google ドライブで PDF として保存します", "updateTime":"1370287324050", "ownerId":"cloudprinting@gmail.com", "type":"DRIVE", "tags":["save","docs","pdf","google","__google__drive_enabled"], "proxy":"google-wide", "ownerName":"Cloud Print", "createTime":"1311368403894", "defaultDisplayName":"ドライブに保存", "connectionStatus":"ONLINE", "name":"Save to Google Docs", "id":"__google__docs", "accessTime":"1316132041869", "status":"" }], "success":true, ・・・ }
また、https://www.google.com/cloudprint/list?proxy=<プロキシ>
へ GET すると、指定プロキシへ属するプリンタ一覧を取得できます。
印刷処理 (Google Drive へ保存)
最後に、「ドライブに保存」 プリンタへファイルを印刷してみます。 (実際は Google Drive へファイル保存)
印刷は、下記の必須パラメータ(他にもパラメータあり)を multipart/form-data
で https://www.google.com/cloudprint/submit
へ POST します。
パラメータ | 値 |
---|---|
printerid | プリンタ ID (※1) |
title | 印刷タイトル |
ticket | 印刷設定 (※2) |
content | 印刷するファイルの内容 |
(※1)今回は __google__docs を使用 (※2)CJT (Cloud Job Ticket) フォーマットで印刷オプションなどを指定
submit_file.groovy
@Grab('com.mashape.unirest:unirest-java:1.4.9') import com.mashape.unirest.http.Unirest import groovy.json.JsonSlurper import groovy.json.JsonBuilder import groovy.transform.BaseScript @BaseScript TokenScript baseScript addShutdownHook { Unirest.shutdown() } def ticketBuilder = new JsonBuilder() // ticket の内容 ticketBuilder ( version: '1.0', print: {} ) def json = new JsonSlurper() def conf = json.parse(new File(args[0])).installed def token = json.parse(new File(args[1])) def file = new File(args[2]) // アクセストークンの取得 def newToken = accessToken( conf.client_id, conf.client_secret, token.refresh_token ) // ticket の JSON 文字列化 def ticket = ticketBuilder.toString() // 印刷 def res = Unirest.post('https://www.google.com/cloudprint/submit') .header('Authorization', "${newToken.token_type} ${newToken.access_token}") .field('printerid', '__google__docs') .field('title', file.name) .field('ticket', ticket) .field('content', file) .asJson() println res.body
PDF ファイルを POST した結果は以下の通りです。
Google Drive で確認すると sample1.pdf ファイルが保存されていました。
実行結果 (出力結果は加工済、実際は改行・字下げは無し)
> groovy submit_file.groovy client_secret.json token.json data/sample1.pdf { "request":{・・・}, "success":true,"xsrf_token":"AIp・・・", "message":"印刷ジョブが追加されました。", "job":{ "ticketUrl":"https://www.google.com/cloudprint/ticket?format=xps&output=xml&jobid=0db・・・", "printerName":"", "errorCode":"", ・・・ "title":"sample1.pdf", "tags":["^own"], ・・・ "printerid":"__google__docs", ・・・ "contentType":"application/pdf", "status":"DONE" } }
また、試しに以下のような拡張子のファイルを POST してみたところ、自動的に PDF へ変換され Google Drive へ保存されました。 (例えば sample1.html は sample1.html.pdf で保存)
- .html
- .docx
- .odt
Android エミュレータの hosts ファイルを編集
Android エミュレータの hosts ファイルを編集する方法です。
以下のような方法が考えられます。
- (a) shell で hosts へ行を追加
- (b) pull と push で hosts を変更
(a) shell で hosts へ行を追加
行を追加するだけなら以下のように shell を使うだけです。
adb remount
しておかないと /system/etc/hosts ファイルを更新できずに Read-only file system
のようなエラーメッセージが表示される点に注意。 (既に adb remount 済みの場合は不要)
hosts への行追加
> adb remount remount succeeded > adb shell "echo 10.0.2.2 server1 >> /system/etc/hosts"
/system/etc/hosts の内容を確認してみます。
hosts の内容確認
> adb shell cat /system/etc/hosts 127.0.0.1 localhost 10.0.2.2 server1
10.0.2.2 はエミュレータから接続する際のホスト OS の IP アドレスなので、ping してみます。
ping の実行
> adb shell ping server1 PING server1 (10.0.2.2) 56(84) bytes of data. 64 bytes from server1 (10.0.2.2): icmp_seq=1 ttl=255 time=0.000 ms ・・・
remount に関して
adb remount
する事で以下のように /system が rw で再マウントされます。
adb remount 前 (ro)
/dev/block/mtdblock0 /system ext4 ro,relatime,data=ordered 0 0
adb remount 後 (rw)
/dev/block/mtdblock0 /system ext4 rw,relatime,data=ordered 0 0
(b) pull と push で hosts を変更
次は hosts ファイルを編集する方法です。
エミュレータには vi 等のエディタが用意されていないようなので、pull で hosts ファイルを取得して、ホスト OS 側で hosts ファイルを編集し、push でエミュレータへ反映します。
まずは adb pull
で hosts ファイルを取得します。
hosts を pull
> adb pull /system/etc/hosts
ホスト OS のカレントディレクトリへ pull された hosts ファイルを編集します。
hosts 変更例
127.0.0.1 localhost 10.0.2.2 server1
変更した hosts を push します。
まだ一度も remount していない場合は remount する必要があります。
hosts を push
> adb remount ・・・ > adb push hosts /system/etc/
Swift のビルド環境を Docker で構築
Ubuntu をベースとした Swift のビルド環境を Docker で構築してみました。
使用したソースは http://github.com/fits/try_samples/tree/master/blog/20151207/
Docker イメージ作成
ubuntu:15.10
のイメージをベースに Swift をセットアップしました。
clang 等の必要なパッケージをインストールした後、swift-2.2-SNAPSHOT-2015-12-01-b-ubuntu15.10.tar.gz をダウンロードして解凍し、環境変数 PATH へ swift-2.2-・・・/usr/bin
のパスを追加するだけです。
また、clang
をインストールしただけだと swiftc の実行時に /usr/bin/ld: warning: libicuuc.so.55, needed by /swift-2.2・・・/usr/lib/swift/linux/libswiftCore.so, not found (try using -rpath or -rpath-link)
のようなメッセージが出力されたので、libicu-dev
もインストールするようにしています。
Dockerfile
FROM ubuntu:15.10 ENV SWIFT_PACKAGE swift-2.2-SNAPSHOT-2015-12-01-b-ubuntu15.10 RUN apt-get update RUN apt-get -y upgrade RUN apt-get -y install curl clang libicu-dev # swift のパッケージをダウンロード RUN curl https://swift.org/builds/ubuntu1510/swift-2.2-SNAPSHOT-2015-12-01-b/$SWIFT_PACKAGE.tar.gz -o $SWIFT_PACKAGE.tar.gz RUN tar zxf $SWIFT_PACKAGE.tar.gz RUN rm -f $SWIFT_PACKAGE.tar.gz RUN apt-get clean # 環境変数 PATH の設定 ENV PATH /$SWIFT_PACKAGE/usr/bin:$PATH
docker build
で Docker イメージを作成します。
Docker イメージの作成
$ docker build --no-cache -t sample/swift:0.1 . ・・・ Successfully built ・・・
動作確認
作成した Docker イメージを使って以下のようなサンプルソースを実行してみます。
/vagrant/work/sample.swift
let a = 11 let b = 2 print("result = \(a * b)")
まず、sample/swift:0.1
イメージを使って Docker コンテナを起動します。
Docker コンテナの起動
$ docker run --rm -it -v /vagrant/work:/work sample/swift:0.1 bash ・・・ # cd /work
コンテナ内で sample.swift を実行します。
直接実行
# swift sample.swift result = 22
ビルドしてから実行
# swiftc sample.swift # ./sample result = 22
Objective-C のビルド環境を Docker で構築
CentOS をベースとした Objective-C のビルド環境を Docker で構築してみました。
使用したソースは http://github.com/fits/try_samples/tree/master/blog/20151130/
Docker イメージ作成
今回は clang と GNUstep を使う事にします。
バージョンは古くなってしまいますが、どちらも yum でインストールするようにしました。
make
をインストールしておかないと GNUstep の gnustep-config
コマンドが機能しない (結果が空になる) のでご注意ください。
また、gcc-objc
をインストールしておかないと、Objective-C のソースをビルドする際に必要なヘッダーファイルを参照できず、fatal error: 'inttypes.h' file not found
のようなエラーが発生しました。
Dockerfile
FROM centos RUN yum -y update RUN yum -y install epel-release RUN yum -y install clang make gcc-objc gnustep-base-devel RUN yum clean all
docker build
で Docker イメージを作成します。
Docker イメージの作成
$ docker build --no-cache -t sample/objc:0.1 . ・・・ Successfully built ・・・
動作確認
以下のようなサンプルソースをビルドしてみます。
/vagrant/work/Sample.h
#import <Foundation/NSObject.h> #import <Foundation/NSString.h> @interface Sample : NSObject { NSString* _name; } @property (nonatomic, copy) NSString* name; - (void)log; @end
/vagrant/work/Sample.m
#import <Foundation/Foundation.h> #import "Sample.h" @implementation Sample : NSObject @synthesize name = _name; - (void)log { NSLog(@"%@", _name); } - (void)dealloc { [_name release]; [super dealloc]; } @end int main(int argc, const char * argv[]) { @autoreleasepool { Sample* s = [[Sample new] autorelease]; s.name = @"test"; [s log]; } return 0; }
まずは sample/objc:0.1
イメージを使って Docker コンテナを起動します。
Docker コンテナの起動
$ docker run --rm -it -v /vagrant/work:/work sample/objc:0.1 bash
コンテナ内で clang を使ってビルドします。
コンパイラオプションに関しては以下で妥当なのかは不明ですが、一応ビルドには成功しました。 (場合によっては -objcmt-migrate-all
等の指定も必要かもしれません)
ビルド
# cd work # clang `gnustep-config --objc-flags` `gnustep-config --objc-libs` -lgnustep-base -I /usr/lib/gcc/x86_64-redhat-linux/4.8.3/include Sample.m -o Sample clang: warning: argument unused during compilation: '-shared-libgcc'
一応、それぞれのオプションを指定しなかった場合に発生したエラーをまとめておきます。
オプション指定 | 指定しなかった場合のエラー例 |
---|---|
`gnustep-config --objc-flags` | 無し(指定しなくてもビルドは成功) |
`gnustep-config --objc-libs` | undefined reference to symbol 'objc_getProperty' |
-lgnustep-base | undefined reference to `NSLog' |
-I /usr/lib/gcc/・・・/include | fatal error: 'objc/objc.h' file not found |
ビルド結果の Sample
を実行してみると一応動作しました。
実行例
# ./Sample 2015-11-30 20:15:00.121 Sample[32] test
なお、clang -v
の実行結果は以下のようになりました。
clang -v
# clang -v clang version 3.4.2 (tags/RELEASE_34/dot2-final) Target: x86_64-redhat-linux-gnu Thread model: posix Found candidate GCC installation: /usr/bin/../lib/gcc/x86_64-redhat-linux/4.8.2 Found candidate GCC installation: /usr/bin/../lib/gcc/x86_64-redhat-linux/4.8.3 Found candidate GCC installation: /usr/lib/gcc/x86_64-redhat-linux/4.8.2 Found candidate GCC installation: /usr/lib/gcc/x86_64-redhat-linux/4.8.3 Selected GCC installation: /usr/bin/../lib/gcc/x86_64-redhat-linux/4.8.3