QueryStream

YAML/JSON データファイルとテンプレートファイルを組み合わせて、テキストコンテンツ内の QueryStream 記法を展開する汎用 Ruby ライブラリ。

インストール

# Gemfile
gem 'query-stream'
bundle install

Ruby 3.4 以上が必要です。

基本的な使い方

require 'query_stream'

# テキスト内の QueryStream 記法を展開
source = File.read('contents/05-references.md')
result = QueryStream.render(source, data_dir: 'data', templates_dir: 'templates')

data_dir / templates_dir は後述の configure で既定値を決めておけば省略できます。

QueryStream 記法

= [源泉] | [抽出条件] | [ソート] | [件数] | [スタイル]

= books                           # 全件展開
= books | tags=ruby               # タグで絞り込み
= books | tags=ruby | -title | 5  # 絞り込み+降順ソート+5件
= book | 楽しいRuby               # 主キーで一件検索
= books | :full                   # fullスタイルで展開

具体例:vivlio-style での書籍データ展開

データファイル例 (data/books.yml)

- title: 楽しいRuby
  author: 
    name: 高橋征義
  desc: Rubyを楽しく学べる入門書。
  cover: ruby.webp

- title: はじめてのC
  author: 
    name: 柴田望洋
  desc: C言語の定番入門書。
  cover: c.webp

テンプレートファイル例 (templates/_book.md)

:::{.book-card}
![](cover)
**=title**
=desc
:::

Markdown記述

## 参考書籍

= books

---

展開結果

## 参考書籍

:::{.book-card}
![](ruby.webp)
**楽しいRuby**
Rubyを楽しく学べる入門書。
:::

:::{.book-card}
![](c.webp)
**はじめてのC**
C言語の定番入門書。
:::

---

VFMフェンス記法(:::{.book-card})にも対応しており、各レコードが個別のフェンスで囲まれて展開されます。

設定

QueryStream.configure do |config|
  config.data_dir       = 'data'
  config.templates_dir  = 'templates'
  config.default_format = :md
  config.post_render    = ->(text, context) { text }
end

エラー・警告の扱い

この gem はメッセージを組み立てません。 どう表示するか、どの言語で出すか、そもそもログに残すかは、すべて呼び出し元が決めます。そのため展開時の出来事は、構造化された例外としてコールバックへ渡されます。

QueryStream.render(
  content,
  data_dir:        'data',
  templates_dir:   'templates',
  source_filename: 'chapter.md',      # 位置情報(location)の表示に使います
  on_error:        ->(e) { },         # 展開失敗。その行は元の記法のまま残ります
  on_warning:      ->(w) { },         # 一件検索の該当なし/複数ヒット
  post_render:     ->(text, ctx) { }  # 展開結果の後処理
)

1 行の失敗で全体は止まりません。 失敗した行は元の記法のまま残り、後続の記法は展開され続けます。

例外クラス 属性
TemplateNotFoundError template_path, query, location, hint
DataNotFoundError expected_path, query, location
DataLoadError file_path, cause_error
UnknownKeyError key_path, available_keys, template_path, location
NoResultWarning query, location
AmbiguousQueryWarning query, location, count

前 4 つは QueryStream::Error、後 2 つは QueryStream::Warning を継承します。gem の外へ出る例外はこの 2 系統だけです。壊れた YAML も、不正な UTF-8 を含む原稿も、生の ArgumentErrorNoMethodError にはなりません。

たとえば UnknownKeyError は「使えるキーの一覧」と「直すべき雛形のパス」まで持って届くので、呼び出し元で打ち間違いの案内を組み立てられます。location は記法が書かれた原稿の位置、template_path は直す雛形の位置で、直すのは雛形のほうです。

on_error: lambda do |e|
  next unless e.is_a?(QueryStream::UnknownKeyError)

  keys = e.available_keys.map(&:to_s)
  near = DidYouMean::SpellChecker.new(dictionary: keys).correct(e.key_path.split('.').first).first
  puts "#{e.location} もしかして =#{near}?(#{e.template_path} / 使えるキー: #{keys.join(', ')})"
end

例外はファイル権限のみ扱いが異なり、Errno::EACCES のまま伝わります。環境の問題(chmod で直る)とデータの問題(中身を直す)は、対処が違うためです。

post_render

展開結果を、その出どころの情報つきで受け取れます。gem 側は用途を規定しないので、画像パスの解決など固有の後処理を差し込めます。

post_render: lambda do |text, context|
  # context: source, data_file, data_dir, template_path, query, location
  MyImageResolver.rewrite(text, context)
end

戻り値が String ならそれを、そうでなければ元の展開結果を採用します。

ライセンス

MIT License