Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ javascript/packages/core/src/action-view-helpers.ts
javascript/packages/core/src/errors.ts
javascript/packages/core/src/html-entities.json
javascript/packages/core/src/node-type-guards.ts
javascript/packages/core/src/config.ts
javascript/packages/core/src/nodes.ts
javascript/packages/core/src/visitor.ts
javascript/packages/node/extension/error_helpers.cpp
Expand All @@ -116,6 +117,7 @@ lib/herb/errors.rb
lib/herb/visitor.rb
rust/src/action_view_helpers.rs
rust/src/ast/nodes.rs
rust/src/config.rs
rust/src/errors.rs
rust/src/nodes.rs
rust/src/union_types.rs
Expand Down
14 changes: 14 additions & 0 deletions config/options.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
framework:
default: ruby
values:
- ruby
- actionview
- hanami
- sinatra

template_engine:
default: erubi
values:
- erubi
- erb
- herb
17 changes: 17 additions & 0 deletions javascript/packages/config/src/config-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,29 @@ export const ValidatorsConfigSchema = z.object({
accessibility: z.boolean().optional().describe("Enable or disable the accessibility validator (default: true)"),
}).strict().optional()

export const FrameworkSchema = z.enum(["ruby", "actionview", "hanami", "sinatra"]).optional()
.describe("Framework context (default: 'ruby')")

export const TemplateEngineSchema = z.enum(["erubi", "erb", "herb"]).optional()
.describe("Template engine used for compilation (default: 'erubi')")

export const ParserOptionsSchema = z.object({
strict: z.boolean().optional().describe("Enable strict parsing mode (default: true)"),
render_nodes: z.boolean().optional().describe("Enable render node detection"),
strict_locals: z.boolean().optional().describe("Enable strict locals detection"),
}).strict().optional()

export const EngineConfigSchema = z.object({
optimize: z.boolean().optional().describe("Enable compile-time optimizations (default: false)"),
debug: z.boolean().optional().describe("Enable debug mode (default: false)"),
parser_options: ParserOptionsSchema.describe("Parser options passed through to Herb.parse"),
validators: ValidatorsConfigSchema.describe("Per-validator enable/disable configuration"),
}).strict().optional()

export const HerbConfigSchema = z.object({
version: z.string().describe("Configuration file version"),
framework: FrameworkSchema,
template_engine: TemplateEngineSchema,
files: FilesConfigSchema.describe("Top-level file configuration"),
engine: EngineConfigSchema.describe("Engine configuration"),
linter: LinterConfigSchema,
Expand Down
8 changes: 8 additions & 0 deletions javascript/packages/config/src/config-template.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,14 @@

version: 0.9.7

# # Framework and template engine configuration
#
# # Options: ruby | actionview | hanami | sinatra (default: ruby)
# framework: ruby
#
# # Options: erubi | erb | herb (default: erubi)
# template_engine: erubi

# files:
# # Additional patterns beyond the defaults (**.html, **.rhtml, **.html.erb, etc.)
# include:
Expand Down
1 change: 1 addition & 0 deletions javascript/packages/core/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from "./action-view-helpers.js"
export * from "./config.js"
export * from "./ast-utils.js"
export * from "./html-constants.js"
export * from "./html-character-references.js"
Expand Down
30 changes: 30 additions & 0 deletions lib/herb/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@

module Herb
class Configuration
OPTIONS_PATH = File.expand_path("../../config/options.yml", __dir__ || __FILE__).freeze #: String
OPTIONS = YAML.safe_load_file(OPTIONS_PATH).freeze #: Hash[String, untyped]

VALID_FRAMEWORKS = OPTIONS["framework"]["values"].freeze #: Array[String]
VALID_TEMPLATE_ENGINES = OPTIONS["template_engine"]["values"].freeze #: Array[String]

CONFIG_FILENAMES = [".herb.yml"].freeze

PROJECT_INDICATORS = [
Expand Down Expand Up @@ -42,6 +48,30 @@ def version
@config["version"]
end

#: () -> String
def framework
value = @config["framework"] || "ruby"

unless VALID_FRAMEWORKS.include?(value)
warn "[Herb] Unknown framework: #{value.inspect}. Valid values: #{VALID_FRAMEWORKS.join(", ")}. Defaulting to 'ruby'."
return "ruby"
end

value
end

#: () -> String
def template_engine
value = @config["template_engine"] || "erubi"

unless VALID_TEMPLATE_ENGINES.include?(value)
warn "[Herb] Unknown template_engine: #{value.inspect}. Valid values: #{VALID_TEMPLATE_ENGINES.join(", ")}. Defaulting to 'erubi'."
return "erubi"
end

value
end

def files
@config["files"] || {}
end
Expand Down
3 changes: 3 additions & 0 deletions lib/herb/defaults.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
framework: ruby
template_engine: erubi

files:
include:
- "**/*.herb"
Expand Down
14 changes: 14 additions & 0 deletions sig/herb/configuration.rbs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

43 changes: 43 additions & 0 deletions templates/javascript/packages/core/src/config.ts.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<%
options = YAML.safe_load_file("config/options.yml")

def camel_case(string)
parts = string.split("_")
parts[0] + parts[1..].map(&:capitalize).join
end

def pascal_case(string)
string.split("_").map(&:capitalize).join
end
-%>

<% options.each do |key, config| -%>
export type <%= pascal_case(key) %> = <%= config["values"].map { |v| "\"#{v}\"" }.join(" | ") %>
<% end -%>

<% options.each do |key, config| -%>
export const VALID_<%= key.upcase %>S: readonly <%= pascal_case(key) %>[] = [<%= config["values"].map { |v| "\"#{v}\"" }.join(", ") %>] as const
<% end -%>

<% options.each do |key, config| -%>
export const DEFAULT_<%= key.upcase %>: <%= pascal_case(key) %> = "<%= config["default"] %>"
<% end -%>

export interface HerbConfig {
<% options.each do |key, _config| -%>
<%= camel_case(key) %>: <%= pascal_case(key) %>
<% end -%>
}

export const DEFAULT_CONFIG: HerbConfig = {
<% options.each do |key, _config| -%>
<%= camel_case(key) %>: DEFAULT_<%= key.upcase %>,
<% end -%>
}

<% options.each do |key, _config| -%>
export function isValid<%= pascal_case(key) %>(value: string): value is <%= pascal_case(key) %> {
return (VALID_<%= key.upcase %>S as readonly string[]).includes(value)
}

<% end -%>
50 changes: 50 additions & 0 deletions templates/rust/src/config.rs.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<%
options = YAML.safe_load_file("config/options.yml")

def pascal_case(string)
string.split("_").map(&:capitalize).join
end

def title_case(string)
string.split("_").map(&:capitalize).join("")
end
-%>

use std::fmt;

<% options.each do |key, config| -%>
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum <%= pascal_case(key) %> {
<% config["values"].each do |value| -%>
<%= title_case(value) %>,
<% end -%>
}

impl Default for <%= pascal_case(key) %> {
fn default() -> Self {
<%= pascal_case(key) %>::<%= title_case(config["default"]) %>
}
}

impl fmt::Display for <%= pascal_case(key) %> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
<% config["values"].each do |value| -%>
<%= pascal_case(key) %>::<%= title_case(value) %> => write!(f, "<%= value %>"),
<% end -%>
}
}
}

impl <%= pascal_case(key) %> {
pub fn from_str(string: &str) -> Option<Self> {
match string {
<% config["values"].each do |value| -%>
"<%= value %>" => Some(<%= pascal_case(key) %>::<%= title_case(value) %>),
<% end -%>
_ => None,
}
}
}

<% end -%>
76 changes: 76 additions & 0 deletions test/configuration_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -690,4 +690,80 @@ def write_config(content, filename = ".herb.yml")

assert_equal false, engine.debug
end

test "framework defaults to ruby" do
config = Herb::Configuration.load(@temp_dir)

assert_equal "ruby", config.framework
end

test "framework reads from config file" do
write_config(<<~YAML)
framework: actionview
YAML

config = Herb::Configuration.load(@temp_dir)

assert_equal "actionview", config.framework
end

test "framework warns on invalid value and defaults to ruby" do
write_config(<<~YAML)
framework: invalid
YAML

config = Herb::Configuration.load(@temp_dir)

assert_output(nil, /Unknown framework/) do
assert_equal "ruby", config.framework
end
end

test "framework accepts all valid values" do
Herb::Configuration::VALID_FRAMEWORKS.each do |framework|
write_config("framework: #{framework}")

config = Herb::Configuration.load(@temp_dir)

assert_equal framework, config.framework
end
end

test "template_engine defaults to erubi" do
config = Herb::Configuration.load(@temp_dir)

assert_equal "erubi", config.template_engine
end

test "template_engine reads from config file" do
write_config(<<~YAML)
template_engine: herb
YAML

config = Herb::Configuration.load(@temp_dir)

assert_equal "herb", config.template_engine
end

test "template_engine warns on invalid value and defaults to erubi" do
write_config(<<~YAML)
template_engine: invalid
YAML

config = Herb::Configuration.load(@temp_dir)

assert_output(nil, /Unknown template_engine/) do
assert_equal "erubi", config.template_engine
end
end

test "template_engine accepts all valid values" do
Herb::Configuration::VALID_TEMPLATE_ENGINES.each do |engine|
write_config("template_engine: #{engine}")

config = Herb::Configuration.load(@temp_dir)

assert_equal engine, config.template_engine
end
end
end
Loading