技術書典5に「しがないラジオ」としてサークル参加参加してきました

f:id:zuckey_17:20181021141145p:plain:w300

技術書典5に「しがないラジオ」としてサークル参加参加してきました。
出した本は「完全SIer脱出マニュアル」という本です。
技術書典は技術にまつわる同人誌の即売会で、技術系コミケ *1というとわかりやすいかと思います。
半年に1回のペースで開催されており、第4回の前回は一般参加者として書籍を買う側として参加しましたが、今回第5回はサークル参加ということで書籍を出す側で参加しました。

もともと出たいなと思ってはいましたが具体的には何もなく、Podcastでともにパーソナリティをしているgamiさんが勢いで申し込んでくれ、参加が決定しました。

完全SIer脱出マニュアル

BOOTHで買えます 👇

booth.pm

しがないラジオというのは技術系のポッドキャストで、僕とgamiさんがパーソナリティをつとめており、最近では楽しく働いているエンジニアのキャリアなどを話しています。
タイトルどおりの本で、SIer(=楽しくない職場。といってもいいと思います)から脱出したい人のためにノウハウや考え方を、しがないラジオでこれまで聞いてきたエピソードトークから抽象化して書いた本です。

目次なんかは、BOOTHのリンクからも見れますし、読んだ方々の感想は、togertterにもまとまっているので良ければ見てみてください。

togetter.com

担当

ノウハウや考え方を書いてある本編を僕自身は書いておらず、ディスカッションの場にいたりチェックくらいで、僕はこれらの本編を読んだあとにそのもととなったしがないラジオのエピソードをピックアップして紹介する付録を担当しました。
また、もう1つ付録の特典エピソードについてはいつものごとく編集作業などを担当しました。

売れたのか

結論から言うと、想像以上に売れました。

売れた要因?

gamiさんの煽りタイトルをつける能力が炸裂し、お願いしたデザイナーさんの表紙 *2がわかりやすくマッチしていました。
やはり人の流れの早いイベントだったので、ポッドキャストのリスナーさん以外にも購入していただけたり立ち読みしていただけていたのは、そのパッと目に入るわかりやすい煽りが良かったのだと思いました。

同人誌即売イベントで多い立ち読みですが、そこで読んでいただいたかたに買ってくださったのは大きいと思います。
タイトルに反してすごく客観的に、適切なエクスキューズをしつつ書かれた本だったのでそのへんがギャップとして刺さった部分なのかなと思いました。
ディスカッションの中でもかなり意識していたところでもあったし、そのへんもgamiさんの良さなのかなと思います。

リスナー層の一部にお買い上げいただくための特典エピソードをとれたというのが僕の唯一の売上への貢献だったかなと思います。

焦りと後悔

とそんなふうに結果としてはよかったのですが、とあるポッドキャストも聞いてくださっているかたのブログにこのように書かれていました。

リスナー勢としてあえて欠点を上げるならばずっきー色が薄いことでしょうか(笑)

iwasiman.hatenablog.com

また、他のブログの方もこのようなことを書かれていました。

今回、私と同じく初のサークル参加でかなりバズっていたのが gami さんの『完全 SIer 脱出マニュアル』。

note.mu

確かにgamiさんが申し込んでくれたところもありますし、あまり熱が盛り上がらなかったというのは実際にPodcastでも話しましたが *3 、本当にそのとおりでしたし、他エピソードの編集もあり意外と忙しく乗れなかったというところでした。
そういうあれこれで、終わってみると僕らの *4 本がすごく好評なのをみてすごく悔しいなり焦るなりをしました。

  • 自分が書いたのではこのようなことにはならないのだろう
  • 同人誌なのに自分の子供感がなくて即売会にちょっと居づらい

そんな思いをもって帰路についたので、おそらく次回も僕らは何かしら頒布するつもりではいますが *5、次は僕も本を書こうと思います。
なので何を書くかもまだきまっていないですが、とりあえずリポジトリは作り、gamiさんの手順を参考にして、なにもないPDFを出力できるようにしました。

そういえば今朝公開されたgamiさんのブログに技術書典サークル参加へのいろいろなノウハウが乗っていたので参考にしたいと思います。笑

jumpei-ikegami.hatenablog.com

最後に

技術書典5で「完全SIer脱出マニュアル」でかってくださった皆様本当にありがとうございました。
あまり制作には多く関わっていませんでしたが、しがないラジオというのが愛されていることもわかったので非常に嬉しかったです。

また、次自分も出すという宣言をここでしたのでしっかり頑張ります。

*1:コミケでも技術書はうられているようですが。

*2:ありがとうございます

*3:記事公開時点ではリリースされておりませんが

*4:あえてそれでも僕らのといいますが

*5:あまり決まってはいないですが

textlint のルール作成 はじめました

f:id:zuckey_17:20181007235919j:plain

前回はこちらでtextlintを簡単に触ってみました。

公式サイトのCreating Rulesを参考にします。

【注意】

説明の都合上、ASTなどの単語が出てきておりますが、僕自身あまり深く理解しておりません。 細かな理解が誤っていれば教えていただきたいです。

no-todoを作る

前回も利用したno-todoというルールを自分で作成してみます。

no-todoというルールは

  • - [ ]がリストのすべての項目に含まれないこと
  • todo:がプレーンなテキストの一部に含まれないこと

を確かめるためのLintルールでした。

準備

create-textlint-ruleのインストールとプロジェクトの作成

// 公式ではグローバルにインストールしていますが、ここではローカルにインストールしています。
$ npm install create-textlint-rule

// create-textlint-rule <ルール名> を実行すると、
// "textlint-rule-<ルール名>"というプロジェクトディレクトリが作成されます。
$ npx create-textlint-rule no-todo

$ ls textlint-rule-no-todo

-rw-r--r--    1 zuckey  staff     598 10  7 23:12 README.md
-rw-r--r--    1 zuckey  staff  179575 10  7 23:12 package-lock.json
drwxr-xr-x    4 zuckey  staff     128 10  7 23:12 lib
drwxr-xr-x  392 zuckey  staff   12544 10  7 23:12 node_modules
-rw-r--r--    1 zuckey  staff     471 10  7 23:12 package.json
drwxr-xr-x    3 zuckey  staff      96 10  7 23:12 test
drwxr-xr-x    3 zuckey  staff      96 10  7 23:12 src

コードを書く

先程作成した、プロジェクト直下src/の中にコードを記載していきます。

Stringノードのチェック

/**
 * @param {RuleContext} context
 */
export default function(context) {
    // さまざまなプロパティが定義されているので事前に受け取っておく
    const { Syntax, getSource, RuleError, report } = context;
    
    // ルールオブジェクトを返す。
    return {
        // [Syntax.Str]は、markdown文書を解析して得られたASTのStr(ing)ノードに対して
        // ルールを設定したいときに返却するオブジェクトのプロパティとして定義します。
        // ここの中で report メソッドを呼ぶと、エラーの表示が可能です。
        [Syntax.Str](node) { 
            const text = getSource(node); // ノードから文字列だけを取り出しています。

            const match = text.match(/todo:/i);
            // "todo:"という文字列がないかどうかを、正規表現で大文字小文字関係なくチェックします。
            // 存在すれば、↓ のif文の中でreportを呼びます。
            if (match) {
                report(
                    node,
                    new RuleError(`Found TODO: '${text}'`, {
                        index: match.index
                    })
                );
            }
        },
    };
}

ListItem ノードのチェック

/**
 * @param {RuleContext} context
 */
export default function(context) {
    const { Syntax, getSource, RuleError, report } = context;
    return {
        ...,
        // [Syntax.ListItem]は、markdown文書を解析して得られたASTのListItemノードに対して
        // ルールを設定したいときに返却するオブジェクトのプロパティとして定義します。
        [Syntax.ListItem](node) {
            const text = context.getSource(node); // ノードから文字列だけを取り出しています。
            const match = text.match(/\[\s+\]\s/i);
            // "[ ]"という文字列がないかどうかを、正規表現でチェックします。
            // 存在すれば、↓ のif文の中でreportを呼びます。
            if (match) {
                report(
                    node,
                    new context.RuleError(`Found TODO: '${text}'`, {
                        index: match.index
                    })
                );
            }
        }
    };
}

コードのビルド

$ npm run build

と実行すると、トランスパイルされ、lib/以下にコードが書き込まれます。

実行

例のによって以下のファイルをチェックします。

# file.md

- [ ] textlintのブログを書く

`- [ ]` これはエラーにならない

# 接続詞

僕は、眠い。
しかし、ブログを書かないと行けない。
僕は2あれ。
しかし、眠い。
$ npx textlint --rulesdir lib/ file.md -f pretty-error
no-todo: Found TODO: '- [ ] textlintのブログを書く'
/Users/zuckey/.ghq/github.com/zuckeyM-17/textlint-sandbox/file.md:3:3
         v
    2.
    3. - [ ] textlintのブログを書く
    4.
         ^

いい感じですね。

しかし、本来であれば、この実行ではわからないのですが、todo:という文字列が、コードブロックや、リンクの中にある場合に反応しないようにしたいです。 そのような高度なルールの作成は次回以降に譲りたいと思います。

まとめ

  • textlintルールの作り方の概要
    • 特定のプロパティをもったルールオブジェクトを返す
    • そのプロパティの中で各ノードへの処理を書く
    • reportメソッドを読んでエラーを出力する
  • これで簡単なルールは作れそうです

textlint に入門した

久しぶりのブログ更新。 最近入門して、ちょっとだけ書いているtextlint、入門したときのメモをまとめました。

f:id:zuckey_17:20180930233703j:plain

参考

公式サイト

textlint.github.io

ドットインストール

既存のものを使うだけならドットインストールでも学習できます。

https://dotinstall.com/lessons/basic_textlint

前提

  1. すべてのルールはプラグインになっており、textlintはそれらを読み込むようにして利用する
  2. textlintはMarkdownやテキスト、HTMLをプラグインを利用してパースする
  3. textlintはテキストのパターンを評価するためにASTとしてテキストを扱う
  4. textlintはエラーや注意を表示する

npm を使ってインストールして適当なルールを使ってみる

npmとルールのインストール

$ mkdir textlint-test && cd textlint-test
$ npm init --yes
$ npm install -D textlint
$ npm install -D textlint-rule-no-todo
$ ./node_modules/.bin/textlint --rule no-todo file.md

マークダウンをファイル(file.md)に保存

# file.md

- [ ] textlintのブログを書く

`- [ ]` これはエラーにならない

はじめてのチェック

$ npx textlint --rule no-todo file.md

/Users/zuckey/.ghq/github.com/zuckeyM-17/textlint-sandbox/file.md
  3:3  error  Found TODO: '- [ ] textlintのブログを書く'  no-todo

✖ 1 problem (1 error, 0 warnings)

設定ファイル

.textlintrcを作成する

$ npx textlint --init
.textlintrc is created.

$ cat .textlintrc
{
  "filters": {},
  "rules": {
    "no-todo": true
  }
}%

先程、 no-todoをインストールしているので、initの時点で追加されている

ルールの指定は必要ない

$ npx textlint file.md

設定ファイルの書き方

{
  "rules": {
    "no-todo": true, // ルールを有効に
    "< name >": false, # ルールを無効に
    "< name >": { // ルールには細かいオプションがあるものもある
        "key": "value" // JSONのkey valueで表される
    },
    "< name >":  {
        "severity": "warning" // error か warningルールの重要度を設定できる
    }
  }
}

ルールは作成できることができ、ルール名は以下のようになっている必要がある

  • textlint-rule-< name >
  • < name >
  • @scope/textlint-rule-< name >
  • @scope/< name >

プリセットルール

プリセットルールはルールの集まりで、以下のようにして使う

集まりすべてを有効にする場合

{
  "rules": {
    "preset-example": true
  }
}

もちろん各ルールで設定可能

{
  "rules": {
    "preset-example": {
        "foo": true // preset-exampleに含まれる、"text-lint-rule-foo" はこのようにして指定する
    }
  }
}

実際にやってみる

連続する接続詞でwarning

ルールのインストール

$ npm install -D  textlint-rule-no-start-duplicated-conjunction

設定を書く

.textlintrc

{
  "filters": {},
  "rules": {
    "no-todo": true,
    "no-start-duplicated-conjunction": { // text-lint-< name > のnameをkeyにする
      "interval": 2 # 同じ接続詞を使うには、 2文以上間に挟まないといけない
    }
  }
}

以下のファイルを保存

# file.md

- [ ] textlintのブログを書く

`- [ ]` これはエラーにならない

## 接続詞

僕は、眠い。
しかし、ブログを書かないといけない。
なぜなら週1でブログを書くと決めているからだ。
しかし、眠い。

動かしてみる

$ npx textlint file.md

/Users/zuckey/.ghq/github.com/zuckeyM-17/textlint-sandbox/file.md
   3:3  error  Found TODO: '- [ ] textlintのブログを書く'  no-todo
  11:1  error  Don't repeat "しかし" in 2 phrases          no-start-duplicated-conjunction

✖ 2 problems (2 errors, 0 warnings)

他の設定

.textlintrc

{
  "filters": {},
  "rules": {
    "no-todo": true,
    "no-start-duplicated-conjunction": {
      "interval": 2,
      "severity": "warning" # severityをwarningにしてみる
    }
  }
}
$ npx textlint file.md

/Users/zuckey/.ghq/github.com/zuckeyM-17/textlint-sandbox/file.md
   3:3  error    Found TODO: '- [ ] textlintのブログを書く'  no-todo
  12:1  warning  Don't repeat "しかし" in 2 phrases          no-start-duplicated-conjunction

✖ 2 problems (1 error, 1 warning)

まとめ

  • 簡単にtextlintに入門した
  • 次はルールを作成したい

CSSだけでアコーディオンを実装する

僕はCSSが苦手なんですが、必要にかられてアコーディオンの実装をしたいということになり、少し前にCSS単体で実現できると聞いたのでやってみました。

  • CSS上級者はこんな実装しない
  • この実装はこういうときに困る

などありましたらご指摘いただければ嬉しいです。

動き

FAQページによくありがちです。

f:id:zuckey_17:20180702000417g:plain

実装

<div class="faq">
    <input type="checkbox" id="question1" class="checkbox" />
    <label for="question1">
        <div class="question">
            <span class="text">質問①</span>
            <span class="collapse-handle"></span>
        </div>
    </label>
    <div class="answer">
        質問①の答えです
    </div>
    <input type="checkbox" id="question2" class="checkbox" />
    <label for="question2">
        <div class="question">
            <span class="text">質問②</span>
            <span class="collapse-handle"></span>
        </div>
    </label>
    <div class="answer">
        質問②の答えです
    </div>
</div>
  .faq .question {
      height: 4em;
      display: flex;
      position: relative;
  }
  
  .faq label {
    display: block;
    cursor :pointer;
    transition: all 0.5s;
  }
  
  /* チェックボックスは表示しない */
  .faq input {
    display: none;
  }

  /* 開けることを示すための下矢印と答えをチェックボックスが入る前の状態にセット */

  .faq .collapse-handle {
    height: 0.7em;
    width: 1.5em;
    background: image-url("collapse_allow_close.svg") no-repeat;
    position: absolute;
    right: 1em;
    top: 40%;
  }
  
  .faq .answer {
    height: 0;
    padding: 0;
    overflow: hidden;
    opacity: 0;
    transition: 0.8s;
  }

  /* チェックボックスにチェックが入ったら、以下の要素が変更になる */
  
  .checkbox:checked + label .collapse-handle {
    height: 0.7em;
    width: 1.5em;
    background: image-url("collapse_allow_open.svg") no-repeat;
    position: absolute;
    right: 1em;
    top: 40%;
  }
  
  .checkbox:checked + label + .answer {
    height: auto;
    opacity: 1;
    background-color: #FAFAFA;
    color: #0A0E1F;
    display: flex;
    align-items: flex-start;
    padding: 1em 1em 1em 0;
  }

まとめ

意外と簡単にできました。
普段ならJSでやってしまいがちですが、CSSでやるのは新鮮でした。
これから頑張っていきたい気持ち💪

Railsの画像アップロードで必要に応じてCarrierWaveを回避したい

github.com

Ruby on Railsのフォームから画像をサーバにアップロードするというのを CarrierWave というGemを使って実装しています。

状況

ユーザー作成時に、基本的にはフォームからアバター用画像をアップロードさせるという簡単な実装で、もし画像の指定がなければそれを適用するというようなときにデフォルト画像を適用するということを実装しようとして少しハマったのでその回避方法を書いておきます。
Gemの中身を調べるということまで手が及んでいないので、回避方法が誤っているなどありましたら、指摘いただけると嬉しいです。

今回の僕の環境はRails 5.1です。

実装

実際には、S3へのデータアップロードなどをしていますが、今回は簡単のため、

  • name
  • image

f:id:zuckey_17:20180624233709p:plain:w200

を入力したら

  • Userのレコードが作成され
  • image にはファイル名が入り
  • 画像の実態はpublic/uploads に保存される

というような実装になっています。

# app/uploaders/user_image_uploader.rb

class UserImageUploader < CarrierWave::Uploader::Base
  storage :file
end

# app/models/users.rb

class User < ApplicationRecord
  mount_uploader :image, UserImageUploader
end

# app/controllers/user_controler.rb

  def create
    @user = User.new(user_params)
    
    respond_to do |format|
      if @user.save
        format.html { redirect_to @user, notice: 'User was successfully created.' }
        format.json { render :show, status: :created, location: @user }
      else
        format.html { render :new }
        format.json { render json: @user.errors, status: :unprocessable_entity }
      end
    end
  end

上記のように書くだけで先程の要件を満たすことができます。

回避方法

f:id:zuckey_17:20180624234531p:plain

さて、今回実現したいことは単純で、public/uploads/default.pngUser.imageに指定したいということです。

実際は以下の方法でCarrierWavemount_uploader 回避しました。

# app/controllers/user_controler.rb

  def create
    @user = User.new(user_params)

    # imageに指定がないとき、デフォルト画像を使う
    @user[:image] = "default.png" if params[:image].nil?
    @user.save
    
    respond_to do |format|
      if @user.save
        format.html { redirect_to @user, notice: 'User was successfully created.' }
        format.json { render :show, status: :created, location: @user }
      else
        format.html { render :new }
        format.json { render json: @user.errors, status: :unprocessable_entity }
      end
    end
  end

@user.imageに代入することはできず、ブラケットでの指定しか成功しませんでした。 もう少し中身を見て挙動を把握したいです。

Slack KPT Bot を簡単に導入できるように整備した

github.com

以前から作っていたGoogle Apps Script(GAS) + Google Spreadsheet によるKPT botを導入しやすくしました。

以下、READMEを日本語で

使い方

1. クローンして初期化

$ git clone git@github.com:zuckeyM-17/gas-kpt-bot.git
$ npm install
$ npm run login

2. Google Spreadsheetを作成

作成し、↓のようなURLを入手します。

https://docs.google.com/spreadsheets/d/【Google Spreadsheet ID】/edit#gid=0

3. Spreadsheetに紐付いたGASを作成

$ npx clasp create "slack kpt bot" "【Google Spreadsheet ID】"

4. SlackでIncoming Webhook のURLを入手し、index.jsを変更

ref: https://api.slack.com/incoming-webhooks

index.jsの1行目を修正します。

5. デプロイ

npm run deploy

6. GASのコンソール上で、スクリプトを公開する

ウェブアプリケーションとして公開

f:id:zuckey_17:20180610234629p:plain

設定

f:id:zuckey_17:20180610234638p:plain

POSTのためのURLを入手

f:id:zuckey_17:20180610234701p:plain

7. 上記で入手したURLをもとにOutgoing Webhookを設定

ref: https://api.slack.com/custom-integrations/outgoing-webhooks

トリガーとなる単語には以下を入れます。

K,P,T,k,p,t,start,end

参考

blog.zuckey17.org

blog.zuckey17.org

blog.zuckey17.org

blog.zuckey17.org

【GAS】Google Apps Scriptをコマンドラインからデプロイできる `clasp` の使い方

github.com

Google Apps Script(以下、GAS)をデプロイするときに、いちいちブラウザでコンソールを立ち上げ、コピペするのが嫌だなぁと思っていました。
また、コードの変更をGitで管理したいというような思いもあると思います。
claspはGASのデプロイやバージョン管理などの機能を提供してくれるコマンドラインツールです。
本エントリでは、その使い方を簡単に紹介します。

準備

$ mkdir gas-clasp && cd gas-clasp
$ npm init
$ npm i @google/clasp

公式のREADMEでは、グローバルにインストールするのが書かれていますが、npxでたたけば良いのでローカルで行います。

(参考)

blog.zuckey17.org

GASのAPI利用

https://script.google.com/home/usersettings

こちらにアクセスして、GASのAPI利用を可能にします。 f:id:zuckey_17:20180610175646p:plain:w300

Googleへのログイン

以下のコマンドで、Googleアカウントの認証を行います。

npx clasp login --ownkey

ブラウザが立ち上がり、認証が求められます。アカウントを選ぶと以下のように表示されるので「許可」を押します。

f:id:zuckey_17:20180610175232p:plain:w200

--ownkeyはカレントディレクトリに、.clasprc.jsonを作成するためのオプションで、つけないとホームディレクトリに配置されてしまいます。

スクリプトの作成

GASの作成には以下のコマンドを叩きます。

$ npx clasp create "スクリプトタイトル"
Created new script: https://script.google.com/d/【script id】/edit
Cloned 1 file.
└─ appsscript.json

上記の結果を見れば分かる通り、表示されたURLのGASを1つ作り、そこから設定ファイルとしてappsscript.jsonをダウンロードしてくれます。

また、このコマンドは裏で.clasp.jsonというファイルを作成しており、中身は作成したGASのscriptIdの値を示したjsonになっています。

その他の方法

Google Spreadsheetと紐付いたGASの作成

以前、このブログで

blog.zuckey17.org

Google Spreadsheet を GASから扱った事例を紹介しましたが、既存のGoogle Spreadsheet に紐付いた GASを作成したい場合は、そのGoogle SpreadsheetのIDを指定して作成することも可能です。

$ npx clasp create "スクリプトタイトル" "Google SpreadsheetのID"

そのIDは、SpreadsheetのURLの

https://docs.google.com/spreadsheets/d/【...この部分...】/edit

です。

既存スクリプトのクローン

また既存のスクリプトをクローンすることもできます。

$ npx clasp clone "スクリプト ID"

で、スクリプトIDは、GASページURLの

https://script.google.com/d/【...この部分...】/edit

です。

JavaScriptでのコードの記述とPush

今回はGASのコードについての説明は省略します。 以下のJavaScriptファイルを作成して、index.jsとして保存します。

function myFunction() {
   Logger.log('hello, world!');
}

この段階で以下のようなディレクトリは以下のようになっています。

 index.js
 appsscript.json
 .clasp.json
 .clasprc.json
 package-lock.json
 package.json
 node_modules

GASにアップロードするのは、index.jsappsscript.jsonだけなので、.claspignoreというファイルを作成して、アップロードするファイルを制限します。

**/**
!index.js
!appsscript.json

こちらを.claspignoreに保存します。

そして、以下のコマンドをたたけば、デプロイが完了します。

$ npx clasp push
└─ appsscript.json
└─ index.js
Pushed 2 files.

$ npx clasp deploy
Created version 1.
- 【バージョンのハッシュ】

すると、index.jsindex.gsとして登録されていることがわかります。

f:id:zuckey_17:20180610215116p:plain:w300

ここで実行ボタンを押し、⌘ + enterでログを確認すると、

f:id:zuckey_17:20180610215756p:plain

というのが確認でき、デプロイが成功していることがわかります。

まとめ

claspを使って、コマンドラインからGASをデプロイというのをやってみました。 簡単にできたので、これまで作っていたKPT Botをこれで簡単にデプロイできるようにしたいと思っています。

参考になれば嬉しいです!

感想、ご指摘などあれば、コメントもしくはTwitter @zuckey17まで連絡いただけますと嬉しいです!!