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 

パイプライン:

1. Source  - 

トークンの自動判別:

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.0.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) ⇒ String

テキストコンテンツ内の QueryStream 記法をすべて展開する

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を使用)

Returns:

  • (String)

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



57
58
59
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
# File 'lib/query_stream.rb', line 57

def render(content, source_filename: nil, data_dir: nil, templates_dir: 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)
      expanded = render_query(
        line.chomp, line_number:, source_filename:, data_dir:, templates_dir:
      )
      result << expanded << "\n"
    else
      result << line
    end
  end

  result.join
end

.render_query(query, line_number: nil, source_filename: nil, data_dir: nil, templates_dir: 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)

    展開後のテキスト



101
102
103
104
105
106
107
108
109
110
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
# File 'lib/query_stream.rb', line 101

def render_query(query, line_number: nil, source_filename: nil, data_dir: nil, templates_dir: 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 ---
  data_file = DataResolver.resolve(parsed[:source], data_dir)
  unless data_file
    expected = File.join(data_dir, "#{parsed[:source]}.yml")
    logger.error("データファイルが見つかりません(#{location})")
    logger.error("  記法: #{query}")
    logger.error("  期待: #{expected}")
    raise DataNotFoundError, "データファイルが見つかりません: #{expected}"
  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
      logger.warn("一件検索で該当なし(#{location}): #{query}")
      return ''
    when 1
      # 正常
    else
      logger.warn("一件検索で複数件ヒット(#{location}): #{query}")
      logger.warn("  #{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)
    logger.error("テンプレートファイルが見つかりません(#{location})")
    logger.error("  記法: #{query}")
    logger.error("  期待: #{template_path}")
    logger.error("  ヒント: #{hint}") if hint
    raise TemplateNotFoundError, "テンプレートファイルが見つかりません: #{template_path}"
  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 記法のリスト



168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
# File 'lib/query_stream.rb', line 168

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