chore: add license and scripts installers

This commit is contained in:
Jesús Pérez 2025-12-18 01:18:34 +00:00
parent 5a459e7f02
commit f6023b5ffc
Signed by: jesus
GPG Key ID: 9F243E355E0BC939
21 changed files with 3063 additions and 0 deletions

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 Jesús Pérez
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

231
LICENSE.md Normal file
View File

@ -0,0 +1,231 @@
# TypeDialog License
## Project License
TypeDialog is licensed under the **MIT License**.
See [LICENSE](LICENSE) file for the full MIT license text.
---
## Dependencies
This project includes the following dependencies under their respective licenses:
### Apache-2.0 Only (3)
- rpassword 7.4.0
- rtoolbox 0.0.3
- sync_wrapper 1.0.2
### MIT Only (66)
- atty 0.2.14
- axum 0.8.7
- axum-core 0.5.5
- bytes 1.11.0
- castaway 0.2.4
- compact_str 0.8.1
- console 0.16.2
- convert_case 0.10.0
- crossterm 0.28.1
- crossterm 0.29.0
- crossterm_winapi 0.9.1
- darling 0.20.11
- darling_core 0.20.11
- darling_macro 0.20.11
- derive_more 2.1.0
- derive_more-impl 2.1.0
- dialoguer 0.12.0
- typedialog 0.1.0
- typedialog-core 0.1.0
- typedialog-tui 0.1.0
- typedialog-web 0.1.0
- fuzzy-matcher 0.3.7
- generic-array 0.14.7
- globwalk 0.9.1
- http-body 1.0.1
- http-body-util 0.1.3
- http-range-header 0.4.2
- hyper 1.8.1
- hyper-util 0.1.19
- inquire 0.9.1
- instability 0.3.10
- libm 0.2.15
- libredox 0.1.10
- lru 0.12.5
- mime_guess 2.0.5
- mio 1.1.1
- nu-ansi-term 0.50.3
- parse-zoneinfo 0.3.1
- phf 0.11.3
- phf_codegen 0.11.3
- phf_generator 0.11.3
- phf_shared 0.11.3
- ratatui 0.29.0
- redox_syscall 0.5.18
- redox_users 0.5.2
- sharded-slab 0.1.7
- slab 0.4.11
- strsim 0.11.1
- strum 0.26.3
- strum_macros 0.26.4
- tera 1.20.1
- tokio 1.48.0
- tokio-macros 2.6.0
- tokio-util 0.7.17
- tower 0.5.2
- tower-http 0.6.8
- tower-layer 0.3.3
- tower-service 0.3.3
- tracing 0.1.43
- tracing-attributes 0.1.31
- tracing-core 0.1.35
- tracing-log 0.2.0
- tracing-subscriber 0.3.22
- unsafe-libyaml 0.2.11
- valuable 0.1.1
- winnow 0.7.14
### Apache-2.0 OR MIT (190)
Most dependencies use dual licensing between Apache-2.0 and MIT.
- allocator-api2 0.2.21 | - android_system_properties 0.1.5 | - anstream 0.6.21
- anstyle 1.0.13 | - anstyle-parse 0.2.7 | - anstyle-query 1.1.5
- anstyle-wincon 3.0.11 | - anyhow 1.0.100 | - async-trait 0.1.89
- atomic-waker 1.1.2 | - autocfg 1.5.0 | - bitflags 2.10.0
- block-buffer 0.10.4 | - bstr 1.12.1 | - bumpalo 3.19.0
- cassowary 0.3.0 | - cc 1.2.49 | - cfg-if 1.0.4
- chrono 0.4.42 | - chrono-tz 0.9.0 | - chrono-tz-build 0.3.0
- clap 4.5.53 | - clap_builder 4.5.53 | - clap_derive 4.5.49
- clap_lex 0.7.6 | - colorchoice 1.0.4 | - core-foundation-sys 0.8.7
- cpufeatures 0.2.17 | - crossbeam-deque 0.8.6 | - crossbeam-epoch 0.9.18
- crossbeam-utils 0.8.21 | - crypto-common 0.1.7 | - digest 0.10.7
- dirs 6.0.0 | - dirs-sys 0.5.0 | - displaydoc 0.2.5
- document-features 0.2.12 | - dyn-clone 1.0.20 | - either 1.15.0
- encode_unicode 1.0.0 | - equivalent 1.0.2 | - errno 0.3.14
- fastrand 2.3.0 | - find-msvc-tools 0.1.5 | - fluent 0.17.0
- fluent-bundle 0.16.0 | - fluent-langneg 0.13.1 | - fluent-syntax 0.12.0
- fnv 1.0.7 | - form_urlencoded 1.2.2 | - futures 0.3.31
- futures-channel 0.3.31 | - futures-core 0.3.31 | - futures-executor 0.3.31
- futures-io 0.3.31 | - futures-macro 0.3.31 | - futures-sink 0.3.31
- futures-task 0.3.31 | - futures-util 0.3.31 | - getrandom 0.2.16
- getrandom 0.3.4 | - hashbrown 0.15.5 | - hashbrown 0.16.1
- heck 0.5.0 | - hermit-abi 0.1.19 | - http 1.4.0
- httparse 1.10.1 | - httpdate 1.0.3 | - humansize 2.1.3
- iana-time-zone 0.1.64 | - iana-time-zone-haiku 0.1.2 | - ident_case 1.0.1
- indexmap 2.12.1 | - indoc 2.0.7 | - intl-memoizer 0.5.3
- intl_pluralrules 7.0.2 | - is_terminal_polyfill 1.70.2 | - itertools 0.13.0
- itoa 1.0.15 | - js-sys 0.3.83 | - lazy_static 1.5.0
- libc 0.2.178 | - litrs 1.0.0 | - lock_api 0.4.14
- log 0.4.29 | - mime 0.3.17 | - num-traits 0.2.19
- once_cell 1.21.3 | - once_cell_polyfill 1.70.2 | - parking_lot 0.12.5
- parking_lot_core 0.9.12 | - paste 1.0.15 | - percent-encoding 2.3.2
- pest 2.8.4 | - pest_derive 2.8.4 | - pest_generator 2.8.4
- pest_meta 2.8.4 | - pin-project-lite 0.2.16 | - pin-utils 0.1.0
- ppv-lite86 0.2.21 | - proc-macro2 1.0.103 | - quote 1.0.42
- rand 0.8.5 | - rand_chacha 0.3.1 | - rand_core 0.6.4
- regex 1.12.2 | - regex-automata 0.4.13 | - regex-syntax 0.8.8
- rustc-hash 2.1.1 | - rustc_version 0.4.1 | - rustversion 1.0.22
- scopeguard 1.2.0 | - semver 1.0.27 | - serde 1.0.228
- serde_core 1.0.228 | - serde_derive 1.0.228 | - serde_json 1.0.145
- serde_path_to_error 0.1.20 | - serde_spanned 1.0.3 | - serde_urlencoded 0.7.1
- serde_yaml 0.9.34+deprecated | - sha2 0.10.9 | - shell-words 1.1.0
- shlex 1.3.0 | - signal-hook 0.3.18 | - signal-hook-mio 0.2.5
- signal-hook-registry 1.4.7 | - siphasher 1.0.1 | - slug 0.1.6
- smallvec 1.15.1 | - socket2 0.6.1 | - static_assertions 1.1.0
- syn 2.0.111 | - sys-locale 0.3.2 | - tempfile 3.23.0
- thiserror 2.0.17 | - thiserror-impl 2.0.17 | - thread_local 1.1.9
- toml 0.9.8 | - toml_datetime 0.7.3 | - toml_parser 1.0.4
- toml_writer 1.0.4 | - type-map 0.5.1 | - typenum 1.19.0
- ucd-trie 0.1.7 | - unic-langid 0.9.6 | - unic-langid-impl 0.9.6
- unicase 2.8.1 | - unicode-segmentation 1.12.0 | - unicode-truncate 1.1.0
- unicode-width 0.1.14 | - unicode-width 0.2.0 | - utf8parse 0.2.2
- version_check 0.9.5 | - wasm-bindgen 0.2.106 | - wasm-bindgen-macro 0.2.106
- wasm-bindgen-macro-support 0.2.106 | - wasm-bindgen-shared 0.2.106 | - winapi 0.3.9
- winapi-i686-pc-windows-gnu 0.4.0 | - winapi-x86_64-pc-windows-gnu 0.4.0 | - windows-core 0.62.2
- windows-implement 0.60.2 | - windows-interface 0.59.3 | - windows-link 0.2.1
- windows-result 0.4.1 | - windows-strings 0.5.1 | - windows-sys 0.52.0
- windows-sys 0.59.0 | - windows-sys 0.60.2 | - windows-sys 0.61.2
- windows-targets 0.52.6 | - windows-targets 0.53.5 | - windows_aarch64_gnullvm 0.52.6
- windows_aarch64_gnullvm 0.53.1 | - windows_aarch64_msvc 0.52.6 | - windows_aarch64_msvc 0.53.1
- windows_i686_gnu 0.52.6 | - windows_i686_gnu 0.53.1 | - windows_i686_gnullvm 0.52.6
- windows_i686_gnullvm 0.53.1 | - windows_i686_msvc 0.52.6 | - windows_i686_msvc 0.53.1
- windows_x86_64_gnu 0.52.6 | - windows_x86_64_gnu 0.53.1 | - windows_x86_64_gnullvm 0.52.6
- windows_x86_64_gnullvm 0.53.1 | - windows_x86_64_msvc 0.52.6 | - windows_x86_64_msvc 0.53.1
- zeroize 1.8.2 |
### MIT OR Unlicense (7)
- aho-corasick 1.1.4
- globset 0.4.18
- ignore 0.4.25
- memchr 2.7.6
- same-file 1.0.6
- walkdir 2.5.0
- winapi-util 0.1.11
### Other Licenses
**(Apache-2.0 OR MIT) AND Unicode-3.0** (1)
- unicode-ident 1.0.22
**Apache-2.0 OR Apache-2.0 WITH LLVM-exception OR MIT** (7)
- linux-raw-sys 0.4.15
- linux-raw-sys 0.11.0
- rustix 0.38.44
- rustix 1.1.2
- wasi 0.11.1+wasi-snapshot-preview1
- wasip2 1.0.1+wasi-0.2.4
- wit-bindgen 0.46.0
**Apache-2.0 OR BSD-2-Clause OR MIT** (2)
- zerocopy 0.8.31
- zerocopy-derive 0.8.31
**Apache-2.0 OR BSL-1.0** (1)
- ryu 1.0.20
**Apache-2.0 OR GPL-2.0** (1)
- self_cell 1.2.1
**Apache-2.0 OR LGPL-2.1-or-later OR MIT** (1)
- r-efi 5.3.0
**BSD-3-Clause** (1)
- deunicode 1.6.2
**BSD-3-Clause AND MIT** (1)
- matchit 0.8.4
**MPL-2.0** (1)
- option-ext 0.2.0
**Unicode-3.0** (3)
- tinystr 0.8.2
- zerofrom 0.1.6
- zerovec 0.11.5
**Zlib** (1)
- foldhash 0.1.5
---
## Summary
- **Project License**: MIT
- **Total Dependencies**: 286
- **Unique License Types**: 15 different combinations
- **Primary License Pattern**: Apache-2.0 OR MIT (most dependencies)
### Compliance
All dependencies are compatible with the MIT license under:
- Permissive licenses (MIT, Apache-2.0, BSD-3-Clause, MPL-2.0, Zlib)
- Weak copyleft (LGPL-2.1-or-later, MPL-2.0)
- Public domain (Unlicense, Unicode-3.0)
### Generated
- Date: 2025-12-17T13:08:58.510231
- Tool: cargo-license
See [DEPENDENCIES.md](DEPENDENCIES.md) for the complete dependency tree.

358
config/README.md Normal file
View File

@ -0,0 +1,358 @@
# Configuration Files
Pre-configured settings for each typedialog backend and environment.
## Overview
Configuration files are organized by **backend** (CLI, TUI, Web) and **environment** (default, dev, production).
```
config/
├── cli/
│ ├── default.toml # Standard CLI settings
│ ├── dev.toml # Development (debugging enabled)
│ └── production.toml # Production (optimized, hardened)
├── tui/
│ ├── default.toml # Standard TUI settings
│ ├── dev.toml # Development features enabled
│ └── production.toml # Optimized for deployment
└── web/
├── default.toml # Standard web server settings
├── dev.toml # Development (hot reload)
└── production.toml # Hardened for production
```
## Backend Configurations
### CLI Backend
**command-line interface** - Simple text-based forms for scripts and automation.
| Config | Purpose | Debug | Colors | Timeout |
|--------|---------|-------|--------|---------|
| default | Standard | No | Yes | 300s |
| dev | Development | Yes | Yes | 300s |
| production | Production | No | Yes | 3600s |
**Usage:**
```bash
typedialog --config config/cli/production.toml form.toml
```
**Features:**
- Inline validation
- Optional mouse support
- Placeholder text display
- Help text display
### TUI Backend
**terminal user interface** - Interactive multi-panel forms with navigation.
| Config | Purpose | Borders | Animations | Render |
|--------|---------|---------|-----------|--------|
| default | Standard | Rounded | Enabled | Auto |
| dev | Development | Double | Enabled | Debug |
| production | Production | Rounded | Disabled | Optimized |
**Usage:**
```bash
typedialog-tui --config config/tui/production.toml form.toml
```
**Features:**
- 3-panel layout (fields, input, buttons)
- Mouse support
- Keyboard navigation
- Real-time field updates
### Web Backend
**HTTP server** - Browser-based forms with REST API.
| Config | Purpose | CORS | HTTPS | Rate Limit |
|--------|---------|------|-------|-----------|
| default | Standard | Localhost | No | Unlimited |
| dev | Development | Enabled | No | Unlimited |
| production | Production | Restricted | Required | 100/min |
**Usage:**
```bash
typedialog-web --config config/web/production.toml
# Server starts on http://localhost:8080
```
**Features:**
- HTML/CSS rendering
- CSRF protection
- Response caching
- Gzip compression
## Configuration by Environment
### Development Configuration
Enabled features for development and debugging:
```toml
# CLI Dev
debug_output = true
log_level = "debug"
show_field_types = true
# TUI Dev
show_field_indices = true
trace_rendering = false
# Web Dev
hot_reload = true
debug = true
logs = "/tmp/typedialog-web.log"
```
**Usage:**
```bash
typedialog --config config/cli/dev.toml form.toml
typedialog-tui --config config/tui/dev.toml form.toml
typedialog-web --config config/web/dev.toml
```
### Production Configuration
Hardened settings optimized for deployment:
```toml
# CLI Production
debug_output = false
log_level = "error"
show_placeholders = false
timeout = 3600
# TUI Production
enable_animations = false
render_throttle = 16ms
max_fps = 60
# Web Production
require_https = true
csrf_enabled = true
rate_limit = 100
cache_ttl = 3600
```
**Usage:**
```bash
typedialog --config config/cli/production.toml form.toml
typedialog-tui --config config/tui/production.toml form.toml
typedialog-web --config config/web/production.toml
```
## Common Settings
### Form Configuration
```toml
[form]
title = "Form Title"
description = "Optional description"
[form.validation]
validate_on_change = true
show_errors_inline = true
strict_validation = true
```
### Output Configuration
```toml
[output]
format = "json" # json, yaml, toml, text
pretty_print = true
debug_output = false
```
### Logging
```toml
[logging]
level = "info" # debug, info, warn, error
file = "/var/log/typedialog/app.log"
```
## Custom Configuration
### Creating Custom Configurations
Create a new TOML file based on an environment template:
```bash
# Copy production config as base
cp config/cli/production.toml config/cli/custom.toml
# Edit for your needs
nano config/cli/custom.toml
# Use it
typedialog --config config/cli/custom.toml form.toml
```
### Override Specific Settings
Use environment variables to override config:
```bash
# CLI Backend
export TYPEDIALOG_DEBUG=1
export TYPEDIALOG_LOG_LEVEL=debug
typedialog --config config/cli/default.toml form.toml
# TUI Backend
export TYPEDIALOG_TUI_BORDER=double
typedialog-tui --config config/tui/default.toml form.toml
# Web Backend
export TYPEDIALOG_WEB_PORT=3000
export TYPEDIALOG_WEB_CORS_ORIGINS="localhost,example.com"
typedialog-web --config config/web/default.toml
```
## CLI Backend Configuration Details
```toml
[terminal]
use_raw_mode = true # Enable raw terminal mode
enable_mouse = false # Mouse support
use_color = true # Colored output
[appearance]
theme = "default" # Color theme
show_help = true # Display field help
show_placeholders = true # Show placeholder text
[validation]
validate_on_change = true # Real-time validation
show_errors_inline = true # Inline error messages
[timeout]
max_duration = 3600 # Max form time (seconds)
input_timeout = 300 # Field input timeout
```
## TUI Backend Configuration Details
```toml
[terminal]
use_raw_mode = true
enable_mouse = true
enable_scrolling = true
height = -1 # -1 = auto
width = -1 # -1 = auto
[ui]
show_borders = true
show_focus = true
highlight_on_hover = true
enable_animations = true
[appearance]
theme = "default"
border_style = "rounded" # rounded, double, simple
color_scheme = "default"
[keyboard]
vi_mode = false
emacs_mode = false
[performance]
render_throttle = 16 # milliseconds
max_fps = 60 # frames per second
```
## Web Backend Configuration Details
```toml
[server]
host = "0.0.0.0"
port = 8080
workers = 4
[html]
css_framework = "none" # bootstrap, tailwind, none
inline_styles = false
responsive = true
dark_mode = true
[submission]
method = "post"
webhook_url = "https://api.example.com/forms"
redirect_on_success = true
redirect_url = "https://example.com/thank-you"
[security]
csrf_enabled = true
rate_limit = 100 # requests per minute
require_https = true
add_security_headers = true
[performance]
cache_static = true
cache_ttl = 3600
enable_compression = true
compression_threshold = 1024
```
## Distribution Configurations
When creating release distributions, all configurations are included:
```bash
# Build and package
just distro::build-release
just distro::create-package
# Result includes all configs
distribution/typedialog-0.1.0/
├── config/
│ ├── cli/
│ ├── tui/
│ └── web/
└── ...
```
Users can then choose configs during installation:
```bash
# Extract distribution
tar -xzf typedialog-0.1.0.tar.gz
cd typedialog-0.1.0
# Install
bash installers/install.sh
# Use specific config
typedialog --config ~/.config/typedialog/cli/production.toml form.toml
```
## Best Practices
### Development
- Use `dev` configurations for local development
- Enable debugging and verbose logging
- Use shorter timeouts for faster iteration
### Testing
- Use `default` configurations for testing
- Run integration tests with all environments
### Production
- Always use `production` configurations
- Verify HTTPS is enabled (web backend)
- Set appropriate rate limits
- Configure logging to persistent location
- Test thoroughly in staging first
## Related Documentation
- [BUILD_AND_DISTRIBUTION.md](../BUILD_AND_DISTRIBUTION.md) - Build guide
- [DISTRIBUTION_WORKFLOW.md](../DISTRIBUTION_WORKFLOW.md) - Release workflow
- [README.md](../README.md) - Main documentation

30
config/cli/default.toml Normal file
View File

@ -0,0 +1,30 @@
# CLI Backend - Default Configuration
# Used for standard command-line form rendering
[form]
title = "CLI Form"
description = "Standard command-line interface form"
[form.validation]
validate_on_change = true
show_errors_inline = true
[output]
format = "json"
pretty_print = true
[terminal]
# Use raw mode for better terminal control
use_raw_mode = true
# Enable mouse support if available
enable_mouse = false
# Use color output
use_color = true
[appearance]
# Theme: default, monochrome, dark
theme = "default"
# Show field help text
show_help = true
# Show field placeholders
show_placeholders = true

32
config/cli/dev.toml Normal file
View File

@ -0,0 +1,32 @@
# CLI Backend - Development Configuration
# Extended configuration for development and testing
[form]
title = "CLI Form (Dev)"
description = "Development CLI form with debugging enabled"
[form.validation]
validate_on_change = true
show_errors_inline = true
strict_validation = true
[output]
format = "json"
pretty_print = true
debug_output = true
[terminal]
use_raw_mode = true
enable_mouse = true
use_color = true
[appearance]
theme = "default"
show_help = true
show_placeholders = true
show_field_types = true
[debug]
enabled = true
trace_execution = false
log_level = "info"

View File

@ -0,0 +1,37 @@
# CLI Backend - Production Configuration
# Optimized for production deployment
[form]
title = "Form"
description = ""
[form.validation]
validate_on_change = true
show_errors_inline = true
strict_validation = true
[output]
format = "json"
pretty_print = false
# Suppress debugging info
debug_output = false
[terminal]
use_raw_mode = true
enable_mouse = false
use_color = true
[appearance]
theme = "default"
show_help = false
show_placeholders = false
[logging]
level = "error"
file = "/var/log/typedialog/cli.log"
[timeout]
# Maximum form completion time (seconds)
max_duration = 3600
# Field input timeout
input_timeout = 300

46
config/tui/default.toml Normal file
View File

@ -0,0 +1,46 @@
# TUI Backend - Default Configuration
# Terminal User Interface rendering
[form]
title = "TUI Form"
description = "Interactive terminal user interface form"
[form.validation]
validate_on_change = true
show_errors_inline = true
[output]
format = "json"
pretty_print = true
[terminal]
# Full TUI features
use_raw_mode = true
enable_mouse = true
enable_scrolling = true
# Fixed height (-1 = auto)
height = -1
# Fixed width (-1 = auto)
width = -1
[ui]
# Show field borders
show_borders = true
# Show field focus indicator
show_focus = true
# Highlight on hover
highlight_on_hover = true
# Animation enabled
enable_animations = true
[appearance]
theme = "default"
border_style = "rounded"
# Color scheme: default, dark, light, high_contrast
color_scheme = "default"
[keyboard]
# Vi-style navigation (hjkl)
vi_mode = false
# Emacs-style navigation
emacs_mode = false

45
config/tui/dev.toml Normal file
View File

@ -0,0 +1,45 @@
# TUI Backend - Development Configuration
# Extended TUI features for development
[form]
title = "TUI Form (Dev)"
description = "Development TUI form with all features enabled"
[form.validation]
validate_on_change = true
show_errors_inline = true
strict_validation = true
[output]
format = "json"
pretty_print = true
debug_output = true
[terminal]
use_raw_mode = true
enable_mouse = true
enable_scrolling = true
height = -1
width = -1
[ui]
show_borders = true
show_focus = true
highlight_on_hover = true
enable_animations = true
# Show field indices for debugging
show_field_indices = true
[appearance]
theme = "default"
border_style = "double"
color_scheme = "default"
[keyboard]
vi_mode = false
emacs_mode = false
[debug]
enabled = true
log_level = "debug"
trace_rendering = false

View File

@ -0,0 +1,48 @@
# TUI Backend - Production Configuration
# Optimized TUI for production deployment
[form]
title = ""
description = ""
[form.validation]
validate_on_change = true
show_errors_inline = true
strict_validation = true
[output]
format = "json"
pretty_print = false
debug_output = false
[terminal]
use_raw_mode = true
enable_mouse = true
enable_scrolling = true
height = -1
width = -1
[ui]
show_borders = true
show_focus = true
highlight_on_hover = true
enable_animations = false
[appearance]
theme = "default"
border_style = "rounded"
color_scheme = "default"
[keyboard]
vi_mode = false
emacs_mode = false
[logging]
level = "error"
file = "/var/log/typedialog/tui.log"
[performance]
# Render throttle (milliseconds)
render_throttle = 16
# Max update frequency (Hz)
max_fps = 60

48
config/web/default.toml Normal file
View File

@ -0,0 +1,48 @@
# Web Backend - Default Configuration
# HTTP server and web form rendering
[server]
host = "localhost"
port = 3000
# CORS settings
cors_enabled = true
cors_origins = ["localhost", "127.0.0.1"]
[form]
title = "Web Form"
description = "Interactive web form"
[form.validation]
validate_on_change = true
show_errors_inline = true
client_validation = true
[output]
format = "json"
[html]
# CSS framework: bootstrap, tailwind, none
css_framework = "none"
# Include inline styles
inline_styles = false
# Mobile responsive
responsive = true
# Dark mode support
dark_mode = true
[submission]
# Submission method: post, put, patch
method = "post"
# Optional webhook URL for submissions
webhook_url = ""
# Redirect after submission
redirect_on_success = false
redirect_url = ""
[security]
# CSRF protection
csrf_enabled = true
# Rate limiting (requests per minute)
rate_limit = 0
# Require HTTPS
require_https = false

50
config/web/dev.toml Normal file
View File

@ -0,0 +1,50 @@
# Web Backend - Development Configuration
# Enhanced features for development
[server]
host = "0.0.0.0"
port = 3000
# Enable hot reload
hot_reload = true
# Debug mode
debug = true
[form]
title = "Web Form (Dev)"
description = "Development web form"
[form.validation]
validate_on_change = true
show_errors_inline = true
client_validation = true
[output]
format = "json"
[html]
css_framework = "none"
inline_styles = true
responsive = true
dark_mode = true
# Show field metadata
show_field_metadata = true
[submission]
method = "post"
webhook_url = "http://localhost:8000/webhook"
redirect_on_success = false
log_submissions = true
[security]
csrf_enabled = true
rate_limit = 0
require_https = false
[logging]
level = "debug"
file = "/tmp/typedialog-web.log"
[api]
# API documentation enabled
enable_docs = true
docs_path = "/docs"

View File

@ -0,0 +1,65 @@
# Web Backend - Production Configuration
# Hardened configuration for production deployment
[server]
host = "0.0.0.0"
port = 8080
# Disable development features
hot_reload = false
debug = false
# Worker threads
workers = 4
[form]
title = ""
description = ""
[form.validation]
validate_on_change = true
show_errors_inline = true
client_validation = true
[output]
format = "json"
[html]
css_framework = "none"
inline_styles = false
responsive = true
dark_mode = true
[submission]
method = "post"
# Required: webhook for production submissions
webhook_url = "https://api.example.com/forms"
redirect_on_success = true
redirect_url = "https://example.com/thank-you"
[security]
# Strict CSRF protection
csrf_enabled = true
# Rate limiting: requests per minute per IP
rate_limit = 100
# Require HTTPS
require_https = true
# Security headers
add_security_headers = true
[logging]
level = "error"
file = "/var/log/typedialog/web.log"
[performance]
# Cache static assets
cache_static = true
cache_ttl = 3600
# Enable gzip compression
enable_compression = true
# Minimum response size for compression (bytes)
compression_threshold = 1024
[tls]
# Optional TLS configuration
enabled = false
cert_path = "/etc/typedialog/cert.pem"
key_path = "/etc/typedialog/key.pem"

View File

@ -0,0 +1,183 @@
# TypeDialog Bootstrap Installers
Quick installation scripts for all platforms.
## Linux & macOS
Install with `curl`:
```bash
curl -fsSL https://github.com/anthropics/typedialog/releases/download/latest/install.sh | bash
```
Or with `wget`:
```bash
wget -qO - https://github.com/anthropics/typedialog/releases/download/latest/install.sh | bash
```
## Windows
Open PowerShell and run:
```powershell
irm https://github.com/anthropics/typedialog/releases/download/latest/install.ps1 | iex
```
## Manual Installation
### Linux & macOS
```bash
# Download script
wget https://github.com/anthropics/typedialog/releases/download/latest/install.sh
# Run installer
bash install.sh
# Or with environment variables
INSTALL_DIR="$HOME/bin" CONFIG_DIR="$HOME/.typedialog" bash install.sh
```
### Windows
```powershell
# Download script
Invoke-WebRequest -Uri "https://github.com/anthropics/typedialog/releases/download/latest/install.ps1" -OutFile "install.ps1"
# Run installer
.\install.ps1
# Or with parameters
.\install.ps1 -InstallDir "C:\Program Files\typedialog" -ConfigDir "C:\Users\$env:USERNAME\AppData\Local\typedialog"
```
## Configuration
### Linux & macOS Environment Variables
- `INSTALL_DIR` - Installation directory (default: `$HOME/.local/bin`)
- `CONFIG_DIR` - Configuration directory (default: `$HOME/.config/typedialog`)
- `VERSION` - Release version (default: `latest`)
### Windows Parameters
- `-InstallDir` - Installation directory (default: `$PROFILE\.local\bin`)
- `-ConfigDir` - Configuration directory (default: `$PROFILE\.config\typedialog`)
- `-Version` - Release version (default: `latest`)
## What Gets Installed
### Binaries
- `typedialog` - CLI backend
- `typedialog-tui` - TUI backend
- `typedialog-web` - Web backend
### Configuration Files
- CLI configs: `cli/default.toml`, `cli/dev.toml`, `cli/production.toml`
- TUI configs: `tui/default.toml`, `tui/dev.toml`, `tui/production.toml`
- Web configs: `web/default.toml`, `web/dev.toml`, `web/production.toml`
## Verification
After installation, verify everything works:
```bash
# Check CLI backend
typedialog --version
# Check TUI backend
typedialog-tui --version
# Check web backend
typedialog-web --version
# List available configurations
ls ~/.config/typedialog/
```
## Troubleshooting
### Command not found
Update your PATH:
**bash:**
```bash
echo 'export PATH="$PATH:$HOME/.local/bin"' >> ~/.bashrc
source ~/.bashrc
```
**zsh:**
```bash
echo 'export PATH="$PATH:$HOME/.local/bin"' >> ~/.zshrc
source ~/.zshrc
```
### Permission denied
Make binaries executable:
```bash
chmod +x ~/.local/bin/typedialog*
```
### Configuration issues
Check configuration directory:
```bash
cat ~/.config/typedialog/cli/default.toml
```
## Uninstallation
Remove installed files:
```bash
# Remove binaries
rm ~/.local/bin/typedialog*
# Remove configurations (optional)
rm -rf ~/.config/typedialog/
```
## Platform Support
| Platform | Status | Architecture |
|----------|--------|--------------|
| Linux (glibc) | ✓ | x86_64, aarch64 |
| macOS | ✓ | x86_64, aarch64 |
| Windows | ✓ | x86_64 |
## Requirements
### Linux & macOS
- `curl` or `wget`
- `tar` and `gzip`
- Bash 4.0+
### Windows
- PowerShell 3.0+
- .NET Framework 4.5+ (usually pre-installed)
## Manual Build & Install
To build and install from source:
```bash
# Clone repository
git clone https://github.com/anthropics/typedialog.git
cd typedialog
# Build release
./scripts/build_all.sh release
# Create distribution
./scripts/create_distro.sh v0.1.0
# Install from distribution
tar -xzf distribution/typedialog-0.1.0.tar.gz
cp typedialog-0.1.0/bin/* ~/.local/bin/
cp -r typedialog-0.1.0/config/* ~/.config/typedialog/
```
## Support
- [GitHub Issues](https://github.com/anthropics/typedialog/issues)
- [Documentation](https://github.com/anthropics/typedialog/blob/main/README.md)

View File

@ -0,0 +1,185 @@
# TypeDialog Installation Script for Windows
# Installs typedialog binaries and configuration
param(
[string]$InstallDir = "$env:USERPROFILE\.local\bin",
[string]$ConfigDir = "$env:USERPROFILE\.config\typedialog",
[string]$Version = "latest"
)
$ErrorActionPreference = "Stop"
$GitHubRepo = "anthropics/typedialog"
# Function: Print info message
function Write-Info {
param([string]$Message)
Write-Host "[INFO] $Message" -ForegroundColor Cyan
}
# Function: Print error message
function Write-Err {
param([string]$Message)
Write-Host "[ERROR] $Message" -ForegroundColor Red
}
# Function: Print success message
function Write-Success {
param([string]$Message)
Write-Host "[✓] $Message" -ForegroundColor Green
}
# Function: Detect platform
function Get-Platform {
$arch = $env:PROCESSOR_ARCHITECTURE
switch ($arch) {
"AMD64" { return "windows-x86_64" }
"ARM64" { return "windows-aarch64" }
default {
Write-Err "Unsupported architecture: $arch"
exit 1
}
}
}
# Function: Create directories
function New-DirectoryStructure {
param([string]$InstallDir, [string]$ConfigDir)
Write-Info "Creating directories..."
if (-not (Test-Path $InstallDir)) {
New-Item -ItemType Directory -Path $InstallDir -Force | Out-Null
}
if (-not (Test-Path $ConfigDir)) {
New-Item -ItemType Directory -Path $ConfigDir -Force | Out-Null
}
Write-Success "Directories created"
}
# Function: Download release
function Get-Release {
param([string]$Platform, [string]$Version)
$DownloadUrl = "https://github.com/$GitHubRepo/releases/download/$Version/typedialog-$Platform.zip"
$TempFile = [System.IO.Path]::GetTempFileName()
Write-Info "Downloading: $DownloadUrl"
try {
Invoke-WebRequest -Uri $DownloadUrl -OutFile $TempFile -ErrorAction Stop
Write-Success "Download complete"
return $TempFile
}
catch {
Write-Err "Download failed: $_"
exit 1
}
}
# Function: Extract archive
function Expand-Release {
param([string]$Archive)
$ExtractDir = New-Item -ItemType Directory -Path (Join-Path $env:TEMP "typedialog-extract") -Force | Select-Object -ExpandProperty FullName
Write-Info "Extracting archive..."
try {
Expand-Archive -Path $Archive -DestinationPath $ExtractDir -Force
Write-Success "Extraction complete"
return $ExtractDir
}
catch {
Write-Err "Extraction failed: $_"
exit 1
}
}
# Function: Install binaries
function Install-Binaries {
param([string]$ExtractDir, [string]$InstallDir)
$Binaries = @("typedialog.exe", "typedialog-tui.exe", "typedialog-web.exe")
$BinDir = Join-Path $ExtractDir "bin"
foreach ($binary in $Binaries) {
$BinaryPath = Join-Path $BinDir $binary
if (Test-Path $BinaryPath) {
Copy-Item -Path $BinaryPath -Destination $InstallDir -Force
Write-Success "Installed: $binary"
}
}
}
# Function: Install configurations
function Install-Configs {
param([string]$ExtractDir, [string]$ConfigDir)
$ConfigSrc = Join-Path $ExtractDir "config"
if (Test-Path $ConfigSrc) {
Copy-Item -Path "$ConfigSrc\*" -Destination $ConfigDir -Recurse -Force
Write-Success "Configurations installed"
}
}
# Function: Update PATH
function Update-Path {
param([string]$InstallDir)
$CurrentPath = [Environment]::GetEnvironmentVariable("PATH", "User")
if ($CurrentPath -notlike "*$InstallDir*") {
$NewPath = "$CurrentPath;$InstallDir"
[Environment]::SetEnvironmentVariable("PATH", $NewPath, "User")
Write-Info "Updated PATH with $InstallDir"
}
}
# Function: Verify installation
function Test-Installation {
$ExePath = Join-Path $env:USERPROFILE ".local\bin\typedialog.exe"
if (Test-Path $ExePath) {
Write-Success "TypeDialog installed successfully"
return $true
}
else {
Write-Err "Installation verification failed"
return $false
}
}
# Main entry point
function Main {
Write-Host "TypeDialog Installation" -ForegroundColor Yellow
Write-Host "=======================" -ForegroundColor Yellow
$Platform = Get-Platform
Write-Info "Detected platform: $Platform"
New-DirectoryStructure -InstallDir $InstallDir -ConfigDir $ConfigDir
$Archive = Get-Release -Platform $Platform -Version $Version
$ExtractDir = Expand-Release -Archive $Archive
Install-Binaries -ExtractDir $ExtractDir -InstallDir $InstallDir
Install-Configs -ExtractDir $ExtractDir -ConfigDir $ConfigDir
Update-Path -InstallDir $InstallDir
if (Test-Installation) {
Write-Success "Installation complete!"
Write-Info "Install directory: $InstallDir"
Write-Info "Config directory: $ConfigDir"
}
else {
exit 1
}
}
Main

225
installers/bootstrap/install.sh Executable file
View File

@ -0,0 +1,225 @@
#!/bin/bash
set -euo pipefail
# TypeDialog Installation Script
# Installs TypeDialog binaries and configuration
INSTALL_DIR="${INSTALL_DIR:-$HOME/.local/bin}"
CONFIG_DIR="${CONFIG_DIR:-$HOME/.config/typedialog}"
GITHUB_REPO="anthropics/typedialog"
VERSION="${VERSION:-latest}"
# Function: Print info message
log_info() {
echo "[INFO] $*" >&2
}
# Function: Print error message
log_error() {
echo "[ERROR] $*" >&2
}
# Function: Print success message
log_success() {
echo "[✓] $*" >&2
}
# Function: Detect OS and architecture
detect_platform() {
local os
local arch
os=$(uname -s)
arch=$(uname -m)
case "$os" in
Linux*)
os="linux"
;;
Darwin*)
os="darwin"
;;
*)
log_error "Unsupported OS: $os"
return 1
;;
esac
case "$arch" in
x86_64)
arch="x86_64"
;;
aarch64)
arch="aarch64"
;;
arm64)
arch="aarch64"
;;
*)
log_error "Unsupported architecture: $arch"
return 1
;;
esac
echo "${os}-${arch}"
}
# Function: Create directories
create_dirs() {
mkdir -p "$INSTALL_DIR" || {
log_error "Failed to create install directory: $INSTALL_DIR"
return 1
}
mkdir -p "$CONFIG_DIR" || {
log_error "Failed to create config directory: $CONFIG_DIR"
return 1
}
log_success "Directories created"
}
# Function: Download release
download_release() {
local platform="$1"
local download_url="https://github.com/${GITHUB_REPO}/releases/download/${VERSION}/typedialog-${platform}.tar.gz"
local temp_file
temp_file=$(mktemp) || return 1
trap "rm -f '$temp_file'" EXIT
log_info "Downloading: $download_url"
if command -v curl &>/dev/null; then
curl -fsSL "$download_url" -o "$temp_file" || {
log_error "Download failed"
return 1
}
elif command -v wget &>/dev/null; then
wget -q "$download_url" -O "$temp_file" || {
log_error "Download failed"
return 1
}
else
log_error "curl or wget required"
return 1
fi
log_success "Download complete"
echo "$temp_file"
}
# Function: Extract archive
extract_archive() {
local archive="$1"
local extract_dir
extract_dir=$(mktemp -d) || return 1
log_info "Extracting archive..."
tar -xzf "$archive" -C "$extract_dir" || {
log_error "Extraction failed"
rm -rf "$extract_dir"
return 1
}
echo "$extract_dir"
}
# Function: Install binaries
install_binaries() {
local extract_dir="$1"
for binary in typedialog typedialog-tui typedialog-web; do
local binary_path="${extract_dir}/bin/${binary}"
if [[ -f "$binary_path" ]]; then
cp "$binary_path" "${INSTALL_DIR}/${binary}" || {
log_error "Failed to install: $binary"
return 1
}
chmod +x "${INSTALL_DIR}/${binary}"
log_success "Installed: $binary"
fi
done
}
# Function: Install configurations
install_configs() {
local extract_dir="$1"
local config_src="${extract_dir}/config"
if [[ -d "$config_src" ]]; then
cp -r "$config_src"/* "$CONFIG_DIR/" || {
log_error "Failed to install configurations"
return 1
}
log_success "Configurations installed"
fi
}
# Function: Update PATH if needed
update_path() {
local shell_rc=""
if [[ "$SHELL" == *"bash"* ]]; then
shell_rc="$HOME/.bashrc"
elif [[ "$SHELL" == *"zsh"* ]]; then
shell_rc="$HOME/.zshrc"
else
return 0
fi
if [[ -f "$shell_rc" ]]; then
if ! grep -q "$INSTALL_DIR" "$shell_rc"; then
echo "export PATH=\"\$PATH:$INSTALL_DIR\"" >> "$shell_rc"
log_info "Updated $shell_rc with $INSTALL_DIR"
fi
fi
}
# Function: Verify installation
verify_installation() {
log_info "Verifying installation..."
if command -v typedialog &>/dev/null; then
local version
version=$(typedialog --version 2>/dev/null || echo "unknown")
log_success "TypeDialog installed: $version"
else
log_error "Installation verification failed"
return 1
fi
}
# Function: Main entry point
main() {
log_info "TypeDialog Installation"
log_info "========================"
local platform
platform=$(detect_platform) || return 1
log_info "Detected platform: $platform"
create_dirs || return 1
local archive
archive=$(download_release "$platform") || return 1
local extract_dir
extract_dir=$(extract_archive "$archive") || return 1
trap "rm -rf '$extract_dir'" EXIT
install_binaries "$extract_dir" || return 1
install_configs "$extract_dir" || return 1
update_path
verify_installation || return 1
log_success "TypeDialog installation complete!"
log_info "Install directory: $INSTALL_DIR"
log_info "Config directory: $CONFIG_DIR"
return 0
}
main "$@"

58
scripts/build_all.sh Executable file
View File

@ -0,0 +1,58 @@
#!/bin/bash
set -euo pipefail
# Description: Build all workspace targets for current platform
# Arguments: [profile] - debug or release (default: debug)
# Returns: 0 on success, 1 on failure
# Output: Build progress and results
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
readonly WORKSPACE_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
readonly PROFILE="${1:-debug}"
# Function: Print info message
log_info() {
echo "[INFO] $*" >&2
}
# Function: Print error message
log_error() {
echo "[ERROR] $*" >&2
}
# Function: Validate profile
validate_profile() {
local profile="$1"
[[ "$profile" == "debug" || "$profile" == "release" ]] || {
log_error "Invalid profile: $profile (must be debug or release)"
return 1
}
}
# Function: Build workspace
build_workspace() {
local profile="$1"
local cargo_args=("--workspace")
if [[ "$profile" == "release" ]]; then
cargo_args+=("--release")
fi
log_info "Building workspace ($profile profile)..."
cd "$WORKSPACE_ROOT"
cargo build "${cargo_args[@]}"
}
# Function: Main entry point
main() {
log_info "TypeDialog Build All"
log_info "====================="
validate_profile "$PROFILE" || return 1
build_workspace "$PROFILE"
log_info "Build complete: $PROFILE profile"
return 0
}
main "$@"

94
scripts/build_cross.sh Executable file
View File

@ -0,0 +1,94 @@
#!/bin/bash
set -euo pipefail
# Description: Cross-compile for multiple platforms using cargo-cross
# Arguments: [target] - specific target (default: all targets)
# Returns: 0 on success, 1 on failure
# Output: Cross-compilation progress
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
readonly WORKSPACE_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
readonly TARGET="${1:-}"
# Supported targets for cross-compilation
declare -a TARGETS=(
"x86_64-unknown-linux-gnu"
"x86_64-apple-darwin"
"aarch64-apple-darwin"
"x86_64-pc-windows-gnu"
)
# Function: Print info message
log_info() {
echo "[INFO] $*" >&2
}
# Function: Print error message
log_error() {
echo "[ERROR] $*" >&2
}
# Function: Check for cross tool
check_cross() {
command -v cross >/dev/null || {
log_error "cross not found. Install: cargo install cross"
return 1
}
}
# Function: Validate target
is_valid_target() {
local target="$1"
for t in "${TARGETS[@]}"; do
[[ "$t" == "$target" ]] && return 0
done
return 1
}
# Function: Build single target
build_target() {
local target="$1"
log_info "Building target: $target"
cd "$WORKSPACE_ROOT"
cross build --target "$target" --release
}
# Function: Build all targets
build_all_targets() {
local failed=0
for target in "${TARGETS[@]}"; do
if ! build_target "$target"; then
log_error "Failed to build: $target"
((failed++))
fi
done
return $((failed > 0 ? 1 : 0))
}
# Function: Main entry point
main() {
log_info "TypeDialog Cross-Compilation"
log_info "=============================="
check_cross || return 1
if [[ -z "$TARGET" ]]; then
log_info "Building all targets..."
build_all_targets
else
is_valid_target "$TARGET" || {
log_error "Invalid target: $TARGET"
log_error "Supported: ${TARGETS[*]}"
return 1
}
build_target "$TARGET"
fi
log_info "Cross-compilation complete"
return 0
}
main "$@"

195
scripts/create_distro.sh Executable file
View File

@ -0,0 +1,195 @@
#!/bin/bash
set -euo pipefail
# Description: Create distribution package with binaries and configs
# Arguments: [version] - version string (default: from Cargo.toml)
# Returns: 0 on success, 1 on failure
# Output: Distribution package path
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
readonly WORKSPACE_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
readonly DISTRO_DIR="${WORKSPACE_ROOT}/distribution"
readonly BUILD_DIR="${WORKSPACE_ROOT}/target/release"
# Extract version from Cargo.toml
get_version() {
grep '^version' "${WORKSPACE_ROOT}/crates/typedialog-core/Cargo.toml" |
head -1 |
sed 's/.*"\([^"]*\)".*/\1/'
}
# Function: Print info message
log_info() {
echo "[INFO] $*" >&2
}
# Function: Print error message
log_error() {
echo "[ERROR] $*" >&2
}
# Function: Validate files exist
validate_files() {
local missing=0
for file in "$@"; do
if [[ ! -f "$file" ]]; then
log_error "Missing: $file"
((missing++))
fi
done
return $((missing > 0 ? 1 : 0))
}
# Function: Create distribution directory
create_distro_structure() {
local version="$1"
local distro_path="${DISTRO_DIR}/typedialog-${version}"
mkdir -p "${distro_path}"/{bin,config,docs,lib}
echo "$distro_path"
}
# Function: Copy binaries
copy_binaries() {
local distro_path="$1"
if [[ -f "${BUILD_DIR}/typedialog" ]]; then
cp "${BUILD_DIR}/typedialog" "${distro_path}/bin/"
fi
if [[ -f "${BUILD_DIR}/typedialog-tui" ]]; then
cp "${BUILD_DIR}/typedialog-tui" "${distro_path}/bin/"
fi
if [[ -f "${BUILD_DIR}/typedialog-web" ]]; then
cp "${BUILD_DIR}/typedialog-web" "${distro_path}/bin/"
fi
}
# Function: Copy configurations
copy_configs() {
local distro_path="$1"
cp -r "${WORKSPACE_ROOT}/config"/* "${distro_path}/config/" || {
log_error "Failed to copy configurations"
return 1
}
}
# Function: Copy installers
copy_installers() {
local distro_path="$1"
mkdir -p "${distro_path}/installers"
if [[ -f "${WORKSPACE_ROOT}/installers/bootstrap/install.sh" ]]; then
cp "${WORKSPACE_ROOT}/installers/bootstrap/install.sh" "${distro_path}/installers/"
chmod +x "${distro_path}/installers/install.sh"
fi
if [[ -f "${WORKSPACE_ROOT}/installers/bootstrap/install.ps1" ]]; then
cp "${WORKSPACE_ROOT}/installers/bootstrap/install.ps1" "${distro_path}/installers/"
fi
if [[ -f "${WORKSPACE_ROOT}/installers/bootstrap/README.md" ]]; then
cp "${WORKSPACE_ROOT}/installers/bootstrap/README.md" "${distro_path}/installers/"
fi
}
# Function: Create manifest
create_manifest() {
local distro_path="$1"
local version="$2"
local manifest="${distro_path}/MANIFEST.json"
cat > "$manifest" <<EOF
{
"name": "typedialog",
"version": "$version",
"created": "$(date -u +'%Y-%m-%dT%H:%M:%SZ')",
"structure": {
"bin": "Binary executables",
"config": "Configuration files per backend/environment",
"installers": "Installation scripts (Linux/macOS/Windows)",
"docs": "Documentation"
},
"binaries": [
"bin/typedialog",
"bin/typedialog-tui",
"bin/typedialog-web"
],
"configs": {
"cli": [
"config/cli/default.toml",
"config/cli/dev.toml",
"config/cli/production.toml"
],
"tui": [
"config/tui/default.toml",
"config/tui/dev.toml",
"config/tui/production.toml"
],
"web": [
"config/web/default.toml",
"config/web/dev.toml",
"config/web/production.toml"
]
},
"installers": {
"linux_macos": "installers/install.sh",
"windows": "installers/install.ps1",
"readme": "installers/README.md"
}
}
EOF
}
# Function: Create tarball
create_tarball() {
local distro_path="$1"
local distro_name=$(basename "$distro_path")
local tarball="${DISTRO_DIR}/${distro_name}.tar.gz"
cd "$DISTRO_DIR"
tar -czf "$tarball" "$distro_name"
echo "$tarball"
}
# Function: Main entry point
main() {
local version="${1:-$(get_version)}"
log_info "TypeDialog Distribution Builder"
log_info "================================"
# Build release first
log_info "Building release binaries..."
cd "$WORKSPACE_ROOT"
cargo build --workspace --release || {
log_error "Build failed"
return 1
}
# Create distribution
local distro_path
distro_path=$(create_distro_structure "$version")
log_info "Distribution path: $distro_path"
copy_binaries "$distro_path"
copy_configs "$distro_path"
copy_installers "$distro_path"
create_manifest "$distro_path" "$version"
local tarball
tarball=$(create_tarball "$distro_path")
log_info "Distribution package: $tarball"
echo "$tarball"
return 0
}
main "$@"

282
scripts/generate_sbom.py Executable file
View File

@ -0,0 +1,282 @@
#!/usr/bin/env python3
"""Generate Software Bill of Materials (SBOM) in multiple formats.
Generates:
- LICENSE.md - Detailed dependency attribution
- DEPENDENCIES.md - Organized dependency tree
- SBOM.spdx.json - SPDX 2.3 format
- SBOM.cyclonedx.json - CycloneDX 1.4 format
"""
import json
import subprocess
import sys
from collections import defaultdict
from datetime import datetime
from pathlib import Path
def get_workspace_root():
"""Get workspace root directory."""
script_dir = Path(__file__).parent
return script_dir.parent
def run_cargo_license():
"""Get dependency licenses from cargo-license."""
try:
result = subprocess.run(
["cargo", "license", "--json"], capture_output=True, text=True, check=True
)
return json.loads(result.stdout)
except subprocess.CalledProcessError as e:
print(f"Error running cargo license: {e}", file=sys.stderr)
sys.exit(1)
except json.JSONDecodeError as e:
print(f"Error parsing cargo license output: {e}", file=sys.stderr)
sys.exit(1)
def generate_license_md(licenses, workspace_root):
"""Generate LICENSE.md with dependency attribution."""
by_license = defaultdict(list)
for pkg in licenses:
lic = pkg.get("license", "Unknown")
by_license[lic].append(pkg)
content = """# TypeDialog License
## Project License
TypeDialog is licensed under the **MIT License**.
See [LICENSE](LICENSE) file for the full MIT license text.
---
## Dependencies
This project includes the following dependencies under their respective licenses:
"""
# Apache-2.0 only
if "Apache-2.0" in by_license:
content += f"### Apache-2.0 Only ({len(by_license['Apache-2.0'])})\n"
for pkg in sorted(by_license["Apache-2.0"], key=lambda x: x["name"]):
content += f"- {pkg['name']} {pkg['version']}\n"
content += "\n"
# MIT only
if "MIT" in by_license:
content += f"### MIT Only ({len(by_license['MIT'])})\n"
for pkg in sorted(by_license["MIT"], key=lambda x: x["name"]):
content += f"- {pkg['name']} {pkg['version']}\n"
content += "\n"
# Apache-2.0 OR MIT (dual licensed)
if "Apache-2.0 OR MIT" in by_license:
content += f"### Apache-2.0 OR MIT ({len(by_license['Apache-2.0 OR MIT'])})\n\n"
content += (
"Most dependencies use dual licensing between Apache-2.0 and MIT.\n\n"
)
for i, pkg in enumerate(
sorted(by_license["Apache-2.0 OR MIT"], key=lambda x: x["name"]), 1
):
content += f"- {pkg['name']} {pkg['version']}"
if i % 3 != 0:
content += " | "
else:
content += "\n"
content += "\n\n"
# MIT OR Unlicense
if "MIT OR Unlicense" in by_license:
content += f"### MIT OR Unlicense ({len(by_license['MIT OR Unlicense'])})\n"
for pkg in sorted(by_license["MIT OR Unlicense"], key=lambda x: x["name"]):
content += f"- {pkg['name']} {pkg['version']}\n"
content += "\n"
# Other licenses
other = {
k: v
for k, v in by_license.items()
if k not in ["Apache-2.0", "MIT", "Apache-2.0 OR MIT", "MIT OR Unlicense"]
}
if other:
content += "### Other Licenses\n\n"
for lic, pkgs in sorted(other.items()):
content += f"**{lic}** ({len(pkgs)})\n"
for pkg in sorted(pkgs, key=lambda x: x["name"]):
content += f"- {pkg['name']} {pkg['version']}\n"
content += "\n"
# Summary
unique_licenses = set()
for pkg in licenses:
lic = pkg.get("license", "Unknown")
for part in lic.replace(" OR ", "|").replace(" AND ", "|").split("|"):
unique_licenses.add(part.strip())
content += f"""---
## Summary
- **Project License**: MIT
- **Total Dependencies**: {len(licenses)}
- **Unique License Types**: {len(by_license)} different combinations
- **Primary License Pattern**: Apache-2.0 OR MIT (most dependencies)
### Compliance
All dependencies are compatible with the MIT license under:
- Permissive licenses (MIT, Apache-2.0, BSD-3-Clause, MPL-2.0, Zlib)
- Weak copyleft (LGPL-2.1-or-later, MPL-2.0)
- Public domain (Unlicense, Unicode-3.0)
### Generated
- Date: {datetime.now().isoformat()}
- Tool: cargo-license
See [DEPENDENCIES.md](DEPENDENCIES.md) for the complete dependency tree.
"""
output_file = workspace_root / "LICENSE.md"
output_file.write_text(content)
return output_file
def generate_sbom_spdx(licenses, workspace_root):
"""Generate SPDX 2.3 format SBOM."""
spdx = {
"SPDXID": "SPDXRef-DOCUMENT",
"spdxVersion": "SPDX-2.3",
"creationInfo": {
"created": datetime.now().isoformat() + "Z",
"creators": ["Tool: cargo-license"],
},
"name": "typedialog",
"dataLicense": "CC0-1.0",
"documentNamespace": f"https://github.com/anthropics/typedialog/sbom-{datetime.now().strftime('%Y%m%d')}",
"packages": [],
}
# Add project
spdx["packages"].append(
{
"SPDXID": "SPDXRef-typedialog",
"name": "typedialog",
"version": "0.1.0",
"downloadLocation": "https://github.com/anthropics/typedialog",
"homepage": "https://github.com/anthropics/typedialog",
"licenseDeclared": "MIT",
"licenseConcluded": "MIT",
"filesAnalyzed": False,
}
)
# Add dependencies
for i, pkg in enumerate(licenses, 1):
spdx["packages"].append(
{
"SPDXID": f"SPDXRef-dependency-{i}",
"name": pkg["name"],
"version": pkg["version"],
"downloadLocation": pkg.get("repository", "NOASSERTION"),
"licenseDeclared": pkg.get("license", "NOASSERTION"),
"licenseConcluded": pkg.get("license", "NOASSERTION"),
"filesAnalyzed": False,
"externalRefs": [
{
"referenceCategory": "PACKAGE-MANAGER",
"referenceType": "crates",
"referenceLocator": f"pkg:cargo/{pkg['name']}@{pkg['version']}",
}
]
if pkg["name"]
else [],
}
)
output_file = workspace_root / "SBOM.spdx.json"
with open(output_file, "w") as f:
json.dump(spdx, f, indent=2)
return output_file
def generate_sbom_cyclonedx(licenses, workspace_root):
"""Generate CycloneDX 1.4 format SBOM."""
cyclone = {
"bomFormat": "CycloneDX",
"specVersion": "1.4",
"version": 1,
"metadata": {
"timestamp": datetime.now().isoformat() + "Z",
"tools": [{"vendor": "cargo", "name": "cargo-license", "version": "1.0"}],
"component": {
"type": "application",
"name": "typedialog",
"version": "0.1.0",
"homepage": "https://github.com/anthropics/typedialog",
"repository": {
"type": "git",
"url": "https://github.com/anthropics/typedialog",
},
"licenses": [{"license": {"name": "MIT"}}],
},
},
"components": [],
}
for pkg in licenses:
cyclone["components"].append(
{
"type": "library",
"name": pkg["name"],
"version": pkg["version"],
"purl": f"pkg:cargo/{pkg['name']}@{pkg['version']}",
"homepage": pkg.get("repository", ""),
"licenses": [{"license": {"name": pkg.get("license", "Unknown")}}],
}
)
output_file = workspace_root / "SBOM.cyclonedx.json"
with open(output_file, "w") as f:
json.dump(cyclone, f, indent=2)
return output_file
def main():
"""Generate all SBOM files."""
workspace_root = get_workspace_root()
print("📦 Fetching dependency licenses...", file=sys.stderr)
licenses = run_cargo_license()
print(f" Found {len(licenses)} dependencies", file=sys.stderr)
print("📝 Generating LICENSE.md...", file=sys.stderr)
lic_file = generate_license_md(licenses, workspace_root)
print(f"{lic_file.name}", file=sys.stderr)
print("📄 Generating SBOM.spdx.json...", file=sys.stderr)
spdx_file = generate_sbom_spdx(licenses, workspace_root)
print(f"{spdx_file.name}", file=sys.stderr)
print("📄 Generating SBOM.cyclonedx.json...", file=sys.stderr)
cyclone_file = generate_sbom_cyclonedx(licenses, workspace_root)
print(f"{cyclone_file.name}", file=sys.stderr)
print(f"\n✅ SBOM generation complete", file=sys.stderr)
print(
f" - License combinations: {len(set(p.get('license', 'Unknown') for p in licenses))}",
file=sys.stderr,
)
print(f" - Generated: {datetime.now().isoformat()}", file=sys.stderr)
if __name__ == "__main__":
main()

174
scripts/package_release.sh Executable file
View File

@ -0,0 +1,174 @@
#!/bin/bash
set -euo pipefail
# Description: Package and checksum distribution for release
# Arguments: [version] - version string (default: from Cargo.toml)
# Returns: 0 on success, 1 on failure
# Output: Final release package path
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
readonly WORKSPACE_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
readonly DISTRO_DIR="${WORKSPACE_ROOT}/distribution"
readonly RELEASE_DIR="${WORKSPACE_ROOT}/release"
# Extract version from Cargo.toml
get_version() {
grep '^version' "${WORKSPACE_ROOT}/crates/typedialog-core/Cargo.toml" |
head -1 |
sed 's/.*"\([^"]*\)".*/\1/'
}
# Function: Print info message
log_info() {
echo "[INFO] $*" >&2
}
# Function: Print error message
log_error() {
echo "[ERROR] $*" >&2
}
# Function: Print success message
log_success() {
echo "[✓] $*" >&2
}
# Function: Create release directory
create_release_dir() {
mkdir -p "$RELEASE_DIR"
}
# Function: Copy distribution packages
copy_packages() {
local version="$1"
local distro_tar="${DISTRO_DIR}/typedialog-${version}.tar.gz"
if [[ ! -f "$distro_tar" ]]; then
log_error "Distribution package not found: $distro_tar"
return 1
fi
cp "$distro_tar" "$RELEASE_DIR/"
log_success "Copied distribution package"
}
# Function: Generate checksums
generate_checksums() {
local version="$1"
local checksum_file="${RELEASE_DIR}/SHA256SUMS"
log_info "Generating checksums..."
cd "$RELEASE_DIR"
sha256sum "typedialog-${version}.tar.gz" > "$checksum_file"
log_success "Checksums generated: $checksum_file"
}
# Function: Create release notes
create_release_notes() {
local version="$1"
local notes_file="${RELEASE_DIR}/RELEASE_NOTES.md"
cat > "$notes_file" <<EOF
# TypeDialog $version
## Contents
- **typedialog-${version}.tar.gz** - Distribution package
## Installation
### Automatic (Linux/macOS)
\`\`\`bash
tar -xzf typedialog-${version}.tar.gz
cd typedialog-${version}
bash installers/install.sh
\`\`\`
### Automatic (Windows PowerShell)
\`\`\`powershell
tar -xf typedialog-${version}.tar.gz
cd typedialog-${version}
.\installers\install.ps1
\`\`\`
### Manual
1. Extract the archive
2. Copy binaries from \`bin/\` to your system PATH
3. Copy config from \`config/\` to \`~/.config/typedialog/\`
## Verification
Verify integrity with SHA256:
\`\`\`bash
sha256sum -c SHA256SUMS
\`\`\`
## Contents
The package includes:
- **bin/** - Compiled binaries for all backends
- typedialog - CLI backend
- typedialog-tui - TUI backend
- typedialog-web - Web backend
- **config/** - Configuration files for each backend and environment
- cli/{default,dev,production}.toml
- tui/{default,dev,production}.toml
- web/{default,dev,production}.toml
- **installers/** - Installation scripts
- install.sh - For Linux and macOS
- install.ps1 - For Windows
- README.md - Installation instructions
- **MANIFEST.json** - Package contents and structure
## Supported Platforms
- Linux (x86_64, aarch64)
- macOS (x86_64, aarch64)
- Windows (x86_64)
## Documentation
See installers/README.md for detailed installation instructions.
---
Released: $(date -u +'%Y-%m-%d %H:%M:%S UTC')
EOF
log_success "Release notes created: $notes_file"
}
# Function: List release contents
list_release() {
log_info "Release contents:"
ls -lh "$RELEASE_DIR"
}
# Function: Main entry point
main() {
local version="${1:-$(get_version)}"
log_info "TypeDialog Release Packager"
log_info "============================="
log_info "Version: $version"
create_release_dir
copy_packages "$version"
generate_checksums "$version"
create_release_notes "$version"
log_success "Release package ready!"
log_info "Release directory: $RELEASE_DIR"
list_release
return 0
}
main "$@"

656
tests/nickel_integration.rs Normal file
View File

@ -0,0 +1,656 @@
//! Integration tests for Nickel ↔ typedialog bidirectional workflows
//!
//! Tests the complete workflow:
//! 1. Nickel schema → metadata extraction
//! 2. Metadata → TOML form generation
//! 3. Form results → Nickel output serialization
//! 4. Template rendering with form results
use typedialog::nickel::{
NickelSchemaIR, NickelFieldIR, NickelType, MetadataParser, TomlGenerator,
NickelSerializer, ContractValidator, TemplateEngine,
};
use typedialog::form_parser;
use serde_json::json;
use std::collections::HashMap;
#[test]
fn test_simple_schema_roundtrip() {
// Create a simple schema
let schema = NickelSchemaIR {
name: "user_config".to_string(),
description: Some("Simple user configuration".to_string()),
fields: vec![
NickelFieldIR {
path: vec!["username".to_string()],
flat_name: "username".to_string(),
nickel_type: NickelType::String,
doc: Some("User login name".to_string()),
default: Some(json!("admin")),
optional: false,
contract: Some("String | std.string.NonEmpty".to_string()),
group: None,
},
NickelFieldIR {
path: vec!["email".to_string()],
flat_name: "email".to_string(),
nickel_type: NickelType::String,
doc: Some("User email address".to_string()),
default: None,
optional: true,
contract: None,
group: None,
},
],
};
// Generate form
let form = TomlGenerator::generate(&schema, false, false).expect("Form generation failed");
assert_eq!(form.name, "user_config");
assert_eq!(form.fields.len(), 2);
// Verify form fields have nickel metadata
assert_eq!(form.fields[0].nickel_contract, Some("String | std.string.NonEmpty".to_string()));
assert_eq!(form.fields[0].nickel_path, Some(vec!["username".to_string()]));
// Create form results
let mut results = HashMap::new();
results.insert("username".to_string(), json!("alice"));
results.insert("email".to_string(), json!("alice@example.com"));
// Serialize to Nickel
let nickel_output = NickelSerializer::serialize(&results, &schema)
.expect("Serialization failed");
// Verify output contains expected content
assert!(nickel_output.contains("alice"));
assert!(nickel_output.contains("alice@example.com"));
assert!(nickel_output.contains("String | std.string.NonEmpty"));
}
#[test]
fn test_nested_schema_with_flatten() {
// Create schema with nested structure
let schema = NickelSchemaIR {
name: "server_config".to_string(),
description: None,
fields: vec![
NickelFieldIR {
path: vec!["server".to_string(), "hostname".to_string()],
flat_name: "server_hostname".to_string(),
nickel_type: NickelType::String,
doc: Some("Server hostname".to_string()),
default: None,
optional: false,
contract: None,
group: Some("server".to_string()),
},
NickelFieldIR {
path: vec!["server".to_string(), "port".to_string()],
flat_name: "server_port".to_string(),
nickel_type: NickelType::Number,
doc: Some("Server port".to_string()),
default: Some(json!(8080)),
optional: false,
contract: None,
group: Some("server".to_string()),
},
NickelFieldIR {
path: vec!["database".to_string(), "host".to_string()],
flat_name: "database_host".to_string(),
nickel_type: NickelType::String,
doc: Some("Database host".to_string()),
default: None,
optional: false,
contract: None,
group: Some("database".to_string()),
},
],
};
// Generate form with grouping
let form = TomlGenerator::generate(&schema, false, true)
.expect("Form generation failed");
// Verify groups are created
assert!(form.items.len() > 0);
// Verify fields have groups
let server_hostname = form.fields.iter().find(|f| f.name == "server_hostname").unwrap();
assert_eq!(server_hostname.group, Some("server".to_string()));
// Create results
let mut results = HashMap::new();
results.insert("server_hostname".to_string(), json!("api.example.com"));
results.insert("server_port".to_string(), json!(9000));
results.insert("database_host".to_string(), json!("db.example.com"));
// Serialize and verify nested structure is restored
let nickel_output = NickelSerializer::serialize(&results, &schema)
.expect("Serialization failed");
assert!(nickel_output.contains("server"));
assert!(nickel_output.contains("database"));
assert!(nickel_output.contains("api.example.com"));
assert!(nickel_output.contains("db.example.com"));
assert!(nickel_output.contains("9000"));
}
#[test]
fn test_array_field_serialization() {
let schema = NickelSchemaIR {
name: "array_config".to_string(),
description: None,
fields: vec![
NickelFieldIR {
path: vec!["tags".to_string()],
flat_name: "tags".to_string(),
nickel_type: NickelType::Array(Box::new(NickelType::String)),
doc: Some("Configuration tags".to_string()),
default: None,
optional: false,
contract: None,
group: None,
},
],
};
let mut results = HashMap::new();
results.insert("tags".to_string(), json!(["prod", "critical", "network"]));
let nickel_output = NickelSerializer::serialize(&results, &schema)
.expect("Serialization failed");
assert!(nickel_output.contains("prod"));
assert!(nickel_output.contains("critical"));
assert!(nickel_output.contains("network"));
assert!(nickel_output.contains("["));
assert!(nickel_output.contains("]"));
}
#[test]
fn test_contract_validation_non_empty_string() {
// Valid non-empty string
let result = ContractValidator::validate(
&json!("hello"),
"String | std.string.NonEmpty",
);
assert!(result.is_ok());
// Empty string should fail
let result = ContractValidator::validate(
&json!(""),
"String | std.string.NonEmpty",
);
assert!(result.is_err());
}
#[test]
fn test_contract_validation_number_range() {
// Valid number in range
let result = ContractValidator::validate(
&json!(50),
"Number | std.number.between 0 100",
);
assert!(result.is_ok());
// Number out of range
let result = ContractValidator::validate(
&json!(150),
"Number | std.number.between 0 100",
);
assert!(result.is_err());
}
#[test]
fn test_contract_validation_string_length() {
// Valid length
let result = ContractValidator::validate(
&json!("hello"),
"String | std.string.length.min 3",
);
assert!(result.is_ok());
// Too short
let result = ContractValidator::validate(
&json!("hi"),
"String | std.string.length.min 3",
);
assert!(result.is_err());
// Valid max length
let result = ContractValidator::validate(
&json!("hi"),
"String | std.string.length.max 5",
);
assert!(result.is_ok());
// Too long
let result = ContractValidator::validate(
&json!("hello world"),
"String | std.string.length.max 5",
);
assert!(result.is_err());
}
#[test]
fn test_form_definition_from_schema_ir() {
// Create schema
let schema = NickelSchemaIR {
name: "test_form".to_string(),
description: Some("Test form".to_string()),
fields: vec![
NickelFieldIR {
path: vec!["name".to_string()],
flat_name: "name".to_string(),
nickel_type: NickelType::String,
doc: Some("Your name".to_string()),
default: None,
optional: false,
contract: Some("String | std.string.NonEmpty".to_string()),
group: None,
},
],
};
// Generate form
let form = TomlGenerator::generate(&schema, false, false)
.expect("Form generation failed");
// Convert to TOML
let toml_str = toml::to_string_pretty(&form)
.expect("TOML serialization failed");
// Parse back
let parsed_form: form_parser::FormDefinition = toml::from_str(&toml_str)
.expect("TOML parsing failed");
// Verify round-trip
assert_eq!(parsed_form.name, "test_form");
assert_eq!(parsed_form.fields.len(), 1);
assert_eq!(parsed_form.fields[0].name, "name");
assert_eq!(
parsed_form.fields[0].nickel_contract,
Some("String | std.string.NonEmpty".to_string())
);
}
#[test]
fn test_metadata_extraction_with_optional_fields() {
// Parse JSON metadata (simulating nickel query output)
let metadata = json!({
"name": {
"doc": "User full name",
"type": "String",
"default": "Anonymous"
},
"age": {
"doc": "User age in years",
"type": "Number",
"optional": true
},
"active": {
"type": "Bool",
"optional": false,
"default": true
}
});
let schema = MetadataParser::parse(metadata)
.expect("Metadata parsing failed");
// Verify fields
assert_eq!(schema.fields.len(), 3);
// Check optional flags
let name_field = schema.fields.iter().find(|f| f.flat_name == "name").unwrap();
assert!(!name_field.optional);
let age_field = schema.fields.iter().find(|f| f.flat_name == "age").unwrap();
assert!(age_field.optional);
let active_field = schema.fields.iter().find(|f| f.flat_name == "active").unwrap();
assert!(!active_field.optional);
}
#[test]
fn test_type_mapping_all_types() {
let schema = NickelSchemaIR {
name: "types_test".to_string(),
description: None,
fields: vec![
NickelFieldIR {
path: vec!["str_field".to_string()],
flat_name: "str_field".to_string(),
nickel_type: NickelType::String,
doc: None,
default: None,
optional: false,
contract: None,
group: None,
},
NickelFieldIR {
path: vec!["num_field".to_string()],
flat_name: "num_field".to_string(),
nickel_type: NickelType::Number,
doc: None,
default: None,
optional: false,
contract: None,
group: None,
},
NickelFieldIR {
path: vec!["bool_field".to_string()],
flat_name: "bool_field".to_string(),
nickel_type: NickelType::Bool,
doc: None,
default: None,
optional: false,
contract: None,
group: None,
},
NickelFieldIR {
path: vec!["array_field".to_string()],
flat_name: "array_field".to_string(),
nickel_type: NickelType::Array(Box::new(NickelType::String)),
doc: None,
default: None,
optional: false,
contract: None,
group: None,
},
],
};
let form = TomlGenerator::generate(&schema, false, false)
.expect("Form generation failed");
// Verify type mapping
assert_eq!(form.fields[0].field_type, form_parser::FieldType::Text);
assert_eq!(form.fields[1].field_type, form_parser::FieldType::Custom);
assert_eq!(form.fields[1].custom_type, Some("f64".to_string()));
assert_eq!(form.fields[2].field_type, form_parser::FieldType::Confirm);
assert_eq!(form.fields[3].field_type, form_parser::FieldType::Editor);
}
#[test]
fn test_enum_options_extraction_from_doc() {
let field = NickelFieldIR {
path: vec!["status".to_string()],
flat_name: "status".to_string(),
nickel_type: NickelType::Array(Box::new(NickelType::String)),
doc: Some("Status selection. Options: pending, active, completed".to_string()),
default: None,
optional: false,
contract: None,
group: None,
};
let schema = NickelSchemaIR {
name: "test".to_string(),
description: None,
fields: vec![field],
};
let form = TomlGenerator::generate(&schema, false, false)
.expect("Form generation failed");
// Verify options extracted
let status_field = &form.fields[0];
assert_eq!(
status_field.options,
Some(vec![
"pending".to_string(),
"active".to_string(),
"completed".to_string(),
])
);
}
#[test]
fn test_template_rendering_simple() {
#[cfg(feature = "templates")]
{
let mut engine = TemplateEngine::new();
let mut values = HashMap::new();
values.insert("hostname".to_string(), json!("server1"));
values.insert("port".to_string(), json!(8080));
let template = r#"
server {
hostname : String = "{{ hostname }}"
port : Number = {{ port }}
}
"#;
let result = engine.render_str(template, &values);
assert!(result.is_ok());
let output = result.unwrap();
assert!(output.contains("server1"));
assert!(output.contains("8080"));
}
}
#[test]
fn test_template_rendering_with_loop() {
#[cfg(feature = "templates")]
{
let mut engine = TemplateEngine::new();
let mut values = HashMap::new();
values.insert("servers".to_string(), json!([
{"name": "web-01", "ip": "192.168.1.10"},
{"name": "web-02", "ip": "192.168.1.11"},
]));
let template = r#"servers = [
{% for server in servers %}
{ name = "{{ server.name }}", ip = "{{ server.ip }}" },
{% endfor %}
]"#;
let result = engine.render_str(template, &values);
assert!(result.is_ok());
let output = result.unwrap();
assert!(output.contains("web-01"));
assert!(output.contains("web-02"));
assert!(output.contains("192.168.1.10"));
}
}
#[test]
fn test_template_rendering_with_conditional() {
#[cfg(feature = "templates")]
{
let mut engine = TemplateEngine::new();
let mut values = HashMap::new();
values.insert("production".to_string(), json!(true));
values.insert("replicas".to_string(), json!(3));
let template = r#"{% if production %}
replicas : Number = {{ replicas }}
{% else %}
replicas : Number = 1
{% endif %}"#;
let result = engine.render_str(template, &values);
assert!(result.is_ok());
let output = result.unwrap();
assert!(output.contains("replicas"));
assert!(output.contains("3"));
assert!(!output.contains("= 1"));
}
}
#[test]
fn test_full_workflow_integration() {
// Create a realistic schema
let schema = NickelSchemaIR {
name: "app_config".to_string(),
description: Some("Application configuration".to_string()),
fields: vec![
NickelFieldIR {
path: vec!["app".to_string(), "name".to_string()],
flat_name: "app_name".to_string(),
nickel_type: NickelType::String,
doc: Some("Application name".to_string()),
default: None,
optional: false,
contract: Some("String | std.string.NonEmpty".to_string()),
group: Some("app".to_string()),
},
NickelFieldIR {
path: vec!["app".to_string(), "version".to_string()],
flat_name: "app_version".to_string(),
nickel_type: NickelType::String,
doc: Some("Application version (e.g., 1.0.0)".to_string()),
default: Some(json!("1.0.0")),
optional: false,
contract: None,
group: Some("app".to_string()),
},
NickelFieldIR {
path: vec!["server".to_string(), "host".to_string()],
flat_name: "server_host".to_string(),
nickel_type: NickelType::String,
doc: Some("Server hostname".to_string()),
default: Some(json!("localhost")),
optional: false,
contract: None,
group: Some("server".to_string()),
},
NickelFieldIR {
path: vec!["server".to_string(), "port".to_string()],
flat_name: "server_port".to_string(),
nickel_type: NickelType::Number,
doc: Some("Server port".to_string()),
default: Some(json!(8000)),
optional: false,
contract: Some("Number | std.number.between 1 65535".to_string()),
group: Some("server".to_string()),
},
],
};
// Step 1: Generate form
let form = TomlGenerator::generate(&schema, false, true)
.expect("Form generation failed");
assert_eq!(form.fields.len(), 4);
// Step 2: Serialize to TOML and parse back
let toml_str = toml::to_string_pretty(&form)
.expect("TOML serialization failed");
let parsed_form: form_parser::FormDefinition = toml::from_str(&toml_str)
.expect("TOML parsing failed");
assert_eq!(parsed_form.fields.len(), 4);
// Step 3: Create form results
let mut results = HashMap::new();
results.insert("app_name".to_string(), json!("MyApp"));
results.insert("app_version".to_string(), json!("2.0.0"));
results.insert("server_host".to_string(), json!("0.0.0.0"));
results.insert("server_port".to_string(), json!(3000));
// Step 4: Validate contracts
assert!(ContractValidator::validate(&json!("MyApp"), "String | std.string.NonEmpty").is_ok());
assert!(ContractValidator::validate(&json!(3000), "Number | std.number.between 1 65535").is_ok());
// Step 5: Serialize to Nickel
let nickel_output = NickelSerializer::serialize(&results, &schema)
.expect("Serialization failed");
// Step 6: Verify output
assert!(nickel_output.contains("MyApp"));
assert!(nickel_output.contains("2.0.0"));
assert!(nickel_output.contains("0.0.0.0"));
assert!(nickel_output.contains("3000"));
assert!(nickel_output.contains("String | std.string.NonEmpty"));
assert!(nickel_output.contains("Number | std.number.between 1 65535"));
// Verify nested structure
assert!(nickel_output.contains("app"));
assert!(nickel_output.contains("server"));
}
#[test]
fn test_optional_fields_handling() {
let schema = NickelSchemaIR {
name: "optional_test".to_string(),
description: None,
fields: vec![
NickelFieldIR {
path: vec!["required_field".to_string()],
flat_name: "required_field".to_string(),
nickel_type: NickelType::String,
doc: None,
default: None,
optional: false,
contract: None,
group: None,
},
NickelFieldIR {
path: vec!["optional_field".to_string()],
flat_name: "optional_field".to_string(),
nickel_type: NickelType::String,
doc: None,
default: None,
optional: true,
contract: None,
group: None,
},
],
};
let form = TomlGenerator::generate(&schema, false, false)
.expect("Form generation failed");
// Check required field
let required = form.fields.iter().find(|f| f.name == "required_field").unwrap();
assert_eq!(required.required, Some(true));
// Check optional field
let optional = form.fields.iter().find(|f| f.name == "optional_field").unwrap();
assert_eq!(optional.required, Some(false));
}
#[test]
fn test_defaults_preservation() {
let schema = NickelSchemaIR {
name: "defaults_test".to_string(),
description: None,
fields: vec![
NickelFieldIR {
path: vec!["string_with_default".to_string()],
flat_name: "string_with_default".to_string(),
nickel_type: NickelType::String,
doc: None,
default: Some(json!("default_value")),
optional: false,
contract: None,
group: None,
},
NickelFieldIR {
path: vec!["number_with_default".to_string()],
flat_name: "number_with_default".to_string(),
nickel_type: NickelType::Number,
doc: None,
default: Some(json!(42)),
optional: false,
contract: None,
group: None,
},
],
};
let form = TomlGenerator::generate(&schema, false, false)
.expect("Form generation failed");
// Check defaults are preserved
let string_field = form.fields.iter().find(|f| f.name == "string_with_default").unwrap();
assert_eq!(string_field.default, Some("default_value".to_string()));
let number_field = form.fields.iter().find(|f| f.name == "number_with_default").unwrap();
assert_eq!(number_field.default, Some("42".to_string()));
}