文字っぽいの。

文字を書いています。写真も混ざります。

MacにHomebrewでOMakeを入れられないので、opam経由でインストールする。

環境

前提

久しぶりにOMakeを使おうと思ってHomebrewでインストールしようとしたら

$ brew install o-make
Error: No available formula with the name "o-make"
==> Searching for a previously deleted formula (in the last month)...
Error: No previously deleted formula found.
==> Searching for similarly named formulae...
This similarly named formula was found:
automake
To install it, run:
  brew install automake
==> Searching taps...
==> Searching taps on GitHub...
Error: No formulae found in taps.

となった。簡単に調べて見たけど、メンテされなくて死んだ雰囲気を感じる。

github.com

仕方ないので、他の方法で導入する。

opamをセットアップする

opamはOCaml Package Managerのこと。

$ brew install opam
$ opam init // 色々聞かれるので答える
$ eval $(opam env)

これでopamのセットアップ終了。ちなみに opam init 時に 「.zshrc に設定入れとく?」と聞かれるけど、これを任せると絶対パスで記載されて不便なので、自分で

# opam configuration
test -r $HOME/.opam/opam-init/init.zsh && . $HOME/.opam/opam-init/init.zsh > /dev/null 2> /dev/null || true

.zshrcに書くのが良いと思います。

OMakeを入れる

あとは簡単で

$ opam install omake

して待つだけ。インストールできたら

$ omake --version

で使えるか確認しましょう。

SwiftUIでUILabelやNSAttributedStringを利用せずに文字装飾をがんばる

1つのText 内で文字色を変えたり、太字にしたり、下線を入れたりと、文字装飾を行いたいことがあると思います。UIKit時代ではUILabelUITextViewNSAttributedStringを利用して実装していたと思います。

今回は例として複数の tag, で結合して表示したい場合考えていきます。さて、なにも装飾せずに結合して表示するのはとても簡単です。

struct ContentView: View {
    let tags = Array(repeating: "tag", count: 10)

    var body: some View {
        Text(tags.joined(separator: ", "))
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView().previewLayout(.sizeThatFits)
    }
}

としてあげれば

f:id:FromAtom:20200510225845p:plain

このように簡単に表示できます。もしタグが100件になったとしても。

f:id:FromAtom:20200510225946p:plain

このように問題なく表示できます。

さて、ここで「,だけ色を赤くしてください」という要求が来たらどうしましょう。思いついた解決策は3つ。

  • HStackにTextを積んでいく
  • UIViewRepresentableを使って、UILabelとNSAttributedStringを使う
  • Textを結合する

HStackにTextを積んでいく

まず1つめの「HStackにTextを積んでいく」ですが、これはダメでした。要素が少ない場合は良いのですが、HStackは折り返しに対応してないので、思った通りの挙動をしてくれません。

f:id:FromAtom:20200510230728p:plain

UIViewRepresentableを使って、UILabelとNSAttributedStringを使う

これも粘ったんですが、「高さがうまく算出できずに表示がぶっ壊れる」問題が解決できませんでした。

これらの記事を参考に書いてみたコードがこちら

struct TextWithAttributedString: UIViewRepresentable {
    typealias UIViewType = UILabel

    var width: CGFloat
    var attributedString: NSAttributedString

    func makeUIView(context: Context) -> UILabel {
        let label = UILabel()
        label.numberOfLines = 0
        label.attributedText = attributedString
        label.lineBreakMode = .byTruncatingTail
        label.preferredMaxLayoutWidth = width
        label.setContentHuggingPriority(.defaultHigh, for: .horizontal)
        label.setContentHuggingPriority(.defaultHigh, for: .vertical)

        return label
    }

    func updateUIView(_ uiView: UILabel, context: Context) {
        uiView.attributedText = attributedString
        uiView.setContentHuggingPriority(.defaultHigh, for: .horizontal)
        uiView.setContentHuggingPriority(.defaultHigh, for: .vertical)
    }

}

struct TextWithAttributedString_Previews: PreviewProvider {
    static var previews: some View {
        let tags = Array<String>(repeating: "tag", count: 100)
        let attributedString = NSAttributedString(string: tags.joined(separator: ", "))

        return Group {
            GeometryReader { geometry in
                TextWithAttributedString(width: geometry.size.width, attributedString: attributedString)
                .fixedSize()
            }.previewLayout(.sizeThatFits)
        }
    }
}

そしてプレビューがこちら

f:id:FromAtom:20200510231821p:plain

一見良さそうなんですが、 .sizeThatFits しているのに無駄な高さが発生してしまっています。もちろん、ここで実装した TextWithAttributedString を別のView内で使うと高さ計算がおかしくなっており、表示が重なったり無駄な空白ができてしまいます。

執筆時に思いついたんですが、UILabelではなくてUITextViewを利用すれば、うまくいくかもしれません。

Textを結合する

最後の方法です。実はSwiftUIの Text+ でつなげていくことができます。つまり、

struct ContentView: View {
    var body: some View {
        Text("A") + Text("B") + Text("C")
    }
}

という書き方が可能です。この機能を利用して実装してみたのがこちら、

struct ContentView: View {
    let tags = Array(repeating: "tag", count: 30) + ["lastTag"]

    var body: some View {
        tags
            .dropLast()
            .map { Text($0) + Text(",").foregroundColor(.red) }
            .reduce(Text("")) { $0 + $1 }
            + Text(tags.last ?? "")
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        return ContentView().previewLayout(.sizeThatFits)
    }
}

実行してあげると、

f:id:FromAtom:20200510234536p:plain

このように , を赤くすることができました。

まとめ

SwiftUIのTextは + で結合できるので、UIKit時代は NSAttributedString で行っていた文章内の色分けや下線・太字処理を簡単に実現できるようになりました。今回はタグをつなぎ合わせる , を赤くするだけの簡単なサンプルでしたが、例えば文章中のハッシュタグは青色にして太字にするといった処理も(データ形式によりますが)比較的簡単に書けるようになったと思います。

GitHub謹製のghコマンドとpecoを組み合わせて、高速にPull Requestのブランチにチェックアウトする。

GitHub公式からghというCLIツールがbetaリリースされています。まだbeta版ですが、非常にシンプルで使いやすいCLIツールです。 この記事では、その ghとpeco を利用して、高速にPull Requestに対応するブランチにチェックアウトする方法を説明します。

コードレビューをお願いされて「checkoutして挙動を確認したいな」という時に、ブラウザでGitHubを開いてブランチ名をコピーする必要がなくなるので非常に便利です。

様子

Image from Gyazo

手順

macOS 10.15.4での手順になります。まず、pecoとghが入っていない場合は準備します。

$ brew install peco
$ brew install gh

次にこちらを .zshrc に追記します。

function peco-checkout-pull-request () {
    local selected_pr_id=$(gh pr list | peco | awk '{ print $1 }')
    if [ -n "$selected_pr_id" ]; then
        BUFFER="gh pr checkout ${selected_pr_id}"
        zle accept-line
    fi
    zle clear-screen
}
zle -N peco-checkout-pull-request

そして $ source .zshrc をするなどして .zshrc の変更を読み込んだあとで

$ peco-checkout-pull-request

とすると、pecoでP-Rを選択してCheckoutできます。このままだと毎回コマンドを打つ必要があって不便なので .zshrc

bindkey "^g^p" peco-checkout-pull-request

と書いておくと Ctrl+g, Ctrl+p と入力することでP-Rを選択してCheckoutできます。

まとめ

gh には他にも便利な機能があるので、「pecoでPull Requestを選択してブラウザで開く」といった機能も作れそうですね。

Slackのカスタム絵文字をesaにらくらくコピー!「Utsushie(写し絵)」シリーズを作りました。

f:id:FromAtom:20200204114423p:plain

はじめに

企業やグループでSlackを使っていると、カスタム絵文字をいっぱい登録しますよね。そしてesaも使っていると、Slackで登録したカスタム絵文字と同じものがesaでも使いたくなってきます。

そこで、Slackに登録されたカスタム絵文字をかんたんにesaにもコピーできる「Utsushie(写し絵)」シリーズを作りました。この記事ではUtsushieシリーズの紹介をしたいと思います。

Utsushieシリーズ

Utsushieシリーズには

  • Utsushie
  • Utsushie-Stream

の2つがあります。それぞれできることや使い所が違うので解説していきます。

Utsushie

github.com

UtsushieはSlackに登録されているCustom EmojiをesaにコピーするCLIツールです。なお、esaのカスタム絵文字仕様 に準拠していないCustom Emojiは無視されます。

「とにかく一気にSlackからesaに絵文字をコピーしたいんじゃ!」というときに便利です。また、esa上でにすでに登録されているカスタム絵文字を全削除したあとでコピーをする --clean や、 コピーや画像のダウンロードが一切走らない --dry-run オプションも用意してあります。

Utsushie-Stream

github.com

Utsushie-Stream はSlackの emoji_changed event に反応して、Slackに登録されたEmojiをesaにコピーするツールです。反応するイベントは

  • 絵文字の追加
  • aliasの追加
  • 絵文字の削除

の3つです。これらのイベントに対応してesaにも絵文字やaliasを追加したり、削除したりしてくれます。ただし、イベントベースで処理が発生するので、すでにSlackに登録されている絵文字はコピーされません。

また、絵文字のコピー・削除が発生時にはSlackのチャンネルに

f:id:FromAtom:20200204130744p:plain

と通知が飛んできます。esaのフォーマットに適合していない絵文字( :テストです: )の場合は

f:id:FromAtom:20200204130953p:plain

といった形でエラーも通知してくれます。

おすすめの使い方

  1. UtsushieでSlackからesaに絵文字をすべてコピーする
    • Slackの絵文字量によってはすごい時間がかかります
  2. Utsushie-Streamをセットアップする

という手順で環境を整えておくと、自動でSlackとesaでカスタム絵文字の状態を同期されるようになります。ただし、esaのカスタム絵文字仕様 に反する絵文字はコピーされませんので注意してください。

なお、それぞれのツールの使い方は、GitHub RepositoryのREADMEに書いてありますので、そちらをご参照ください。

よもやま話

絵文字(Emoji) を 移す(Utsusu)からUtsushiEです。写し絵か影絵かすごい悩んですが、影絵だとKageeになって、ケギーとしか読めなかったのでやめました。

ただ、OSSロゴに利用しているのは影絵の手遊びの代表的な存在であるキツネです。写し絵っぽいアイコン思いつかなかったんですよね。映写機だと動画制作ツール感が出てしまって。あと、本当はキツネの形した手のイラストが使いたかったんですが、そんな都合の良い素材はなかったのでシルエット感が強いキツネの素材を使わせてもらいました。

元ネタ

このツールの元ネタは esa5周年パーティ にて

次に、ぷりんたい (@spacepro_be)さんより、SmartHRさんの社内で使われているカスタムemojiをslackとesaで連携するツールについて、ツールを作った @yamashushさんに代わって、紹介していただきました。

といったLTで聞いた話です。とても便利そうで自分も使いたかったのですが、OSSとして公開される霊圧を感じられなかったので「待ってないで自分で作るかー」とエイヤで作った次第です。実は気づかないうちに公開されてたりするんだろうか……?

まとめ

Slackからesaに簡単にカスタム絵文字をコピーできるツール「Utsushie」と「Utsushie-Stream」を作りました。ぜひ、ご利用ください。 (\( ⁰⊖⁰)/)

Swift5で正規表現を書く時に "\" を2回書きたくないときはRaw String Literalを使おう。

問題

Swiftで正規表現を書くと

let regex = "\\A\\s*[-+*]\\s*\\S+\\z"  // Markdownの箇条書きか判定する雑な正規表現

と書かないといけない。これでは不便ですね。

解決方法

Swift 5からRaw strings literalの仕組みが導入されました。このliteralを利用すると

let regex = #"\A\s*[-+*]\s*\S+\z"#  // Markdownの箇条書きか判定する雑な正規表現

と書けます。 #"ここに文字が入る"# という記法です。これで、わかりやすく正規表現を記述することができますね。

余談

また、このliteral内では " も利用することが可能で

let string = #"The word "bookstore" is a compound consisted of "book" and "store.""#
// => The word "bookstore" is a compound consisted of "book" and "store."

この様に文字列内で " をそのまま利用できます。また、文字列内で "# を使いたい場合は

let string = ##"The word "bookstore"#hashtag"##
// => The word "bookstore"#hashtag

この様に、前後の #を増やしてあげれば良いです。

さらに、文字列補間もしたいですよね。そういう場合は

let name = "太郎"
let string = #"犬の名前は "\#(name)" です。"# // => 犬の名前は "太郎" です。

\#(name) とすることで利用できます。

XiaomiのMi band 4を買った

f:id:FromAtom:20200111175358j:plain

電車通勤になってから、携帯を取り出すのではなく腕時計で時間を確認したいと思うことが増えた。 せっかくなのでスマートウォッチというのが欲しいが、いかんせんAppleWatchは高すぎる。やりたいこととしては、

  • 時間がわかる
  • スマホに通知が来た時に分かって、ちょっとだけ内容がわかる
  • iOSと連携できる

くらい。睡眠やアクティビティの計測とか、音楽の操作はあったら嬉しいけど多分使わないからいいやという温度感。 そんな時に、このMi band4を見つけた。

良い点

  • とにかく安い4000円弱
  • 別売りのバンドも10色セットで1500円と安い
    • 個別でも買えるけどすこし割高になる
  • カラー液晶でタッチパネルになっている
  • 小さいし軽い
  • バッテリーが20日も持つ
    • 2日使ってて4%しか減らなかったので、確かに20日持ちそう感がある
    • 週末に充電しておけばいいので使いやすい
  • サードパーティ製のfaceが使える。自分でface作ったりもできる。
    • f:id:FromAtom:20200111174431j:plain
  • UIは英語しか無いが、通知内の日本語はちゃんと表示される
  • 心拍数、睡眠、運動のトラッキングもできる
    • 精度は不明

微妙な点

  • 公式が提供しているfaceがダサい
  • なおかつAndroidじゃないとサードパーティ製の文字盤を使えない
    • iOSではデフォルトが一番いいと思います
    • 追記:iOSでもAmazToolsというアプリを使うとできるとの情報をもらいました
  • 一度変更したfaceをデフォルトに戻す方法が全然わからない
    • ペアリングを切ると元に戻る
    • これ以外の方法がわからん
  • 設定用アプリは日本語だけど、Mi band4側のUIは英語だけなので苦手な人は苦手そう
  • バンドから本体を取り外さないと充電できない
    • 一応、非公式でクリップ式の充電アダプタが出ている

高スペックなのに低価格。一体何がどうしてこんなに安いのか分からないけど、欲しかった機能が必要十分にあるので大変便利。良い買い物でした。

Macのzshで時間がかかる処理が終わったら通知してくれるようにする

はじめに

開発をしていると、テストやセットアップ処理、ライブラリの導入などで長い時間待つことがあると思います。 そんな時に暇すぎてTwitterを見に行くと、そのまま夕方になり生産性が破滅することが良くあると思います。

なので長い処理が終わったら、通知が来るようにしてTwitterランドから仕事に戻れるようにしました。

動かした環境

利用するもの

  • terminal-notifier
  • zplug
  • zsh-notify or iterm-notify

手順

では準備していきましょう。

terminal-notifierをインストールする

こいつはターミナルからmacOSの通知を出してくれる君です。Homebrewで入れていきます。

brew install terminal-notifier

インストールしたら下記のコマンドを試してみましょう。

terminal-notifier -message 'HELLO'

そして下記のように通知が来たら成功です。

f:id:FromAtom:20191210193602p:plain

さて、これで現状でも

処理が長いコマンド | terminal-notifier -message '終わったよ'

などとすればコマンドの処理が終わった後に通知が来ます。 しかし、大体の場合 "処理が長いコマンド" を実行したあとで「あ!通知設定するの忘れてた」となると思います。 これを防ぐため、一定以上実行に時間がかかった処理が終わったら通知が来るようにする zsh-notify を入れます。 の、前に、 zsh-notify のようなプラグインを管理してくれる zplug を導入します。

zplugをインストールする

これもHomebrewで簡単に入ります。

brew install zplug

zsh-notifyをインストールする

.zshrc に下記の処理を追記します。

export ZPLUG_HOME=/usr/local/opt/zplug
source $ZPLUG_HOME/init.zsh

zplug "marzocchi/zsh-notify"

if ! zplug check --verbose; then
    printf "Install? [y/N]: "
    if read -q; then
        echo; zplug install
    fi
fi

zplug load

これを書いたあとでzshを再起動すると下記のように、プラグインのインストールを促してくれます。

f:id:FromAtom:20191210195251p:plain

ここで y を入力すれば、zsh-notify がインストールされます。

試してみる

試しに下記のコマンドを実行してみましょう。なお、ターミナルにフォーカスが当たってると通知が来ないので注意です。

sleep 40

下記のように通知が来たらOKです。

f:id:FromAtom:20191210200811p:plain

通知される時間を変える

デフォルトでは30秒以上の処理で通知が来ますが、この時間を変えたい場合は .zshrc に、

 zstyle ':notify:*' command-complete-timeout 15

みたいに追記しましょう。これで15秒がしきい値になります。

iTerm2をご利用の方へ

iTerm2を利用している方は marzocchi/zsh-notify ではなくて marzocchi/iterm-notify が用意されているので、そちらを利用するのが良いでしょう。