jq
jqはJSONをいい感じにクエリできるやつで、広く使われている。
$ echo '{"foo": "bar"}' | jq '.foo' "bar"
例えばGitHub Actionsのランナーにもデフォルトで入っている。
あるいはGitHubのCLIツール gh
にも --jq
オプションがあって、統合されている。
つまりソフトウェアエンジニアにとっては、jqはJSONを触るときのデファクトスタンダードツールと言える。
ショートカット.app
iOS/iPadOS/macOSには、ショートカット.appがある。特に説明は不要と思う。
ショートカットでなんらかのJSON APIを利用したい場合、「URLの内容を取得」アクションでデータを取得して、「辞書の値を取得」(あるいは「リストから項目を取得」)アクションで情報を取り出す。
これでいいといえばいい。が、やはりjqを使いたい。
Swiftからgojqを使う
id:itchyny さんが作っている、Goによるjqの実装、gojqというのがある。
これを使う。
Goにはgomobileというのがあって、Goでモバイルアプリを作るやつという印象だが、実際にはGoでライブラリを作ることもできる。
ということで、gojqを呼び出すためのグルーコードを書いていく。
package binding import ( "github.com/itchyny/gojq" _ "golang.org/x/mobile/bind" ) type Query struct { query *gojq.Query } func NewQuery(src string) (*Query, error) { query, err := gojq.Parse(src) if err != nil { return nil, err } return &Query{query: query}, nil } func (q *Query) Run(input []byte) (*Iterator, error) { ... }
そしてうまいことオプションをつけて gomobile
コマンドを実行する。
$ gomobile bind \ -target=ios,iossimulator,macos,maccatalyst \ -iosversion 14 \ -prefix GOJQ \ -o Frameworks/GOJQBinding.xcframework \ github.com/cockscomb/swift-gojq/binding
-target
にiOSやmacOSを指定する。-prefix
も設定できる。これで、XCFramework形式で出力できる。
これは次のようなインターフェースを持っていて、Objective-Cのヘッダが作られる(ここではSwiftから見た様子の一部を抜粋している)。
import Foundation open class GOJQBindingIterator : NSObject, goSeqRefInterface { public init() open func next() throws -> Data } open class GOJQBindingQuery : NSObject, goSeqRefInterface { public init?(_ src: String?) open func run(_ input: Data?) throws -> GOJQBindingIterator } public func GOJQBindingNewQuery(_ src: String?, _ error: NSErrorPointer) -> GOJQBindingQuery?
Goの string
がSwiftの String
になったり、同じように []byte
が Data
になったり、基本的な変換はもちろん行われる。さらに error
は NSError
になるし、Goの構造体が NSObject
を継承したクラスに変換されたりしている。よくできている。(Swiftでところどころ Optional
になるのは仕方ない。)
ここまでやってくれると、もうちょっとSwiftから使いやすいようにラッパを書くのも簡単だ。
import GOJQBinding enum QueryError: Error { case unknown } public struct Query { private let binding: GOJQBindingQuery public init(_ query: String) throws { var error: NSError? guard let binding = GOJQBindingNewQuery(query, &error) else { throw error ?? QueryError.unknown } self.binding = binding } public func run(_ input: Data) throws -> AsyncThrowingStream<Data, any Error> { ... } }
あとはXCFrameworkを binaryTarget
に加えたSwift Packageを作る。
// swift-tools-version: 5.7 import PackageDescription let package = Package( name: "SwiftGoJq", platforms: [ .macOS(.v13), .macCatalyst(.v14), .iOS(.v14) ], products: [ .library(name: “SwiftGoJq”, targets: ["SwiftGoJq"]), ], dependencies: [], targets: [ .binaryTarget( name: "GOJQBinding", url: "https://github.com/cockscomb/swift-gojq/releases/download/0.1.0/GOJQBinding.xcframework.zip", checksum: "1c45710de17fb7020dcfc75105344729725c5e3875e7058e98790e5f4e178162"), .target( name: "SwiftGoJq", dependencies: [ "GOJQBinding", ]), ] )
GitHubに置いておいたのでどうぞご利用ください。
ショートカット
これでようやく本題の、ショートカットのアクションにしていく。
iOS 16/macOS VenturaからApp Intentsフレームワークというのが追加されている。これはアプリの機能をシステムに公開するための新しいしくみだ。これを使う。
次のように AppIntent
プロトコルを実装したコードをアプリに含めておくと、システムが自動的に認識して、ショートカットから呼び出せるアクションにしてくれる。XxxManager
に登録、みたいなことはいっさい不要だ。
import AppIntents import AsyncAlgorithms import SwiftGoJq struct JQIntent: AppIntent { static var title: LocalizedStringResource = "jq" @Parameter(title: "JSON") var input: String @Parameter(title: "Query") var query: String static var parameterSummary: some ParameterSummary { Summary("\(\.$input) | jq '\(\.$query)'") } func perform() async throws -> some IntentResult { let jq = try Query(query) let results = try jq.run(input) let array = try await Array(results) return .result(value: array) } }
@Parameter
のついたプロパティが入力で、perform
メソッドが実行され、結果を出力できる。async throws
なので非同期的な処理も容易だ。
また parameterSummary
によってショートカット.app中での表示をコントロールできる。ここでは、入力 | jq 'クエリ'
という感じで、シェルっぽくしてみた。かわいいでしょう。
実際の表示は次のとおりで、入力がうまく表示されている。
いかがでしたか
iOS 16で追加されたApp Intentsフレームワークのおかげで、とても簡単にショートカット用のアクションを提供できた。App IntentsやiOS 16のことがもっと知りたくなってきたと思う。
iOS 16について学ぶのに何かいいリソースはないかな〜。
お〜っと、2022年12月24日発売の「WEB+DB PRESS Vol.132」に、ちょうどiOS 16の特集「iOS 16最前線」が載っているぞ!!
ということで、こちらの特集をはてなの同僚 id:yutailang0119 / id:kouki_dan と一緒に書きました。App Intentsについても紹介していますので、どうぞお買い求めください。もちろん他の記事もどれもおもしろいので、年末年始のお供にぴったりです。
こちらからは以上です。ハッピーホリデー!
以上、「potatotips #80 iOS/Android開発Tips共有会」での発表を再録しました。当日は主催者および参加者のみなさんのおかげで、とても楽しく過ごせました。またどのLTもたいへんおもしろく拝見しました。ありがとうございました。またよろしくお願いします。