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まで連絡いただけますと嬉しいです!!

Slack KPT Bot 導入してから1ヶ月経ったので振り返りをしてみた

blog.zuckey17.org blog.zuckey17.org

このあたりのエントリで、Slackでの KPT Botを作成しました。

導入から1ヶ月くらい経ったので、振り返りたいと思います。

導入の経緯

入社して*1「なんか改善MTG」という打ち合わせが設定されていたのですが、みんながパラパラと共有事項を話して終わりでした。
改善MTGということなので、なにか問題を見つけて改善できるものにしたいなと思いました。

急激に人が増えたこともあり、*2業務上の単なる会話ではなく、もっとお互いを知っていけるような会話を増やしたい(というより、僕がチームの人を知っていきたい)という気持ちもありました。

Botに優しさを求める

はじめ、Botのアイコンは、Slackデフォルトのロボットアイコンでしたが、投稿したときにテンションが上がらない、ということでかわいいタレントさん*3をアイコンに設定するという案を投げたところ、吉岡里帆さんという名前が上がりました。

f:id:zuckey_17:20180607081457p:plain:w300

さらに、registering.... 受け付けました!というのがあまり愛がない、という意見も出ました。

f:id:zuckey_17:20180607084705p:plain:w300

なので振り切ってめちゃくちゃ可愛くしたところ、

f:id:zuckey_17:20180607085444p:plain:w300

キャピキャピしすぎて見るのが辛い、との指摘を受け、最終的には↓の感じに収まりました。

f:id:zuckey_17:20180607085108p:plain:w300

改善の早さもKeepで褒められました。

f:id:zuckey_17:20180607085700p:plain:w300

チーム全員でのデバッグ

チームは全員開発者なので、デバッグも全員でやりました。
Bot開発に伴う言語の処理について、諸先輩方が優しく教えてくださいます😇

f:id:zuckey_17:20180607090352p:plain:w200

f:id:zuckey_17:20180609022955p:plain:w400

f:id:zuckey_17:20180609023014p:plain:w400

勉強になりますね。

Keepで拍手しながら進めると楽しい

Keepは良かったことを「良かったよね」と確認することができるので、拍手して進めます。
せっかく機能をリリースしたのにそれを喜ばないのは寂しいし、良いことをすれば「よかったよ」と言われたいものです。
また、このKPT BotはKeep => Problem => Tryの順にまとめてくれるので、振り返りのはじめにこれでテンションを上げていくというのは、その場に馴染むことができるという意味で成功でした。

ちょっと強引でも良いからかさ増しする

よくKPTで問題に挙げられると思うのですが、SlackでのKPTでもやはり普段から意識しないとあまり数がたまらないです。
なので、導入した僕が積極的にくだらないことをKPTに載せたり、裏でこういうことを書いとけばいいんじゃない、とか、新卒のエンジニアにとりあえずなんでもいいから1つ書くように言ったりしてました。
*4 項目が多いだけで活発感が出るので非常に良いです。

まとめ

KPT、入社すぐの僕が導入するのはやはり少し抵抗がありましたが、実際やってみて

f:id:zuckey_17:20180609023310p:plain:w300

というKeepを上げていただいたり、

f:id:zuckey_17:20180609023512p:plain:w400

という感じで、業務や環境だけでなく、振り返りの会自体も改善していっているので、導入してすごく良かったなと思えました。

当初個人的に目標だったチームの人のことを知る、というのも小さなところから良いと思ったこと、悪いと思ったこと、ちょっとした挑戦をカジュアルに知れて、仲良くなれた気がしました。
みなさんもぜひやってみては、いかがでしょうか??

*1:3月末入社でした

*2:4ヶ月で2倍!!

*3:チームは悲しいことに現在男性ばかりなのです。本当はキャラクターとか作りたい

*4:この根回しは全員の前で暴露されましたがw

しがないラジオMeetup 1を開催しました!

shiganai.connpass.com

開催しました。
総勢40数名の方に来ていただき、本当に感謝です。
参加してくださった方々本当にありがとうございました。

Podcastはリスナーの可視化が難しいとは聞きますが、2017年の3月から始めて1年2ヶ月でここまで多くの方に支えていただいているのだなと再認識し感動しました。

細かいまとめなどは、togetterや参加者の方々がまとめてくださっているので、そちらを参照ください。

togetter.com

twitter.com

また、編集を言い訳にほとんど何もしなかった僕に変わって、この会を企画から準備までしてくれた gamiさん、本当にありがとうございました。

これからも、さまざまな「しがない」エピソードをお届けしていくつもりですので、応援よろしくおねがいします。