yikegaya’s blog

yikegayaのブログ

マークダウンをHTMLに変換するツールをいくつか試してみた

Gitのレポジトリにコミットされているマークダウンで書かれたドキュメントがあって、それをCI/CDパイプラインの中でHTMLに変換してBASIC認証かけて公開したい。。

というタスクを業務で振られたんでマークダウン をHTMLに変換するツールをいくつか試した。その時の内容を書いてみる。

要件

  • マークダウン ファイルをCLIでHTMLに変換したい
  • 所定のフォルダの中にHTMLとして吐き出してNginxでBASIC認証をかけて公開したい
  • マークダウンの中のリンク先のファイル拡張子がマークダウン用の「.md」になっているのを「.html」に変換したい
  • 見た目もいい感じにしたい

試したもの

  • pandoc
  • redcarpet
  • vuepress
  • gatsby.js

以下それぞれのツールについて

pandoc

  • ツール自体の実装はHaskell
  • マークダウン→HTMLだけじゃなくPDFとかOfficeファイルとかいろんなフォーマットに対応している
  • Haskellの実行環境作らなくても公式のDockerイメージがあるのでpullしてすぐ使える。
  • ファイル内のリンク先の拡張子を「.md」から「.html」に変換するのがちょっと面倒。フィルタ用のスクリプトを書いてコマンド実行時にオプションで渡さないといけないんだけどその場合その言語の実行環境も作らないといけなかったりちょっと面倒。
  • 個人的にRubyが好きなのでRubyスクリプト書く手段を調べた結果、rubyでのラッパーだったりrubyでフィルターを書くgemがあったんでその辺りは良さそう
  • 見た目をいい感じにするにはCSS書いて読み込ませる必要あり

redcarpet

  • マークダウンをHTMLに変換するRubyのgem
  • Railsの中のhelper関数の中で使ってWebアプリケーション上でマークダウン をHTMLにして表示する。みたいな使われ方が多そう
  • 今回のような使い方で見た目をいい感じにするにはCSS書いて読み込ませる必要あり。Railsアプリ内で使うならRailsCSSを適用すればいいんだろうけど、今回の要件だとちょっと面倒
  • カスタマイズするにはgem内で定義されているクラスを再オープンしてメソッドをオーバライドしていく↓

こんな感じ

class CustomHTMLRender < Redcarpet::Render::HTML
  def table(header, body)
    "<table class='table'>" \
      "<thead>#{header}</thead>" \
      "<tbody>#{body}</tbody>" \
    "</table>"
  end
end

  options ||= {
    tables: true,
  }
  markdown = Redcarpet::Markdown.new(CustomHTMLRender, options)

vuepress

  • 見た目がデフォルトでもかっこいい。カスタマイズも楽
  • カスタマイズはスタイルシート追加、config追加で基本対応可能
  • 名前にvueとついてるけど普通に使う分にはVueのコードを書く必要ない。凝ったカスタマイズをする場合はVueでプログラムを書く必要あり
  • 開発用のホットリロードのコマンドと本番配布用にdistフォルダにbundleしたコードを吐き出すコマンドが用意されていてwebpackを書く必要がない
  • 特に設定したりプログラム書かなくてもリンク先を「.html」に自動で変換してくれる

gatsby

  • 有名で情報量が多い
  • vuepressは名前の通りvueだけどこっちはReactっぽく書ける
  • マークダウンに対応するにはjsライブラリを追加する必要あり
  • GraphQLで記事を管理できる。ワードプレスみたいなCMS感覚で使いたいならvuepressよりこっちのがいいのかもしれない。。けど今回のCI/CDでコマンド叩いてHTML作ってNginxで公開すればいいのでvuepressのがいいかな?

結果

要件に合致しているvuepressにしました。

Vue +GoでPocketの記事にまとめてタグ付ができるサービスを作った

Pocketに記事をどんどん放り込んでいて整理できてない。。というモヤモヤがありPocketのAPIを使ってうまいこと整理できないかと考えてた。

https://ikeyu0806.hatenablog.com/entry/2021/05/09/113856

で、整理するならとりあえず記事にタグ付けできる機能があるんでそれを使うと良さそうなんだけど現状あんまり使えていない。

  • 公式のWebページからだと記事1つ1つ選択してタグ付しないといけないので面倒な気がする
  • 記事をクリップした時にタグ付けできるけどつける習慣がないので現状ほとんどついてない

なのでまとめてチェックボックスとかで選択してタグ付できる自分用Webサービスを作ってみたのでここまでの振り返りと感想を書いてみる。

画面こんな感じ

f:id:ikeyu0806720:20210528170348p:plain

ソースコード

github.com

使ってる技術

バックエンド: Go言語

選んだ理由

  • 仕事ではバックエンドはずっとRubyしか書いてないんでプライベートは静的型付け言語で書きたい。
  • 最近マイクロサービスの現場でよく使われてるんでその内触る機会があるかも
  • プライベートでちょっとしたもの作るのに良さそう
  • 構造体とか並行処理とかポインタとかスクリプト言語にはないいろいろな気づきが得られる。。気がする

フロントエンド: Vue

選んだ理由

  • 現状Reactとほぼ二択だけど仕事がReactなんで違う方触りたい

あとCSSフレームワークはなんとなくbuefyを使ってみた。

buefy.org

あと勉強のため以下の方針で作ってみる

  • 以前GoでHTTPサーバを作ったときはライブラリ使ったけど今回は標準ライブラリのみで書いてみる
  • Nuxtは使わずスクラッチでwebpackを書いてbundleする

開発環境

  • ホスト側での作業だとGOPATHの管理が面倒だったり将来的にサーバにあげようと思った時に楽なんでDocker上で作業した。
  • GoのサーバでWebpackでbundleしたjsを読み込んだHTMLをホストしてくれればいいしMySQLとかRedisみたいなストレージも今回は不要。なのでコンテナ一つで事足りるのでDocker Composeは使わない。
  • webpackはwatchコマンド付きでbundleしてbundle先のvolumeはDockerコンテナと共有する。これでいちいち再起動しなくても開発できる。

実装

やってることざっくり

クライアントのボタンを押すとバックエンドからPocketのAPIを叩いてリクエストークン取得→リクエストークン認証→アクセスキー取得→取得したアクセスキーを使って記事取得やタグ付をする。

よくあるWebのシステムで特に特殊なところはないかな。

面倒だったところ

Goでjsonを扱う場合、同じキー値とデータ型を持った構造体を定義してその構造体を基にjsonをパースしていくんだけどネストが深いとちょっと戸惑う

{
    "consumer_key": "コンシューマキー",
    "access_token": "アクセストークン",
    "actions": [
        {
            "action": "tags_add",
            "item_id": "528698205",
            "tags": [
                "プログラミング", "javascript"
            ]
        }
    ]
}

Goの構造体

type ModifyRequestParam struct {
    ConsumerKey string          `json:"consumer_key"`
    AccessToken string          `json:"access_token"`
    Actions     []*ModifyAction `json:"actions"`
}

type ModifyAction struct {
    Action string   `json:"action"`
    ItemID string   `json:"item_id"`
    Tags   []string `json:"tags"`
}

リクエス

# request_paramはVueからのリクエスト。Vueのフォームから入力したItemIDやTagが渡される。
modify_action := new(entity.ModifyAction)
modify_action.Action = "tags_add"
modify_action.ItemID = request_param.ItemIDs
modify_action.Tags = request_param.Tags

使ってみてどうなのか

  • タグ付は実際本家より楽にできるようになって快適になった気がする。
  • 記事一覧も本家だと画像付きで表示されて1画面で確認できる記事本数が少なかったり、クリックしたときに記事の元のリンクに飛んでくれず、Pocket内のサイトで文章が表示されるんで今回作ったサイトで見た方が快適な気がしている。

雑に作った割には結構使えるんじゃないかなこれ。 多分Pocket本家のサイトも有料プランがあるんで課金すれば便利に使えると思うんだけど無料枠だと流石に使いにくいところはあるので。

今後やりたいこと、課題

  • バックエンドの処理を並行処理にしていきたい
  • とりあえずローカルでしか動かない前提なのでアクセストークンの扱いはゆるい(有効期限なしでlocalStorageに入れてある)
  • いい感じに記事ソートしたいとかアーカイブもまとめてできるようにしたいとかUIもっといい感じに作れそう。。

とかやりたいこと諸々あるけど、とりあえずタグなしの記事にまとめてタグ付けして記事を整理しやすくなったので一旦当初の目的は果たせたかな。

curlでPocketのアクセストークンを取得する

あとあと読みたいネットの記事のURLをPocketにどんどん放り込んでたんだけど、未読がたまっていく一方だったりして少し整理したりいい感じに管理できるプログラム作れないかな。。と思ってとりあえずPocketのAPI叩いてクリップした記事を取得してみた。

Pocket

Pocket

  • Read It Later, Inc
  • ニュース
  • 無料
apps.apple.com

Pocketの認証手順

他のFacebookとかTwitterOAuth認証と同じような流れ。割と面倒。

  1. Pocketにログインする。
  2. Developer向けのページにアクセスしてApplicationを登録する。 https://getpocket.com/developer/
  3. アプリケーションを登録するとコンシューマキーが発行されるので抑えておく
  4. コンシューマキーをbodyに含めてPocketのAPIにhttpリクエストを送ってリクエストークンを取得する
  5. リクエストークンを含んだURLをwebで開いて認証する。この時にリダイレクト先のWebページを用意しておく
  6. 認証されたリクエストークンを使って再びPocketのAPIにhttpリクエストを送ってアクセストークンを取得
  7. アクセストークンが取得できたらそのトークンを元にhttpリクエスト送って目的の記事検索やその他諸々ができるようになる。

curlでの取得例

リクエストークン取得

curl -X POST -H "Content-Type: application/json" -H "X-Accept: application/json"
-d '{"consumer_key":"{発行したコンシューマキー}", "redirect_uri":"http://localhost:8080"}'
https://getpocket.com/v3/oauth/request

リクエストークン認証

この時にリダイレクト先のURLが必要なんだけど今回はCLI上で試してみたいだけなんで適当にlocalhostphpのサーバ実行してそこを指定する

# リダイレクト先のサーバ
php -S localhost:8080

# openコマンドでchromeからwebページを開く
open -a '/Applications/Google Chrome.app' https://getpocket.com/auth/authorize?request_token={リクエストトークン}&redirect_uri=http://localhost:8080

うまくいくとこんな感じのサイトが出るので認証 f:id:ikeyu0806720:20210509113004p:plain

アクセストークン取得

curl -X POST -H "Content-Type: application/json" -H "X-Accept: application/json"
-d '{"consumer_key":"{コンシューマキー}", "code":"{リクエストトークン}"}'
https://getpocket.com/v3/oauth/authorize

試しに記事1件取得してみる

curl -X POST -H "Content-Type: application/json" -H "X-Accept: application/json"
-d '{"consumer_key":"{コンシューマキー}", "code":"{リクエストトークン}"}'
https://getpocket.com/v3/oauth/authorize

結果

{
    "status": 1,
    "complete": 1,
    "list": {
        "3325624398": {
            "item_id": "3325624398",
            "resolved_id": "3325624398",
            "given_url": "https://togetter.com/li/1710737",
            "given_title": "【朗報】mRNAワクチンは、変異株にも相当効果が高い~「圧倒的すぎて専門家も引き気味」「ガラケー時代のiPhone」 - Togetter",
            "favorite": "0",
            "status": "0",
            "time_added": "1620527156",
            "time_updated": "1620527156",
            "time_read": "0",
            "time_favorited": "0",
            "sort_id": 0,
            "resolved_title": "【朗報】mRNAワクチンは、変異株にも相当効果が高い~「圧倒的すぎて専門家も引き気味」「ガラケー時代のiPhone」",
            "resolved_url": "https://togetter.com/li/1710737",
            "excerpt": "よく、この時代に間に合ったものだな…/※なお「こういう人もいる」的な感じで、リプ欄でみた、ちょっと賛同し難い意見も収録しています",
            "is_article": "0",
            "is_index": "0",
            "has_video": "0",
            "has_image": "0",
            "word_count": "0",
            "lang": "ja",
            "top_image_url": "https://s.togetter.com/ogp/9eb2843312e0379332e5717d0bb6165e-1200x630.png",
            "domain_metadata": {
                "name": "トゥギャッター公式",
                "logo": "https://logo.clearbit.com/togetter.com?size=800",
                "greyscale_logo": "https://logo.clearbit.com/togetter.com?size=800&greyscale=true"
            },
            "listen_duration_estimate": 0
        }
    },
    "error": null,
    "search_meta": {
        "search_type": "normal"
    },
    "since": 1620527161
}

とりあえず取得できたけどどう使おうかな。。

iOS開発に少しだけ手を出してみた感想

仕事で使うとかモバイルエンジニアにキャリアチェンジしたいとかではないんだけど、iOS開発環境少し触ってみたので感想書いてみる。

現状

ここ数年Webの開発エンジニアポジションで直接のモバイル開発経験なし。モバイルに渡すサーバサイドのAPIとかWebView開発経験はある。

動機

  • 単なる好奇心
  • ReactNativeを軽く動かしてみたが中身がブラックボックスすぎて少しは中を知ってみたくなった。
  • 自分が担当でなくてもモバイル開発ってどんなもんかちょっとでも知っておくと業務上のコミュニケーションが取りやすくなったりいいことあるかなとか

やったこと

Udemyのセールで1300円でオンライン講座買ったけど37時間あり異常に充実している。。ただやっぱり長くてとりあえず半分まで受講したところで気が済んでしまった状況。

残りの内容はJSON解析して画面に描画したりFirebaseと連動させたりみたいな内容みたいだけどその辺りは普段Reactでやってる作業とそんなに差がなさそうなんで気が済んでしまった。

受講した内容

  • Swiftについての講義
  • Xcode上でのStoryBoardを使ったiPhoneアプリ開発
  • WebViewでのWebサイト表示方法
  • MVCパターンでのフォルダ分割
  • CocoaPodsでのライブラリ管理
  • JSONXML解析
  • Firebase連携
  • airbnbのlottie-iosライブラリを使ったアニメーション実装

など

買った講座

www.udemy.com

感想

  • swiftは概ね違和感なく使えそう。ただDelegateパターンでのプログラミングはまだ慣れない。
  • あとswiftにはオプショナル型っていうnilの値を許容するか制御する型の仕組みがあって独特な感じだけどそこまでxcodeで書いてると勝手に修正してくれるんでそんなに辛くはなさそう。
  • 簡単な画面のアプリ作るくらいなら今すぐできそう。ただ複雑なUI(カレンダーとか)開発だとWeb画面の開発と勝手が違うんでしばらくつまづきそう
  • xcodeでの開発が最初戸惑う

xcode上での開発

  • xcodeでStoryBoardっていうXMLファイルを選択するとiPhoneの実画面のプレビュー画像みたいなのが出てくる
  • そこに配置したボタンやテキストなどのUI部品を配置
  • UI部品をクリックしてxcode上で色や文字の大きさフォントをぽちぽち選んでくとStoryBoardに反映される
  • 配置したUI部品を選択してCtrlキーを押しながらエディタ状のコードにドラッグアンドドロップして動きをつけていく

。。みたいな開発をやったんだけどこの作業がプログラミングいらずのGUI作業でどんどん進むので最初戸惑った。

GUI作業でコードが更新されてくと何が起こってんだこれ?ってなるけど実際は画面構成とUI部品の見た目を制御するXMLとそこに動作をつけるSwiftを作っているのであってこれはWebでいうHTML、CSSとJSの関係とまあ似たようなもんだという理解でいる。

慣れるとHTMLにCSSやJS当てていくよりも直感的に画面作れそうだしこっちの方が初学者には優しいんだろうなとは思う。

まだよくわかってないこと

  • SwiftUIっていう新しい開発の仕組みがあるらしくてこれが何なのか
  • SwiftUIは現状の現場ではあまり使われてないっぽいけどいつ頃主流になるのか、現状の開発とどう共存してくのか
  • iOS開発環境だとC、C++とMetalって言語も使えるらしいけど普通のアプリ作るときにはほぼほぼ関係ないコア機能作るときの選択。ってことでいいのかな?
  • AR、VR機械学習絡みのAPIは面白そうだけど全くわかってない

これどこまで首突っ込むか

現状ポジション的にはWebのエンジニアでだしモバイル開発のAPIに詳しくなるより普段触ってる技術(サーバサイドのAPI開発、JS、AWS)の深い知識を身につけていく方がまだ優先度高いかな。。と思う。大体その領域で十分仕事あるし。

ただWebの開発とは違った体験が新鮮で面白くはあるんでちょくちょく気晴らし程度に触ってはみたいかな。

フリーランス1年目の確定申告振り返り

フリーランスになって初の確定申告があった。税理士には頼まずに自力でやってみたんでその記録。

確定申告に向けての準備

フリーランスなりたての時にやったこと

  • クラウドの会計ソフトに登録する。自分の場合はfreeeのスタータプランに登録した。
  • 事業用の口座、クレジットカードを作って会計ソフトに連動させる。
  • 家で仕事してるんで家賃、水道光熱費、ガス代、通信費などを按分として計上するようにfreeeで設定する
  • 家賃、水道光熱費、ガス台、通信費を事業用口座に切り替える(これ面倒)

その他節税

  • ふるさと納税をしておく
  • ソフトウェア業なので技術書、PC代、オンライン講座の受講料、資格の受験料は経費としてつけた

早めにやっとけばよかったこと

  • マイナンバーカード を作ってなかったんでe-taxができなかった。
  • Suicaを途中からスマホアプリに切り替えた。カードだと交通費の管理が面倒。
  • ほぼほぼ無いけど紙の領収書が出た時Google Driveに入れてたんだけどfreeeに画像登録できるんでそっちの方がよかったか?
  • 昨年の1月は会社員だったんで会社から源泉徴収票貰っとかないといけなかった。退職時に貰ってなかったんで連絡して貰ったんだけど早めに準備しとけばよかったかな。
  • 家賃、水道光熱費、ガス代、通信費の切り替えに時間がかかるので開業前にやってしまうくらいでよかったな。
  • フリーランスだと住民税、年金、健康保険の支払いも自分でやるんだけど自動引き落としに切り替えないとかなり面倒なんでこれも早くやっちゃった方が楽だった

失敗

素人だったんで1回目申告した後に見事に税務署から指摘があって出し直した。。 売り上げを銀行口座に振り込まれた日付で計上してたんだけど、請求書を発行した時点で計上しないとダメだったらしく2020年12月に請求書発行して1月に振り込まれた分の売り上げが抜けてた。

ありがたいことに税務署の人が丁寧に対応してくれて、名前とか住所書いて判子押して出し押せばOKの訂正版送ってくれたんで期限内には出し直せたけど焦った。

感想

とりあえず会計ソフトにまめに登録しとけば税理士なしでもなんとかなる感じはあったかな。来年もフリーランス続けてる可能性は高いんで次は一発で通したい。

Kindle Unlimitedで技術書いろいろ読んでみた

Amazonの技術書眺めてたら割とKindle Unlimitedで読みたい本あるな。。と思い加入していろいろ技術書読んでみた。

使ってみた感じ、買ったら3000円くらいする技術書が月1000円で読み放題で思ったよりいい本もあるんで結構良い気がしている。

以下読んだ本の記録。 最近別にインフラ担当じゃないけどインフラ周りに興味があってそっち方面の本多め。

Amazon Web Servicesネットワーク入門

個人開発でAWSのECS環境作ったんだけどネットワーク周り復習したくなって読んだ。

AWS Lambda実践ガイド

そういえばLabmda使ったことないな。。と思いどんなもんか気になって読んでみた。 どんな場面で使えるのか。ってのはざっくり把握できた気がする。

AWS Lambda実践ガイド

AWS Lambda実践ガイド

kubectl完全逆引きリファレンス

Kubernetesがどんなもんか気になるので読んでみた

実践Helm─自作アプリをKubernetesクラスタに簡単デプロイ!

これもKubernetesがどんなもんか気になるので読んでみた。Kubenetesのデプロイツール。現状Kubenetesのデプロイツールはほぼこれが標準らしい。

Google Cloud Platform エンタープライズ設計ガイド

GCP触ったことがなかったのでどんなもんか気になって読んだ。AWSとの比較が多くてGCPは何が違うのか知るにはちょうどよかった気がする

さわって学ぶクラウドインフラ docker基礎からのコンテナ構築

似たような本読んだことあったしDocker普通に触ってるからそんなに目新しい情報もなかったけどKubenetesについても書かれていてその辺りは勉強になった気がする

GitLab実践ガイド

今の案件がGitLabなので読んでみた。Githubに比べてCI/CD関連の機能多めってことは薄々感じてたけどその辺りがまとまって書いてあってよかった。

以上

terraformでRDSに接続するAPIサーバをECSにデプロイする

terraformでGoのAPIを動かすECSを作ろうとした時、DBの接続で結構手こずった。

初回のterraform実行でRDS構築してさらにECR(コンテナのレジストリ)からdockerイメージをpullしてECS内にコンテナを立ち上げるんだけど、その時に以下の2点がうまくできずにしばらくハマった。

  1. どうやってRDSのエンドポイントとかDB名、ユーザ、パスワード情報渡そうか
  2. Migrationのタイミング

で、とりあえず作った内容を書いてみる

1のRDSの情報

terraformならECSのresouce定義内でRDSのエンドポイントやDB名を参照できる。ユーザ、パスワードはterraformの変数として定義して同じように渡せる。

以下タスク定義内に書いたコンテナの情報。dockerイメージはAPIエンドポイントを立ち上げるGoで書いたサーバ。

resource "aws_ecs_task_definition" "movie-backend" {
  family = "movie_backend"

  requires_compatibilities = ["FARGATE"]

  cpu    = "256"
  memory = "512"

  network_mode = "awsvpc"

  execution_role_arn    = aws_iam_role.ecs_task_execution_role.arn

  container_definitions = <<EOL
[
  {
    "name": "movie_backend",
    "image": "public.ecr.aws/e9z3g6v3/movie-info-backend:latest",
    "portMappings": [
      {
        "containerPort": 8080,
        "hostPort": 8080
      }
    ],
    "environment": [
      {
        "name": "DB_HOST",
        "value": "${aws_db_instance.movie-backend.endpoint}"
      },
      {
        "name": "DB_USER",
        "value": "${var.db_user}"
      },
      {
        "name": "DB_PASSWORD",
        "value": "${var.db_pass}"
      },
      {
        "name": "DB_NAME",
        "value": "${aws_db_instance.movie-backend.name}"
      },
      {
        "name": "DATA_SOURCE",
        "value": "${var.db_user}:${var.db_pass}@tcp(${aws_db_instance.movie-backend.endpoint})/${aws_db_instance.movie-backend.name}?charset=utf8&parseTime=True&loc=Local"
      }
    ],
    "logConfiguration": {
      "logDriver": "awslogs",
      "options": {
        "awslogs-region": "ap-northeast-1",
        "awslogs-stream-prefix": "movie-backend",
        "awslogs-group": "/ecs/movie-backend"
      }
    }
  }
]
EOL
}

こんな感じでそのままGoのコードに渡せばMySQLにつながるように文字列作れるの便利だな。。

${var.db_user}:${var.db_pass}@tcp(${aws_db_instance.movie-backend.endpoint})/${aws_db_instance.movie-backend.name}?charset=utf8&parseTime=True&loc=Local

2のMigration

ここはとりあえずDB、Migration用のECSのタスク定義を作って手動で実行することにした。今のところ手動だけどここはAWSのCodeBuild使って自動化もできそう

作ったterraformのコード。大体上のサーバ実行用のタスク定義と同じなんだけど、commandオプションでイメージ内で最終的に実行されるコマンドをMigrationに上書きしている

resource "aws_ecs_task_definition" "movie-db-migration" { family = "movie_db_migration"

requires_compatibilities = ["FARGATE"]

cpu = "256" memory = "512"

network_mode = "awsvpc"

execution_role_arn = aws_iam_role.ecs_task_execution_role.arn

container_definitions = <<EOL [ { "name": "movie_db_migrate", "image": "public.ecr.aws/e9z3g6v3/movie-info-backend:latest", "command": ["sql-migrate", "up"], "environment": [ { "name": "DATA_SOURCE", "value": "${var.db_user}:${var.db_pass}@tcp(${aws_db_instance.movie-backend.endpoint})/${aws_db_instance.movie-backend.name}?charset=utf8&parseTime=True&loc=Local" } ], "logConfiguration": { "logDriver": "awslogs", "options": { "awslogs-region": "ap-northeast-1", "awslogs-stream-prefix": "movie-db-migration", "awslogs-group": "/ecs/movie-db-migration" } } } ] EOL }

以上の作業レポジトリ

github.com