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 | =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, DataLoadError, DataNotFoundError, Error, InvalidDateError, NoResultWarning, TemplateNotFoundError, UnknownKeyError, Warning
Constant Summary collapse
- QUERY_STREAM_PATTERN =
QueryStream 記法を検出する正規表現 行頭 = の直後に英数字/ハイフン/アンダースコアのデータ名(スペースは任意)
/^=\s*([a-zA-Z][a-zA-Z0-9_-]*)(?:\s*\|.*)?$/- MAX_PATH_BYTES =
これを超える長さの引数はパスとみなさず本文として扱う(scan の path/content 判定)。 主要な OS の PATH_MAX は 4096 前後で、それより長い「パス」は実在しない。
4096- VERSION =
'1.4.0'
Class Method Summary collapse
-
.configuration ⇒ Configuration
グローバル設定を返す.
-
.configure {|Configuration| ... } ⇒ Object
設定をブロックで変更する.
-
.render(content, source_filename: nil, data_dir: nil, templates_dir: nil, on_error: nil, on_warning: nil, post_render: nil) ⇒ String
テキストコンテンツ内の QueryStream 記法をすべて展開する 1行の展開に失敗しても残りの行の処理を継続する。 エラー情報は例外の属性として呼び出し元に委ねる(gem 内ではログ出力しない)。.
-
.render_query(query, line_number: nil, source_filename: nil, data_dir: nil, templates_dir: nil, on_warning: nil, post_render: nil) ⇒ String
単一の QueryStream 記法を展開する.
-
.scan(path_or_content) ⇒ Array<String>
テキスト内の QueryStream 記法を検出してリストを返す.
Class Method Details
.configuration ⇒ Configuration
グローバル設定を返す
39 40 41 |
# File 'lib/query_stream.rb', line 39 def configuration @configuration ||= Configuration.new end |
.configure {|Configuration| ... } ⇒ Object
設定をブロックで変更する
45 46 47 |
# File 'lib/query_stream.rb', line 45 def configure yield(configuration) end |
.render(content, source_filename: nil, data_dir: nil, templates_dir: nil, on_error: nil, on_warning: nil, post_render: nil) ⇒ String
テキストコンテンツ内の QueryStream 記法をすべて展開する 1行の展開に失敗しても残りの行の処理を継続する。 エラー情報は例外の属性として呼び出し元に委ねる(gem 内ではログ出力しない)。
60 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 103 |
# File 'lib/query_stream.rb', line 60 def render(content, source_filename: nil, data_dir: nil, templates_dir: nil, on_error: nil, on_warning: nil, post_render: nil) data_dir ||= configuration.data_dir templates_dir ||= configuration.templates_dir post_render ||= configuration.post_render lines = content.lines result = [] in_code_block = false lines.each_with_index do |line, idx| line_number = idx + 1 # コードブロック内はスキップ if code_fence?(line) in_code_block = !in_code_block result << line next end if in_code_block result << line next end # QueryStream 記法の検出 if query_line?(line) begin = render_query( line.chomp, line_number:, source_filename:, data_dir:, templates_dir:, on_warning:, post_render: ) result << << "\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, post_render: nil) ⇒ String
単一の QueryStream 記法を展開する
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 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 |
# File 'lib/query_stream.rb', line 113 def render_query(query, line_number: nil, source_filename: nil, data_dir: nil, templates_dir: nil, on_warning: nil, post_render: 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 --- rendered = TemplateCompiler.render(template_content, records, source_filename:, line_number:, template_path:) # --- Phase: Post-render filter --- # 展開結果を呼び出し元の後段フィルタへ通す(画像パス解決など gem 外の後処理)。 # コールバックが String 以外を返した場合は安全側に倒して元の展開結果を採用する。 return rendered unless post_render context = { source: parsed[:source], # 記法に書かれた論理名(例: "physics_book") data_file: data_file, # 単複解決後の実パス(例: "data/physics_books.yml") data_dir: data_dir, template_path: template_path, query: query, location: location # "filename:line" 形式 } result = post_render.call(rendered, context) result.is_a?(String) ? result : rendered end |
.scan(path_or_content) ⇒ Array<String>
テキスト内の QueryStream 記法を検出してリストを返す
206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 |
# File 'lib/query_stream.rb', line 206 def scan(path_or_content) content = readable_file?(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 code_fence?(line) in_code_block = !in_code_block next end next if in_code_block queries << line.chomp if query_line?(line) end queries end |