最近はどういうaltJsが使われているのか
Twitterでアンケートとりました。偏りはあると思いますが、採用判断の参考にどうぞ。
おおむね6割がJavaScript (or Babel)、2割がTypeScript、1割がCoffeeScript、Flowはだいぶ少ない、という感じでしょうか。
最近のJavaScript系言語がいくつかありますが、業務では主に何を使ってますか。
— FUJI Goro (@__gfx__) September 14, 2016
TypeScriptが意外と使われてますね、1割くらいかと思っていました。
dts-gen(1) でJavaScripファイルからdtsを生成する
2016-09-12のJS: Polymer 2.0 Preview、Service Worker入門、JSから型定義ファイルの生成 - JSer.info で gen-flow-files
と dts-gen
を知ったのでちょっとだけ使ってみました。
gen-flow-files (as of flow v0.32.0)
Release v0.32.0 · facebook/flow · GitHub
We're starting work on flow gen-flow-files, which consumes Flow code and outputs .flow files containing only the types exported. It's alpha-level and we're still iterating on it, so use at your own peril!
flow(1)のサブコマンドで、flow type annotation付きのJSファイルから型定義ファイルを抽出するものです。処理対象のコードは、すべて型アノテーション付きでなければいけません。
ふつうのJSファイルには使えないので、何に使うのかよくわかりませんでした。npm packageを作る際、pure JSと型定義ファイルに分割する用でしょうか。TypeScriptはそうしているので。
dts-gen (as of v0.4.16)
こちらは独立したコマンドで、素のJSから TypeScript definition files (dts) を生成するコマンドです。
いまのところ入力はglobalにインストールされたモジュールかロードされたシンボルのみなので使い勝手がイマイチですが、説明にある通り手書きでdtsを書く際のスターターとして使い、そのままだとany
が多いので型を書き足していくような使い方を想定していそうです。
たとえばnodejs標準ライブラリのchild_processはこんな感じです。
dts-gen -s -m child_process
export class ChildProcess { constructor(); kill(sig: any): any; ref(): void; spawn(options: any): any; unref(): void; } export function exec(command: any, ...args: any[]): any; export function execFile(file: any, ...args: any[]): any; export function execFileSync(...args: any[]): any; export function execSync(command: any, ...args: any[]): any; export function fork(modulePath: any, ...args: any[]): any; export function spawn(...args: any[]): any; export function spawnSync(...args: any[]): any;
class ChildProcess
のメソッドはそこそこ揃ってますが、プロパティは全滅ですね。 ChildProcess#disconnect()
など見当たらないメソッドもあります。
コードを読んでみると、入力を純粋にJSのオブジェクトとして評価してそこから情報を取り出しているだけの非常に単純なものなのでした。将来的にはフロー解析して型推論してくれるとよさそうです。なお、tscの --allowJs
との差などは調べてません。
2016年9月現在だとReactアプリのTypeScript採用は時期尚早(※追記あり)
追記(2019/04/16): 2017年半ばにここで触れているプロジェクトはTSに移行しました。今となってはTS+Reactの組み合わせは全く問題がなく、むしろ非常に相性のよい組み合わせであるとすらいえます。
TypeScript化の調査 2016年9月現在(React v15.3.1, TypeScript 2.0-rc)の話です。
いま開発しているウェブアプリのフロントエンドをTypeScript化しようと思ってちょっと調べてみたんですが、今やるのはいくらTypeScript推進派でもちょっと厳しいなと。
- TypeScriptでimportできるライブラリは、TypeScriptのコード(.ts)またはdts: TypeScript definition files (.d.ts) のみ
- Reactは素のJS + 一部FlowType なのでdtsの公式提供は期待できない
- DefinitelyTypedのdtsは手動でメンテナンスされているよう
- https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/react
- これが現在 v0.14 ベースで古い
- v15.3ベースに修正するにしても作業量が多すぎてつらい
- 仮に今がんばってv15.3ベースにしたとしても将来にわたって最新版に追従し続けるのがつらい
- つまりこの規模の大型ライブラリのdtsを手作業でメンテナンスし続けるのはつらい
将来的には、 ReactコードのほぼすべてにFlowTypeアノテーションがついてdtsがそこから自動生成されるというようにはできるだろうと思っているのですが、現状そうではないので、繰り返しになりますが現時点での導入はかなり厳しいと思います。
次のように、v15.3のランタイムとv0.14のdtsを組み合わせてなんとなく動かすことはできるんですが、たぶん茨の道だろうと思いますね…:
追記:
--allowJs
でとりあえず導入みたいなのはありかもしれないですね。TypeScriptの恩恵を受けられないので素のJSよりどのくらい楽になるかはちょっとやってみないとわかりませんが。
CircleCIの"Auto-cancel redundant builds"を有効にするとPRごとの冗長なCIをキャンセルできる
2016/7/27 にこんなアップデートがあったようです。
Changelog - We've added the option to auto-cancel redundant builds. Read more, and how to enable it, here: https://t.co/MTjOV7ttkF
— CircleCI (@circleci) July 26, 2016
Project Settings → Advanced Settings に設定項目があります。
With the exception of your default branch, we will automatically cancel any queued or running builds on a branch when a newer build is triggered on that same branch. This feature will only apply to builds triggered by pushes to GitHub.
説明にあるとおり、あるブランチの最新のビルドがトリガーされたとき、そのトリガーされたもの以外の実行中ないしキューにあるビルドをキャンセルする機能です。ただし、デフォルトブランチ(=開発ブランチ)ではこの自動キャンセルを行いません。
pull-requestの更新が多くてビルドキューが増えてしまうようであれば有効にしてみてはいかがでしょうか。というかデフォルト有効でもいいくらいですね。
PlantUML Web APIをSpark Framework + Kotlinなウェブアプリにしてみた
PlantUML を使うとき、 java -jar plantuml.jar
だとほんの小さなダイアグラムの生成にも数秒かかってしまいます。ローカルではそれでいいかもしれませんが、ウェブアプリに組み込むのはちょっとイマイチですよね。
PlantUMLはJava製ツールなのでJavaのウェブアプリにしてしまえば高速になるはず、と思ってやってみました。
リポジトリ: https://github.com/gfx/plantuml-service
ウェブアプリといってもパスが /svg/:source
しかないような最小限のものです。 :source
はPlantUML Text Encodingでエンコードされたソース、または生のPlantUMLソースです。特にキャッシュなどはしませんが、手元のMBAだと小さなダイアグラムの生成で70msほどになったので実用に耐えそうです。
PlantUMLは plantuml-serverというウェブアプリも存在しますが、これはどちらかというとデモなので、純粋にもう少し小さなものが欲しかったのでした。
ウェブアプリケーションフレームワークとしては、SinatraライクなSparkを使ってみましたが、この程度の小さなものだとJettyを直接使っても大差ないかもしれません。Kotlinで書いたのは完全に趣味です。またビルドツールとしてはmavenではなくgradleを使っています。
これはデモも兼ねてHerokuにデプロイしています。Gradleで依存性の解決を行っているので特にKotlin由来の問題はありません。いつものHerokuアプリのように以下のファイルを用意するだけですね。
- app.json
- Procfile
- system.properties (Java8を明示するため)
あとは Deploying Gradle Apps on Heroku | Heroku Dev Centerにしたがって、 build.gradle
に stage
というタスクを用意して、そこでビルドを行うようにします。
以上。まだこれ自体はプロダクションに導入しているわけではありませんが、当初の目的であった高速化は達成できました。PlantUMLをJVMで動くウェブアプリにするというアイデアは効果がありそうです。
TypeScript 2.0+NodeJSツールを開発するときのプロジェクト構成
npm dependenciesを更新してGitHub Compare Viewのリンク付きでPRするツールを定期実行する - Islands in the byte stream
このci-npm-updateはTypeScript 2.0 (beta) で書いたので、TypeScript+NodeJSツールを開発するときのプロジェクト構成の一例としてざっと解説しておきます。
最近はRailsなどのウェブアプリのJSもnpmで管理するようになったため、そういう条件でNodeJSツールを開発することも増えてくることでしょう。
Table of Contents
- Table of Contents
- エディタ
- tsconfig.json
- TSLint
- Task Runner
- Visual Studio Code Tasks
- shrinkwrap
- 所感
- See Also
エディタ
Visual Studio CodeはさすがにTypeScriptサポートはしっかりしていて、次のようにTypeScriptの型定義にしたがって補完をだせます。補完の際に引っかかりを感じることもないですし、構文エラーの指摘なども迅速です。
ただし今回はTypeScript 2.0 betaを使いたいので、 npm install --dev typescript@beta
で入れたtypescript language service*1を使うような設定が必要です。
今回は .vscode/settings.json
に、以下のようなJSONを設定しました。
{ "typescript.tsdk": "./node_modules/typescript/lib/" }
vscodeで本格的にプログラミングするのが初めてなら、ざっとドキュメントも目を通しておくといいでしょう。
とりあえずvscodeで最初におぼえるべきショートカット (for Mac) は command + shift + p (コマンドパレット)です。これはIntelliJ IDEAでいうところの command + shfit + a で、エディタコマンドを検索して実行するためのものです。最初はショートカットを無理におぼえるのではなく、コマンドパレット経由で呼び出すのが楽だと思います。
tsconfig.json
tsconfig.json
はTypeScript compiler (tsc) に与える設定です。これを適切に設定しておくと、引数なしでtsc(1)を起動してもよしなにビルドしてくれます。
リファレンス:
ci-npm-updateでの設定:
ci-npm-update/tsconfig.json at master · gfx/ci-npm-update · GitHub
またtsconfigを作るときにプロジェクトの構成と配布形式を意識することになります。
つまり、今回はnpm distとして配布するので、TypeScriptソースファイルは配布物に含めず、ビルドしたJSと型定義ファイル *.d.ts
をパッケージに含めます*2。また開発中は度々ビルドしてJSと型定義ファイルを生成するので、その成果物をどこに置くかを決めます。
成果物はデフォルトだとTSファイルと同じディレクトリにおかれます。このへんは好みだと思うのですが、私はこの挙動を変えたかったので、以下のような構成にしました。
src/
- TSファイル。npm distには含めないlib/
- JSファイルと型定義ファイル。npm distに含める
そしてこれにしたがって outDir
を設定します。
TSLint
コーディングスタイルを強制するツールです。
Visual Studio Codeのエクステンションを入れると編集中に報告してくれるのではかどります。
TSLint | Visual Studio Marketplace
なおtslintは "tslint:recommended" というプリセットがあるのですが、Visual Studio Codeのデフォルトのフォーマットルールだと警告をだす設定があり、一部変えています。なるべく奨励と銘打たれたスタイルで行きたいのですが、難しいものですね。
ci-npm-update/tslint.json at master · gfx/ci-npm-update · GitHub
Task Runner
今回はシンプルにしたかったし、TSファイルをビルドするだけなのでgulpやgruntは使ってません。そのかわり、npm scriptsを設定してコマンド一つでビルドできるようにしています。
ci-npm-update/package.json at master · gfx/ci-npm-update · GitHub
ビルドの際にtslintも通すようにしているので、たとえばmissing semicolonのよようなちょっとしたtslint違反でもビルドが通らないのですが、このへんは慣れれば減ってくるはずです。
Visual Studio Code Tasks
npm scriptsをvscodeから起動するために .vscode/tasks.json
を設定しています。
リファレンス:
プロジェクトの設定:
ci-npm-update/tasks.json at master · gfx/ci-npm-update · GitHub
(このJSONにはコメントを書けるようだ…)
開発中よく使うのは command+shift+b のビルドコマンドでした。
shrinkwrap
このプロジェクトでは npm-shrinkwrap.json
をコミットしていますが、プロジェクトのドッグフーディングのために入れているだけです。。普通のCLIツールやライブラリではこれは不要なので真似しないでください。
所感
TypeScript 2.0とその開発環境は非常に良く出来ていると感じます。もはやES5とは別物といっていいくらい快適ですし、ES2015とくらべても生産性(かけた時間に対する品質のよさ)が高いと思います。
ウェブアプリでもどんどん使っていきたい。
See Also
npm dependenciesを更新してGitHub Compare Viewのリンク付きでPRするツールを定期実行する
タイトルに要素を詰め込みすぎましたが、要は CircleCIを使ってbundle updateを定期実行する - Qiita の npm update
版です。web appのJavaScriptライブラリ管理にnpmを使うとき、依存関係のアップデートを継続的に行うためのツールです。
https://github.com/bitjourney/ci-npm-update
これはいまのところGitHub専用です*1。CIサービスはドッグフーディングも兼ねてCircle CI用の起動スクリプトを同梱してますが、実際には ci-npm-update
を定期実行するだけなので簡単に代替可能です。
これはCircle CI + Heroku Schedulerで動かしていて、以下のようなフローです。Circle CIはJenkins含め他のCIでも動かせますし、Heroku Schedulerはcronなどでも代替できます。
- Heroku Schedulerはci-npm-updateを実行するためのパラメータを渡してCircle CIのビルドを起動する
- ビルドのdeploymentフェーズで
ci-npm-update --execute
を実行する - ci-npm-updateは
npm-shrinkwrap.json
を削除したあとnpm install
をしなおす((npm update --depth 9999
のほうが効率がいいはずなのですが、こちらはある程度の規模のプロジェクトだとOOMで落ちてしまうのでnpm install
しなおすことにしました。)) - ci-npm-updateはさらに
npm shrinkwrap
で依存関係のバージョンを固定し、変更をcommitし、pushし、githubのpull-requestをつくる - このときpull-requestはアップデートの前後の
npm-shrinkwrap.json
をもとにgithub compare viewへのリンクをつくり、PRの本文に入れる
送られるpull-requestはこんな感じです。
https://github.com/gfx/ci-npm-update/pull/8
さて、それではこれを定期的に実行する必要がありますが、これは元エントリのとおりCircle CIのparameterized buildをHeroku Schedulerで起動します。
そのアプリのテンプレートも同リポジトリに入っているので、簡単に試したければ "Deploy to Heroku" ボタンで適当にHeroku appをつくり、Heroku Schedulerで定期実行します。たとえばci-npm-update自身の設定はこんな感じ。
あとは各種設定が必要ですね。
設定
Heroku
"Deploy to Heroku" ボタンでアプリを生成するときにCIRCLECI_TOKEN
などの設定が必要なので、それらを指定してください。
TRIGGER_NAME
は circle.yml でci-npm-updateを起動する条件分岐に使います。必要なければデフォルトでかまいません。
circle.yml
Heroku appの TRIGGER_NAME
が渡ってきたらci-npm-updateを起動する、という設定を追加します。
ci-npm-update自身の設定はこんな感じです。
https://github.com/gfx/ci-npm-update/blob/master/circle.yml
抜粋:
deployment: update-dependencies: branch: master commands: - > if [ -n "${NPM_UPDATE}" ] ; then bin/ci-npm-update --execute fi
Circle CI
Circle Ciからは、(1) github repoにgit pushする権限 (2) github repoにpull-requestを送る権限 がそれぞれ必要です。
まずpushする権限をつくるために、repoに対してread/writeできるuser keyを作ります。
Circle CIの Project Settings -> Checkout SSH key -> Add user key
次に、GitHubのaccess tokenを生成してCircle CIに設定します。
GitHub Settings -> Personal access tokens で "repo" scopeをもつaccess tokenを生成し、
Circle CI projectの環境変数で GITHUB_ACCESS_TOKEN
という名前で設定してください。
また、Circle CIでgit commit
をするために git config user.name
と git config user.email
が必要なので、それぞれ環境変数GIT_USER_NAME
とGIT_USER_EMAIL
を通じて設定します。
設定が済むとこんな感じです。
検証
もろもろうまくいっていれば、以下のコマンドでHeroku Schedulerを待たずにci-npm-updateを起動でき、pull-requstが送られるはずです。
heroku run -a $app './build-circleci'
成功すると、create pull-request apiの結果であるJSONが表示されます。
また、ローカルで実行する場合は、無引数で実行するとdry runモードで実行しPRの内容を確認できます。ただし、dry-runモードでも node_modules/
の内容はアップデートされるのでご注意ください。
まだ作ったばかりで導入実績も少ないですが、よければ使ってみてフィードバックをいただけると嬉しいです。
*1:github enterpriseでも動くはずですが試してません。他のgit hosting serviceも要望があれば対応するかも。
ExcelからtextareaにコピペするとHTMLのtableに変換するスニペット
ユースケース
- Excelから社内ブログやWikiに表をコピペしたい
実装
とりあえず paste
イベントをうけて処理するのでそのようにする。今回はCodeMirrorで制御されているtextarea
なので、CodeMirrorを使ってないない場合は適宜読み替えてください。
ClipboardData
pasteで発行されるClipBoardEventに clipboardData: DataTransfer
プロパティがあります。
spec: https://www.w3.org/TR/clipboard-apis/
// Web IDL dictionary ClipboardEventInit : EventInit { DataTransfer? clipboardData = null; };
こいつが items: DataTransferItemList
をもっています。
// Web IDL interface DataTransfer { attribute DOMString dropEffect; attribute DOMString effectAllowed; [SameObject] readonly attribute DataTransferItemList items; void setDragImage(Element image, long x, long y); /* old interface */ [SameObject] readonly attribute DOMString[] types; DOMString getData(DOMString format); void setData(DOMString format, DOMString data); void clearData(optional DOMString format); [SameObject] readonly attribute FileList files; }
なぜクリップボードの中身がリスト状の items
になっているかというと、コピーしたものの表現が単一ではないからですね。
たとえば、ブラウザからのコピペはスタイルが適用されているので、 text/plain
なプレインテキストと text/html
なHTMLテキスト両方を含みます。画像の場合、参照URLがtext/plain
で、画像のバイナリデータがimage/*
で来ます。ただし、詳細はブラウザごとの違いも多く、たとえばLibreOfficeのスプレッドシートからのペーストは、Chromeだと画像化したtext/png
なitemが含まれますが、IEやFirefoxではテキストデータのみ存在します。
// Web IDL interface DataTransferItemList { readonly attribute unsigned long length; getter DataTransferItem (unsigned long index); }
さて、このitems
の要素は DataTransferItem
で、クリップボードの中身の実体です。kind
は文字列かファイルかという情報で、type
がMIME typeです。ただし、Firefoxなどitems
の存在しないブラウザもあります。まだ仕様が標準化されていないからでしょう。
// Web IDL interface DataTransferItem { readonly attribute DOMString kind; // "string" or "file" readonly attribute DOMString type; // "text/plain", "image/png", ... void getAsString(FunctionStringCallback? _callback); File? getAsFile(); }; callback FunctionStringCallback = void (DOMString data);
ここで、ExcelのシートからChromeにペーストすると、 items
の中身は text/plain
, text/html
, image/png
が入っており、最後の画像データは該当部分のスクショになっています。Excelの要素をGitHub issuesのtextarea
にExcelからコピペすると、画像が貼り付けられるのはそのためです。
画像のコピーの場合、items
は text/plain
, image/png
なので、text/html
の有無が画像コピーとの違いとなります。
なので、「画像のコピーは画像としてアップロードして貼り付けて、Excelからのコピペは<table>...</table>
に変換して貼り付ける」とするならば、以下のロジックでよさそうです。
text/plain
とtext/html
のデータが存在して、text/html
のなかにtable
要素が含まれるとき、そのtable
要素をペーストする
以上を加味すると、ペーストイベントをハンドルするイベントリスナは以下のようになるでしょう。
// JavaScript var editor = CodeMirror.fromTextArea(...); editor.on('paste', function(_, e) { // see https://html.spec.whatwg.org/multipage/interaction.html const clipboardData = e.clipboardData; const plainTextItem = clipboardData.getData('text/plain'); const rtfItem = clipboardData.getData('text/rtf'); const htmlItem = clipboardData.getData('text/html'); const imageItem = (function (items) { if (!items) { return null; } for (let i = 0; i < items.length; i++) { if (items[i].type.startsWith('image/')) { return items[i]; } } return null; })(clipboardData.items); console.log(clipboardData.types); console.log([plainTextItem, rtfItem, htmlItem, imageItem]); if (htmlItem && plainTextItem) { // rich contents, such as Excel and Power Point const html = new DOMParser().parseFromString(htmlItem, 'text/html'); const tables = html.getElementsByTagName('table'); if (tables.length) { // includes <table> tags const content = html.getElementsByTagName('body')[0].innerHTML; editor.replaceSelection(content); e.preventDefault(); e.stopPropagation(); } else { // TODO: ask users what to paste (HTML or plain text) // fallback to default } } else if (imageItem) { const blob = imageItem.getAsFile(); const matched = blob.type.match(/^image\/(\w+)$/); if (matched) { const ext = matched[1]; // e.g. "png", "jpeg", or "gif" const filename = 'clipboard.' + ext; const formData = new FormData(); formData.append('file', blob, filename); upload(formData); e.preventDefault(); e.stopPropagation(); } } });
というわけで実装してみたものの、だいぶ複雑だしブラウザによる挙動の違いもあるのでちょっと使いづらい部分があります。
TSVやCSVをコードスニペットとして貼り付けるとtableとして表示する、というほうがいいかもしれないですね。
公開用DockerイメージにAndroid SDKを含めるのはライセンス違反という話
Android Software Development Kit License Agreementにこういう項目があります。
3.4 You may not use the SDK for any purpose not expressly permitted by the License Agreement. Except to the extent required by applicable third party licenses, you may not: (a) copy (except for backup purposes), modify, adapt, redistribute, decompile, reverse engineer, disassemble, or create derivative works of the SDK or any part of the SDK; or (b) load any part of the SDK onto a mobile handset or any other hardware device except a personal computer, combine any part of the SDK with other software, or distribute any software or device incorporating a part of the SDK.
まあ要は、変更したり再配布したりリバースエンジニアリングしたりするなってことですね。そして、Docker imageにAndroid SDKを含めて再配布するのはまさにライセンス違反というわけです。
DockerでAndroidアプリをビルドするのがそれなりに広まってきた様子ですが、Android SDKの含まれたイメージをDocker Hubで公開してはいけないということになります。
Android SDKライセンスが現状に追いついていないともいえるかもしれませんが、ライセンスはライセンスです。AndroidアプリをDockerでビルドしている各位はご注意ください。
私もうっかりDocker HubでSDK入のイメージを公開しているのですが、近日中に削除する予定です*1。これらに依存している場合はご注意ください。
削除予定のイメージ
*1:Dockerfileを公開することは問題ないので残します。
Makefileを自己文書化するハックを注入するスクリプト: inject-make-help
Re: Makefileを自己文書化する make2help
| おそらくはそれさえも平凡な日々
この主のコマンドのインストールを強制するのめんどうだし、自己文書化hackを注入&管理するコマンドのほうがうれしそう。 / “Makefileを自己文書化する `make2help` | おそらくはそれさえも平凡な日々” https://t.co/VDmjEkyevs
— FUJI Goro (@__gfx__) June 13, 2016
ぼくもmakeはわりと使う方で、AndroidプロジェクトだろうとiOSプロジェクトだろうとよく使うコマンドセットをMakefileとして追加するのが大好きなんですが、self-documented Makefileは存在はしっていたもののセットアップが面倒でやっていませんでした。
じゃあ make2help
はどうかというと、ただひとつのコマンドをインストールすると常self-documented Makefileを使えるという点では優れたアイデアなんですが、じゃあ今度はプロジェクトメンバーに make2help
をインストールするように指示しないといけなくなってしまうのがイマイチだなあと。makeのメリットの一つは、敢えてインストールしなくてもだいたい入っているという点だと思うので。
というわけで、self-documented Makefile hackをMakefileに注入するスクリプトを書きました。まあホントはこのhackもgrep(1)
とか awk(1)
ではなく perl(1)
で書き直したいところですが、とりあえずオリジナルのものを注入するだけです。すでに注入済みなら何もしません。
これなら、誰か一人が inject-make-help
を実行したあとの Makefile
をコミットしておけば誰でも make help
が使えます。どうでしょう。
#!/usr/bin/env perl # inject-make-help(1): To inject help hacks to the Makefile use 5.10.0; use strict; use warnings; use autodie; # FIXME: use perl(1) instead of grep(1), sort(1), and awk(1) my $help_task = q{ help: @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' } =~ s/^[ ]{4}/\t/xmsgr; my $makefile = shift(@ARGV) // 'Makefile'; my $content = do { local $/; open my $fh, '<', $makefile; <$fh>; }; if ($content !~ /\Q$help_task/xms) { $content .= $help_task; my($phony_tasks) = ($content =~ /^\.PHONY:([^\n]+)/xms); if (not defined $phony_tasks) { $content .= q{ .PHONY: help } =~ s/^[ ]{4}/\t/xmsgr; } elsif ($phony_tasks !~ /\bhelp\b/xms) { $content =~ s/^(\.PHONY:[^\n]+)/$1 help/xms; } say "Inject help triks to $makefile"; open my $fh, '>', $makefile; print $fh $content; close $fh; }
ターゲットの Makefile
はこんな感じ:
.DEFAULT_GOAL := help foo: ## The foo task echo foo bar: ## The bar task echo bar
これに対して inject-make-help
をするとこんな感じになります:
.DEFAULT_GOAL := help foo: ## The foo task echo foo bar: ## The bar task echo bar help: @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' .PHONY: help
.PHONY
は既存のものがあればそれに対して help
を追加します。