SwiftUIのTextは+演算子で結合できる(fontやforegroundColorなどを変えていてもできる)

  • SwiftUI.Text には + 演算子が用意されているので Text("Foo") + Text("Bar") のように結合ができる
  • Text("Foo").font(.title) + Text("Bar").foregroundColor(.secondary) のように一部のmodifierを付けていても結合できる
    • これらのmodifierは、多くのViewでは some View が戻り値だが、Textでは型が変わらずにTextのままなので、そのまま + 演算子が使える

基本的なテクニックではあるけれど、modifierを呼び出した結果が some View じゃないのは意外と気にせずに使っている部分でもありそうなのでメモ。

CircleCI公式のDockerイメージにはyqがプリインストールされているので、自分で入れる必要はない

CircleCIのダイナミックコンフィグとsetupワークフロー*1で、path-filtering Orbcontinuation Orbを使う時の話題ですが、タイトルがすべてです。

github.com

設定ファイルをマージする用途でyqを使うために、自分でインストールするような記述を色んなブログで見かけたりもしたのですが、path-filtering/filter job, continuation/continue jobのデフォルトexecutorとなるDockerイメージにはyqがプリインストール済みです。

Open Test Reporting: platform-agnosticなテストレポートのフォーマット

たまたまJUnit 5.9.0のリリースノートを読んでいたら面白いものが目に着きました。

  • XML reports in new Open Test Reporting format

テストレポートのフォーマットとしてはJUnitデファクトスタンダードのようになっていて、様々なプログラミング言語やテストフレームワーク、またCIサービスがテスト結果のサマリー表示のためにサポートしています。しかしJUnitは元々Java世界のものであり、他言語のテスト結果をJUnitフォーマットに出力しようとすると構造を無理矢理合わせることになったり、拡張性もなかったり(もしくは独自拡張になったり)します。言語・テストフレームワーク非依存な新しい仕様を定義することでこうした状況を解消しようという取り組みで面白いですね。

Design goalsとして

  • human-readable
  • streamable
  • machine-readable
  • schema-aware
  • extensible

の5つが挙げられています。ファイル形式としてはJSONとかではなく、JUnitと同じくXMLではあるんですね 👀

JSON is less verbose than XML, but the latter provides more expressive ways to define schemas. Moreover, XML has typed extensions built-in via the use of multiple schemas. Thus, the new formats use XML with accompanying XML schemas.

世の中のエコシステムがどれだけここに乗っかっていくか気になるところですが、まずは本家JUnit自体がサポートしたということで、着目していきたいですね。

SwiftUI.ViewとUIHostingControllerの初期化順序の調整

UIHostingControllerを使ってSwiftUI.Viewを表示する時に、Viewの引数に渡すクロージャーの中でViewControllerを使いたいことが稀によくある(本当に?)。

素直に考えるとこうなる。

// 順序的にvcを使えない
let view = FooView(onTap: {
    // vcで何かしたい
})
let vc = UIHostingController(rootView: view)

少し頭を捻るとこうなる。

let view = FooView()
let vc = UIHostingController(rootView: view)
vc.rootView.onTap = { [weak vc] in ... }

UIHostingController.rootViewpublic var rootView: Content なので、rootViewのプロパティを変更することはできる。だけどViewのプロパティを自分で書き換えるのはなんだかしっくりこない。

rootViewがvarということは別のことが考えられる。

let vc = UIHostingController<FooView?>(rootView: nil)
let view = FooView(onTap: { [weak vc] in ... })
vc.rootView = view

rootViewをnilで初期化しておき、後で差し替える作戦。 open class UIHostingController<Content> : UIViewController where Content : View という定義に対してnilを渡せるのか?という気持ちに一瞬なるが、これはOptional(とNever)がconditonal conformanceでViewに準拠しているのでvalidである。

extension Optional : View where Wrapped : View {
    public typealias Body = Never
}

という感じで、rootViewにnilを渡すことによる遅延初期化を上手く使っていきましょうというお話でした。

RenovateのHandlebarsテンプレート用の`lowercase`ヘルパーを追加した

RenovateとHandlebarsのテンプレート

Renovateでは様々な設定オプション正規表現を用いるregex managerHandlebarsのテンプレートを使用できる箇所があります。

例えばPRのdescriptionの内容を追加できるprBodyNotesなどがありますが、RenovateがPRを作る際のブランチ名を決めるbranchNameもデフォルト値は次のようなテンプレートになっています。

"{{{branchPrefix}}}{{{additionalBranchPrefix}}}{{{branchTopic}}}"

次のリンクはテンプレート中で使えるフィールド(変数)のドキュメントです。

repology datasourceと大文字小文字

さて、最近regex managerと組み合わせてrepology datasourceを使いたいことがありました。repology datasourceの中で使われているRepologyのSingle project API*1 https://repology.org/api/v1/project/$project の問い合わせでは、プロジェクト名が全て小文字である必要があります。

https://repology.org/api/v1/project/firefox なら結果が得られますが、https://repology.org/api/v1/project/Firefox だと結果は空になってしまいます。

正規表現で何かしらのファイルからキャプチャできるライブラリーの名称に大文字が含まれていると、そのままではrepology datasourceで問い合わせることができません。またRenovateが組み込みで提供するHandlebarsのヘルパー(テンプレート内で使える関数のようなもの)は当時、次の6つでした。

  • stringToPrettyJSON
  • encodeURIComponent
  • replace
  • containsString
  • and
  • or

replaceを複数回使って個々のライブラリーを列挙するわけにもいかないので、lowercaseヘルパーを追加しようと思い立ちました。まずIssueを立てて方針に問題がないかを確認してから、自分で実装まで行いました。

実装自体はJavaScripttoLowerCase()を呼んでヘルパーとして登録するだけで至極簡単ですね。

まとめ

これで packageNameTemplate: "{{{lowercase depName}}}" のようにして、キャプチャしたライブラリー名を小文字に変換してRepologyへの問い合わせに使えるようになりました。めでたしめでたし。

regex managerとrepology datasourceの組み合わせについては、後日また実用例の紹介をする予定です。乞うご期待(自分へのプレッシャー)。

Renovateの「About us」というドキュメントにcontributorとしてノミネートされた

はい。

こちらのページからご確認いただけます。

これまでのPR一覧を見ると、社内でRenovateを導入していくにあたってGradleとかsbt周りをいじったり、ちょっとしたリファクタリングなどもやっていた感じですね。今後も何かしら少しずつ貢献していきたい所存です。

あわせて読みたい

Accompanistの開発版のバージョニングが独特で、Renovateと相性が悪い

Accompanistとは

Google公式の、Jetpack Composeを補完する拡張ライブラリーです。

Accompanistの開発版のバージョニング

直近の0.24系の開発版が次のようになっている。

  1. v0.24.0-alpha
  2. v0.24.1-alpha
  3. v0.24.2-alpha
  4. v0.24.3-alpha
  5. v0.24.4-alpha
  6. v0.24.5-alpha
  7. v0.24.6-alpha
  8. v0.24.7-alpha
  9. v0.24.8-beta(ここからbetaに変わった)
  10. v0.24.9-beta
  11. v0.24.10-beta

SemVer的に見ると、v0.24.z のz部分はmajor・minor・patchのpatch部分に見えるが、Accompanistは現状SemVer(のpatch)を使ったバージョニングではなさそうで、z部分は開発版として何番目のリリースか、という数字でしかないようである。alphaの中で何番目、betaの中で何番目、という情報はなく、開発版全体として何番目で、その時の状態がalphaかbetaなのかが最後に付く、となっている。

SemVerなら

  1. v0.24-alpha.1
  2. v0.24-alpha.2
  3. v0.24-alpha.3
  4. v0.24-alpha.4
  5. v0.24-alpha.5
  6. v0.24-alpha.6
  7. v0.24-alpha.7
  8. v0.24-alpha.8
  9. v0.24-beta.1
  10. v0.24-beta.2
  11. v0.24-beta.3

という付け方になるであろう。

Renovateでの開発版のバージョンへの更新

RenovateはデフォルトでignoreUnstableという設定がオンになっていて、安定版から開発版(非安定版)への更新がされないようになっている。ただし例外があって、現在使用中のバージョンが安定版ではなく開発版であり、更新対象となる最新版とmajor・minor・patchが一致する場合は(SemVerでいうところのpre-release version以降だけが更新されたら)更新してくれる。

By default, Renovate won't update any package versions to unstable versions (e.g. 4.0.0-rc3) unless the current version has the same major.minor.patch and was already unstable (e.g. it was already on 4.0.0-rc2).

ここでAccompanistの開発版のバージョニングに戻ってみると、SemVerの観点では毎回patch部分が更新されていることになる。従って先述の条件を満たさず、例えば v0.24.8-beta から v0.24.10-beta への更新のPRは用意されないことになっている。この、ある開発版から、major・minor・patchが一致しない別の開発版への更新が起きないことはドキュメントにも明記されている。

Renovate will also not "jump" unstable versions automatically, e.g. if you are on 4.0.0-rc2 and newer versions 4.0.0 and 4.1.0-alpha.1 exist then Renovate will update you to 4.0.0 only.

ということで、このようなバージョニングでも開発版に追従していきたい時*1ignoreUnstable を明示的にオフ(false)にしましょう。

  "packageRules": [
    {
      "matchPackagePatterns": ["^com\\.google\\.accompanist:"],
      "ignoreUnstable": false
    }
  ]

*1:Jetpack Composeの開発版を使う時は、Accompanistも対応する開発版を使うことになる