query-stream 1.2.2 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +215 -0
- data/README.md +214 -3
- data/RELEASE_NOTE.md +100 -0
- data/lib/query_stream/command.rb +11 -8
- data/lib/query_stream/configuration.rb +9 -8
- data/lib/query_stream/data_resolver.rb +23 -4
- data/lib/query_stream/errors.rb +24 -3
- data/lib/query_stream/filter_engine.rb +102 -25
- data/lib/query_stream/query_stream_parser.rb +48 -35
- data/lib/query_stream/singularize.rb +0 -2
- data/lib/query_stream/template_compiler.rb +93 -17
- data/lib/query_stream/version.rb +1 -1
- data/lib/query_stream.rb +71 -23
- data/query-stream.gemspec +9 -9
- metadata +24 -23
- data/Gemfile +0 -5
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
# ================================================================
|
|
4
|
-
# File: lib/query_stream/template_compiler.rb
|
|
5
3
|
# ================================================================
|
|
6
4
|
# 責務:
|
|
7
5
|
# テンプレートファイル(_book.md 等)にデータを流し込み、
|
|
@@ -9,6 +7,8 @@
|
|
|
9
7
|
#
|
|
10
8
|
# 変換ルール:
|
|
11
9
|
# - `= key` のみの行 → key の値を展開(nil/空文字なら行スキップ)
|
|
10
|
+
# - `= key` が 2 つ以上ある行 → 区切り(/・、,,)と括弧で部品に分け、
|
|
11
|
+
# 空のキーを含む部品だけを消す(部品が全部消えたら行スキップ。表の行は対象外)
|
|
12
12
|
# - `prefix = key` → prefix を残してkey の値を展開
|
|
13
13
|
# - `` → 変数展開(拡張子なし → 変数、拡張子あり → リテラル)
|
|
14
14
|
# - `= key` を含まない行 → リテラル出力(ヘッダー等は一度だけ出力)
|
|
@@ -44,6 +44,21 @@ module QueryStream
|
|
|
44
44
|
# VFM フェンス終了行: 単独の :::
|
|
45
45
|
FENCE_CLOSE_PATTERN = /\A:::\s*\z/
|
|
46
46
|
|
|
47
|
+
# 部品の区切り: 全角の斜線・中黒・読点・コンマと半角のコンマ(前後の空白ごと)、
|
|
48
|
+
# 前後に空白のある半角の斜線。空白なしの半角の斜線を区切りにしないのは、
|
|
49
|
+
# URL(https://example.com/books/= id)を割らないため。
|
|
50
|
+
PART_SEPARATOR = %r{\s*[/・、,,]\s*|\s+/\s+}
|
|
51
|
+
|
|
52
|
+
# 変数を含みうる括弧(全角・半角)。中身ごと 1 つの部品として扱う(直前の空白も含める)。
|
|
53
|
+
# `]` の直後の括弧は Markdown のリンク先(`[= title](= url)`)なので含めない。
|
|
54
|
+
OPTIONAL_GROUP = /\s*(?<!\])[((][^()()]*[))]/
|
|
55
|
+
|
|
56
|
+
# 行頭の Markdown の目印(箇条書き・番号・引用・見出し)。部品が消えても残す。
|
|
57
|
+
LINE_MARKER = /\A\s*(?:(?:[-*+]|\d+\.)\s+|>\s?|\#{1,6}\s+)*/
|
|
58
|
+
|
|
59
|
+
# 括弧を部品として退避するときの目印(テンプレートに現れない制御文字で挟む)
|
|
60
|
+
GROUP_PLACEHOLDER = /\u0000(\d+)\u0000/
|
|
61
|
+
|
|
47
62
|
module_function
|
|
48
63
|
|
|
49
64
|
# テンプレートにレコード群を流し込んでテキストを生成する
|
|
@@ -51,10 +66,11 @@ module QueryStream
|
|
|
51
66
|
# @param records [Array<Hash>] データレコード群
|
|
52
67
|
# @param source_filename [String, nil] エラー報告用ファイル名
|
|
53
68
|
# @param line_number [Integer, nil] エラー報告用行番号
|
|
69
|
+
# @param template_path [String, nil] エラー報告用テンプレートパス(キーの打ち間違いはここを直す)
|
|
54
70
|
# @return [String] 展開後のテキスト
|
|
55
|
-
def render(template, records, source_filename: nil, line_number: nil)
|
|
71
|
+
def render(template, records, source_filename: nil, line_number: nil, template_path: nil)
|
|
56
72
|
lines = template.lines
|
|
57
|
-
validate_template_keys!(lines, records
|
|
73
|
+
validate_template_keys!(lines, records, source_filename:, line_number:, template_path:) if records.any?
|
|
58
74
|
|
|
59
75
|
parts = classify_lines(lines)
|
|
60
76
|
return '' if records.empty?
|
|
@@ -218,7 +234,7 @@ module QueryStream
|
|
|
218
234
|
end
|
|
219
235
|
|
|
220
236
|
# テンプレート行をレコードデータで展開する
|
|
221
|
-
# nil
|
|
237
|
+
# 行ごと消すときは nil を返す
|
|
222
238
|
# @param line [String] テンプレート行
|
|
223
239
|
# @param record [Hash] データレコード
|
|
224
240
|
# @return [String, nil] 展開後の行、またはスキップ時 nil
|
|
@@ -269,10 +285,72 @@ module QueryStream
|
|
|
269
285
|
end
|
|
270
286
|
|
|
271
287
|
# = key パターンの変数を展開する(ドット記法対応)
|
|
288
|
+
#
|
|
289
|
+
# 変数が 2 つ以上ある行は、空のキーを含む部品だけを消す(expand_parts)。
|
|
290
|
+
# `= author.name 著/= publisher/= year 年` で発行年だけが無い本でも、著者と出版社は出る。
|
|
291
|
+
# 変数が 1 つの行と表の行(セルを消すと列がずれる)は、空なら行ごと消す。
|
|
272
292
|
# @param line [String] テンプレート行
|
|
273
293
|
# @param record [Hash] データレコード
|
|
274
294
|
# @return [String, nil] 展開後の行、またはスキップ時 nil
|
|
275
295
|
def expand_variables(line, record)
|
|
296
|
+
return expand_parts(line, record) if partial_line?(line)
|
|
297
|
+
|
|
298
|
+
expand_whole(line, record)
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
# 部品ごとに消せる行か。変数が 2 つ以上あり、表の行でないもの。
|
|
302
|
+
def partial_line?(line)
|
|
303
|
+
!line.lstrip.start_with?('|') && line.scan(VARIABLE_PATTERN).size >= 2
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
# 区切りと括弧で行を部品に分け、空のキーを含む部品だけを消す。
|
|
307
|
+
#
|
|
308
|
+
# - 変数を含む括弧(`(= year 年)`)は 1 つの部品。空なら直前の空白ごと消す。
|
|
309
|
+
# - 括弧の外は PART_SEPARATOR で分ける。残った部品は、前の部品の後ろにあった区切りでつなぐ。
|
|
310
|
+
# - 行頭の Markdown の目印(`- ` など)と行末の改行は、部品が消えても残す。
|
|
311
|
+
# - 変数を含む部品が 1 つも残らなければ nil(行ごと消す)。
|
|
312
|
+
# キーがすべて埋まっていれば、区切りも括弧も元のまま戻るので、出力は expand_whole と同じになる。
|
|
313
|
+
# @param line [String] テンプレート行
|
|
314
|
+
# @param record [Hash] データレコード
|
|
315
|
+
# @return [String, nil] 展開後の行、またはスキップ時 nil
|
|
316
|
+
def expand_parts(line, record)
|
|
317
|
+
newline = line[/\R\z/].to_s
|
|
318
|
+
body = line.chomp
|
|
319
|
+
marker = body[LINE_MARKER]
|
|
320
|
+
body = body.delete_prefix(marker)
|
|
321
|
+
|
|
322
|
+
groups = []
|
|
323
|
+
masked = body.gsub(OPTIONAL_GROUP) do |group|
|
|
324
|
+
next group unless group.match?(VARIABLE_PATTERN)
|
|
325
|
+
|
|
326
|
+
groups << group
|
|
327
|
+
"\u0000#{groups.size - 1}\u0000"
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
kept = masked.split(/(#{PART_SEPARATOR})/).each_slice(2).filter_map do |part, separator|
|
|
331
|
+
text = expand_part(part, groups, record)
|
|
332
|
+
text && [text, separator.to_s, variable_part?(part)]
|
|
333
|
+
end
|
|
334
|
+
return nil unless kept.any? { |_, _, variable| variable }
|
|
335
|
+
|
|
336
|
+
joined = kept.each_with_index.map { |(text, separator, _), i| i == kept.size - 1 ? text : text + separator }.join
|
|
337
|
+
"#{marker}#{joined}#{newline}"
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
# 部品 1 つを展開する。空のキーを含めば nil。括弧は中のキーが空ならそこだけ消す。
|
|
341
|
+
# 括弧の外を先に展開するのは、値の中の `= …` をもう一度展開しないため(括弧は退避中)。
|
|
342
|
+
def expand_part(part, groups, record)
|
|
343
|
+
outer = expand_whole(part, record)
|
|
344
|
+
return nil unless outer
|
|
345
|
+
|
|
346
|
+
text = outer.gsub(GROUP_PLACEHOLDER) { expand_whole(groups[::Regexp.last_match(1).to_i], record).to_s }
|
|
347
|
+
text.strip.empty? ? nil : text
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
def variable_part?(part) = part.match?(VARIABLE_PATTERN) || part.match?(GROUP_PLACEHOLDER)
|
|
351
|
+
|
|
352
|
+
# 行の変数をすべて展開する。空のキーが 1 つでもあれば nil(行ごと消す)。
|
|
353
|
+
def expand_whole(line, record)
|
|
276
354
|
result = line.dup
|
|
277
355
|
|
|
278
356
|
result.gsub!(VARIABLE_PATTERN) do |_match|
|
|
@@ -302,15 +380,19 @@ module QueryStream
|
|
|
302
380
|
end
|
|
303
381
|
|
|
304
382
|
# テンプレート内のキーがデータに存在するかを検証する
|
|
383
|
+
#
|
|
384
|
+
# どれか 1 件にでもあるキーは正しいキーとみなす。省ける項目(発行年など)が最初の 1 件に
|
|
385
|
+
# 無いだけで、打ち間違いとして止めないため。値の無い件では、その行・部品を消す(expand_variables)。
|
|
305
386
|
# @param lines [Array<String>] テンプレートの行リスト
|
|
306
|
-
# @param
|
|
387
|
+
# @param records [Array<Hash>] レコード群
|
|
307
388
|
# @param source_filename [String, nil] エラー報告用ファイル名
|
|
308
389
|
# @param line_number [Integer, nil] エラー報告用行番号
|
|
309
|
-
|
|
310
|
-
|
|
390
|
+
# @param template_path [String, nil] エラー報告用テンプレートパス
|
|
391
|
+
def validate_template_keys!(lines, records, source_filename: nil, line_number: nil, template_path: nil)
|
|
392
|
+
available_keys = records.flat_map(&:keys).uniq
|
|
393
|
+
return if available_keys.empty?
|
|
311
394
|
|
|
312
395
|
location = source_filename ? "#{source_filename}:#{line_number}" : ''
|
|
313
|
-
available_keys = sample_record.keys
|
|
314
396
|
in_code_block = false
|
|
315
397
|
|
|
316
398
|
lines.each do |line|
|
|
@@ -327,10 +409,7 @@ module QueryStream
|
|
|
327
409
|
line.scan(VARIABLE_PATTERN).each do |(key_path)|
|
|
328
410
|
root_key = key_path.split('.').first.to_sym
|
|
329
411
|
unless available_keys.include?(root_key)
|
|
330
|
-
|
|
331
|
-
QueryStream.logger.error("#{msg}(#{location})")
|
|
332
|
-
QueryStream.logger.error(" 利用可能なキー: #{available_keys.join(', ')}")
|
|
333
|
-
raise UnknownKeyError, msg
|
|
412
|
+
raise UnknownKeyError.new(key_path:, available_keys:, template_path:, location:)
|
|
334
413
|
end
|
|
335
414
|
end
|
|
336
415
|
|
|
@@ -341,10 +420,7 @@ module QueryStream
|
|
|
341
420
|
|
|
342
421
|
root_key = src.split('.').first.to_sym
|
|
343
422
|
unless available_keys.include?(root_key)
|
|
344
|
-
|
|
345
|
-
QueryStream.logger.error("#{msg}(#{location})")
|
|
346
|
-
QueryStream.logger.error(" 利用可能なキー: #{available_keys.join(', ')}")
|
|
347
|
-
raise UnknownKeyError, msg
|
|
423
|
+
raise UnknownKeyError.new(key_path: src, available_keys:, template_path:, location:)
|
|
348
424
|
end
|
|
349
425
|
end
|
|
350
426
|
end
|
data/lib/query_stream/version.rb
CHANGED
data/lib/query_stream.rb
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
# ================================================================
|
|
4
|
-
# File: lib/query_stream.rb
|
|
5
3
|
# ================================================================
|
|
6
4
|
# 責務:
|
|
7
5
|
# QueryStream gem のエントリポイント。
|
|
@@ -29,6 +27,10 @@ module QueryStream
|
|
|
29
27
|
# 行頭 = の直後に英数字/ハイフン/アンダースコアのデータ名(スペースは任意)
|
|
30
28
|
QUERY_STREAM_PATTERN = /^=\s*([a-zA-Z][a-zA-Z0-9_-]*)(?:\s*\|.*)?$/
|
|
31
29
|
|
|
30
|
+
# これを超える長さの引数はパスとみなさず本文として扱う(scan の path/content 判定)。
|
|
31
|
+
# 主要な OS の PATH_MAX は 4096 前後で、それより長い「パス」は実在しない。
|
|
32
|
+
MAX_PATH_BYTES = 4096
|
|
33
|
+
|
|
32
34
|
class << self
|
|
33
35
|
# グローバル設定を返す
|
|
34
36
|
# @return [Configuration]
|
|
@@ -42,12 +44,6 @@ module QueryStream
|
|
|
42
44
|
yield(configuration)
|
|
43
45
|
end
|
|
44
46
|
|
|
45
|
-
# ロガーへのショートカット
|
|
46
|
-
# @return [Logger]
|
|
47
|
-
def logger
|
|
48
|
-
configuration.logger
|
|
49
|
-
end
|
|
50
|
-
|
|
51
47
|
# テキストコンテンツ内の QueryStream 記法をすべて展開する
|
|
52
48
|
# 1行の展開に失敗しても残りの行の処理を継続する。
|
|
53
49
|
# エラー情報は例外の属性として呼び出し元に委ねる(gem 内ではログ出力しない)。
|
|
@@ -57,10 +53,13 @@ module QueryStream
|
|
|
57
53
|
# @param templates_dir [String, nil] テンプレートディレクトリ(nil時はconfigを使用)
|
|
58
54
|
# @param on_error [Proc, nil] エラー時コールバック。|exception| を受け取る。省略時は何もしない。
|
|
59
55
|
# @param on_warning [Proc, nil] 警告時コールバック。|warning| を受け取る。省略時は何もしない。
|
|
56
|
+
# @param post_render [Proc, nil] 展開結果の後段フィルタ。(text, context) -> String。省略時は configuration.post_render を使う。
|
|
60
57
|
# @return [String] 展開後のテキストコンテンツ
|
|
61
|
-
def render(content, source_filename: nil, data_dir: nil, templates_dir: nil, on_error: nil, on_warning: nil
|
|
58
|
+
def render(content, source_filename: nil, data_dir: nil, templates_dir: nil, on_error: nil, on_warning: nil,
|
|
59
|
+
post_render: nil)
|
|
62
60
|
data_dir ||= configuration.data_dir
|
|
63
61
|
templates_dir ||= configuration.templates_dir
|
|
62
|
+
post_render ||= configuration.post_render
|
|
64
63
|
|
|
65
64
|
lines = content.lines
|
|
66
65
|
result = []
|
|
@@ -70,7 +69,7 @@ module QueryStream
|
|
|
70
69
|
line_number = idx + 1
|
|
71
70
|
|
|
72
71
|
# コードブロック内はスキップ
|
|
73
|
-
if
|
|
72
|
+
if code_fence?(line)
|
|
74
73
|
in_code_block = !in_code_block
|
|
75
74
|
result << line
|
|
76
75
|
next
|
|
@@ -82,10 +81,10 @@ module QueryStream
|
|
|
82
81
|
end
|
|
83
82
|
|
|
84
83
|
# QueryStream 記法の検出
|
|
85
|
-
if
|
|
84
|
+
if query_line?(line)
|
|
86
85
|
begin
|
|
87
86
|
expanded = render_query(
|
|
88
|
-
line.chomp, line_number:, source_filename:, data_dir:, templates_dir:, on_warning:
|
|
87
|
+
line.chomp, line_number:, source_filename:, data_dir:, templates_dir:, on_warning:, post_render:
|
|
89
88
|
)
|
|
90
89
|
result << expanded << "\n"
|
|
91
90
|
rescue Error => e
|
|
@@ -107,8 +106,10 @@ module QueryStream
|
|
|
107
106
|
# @param source_filename [String, nil] ソースファイル名
|
|
108
107
|
# @param data_dir [String, nil] データディレクトリ
|
|
109
108
|
# @param templates_dir [String, nil] テンプレートディレクトリ
|
|
109
|
+
# @param post_render [Proc, nil] 展開結果の後段フィルタ。(text, context) -> String
|
|
110
110
|
# @return [String] 展開後のテキスト
|
|
111
|
-
def render_query(query, line_number: nil, source_filename: nil, data_dir: nil, templates_dir: nil, on_warning: nil
|
|
111
|
+
def render_query(query, line_number: nil, source_filename: nil, data_dir: nil, templates_dir: nil, on_warning: nil,
|
|
112
|
+
post_render: nil)
|
|
112
113
|
data_dir ||= configuration.data_dir
|
|
113
114
|
templates_dir ||= configuration.templates_dir
|
|
114
115
|
location = source_filename ? "#{source_filename}:#{line_number}" : "行#{line_number}"
|
|
@@ -117,8 +118,6 @@ module QueryStream
|
|
|
117
118
|
parsed = QueryStreamParser.parse(query)
|
|
118
119
|
|
|
119
120
|
# --- Phase: Load Data ---
|
|
120
|
-
# gem 内でログ出力せず、構造化された属性を持つ例外を raise する。
|
|
121
|
-
# メッセージの構成・ログ出力・i18n は呼び出し元の責務とする。
|
|
122
121
|
data_file = DataResolver.resolve(parsed[:source], data_dir)
|
|
123
122
|
unless data_file
|
|
124
123
|
expected = File.join(data_dir, "#{parsed[:source]}.yml")
|
|
@@ -144,13 +143,11 @@ module QueryStream
|
|
|
144
143
|
if parsed[:single_lookup]
|
|
145
144
|
case records.size
|
|
146
145
|
when 0
|
|
147
|
-
# gem 内でログ出力せず、構造化された属性を持つ警告を呼び出し元に委ねる。
|
|
148
146
|
on_warning&.call(NoResultWarning.new(query: query, location: location))
|
|
149
147
|
return ''
|
|
150
148
|
when 1
|
|
151
149
|
# 正常
|
|
152
150
|
else
|
|
153
|
-
# gem 内でログ出力せず、構造化された属性を持つ警告を呼び出し元に委ねる。
|
|
154
151
|
on_warning&.call(
|
|
155
152
|
AmbiguousQueryWarning.new(query: query, location: location, count: records.size)
|
|
156
153
|
)
|
|
@@ -165,8 +162,6 @@ module QueryStream
|
|
|
165
162
|
|
|
166
163
|
unless File.exist?(template_path)
|
|
167
164
|
hint = build_template_hint(singular, style, format, templates_dir)
|
|
168
|
-
# gem 内でログ出力せず、構造化された属性を持つ例外を raise する。
|
|
169
|
-
# メッセージの構成・ログ出力・i18n は呼び出し元の責務とする。
|
|
170
165
|
raise TemplateNotFoundError.new(
|
|
171
166
|
template_path: template_path,
|
|
172
167
|
query: query,
|
|
@@ -178,26 +173,42 @@ module QueryStream
|
|
|
178
173
|
template_content = File.read(template_path, encoding: 'utf-8')
|
|
179
174
|
|
|
180
175
|
# --- Phase: Render ---
|
|
181
|
-
TemplateCompiler.render(template_content, records, source_filename:, line_number:)
|
|
176
|
+
rendered = TemplateCompiler.render(template_content, records, source_filename:, line_number:, template_path:)
|
|
177
|
+
|
|
178
|
+
# --- Phase: Post-render filter ---
|
|
179
|
+
# 展開結果を呼び出し元の後段フィルタへ通す(画像パス解決など gem 外の後処理)。
|
|
180
|
+
# コールバックが String 以外を返した場合は、元の展開結果を採用する。
|
|
181
|
+
return rendered unless post_render
|
|
182
|
+
|
|
183
|
+
context = {
|
|
184
|
+
source: parsed[:source], # 記法に書かれた論理名(例: "physics_book")
|
|
185
|
+
data_file: data_file, # 単複解決後の実パス(例: "data/physics_books.yml")
|
|
186
|
+
data_dir: data_dir,
|
|
187
|
+
template_path: template_path,
|
|
188
|
+
query: query,
|
|
189
|
+
location: location # "filename:line" 形式
|
|
190
|
+
}
|
|
191
|
+
result = post_render.call(rendered, context)
|
|
192
|
+
result.is_a?(String) ? result : rendered
|
|
182
193
|
end
|
|
183
194
|
|
|
184
195
|
# テキスト内の QueryStream 記法を検出してリストを返す
|
|
185
196
|
# @param path_or_content [String] ファイルパスまたはテキストコンテンツ
|
|
186
197
|
# @return [Array<String>] 検出された QueryStream 記法のリスト
|
|
187
198
|
def scan(path_or_content)
|
|
188
|
-
content =
|
|
199
|
+
content = readable_file?(path_or_content) ? File.read(path_or_content, encoding: 'utf-8') : path_or_content
|
|
189
200
|
lines = content.lines
|
|
190
201
|
queries = []
|
|
191
202
|
in_code_block = false
|
|
192
203
|
|
|
193
204
|
lines.each do |line|
|
|
194
|
-
if
|
|
205
|
+
if code_fence?(line)
|
|
195
206
|
in_code_block = !in_code_block
|
|
196
207
|
next
|
|
197
208
|
end
|
|
198
209
|
next if in_code_block
|
|
199
210
|
|
|
200
|
-
queries << line.chomp if
|
|
211
|
+
queries << line.chomp if query_line?(line)
|
|
201
212
|
end
|
|
202
213
|
|
|
203
214
|
queries
|
|
@@ -205,6 +216,43 @@ module QueryStream
|
|
|
205
216
|
|
|
206
217
|
private
|
|
207
218
|
|
|
219
|
+
# その行を QueryStream 記法として扱うか。
|
|
220
|
+
#
|
|
221
|
+
# 不正な UTF-8 を含む行は記法として解釈せず、本文としてそのまま返す。`match?` はそういう行に
|
|
222
|
+
# `ArgumentError` を投げるので、捕まえないと展開全体が止まる。scrub しないのは、原稿のバイト列を
|
|
223
|
+
# 変えないため。
|
|
224
|
+
#
|
|
225
|
+
# EncodingError と ArgumentError の両方を捕まえる。壊れたバイト列に Ruby が投げる例外はメソッドで
|
|
226
|
+
# 異なる(match? / 先頭破損の lstrip は ArgumentError、strip は Encoding::CompatibilityError)。
|
|
227
|
+
def query_line?(line)
|
|
228
|
+
line.match?(QUERY_STREAM_PATTERN)
|
|
229
|
+
rescue EncodingError, ArgumentError
|
|
230
|
+
false
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
# コードフェンスの開始/終了行か。
|
|
234
|
+
# `lstrip` も不正な UTF-8 で ArgumentError を投げるので、query_line? と同じく本文として扱う。
|
|
235
|
+
def code_fence?(line)
|
|
236
|
+
line.lstrip.start_with?('```')
|
|
237
|
+
rescue EncodingError, ArgumentError
|
|
238
|
+
false
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
# 引数をファイルパスとして読むべきか。
|
|
242
|
+
#
|
|
243
|
+
# `scan` はパスと本文のどちらも受け取る。本文を `File.exist?` へ渡すと、NUL バイトを含む場合は
|
|
244
|
+
# `ArgumentError` になり、ディレクトリは `File.read` が `Errno::EISDIR` を出す。
|
|
245
|
+
# パスとして成立する形だけを先に選び、判定の失敗も本文として扱う。
|
|
246
|
+
def readable_file?(str)
|
|
247
|
+
s = str.to_s
|
|
248
|
+
return false if s.empty? || s.include?("\0") || s.include?("\n")
|
|
249
|
+
return false if s.bytesize > MAX_PATH_BYTES
|
|
250
|
+
|
|
251
|
+
File.file?(s)
|
|
252
|
+
rescue ArgumentError, SystemCallError
|
|
253
|
+
false
|
|
254
|
+
end
|
|
255
|
+
|
|
208
256
|
# テンプレートファイルパスを解決する
|
|
209
257
|
# @param singular_name [String] 単数形のデータ名
|
|
210
258
|
# @param style [String, nil] スタイル名
|
data/query-stream.gemspec
CHANGED
|
@@ -8,29 +8,29 @@ Gem::Specification.new do |spec|
|
|
|
8
8
|
spec.authors = ['Atelier Mirai']
|
|
9
9
|
spec.email = ['contact@atelier-mirai.net']
|
|
10
10
|
|
|
11
|
-
spec.summary = '
|
|
12
|
-
spec.description = '
|
|
13
|
-
|
|
11
|
+
spec.summary = 'YAML / JSON のデータとテンプレートから、文章中の 1 行の記法を展開する Ruby ライブラリ'
|
|
12
|
+
spec.description = 'QueryStream は、文章中に書いた `= books | tags=ruby | -year | 5` のような 1 行の記法を、' \
|
|
13
|
+
'YAML / JSON のデータファイルとテンプレートを組み合わせて展開する汎用の Ruby ライブラリです。' \
|
|
14
|
+
'抽出条件、並べ替え、件数、スタイルを指定できます。'
|
|
14
15
|
spec.homepage = 'https://github.com/Atelier-Mirai/query-stream'
|
|
15
16
|
spec.license = 'MIT'
|
|
16
17
|
|
|
17
18
|
spec.metadata['source_code_uri'] = 'https://github.com/Atelier-Mirai/query-stream'
|
|
18
19
|
spec.metadata['changelog_uri'] = 'https://github.com/Atelier-Mirai/query-stream/blob/master/CHANGELOG.md'
|
|
19
20
|
|
|
20
|
-
spec.required_ruby_version = '>= 4
|
|
21
|
+
spec.required_ruby_version = '>= 3.4'
|
|
21
22
|
|
|
22
|
-
spec.files = Dir.glob('{lib,bin}/**/*') +
|
|
23
|
+
spec.files = Dir.glob('{lib,bin}/**/*') +
|
|
24
|
+
%w[README.md CHANGELOG.md RELEASE_NOTE.md LICENSE query-stream.gemspec]
|
|
23
25
|
spec.bindir = 'bin'
|
|
24
26
|
spec.executables = ['query-stream']
|
|
25
27
|
spec.require_paths = ['lib']
|
|
26
28
|
|
|
27
|
-
# Runtime dependencies
|
|
28
|
-
spec.add_dependency 'logger'
|
|
29
29
|
spec.add_dependency 'samovar', '~> 2.1'
|
|
30
30
|
|
|
31
|
-
# Development dependencies
|
|
32
31
|
spec.add_development_dependency 'bundler'
|
|
33
32
|
spec.add_development_dependency 'rake', '~> 13.2'
|
|
34
|
-
spec.add_development_dependency 'minitest', '
|
|
33
|
+
spec.add_development_dependency 'minitest', '>= 5.22'
|
|
34
|
+
spec.add_development_dependency 'rubocop', '~> 1.81'
|
|
35
35
|
spec.metadata['rubygems_mfa_required'] = 'false'
|
|
36
36
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: query-stream
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Atelier Mirai
|
|
@@ -9,20 +9,6 @@ bindir: bin
|
|
|
9
9
|
cert_chain: []
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
|
-
- !ruby/object:Gem::Dependency
|
|
13
|
-
name: logger
|
|
14
|
-
requirement: !ruby/object:Gem::Requirement
|
|
15
|
-
requirements:
|
|
16
|
-
- - ">="
|
|
17
|
-
- !ruby/object:Gem::Version
|
|
18
|
-
version: '0'
|
|
19
|
-
type: :runtime
|
|
20
|
-
prerelease: false
|
|
21
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
-
requirements:
|
|
23
|
-
- - ">="
|
|
24
|
-
- !ruby/object:Gem::Version
|
|
25
|
-
version: '0'
|
|
26
12
|
- !ruby/object:Gem::Dependency
|
|
27
13
|
name: samovar
|
|
28
14
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -69,18 +55,32 @@ dependencies:
|
|
|
69
55
|
name: minitest
|
|
70
56
|
requirement: !ruby/object:Gem::Requirement
|
|
71
57
|
requirements:
|
|
72
|
-
- - "
|
|
58
|
+
- - ">="
|
|
73
59
|
- !ruby/object:Gem::Version
|
|
74
60
|
version: '5.22'
|
|
75
61
|
type: :development
|
|
76
62
|
prerelease: false
|
|
77
63
|
version_requirements: !ruby/object:Gem::Requirement
|
|
78
64
|
requirements:
|
|
79
|
-
- - "
|
|
65
|
+
- - ">="
|
|
80
66
|
- !ruby/object:Gem::Version
|
|
81
67
|
version: '5.22'
|
|
82
|
-
|
|
83
|
-
|
|
68
|
+
- !ruby/object:Gem::Dependency
|
|
69
|
+
name: rubocop
|
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
|
71
|
+
requirements:
|
|
72
|
+
- - "~>"
|
|
73
|
+
- !ruby/object:Gem::Version
|
|
74
|
+
version: '1.81'
|
|
75
|
+
type: :development
|
|
76
|
+
prerelease: false
|
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - "~>"
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: '1.81'
|
|
82
|
+
description: QueryStream は、文章中に書いた `= books | tags=ruby | -year | 5` のような 1 行の記法を、YAML
|
|
83
|
+
/ JSON のデータファイルとテンプレートを組み合わせて展開する汎用の Ruby ライブラリです。抽出条件、並べ替え、件数、スタイルを指定できます。
|
|
84
84
|
email:
|
|
85
85
|
- contact@atelier-mirai.net
|
|
86
86
|
executables:
|
|
@@ -88,9 +88,10 @@ executables:
|
|
|
88
88
|
extensions: []
|
|
89
89
|
extra_rdoc_files: []
|
|
90
90
|
files:
|
|
91
|
-
-
|
|
91
|
+
- CHANGELOG.md
|
|
92
92
|
- LICENSE
|
|
93
93
|
- README.md
|
|
94
|
+
- RELEASE_NOTE.md
|
|
94
95
|
- bin/query-stream
|
|
95
96
|
- lib/query_stream.rb
|
|
96
97
|
- lib/query_stream/command.rb
|
|
@@ -117,14 +118,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
117
118
|
requirements:
|
|
118
119
|
- - ">="
|
|
119
120
|
- !ruby/object:Gem::Version
|
|
120
|
-
version: '4
|
|
121
|
+
version: '3.4'
|
|
121
122
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
122
123
|
requirements:
|
|
123
124
|
- - ">="
|
|
124
125
|
- !ruby/object:Gem::Version
|
|
125
126
|
version: '0'
|
|
126
127
|
requirements: []
|
|
127
|
-
rubygems_version: 4.0.
|
|
128
|
+
rubygems_version: 4.0.20
|
|
128
129
|
specification_version: 4
|
|
129
|
-
summary:
|
|
130
|
+
summary: YAML / JSON のデータとテンプレートから、文章中の 1 行の記法を展開する Ruby ライブラリ
|
|
130
131
|
test_files: []
|