selmertsxの素振り日記

ひたすら日々の素振り内容を書き続けるだけの日記

JavaScriptで正規表現(入門レベル)

モチベーション

現在、iioptという画像圧縮ツールを作成しています。このツールの一つの機能として、git pre-commit にhookしてcommit対象の画像を抽出し、圧縮が必要であれば圧縮するという機能を開発中です。

本記事は、そこで学んだ正規表現の書き方について記載します。記事の作成にあたって、下記のサイトを参考にさせていただきました。

developer.mozilla.org

※ 今まであんまり正規表現に触ってこなかったゆるふわエンジニアの記事なので、ある程度触った方には学ぶことがないかと思います。そのような方は、回れ右して頂くか、間違ってる点とう発見したらコメント貰えると喜びます!

やりたいこと

git pre-commit 時に git diff --cached --name-status を実行したとき、下記の結果が得られたとします。

# git diff --cached --name-status
M       images/need_match.jpg
M       images/not_matchjpg
M       images/need_match.png
M       images/not_match.ts
D       not_match.jpg
R100    images/sample.jpg       not_match.jpg
A       need_match.jpg
M       need_match.png

このとき、頭文字がM,Aであり、かつ拡張子がpng, jpgのファイル名のみを取得したいです。なので、求める最終的なアウトプットは下記のようになります。

'images/need_match.jpg',
'images/need_match.png',
'need_match.jpg',
'need_match.png'

最終的に採用したコード

  const input = `
M       images/need_match.jpg
M       images/not_matchjpg
M       images/need_match.png
M       images/not_match.ts
D       not_match.jpg
R100    images/sample.jpg       not_match.jpg
A       need_match.jpg
M       need_match.png
`
  const re = /^[AM]\s.+\.(?:jpg|png)$/gm;
  const files = input.match(re).map((line) => {
    console.log(line);
    return line.replace(/^[AM]\s*/, "")
  });
  console.log(files);
'images/need_match.jpg',
'images/need_match.png',
'need_match.jpg',
'need_match.png'

パターンについて

頭文字の判断

まず、頭文字 A & Mのもののみ取得したいです。 その場合、正規表現/^[AM]/となります。 実際にコードを書いて確かめると、下記のような結果が得られます。

console.log(/^[AM]/.test('A       sample1.jpg'));
console.log(/^[AM]/.test('D       hoge.jpg'));
true
false

ここで把握するべきものは、^[]の2つです。

まず[] についてです。これは文字列の集合を意味し、囲まれた文字のいずれか 1 個にマッチします。[]の中では - を使えば範囲を指定することができます。[0-9]とすれば、0から9までの数値すべてにマッチします。

> /[A-Z]/.test('BC123')
true
> /[A-B]/.test('123')
false

次の^ は、入力の先頭にマッチします。例えば、'BA' という文字列があったときに、/A/はマッチしますが、/^A/ はマッチしません。

ということで /^[AM]/ という正規表現を用意すれば、頭文字 A or M の文字列にマッチさせることができます。

特殊文字\sでタブ or 空白にマッチさせる

頭文字 A or M にマッチさせることができたので、次は空白 or タブにマッチさせたい。ここで把握するべき要素は、\s である。

まず、'M hoge.jpg' にマッチする正規表現を書きたい。 このとき必要となるのが、特殊文字 \s であす。 \sはmdnのドキュメントにおいて、「スペース、タブ、改ページ、改行を含む 1 個のホワイトスペース文字にマッチ」するものと書かれています。

> /^[AM]\s/.test('Mhoge')
false
> /^[AM]\s/.test('M hoge')
true

末尾マッチとグループ化

これまでの内容で出来上がった正規表現は、 /^[AM]\s/となります。この正規表現からマッチする文字列は、 A hogehogeM hogehoge のような文字列です。

ここから、A hoge_hage/hogehoge.jpgM hogehage.png のような文字列にマッチさせる正規表現になるよう、仕上げていく必要があります。今回は ., +, と$、そしてグループ化です。

まず . ですが、これは改行文字以外のすべての文字列にヒットします。次に+は、直前の文字の1回以上の繰り返しにマッチします。よって、.+ は1文字以上何かの文字列があれば、全てにマッチさせることになります。

> /^[AM]\s.+/.test('A &&&hogeあ')
true

このように自由度高く受け入れられるようにした理由としては、ユーザーがどのような名前の画像を用意するかわからなかったため、受け入れられる文字列を幅広く設定したかったからです。iioptはCLIツールなので、自分で自分に悪意を持って操作しない限り、問題が起きる可能性が少ないと考えたためです。

次にjpg, pngの拡張子を持つパスを取得できるようにします。ここで重要になってくるのが、$です。まず、jpgの拡張子を持つファイルだけ取得できるようにしましょう。$入力の末尾にマッチする特殊文字なので、/.jpg$/ のように書くと、hogehoge.jpg などのファイルパスにマッチできるようになります。これまで正規表現とくっつけると、下記のようになります。

> /^[AM]\s.+\.jpg$/.test('A &&&/hoge')
false
> /^[AM]\s.+\.jpg$/.test('A &&&/hoge.jpg')
true
> /^[AM]\s.+\.jpg$/.test('A &&&/hoge.jpga')
false

jpgだけでなくて、pngも取得したいです。ここでグループ化 という概念が出てきます。

正規表現 - JavaScript | MDN

グループ化は、(?: PATTERN) といった書き方で使います。簡単な例を以下に記載します。

 // "foo"で1つのパターンとして認識するため、"foofoo"がtrueになる
> /(?:foo){2,3}/.test("foofoo")
true
// "o" がパターンとして認識される。そのため、foofoo だとoが3つ続かないのでfalse
> /foo{2,3}/.test("foofoo")  
false
// "foooo" はfoの後に o が2~3続くのでtrue
> /foo{2,3}/.test("foooo") 
true

このグループ化を使うと、jpg, png の両方の拡張子に対応する正規表現は、/\.(?:jpg|png)$/ となります。 これまでの正規表現と繋げると、/^[AM]\s.+\.(?:jpg|png)$/ となって、ここのサイトを使って図にすると、下記の図のようになります。

f:id:selmertsx:20180130223827p:plain

※ この正規表現に、セキュリティ的に問題がある!などご存知の方がいましたら、ご指摘頂けると幸いです。

正規表現フラグ

/m フラグ

これまでの結果から、M need_match.png のような文字列が与えられたときに、マッチするために必要な正規表現/^[AM]\s.+\.(?:jpg|png)$/ であるということがわかりました。でも今回与えられる文字列は複数行あります。そういうときは、 /m フラグをつけましょう。複数検索ができるようになります。/mをつけて、確認した結果を以下に記載します。

  const input = `
M       images/not_matchjpg
M       images/need_match.jpg
M       images/need_match.png
M       images/not_match.ts
D       not_match.jpg
R100    images/sample.jpg       not_match.jpg
A       need_match.jpg
M       need_match.png
`
  const re = /^[AM]\s.+\.(?:jpg|png)$/m;
  const matches = input.match(re);
  console.log(matches);
[ 'M       images/need_match.jpg',
  index: 1,
  input: '\nM       images/need_match.jpg\nM ...

2行目の取得するべき文字列を取得してくれました。 が、3行目以降は検索しませんでした。 以上の結果から、/m だけでは、全ての行から一致するものを取得することができません。

/mg フラグ

そこで必要となってくるのが、/g フラグです。 これは、グローバルサーチ と呼ばれるもので、マッチした全ての文字列を取得することができます。 下記が実行結果になります。

// /g をつけないと最初に一致したもので終了
> `hage hoge fuga fugu hogo`.match(/fu\w*/);
[ 'fuga', index: 10, input: 'hage hoge fuga fugu hogo' ]
// /gを付けると、マッチする文字列全てを取得可能
> `hage hoge fuga fugu hogo`.match(/fu\w*/g);
[ 'fuga', 'fugu' ]

これらのフラグですが、組み合わせて使うことが可能です。ということで、/m と /gを組み合わせると下記のような結果になります。

  console.log('今回マッチさせたいもの');
  const input = `
Mimages/not_match.jpg
M       images/not_matchjpg
M       images/need_match.png
M       images/not_match.ts
D       not_match.jpg
R100    images/sample.jpg       not_match.jpg
A       need_match.jpg
M       need_match.png
A       &&HOGE/need_match.png
`
  const re = /^[AM]\s.+\.(?:jpg|png)$/gm;
  const files = input.match(re).map((line) => {
    return line.replace(/^[AM]\s*/, "")
  });
  console.log(files);
今回マッチさせたいもの
[ 'images/need_match.png',
  'need_match.jpg',
  'need_match.png',
  '&&HOGE/need_match.png' ]

ということで、欲しいデータだけ取得することができました。

ふつうのLinuxプログラミング読書会 (第2章)

2章: Linuxカーネルの世界

最初に

本資料は、学習者が本を元にサマリーを書いたものです。 分量はものすごく減っていますし、間違ったことを書いている可能性もあります。なので、気になった人は直接本を読んで頂けると幸いです!!

知識として学べること

Linux世界とカーネル

Linuxとは何かと聞かれたとき、OSと答える人が多い。けれども、OSとは厳密な定義がなく、Linux世界 = OS とは言い難い。本書では、Linux世界とは カーネル が作り出した世界であると捉えて話を進める。

カーネルのコードは、/boot下にあるので簡単に確認できる。

# amazon linuxのカーネル
$ ls  /boot/ | grep vm
vmlinuz-4.9.77-31.58.amzn1.x86_64

カーネルのお仕事は、コンピュータを構成する全てのハードウェアとソフトウェアを管理すること。なので、デバイスデバイスドライバの取扱いもカーネルのお仕事と言って良い。

カーネルにお仕事をさせるための手段としてシステムコールというものが存在する。macOS上でシステムコールについて確認する方法として、dtrussというコマンドが存在する。

# vscodeのdtruss結果
$ sudo dtruss -c -p 85439
kevent(0x7, 0x7FFF59C8AE60, 0x0)         = 1 0
read(0x1F, "\001\0", 0x10000)        = 882 0
write(0xB, "\0", 0x1)        = 1 0
write(0x1F, "\001\0", 0x30)      = 48 0
write(0xB, "\0", 0x1)        = 1 0
select(0x8, 0x700004AB7E20, 0x0, 0x0, 0x700004AB7EA8)        = 1 0

...
CALL                                        COUNT
psynch_cvwait                                   2
read                                           14
psynch_mutexdrop                               15
psynch_mutexwait                               15
write                                          19
kevent                                         21
select                                         21

上記システムコールが、vscode上でちょいちょいコードを書いてみたりした結果、実行されたものである。read, write, select等々はmanコマンドで確認することができる。

ライブラリ

Linuxで使う関数をまとめたものをライブラリと呼ぶ。任意のライブラリに用意されている関数をライブラリ関数と呼ぶ。

システムコールカーネルに仕事をさせるものであるが、 ライブラリ関数は必ずしもそうでない。例えば strlen() などは、システムコールを利用しない。

libc

ライブラリの中でも、標準Cライブラリが特に重要。 printf() などが格納されている。

PRINTF(3)                BSD Library Functions Manual                PRINTF(3)

NAME
     printf, fprintf, sprintf, snprintf, asprintf, dprintf, vprintf, vfprintf, vsprintf, vsnprintf,
     vasprintf, vdprintf -- formatted output conversion

LIBRARY
     Standard C Library (libc, -lc) #<= ここに書いてある

nodejsでdebuggerを動かす

モチベーション

typescript & vscode & jest環境でdebugするにあたって、debuggerを使っていたが、そもそもdebuggerって何よ。ってくらいの理解だったので、この機会に色々確認したかった。

debuggerの実行方法

https://nodejs.org/dist/latest-v6.x/docs/api/debugger.html そもそも debuggerとは、nodejs本体に搭載されている機能である。

console.log('hogehoge');
var a = "hogehoge";
debugger;
a = "1234";
console.log('hagehage');
$ node debug test.js
< Debugger listening on [::]:5858
connecting to 127.0.0.1:5858 ... ok
break in test.js:1
> 1 console.log('hogehoge');
  2 debugger;
  3
debug> repl
Press Ctrl + C to leave debug repl
> a
'hogehoge'

ということで、何の設定もしなくても動く。

debuggerのコマンド

watch

指定した変数の変化を追ってくれる

debug> watch('a');
debug> n
< hogehoge
break in test.js:2
Watchers:
  0: a = undefined

  1 console.log('hogehoge');
> 2 var a = "hogehoge";
  3 debugger;
  4 a = "1234";
debug> n
break in test.js:3
Watchers:
  0: a = "hogehoge"

  1 console.log('hogehoge');
  2 var a = "hogehoge";
> 3 debugger;
  4 a = "1234";
  5 console.log('hagehage');
debug> unwatch('a')
debug> n
break in test.js:4
  2 var a = "a";
  3 debugger;
> 4 a = "1234";
  5 console.log('hagehage');

list

Listはソースコードを、実行中の部分から前後5行の範囲内で表示してくれる。

debug> list(1)
> 1 console.log('hogehoge');
  2 var a = "hogehoge";
debug> list(5)
> 1 console.log('hogehoge');
  2 var a = "hogehoge";
  3 debugger;
  4 a = "1234";
  5 console.log('hagehage');

とりあえず、普通に使う分には把握した気がする。

ふつうのLinuxプログラミング読書 (1章)

第1章は下記のような構成になっています

  • この本で学ぶことができること
  • C言語開発の一通りの手順
  • 情報の探し方と基礎知識について

それぞれについて、簡単にまとめます

この本で学べる内容

Linuxを構成するものやその世界を見て、Linuxに関する常識を身につけることを目的として、下記3点の概念について学びます。

  • ファイルプロセス
  • プロセス
  • ストリーム

この3点を選んだ理由は、この3つがLinux世界を構成する重要な概念だからです。

C言語開発の一通りの手順

ここでは下記の内容について学びます

gccを使ったビルド

# -o optionでビルドされるプログラム名を指定する
gcc -o ${ビルドされるプログラム名} {Cのプログラム名}

github.com

コマンドライン引数

int main (int argc, char *argv[]){
   ...
}

上記のプログラムにコマンドライン引数を与えて実行すると、argc, argvの値は下記のようになります。

$ ./args x y
argc=3
argv[0] = ./args 
argv[1] = x
argv[2] = y

ここで./args * などのグロブを使って、複数の引数を一度に渡すことも可能です。 また、ダブルコーテーションを使うと、コマンドライン変数をまとめることができます。 ./args "*"./args "x y z"と入力した場合、"*" や "x y z" も1つのコマンドライン引数とみなされます。

情報の探し方と基礎知識

cプログラミングで情報を集める際、manコマンドが便利です。 例えば、man strlen とすると、下記のような出力が得られます。

STRLEN(3)                BSD Library Functions Manual                STRLEN(3)

NAME
     strlen, strnlen -- find length of string

LIBRARY
     Standard C Library (libc, -lc)

SYNOPSIS
     #include <string.h>

     size_t
     strlen(const char *s);

     size_t
     strnlen(const char *s, size_t maxlen);
...

printfのように、コマンドや関数など、複数のセクションに同じ名前のページが存在する場合、man printf と入力すると下記のような出力が得られます。

PRINTF(1)                 BSD General Commands Manual                PRINTF(1)

NAME
     printf -- formatted output

SYNOPSIS
     printf format [arguments ...]
...
SEE ALSO
     echo(1), printf(3)

PRINTF(1)の(1)はコマンドを示します。STRLEN(3) の (3)は関数を示します。 LinuxUnixの世界では、この使われ方はよく出るので覚えておいて損は無いです。その他にも man png と入力するとpngのフォーマットが出力されたりします。表にすると下記の通りです。

1: ユーザーコマンド ( man printf)
2: システムコール (次の章で説明)
3: ライブラリ関数 (`man 3 printf` or `man strlen`)
4: デバイスファイル (`man tcp`)
5: ファイルフォーマット (`man png`)

まとめ

以上、この本で学べる内容、C言語でのビルドから、コマンドの調べ方まで、Linuxの世界を学ぶために必要な道具を渡される感じの1章でした。

自作ライブラリのバージョンの上げ方

モチベーション

github.com

  • iioptのupdateをしたい
  • updateに際して必要な対応を理解し、まとめたい

※ 自作ライブラリの管理は初めてなので、おかしい点があればコメント頂けるとうれしいです!

libraryのupdateで必要なこと

Libraryのバージョンを上げるときには、機能開発とは別に下記2点の対応が必要となってきます。

  • CHANGELOG.mdの作成
  • libraryのversionを上げる

この記事では、それぞれについて、資料を読んだのでまとめます。

CHANGELOG.mdの作成

CHANGELOGの作り方については、下記の資料を参考にしました。

keepachangelog.com

Wiki で一番最初に参照されているので、それなりの知名度があるのでしょう。 Changelog - Wikipedia

この Keep a ChangeLog のホームーページ上で記載されている内容について、簡単にまとめようと思います。

Change Logを作る意味

Change Logとは、このプロジェクトの各リリース間における変更について、何が注目に値するのか、開発者や利用者に対して、正確に把握して貰うために作るものです。

開発者も利用者も、新しく追加された機能や、その背景が気になるはずです。なのでChage Logを作って、それらを追えるようにする必要があります。

Change Logに書く内容

keep-a-changelog/CHANGELOG.md at master · olivierlacan/keep-a-changelog · GitHub

具体的にどのように書くのか把握するため、Keep a Change Logのサイト自体の Change Logを確認しました。抜粋したものを下に書きます。

Changelog
All notable changes to this project will be documented in this file.

The format is based on Keep a Changelog and this project adheres to Semantic Versioning.

Unreleased
1.0.0 - 2017-06-20
Added
New visual identity by @tylerfortune8.
Version navigation.

Changed
Start using "changelog" over "change log" since it's the common usage.
Start versioning based on the current English version at 0.3.0 to help translation authors keep things up-to-date.

0.0.3 - 2014-08-09
Added
"Why should I care?" section mentioning The Changelog podcast.

Change Logは上から最新のものを記載します。 それぞれのバージョンについて、更新内容と更新した日付を書きます。 更新内容については、added, changed, removed, fixed, Security の5つの項目に分けて記載します。それぞれ、書く内容は下記のようになっています。

  • Added: そのバージョンアップで追加した新しい機能
  • Changed: 既存の機能の修正
  • Removed: 機能の削除
  • Fixed: バグの修正
  • Security: 脆弱性対策

小ネタとして、まだリリースしていないけれども開発した内容は Unreleasedに乗せておくとよいとのことです。その理由は下記の通りです。

  • 人々は次のリリースで使えるようになる機能を見ることができる
  • releaseするときに、unreleased sectionを次のリリースバージョンに置き換えれば良い。

libraryのバージョンの上げ方

セマンティック バージョニング 2.0.0 | Semantic Versioning

libraryのバージョンの決め方は、こちらのサイトを参考にしました。

  • APIの変更に互換性のない場合はメジャーバージョンを、
  • 後方互換性があり機能性を追加した場合はマイナーバージョンを、
  • 後方互換性を伴うバグ修正をした場合はパッチバージョンを上げます

ということです。今回のiioptのupdateは機能追加だけなので、v0.2.0 としました。

まとめ

以上、自作ライブラリのバージョンの上げ方をまとめました。SemVerについては深掘りが足りてないので、おいおいちゃんと読んでいこうと思います。

TypeScript & Jest & VSCode環境でDebuggerを動かす

モチベーション

  • TypeScript & Jest & VSCode環境でDebuggerを動かしたい。
  • ちゃんと設定ファイルを見れていなかったので、これを期に一通り確認したい

動作内容

test以外のコードでも、debuggerは効きます。

f:id:selmertsx:20180118213748p:plain f:id:selmertsx:20180118213806p:plain

設定ファイル一覧

vscodeの設定

{
  "version": "0.2.0",
  "configurations": [
    {
      "type": "node",
      "request": "launch",
      "name": "Debug Jest",
      "preLaunchTask": "build", 
      "program": "${workspaceRoot}/node_modules/jest/bin/jest.js",
      "args": [
        "${file}",
        // 現在のプロセスで逐次テストを実行していく設定.
        // この設定をしないと複数のchildプロセスでテストを実行するらしい
        // cliのpackage の性質上、子プロセスがテスト画像を変更してしまうリスクがあるし、
        // debuggingのために有用とのことだったので、この設定を有効にした
        // https://facebook.github.io/jest/docs/en/cli.html#runinband
        "--runInBand", 
        // debugの際はcacheを無効にした。
        // cacheを無効にすると、2倍くらい遅くなるらしい。
        "--no-cache"
      ],
      "cwd": "${workspaceRoot}",
      "console": "integratedTerminal"
    }
  ]
}

tasks.json

https://vscode-doc-jp.github.io/docs/userguide/tasks.html

{
  "version": "2.0.0",
  "tasks": [
    {
      "label": "build",
      "type": "shell",
      "command": "yarn build"
    }
  ]
}

とりあえず、テスト前にbuildすれば良いだけの設定。 typeのところには、npm, shell, gulpが使えるようだ。下記資料参照。 https://github.com/Microsoft/vscode/blob/master/.vscode/tasks.json typeのところにyarnが無かったので、shellとおいて commandの部分に yarn buildと書いた。

jest.config.json

https://facebook.github.io/jest/docs/en/configuration.html

module.exports = {
  rootDir: "./",
  transform: {
    "^.+\\.tsx?$": "ts-jest"
  },
  testRegex: "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$",
  modulePathIgnorePatterns: [
    "<rootDir>/build/"
  ],
  moduleFileExtensions: [
    "ts",
    "js",
    "json"
  ]
}

transform はソースコードを変換する必要があるものを正規表現で記載するもの。 typescriptなど利用している場合は、ここに記載する必要がある。 typescriptを使う場合、jest本家は下記のようにコンパイルしている。 https://github.com/facebook/jest/blob/master/examples/typescript/package.json#L18

今回は ts-jestを利用しているため、ts-jestのドキュメントが指定する通りにした。 https://github.com/kulshekhar/ts-jest#usage

tsconfig.json

{
  "compilerOptions": {
    "allowSyntheticDefaultImports": true,
    "alwaysStrict": true,
    "forceConsistentCasingInFileNames": true,
    "module": "commonjs",
    "moduleResolution": "node",
    "noEmitOnError": true,
    "noFallthroughCasesInSwitch": true,
    "noImplicitAny": false,
    "noImplicitThis": true,
    "noImplicitReturns": true,
    "noUnusedParameters": false,
    "noUnusedLocals": false,
    "outDir": "../build/",
    "removeComments": true,
    "strictNullChecks": false,
    "target": "es5",
    "traceResolution": false
  },
  "include": [
    "**/*.ts"
  ],
  "exclude": [
    "**/*.spec.ts"
  ]
}

debugのための設定としては、exclude の部分だけである。 testコードはcompileする必要がないので、excludeに含めた。

Serverless Architecture を翻訳しようとしてFAQを読んだ

Frequently Asked Questions

モチベーション

serverlessに関して、体系的な知識を身につけたかったので、下記ページの読んで、自分のブログに理解した内容を掲載したかった。

martinfowler.com

FAQの内容

Frequently Asked Questions

Can I translate one of your web articles?

In general I'm happy for people to do translations. However in recent months I've learned that many translations that are done are of poor quality. So if you do a translation I will try to check that it's of decent quality by finding a colleague to take a look it. As long as they tell me it's ok, I'm happy for it to be posted. I do need you to put a link to the original article and the date of the translation in the translation. (The date helps people spot if I've changed the article since the translation.) Once you've done that please let me know and I will link to it in my paper, (but that may take a while if I'm busy traveling). The only exception to this is material that I'm working into a book. I have to be careful with these since they will be published commercially.

I'm afraid I don't host translations on martinfowler.com as I'm not comfortable serving up text that I can't comprehend.

I haven't checked the older translations to see if they are of decent quality, if you see one that is of poor quality, please email me and I'll at least remove the link.

翻訳したものを公開するときは、その文章が正しいかどうかチェックしたいから、必ず fowler氏のチェックを通してねとのこと。

結論

martinfowler.com の文章を翻訳するときは、必ずfowlerさんにemailで送ってチェックして貰うこと。チェックしてもらってない文章は、ブログに掲載しないこと。