synthra 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.rspec +4 -0
- data/.rubocop.yml +40 -0
- data/.yardopts +18 -0
- data/ACCESS_DOCS.md +116 -0
- data/ADVANCED_FEATURES_SUMMARY.md +245 -0
- data/CHANGELOG.md +498 -0
- data/CODE_OF_CONDUCT.md +122 -0
- data/CONTRIBUTING.md +307 -0
- data/DOCUMENTATION.md +204 -0
- data/DOCUMENTATION_GUIDE.md +194 -0
- data/LICENSE +22 -0
- data/README.md +1458 -0
- data/Rakefile +128 -0
- data/benchmark/README.md +591 -0
- data/benchmark/batch_generation.rb +63 -0
- data/benchmark/engine_comparison.rb +295 -0
- data/benchmark/single_record.rb +108 -0
- data/benchmark/streaming.rb +74 -0
- data/docs/ACTIVERECORD_INFERENCE.md +524 -0
- data/docs/API_SERVER.md +278 -0
- data/docs/CONFIG_FILE.md +315 -0
- data/docs/DATA_CONTRACTS.md +312 -0
- data/docs/ENHANCED_REPL.md +304 -0
- data/docs/FACTORY_BOT.md +271 -0
- data/docs/GITHUB_ACTION.md +420 -0
- data/docs/GRAPHQL_EXPORT.md +303 -0
- data/docs/GRAPHQL_FEDERATION.md +651 -0
- data/docs/HOW_TO_GENERATE_DOCS.md +252 -0
- data/docs/LSP.md +189 -0
- data/docs/MIGRATION_GENERATOR.md +547 -0
- data/docs/MOCK_SERVER.md +280 -0
- data/docs/NATIVE_ENGINE.md +298 -0
- data/docs/OPENAPI_EXPORT.md +252 -0
- data/docs/PERFORMANCE_MODE.md +228 -0
- data/docs/PERSONAS.md +547 -0
- data/docs/PROPERTY_TESTING.md +284 -0
- data/docs/PROTOBUF_EXPORT.md +276 -0
- data/docs/QUALITY_METRICS.md +555 -0
- data/docs/QUICK_REFERENCE.md +88 -0
- data/docs/RAILS_ENGINE.md +404 -0
- data/docs/RAILS_INTEGRATION.md +454 -0
- data/docs/README.md +175 -0
- data/docs/REPL_QUICK_REFERENCE.md +149 -0
- data/docs/RUST_INTEGRATION_GUIDE.md +1376 -0
- data/docs/SCENARIOS.md +576 -0
- data/docs/SECURITY_TESTING.md +318 -0
- data/docs/SNAPSHOT_TESTING.md +308 -0
- data/docs/STRING_TEXT_TYPES_REFERENCE.md +512 -0
- data/docs/TERRAFORM_EXPORT.md +447 -0
- data/docs/TIME_TRAVEL.md +507 -0
- data/docs/USAGE_GUIDE.md +980 -0
- data/docs/WEBHOOK_SIMULATOR.md +579 -0
- data/docs/advanced_features.md +183 -0
- data/docs/api_reference.md +153 -0
- data/docs/behaviors.md +138 -0
- data/docs/best_practices.md +191 -0
- data/docs/dsl_reference.md +172 -0
- data/docs/examples.md +162 -0
- data/docs/generation_modes.md +125 -0
- data/docs/getting_started.md +92 -0
- data/docs/index.html +177 -0
- data/docs/strategic/CLOUD_REGISTRY_ARCHITECTURE.md +268 -0
- data/docs/strategic/SIMD_OPTIMIZATION_STRATEGY.md +153 -0
- data/docs/troubleshooting.md +215 -0
- data/docs/type_reference.md +224 -0
- data/examples/hostile_mode_demo.rb +64 -0
- data/exe/synthra +6 -0
- data/ext/synthra_native/Cargo.lock +972 -0
- data/ext/synthra_native/Cargo.toml +46 -0
- data/ext/synthra_native/extconf.rb +41 -0
- data/ext/synthra_native/src/generator.rs +397 -0
- data/ext/synthra_native/src/lib.rs +229 -0
- data/ext/synthra_native/src/types.rs +1485 -0
- data/lib/generators/synthra/install_generator.rb +86 -0
- data/lib/generators/synthra/templates/api_response.dsl +15 -0
- data/lib/generators/synthra/templates/fake_data.rake +116 -0
- data/lib/generators/synthra/templates/synthra.yml +31 -0
- data/lib/generators/synthra/templates/synthra_support.rb +19 -0
- data/lib/generators/synthra/templates/user.dsl +11 -0
- data/lib/synthra/activerecord_inference.rb +394 -0
- data/lib/synthra/api.rb +381 -0
- data/lib/synthra/api_server.rb +718 -0
- data/lib/synthra/behaviors/applicator.rb +165 -0
- data/lib/synthra/behaviors/base.rb +155 -0
- data/lib/synthra/behaviors/close_connection.rb +60 -0
- data/lib/synthra/behaviors/deprecated.rb +100 -0
- data/lib/synthra/behaviors/failure.rb +69 -0
- data/lib/synthra/behaviors/latency.rb +103 -0
- data/lib/synthra/behaviors/partial_data.rb +83 -0
- data/lib/synthra/behaviors/randomize_order.rb +74 -0
- data/lib/synthra/behaviors/registry.rb +238 -0
- data/lib/synthra/behaviors/simulate_error.rb +79 -0
- data/lib/synthra/cli/commands/base.rb +105 -0
- data/lib/synthra/cli/commands/diff.rb +227 -0
- data/lib/synthra/cli/commands/docs.rb +67 -0
- data/lib/synthra/cli/commands/export.rb +150 -0
- data/lib/synthra/cli/commands/generate.rb +72 -0
- data/lib/synthra/cli/commands/import.rb +80 -0
- data/lib/synthra/cli/commands/lint.rb +53 -0
- data/lib/synthra/cli/commands/live.rb +57 -0
- data/lib/synthra/cli/commands/seed.rb +99 -0
- data/lib/synthra/cli/commands/validate.rb +28 -0
- data/lib/synthra/cli.rb +2471 -0
- data/lib/synthra/config_file.rb +129 -0
- data/lib/synthra/configuration.rb +281 -0
- data/lib/synthra/contracts_registry.rb +408 -0
- data/lib/synthra/database_seeder.rb +268 -0
- data/lib/synthra/deterministic_ids.rb +218 -0
- data/lib/synthra/documentation_generator.rb +414 -0
- data/lib/synthra/engine.rb +251 -0
- data/lib/synthra/errors.rb +1169 -0
- data/lib/synthra/export/base.rb +85 -0
- data/lib/synthra/export/csv.rb +101 -0
- data/lib/synthra/export/graphql.rb +266 -0
- data/lib/synthra/export/graphql_federation.rb +377 -0
- data/lib/synthra/export/graphviz.rb +258 -0
- data/lib/synthra/export/javascript.rb +327 -0
- data/lib/synthra/export/json_data.rb +61 -0
- data/lib/synthra/export/json_schema.rb +290 -0
- data/lib/synthra/export/openapi.rb +514 -0
- data/lib/synthra/export/protobuf.rb +483 -0
- data/lib/synthra/export/python.rb +560 -0
- data/lib/synthra/export/sql.rb +381 -0
- data/lib/synthra/export/sql_insert.rb +152 -0
- data/lib/synthra/export/terraform.rb +501 -0
- data/lib/synthra/export/type_mapping.rb +389 -0
- data/lib/synthra/export/typescript.rb +300 -0
- data/lib/synthra/export/xml_data.rb +104 -0
- data/lib/synthra/export/yaml_data.rb +56 -0
- data/lib/synthra/export.rb +404 -0
- data/lib/synthra/factory_bot_integration.rb +157 -0
- data/lib/synthra/field.rb +440 -0
- data/lib/synthra/functions/registry.rb +104 -0
- data/lib/synthra/generator/context.rb +372 -0
- data/lib/synthra/generator/engine.rb +336 -0
- data/lib/synthra/generator/faker_adapter.rb +425 -0
- data/lib/synthra/generator/modes.rb +444 -0
- data/lib/synthra/generator/resolver.rb +256 -0
- data/lib/synthra/generator/rng.rb +273 -0
- data/lib/synthra/generator/streamer.rb +63 -0
- data/lib/synthra/generator/uniqueness.rb +118 -0
- data/lib/synthra/initializer.rb +97 -0
- data/lib/synthra/limits.rb +279 -0
- data/lib/synthra/live_preview.rb +518 -0
- data/lib/synthra/loader_config.rb +78 -0
- data/lib/synthra/lsp/server.rb +689 -0
- data/lib/synthra/migration_generator.rb +301 -0
- data/lib/synthra/mixin.rb +174 -0
- data/lib/synthra/mock_server.rb +458 -0
- data/lib/synthra/native_engine.rb +304 -0
- data/lib/synthra/openapi_importer.rb +228 -0
- data/lib/synthra/output/json_formatter.rb +40 -0
- data/lib/synthra/output/ndjson_formatter.rb +55 -0
- data/lib/synthra/parser/ast.rb +1287 -0
- data/lib/synthra/parser/lexer.rb +1152 -0
- data/lib/synthra/parser/parser.rb +1664 -0
- data/lib/synthra/parser/tokens.rb +461 -0
- data/lib/synthra/performance_mode.rb +364 -0
- data/lib/synthra/personas.rb +397 -0
- data/lib/synthra/property_testing.rb +172 -0
- data/lib/synthra/quality_metrics.rb +405 -0
- data/lib/synthra/rails_test_helper.rb +248 -0
- data/lib/synthra/registry.rb +533 -0
- data/lib/synthra/relationships.rb +193 -0
- data/lib/synthra/repl/enhanced_repl.rb +607 -0
- data/lib/synthra/repl/formatter.rb +191 -0
- data/lib/synthra/scenarios.rb +423 -0
- data/lib/synthra/schema.rb +605 -0
- data/lib/synthra/schema_inheritance.rb +104 -0
- data/lib/synthra/schema_versioning.rb +212 -0
- data/lib/synthra/snapshot_testing.rb +199 -0
- data/lib/synthra/time_travel.rb +338 -0
- data/lib/synthra/type_definitions.rb +274 -0
- data/lib/synthra/types/address_location/addresses.rb +125 -0
- data/lib/synthra/types/address_location/airports.rb +200 -0
- data/lib/synthra/types/address_location/banks_hospitals.rb +207 -0
- data/lib/synthra/types/address_location/locations.rb +406 -0
- data/lib/synthra/types/base.rb +48 -0
- data/lib/synthra/types/commerce_products/commerce.rb +119 -0
- data/lib/synthra/types/commerce_products/companies.rb +71 -0
- data/lib/synthra/types/commerce_products/construction.rb +126 -0
- data/lib/synthra/types/commerce_products/products.rb +205 -0
- data/lib/synthra/types/core/collections.rb +513 -0
- data/lib/synthra/types/core/defaults.rb +76 -0
- data/lib/synthra/types/core/enums.rb +102 -0
- data/lib/synthra/types/core/identifiers.rb +445 -0
- data/lib/synthra/types/core/primitives.rb +586 -0
- data/lib/synthra/types/core/references.rb +466 -0
- data/lib/synthra/types/core/sequences.rb +304 -0
- data/lib/synthra/types/crypto/crypto.rb +162 -0
- data/lib/synthra/types/date_time/dates.rb +387 -0
- data/lib/synthra/types/finance_banking/banking.rb +424 -0
- data/lib/synthra/types/finance_banking/credit_cards.rb +24 -0
- data/lib/synthra/types/finance_banking/identifiers.rb +93 -0
- data/lib/synthra/types/formula.rb +184 -0
- data/lib/synthra/types/health_medical/medical.rb +150 -0
- data/lib/synthra/types/hostile_payloads.rb +122 -0
- data/lib/synthra/types/json_array.rb +51 -0
- data/lib/synthra/types/media_entertainment/media.rb +62 -0
- data/lib/synthra/types/naughty_string.rb +30 -0
- data/lib/synthra/types/personal_names/chinese.rb +41 -0
- data/lib/synthra/types/personal_names/identifiers.rb +187 -0
- data/lib/synthra/types/personal_names/names.rb +246 -0
- data/lib/synthra/types/personal_names/national_id.rb +89 -0
- data/lib/synthra/types/personal_names/titles_suffixes.rb +41 -0
- data/lib/synthra/types/regex.rb +248 -0
- data/lib/synthra/types/registry.rb +182 -0
- data/lib/synthra/types/repeating_element.rb +50 -0
- data/lib/synthra/types/scenario.rb +29 -0
- data/lib/synthra/types/technology_internet/apps.rb +67 -0
- data/lib/synthra/types/technology_internet/communication.rb +162 -0
- data/lib/synthra/types/technology_internet/devices.rb +80 -0
- data/lib/synthra/types/technology_internet/formats.rb +139 -0
- data/lib/synthra/types/technology_internet/networking.rb +143 -0
- data/lib/synthra/types/template.rb +92 -0
- data/lib/synthra/types/text_content/business.rb +128 -0
- data/lib/synthra/types/text_content/colors.rb +70 -0
- data/lib/synthra/types/text_content/misc.rb +237 -0
- data/lib/synthra/types/text_content/security.rb +130 -0
- data/lib/synthra/types/text_content/text_generation.rb +532 -0
- data/lib/synthra/types/travel/travel.rb +150 -0
- data/lib/synthra/utils/string_distance.rb +133 -0
- data/lib/synthra/validator/dsl_validator.rb +339 -0
- data/lib/synthra/validator/path_validator.rb +615 -0
- data/lib/synthra/version.rb +35 -0
- data/lib/synthra/webhook_simulator.rb +341 -0
- data/lib/synthra.rb +259 -0
- data/schemas/address.dsl +16 -0
- data/schemas/api_response.dsl +8 -0
- data/schemas/error_payload.dsl +9 -0
- data/schemas/order.dsl +10 -0
- data/schemas/order_item.dsl +8 -0
- data/schemas/payment.dsl +11 -0
- data/schemas/social_post.dsl +13 -0
- data/schemas/user.dsl +10 -0
- data/scripts/batch_fix_all.rb +96 -0
- data/scripts/delete_old_files.rb +32 -0
- data/scripts/fix_all_grouped_files.rb +137 -0
- data/scripts/fix_all_indentation.rb +48 -0
- data/scripts/fix_all_syntax.rb +124 -0
- data/scripts/fix_grouped_files.rb +184 -0
- data/scripts/fix_syntax_errors.rb +108 -0
- data/scripts/group_domain_types.rb +112 -0
- data/scripts/group_domain_types_fixed.rb +150 -0
- data/scripts/merge_domains_to_one_file.rb +68 -0
- data/scripts/move_existing_types.rb +130 -0
- data/scripts/split_grouped_types.rb +142 -0
- data/tech_docs/README.md +134 -0
- data/tech_docs/advanced/streaming.md +405 -0
- data/tech_docs/advanced/thread_safety.md +253 -0
- data/tech_docs/api/overview.md +485 -0
- data/tech_docs/appendices/type_chart.md +193 -0
- data/tech_docs/basic_concepts.md +383 -0
- data/tech_docs/behaviors/overview.md +346 -0
- data/tech_docs/dsl/complex_types.md +790 -0
- data/tech_docs/dsl/core_types.md +464 -0
- data/tech_docs/dsl/datetime_types.md +325 -0
- data/tech_docs/dsl/field_modifiers.md +414 -0
- data/tech_docs/dsl/grammar.md +431 -0
- data/tech_docs/dsl/schema_definition.md +399 -0
- data/tech_docs/export/README.md +276 -0
- data/tech_docs/installation.md +273 -0
- data/tech_docs/integration/ci_cd.md +707 -0
- data/tech_docs/integration/ci_cd_guide.md +579 -0
- data/tech_docs/integration/factory_bot.md +485 -0
- data/tech_docs/integration/rails.md +630 -0
- data/tech_docs/integration/rspec.md +449 -0
- data/tech_docs/modes/overview.md +350 -0
- data/tech_docs/performance/NATIVE_RUST_EXTENSION.md +1270 -0
- data/tech_docs/performance/OPTIMIZATION_GUIDE.md +901 -0
- data/tech_docs/quick_start.md +256 -0
- data/tech_docs/templates/README.md +805 -0
- data/tech_docs/tutorials/advanced.md +371 -0
- data/tech_docs/tutorials/getting_started.md +189 -0
- data/tech_docs/tutorials/intermediate.md +231 -0
- data/tech_docs/tutorials/template_gallery.md +569 -0
- data/tech_example/01_basic_usage.rb +238 -0
- data/tech_example/02_types_demo.rb +336 -0
- data/tech_example/04_cli_usage.md +429 -0
- data/tech_example/05_database_seeding.rb +283 -0
- data/tech_example/07_rspec_integration.rb +359 -0
- data/tech_example/10_custom_types.rb +387 -0
- data/tech_example/12_behaviors.rb +372 -0
- data/tech_example/13_twitter_dm_example.rb +120 -0
- data/tech_example/14_exact_json_structure.rb +253 -0
- data/tech_example/16_lsp_server.rb +56 -0
- data/tech_example/17_property_testing.rb +92 -0
- data/tech_example/18_enhanced_repl.rb +120 -0
- data/tech_example/19_security_fuzzing.rb +145 -0
- data/tech_example/NEW_FEATURES.md +169 -0
- data/tech_example/README.md +118 -0
- data/tech_example/schemas/api_response.dsl +123 -0
- data/tech_example/schemas/ecommerce.dsl +121 -0
- data/tech_example/schemas/twitter_dm.dsl +109 -0
- data/tech_example/schemas/user.dsl +36 -0
- data/vscode-extension/README.md +246 -0
- data/vscode-extension/language-configuration.json +31 -0
- data/vscode-extension/package.json +55 -0
- data/vscode-extension/snippets/fakedatadsl.json +198 -0
- data/vscode-extension/syntaxes/fakedatadsl.tmLanguage.json +128 -0
- metadata +478 -0
|
@@ -0,0 +1,607 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# =============================================================================
|
|
4
|
+
# Synthra Enhanced REPL with History, Aliases, Export, and Tab Completion
|
|
5
|
+
# =============================================================================
|
|
6
|
+
#
|
|
7
|
+
# Provides enhanced REPL features:
|
|
8
|
+
# - Command aliases (t: table, d: debug, i: inspect)
|
|
9
|
+
# - Readline-based command history with arrow key navigation
|
|
10
|
+
# - Tab completion for schema names and commands
|
|
11
|
+
# - Export to file (gen User 100 > users.json)
|
|
12
|
+
# - Improved error messages with "did you mean" suggestions
|
|
13
|
+
#
|
|
14
|
+
# Note: Most methods in this file are interactive and marked with :nocov:
|
|
15
|
+
# as they require stdin/stdout interaction that's difficult to test.
|
|
16
|
+
#
|
|
17
|
+
# =============================================================================
|
|
18
|
+
|
|
19
|
+
require_relative "formatter"
|
|
20
|
+
|
|
21
|
+
module Synthra
|
|
22
|
+
module REPL
|
|
23
|
+
# Command aliases for the REPL
|
|
24
|
+
# Maps short aliases to full command names
|
|
25
|
+
COMMAND_ALIASES = {
|
|
26
|
+
"t" => "table",
|
|
27
|
+
"d" => "debug",
|
|
28
|
+
"i" => "inspect",
|
|
29
|
+
"g" => "gen",
|
|
30
|
+
"l" => "list",
|
|
31
|
+
"h" => "help",
|
|
32
|
+
"q" => "quit"
|
|
33
|
+
}.freeze
|
|
34
|
+
|
|
35
|
+
# All available REPL commands
|
|
36
|
+
COMMANDS = %w[
|
|
37
|
+
load list info gen table debug inspect seed mode help quit exit
|
|
38
|
+
].freeze
|
|
39
|
+
|
|
40
|
+
# Enhanced REPL with history, completion, and export
|
|
41
|
+
class EnhancedREPL
|
|
42
|
+
# @return [Registry] the schema registry
|
|
43
|
+
attr_reader :registry
|
|
44
|
+
|
|
45
|
+
# @return [Integer, nil] the current seed for deterministic generation
|
|
46
|
+
attr_reader :current_seed
|
|
47
|
+
|
|
48
|
+
# @return [Symbol] the current generation mode
|
|
49
|
+
attr_reader :current_mode
|
|
50
|
+
|
|
51
|
+
# @return [Array<String>] command history
|
|
52
|
+
attr_reader :history
|
|
53
|
+
|
|
54
|
+
# Initialize the Enhanced REPL
|
|
55
|
+
#
|
|
56
|
+
# @param registry [Registry] optional pre-loaded registry
|
|
57
|
+
# @param verbose [Boolean] enable verbose output
|
|
58
|
+
#
|
|
59
|
+
def initialize(registry: nil, verbose: false)
|
|
60
|
+
@registry = registry || Registry.new
|
|
61
|
+
@current_seed = nil
|
|
62
|
+
@current_mode = :random
|
|
63
|
+
@verbose = verbose
|
|
64
|
+
@history = []
|
|
65
|
+
@running = false
|
|
66
|
+
setup_readline
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Check if readline is available
|
|
70
|
+
#
|
|
71
|
+
# @return [Boolean] true if readline is available
|
|
72
|
+
#
|
|
73
|
+
def readline_available?
|
|
74
|
+
@readline_available
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Run the REPL
|
|
78
|
+
#
|
|
79
|
+
# @return [Integer] exit code
|
|
80
|
+
#
|
|
81
|
+
# :nocov:
|
|
82
|
+
def run
|
|
83
|
+
@running = true
|
|
84
|
+
print_welcome
|
|
85
|
+
|
|
86
|
+
while @running
|
|
87
|
+
input = read_input
|
|
88
|
+
break if input.nil?
|
|
89
|
+
|
|
90
|
+
input = input.strip
|
|
91
|
+
next if input.empty?
|
|
92
|
+
|
|
93
|
+
@history << input
|
|
94
|
+
process_command(input)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
CLI::EXIT_SUCCESS
|
|
98
|
+
end
|
|
99
|
+
# :nocov:
|
|
100
|
+
|
|
101
|
+
# Process a single command
|
|
102
|
+
#
|
|
103
|
+
# @param input [String] the command input
|
|
104
|
+
# @return [void]
|
|
105
|
+
#
|
|
106
|
+
def process_command(input)
|
|
107
|
+
# Check for export redirect syntax: command > filename
|
|
108
|
+
if input.include?(" > ")
|
|
109
|
+
command_part, filename = input.split(" > ", 2)
|
|
110
|
+
execute_with_export(command_part.strip, filename.strip)
|
|
111
|
+
else
|
|
112
|
+
execute_command(input)
|
|
113
|
+
end
|
|
114
|
+
rescue => e
|
|
115
|
+
puts "ā Error: #{e.message}"
|
|
116
|
+
puts e.backtrace.first(3).join("\n") if @verbose
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Stop the REPL
|
|
120
|
+
#
|
|
121
|
+
# @return [void]
|
|
122
|
+
#
|
|
123
|
+
def stop
|
|
124
|
+
@running = false
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
private
|
|
128
|
+
|
|
129
|
+
# Set up readline if available
|
|
130
|
+
#
|
|
131
|
+
# @return [void]
|
|
132
|
+
#
|
|
133
|
+
def setup_readline
|
|
134
|
+
begin
|
|
135
|
+
require "readline"
|
|
136
|
+
@readline_available = true
|
|
137
|
+
setup_completion
|
|
138
|
+
rescue LoadError
|
|
139
|
+
@readline_available = false
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Set up tab completion
|
|
144
|
+
#
|
|
145
|
+
# @return [void]
|
|
146
|
+
#
|
|
147
|
+
def setup_completion
|
|
148
|
+
return unless @readline_available
|
|
149
|
+
|
|
150
|
+
Readline.completion_proc = proc do |input|
|
|
151
|
+
completions_for(input)
|
|
152
|
+
end
|
|
153
|
+
Readline.completion_append_character = " "
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# Get completions for the current input
|
|
157
|
+
#
|
|
158
|
+
# @param input [String] partial input
|
|
159
|
+
# @return [Array<String>] matching completions
|
|
160
|
+
#
|
|
161
|
+
def completions_for(input)
|
|
162
|
+
# Get the line buffer to understand context
|
|
163
|
+
line = Readline.line_buffer rescue input
|
|
164
|
+
|
|
165
|
+
if line.nil? || line.strip.empty? || !line.include?(" ")
|
|
166
|
+
# Complete commands
|
|
167
|
+
all_commands = COMMANDS + COMMAND_ALIASES.keys
|
|
168
|
+
all_commands.select { |c| c.start_with?(input.downcase) }
|
|
169
|
+
else
|
|
170
|
+
# Complete schema names for commands that take them
|
|
171
|
+
command = line.split.first&.downcase
|
|
172
|
+
if %w[gen table debug inspect info g t d i].include?(command)
|
|
173
|
+
@registry.names.select { |name| name.downcase.start_with?(input.downcase) }
|
|
174
|
+
else
|
|
175
|
+
[]
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# Read input from user
|
|
181
|
+
#
|
|
182
|
+
# @return [String, nil] user input or nil on EOF
|
|
183
|
+
#
|
|
184
|
+
# :nocov:
|
|
185
|
+
def read_input
|
|
186
|
+
if @readline_available
|
|
187
|
+
Readline.readline("synthra> ", true)
|
|
188
|
+
else
|
|
189
|
+
print "synthra> "
|
|
190
|
+
$stdin.gets
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
# :nocov:
|
|
194
|
+
|
|
195
|
+
# Print welcome message
|
|
196
|
+
#
|
|
197
|
+
# @return [void]
|
|
198
|
+
#
|
|
199
|
+
# :nocov:
|
|
200
|
+
def print_welcome
|
|
201
|
+
puts "=" * 60
|
|
202
|
+
puts "Synthra Interactive REPL (Enhanced)"
|
|
203
|
+
puts "=" * 60
|
|
204
|
+
puts
|
|
205
|
+
puts "Commands (aliases in parentheses):"
|
|
206
|
+
puts " load <path> - Load schemas from file/directory"
|
|
207
|
+
puts " list (l) - List loaded schemas"
|
|
208
|
+
puts " info <Schema> - Show schema details"
|
|
209
|
+
puts " gen <Schema> [N] (g) - Generate records (JSON)"
|
|
210
|
+
puts " table <Schema> [N] (t)- Generate records (table view)"
|
|
211
|
+
puts " debug <Schema> (d) - Step-through debug generation"
|
|
212
|
+
puts " inspect <Schema> (i) - Generate and show detailed inspection"
|
|
213
|
+
puts " seed <number> - Set seed for deterministic output"
|
|
214
|
+
puts " mode <mode> - Set mode (random/edge/invalid/hostile/mixed)"
|
|
215
|
+
puts " help (h) - Show this help"
|
|
216
|
+
puts " quit/exit (q) - Exit REPL"
|
|
217
|
+
puts
|
|
218
|
+
puts "Export to file:"
|
|
219
|
+
puts " gen User 100 > users.json - Export to JSON file"
|
|
220
|
+
puts " table User 10 > users.txt - Export table to text file"
|
|
221
|
+
puts
|
|
222
|
+
if @readline_available
|
|
223
|
+
puts "Tab completion and command history enabled!"
|
|
224
|
+
else
|
|
225
|
+
puts "Install 'readline' gem for history and tab completion"
|
|
226
|
+
end
|
|
227
|
+
puts
|
|
228
|
+
end
|
|
229
|
+
# :nocov:
|
|
230
|
+
|
|
231
|
+
# Execute command with export to file
|
|
232
|
+
#
|
|
233
|
+
# @param command [String] the command to execute
|
|
234
|
+
# @param filename [String] the file to export to
|
|
235
|
+
# @return [void]
|
|
236
|
+
#
|
|
237
|
+
def execute_with_export(command, filename)
|
|
238
|
+
# Capture output
|
|
239
|
+
output = capture_output { execute_command(command) }
|
|
240
|
+
|
|
241
|
+
if output && !output.strip.empty?
|
|
242
|
+
File.write(filename, output)
|
|
243
|
+
puts "ā
Exported to #{filename} (#{output.bytesize} bytes)"
|
|
244
|
+
else
|
|
245
|
+
puts "ā No output to export"
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
# Capture stdout output from a block
|
|
250
|
+
#
|
|
251
|
+
# @yield block to execute
|
|
252
|
+
# @return [String] captured output
|
|
253
|
+
#
|
|
254
|
+
def capture_output
|
|
255
|
+
original_stdout = $stdout
|
|
256
|
+
$stdout = StringIO.new
|
|
257
|
+
yield
|
|
258
|
+
$stdout.string
|
|
259
|
+
ensure
|
|
260
|
+
$stdout = original_stdout
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
# Execute a single command
|
|
264
|
+
#
|
|
265
|
+
# @param input [String] command input
|
|
266
|
+
# @return [void]
|
|
267
|
+
#
|
|
268
|
+
def execute_command(input)
|
|
269
|
+
return if input.nil? || input.strip.empty?
|
|
270
|
+
|
|
271
|
+
# Expand aliases
|
|
272
|
+
parts = input.split(/\s+/, 2)
|
|
273
|
+
command = parts[0]&.downcase
|
|
274
|
+
rest = parts[1]
|
|
275
|
+
|
|
276
|
+
return if command.nil? || command.empty?
|
|
277
|
+
|
|
278
|
+
# Check for alias
|
|
279
|
+
if COMMAND_ALIASES.key?(command)
|
|
280
|
+
command = COMMAND_ALIASES[command]
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
# Reconstruct full input
|
|
284
|
+
full_input = rest ? "#{command} #{rest}" : command
|
|
285
|
+
|
|
286
|
+
case full_input
|
|
287
|
+
when /^load\s+(.+)$/
|
|
288
|
+
cmd_load($1.strip)
|
|
289
|
+
when "list", "l"
|
|
290
|
+
cmd_list
|
|
291
|
+
when /^info\s+(\w+)$/
|
|
292
|
+
cmd_info($1)
|
|
293
|
+
when /^gen\s+(\w+)(?:\s+(\d+))?$/
|
|
294
|
+
cmd_gen($1, ($2 || "1").to_i)
|
|
295
|
+
when /^table\s+(\w+)(?:\s+(\d+))?$/
|
|
296
|
+
cmd_table($1, ($2 || "1").to_i)
|
|
297
|
+
when /^debug\s+(\w+)$/
|
|
298
|
+
cmd_debug($1)
|
|
299
|
+
when /^inspect\s+(\w+)$/
|
|
300
|
+
cmd_inspect($1)
|
|
301
|
+
when /^seed\s+(\d+)$/
|
|
302
|
+
cmd_seed($1.to_i)
|
|
303
|
+
when /^mode\s+(random|edge|invalid|hostile|mixed)$/
|
|
304
|
+
cmd_mode($1.to_sym)
|
|
305
|
+
when "help", "h"
|
|
306
|
+
cmd_help
|
|
307
|
+
when "quit", "exit", "q"
|
|
308
|
+
cmd_quit
|
|
309
|
+
else
|
|
310
|
+
suggest_command(command)
|
|
311
|
+
end
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
# Suggest similar command for typos
|
|
315
|
+
#
|
|
316
|
+
# @param unknown [String] unknown command
|
|
317
|
+
# @return [void]
|
|
318
|
+
#
|
|
319
|
+
def suggest_command(unknown)
|
|
320
|
+
all_commands = COMMANDS + COMMAND_ALIASES.keys
|
|
321
|
+
suggestions = Utils::StringDistance.similar_strings(unknown, all_commands, max_distance: 2)
|
|
322
|
+
|
|
323
|
+
if suggestions.any?
|
|
324
|
+
puts "Unknown command: #{unknown}"
|
|
325
|
+
puts Utils::StringDistance.format_suggestions(suggestions)
|
|
326
|
+
else
|
|
327
|
+
puts "Unknown command: #{unknown}. Type 'help' for available commands."
|
|
328
|
+
end
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
# Load schemas from path
|
|
332
|
+
#
|
|
333
|
+
# @param path [String] file or directory path
|
|
334
|
+
# @return [void]
|
|
335
|
+
#
|
|
336
|
+
def cmd_load(path)
|
|
337
|
+
if File.directory?(path)
|
|
338
|
+
Dir.glob(File.join(path, "*.dsl")).each do |f|
|
|
339
|
+
@registry.load_file(f, validate: false)
|
|
340
|
+
end
|
|
341
|
+
else
|
|
342
|
+
@registry.load_file(path, validate: false)
|
|
343
|
+
end
|
|
344
|
+
puts "ā
Loaded #{@registry.size} schema(s)"
|
|
345
|
+
rescue => e
|
|
346
|
+
puts "ā Error: #{e.message}"
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
# List loaded schemas
|
|
350
|
+
#
|
|
351
|
+
# @return [void]
|
|
352
|
+
#
|
|
353
|
+
def cmd_list
|
|
354
|
+
if @registry.size == 0
|
|
355
|
+
puts "No schemas loaded. Use 'load <path>' first."
|
|
356
|
+
else
|
|
357
|
+
puts "Loaded schemas:"
|
|
358
|
+
@registry.schemas.each do |name, schema|
|
|
359
|
+
version = schema.version ? " v#{schema.version}" : ""
|
|
360
|
+
deprecated = schema.deprecated? ? " [DEPRECATED]" : ""
|
|
361
|
+
puts " - #{name}#{version}#{deprecated}"
|
|
362
|
+
end
|
|
363
|
+
end
|
|
364
|
+
end
|
|
365
|
+
|
|
366
|
+
# Show schema info
|
|
367
|
+
#
|
|
368
|
+
# @param name [String] schema name
|
|
369
|
+
# @return [void]
|
|
370
|
+
#
|
|
371
|
+
def cmd_info(name)
|
|
372
|
+
schema = find_schema(name)
|
|
373
|
+
return unless schema
|
|
374
|
+
|
|
375
|
+
puts "Schema: #{name}"
|
|
376
|
+
puts " Version: #{schema.version || 'not specified'}"
|
|
377
|
+
puts " Fields: #{schema.fields.length}"
|
|
378
|
+
schema.fields.each do |f|
|
|
379
|
+
optional = f.optional? ? "?" : ""
|
|
380
|
+
nullable = f.nullable? ? " (nullable)" : ""
|
|
381
|
+
puts " - #{f.name}#{optional}: #{f.type_name}#{nullable}"
|
|
382
|
+
end
|
|
383
|
+
end
|
|
384
|
+
|
|
385
|
+
# Generate records
|
|
386
|
+
#
|
|
387
|
+
# @param name [String] schema name
|
|
388
|
+
# @param count [Integer] number of records
|
|
389
|
+
# @return [void]
|
|
390
|
+
#
|
|
391
|
+
def cmd_gen(name, count)
|
|
392
|
+
schema = find_schema(name)
|
|
393
|
+
return unless schema
|
|
394
|
+
|
|
395
|
+
opts = build_generation_opts
|
|
396
|
+
|
|
397
|
+
if count == 1
|
|
398
|
+
record = schema.generate(**opts)
|
|
399
|
+
puts JSON.pretty_generate(record)
|
|
400
|
+
else
|
|
401
|
+
records = schema.generate_many(count, **opts)
|
|
402
|
+
puts JSON.pretty_generate(records)
|
|
403
|
+
end
|
|
404
|
+
end
|
|
405
|
+
|
|
406
|
+
# Generate table output
|
|
407
|
+
#
|
|
408
|
+
# @param name [String] schema name
|
|
409
|
+
# @param count [Integer] number of records
|
|
410
|
+
# @return [void]
|
|
411
|
+
#
|
|
412
|
+
def cmd_table(name, count)
|
|
413
|
+
schema = find_schema(name)
|
|
414
|
+
return unless schema
|
|
415
|
+
|
|
416
|
+
opts = build_generation_opts
|
|
417
|
+
|
|
418
|
+
if count == 1
|
|
419
|
+
record = schema.generate(**opts)
|
|
420
|
+
puts Formatter.table(record)
|
|
421
|
+
else
|
|
422
|
+
records = schema.generate_many(count, **opts)
|
|
423
|
+
puts Formatter.table(records)
|
|
424
|
+
end
|
|
425
|
+
end
|
|
426
|
+
|
|
427
|
+
# Debug generation
|
|
428
|
+
#
|
|
429
|
+
# @param name [String] schema name
|
|
430
|
+
# @return [void]
|
|
431
|
+
#
|
|
432
|
+
# :nocov:
|
|
433
|
+
def cmd_debug(name)
|
|
434
|
+
schema = find_schema(name)
|
|
435
|
+
return unless schema
|
|
436
|
+
|
|
437
|
+
puts "š Debug Mode: Generating #{schema.name}"
|
|
438
|
+
puts "=" * 60
|
|
439
|
+
puts "Press Enter to continue after each field, 'q' to quit, 'c' to continue without pausing"
|
|
440
|
+
puts
|
|
441
|
+
|
|
442
|
+
field_index = 0
|
|
443
|
+
generated_fields = {}
|
|
444
|
+
auto_continue = false
|
|
445
|
+
|
|
446
|
+
original_callback = Synthra.configuration.on_field_generated
|
|
447
|
+
|
|
448
|
+
Synthra.configuration.on_field_generated = lambda do |schema_name, field_name, value, duration|
|
|
449
|
+
field_index += 1
|
|
450
|
+
generated_fields[field_name] = value
|
|
451
|
+
|
|
452
|
+
puts "\n[Field #{field_index}] #{field_name}"
|
|
453
|
+
puts "ā" * 60
|
|
454
|
+
puts "Value: #{Formatter.format_value(value, 80)}"
|
|
455
|
+
puts "Duration: #{duration}ms" if duration
|
|
456
|
+
puts "Schema: #{schema_name}"
|
|
457
|
+
|
|
458
|
+
# Show context state - all previously generated fields
|
|
459
|
+
if generated_fields.size > 1
|
|
460
|
+
puts "\nš Context (#{generated_fields.size - 1} prior fields):"
|
|
461
|
+
generated_fields.each do |k, v|
|
|
462
|
+
next if k == field_name
|
|
463
|
+
puts " #{k.to_s.ljust(20)} = #{Formatter.format_value(v, 50)}"
|
|
464
|
+
end
|
|
465
|
+
end
|
|
466
|
+
puts
|
|
467
|
+
|
|
468
|
+
return if auto_continue
|
|
469
|
+
|
|
470
|
+
loop do
|
|
471
|
+
print "Press Enter to continue, 'q' to quit, 'c' to continue without pausing: "
|
|
472
|
+
response = $stdin.gets&.strip&.downcase
|
|
473
|
+
|
|
474
|
+
case response
|
|
475
|
+
when "q", "quit"
|
|
476
|
+
Synthra.configuration.on_field_generated = original_callback
|
|
477
|
+
puts "Debug cancelled."
|
|
478
|
+
throw :debug_cancelled
|
|
479
|
+
when "c", "continue"
|
|
480
|
+
auto_continue = true
|
|
481
|
+
puts "Continuing without pausing..."
|
|
482
|
+
break
|
|
483
|
+
when "", nil
|
|
484
|
+
break
|
|
485
|
+
else
|
|
486
|
+
puts "Invalid input. Press Enter, 'q', or 'c'."
|
|
487
|
+
end
|
|
488
|
+
end
|
|
489
|
+
end
|
|
490
|
+
|
|
491
|
+
begin
|
|
492
|
+
catch(:debug_cancelled) do
|
|
493
|
+
opts = build_generation_opts
|
|
494
|
+
result = schema.generate(**opts)
|
|
495
|
+
|
|
496
|
+
puts
|
|
497
|
+
puts "=" * 60
|
|
498
|
+
puts "ā
Generation Complete!"
|
|
499
|
+
puts "=" * 60
|
|
500
|
+
puts
|
|
501
|
+
puts "Final Record:"
|
|
502
|
+
puts Formatter.table(result)
|
|
503
|
+
end
|
|
504
|
+
ensure
|
|
505
|
+
Synthra.configuration.on_field_generated = original_callback
|
|
506
|
+
end
|
|
507
|
+
end
|
|
508
|
+
# :nocov:
|
|
509
|
+
|
|
510
|
+
# Inspect generation
|
|
511
|
+
#
|
|
512
|
+
# @param name [String] schema name
|
|
513
|
+
# @return [void]
|
|
514
|
+
#
|
|
515
|
+
def cmd_inspect(name)
|
|
516
|
+
schema = find_schema(name)
|
|
517
|
+
return unless schema
|
|
518
|
+
|
|
519
|
+
opts = build_generation_opts
|
|
520
|
+
record = schema.generate(**opts)
|
|
521
|
+
|
|
522
|
+
puts "š Generated Record:"
|
|
523
|
+
puts "=" * 60
|
|
524
|
+
puts Formatter.table(record)
|
|
525
|
+
puts
|
|
526
|
+
puts "š JSON Format:"
|
|
527
|
+
puts JSON.pretty_generate(record)
|
|
528
|
+
puts
|
|
529
|
+
puts "š Statistics:"
|
|
530
|
+
puts " Fields: #{record.keys.length}"
|
|
531
|
+
puts " Total size: #{JSON.generate(record).bytesize} bytes"
|
|
532
|
+
record.each do |key, value|
|
|
533
|
+
type = value.class.name
|
|
534
|
+
size = case value
|
|
535
|
+
when String then "#{value.bytesize} bytes"
|
|
536
|
+
when Hash then "#{value.keys.length} keys"
|
|
537
|
+
when Array then "#{value.length} items"
|
|
538
|
+
else value.inspect.length.to_s + " chars"
|
|
539
|
+
end
|
|
540
|
+
puts " #{key}: #{type} (#{size})"
|
|
541
|
+
end
|
|
542
|
+
end
|
|
543
|
+
|
|
544
|
+
# Set seed
|
|
545
|
+
#
|
|
546
|
+
# @param seed [Integer] seed value
|
|
547
|
+
# @return [void]
|
|
548
|
+
#
|
|
549
|
+
def cmd_seed(seed)
|
|
550
|
+
@current_seed = seed
|
|
551
|
+
puts "ā
Seed set to #{@current_seed}"
|
|
552
|
+
end
|
|
553
|
+
|
|
554
|
+
# Set mode
|
|
555
|
+
#
|
|
556
|
+
# @param mode [Symbol] generation mode
|
|
557
|
+
# @return [void]
|
|
558
|
+
#
|
|
559
|
+
def cmd_mode(mode)
|
|
560
|
+
@current_mode = mode
|
|
561
|
+
puts "ā
Mode set to #{@current_mode}"
|
|
562
|
+
end
|
|
563
|
+
|
|
564
|
+
# Show help
|
|
565
|
+
#
|
|
566
|
+
# @return [void]
|
|
567
|
+
#
|
|
568
|
+
def cmd_help
|
|
569
|
+
puts "Commands: load, list (l), info, gen (g), table (t), debug (d), inspect (i), seed, mode, help (h), quit (q)"
|
|
570
|
+
puts "Export: Use '> filename' to export output (e.g., 'gen User 100 > users.json')"
|
|
571
|
+
end
|
|
572
|
+
|
|
573
|
+
# Quit REPL
|
|
574
|
+
#
|
|
575
|
+
# @return [void]
|
|
576
|
+
#
|
|
577
|
+
def cmd_quit
|
|
578
|
+
puts "Goodbye!"
|
|
579
|
+
stop
|
|
580
|
+
end
|
|
581
|
+
|
|
582
|
+
# Find schema by name with suggestions
|
|
583
|
+
#
|
|
584
|
+
# @param name [String] schema name
|
|
585
|
+
# @return [Schema, nil] schema or nil if not found
|
|
586
|
+
#
|
|
587
|
+
def find_schema(name)
|
|
588
|
+
@registry.schema(name)
|
|
589
|
+
rescue KeyError
|
|
590
|
+
suggestions = Utils::StringDistance.similar_strings(name, @registry.names, max_distance: 3)
|
|
591
|
+
puts "ā Schema '#{name}' not found"
|
|
592
|
+
puts Utils::StringDistance.format_suggestions(suggestions) if suggestions.any?
|
|
593
|
+
nil
|
|
594
|
+
end
|
|
595
|
+
|
|
596
|
+
# Build generation options hash
|
|
597
|
+
#
|
|
598
|
+
# @return [Hash] generation options
|
|
599
|
+
#
|
|
600
|
+
def build_generation_opts
|
|
601
|
+
opts = { registry: @registry, mode: @current_mode }
|
|
602
|
+
opts[:seed] = @current_seed if @current_seed
|
|
603
|
+
opts
|
|
604
|
+
end
|
|
605
|
+
end
|
|
606
|
+
end
|
|
607
|
+
end
|