xseed 1.0.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.
Files changed (41) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/rake.yml +16 -0
  3. data/.github/workflows/release.yml +25 -0
  4. data/.gitignore +72 -0
  5. data/.rspec +3 -0
  6. data/.rubocop.yml +11 -0
  7. data/.rubocop_todo.yml +432 -0
  8. data/CHANGELOG.adoc +446 -0
  9. data/Gemfile +21 -0
  10. data/LICENSE.adoc +29 -0
  11. data/README.adoc +386 -0
  12. data/Rakefile +11 -0
  13. data/examples/README.adoc +334 -0
  14. data/examples/advanced_usage.rb +286 -0
  15. data/examples/html_generation.rb +167 -0
  16. data/examples/parser_usage.rb +102 -0
  17. data/examples/schemas/person.xsd +171 -0
  18. data/examples/simple_generation.rb +149 -0
  19. data/exe/xseed +6 -0
  20. data/lib/xseed/cli.rb +376 -0
  21. data/lib/xseed/documentation/config.rb +101 -0
  22. data/lib/xseed/documentation/constants.rb +76 -0
  23. data/lib/xseed/documentation/generators/hierarchy_table_generator.rb +554 -0
  24. data/lib/xseed/documentation/generators/instance_sample_generator.rb +723 -0
  25. data/lib/xseed/documentation/generators/properties_table_generator.rb +983 -0
  26. data/lib/xseed/documentation/html_generator.rb +836 -0
  27. data/lib/xseed/documentation/html_generator.rb.bak +723 -0
  28. data/lib/xseed/documentation/presentation/css_generator.rb +510 -0
  29. data/lib/xseed/documentation/presentation/javascript_generator.rb +151 -0
  30. data/lib/xseed/documentation/presentation/navigation_builder.rb +169 -0
  31. data/lib/xseed/documentation/schema_loader.rb +121 -0
  32. data/lib/xseed/documentation/utils/helpers.rb +205 -0
  33. data/lib/xseed/documentation/utils/namespaces.rb +149 -0
  34. data/lib/xseed/documentation/utils/references.rb +135 -0
  35. data/lib/xseed/documentation/utils/strings.rb +75 -0
  36. data/lib/xseed/models/element_declaration.rb +144 -0
  37. data/lib/xseed/parser/xsd_parser.rb +192 -0
  38. data/lib/xseed/version.rb +5 -0
  39. data/lib/xseed.rb +76 -0
  40. data/xseed.gemspec +39 -0
  41. metadata +158 -0
@@ -0,0 +1,171 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
3
+ targetNamespace="http://example.com/person"
4
+ xmlns:tns="http://example.com/person"
5
+ elementFormDefault="qualified"
6
+ version="1.0.0">
7
+
8
+ <xs:annotation>
9
+ <xs:documentation>
10
+ Example schema defining person and contact information.
11
+ This schema demonstrates various XSD features including
12
+ complex types, sequences, choices, and attributes.
13
+ </xs:documentation>
14
+ </xs:annotation>
15
+
16
+ <!-- Simple Types -->
17
+ <xs:simpleType name="EmailType">
18
+ <xs:annotation>
19
+ <xs:documentation>Email address with pattern validation</xs:documentation>
20
+ </xs:annotation>
21
+ <xs:restriction base="xs:string">
22
+ <xs:pattern value="[^@]+@[^@]+\.[^@]+"/>
23
+ </xs:restriction>
24
+ </xs:simpleType>
25
+
26
+ <xs:simpleType name="PhoneType">
27
+ <xs:annotation>
28
+ <xs:documentation>Phone number in international format</xs:documentation>
29
+ </xs:annotation>
30
+ <xs:restriction base="xs:string">
31
+ <xs:pattern value="\+[0-9]{1,3}-[0-9]{1,14}"/>
32
+ </xs:restriction>
33
+ </xs:simpleType>
34
+
35
+ <xs:simpleType name="StatusType">
36
+ <xs:annotation>
37
+ <xs:documentation>Person status enumeration</xs:documentation>
38
+ </xs:annotation>
39
+ <xs:restriction base="xs:string">
40
+ <xs:enumeration value="active"/>
41
+ <xs:enumeration value="inactive"/>
42
+ <xs:enumeration value="pending"/>
43
+ </xs:restriction>
44
+ </xs:simpleType>
45
+
46
+ <!-- Complex Types -->
47
+ <xs:complexType name="AddressType">
48
+ <xs:annotation>
49
+ <xs:documentation>Postal address information</xs:documentation>
50
+ </xs:annotation>
51
+ <xs:sequence>
52
+ <xs:element name="street" type="xs:string">
53
+ <xs:annotation>
54
+ <xs:documentation>Street address</xs:documentation>
55
+ </xs:annotation>
56
+ </xs:element>
57
+ <xs:element name="city" type="xs:string">
58
+ <xs:annotation>
59
+ <xs:documentation>City name</xs:documentation>
60
+ </xs:annotation>
61
+ </xs:element>
62
+ <xs:element name="state" type="xs:string" minOccurs="0">
63
+ <xs:annotation>
64
+ <xs:documentation>State or province (optional)</xs:documentation>
65
+ </xs:annotation>
66
+ </xs:element>
67
+ <xs:element name="postalCode" type="xs:string">
68
+ <xs:annotation>
69
+ <xs:documentation>Postal or ZIP code</xs:documentation>
70
+ </xs:annotation>
71
+ </xs:element>
72
+ <xs:element name="country" type="xs:string">
73
+ <xs:annotation>
74
+ <xs:documentation>Country name</xs:documentation>
75
+ </xs:annotation>
76
+ </xs:element>
77
+ </xs:sequence>
78
+ <xs:attribute name="type" type="xs:string" use="optional" default="home">
79
+ <xs:annotation>
80
+ <xs:documentation>Address type (home, work, other)</xs:documentation>
81
+ </xs:annotation>
82
+ </xs:attribute>
83
+ </xs:complexType>
84
+
85
+ <xs:complexType name="ContactInfoType">
86
+ <xs:annotation>
87
+ <xs:documentation>Contact information with choice of email or phone</xs:documentation>
88
+ </xs:annotation>
89
+ <xs:choice>
90
+ <xs:element name="email" type="tns:EmailType">
91
+ <xs:annotation>
92
+ <xs:documentation>Email address</xs:documentation>
93
+ </xs:annotation>
94
+ </xs:element>
95
+ <xs:element name="phone" type="tns:PhoneType">
96
+ <xs:annotation>
97
+ <xs:documentation>Phone number</xs:documentation>
98
+ </xs:annotation>
99
+ </xs:element>
100
+ </xs:choice>
101
+ <xs:attribute name="preferred" type="xs:boolean" default="false">
102
+ <xs:annotation>
103
+ <xs:documentation>Whether this is the preferred contact method</xs:documentation>
104
+ </xs:annotation>
105
+ </xs:attribute>
106
+ </xs:complexType>
107
+
108
+ <xs:complexType name="PersonType">
109
+ <xs:annotation>
110
+ <xs:documentation>A person with name, contact info, and address</xs:documentation>
111
+ </xs:annotation>
112
+ <xs:sequence>
113
+ <xs:element name="firstName" type="xs:string">
114
+ <xs:annotation>
115
+ <xs:documentation>Person's first name</xs:documentation>
116
+ </xs:annotation>
117
+ </xs:element>
118
+ <xs:element name="lastName" type="xs:string">
119
+ <xs:annotation>
120
+ <xs:documentation>Person's last name</xs:documentation>
121
+ </xs:annotation>
122
+ </xs:element>
123
+ <xs:element name="age" type="xs:positiveInteger" minOccurs="0">
124
+ <xs:annotation>
125
+ <xs:documentation>Person's age</xs:documentation>
126
+ </xs:annotation>
127
+ </xs:element>
128
+ <xs:element name="contact" type="tns:ContactInfoType"
129
+ minOccurs="0" maxOccurs="unbounded">
130
+ <xs:annotation>
131
+ <xs:documentation>Contact information (email or phone)</xs:documentation>
132
+ </xs:annotation>
133
+ </xs:element>
134
+ <xs:element name="address" type="tns:AddressType"
135
+ minOccurs="0" maxOccurs="unbounded">
136
+ <xs:annotation>
137
+ <xs:documentation>Postal addresses</xs:documentation>
138
+ </xs:annotation>
139
+ </xs:element>
140
+ </xs:sequence>
141
+ <xs:attribute name="id" type="xs:ID" use="required">
142
+ <xs:annotation>
143
+ <xs:documentation>Unique identifier for the person</xs:documentation>
144
+ </xs:annotation>
145
+ </xs:attribute>
146
+ <xs:attribute name="status" type="tns:StatusType" default="active">
147
+ <xs:annotation>
148
+ <xs:documentation>Person's current status</xs:documentation>
149
+ </xs:annotation>
150
+ </xs:attribute>
151
+ </xs:complexType>
152
+
153
+ <!-- Global Elements -->
154
+ <xs:element name="person" type="tns:PersonType">
155
+ <xs:annotation>
156
+ <xs:documentation>Root element representing a person</xs:documentation>
157
+ </xs:annotation>
158
+ </xs:element>
159
+
160
+ <xs:element name="people">
161
+ <xs:annotation>
162
+ <xs:documentation>Collection of person elements</xs:documentation>
163
+ </xs:annotation>
164
+ <xs:complexType>
165
+ <xs:sequence>
166
+ <xs:element ref="tns:person" maxOccurs="unbounded"/>
167
+ </xs:sequence>
168
+ </xs:complexType>
169
+ </xs:element>
170
+
171
+ </xs:schema>
@@ -0,0 +1,149 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Example: Simple SVG Generation
5
+ #
6
+ # This example demonstrates the basic usage of Xseed for generating
7
+ # SVG diagrams from XSD schema files.
8
+
9
+ require "bundler/setup"
10
+ require "xseed"
11
+
12
+ # Example 1: Generate SVG and print to stdout
13
+ def example_generate_to_stdout
14
+ puts "Example 1: Generate SVG to stdout"
15
+ puts "=" * 60
16
+
17
+ # Path to example XSD file
18
+ xsd_file = File.join(__dir__, "schemas", "person.xsd")
19
+
20
+ # Create generator
21
+ generator = Xseed::Svg::SvgGenerator.new(xsd_file)
22
+
23
+ # Generate SVG content
24
+ svg_content = generator.generate
25
+
26
+ # Print first 200 characters
27
+ puts svg_content[0..200]
28
+ puts "..."
29
+ puts "\n✓ SVG generated successfully (#{svg_content.length} bytes)\n\n"
30
+ end
31
+
32
+ # Example 2: Generate SVG and save to file
33
+ def example_generate_to_file
34
+ puts "Example 2: Generate SVG to file"
35
+ puts "=" * 60
36
+
37
+ xsd_file = File.join(__dir__, "schemas", "person.xsd")
38
+ output_file = File.join(__dir__, "output", "person-diagram.svg")
39
+
40
+ # Ensure output directory exists
41
+ FileUtils.mkdir_p(File.dirname(output_file))
42
+
43
+ # Create generator and generate file
44
+ generator = Xseed::Svg::SvgGenerator.new(xsd_file)
45
+ result = generator.generate_file(output_file)
46
+
47
+ puts "✓ SVG diagram saved to: #{result}"
48
+ puts " File size: #{File.size(result)} bytes\n\n"
49
+ end
50
+
51
+ # Example 3: Error handling
52
+ def example_with_error_handling
53
+ puts "Example 3: Generate with error handling"
54
+ puts "=" * 60
55
+
56
+ xsd_file = File.join(__dir__, "schemas", "person.xsd")
57
+ output_file = File.join(__dir__, "output", "person-safe.svg")
58
+
59
+ FileUtils.mkdir_p(File.dirname(output_file))
60
+
61
+ begin
62
+ generator = Xseed::Svg::SvgGenerator.new(xsd_file)
63
+ generator.generate_file(output_file)
64
+ puts "✓ Generation successful: #{output_file}\n\n"
65
+ rescue ArgumentError => e
66
+ puts "✗ Validation error: #{e.message}\n\n"
67
+ rescue Xseed::ParserError => e
68
+ puts "✗ Parser error: #{e.message}\n\n"
69
+ rescue Xseed::Svg::GenerationError => e
70
+ puts "✗ Generation error: #{e.message}\n\n"
71
+ rescue StandardError => e
72
+ puts "✗ Unexpected error: #{e.message}\n\n"
73
+ end
74
+ end
75
+
76
+ # Example 4: Inspect schema before generation
77
+ def example_inspect_before_generate
78
+ puts "Example 4: Inspect schema metadata"
79
+ puts "=" * 60
80
+
81
+ xsd_file = File.join(__dir__, "schemas", "person.xsd")
82
+
83
+ # Parse to inspect metadata
84
+ parser = Xseed::Parser::XsdParser.new(xsd_file)
85
+
86
+ puts "Schema Information:"
87
+ puts " Target Namespace: #{parser.target_namespace}"
88
+ puts " Version: #{parser.schema_version || 'Not specified'}"
89
+ puts " Elements: #{parser.elements.size}"
90
+ puts " Complex Types: #{parser.complex_types.size}"
91
+ puts " Simple Types: #{parser.simple_types.size}"
92
+ puts ""
93
+
94
+ # Now generate
95
+ generator = Xseed::Svg::SvgGenerator.new(xsd_file)
96
+ output_file = File.join(__dir__, "output", "person-inspected.svg")
97
+ FileUtils.mkdir_p(File.dirname(output_file))
98
+ generator.generate_file(output_file)
99
+
100
+ puts "✓ SVG generated after inspection: #{output_file}\n\n"
101
+ end
102
+
103
+ # Example 5: Batch processing multiple schemas
104
+ def example_batch_processing
105
+ puts "Example 5: Batch process multiple schemas"
106
+ puts "=" * 60
107
+
108
+ schemas_dir = File.join(__dir__, "schemas")
109
+ output_dir = File.join(__dir__, "output", "batch")
110
+ FileUtils.mkdir_p(output_dir)
111
+
112
+ # Find all XSD files
113
+ xsd_files = Dir.glob(File.join(schemas_dir, "*.xsd"))
114
+
115
+ puts "Found #{xsd_files.size} schema files\n"
116
+
117
+ xsd_files.each do |xsd_file|
118
+ basename = File.basename(xsd_file, ".xsd")
119
+ output_file = File.join(output_dir, "#{basename}-diagram.svg")
120
+
121
+ begin
122
+ generator = Xseed::Svg::SvgGenerator.new(xsd_file)
123
+ generator.generate_file(output_file)
124
+ puts " ✓ #{basename}.xsd → #{basename}-diagram.svg"
125
+ rescue StandardError => e
126
+ puts " ✗ #{basename}.xsd: #{e.message}"
127
+ end
128
+ end
129
+
130
+ puts "\n✓ Batch processing complete\n\n"
131
+ end
132
+
133
+ # Run all examples
134
+ if __FILE__ == $PROGRAM_NAME
135
+ puts "\n"
136
+ puts "╔#{'═' * 58}╗"
137
+ puts "║#{' ' * 12}Xseed Simple Generation Examples#{' ' * 13}║"
138
+ puts "╚#{'═' * 58}╝"
139
+ puts "\n"
140
+
141
+ example_generate_to_stdout
142
+ example_generate_to_file
143
+ example_with_error_handling
144
+ example_inspect_before_generate
145
+ example_batch_processing
146
+
147
+ puts "All examples completed!"
148
+ puts "\nGenerated files are in: #{File.join(__dir__, 'output')}"
149
+ end
data/exe/xseed ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "../lib/xseed"
5
+
6
+ Xseed::CLI.start(ARGV)
data/lib/xseed/cli.rb ADDED
@@ -0,0 +1,376 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "thor"
4
+ require "fileutils"
5
+ require_relative "documentation/config"
6
+ require_relative "documentation/html_generator"
7
+ require_relative "version"
8
+
9
+ module Xseed
10
+ # Command-line interface for Xseed gem
11
+ # Provides commands for generating SVG diagrams and HTML documentation from XSD
12
+ class CLI < Thor
13
+ def self.exit_on_failure?
14
+ true
15
+ end
16
+
17
+ # Global options
18
+ class_option :verbose,
19
+ type: :boolean,
20
+ aliases: "-v",
21
+ desc: "Enable verbose output"
22
+
23
+ desc "svg INPUT_XSD [OPTIONS]", "Generate SVG diagram from XSD schema"
24
+ long_desc <<~DESC
25
+ Generate an SVG diagram visualizing the structure of an XSD schema file.
26
+
27
+ This command uses the xsdvi gem for SVG generation.
28
+
29
+ Examples:
30
+
31
+ # Generate SVG and write to file
32
+ $ xseed svg schema.xsd -o diagram.svg
33
+
34
+ # Generate diagram for a single element
35
+ $ xseed svg schema.xsd -r ElementName -o diagram.svg
36
+
37
+ # Output SVG to stdout
38
+ $ xseed svg schema.xsd
39
+
40
+ For more options, see: https://github.com/metanorma/xsdvi-ruby
41
+ DESC
42
+ option :output,
43
+ aliases: "-o",
44
+ desc: "Output file path (default: stdout)",
45
+ banner: "PATH"
46
+ option :root_node_name,
47
+ aliases: "-r",
48
+ desc: "Root element name to visualize",
49
+ banner: "NAME"
50
+ option :one_node_only,
51
+ type: :boolean,
52
+ desc: "Show only the specified element"
53
+ option :force,
54
+ type: :boolean,
55
+ aliases: "-f",
56
+ desc: "Overwrite output file if it exists"
57
+ def svg(input_xsd)
58
+ require "xsdvi"
59
+
60
+ start_time = Time.now
61
+
62
+ # Validate input file
63
+ validate_input_file!(input_xsd)
64
+
65
+ # Check output file permissions if specified
66
+ validate_output_path!(options[:output]) if options[:output]
67
+
68
+ log_verbose "Generating SVG using xsdvi gem"
69
+
70
+ # Create xsdvi components using proper Ruby API
71
+ output_path = options[:output] || "/dev/stdout"
72
+ writer = Xsdvi::Utils::Writer.new(output_path)
73
+ builder = Xsdvi::Tree::Builder.new
74
+ handler = Xsdvi::XsdHandler.new(builder)
75
+
76
+ # Set options
77
+ handler.root_node_name = options[:root_node_name] if options[:root_node_name]
78
+ handler.one_node_only = options[:one_node_only] if options[:one_node_only]
79
+
80
+ # Process XSD file
81
+ handler.process_file(input_xsd)
82
+ root_symbol = builder.root
83
+
84
+ # Generate SVG
85
+ generator = Xsdvi::SVG::Generator.new(writer)
86
+ generator.hide_menu_buttons = options[:one_node_only] if options[:one_node_only]
87
+
88
+ log_with_progress("Generating SVG diagram") do
89
+ generator.draw(root_symbol)
90
+ end
91
+
92
+ if options[:output]
93
+ elapsed = Time.now - start_time
94
+ say "✓ SVG diagram generated: #{options[:output]}", :green
95
+ log_verbose "Generation completed in #{elapsed.round(3)}s"
96
+ end
97
+ rescue LoadError
98
+ error_exit "xsdvi gem not found. Please run: gem install xsdvi"
99
+ rescue ArgumentError => e
100
+ error_exit "Validation error: #{e.message}"
101
+ rescue StandardError => e
102
+ error_exit "SVG generation error: #{e.message}", show_backtrace: true
103
+ end
104
+
105
+ desc "html INPUT_XSD [OPTIONS]",
106
+ "Generate HTML documentation from XSD"
107
+ long_desc <<~DESC
108
+ Generate comprehensive HTML documentation from an XSD schema file.
109
+
110
+ SVG diagrams are automatically generated for all elements using the xsdvi gem.
111
+ The diagrams are placed in a subdirectory (default: diagrams/) and automatically
112
+ embedded in the HTML with <object> tags.
113
+
114
+ Options:
115
+ -o, --output PATH Output HTML file path
116
+ -t, --title TEXT Documentation title
117
+ --css PATH External CSS file to include
118
+ -d, --diagrams-dir DIR Directory for SVG diagrams (default: diagrams)
119
+
120
+ Example:
121
+ $ xseed htm schema.xsd -o docs/index.html
122
+
123
+ This generates:
124
+ docs/index.html (HTML documentation)
125
+ docs/diagrams/*.svg (SVG diagrams for each element)
126
+
127
+ For more information, visit: https://github.com/metanorma/xseed
128
+ DESC
129
+ option :output,
130
+ aliases: "-o",
131
+ desc: "Output file path",
132
+ banner: "PATH"
133
+ option :title,
134
+ aliases: "-t",
135
+ desc: "Documentation title",
136
+ banner: "TEXT"
137
+ option :css,
138
+ desc: "External CSS file",
139
+ banner: "PATH"
140
+ option :diagrams_dir,
141
+ aliases: "-d",
142
+ desc: "Directory for SVG diagrams relative to output (default: diagrams)",
143
+ banner: "DIR"
144
+ option :force,
145
+ type: :boolean,
146
+ aliases: "-f",
147
+ desc: "Overwrite output file if it exists"
148
+ def html(input_xsd)
149
+ start_time = Time.now
150
+
151
+ # Validate input file
152
+ validate_input_file!(input_xsd)
153
+
154
+ # Check output file permissions if specified
155
+ validate_output_path!(options[:output]) if options[:output]
156
+
157
+ # Create config
158
+ config = Xseed::Documentation::Config.new
159
+ config.title = options[:title] if options[:title]
160
+ config.external_css_url = options[:css] if options[:css]
161
+ config.diagrams_dir = options[:diagrams_dir] if options[:diagrams_dir]
162
+
163
+ log_verbose "Creating HTML generator for: #{input_xsd}"
164
+
165
+ # Create generator
166
+ generator = Xseed::Documentation::HtmlGenerator.new(input_xsd, config)
167
+ log_verbose "HTML generator initialized"
168
+
169
+ # Determine output path
170
+ output_path = options[:output]
171
+
172
+ if output_path
173
+ # Check if output file exists
174
+ check_output_file_exists!(output_path)
175
+
176
+ # Generate and write to file
177
+ log_with_progress("Generating HTML documentation") do
178
+ generator.generate_file(output_path)
179
+ end
180
+
181
+ elapsed = Time.now - start_time
182
+ say "✓ HTML documentation generated: #{output_path}", :green
183
+ log_verbose "Generation completed in #{elapsed.round(3)}s"
184
+ else
185
+ # Generate and output to stdout
186
+ log_verbose "Generating HTML to stdout"
187
+ html_content = generator.generate
188
+ say html_content
189
+ end
190
+ rescue ArgumentError => e
191
+ error_exit "Validation error: #{e.message}"
192
+ rescue StandardError => e
193
+ error_exit "HTML generation error: #{e.message}", show_backtrace: true
194
+ end
195
+
196
+ desc "version", "Display Xseed version information"
197
+ def version
198
+ say "━" * 60, :cyan
199
+ say "Xseed - XSD Documentation Generator", :cyan
200
+ say "━" * 60, :cyan
201
+ say ""
202
+ say "Version: #{Xseed::VERSION}", :green
203
+ say ""
204
+ say "Features:"
205
+ say " ✓ HTML documentation generation (native)", :green
206
+ say " ✓ SVG diagram generation (via xsdvi gem)", :green
207
+ say " ✓ XSD schema parsing", :green
208
+ say ""
209
+ say "GitHub: https://github.com/metanorma/xseed"
210
+ say "License: BSD-2-Clause"
211
+ end
212
+
213
+ desc "info INPUT_XSD", "Display information about an XSD schema"
214
+ long_desc <<~DESC
215
+ Display detailed information about an XSD schema file without generating output.
216
+
217
+ Shows:
218
+ • Schema metadata (namespace, version, etc.)
219
+ • Component counts (elements, types, groups)
220
+ • Complexity metrics
221
+ • Estimated generation time
222
+
223
+ Example:
224
+ $ xseed info schema.xsd
225
+ DESC
226
+ def info(input_xsd)
227
+ validate_input_file!(input_xsd)
228
+
229
+ say "━" * 60, :cyan
230
+ say "XSD Schema Information", :cyan
231
+ say "━" * 60, :cyan
232
+ say ""
233
+
234
+ log_verbose "Parsing schema..."
235
+ parser = Parser::XsdParser.new(input_xsd)
236
+
237
+ say "File: #{input_xsd}", :green
238
+ say "Size: #{format_file_size(File.size(input_xsd))}"
239
+ say ""
240
+
241
+ say "Schema Metadata:", :cyan
242
+ say " Target Namespace: #{parser.target_namespace || 'None'}"
243
+ say " Version: #{parser.schema_version || 'Not specified'}"
244
+ say " Element Form Default: #{parser.element_form_default || 'Not specified'}"
245
+ say ""
246
+
247
+ say "Components:", :cyan
248
+ say " Elements: #{parser.elements.size}"
249
+ say " Complex Types: #{parser.complex_types.size}"
250
+ say " Simple Types: #{parser.simple_types.size}"
251
+ say " Groups: #{parser.groups.size}"
252
+ say " Attribute Groups: #{parser.attribute_groups.size}"
253
+ say ""
254
+
255
+ total_components = parser.elements.size + parser.types.size +
256
+ parser.groups.size + parser.attribute_groups.size
257
+ say " Total Components: #{total_components}"
258
+ say ""
259
+
260
+ # Estimate complexity
261
+ complexity = estimate_complexity(total_components)
262
+ say "Complexity: #{complexity[:level]}", complexity[:color]
263
+ say " Estimated generation time: #{complexity[:time]}"
264
+ say ""
265
+
266
+ if parser.documentation
267
+ say "Schema Documentation:", :cyan
268
+ say " #{parser.documentation.lines.first.strip}"
269
+ if parser.documentation.lines.size > 1
270
+ say " (#{parser.documentation.lines.size} lines total)"
271
+ end
272
+ end
273
+ rescue StandardError => e
274
+ error_exit "Error reading schema: #{e.message}"
275
+ end
276
+
277
+ no_commands do
278
+ # Validates input file exists and is readable
279
+ def validate_input_file!(path)
280
+ raise ArgumentError, "File not found: #{path}" unless File.exist?(path)
281
+
282
+ unless File.readable?(path)
283
+ raise ArgumentError, "File not readable: #{path}"
284
+ end
285
+
286
+ raise ArgumentError, "Not a file: #{path}" unless File.file?(path)
287
+
288
+ return if path.end_with?(".xsd")
289
+
290
+ return unless options[:verbose]
291
+
292
+ say "Warning: File does not have .xsd extension",
293
+ :yellow
294
+ end
295
+
296
+ # Validates output path is writable
297
+ def validate_output_path!(path)
298
+ dir = File.dirname(path)
299
+
300
+ unless Dir.exist?(dir)
301
+ log_verbose "Output directory does not exist, will create: #{dir}"
302
+ end
303
+
304
+ return unless File.exist?(path) && !File.writable?(path)
305
+
306
+ raise ArgumentError, "Output file exists but is not writable: #{path}"
307
+ end
308
+
309
+ # Checks if output file exists and handles --force option
310
+ def check_output_file_exists!(path)
311
+ return unless File.exist?(path)
312
+ return if options[:force]
313
+
314
+ say "Warning: Output file already exists: #{path}", :yellow
315
+ return if yes?("Overwrite? (y/n)")
316
+
317
+ say "Aborted.", :red
318
+ exit 1
319
+ end
320
+
321
+ # Logs message if verbose option is enabled
322
+ def log_verbose(message)
323
+ say " → #{message}", :cyan if options[:verbose]
324
+ end
325
+
326
+ # Executes block with progress indicator for verbose mode
327
+ def log_with_progress(message)
328
+ if options[:verbose]
329
+ say " → #{message}...", :cyan
330
+ result = yield
331
+ say " ✓ Complete", :green
332
+ result
333
+ else
334
+ yield
335
+ end
336
+ end
337
+
338
+ # Exits with error message
339
+ def error_exit(message, show_backtrace: false)
340
+ say "✗ Error: #{message}", :red
341
+
342
+ if show_backtrace && options[:verbose]
343
+ say "\nBacktrace:", :red
344
+ say caller.join("\n"), :red
345
+ end
346
+
347
+ exit 1
348
+ end
349
+
350
+ # Formats file size for display
351
+ def format_file_size(bytes)
352
+ return "#{bytes} B" if bytes < 1024
353
+
354
+ kb = bytes / 1024.0
355
+ return "#{kb.round(1)} KB" if kb < 1024
356
+
357
+ mb = kb / 1024.0
358
+ "#{mb.round(2)} MB"
359
+ end
360
+
361
+ # Estimates schema complexity
362
+ def estimate_complexity(component_count)
363
+ case component_count
364
+ when 0..10
365
+ { level: "Simple", time: "< 100ms", color: :green }
366
+ when 11..50
367
+ { level: "Moderate", time: "< 500ms", color: :cyan }
368
+ when 51..200
369
+ { level: "Complex", time: "< 2s", color: :yellow }
370
+ else
371
+ { level: "Very Complex", time: "> 2s", color: :red }
372
+ end
373
+ end
374
+ end
375
+ end
376
+ end