Module: QueryStream

Defined in:
lib/query_stream/errors.rb,
lib/query_stream.rb,
lib/query_stream/command.rb,
lib/query_stream/version.rb,
lib/query_stream/singularize.rb,
lib/query_stream/configuration.rb,
lib/query_stream/data_resolver.rb,
lib/query_stream/filter_engine.rb,
lib/query_stream/template_compiler.rb,
lib/query_stream/query_stream_parser.rb

Overview

File: lib/query_stream/query_stream_parser.rb

責務:

QueryStream 記法(= books | tags=ruby | -title | 5 | :full)を
パースし、構造化されたクエリハッシュを返す。

パイプライン:

1. Source  - データ名(必須)
2. Filter  - 抽出条件(field=value, 比較演算子, 範囲指定)
3. Sort    - ソート条件(-field / +field)
4. Limit   - 件数制限(正の整数)
5. Style   - スタイル名(:stylename または :stylename.ext)

トークンの自動判別:

各トークンは形式で一意に判別される(省略・順序入れ替えに対応)

Defined Under Namespace

Modules: Command, DataResolver, FilterEngine, QueryStreamParser, Singularize, TemplateCompiler Classes: AmbiguousQueryWarning, Configuration, DataNotFoundError, Error, InvalidDateError, NoResultWarning, TemplateNotFoundError, UnknownKeyError, Warning

Constant Summary collapse

QUERY_STREAM_PATTERN =

QueryStream 記法を検出する正規表現行頭 = の直後に英数字/ハイフン/アンダースコアのデータ名(スペースは任意)

/^=\s*([a-zA-Z][a-zA-Z0-9_-]*)(?:\s*\|.*)?$/
VERSION =
'1.2.0'

Class Method Summary collapse

Class Method Details

.configurationConfiguration

グローバル設定を返す

Returns:



35
36
37
# File 'lib/query_stream.rb', line 35

def configuration
  @configuration ||= Configuration.new
end

.configure {|Configuration| ... } ⇒ Object

設定をブロックで変更する

Yields:



41
42
43
# File 'lib/query_stream.rb', line 41

def configure
  yield(configuration)
end

.loggerLogger

ロガーへのショートカット

Returns:

  • (Logger)


47
48
49
# File 'lib/query_stream.rb', line 47

def logger
  configuration.logger
end

.render(content, source_filename: nil, data_dir: nil, templates_dir: nil, on_error: nil, on_warning: nil) ⇒ String

テキストコンテンツ内の QueryStream 記法をすべて展開する1行の展開に失敗しても残りの行の処理を継続する。エラー情報は例外の属性として呼び出し元に委ねる(gem 内ではログ出力しない)。

Parameters:

  • content (String)

    テキストコンテンツ

  • source_filename (String, nil) (defaults to: nil)

    エラー報告用のソースファイル名

  • data_dir (String, nil) (defaults to: nil)

    データディレクトリ(nil時はconfigを使用)

  • templates_dir (String, nil) (defaults to: nil)

    テンプレートディレクトリ(nil時はconfigを使用)

  • on_error (Proc, nil) (defaults to: nil)

    エラー時コールバック。|exception| を受け取る。省略時は何もしない。

  • on_warning (Proc, nil) (defaults to: nil)

    警告時コールバック。|warning| を受け取る。省略時は何もしない。

Returns:

  • (String)

    展開後のテキストコンテンツ



61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/query_stream.rb', line 61

def render(content, source_filename: nil, data_dir: nil, templates_dir: nil, on_error: nil, on_warning: nil)
  data_dir      ||= configuration.data_dir
  templates_dir ||= configuration.templates_dir

  lines = content.lines
  result = []
  in_code_block = false

  lines.each_with_index do |line, idx|
    line_number = idx + 1

    # コードブロック内はスキップ
    if line.lstrip.start_with?('```')
      in_code_block = !in_code_block
      result << line
      next
    end

    if in_code_block
      result << line
      next
    end

    # QueryStream 記法の検出
    if line.match?(QUERY_STREAM_PATTERN)
      begin
        expanded = render_query(
          line.chomp, line_number:, source_filename:, data_dir:, templates_dir:, on_warning:
        )
        result << expanded << "\n"
      rescue Error => e
        # 1行の失敗で残りの展開を止めない。エラー処理は呼び出し元に委ねる。
        on_error&.call(e)
        result << line
      end
    else
      result << line
    end
  end

  result.join
end

.render_query(query, line_number: nil, source_filename: nil, data_dir: nil, templates_dir: nil, on_warning: nil) ⇒ String

単一の QueryStream 記法を展開する

Parameters:

  • query (String)

    QueryStream 記法の行(例: “= books | tags=ruby | :full”)

  • line_number (Integer, nil) (defaults to: nil)

    行番号(エラー報告用)

  • source_filename (String, nil) (defaults to: nil)

    ソースファイル名

  • data_dir (String, nil) (defaults to: nil)

    データディレクトリ

  • templates_dir (String, nil) (defaults to: nil)

    テンプレートディレクトリ

Returns:

  • (String)

    展開後のテキスト



111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
# File 'lib/query_stream.rb', line 111

def render_query(query, line_number: nil, source_filename: nil, data_dir: nil, templates_dir: nil, on_warning: nil)
  data_dir      ||= configuration.data_dir
  templates_dir ||= configuration.templates_dir
  location = source_filename ? "#{source_filename}:#{line_number}" : "#{line_number}"

  # --- Phase: Parse ---
  parsed = QueryStreamParser.parse(query)

  # --- Phase: Load Data ---
  # gem 内でログ出力せず、構造化された属性を持つ例外を raise する。
  # メッセージの構成・ログ出力・i18n は呼び出し元の責務とする。
  data_file = DataResolver.resolve(parsed[:source], data_dir)
  unless data_file
    expected = File.join(data_dir, "#{parsed[:source]}.yml")
    raise DataNotFoundError.new(
      expected_path: expected,
      query:         query,
      location:      location
    )
  end

  records = DataResolver.load_records(data_file)

  # --- Phase: Filter ---
  records = FilterEngine.apply_filters(records, parsed[:filters])

  # --- Phase: Sort ---
  records = FilterEngine.apply_sort(records, parsed[:sort]) if parsed[:sort]

  # --- Phase: Limit ---
  records = records.first(parsed[:limit]) if parsed[:limit]

  # --- Phase: Single record warning ---
  if parsed[:single_lookup]
    case records.size
    when 0
      # gem 内でログ出力せず、構造化された属性を持つ警告を呼び出し元に委ねる。
      on_warning&.call(NoResultWarning.new(query: query, location: location))
      return ''
    when 1
      # 正常
    else
      # gem 内でログ出力せず、構造化された属性を持つ警告を呼び出し元に委ねる。
      on_warning&.call(
        AmbiguousQueryWarning.new(query: query, location: location, count: records.size)
      )
    end
  end

  # --- Phase: Resolve Template ---
  singular = Singularize.call(parsed[:source])
  style = parsed[:style]
  format = parsed[:format]
  template_path = resolve_template_path(singular, style, format, templates_dir)

  unless File.exist?(template_path)
    hint = build_template_hint(singular, style, format, templates_dir)
    # gem 内でログ出力せず、構造化された属性を持つ例外を raise する。
    # メッセージの構成・ログ出力・i18n は呼び出し元の責務とする。
    raise TemplateNotFoundError.new(
      template_path: template_path,
      query:         query,
      location:      location,
      hint:          hint
    )
  end

  template_content = File.read(template_path, encoding: 'utf-8')

  # --- Phase: Render ---
  TemplateCompiler.render(template_content, records, source_filename:, line_number:)
end

.scan(path_or_content) ⇒ Array<String>

テキスト内の QueryStream 記法を検出してリストを返す

Parameters:

  • path_or_content (String)

    ファイルパスまたはテキストコンテンツ

Returns:

  • (Array<String>)

    検出された QueryStream 記法のリスト



187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
# File 'lib/query_stream.rb', line 187

def scan(path_or_content)
  content = File.exist?(path_or_content) ? File.read(path_or_content, encoding: 'utf-8') : path_or_content
  lines = content.lines
  queries = []
  in_code_block = false

  lines.each do |line|
    if line.lstrip.start_with?('```')
      in_code_block = !in_code_block
      next
    end
    next if in_code_block

    queries << line.chomp if line.match?(QUERY_STREAM_PATTERN)
  end

  queries
end