perron 0.9.1 → 0.10.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: cce790442290a0fedc4e4db328e3358b60a7edafafc6a0c980cec22d6f02fba5
4
- data.tar.gz: c0a48eecc48c7a3230de6c36bf3a9180c9125b389282b0f311c02638b2db41cf
3
+ metadata.gz: 2aa4c7af63f95aef3bc25c9ef97a4da02d75486ec3ee975f69a980f66cb34557
4
+ data.tar.gz: f1c62fff6d0ba6c121da85f463dc32fd5656c884d059d0f2955c6a95a4c0bf3a
5
5
  SHA512:
6
- metadata.gz: e3f7bce10120b2feaf667051c8b67efd58adffb83a91d7d853b79b408bfa04cf5e004f40e99357059871c51f42aaf8ee26d32e4b36b776fcb9936309d1a7e94a
7
- data.tar.gz: 9324e15f3d236809cf9422da8e8500d302e64a980352c5a9163734d908f8ecd1774cd1350d762487e9795df887d026858e3bd3bcab440c8f9fb1c547913295d9
6
+ metadata.gz: 175ecbdc5dc5468e28e0abe13926b578fb6669d078b594da1dea91ab8cce68c3a7f63f12d3be4f0de757d270c5d3a95ce96db925ea9c3cbe238e1dd72069aaa1
7
+ data.tar.gz: b65f95000da601c8ea2f6dcea2e1cf9edb907d366ff783b3ba37e4b2c2d0ff0222cf98821f7537690895087c39ad0c902397c6325e309e27019914101072b6df
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- perron (0.9.1)
4
+ perron (0.10.0)
5
5
  csv
6
6
  json
7
7
  psych
data/README.md CHANGED
@@ -31,7 +31,7 @@ rails generate perron:install
31
31
  This creates an initializer:
32
32
  ```ruby
33
33
  Perron.configure do |config|
34
- config.site_name = "AppRefresher"
34
+ config.site_name = "Helptail"
35
35
  end
36
36
  ```
37
37
 
@@ -131,6 +131,71 @@ end
131
131
  ```
132
132
 
133
133
 
134
+ ### Embed Ruby
135
+
136
+ Perron provides flexible options for embedding dynamic Ruby code in your content using ERB.
137
+
138
+
139
+ #### 1. File extension
140
+
141
+ Any content file with a `.erb` extension (e.g., `about.erb`) will automatically have its content processed as ERB.
142
+
143
+
144
+ #### 2. Frontmatter
145
+
146
+ You can enable ERB processing on a per-file basis, even for standard `.md` files, by adding `erb: true` to the file's frontmatter.
147
+ ```markdown
148
+ ---
149
+ title: Dynamic Page
150
+ erb: true
151
+ ---
152
+
153
+ This entire page will be processed by ERB.
154
+ The current time is: <%= Time.current.to_fs(:long_ordinal) %>.
155
+ ```
156
+
157
+
158
+ #### 3. `erbify` helper
159
+
160
+ For the most granular control, the `erbify` helper allows to process specific sections of a file as ERB.
161
+ This is ideal for generating dynamic content like lists or tables from your resource's metadata, without needing to enable ERB for the entire file. The `erbify` helper can be used with a string or, more commonly, a block.
162
+
163
+ **Example:** Generating a list from frontmatter data in a standard `.md` file.
164
+ ```markdown
165
+ ---
166
+ title: Features
167
+ features:
168
+ - Rails based
169
+ - SEO friendly
170
+ - Markdown first
171
+ - ERB support
172
+ ---
173
+
174
+ Check out our amazing features:
175
+
176
+ <%= erbify do %>
177
+ <ul>
178
+ <% @resource.metadata.features.each do |feature| %>
179
+ <li>
180
+ <%= feature %>
181
+ </li>
182
+ <% end %>
183
+ </ul>
184
+ <% end %>
185
+ ```
186
+
187
+ **Result:**
188
+ ```html
189
+ <p>Check out our amazing features:</p>
190
+ <ul>
191
+ <li>Rails based</li>
192
+ <li>SEO friendly</li>
193
+ <li>Markdown first</li>
194
+ <li>ERB support</li>
195
+ </ul>
196
+ ```
197
+
198
+
134
199
  ## Data Files
135
200
 
136
201
  Perron can consume structured data from YML, JSON, or CSV files, making them available within your templates.
@@ -271,8 +336,8 @@ Set site-wide defaults in the initializer:
271
336
  class Content::Post < Perron::Resource
272
337
  # …
273
338
 
274
- config.metadata.description = "AI-powered tool to keep your knowledge base articles images/screenshots and content up-to-date"
275
- config.metadata.author = "Rails Designer"
339
+ config.metadata.description = "Put your routine tasks on autopilot"
340
+ config.metadata.author = "Helptail team"
276
341
  end
277
342
  ```
278
343
 
@@ -283,8 +348,8 @@ Set site-wide defaults in the initializer:
283
348
  Perron.configure do |config|
284
349
  # …
285
350
 
286
- config.metadata.description = "AI-powered tool to keep your knowledge base articles images/screenshots and content up-to-date"
287
- config.metadata.author = "Rails Designer"
351
+ config.metadata.description = "Put your routine tasks on autopilot"
352
+ config.metadata.author = "Helptail team"
288
353
  end
289
354
  ```
290
355
 
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Perron
4
+ module ErbHelper
5
+ def erbify(content = nil, options = {}, &block)
6
+ Perron::Resource::Renderer.erb(content || capture(&block).strip_heredoc, {resource: @resource})
7
+ end
8
+ end
9
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "rails/generators/base"
2
4
 
3
5
  class ContentGenerator < Rails::Generators::NamedBase
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Perron
2
4
  class InstallGenerator < Rails::Generators::Base
3
5
  source_root File.expand_path("templates", __dir__)
@@ -12,5 +14,15 @@ module Perron
12
14
 
13
15
  template "README.md.tt", File.join(data_directory, "README.md")
14
16
  end
17
+
18
+ def add_markdown_gems
19
+ append_to_file "Gemfile", <<~RUBY
20
+
21
+ # Perron can use one of the following gems. Uncomment your preferred choice and run `bundle install`
22
+ # gem "commonmarker"
23
+ # gem "kramdown"
24
+ # gem "redcarpet"
25
+ RUBY
26
+ end
15
27
  end
16
28
  end
@@ -1,7 +1,7 @@
1
1
  Perron.configure do |config|
2
2
  # config.output = "output"
3
3
 
4
- # config.site_name = "AppRefresher"
4
+ # config.site_name = "Helptail"
5
5
 
6
6
  # The build mode for Perron. Can be :standalone or :integrated.
7
7
  # config.mode = :standalone
@@ -9,12 +9,12 @@ Perron.configure do |config|
9
9
  # In `integrated` mode, the root is skipped by default. Set to `true` to enable.
10
10
  # config.include_root = false
11
11
 
12
- # config.default_url_options = {host: "apprefresher.com", protocol: "https", trailing_slash: true}
12
+ # config.default_url_options = {host: "helptail.com", protocol: "https", trailing_slash: true}
13
13
 
14
14
  # Set default meta values
15
15
  # Examples:
16
- # - `config.metadata.description = "AI-powered tool to keep your knowledge base articles images/screenshots and content up-to-date"`
17
- # - `config.metadata.author = "Rails Designer"`
16
+ # - `config.metadata.description = "Put your routine tasks on autopilot"`
17
+ # - `config.metadata.author = "Helptail Team"`
18
18
 
19
19
  # Set meta title suffix
20
20
  # config.metadata.title_suffix = nil
@@ -13,15 +13,18 @@ module Perron
13
13
  def initialize
14
14
  @config = ActiveSupport::OrderedOptions.new
15
15
 
16
+ @config.site_name = nil
17
+ @config.site_description = nil
18
+
19
+ @config.site_email = nil
20
+
16
21
  @config.output = "output"
17
22
 
18
23
  @config.mode = :standalone
19
24
  @config.include_root = false
20
25
 
21
- @config.site_name = nil
22
- @config.site_description = nil
26
+ @config.allowed_extensions = %w[erb md]
23
27
 
24
- @config.allowed_extensions = [".erb", ".md"]
25
28
  @config.exclude_from_public = %w[assets storage]
26
29
  @config.excluded_assets = %w[action_cable actioncable actiontext activestorage rails-ujs trix turbo]
27
30
 
@@ -31,7 +31,7 @@ module Perron
31
31
  FileUtils.mkdir_p(destination)
32
32
  FileUtils.cp_r(Dir.glob("#{source}/*"), destination)
33
33
 
34
- puts " Copied assets to `#{destination.relative_path_from(Rails.root)}`"
34
+ puts " Copied assets to `#{destination.relative_path_from(Rails.root)}`"
35
35
 
36
36
  prune_excluded_assets from: destination
37
37
  end
@@ -41,7 +41,7 @@ module Perron
41
41
  def prune_excluded_assets(from:)
42
42
  return if exclusions.empty?
43
43
 
44
- puts "Pruning excluded assets…"
44
+ puts " Pruning excluded assets…"
45
45
 
46
46
  pattern = /^(#{exclusions.join("|")})(\.esm|\.min)?-[a-f0-9]{8,}/
47
47
 
@@ -35,7 +35,7 @@ module Perron
35
35
  FileUtils.mkdir_p(directory_path)
36
36
  File.write(file_path, html)
37
37
 
38
- puts "✅ Generated: #{@path} -> #{file_path.relative_path_from(@output_path)}"
38
+ print "\e[32m.\e[0m"
39
39
  end
40
40
 
41
41
  def route_info
@@ -23,7 +23,7 @@ module Perron
23
23
  paths.each do |path|
24
24
  FileUtils.cp_r(path, @output_path)
25
25
 
26
- puts " ✅ Copied: #{File.basename(path)}"
26
+ print "\e[32m.\e[0m"
27
27
  end
28
28
  end
29
29
 
@@ -24,16 +24,14 @@ module Perron
24
24
  Perron::Site::Builder::PublicFiles.new.copy
25
25
  end
26
26
 
27
- puts "🚀 Starting site build…"
28
- puts "-" * 15
27
+ puts "\n📝 Generating collections…"
29
28
 
30
29
  paths.each { render_page(it) }
31
30
 
32
31
  Perron::Site::Builder::Sitemap.new(@output_path).generate
33
32
  Perron::Site::Builder::Feeds.new(@output_path).generate
34
33
 
35
- puts "-" * 15
36
- puts "✅ Build complete"
34
+ puts "\n✅ Build complete"
37
35
  end
38
36
 
39
37
  private
@@ -3,14 +3,44 @@
3
3
  module Perron
4
4
  class Data
5
5
  class Proxy
6
- def method_missing(method_name, *arguments, &block)
7
- raise ArgumentError, "Data `#{method_name}` does not accept arguments" if arguments.any?
6
+ include Enumerable
8
7
 
9
- Perron::Data.new(method_name.to_s)
8
+ def initialize(parts = [])
9
+ @parts = parts
10
+ @data = data_for_proxy
10
11
  end
11
12
 
12
- def respond_to_missing?(method_name, include_private = false)
13
- true
13
+ def each(&block) = @data.each(&block)
14
+
15
+ def inspect = @data.inspect
16
+
17
+ def respond_to_missing?(name, include_private = false)
18
+ identifier = File.join(*@parts, name.to_s)
19
+
20
+ Perron::Data.directory?(identifier) || Perron::Data.path_for(identifier) || super
21
+ end
22
+
23
+ def method_missing(name, *arguments, &block)
24
+ raise ArgumentError, "Data access does not accept arguments" if arguments.any? || block
25
+
26
+ new_parts = @parts + [name.to_s]
27
+ identifier = File.join(*new_parts)
28
+
29
+ return Proxy.new(new_parts) if Perron::Data.directory?(identifier)
30
+ return Perron::Data.new(identifier) if Perron::Data.path_for(identifier)
31
+
32
+ super
33
+ end
34
+
35
+ private
36
+
37
+ def data_for_proxy
38
+ return [] if @parts.empty?
39
+
40
+ identifier = File.join(*@parts)
41
+ data_path = Perron::Data.path_for(identifier) || Perron::Data.path_for(File.join(identifier, "index"))
42
+
43
+ data_path ? Perron::Data.new(data_path) : []
14
44
  end
15
45
  end
16
46
  end
@@ -1,19 +1,36 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "erb"
4
- require "singleton"
5
-
6
3
  require "csv"
7
4
 
8
5
  module Perron
9
6
  class Data < SimpleDelegator
10
7
  def initialize(identifier)
11
- @file_path = path_for(identifier)
8
+ @file_path = self.class.path_for!(identifier)
12
9
  @records = records
13
10
 
14
11
  super(records)
15
12
  end
16
13
 
14
+ class << self
15
+ def path_for(identifier)
16
+ path = Pathname.new(identifier)
17
+
18
+ return path.to_s if path.file? && path.absolute?
19
+
20
+ base_path = Rails.root.join("app", "content", "data")
21
+
22
+ SUPPORTED_EXTENSIONS.lazy.map { base_path.join("#{identifier}#{it}") }.find(&:exist?)&.to_s
23
+ end
24
+
25
+ def path_for!(identifier)
26
+ path_for(identifier).tap do |path|
27
+ raise Errors::FileNotFoundError, "No data file found for `#{identifier}`" unless path
28
+ end
29
+ end
30
+
31
+ def directory?(identifier) = Dir.exist?(Rails.root.join("app", "content", "data", identifier))
32
+ end
33
+
17
34
  private
18
35
 
19
36
  PARSER_METHODS = {
@@ -22,21 +39,12 @@ module Perron
22
39
  }.freeze
23
40
  SUPPORTED_EXTENSIONS = PARSER_METHODS.keys
24
41
 
25
- def path_for(identifier)
26
- path = Pathname.new(identifier)
27
-
28
- return path.to_s if path.file? && path.absolute?
29
-
30
- path = SUPPORTED_EXTENSIONS.lazy.map { Rails.root.join("app", "content", "data").join("#{identifier}#{it}") }.find(&:exist?)
31
- path&.to_s or raise Errors::FileNotFoundError, "No data file found for '#{identifier}'"
32
- end
33
-
34
42
  def records
35
43
  content = rendered_from(@file_path)
36
44
  data = parsed_from(content, @file_path)
37
45
 
38
46
  unless data.is_a?(Array)
39
- raise Errors::DataParseError, "Data in '#{@file_path}' must be an array of objects."
47
+ raise Errors::DataParseError, "Data in `#{@file_path}` must be an array of objects."
40
48
  end
41
49
 
42
50
  data.map { Item.new(it) }
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Perron
4
+ class Resource
5
+ module Renderer
6
+ module_function
7
+
8
+ def erb(content, assigns = {})
9
+ ::ApplicationController
10
+ .renderer
11
+ .render(
12
+ inline: content,
13
+ assigns: assigns
14
+ )
15
+ end
16
+ end
17
+ end
18
+ end
@@ -13,8 +13,12 @@ module Perron
13
13
  end
14
14
 
15
15
  def create
16
- @metadata.slug.presence || @resource.filename.sub(/^[\d-]+-/, "").delete_suffixes(Perron.configuration.allowed_extensions)
16
+ @metadata.slug.presence || @resource.filename.sub(/^[\d-]+-/, "").delete_suffixes(dot_prepended_allowed_extensions)
17
17
  end
18
+
19
+ private
20
+
21
+ def dot_prepended_allowed_extensions = Perron.configuration.allowed_extensions.map { ".#{it}" }
18
22
  end
19
23
  end
20
24
  end
@@ -5,6 +5,7 @@ require "perron/site/resource/core"
5
5
  require "perron/site/resource/class_methods"
6
6
  require "perron/site/resource/publishable"
7
7
  require "perron/site/resource/related"
8
+ require "perron/site/resource/renderer"
8
9
  require "perron/site/resource/slug"
9
10
  require "perron/site/resource/separator"
10
11
 
@@ -33,14 +34,11 @@ module Perron
33
34
  alias_method :to_param, :slug
34
35
 
35
36
  def content
36
- return Perron::Resource::Separator.new(raw_content).content unless processable?
37
-
38
- ::ApplicationController
39
- .renderer
40
- .render(
41
- inline: Perron::Resource::Separator.new(raw_content).content,
42
- assigns: {resource: self}
43
- )
37
+ page_content = Perron::Resource::Separator.new(raw_content).content
38
+
39
+ return page_content unless erb_processing?
40
+
41
+ Perron::Resource::Renderer.erb(page_content, {resource: self})
44
42
  end
45
43
 
46
44
  def metadata = Perron::Resource::Separator.new(raw_content).metadata
@@ -55,7 +53,7 @@ module Perron
55
53
 
56
54
  private
57
55
 
58
- def processable?
56
+ def erb_processing?
59
57
  @file_path.ends_with?(".erb") || metadata.erb == true
60
58
  end
61
59
 
data/lib/perron/site.rb CHANGED
@@ -12,16 +12,6 @@ module Perron
12
12
 
13
13
  def build = Perron::Site::Builder.new.build
14
14
 
15
- def name = Perron.configuration.site_name
16
-
17
- def email = Perron.configuration.site_email
18
-
19
- def url
20
- options = Perron.configuration.default_url_options
21
-
22
- "#{options[:protocol]}://#{options[:host]}"
23
- end
24
-
25
15
  def collections
26
16
  @collections ||= Dir.children(Perron.configuration.input)
27
17
  .select { File.directory?(File.join(Perron.configuration.input, it)) }
@@ -1,3 +1,3 @@
1
1
  module Perron
2
- VERSION = "0.9.1"
2
+ VERSION = "0.10.0"
3
3
  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.9.1
4
+ version: 0.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rails Designer Developers
@@ -80,6 +80,7 @@ files:
80
80
  - Rakefile
81
81
  - app/helpers/feeds_helper.rb
82
82
  - app/helpers/meta_tags_helper.rb
83
+ - app/helpers/perron/erb_helper.rb
83
84
  - app/helpers/perron/markdown_helper.rb
84
85
  - bin/console
85
86
  - bin/rails
@@ -127,6 +128,7 @@ files:
127
128
  - lib/perron/site/resource/publishable.rb
128
129
  - lib/perron/site/resource/related.rb
129
130
  - lib/perron/site/resource/related/stop_words.rb
131
+ - lib/perron/site/resource/renderer.rb
130
132
  - lib/perron/site/resource/separator.rb
131
133
  - lib/perron/site/resource/slug.rb
132
134
  - lib/perron/tasks/perron.rake