perron 0.5.0 → 0.7.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: aa3f2904ac098e13aff67d7153df4867f4301a9c273d06963d3ff0330f97fe86
4
- data.tar.gz: e53143e80f15e7fd1cf00b789959e2c0399f1f547459a39ecf927f6859bfd8f3
3
+ metadata.gz: 4ab1fe5550ba3bfd474381303929b49e9dac552d2d086bcbeb79c0760ad59979
4
+ data.tar.gz: 4b64af0d6128def66419351d50f74dffc29e8c74b1cf6f00ec004dc122f426cb
5
5
  SHA512:
6
- metadata.gz: 9e7a6e6594f349a7f6f575d5e7bdc1a54234446846695b848c0ff1e982a2de22898c85749b39af4196f64ba44d991b4d25878f4f8451d5ef4fd8fb9fa093c1e6
7
- data.tar.gz: 7055bbf79e0635ce0e2f38c3397f3e20bb76c70dd228ed5482be2fdf9bfe3d38d986e24a1a632420795adbad36cf5019cde704187fd7d9408cd990122fd209fe
6
+ metadata.gz: a3490ff02b1c8612e99f6403d0db98008b88461dd73b430b3470623a33bd4bd5bdc2945ff61a7bae821e1ff1bbcd3ba9f707be86c28724807300cdf75f476493
7
+ data.tar.gz: 505b439b20f2dbc5d2310aa56d8b8bcd99b793ddf83395f5652be1814e598c26d3f2154cde99531ad79d621ef7377cb12ddc076677e233a54c96faa875ff4cbf
data/Gemfile.lock CHANGED
@@ -1,7 +1,10 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- perron (0.5.0)
4
+ perron (0.7.0)
5
+ csv
6
+ json
7
+ psych
5
8
  rails (>= 7.2.0)
6
9
 
7
10
  GEM
@@ -86,6 +89,7 @@ GEM
86
89
  concurrent-ruby (1.3.5)
87
90
  connection_pool (2.5.3)
88
91
  crass (1.0.6)
92
+ csv (3.3.5)
89
93
  date (3.4.1)
90
94
  debug (1.11.0)
91
95
  irb (~> 1.10)
data/README.md CHANGED
@@ -26,10 +26,6 @@ This creates an initializer:
26
26
  ```ruby
27
27
  Perron.configure do |config|
28
28
  config.site_name = "AppRefresher"
29
-
30
- # Override the defaults (meta) title suffix
31
- # Default: `— Perron.configuration.site_name`
32
- # config.title_suffix = nil
33
29
  end
34
30
  ```
35
31
 
@@ -89,6 +85,35 @@ bundle add {commonmarker,kramdown,redcarpet}
89
85
  ```
90
86
 
91
87
 
88
+ ## Data Files
89
+
90
+ Perron can consume structured data from YML, JSON, or CSV files, making them available within your templates.
91
+ This is useful for populating features, team members, or any other repeated data structure.
92
+
93
+ ### Usage
94
+
95
+ To use a data file, instantiate `Perron::Data` with the basename of the file and iterate over the result.
96
+ ```erb
97
+ <% Perron::Data.new("features").each do |feature| %>
98
+ <h4><%= feature.name %></h4>
99
+ <p><%= feature.description %></p>
100
+ <% end %>
101
+ ```
102
+
103
+ ### File Location and Formats
104
+
105
+ By default, Perron looks up `app/content/data/` for files with a `.yml`, `.json`, or `.csv` extension.
106
+ For a `new("features")` call, it would find `features.yml`, `features.json`, or `features.csv`. You can also provide a full, absolute path to any data file.
107
+
108
+ ### Accessing Data
109
+
110
+ The wrapper object provides flexible, read-only access to each record's attributes. Both dot notation and hash-like key access are supported.
111
+ ```ruby
112
+ feature.name
113
+ feature[:name]
114
+ ```
115
+
116
+
92
117
  ## Metatags
93
118
 
94
119
  The `meta_tags` helper automatically generates SEO and social sharing meta tags for your pages.
@@ -115,7 +140,7 @@ Or exclude certain tags:
115
140
  <%= meta_tags except: %w[twitter_card twitter_image] %>
116
141
  ```
117
142
 
118
- ### Metadata Priority
143
+ ### Priority
119
144
 
120
145
  Values are determined with the following precedence, from highest to lowest:
121
146
 
@@ -172,6 +197,16 @@ RAILS_ENV=production rails perron:build
172
197
  This will create your static site in the configured output directory (`output` by default).
173
198
 
174
199
 
200
+ ## Sites using Perron
201
+
202
+ Sites that use Perron.
203
+
204
+ ### Standalone (as a SSG)
205
+ - [AppRefresher](https://apprefresher.com)
206
+
207
+ ### Integrated (part of a Rails app)
208
+ - [Rails Designers (private community for Rails UI engineers](https://railsdesigners.com)
209
+
175
210
  ## Contributing
176
211
 
177
212
  This project uses [Standard](https://github.com/testdouble/standard) for formatting Ruby code. Please run `be standardrb` before submitting pull requests. Run tests with `rails test`.
@@ -5,5 +5,12 @@ module Perron
5
5
  def copy_initializer
6
6
  template "initializer.rb.tt", "config/initializers/perron.rb"
7
7
  end
8
+
9
+ def create_data_directory
10
+ data_directory = Rails.root.join("app", "views", "content", "data")
11
+ empty_directory data_directory
12
+
13
+ template "README.md.tt", File.join(data_directory, "README.md")
14
+ end
8
15
  end
9
16
  end
@@ -0,0 +1,30 @@
1
+ # Data
2
+
3
+ Perron can consume structured data from YML, JSON, or CSV files, making them available within your templates.
4
+ This is useful for populating features, team members, or any other repeated data structure.
5
+
6
+
7
+ ## Usage
8
+
9
+ To use a data file, instantiate `Perron::Data` with the basename of the file and iterate over the result.
10
+ ```erb
11
+ <% Perron::Data.new("features").each do |feature| %>
12
+ <h4><%= feature.name %></h4>
13
+
14
+ <p><%= feature.description %></p>
15
+ <% end %>
16
+ ```
17
+
18
+ ## File Location and Formats
19
+
20
+ By default, Perron looks up `app/content/data/` for files with a `.yml`, `.json`, or `.csv` extension.
21
+ For a `new("features")` call, it would find `features.yml`, `features.json`, or `features.csv`. You can also provide a full, absolute path to any data file.
22
+
23
+
24
+ ## Accessing Data
25
+
26
+ The wrapper object provides flexible, read-only access to each record's attributes. Both dot notation and hash-like key access are supported.
27
+ ```ruby
28
+ feature.name
29
+ feature[:name]
30
+ ```
@@ -11,12 +11,12 @@ Perron.configure do |config|
11
11
 
12
12
  # config.default_url_options = {host: "apprefresher.com", protocol: "https", trailing_slash: true}
13
13
 
14
- # Override the defaults (meta) title suffix
15
- # Default: `— Perron.configuration.site_name`
16
- # config.title_suffix = nil
17
-
18
14
  # Set default meta values
19
15
  # Examples:
20
16
  # - `config.metadata.description = "AI-powered tool to keep your knowledge base articles images/screenshots and content up-to-date"`
21
17
  # - `config.metadata.author = "Rails Designer"`
18
+
19
+ # Set meta title suffix
20
+ # config.metadata.title_suffix = nil
21
+ # config.metadata.title_separator = " — "
22
22
  end
@@ -23,7 +23,6 @@ module Perron
23
23
  @config.include_root = false
24
24
 
25
25
  @config.site_name = nil
26
- @config.title_suffix = nil
27
26
 
28
27
  @config.allowed_extensions = [".erb", ".md"]
29
28
  @config.exclude_from_public = %w[assets storage]
@@ -36,6 +35,7 @@ module Perron
36
35
  }
37
36
 
38
37
  @config.metadata = ActiveSupport::OrderedOptions.new
38
+ @config.metadata.title_separator = " — "
39
39
  end
40
40
 
41
41
  def input = "app/content"
data/lib/perron/errors.rb CHANGED
@@ -5,5 +5,9 @@ module Perron
5
5
  class FileNotFoundError < StandardError; end
6
6
 
7
7
  class ResourceNotFoundError < StandardError; end
8
+
9
+ class UnsupportedDataFormatError < StandardError; end
10
+
11
+ class DataParseError < StandardError; end
8
12
  end
9
13
  end
@@ -29,7 +29,7 @@ module Perron
29
29
  frontmatter = @resource&.metadata&.stringify_keys || {}
30
30
  defaults = @config.metadata
31
31
 
32
- title = frontmatter["title"] || defaults["title"] || @config.site_name
32
+ title = frontmatter["title"] || defaults["title"] || @config.site_name || Rails.application.name.underscore.camelize
33
33
  description = frontmatter["description"] || defaults["description"]
34
34
  author = frontmatter["author"] || defaults["author"]
35
35
  image = frontmatter["image"] || defaults["image"]
@@ -59,9 +59,12 @@ module Perron
59
59
  end
60
60
 
61
61
  def title_tag(content)
62
- tag.title(
63
- content.then { (it == @config.site_name) ? it : "#{it} #{@config.title_suffix || "— #{@config.site_name}"}" }
64
- )
62
+ resource_title = content.to_s.strip
63
+ title_suffix = Perron.configuration.metadata.title_suffix&.strip
64
+
65
+ suffix = (title_suffix if title_suffix.present? && resource_title != title_suffix)
66
+
67
+ tag.title([resource_title, suffix].compact.join(Perron.configuration.metadata.title_separator))
65
68
  end
66
69
 
67
70
  def meta_tag(attributes)
@@ -30,7 +30,7 @@ module Perron
30
30
  private
31
31
 
32
32
  def paths
33
- @paths ||= Dir.glob("#{@public_dir}/*", File::FNM_DOTMATCH).reject do |path|
33
+ @paths ||= Dir.glob(File.join(@public_dir, "*")).reject do |path|
34
34
  Set.new(Perron.configuration.exclude_from_public + %w[. ..]).include?(File.basename(path))
35
35
  end
36
36
  end
@@ -0,0 +1,144 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "csv"
4
+
5
+ module Perron
6
+ class Data < SimpleDelegator
7
+ def initialize(identifier)
8
+ @file_path = path_for(identifier)
9
+ @records = records
10
+
11
+ super(records)
12
+ end
13
+
14
+ private
15
+
16
+ PARSER_METHODS = {
17
+ ".yml" => :parse_yaml, ".yaml" => :parse_yaml,
18
+ ".json" => :parse_json, ".csv" => :parse_csv
19
+ }.freeze
20
+ SUPPORTED_EXTENSIONS = PARSER_METHODS.keys
21
+
22
+ def path_for(identifier)
23
+ path = Pathname.new(identifier)
24
+
25
+ return path.to_s if path.file? && path.absolute?
26
+
27
+ path = SUPPORTED_EXTENSIONS.lazy.map { Rails.root.join("app", "content", "data").join("#{identifier}#{it}") }.find(&:exist?)
28
+ path&.to_s or raise Errors::FileNotFoundError, "No data file found for '#{identifier}'"
29
+ end
30
+
31
+ def records
32
+ content = File.read(@file_path)
33
+ extension = File.extname(@file_path)
34
+ parser = PARSER_METHODS.fetch(extension) do
35
+ raise Errors::UnsupportedDataFormatError, "Unsupported data format: #{extension}"
36
+ end
37
+
38
+ data = send(parser, content)
39
+
40
+ unless data.is_a?(Array)
41
+ raise Errors::DataParseError, "Data in '#{@file_path}' must be an array of objects."
42
+ end
43
+
44
+ data.map { Item.new(it) }
45
+ rescue Psych::SyntaxError, JSON::ParserError, CSV::MalformedCSVError => error
46
+ raise Errors::DataParseError, "Failed to parse '#{@file_path}': #{error.message}"
47
+ end
48
+
49
+ def parse_yaml(content)
50
+ YAML.safe_load(content, permitted_classes: [Symbol], aliases: true)
51
+ end
52
+
53
+ def parse_json(content)
54
+ JSON.parse(content, symbolize_names: true)
55
+ end
56
+
57
+ def parse_csv(content)
58
+ CSV.new(content, headers: true, header_converters: :symbol).to_a.map(&:to_h)
59
+ end
60
+
61
+ class Item
62
+ def initialize(attributes)
63
+ @attributes = attributes.transform_keys(&:to_sym)
64
+ end
65
+
66
+ def [](key) = @attributes[key.to_sym]
67
+
68
+ def method_missing(method_name, *arguments, &block)
69
+ return super if !@attributes.key?(method_name) || arguments.any? || block
70
+
71
+ @attributes[method_name]
72
+ end
73
+
74
+ def respond_to_missing?(method_name, include_private = false)
75
+ @attributes.key?(method_name) || super
76
+ end
77
+ end
78
+ private_constant :Item
79
+ end
80
+ end
81
+
82
+ # require "csv"
83
+
84
+ # module Perron
85
+ # class Data
86
+ # include Enumerable
87
+
88
+ # def initialize(resource)
89
+ # @file_path = path_for(resource)
90
+ # @data = data
91
+ # end
92
+
93
+ # def each(&block)
94
+ # @data.each(&block)
95
+ # end
96
+
97
+ # private
98
+
99
+ # PARSER_METHODS = {
100
+ # ".csv" => :parse_csv,
101
+ # ".json" => :parse_json,
102
+ # ".yaml" => :parse_yaml,
103
+ # ".yml" => :parse_yaml
104
+ # }.freeze
105
+ # SUPPORTED_EXTENSIONS = PARSER_METHODS.keys.freeze
106
+
107
+ # def path_for(identifier)
108
+ # path = Pathname.new(identifier)
109
+
110
+ # return path.to_s if path.file? && path.absolute?
111
+
112
+ # found_path = SUPPORTED_EXTENSIONS.lazy.map do |extension|
113
+ # Rails.root.join("app", "content", "data").join("#{identifier}#{extension}")
114
+ # end.find(&:exist?)
115
+
116
+ # found_path&.to_s or raise Errors::FileNotFoundError, "No data file found for '#{identifier}'"
117
+ # end
118
+
119
+ # def data
120
+ # content = File.read(@file_path)
121
+ # extension = File.extname(@file_path)
122
+ # parser = PARSER_METHODS.fetch(extension) do
123
+ # raise Errors::UnsupportedDataFormatError, "Unsupported data format: #{extension}"
124
+ # end
125
+
126
+ # raw_data = send(parser, content)
127
+
128
+ # unless raw_data.is_a?(Array)
129
+ # raise Errors::DataParseError, "Data in '#{@file_path}' must be an array of objects."
130
+ # end
131
+
132
+ # struct = Struct.new(*raw_data.first.keys, keyword_init: true)
133
+ # raw_data.map { struct.new(**it) }
134
+ # rescue Psych::SyntaxError, JSON::ParserError, CSV::MalformedCSVError => error
135
+ # raise Errors::DataParseError, "Failed to parse '#{@file_path}': #{error.message}"
136
+ # end
137
+
138
+ # def parse_yaml(content) = YAML.safe_load(content, permitted_classes: [Symbol], aliases: true)
139
+
140
+ # def parse_json(content) = JSON.parse(content, symbolize_names: true)
141
+
142
+ # def parse_csv(content) = CSV.new(content, headers: true, header_converters: :symbol).to_a.map(&:to_h)
143
+ # end
144
+ # end
@@ -4,7 +4,6 @@ require "perron/site/resource/core"
4
4
  require "perron/site/resource/class_methods"
5
5
  require "perron/site/resource/publishable"
6
6
  require "perron/site/resource/slug"
7
- require "perron/site/resource/context"
8
7
  require "perron/site/resource/separator"
9
8
 
10
9
  module Perron
@@ -31,11 +30,14 @@ module Perron
31
30
  alias_method :to_param, :slug
32
31
 
33
32
  def content
34
- if processable?
35
- ERB.new(Perron::Resource::Separator.new(raw_content).content).result(context.binding)
36
- else
37
- Perron::Resource::Separator.new(raw_content).content
38
- end
33
+ return Perron::Resource::Separator.new(raw_content).content unless processable?
34
+
35
+ ::ApplicationController
36
+ .renderer
37
+ .render(
38
+ inline: Perron::Resource::Separator.new(raw_content).content,
39
+ assigns: {resource: self}
40
+ )
39
41
  end
40
42
 
41
43
  def metadata = Perron::Resource::Separator.new(raw_content).metadata
@@ -56,7 +58,5 @@ module Perron
56
58
  @file_path.delete_prefix(Perron.configuration.input).parameterize
57
59
  ).first(ID_LENGTH)
58
60
  end
59
-
60
- def context = Perron::Resource::Context.new(self)
61
61
  end
62
62
  end
data/lib/perron/site.rb CHANGED
@@ -3,6 +3,7 @@
3
3
  require "perron/site/builder"
4
4
  require "perron/site/collection"
5
5
  require "perron/site/resource"
6
+ require "perron/site/data"
6
7
 
7
8
  module Perron
8
9
  module Site
@@ -26,8 +27,10 @@ module Perron
26
27
  .map { Collection.new(it) }
27
28
  end
28
29
 
29
- def collection(name)
30
- Collection.new(name)
30
+ def collection(name) = Collection.new(name)
31
+
32
+ def data(name)
33
+ Perron::Data.new(name)
31
34
  end
32
35
  end
33
36
  end
@@ -1,3 +1,3 @@
1
1
  module Perron
2
- VERSION = "0.5.0"
2
+ VERSION = "0.7.0"
3
3
  end
data/perron.gemspec CHANGED
@@ -19,4 +19,8 @@ Gem::Specification.new do |spec|
19
19
  spec.required_ruby_version = ">= 3.4.0"
20
20
 
21
21
  spec.add_dependency "rails", ">= 7.2.0"
22
+
23
+ spec.add_runtime_dependency "csv"
24
+ spec.add_runtime_dependency "json"
25
+ spec.add_runtime_dependency "psych"
22
26
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: perron
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rails Designer Developers
@@ -23,6 +23,48 @@ dependencies:
23
23
  - - ">="
24
24
  - !ruby/object:Gem::Version
25
25
  version: 7.2.0
26
+ - !ruby/object:Gem::Dependency
27
+ name: csv
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ - !ruby/object:Gem::Dependency
41
+ name: json
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ - !ruby/object:Gem::Dependency
55
+ name: psych
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ type: :runtime
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
26
68
  description: Perron is a Rails-based static site generator that follows Rails conventions.
27
69
  It allows you to create content collections with markdown or ERB, configure SEO
28
70
  metadata, and build production-ready static sites while leveraging your existing
@@ -49,6 +91,7 @@ files:
49
91
  - lib/generators/content/templates/root.erb.tt
50
92
  - lib/generators/content/templates/show.html.erb.tt
51
93
  - lib/generators/perron/install_generator.rb
94
+ - lib/generators/perron/templates/README.md.tt
52
95
  - lib/generators/perron/templates/initializer.rb.tt
53
96
  - lib/perron.rb
54
97
  - lib/perron/configuration.rb
@@ -67,9 +110,9 @@ files:
67
110
  - lib/perron/site/builder/paths.rb
68
111
  - lib/perron/site/builder/public_files.rb
69
112
  - lib/perron/site/collection.rb
113
+ - lib/perron/site/data.rb
70
114
  - lib/perron/site/resource.rb
71
115
  - lib/perron/site/resource/class_methods.rb
72
- - lib/perron/site/resource/context.rb
73
116
  - lib/perron/site/resource/core.rb
74
117
  - lib/perron/site/resource/publishable.rb
75
118
  - lib/perron/site/resource/separator.rb
@@ -97,7 +140,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
97
140
  - !ruby/object:Gem::Version
98
141
  version: '0'
99
142
  requirements: []
100
- rubygems_version: 3.6.7
143
+ rubygems_version: 3.7.0
101
144
  specification_version: 4
102
145
  summary: Rails-based static site generator
103
146
  test_files: []
@@ -1,13 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Perron
4
- class Resource
5
- class Context
6
- def initialize(resource)
7
- @resource = resource
8
- end
9
-
10
- def binding = super
11
- end
12
- end
13
- end