Class: JekyllSQlite::Generator

Inherits:
Jekyll::Generator
  • Object
show all
Defined in:
lib/jekyll-sqlite/generator.rb

Overview

Main generator class rubocop:disable Metrics/ClassLength

Instance Method Summary collapse

Instance Method Details

#_prepare_query(stmt, params) ⇒ Object

Prepare the query by binding the parameters Since we don’t know if the query needs them we ignore all errors about “no such bind parameter”



53
54
55
56
57
58
59
60
61
# File 'lib/jekyll-sqlite/generator.rb', line 53

def _prepare_query(stmt, params)
  stmt.named_params.each do |key|
    val = params[key]
    unless [Integer, String, Float, SQLite3::Blob, nil].include? val.class
      Jekyll.logger.error "#{key} type is #{val.class} in query: #{stmt.get_sql}"
    end
    stmt.bind_param key, params[key]
  end
end

#attach_nested_data(root, path_segments, db, query) ⇒ Object

Recursively attach query results to nested data structures Supports arbitrary levels of nesting (e.g., regions.territories.EmployeeIDs) Handles both arrays and hashes at each level



27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# File 'lib/jekyll-sqlite/generator.rb', line 27

def attach_nested_data(root, path_segments, db, query)
  return 0 if path_segments.empty?

  if path_segments.size == 1
    key = path_segments.first
    db.prepare(query) do |stmt|
      _prepare_query(stmt, get_bind_params(root))
      root[key] = stmt.execute.to_a
    end
    return root[key].size
  end

  first, *remaining = path_segments
  current_level = root[first]

  if current_level.is_a?(Array)
    current_level.sum { |item| attach_nested_data(item, remaining, db, query) }
  else
    attach_nested_data(current_level, remaining, db, query)
  end
end

#build_collection_doc(collection, row, idx) ⇒ Object



147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
# File 'lib/jekyll-sqlite/generator.rb', line 147

def build_collection_doc(collection, row, idx)
  doc = Jekyll::Document.new(synth_doc_path(collection, row, idx),
                             site: @site, collection: collection)
  row.each do |k, v|
    next unless k.is_a?(String)

    v = Time.parse(v) if k == "date" && v.is_a?(String)
    doc.data[k] = v
  end
  # Jekyll's :title permalink placeholder reads data["slug"], not data["title"].
  # Auto-populate slug from title so SQL-provided titles show up in URLs.
  doc.data["slug"] ||= doc.data["title"]
  doc.content = row.key?("content") ? row["content"].to_s : ""
  doc
end

#close_all_databasesObject



19
20
21
# File 'lib/jekyll-sqlite/generator.rb', line 19

def close_all_databases
  @db.each_value(&:close)
end

#column_string(row, key) ⇒ Object



163
164
165
166
# File 'lib/jekyll-sqlite/generator.rb', line 163

def column_string(row, key)
  v = row[key]
  v.is_a?(String) && !v.empty? ? v : nil
end

#gen(root, config_holder) ⇒ Object

Generate the data from the configuration Takes as input the root where the data will be attached and a configuration holder, where the sqlite key can be found Root is either site.data or page.data and config_holder is either site.config or page itself.



102
103
104
105
106
107
108
109
110
111
112
113
114
115
# File 'lib/jekyll-sqlite/generator.rb', line 102

def gen(root, config_holder)
  (config_holder["sqlite"] || []).each do |config|
    unless valid_config?(config)
      Jekyll.logger.error "Jekyll SQLite:", "Invalid Configuration. Skipping"
      next
    end

    if config["collection"]
      generate_collection_from_config(config)
    else
      generate_data_from_config(root, config)
    end
  end
end

#generate(site) ⇒ Object

Entrpoint to the generator, called by Jekyll



170
171
172
173
174
175
176
177
178
179
# File 'lib/jekyll-sqlite/generator.rb', line 170

def generate(site)
  @db = {}
  @site = site
  gen(site.data, site.config)
  site.pages.each do |page|
    gen(page.data, page)
  end
ensure
  close_all_databases
end

#generate_collection_from_config(config) ⇒ Object

Build documents from query rows and append them to the named site collection.



119
120
121
122
123
124
125
126
127
128
129
130
131
132
# File 'lib/jekyll-sqlite/generator.rb', line 119

def generate_collection_from_config(config)
  name = config["collection"]
  collection = @site.collections[name]
  unless collection
    Jekyll.logger.error "Jekyll SQLite:", "Collection '#{name}' not declared in _config.yml"
    return
  end

  db = get_database(config["file"])
  db.results_as_hash = config.fetch("results_as_hash", true)
  rows = db.execute(config["query"])
  rows.each_with_index { |row, idx| collection.docs << build_collection_doc(collection, row, idx) }
  Jekyll.logger.info "Jekyll SQLite:", "Loaded collection #{name}. Count=#{rows.size}"
end

#generate_data_from_config(root, config) ⇒ Object



84
85
86
87
88
89
90
91
92
93
94
# File 'lib/jekyll-sqlite/generator.rb', line 84

def generate_data_from_config(root, config)
  key = config["data"]
  query = config["query"]
  file = config["file"]

  db = get_database(file)
  db.results_as_hash = config.fetch("results_as_hash", true)
  path_segments = key.split(".")
  count = attach_nested_data(root, path_segments, db, query)
  Jekyll.logger.info "Jekyll SQLite:", "Loaded #{key}. Count=#{count}"
end

#get_bind_params(dict) ⇒ Object

pick bindable parameters from the root All primitive values are bound to the query Arrays and Hashes are ignored



80
81
82
# File 'lib/jekyll-sqlite/generator.rb', line 80

def get_bind_params(dict)
  dict.select { |_key, value| !value.is_a?(Array) && !value.is_a?(Hash) }
end

#get_database(file) ⇒ Object



13
14
15
16
17
# File 'lib/jekyll-sqlite/generator.rb', line 13

def get_database(file)
  return @db[file] if @db.key?(file)

  @db[file] = SQLite3::Database.new file, readonly: true
end

#synth_doc_path(collection, row, idx) ⇒ Object

Build a synthetic document path from optional ‘name` and `path` columns. Falls back to a 1-based row id (idx+1) when `name` is missing, and to no subdirectory when `path` is missing. The `name` and `path` SQL columns are what feed Jekyll’s :name and :path permalink placeholders.



138
139
140
141
142
143
144
145
# File 'lib/jekyll-sqlite/generator.rb', line 138

def synth_doc_path(collection, row, idx)
  name = column_string(row, "name") || (idx + 1).to_s
  subdir = column_string(row, "path")
  parts = ["_#{collection.label}"]
  parts << subdir if subdir
  parts << "#{name}.md"
  File.join(@site.source, *parts)
end

#valid_config?(config) ⇒ Boolean

Validate given configuration object. A config is valid when it is a Hash with a query, a readable file, and either a data: or collection: target.

Returns:

  • (Boolean)


67
68
69
70
71
72
73
74
# File 'lib/jekyll-sqlite/generator.rb', line 67

def valid_config?(config)
  return false unless config.is_a? Hash
  return false unless config.key?("query")
  return false unless config["file"] && File.exist?(config["file"])
  return false unless config.key?("data") || config.key?("collection")

  true
end