DRYな備忘録

Don't Repeat Yourself.

react-routerでページを共通Layoutに乗せたりログイン画面は乗せなかったりするルーティングの設定

import React from 'react';
import ReactDOM from 'react-dom'
import {Router, Routes, browserHistory} from 'react-router';

import {MyFancyLayout} from '../../layouts';
import {
    LoginPage, // これと
    HomePage,
    SettingPage,
    NotFoundPage, // これは、レイアウト適用外
} from '../../pages';

ReactDOM.render(
    <Router history={browserHistory}>
        <Route path="/login" component={LoginPage} />
        <Route path="/" component={MyFancyLayout}>
            <IndexRoute component={HomePage} />
            <Route path="/home" component={HomePage} />
            <Route path="/settings" component={SettingsPage} />
        </Route>
        <Route path="*" component={NotFoundPage} />
    </Router>,
    document.querySelector('div#app')
);
// こうしとくと、MyFancyLayoutコンポーネントにおいて
// ネストされたroutesで指定されたコンポーネントが
// props.childrenとして得られるので、renderとかで使えばよい
  • IndexRoute を定義しないと、/レンダリングされない
  • MyFancyLayoutのRouteで*とするとMyFancyLayoutのprops.childrenがundefinedになる

のでちょっとハマった

DRYな備忘録として

入門 React ―コンポーネントベースのWebフロントエンド開発

入門 React ―コンポーネントベースのWebフロントエンド開発

Draft.jsを使ってContentEditableなdivに絵文字をレンダリングしつつ編集可能にする

ぜったいなんかやり方あるだろと思いつつなかなか見つからなくてググり続けたりして6時間ぐらいハマったのでメモ。

f:id:otiai10:20170208000633g:plain

import React from 'react';

import {Editor, EditorState, CompositeDecorator} from 'draft-js';

const getEmojiURL = (key) => {
  // TODO: ここで、:shit:とかくるので、対応したURLを返せばよい
  switch(key) {
  case ':smile:': return 'https://twemoji.maxcdn.com/svg/1f603.svg';
  case ':shit:':  return 'https://twemoji.maxcdn.com/svg/1f4a9.svg';
  default: return null;
  }
};

const Emoji = (props) => {
  const url = getEmojiURL(props.decoratedText);
  if (!url) return <span>{props.children}</span>;
  return (
    <div style={{
      display:'inline-block',
      overflow:'hidden',
      width:'16px',
      height:'16px',
      backgroundImage: `url('${url}')`,
      color: 'transparent',
    }}>{props.children}</div>
  );
};

const EmojiDecorator = {
  strategy: (block, callback /* , content */) => {
    const text = block.getText();
    const regex = /:[^(:| )]+:/g;
    let matched;
    while ((matched = regex.exec(text)) !== null) {
      const start = matched.index, end = start + matched[0].length;
      callback(start, end);
    }
  },
  component: Emoji,
};

const decorator = new CompositeDecorator([
  EmojiDecorator,
]);

export default class MyNiceEditor extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      editorState: EditorState.createEmpty(decorator),
    };
  }
  onChange(editorState) {
    this.setState({editorState});
  }
  render() {
    return (
        <Editor
          editorState={this.state.editorState}
          onChange={this.onChange.bind(this)}
        />
    );
  }
}

とりあえず欲しい感じになったけど、もっと良い方法あるだろうという気持ちですので、ご存知の方おられましたら是非ともご教示ください :bow:

DRYな備忘録として

jestでTypeError: Cannot read property 'instrument' of undefinedと叱られる

とりあえず

./node_modules/.bin/jest --no-cache
# package.jsonのscriptsや、-gで入れてる場合は、 "jest --no-cache" ですもちろん。

としたら通った。

DRY

Learning React Native: Building Native Mobile Apps with JavaScript

Learning React Native: Building Native Mobile Apps with JavaScript

ブラウザでMediaStreamを動画に固めて保存したい

ゴール

ブラウザのJavaScriptで、MediaStreamを動画ファイルにしてローカルに保存できるようにしたい。

参考

tl;dr

  1. 好きな方法でMediaStreamを取得する
  2. 取得したstreamを使ったMediaRecorderを作成する
  3. MediaRecorderのondataavailableで取得したdataを貯めていく
  4. 任意のタイミングで、溜まったdataをBlobにする
  5. BlobからURL.createObjectURLで特定のURLを得る
  6. あとはこれをa.hrefに突っ込んでa.downloadに突っ込んでとかすればよい

サンプル

このへんです。(雑)

github.com

TravisCI using fastlane failed with message "Your bundle is locked to credentials_manager (0.16.2)"

Problem

$ bundle install --jobs=3 --retry=3 --deployment
Fetching gem metadata from https://rubygems.org/........
Fetching version metadata from https://rubygems.org/..
Fetching dependency metadata from https://rubygems.org/.

Your bundle is locked to credentials_manager (0.16.2), but that version could
not be found in any of the sources listed in your Gemfile. If you haven't
changed sources, that means the author of credentials_manager (0.16.2) has
removed it. You'll need to update your bundle to a different version of
credentials_manager (0.16.2) that hasn't been removed in order to install.

Research

github.com

My legacy environments were fixed old fastlane version (1.103.0) and builds started breaking because credentials_manager version 0.16.2 has been removed from rubygems.

Solution

# frozen_string_literal: true
source "https://rubygems.org"

-gem "fastlane", "1.109.0"
+gem "fastlane", "2.5.0"

DRY

Swift実践入門 ── 直感的な文法と安全性を兼ね備えた言語 (WEB+DB PRESS plus)

Swift実践入門 ── 直感的な文法と安全性を兼ね備えた言語 (WEB+DB PRESS plus)

iOSプロジェクトをTravisCIでCIしたい

Travis CIでCIしたい」なのか「TravisでCIしたい」なのか悩みました。

参考

tl;dr

  1. gem install fastlane
  2. 必要があれば sudo xcodebuild -license accept しておく
  3. fastlane init
  4. fastlane test してみる
  5. CIサーバのためにschemeをsharedにする
  6. Travis CI用の設定ファイルを設置
  7. Travis CIのコンソールでこのレポジトリのCIを有効にする

github.com

ログ

fastlaneをインストール

% gem install fastlane
# ... 中略 ...
63 gems installed
% which fastlane
/Users/otiai10/.rbenv/shims/fastlane
% fastlane --help
  fastlane

  CLI for 'fastlane' - The easiest way to automate beta deployments and releases for your iOS and Android apps

        Run using `fastlane [platform] [lane_name]`
        To pass values to the lanes use `fastlane [platform] [lane_name] key:value key2:value2`

  Commands:
        # いろいろ

fastlane init

% fastlane init
# 1. Apple IDを聞かれるので答える
# 2. そのApple IDのpasswordを聞かれるので答える
# 3. Developerポータルで複数チームが紐付いているのでチームを選べと言われるので答える
#        - 個人のorgを選択
# 4. iTunesConnectで複数のチームが紐付いているのでチームを選べと言われるので答える
#        - 個人のorgを選択
# 5. DeveloperポータルとiTunesConnectにこのapp identifierが無いから作るか?と聞かれる
#        - この備忘録用なので要らない n
# 6. なぜかもう一回同じことを聞かれる
#        - この備忘録用なので要らない n
# ls fastlane
Appfile     Fastfile

fastlane/Appfilefastlane/Fastfile がつくられる

fastlane test してみる

% fastlane test
[13:22:24]: -------------------------------------------------
[13:22:24]: --- Step: Verifying required fastlane version ---
[13:22:24]: -------------------------------------------------
[13:22:24]: Your fastlane version 1.111.0 matches the minimum requirement of 1.111.0  ✅
[13:22:24]: ------------------------------
[13:22:24]: --- Step: default_platform ---
[13:22:24]: ------------------------------
[13:22:24]: Driving the lane 'ios test' 🚀
[13:22:24]: ------------------
[13:22:24]: --- Step: scan ---
[13:22:24]: ------------------
[13:22:24]: $ xcodebuild -list -project ./travis-ci-example.xcodeproj
xcode-select: error: tool 'xcodebuild' requires Xcode, but active developer directory '/Library/Developer/CommandLineTools' is a command line tools instance
[13:22:24]: Variable Dump:
[13:22:24]: {:DEFAULT_PLATFORM=>:ios, :PLATFORM_NAME=>:ios, :LANE_NAME=>"ios test"}
[13:22:24]: Error parsing xcode file using `xcodebuild -list -project ./travis-ci-example.xcodeproj`

+------+-------------------------------------+-------------+
|                     fastlane summary                     |
+------+-------------------------------------+-------------+
| Step | Action                              | Time (in s) |
+------+-------------------------------------+-------------+
| 1    | Verifying required fastlane version | 0           |
| 2    | default_platform                    | 0           |
| 💥    | scan                                | 0           |
+------+-------------------------------------+-------------+

[13:22:24]: fastlane finished with errors

[!] Error parsing xcode file using `xcodebuild -list -project ./travis-ci-example.xcodeproj`

しっぱいする

Error parsing xcode file using xcodebuild -list -project ./travis-ci-example.xcodeproj

xcodebuildコマンドが失敗しているというエラーなので、おそらく

ここで言及されているxcode-selectxcodebuildあたりの操作をすっ飛ばしたことに起因すると思われる。

% sudo xcodebuild -license accept
xcode-select: error: tool 'xcodebuild' requires Xcode, but active developer directory '/Library/Developer/CommandLineTools' is a command line tools instance

error: tool ‘xcodebuild’ requires Xcode, but active developer directory is a command line tools instance

Xcodeをソースで落としてきてビルドしたことに起因する。Xcode.appをちゃんとApplicationsに配置した。

参考: osx - xcode-select active developer directory error - Stack Overflow

f:id:otiai10:20161206214640p:plain

で、あらためて

% sudo xcodebuild -license accept

xcodebuild: error: Scheme travis-ci-example is not currently configured for the clean action.

で、もっかい

% fastlane test
# 中略
[13:40:55]: $ xcodebuild clean -showBuildSettings -scheme travis-ci-example -project ./travis-ci-example.xcodeproj
xcodebuild: error: Scheme travis-ci-example is not currently configured for the clean action.
# 後略

しっぱいする。

ビルド可能なschemeが無かった。

これで治った

supported_platforms': [!] undefined method `split' for nil:NilClass (NoMethodError)

なんとなく予想はつくので、Fastfileにて

  desc "Runs all the tests"
  lane :test do
-    scan
+    scan(device: "iPhone 7 (10.1)")
  end

とした

fastlane test 成功

% fastlane test
# ...略...
[18:39:51]: ▸ ✓ testPerformanceExample (0.253 seconds)
[18:39:51]: ▸   Executed 4 tests, with 0 failures (0 unexpected) in 0.519 (0.522) seconds
[18:39:51]: ▸
[18:39:51]: ▸ Test Succeeded
+--------------------+-------+
|        Test Results        |
+--------------------+-------+
| Number of tests    | 5     |
| Number of failures | 0     |
+--------------------+-------+

[18:39:51]: Successfully generated report at '/Users/otiai10/proj/ios/travis-ci-example/fastlane/test_output/report.html'
[18:39:52]: Successfully generated report at '/Users/otiai10/proj/ios/travis-ci-example/fastlane/test_output/report.junit'

+------+-------------------------------------+-------------+
|                     fastlane summary                     |
+------+-------------------------------------+-------------+
| Step | Action                              | Time (in s) |
+------+-------------------------------------+-------------+
| 1    | Verifying required fastlane version | 0           |
| 2    | default_platform                    | 0           |
| 3    | scan                                | 19          |
+------+-------------------------------------+-------------+

[18:39:52]: fastlane.tools finished successfully 🎉
%

良い感じ!

CIサーバのためにschemeをsharedにする

Product > Scheme > Manage Schemes > sharedのチェックボックス

f:id:otiai10:20161207025033p:plain

travis-ci用の設定ファイルを設置

みんなだいすき.travis.yml

language: objective-c
osx_image: xcode8.1
script:
    - fastlane test

Travis CIのコンソールでこのレポジトリのCIを有効にする

もちろんアカウントが必要。 https://travis-ci.org/

f:id:otiai10:20161207025750p:plain

いざpush

every pushでCIがまわるように設定しているので、この状態でpushすれば発動するはず。

[!] The Fastfile requires a fastlane version of >= 1.111.0. You are on 1.109.0. Please update using sudo gem update fastlane.

で、しっぱいする。

https://travis-ci.org/otiai10/ios-travis-ci-example/builds/181730714

どうやらローカルにインストールしたfastlaneが1.111.0で、そのfastlaneでinitしたFastfileなのでfastlaneのバージョン指定が1.111.0になっていたっぽい。これを以下のように変えた

-fastlane_version "1.111.0"
+fastlane_version "1.109.0"

 default_platform :ios

再度push!

https://travis-ci.org/otiai10/ios-travis-ci-example/builds/181803675

f:id:otiai10:20161207085238p:plain

やったー!

雑感

  • 参考にしたドキュメントだと、fastlane/travis.shとかいうのを噛ませて、ビルド発動条件をコントロールしてた
    • できるだけ.travis.ymlでコントロールしたほうがいいんじゃないかな
  • グローバルないしrbenvのlocalにfastlaneをインストールしてしまうと、今回のようにfastlaneのバージョン問題が起こりうる
    • Gemfileとbundle execで実行みたいな構成のほうがいいかと思われる
  • サードパーティのサーバ上ではiOSのビルドできないからMacにJenkins立てるしかない、みたいな偏見あったけど、気のせいだったのかな?
  • fastlane/Appfileやfastlane/certの設定次第ではデプロイもできる(はず、と思っている)ので、やってみたい

DRY

個人開発程度のOCRサーバならHerokuに立てればいいじゃない

このエントリはGo (その2) Advent Calendar 2016 - Qiitaの5日目です。
WETな方でもお世話になっております、otiai10です。

とある個人開発が、もうかれこれ3年ぐらい続いているんですが、ブラウザ上に描画されたちょっとしたテキストをOCR(文字認識)する要件があって、それをずっとさくらVPSPythonでやってましたが、3年もやってると、他の個人開発だったりとか、副業だったりとかでサーバリソースを一緒にしたくないなあ、っていうことが発生してきて、しかもべつに広告とかアフィとかで報酬が発生するタイプの個人開発でもないので、なんで俺が身を削って維持せなあかんねんという気持ちにもなったので、いいかげん、無料でOCRサーバ立てるのをやりました。Goで。Go好きなので。

tl;dr

Deploy

これ押したらもう立ちます。

特徴

  1. Google Cloud Vision API のように自由画像から文字座標を検出したりしない
    • 位置と矩形が確定している場合の文字認識に適してます
  2. Google Cloud Vision API では指定できないchar_whitelist(検出文字制限)ができる
    • 想定される文字が限られている場合に適しています
  3. 無料 ← つよい

みなさんバンバン自分のOCRサーバインスタンスを立てて、レシートを読むなり、アイドルのスタミナ回復時間を読むなりしてください。

やったことその1: GoでC++のライブラリを叩く

Goはcgoっていうのがあって、GoからC/C++C/C++からGoを呼べたりするんですけど、これを使って既存の光学文字認識ライブラリであるTesseract-OCRを呼んでます。

GitHub - otiai10/gosseract: Golang OCR library, wrapping Tesseract-ocr

例。includeしているtess.hは自作のヘッダファイルで、cppファイルが実際にTesseract-OCRを叩いてます。このGoファイルをビルドすると、C/C++のライブラリにリンクされた実行ファイルが手に入る、という寸法です。

package tesseract

/*
#cgo LDFLAGS: -llept -ltesseract
#include "tess.h"
*/
import "C"

// Simple executes tesseract only with source image file path.
func Simple(imgPath string, whitelist string,languages string) string {
    p := C.CString(imgPath)
    w := C.CString(whitelist)
    l := C.CString(languages)

    s := C.simple(p, w,l)
    return C.GoString(s)
}

やったことその2: せっかくなのでWAFっぽいものをつくる

僕がGoの好きなところのひとつは、DIY(Do It Yourself)感がすごいところで、始めやすいし、Write&Runが手軽だし、標準ライブラリが簡潔かつ充実してるし、釘一本からチェーンソーまであって、隅にはペットコーナーすらある的な、ホームセンターみたいな雰囲気が好きです。

ということで、最低限よくやるルーティングと、あとHTTPのフィルタリングあたりをやる小さなツールキットをつくりました。

GitHub - otiai10/marmoset: The very minimum web framework for Golang

func main() {
    router := marmoset.NewRouter()
    router.GET("/hello", func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("hello!"))
    })
    http.ListenAndServe(":8080", router)
}

Viewのレンダリングもやるんですけど、なんかHerokuで動かなくて、アドベントカレンダー的に合わなかったのでocrserver側ではdirty hackしてます…

やったことその3: 1と2を、Uh…!合わせる

PPAPの画像をコラって貼りたいと思ったんだけど、ライセンスフィルタ付きで画像検索したら1個も無えの。

marmosetで小さいサーバをつくって、gosseractでOCRするところをやったのが、こちらになります。

github.com

やったことその4: Herokuデプロイできるようにする

なんかHerokuはbuildpackっていうのがあって、言うなればUbuntuベースのCedarっていうイメージをベースに、自分で好きな拡張をしていけるようになってるっぽい。

上記のgoseractは当然、GoのコンパイルにはTesseract-OCRのヘッダファイル/共有オブジェクトファイルが必要なので、これをHerokuインスタンスに配置して、サーバアプリケーションが立つときに参照できるようにしてやらないといけないんだけど、どうしようかな、と思ってたら直でaptitudeが使えるマジ神なbuildpackがあったので一発解決しました。

これがたぶん今回のミソでした。Tesseract-OCR叩くパッケージにも、webのツールキットにも、OCRサーバのUIにもまだまだTODOが多いので、使いながらブラシアップしていきたいです。

まとめ

Deploy ← これ押したらあなたのOCRサーバがもう立ちます。無料で。

個人開発、いいですよね。知らない技術に触れるチャンスになるので。

追記

現場からは以上です。