Packages
Multi-surface application runtime for Elixir. One TEA module renders to terminal, browser (LiveView), SSH, and MCP (agents). 30+ widgets, flexbox + CSS grid, AI agent runtime, distributed swarm with CRDTs, time-travel debugging, session recording, sandboxed REPL, and agentic commerce.
Current section
Files
Jump to
Current section
Files
lib/raxol/config/generator.ex
defmodule Raxol.Config.Generator do
@moduledoc """
Generates configuration files and templates.
Provides utilities for creating default configurations, generating
configuration templates, and creating environment-specific configs.
"""
alias Raxol.Config.Schema
@doc """
Generates a default configuration file.
"""
def generate_default_config(path, opts \\ []) do
format = Keyword.get(opts, :format, detect_format_from_path(path))
include_comments = Keyword.get(opts, :comments, true)
include_examples = Keyword.get(opts, :examples, false)
config = build_default_config(include_comments, include_examples)
case write_config_file(config, path, format, opts) do
:ok ->
{:ok, path}
{:error, reason} ->
{:error, {:generation_failed, reason}}
end
end
@doc """
Generates environment-specific configuration.
"""
def generate_env_config(env, path, opts \\ []) do
config =
case env do
:development -> development_config()
:production -> production_config()
:test -> test_config()
_ -> %{}
end
merged_config = Map.merge(base_config(), config)
write_config_file(merged_config, path, detect_format_from_path(path), opts)
end
@doc """
Generates a configuration template with all possible options.
"""
def generate_template(path, opts \\ []) do
template = build_template_from_schema(Schema.schema())
write_config_file(template, path, detect_format_from_path(path), opts)
end
@doc """
Generates documentation for configuration options.
"""
def generate_config_docs(output_path \\ "docs/configuration.md") do
docs = Schema.generate_docs()
full_content = """
# Raxol Configuration Reference
This document describes all available configuration options for Raxol.
## Configuration Sources
Raxol loads configuration from multiple sources in the following order of precedence:
1. Runtime configuration (highest priority)
2. Environment variables with `RAXOL_` prefix
3. Configuration files:
- `config/raxol.toml`
- `config/raxol.json`
- `~/.raxol/config.toml`
- `~/.raxol/config.json`
- `/etc/raxol/config.toml`
4. Application environment
5. Default values (lowest priority)
## Environment Variables
Configuration can also be set via environment variables using the `RAXOL_` prefix.
Nested configuration is specified using double underscores (`__`).
Examples:
```bash
export RAXOL_TERMINAL__WIDTH=120
export RAXOL_TERMINAL__HEIGHT=40
export RAXOL_THEME__NAME=dark
```
## Configuration Options
#{docs}
## Example Configuration Files
### TOML Format (config/raxol.toml)
```toml
#{generate_example_toml()}
```
### JSON Format (config/raxol.json)
```json
#{generate_example_json()}
```
"""
File.mkdir_p!(Path.dirname(output_path))
File.write!(output_path, full_content)
{:ok, output_path}
end
@doc """
Validates configuration template against schema.
"""
def validate_template(template_path) do
with {:ok, config} <- Raxol.Config.Loader.load_file(template_path),
{:ok, :valid} <- Schema.validate_config(config) do
{:ok, :valid}
else
{:error, reason} ->
{:error, {:validation_failed, reason}}
end
end
@doc """
Creates a minimal configuration for quick setup.
"""
def generate_minimal_config(path) do
minimal = %{
terminal: %{
width: 80,
height: 24
},
theme: %{
name: "default"
},
logging: %{
level: :info,
file: "logs/raxol.log"
}
}
write_config_file(minimal, path, detect_format_from_path(path), [])
end
# Private functions - Pattern Matching Helpers
defp maybe_add_metadata(
true,
config,
schema,
include_comments,
include_examples
) do
add_metadata_to_config(config, schema, include_comments, include_examples)
end
defp maybe_add_metadata(
false,
config,
_schema,
_include_comments,
_include_examples
),
do: config
defp handle_schema_value(true, key, value, acc) do
Map.put(acc, key, value.default)
end
defp handle_schema_value(false, key, value, acc) do
nested_defaults = extract_defaults_from_schema(value)
handle_nested_defaults(
map_size(nested_defaults) > 0,
key,
nested_defaults,
acc
)
end
defp handle_nested_defaults(true, key, nested_defaults, acc) do
Map.put(acc, key, nested_defaults)
end
defp handle_nested_defaults(false, _key, _nested_defaults, acc), do: acc
defp handle_template_value(true, key, value, acc) do
# This is a field
template_value = generate_template_value(value)
Map.put(acc, key, template_value)
end
defp handle_template_value(false, key, value, acc) do
# This is a nested section
nested_template = extract_template_from_schema(value)
Map.put(acc, key, nested_template)
end
# Private functions
defp build_default_config(include_comments, include_examples) do
schema = Schema.schema()
config = extract_defaults_from_schema(schema)
maybe_add_metadata(
include_comments or include_examples,
config,
schema,
include_comments,
include_examples
)
end
defp extract_defaults_from_schema(schema) when is_map(schema) do
Enum.reduce(schema, %{}, fn {key, value}, acc ->
handle_schema_value(Map.has_key?(value, :default), key, value, acc)
end)
end
defp add_metadata_to_config(
config,
_schema,
_include_comments,
_include_examples
) do
# This would add comments and examples to the configuration
# For now, return the config as-is
config
end
defp base_config do
%{
terminal: %{
width: 80,
height: 24,
scrollback_size: 10_000,
encoding: "UTF-8"
},
buffer: %{
max_size: 1_048_576,
chunk_size: 4096
},
rendering: %{
fps_target: 60,
enable_animations: true
},
theme: %{
name: "default"
}
}
end
def development_config do
%{
logging: %{
level: :debug,
file: "logs/dev.log"
},
plugins: %{
auto_reload: true
},
performance: %{
profiling_enabled: true
},
rendering: %{
performance_mode: false
}
}
end
def production_config do
%{
logging: %{
level: :info,
file: "logs/prod.log",
# 50MB
max_file_size: 52_428_800,
rotation_count: 10
},
performance: %{
cache_size: 1_000_000,
profiling_enabled: false
},
rendering: %{
performance_mode: true,
max_frame_skip: 5
},
security: %{
# 1 hour
session_timeout: 3600,
enable_audit: true
}
}
end
defp test_config do
%{
logging: %{
level: :warning,
file: "logs/test.log"
},
buffer: %{
# Smaller for tests
max_size: 65_536
},
rendering: %{
enable_animations: false
},
performance: %{
cache_size: 1000
}
}
end
defp build_template_from_schema(schema) do
# Build a comprehensive template showing all options
extract_template_from_schema(schema)
end
defp extract_template_from_schema(schema) when is_map(schema) do
Enum.reduce(schema, %{}, fn {key, value}, acc ->
handle_template_value(Map.has_key?(value, :type), key, value, acc)
end)
end
defp generate_template_value(field_schema) do
field_schema.default || type_default(field_schema.type)
end
defp type_default(:string), do: "example_string"
defp type_default(:integer), do: 42
defp type_default(:float), do: 3.14
defp type_default(:boolean), do: true
defp type_default(:atom), do: :example
defp type_default({:enum, values}), do: hd(values)
defp type_default({:list, _}), do: []
defp type_default({:map, _}), do: %{}
defp type_default(_), do: nil
defp write_config_file(config, path, format, opts) do
ensure_directory(path)
case format do
:toml -> write_toml_file(config, path, opts)
:json -> write_json_file(config, path, opts)
:yaml -> write_yaml_file(config, path, opts)
_ -> {:error, {:unsupported_format, format}}
end
end
defp write_toml_file(config, path, _opts) do
# Simplified TOML generation
content = generate_toml_content(config, 0)
File.write(path, content)
end
defp write_json_file(config, path, opts) do
pretty = Keyword.get(opts, :pretty, true)
case Jason.encode(Raxol.Utils.MapUtils.stringify_keys(config),
pretty: pretty
) do
{:ok, content} -> File.write(path, content)
{:error, reason} -> {:error, reason}
end
end
defp write_yaml_file(config, path, _opts) do
# Would need a YAML encoder
content =
"# YAML format not yet implemented\n" <> inspect(config, pretty: true)
File.write(path, content)
end
defp generate_toml_content(config, indent_level) do
indent = String.duplicate(" ", indent_level)
Enum.map_join(config, "\n", fn {key, value} ->
format_toml_entry(indent, key, value, indent_level)
end)
end
defp format_toml_entry(indent, key, value, indent_level) when is_map(value) do
"#{indent}[#{key}]\n" <> generate_toml_content(value, indent_level + 1)
end
defp format_toml_entry(indent, key, value, _indent_level)
when is_binary(value),
do: "#{indent}#{key} = \"#{value}\""
defp format_toml_entry(indent, key, value, _indent_level)
when is_boolean(value),
do: "#{indent}#{key} = #{value}"
defp format_toml_entry(indent, key, value, _indent_level)
when is_atom(value),
do: "#{indent}#{key} = \"#{value}\""
defp format_toml_entry(indent, key, value, _indent_level)
when is_number(value),
do: "#{indent}#{key} = #{value}"
defp format_toml_entry(indent, key, value, _indent_level)
when is_list(value) do
list_str = Enum.map_join(value, ", ", &inspect/1)
"#{indent}#{key} = [#{list_str}]"
end
defp format_toml_entry(indent, key, value, _indent_level),
do: "#{indent}#{key} = #{inspect(value)}"
defp generate_example_toml do
example_config = %{
terminal: %{
width: 120,
height: 40,
scrollback_size: 50_000
},
theme: %{
name: "dark",
auto_switch: true
},
logging: %{
level: "info",
file: "logs/raxol.log"
}
}
generate_toml_content(example_config, 0)
end
defp generate_example_json do
example_config = %{
"terminal" => %{
"width" => 120,
"height" => 40,
"scrollback_size" => 50_000
},
"theme" => %{
"name" => "dark",
"auto_switch" => true
},
"logging" => %{
"level" => "info",
"file" => "logs/raxol.log"
}
}
case Jason.encode(example_config, pretty: true) do
{:ok, json} -> json
_ -> "{}"
end
end
defp detect_format_from_path(path) do
case String.downcase(Path.extname(path)) do
".toml" -> :toml
".json" -> :json
".yaml" -> :yaml
".yml" -> :yaml
# Default to TOML
_ -> :toml
end
end
defp ensure_directory(path) do
path
|> Path.dirname()
|> File.mkdir_p!()
end
end