suma 0.1.22 → 0.1.24

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 414cf7549060d821671a08771418a9f3c163ba23af62ef980eb4b12ccd0f5ad4
4
- data.tar.gz: 0b30377a2b1db558bf62c765f136c5c505ba905b387a5d76e249fc48fce8c237
3
+ metadata.gz: b2e8f69b5a73e7e808b0dceb8443829f1ee07370f391e29144187eac0870b3ba
4
+ data.tar.gz: 912fa2f86f74512f95fb25dbf082de1d65bb65848e832cab66444e1e4e698956
5
5
  SHA512:
6
- metadata.gz: 7e899f31a08df29536d7abe1e8d2fc29a0c20038e1bb7dcedda35a7b9fa5d5041046e963d543401b4eaa100fa9d1aff19218b4e1c72457fb2a081a34211072f5
7
- data.tar.gz: c764fc1985311dafb09074a498cfc30694427e95c2f29d4d243872fca5ddfa0ab6df3d323d785254b3ef69848cf3114c8b0b44b483ae09b97a1e293b05f82356
6
+ metadata.gz: f9d9d3a67a2425b3b96d0a8ceddfa964e1d2a7a391fb626201f4ca1be98c23a96eb622d10ee5b7073c2d80be65f9d9a88b9d75996231c02da5035593aa4e7860
7
+ data.tar.gz: 79435485f1e2c1389a8054153e1e6abfb145f098038937c3f79d3d7a55e9b6b9ead89b78f6a2f74f462d8d71e7f726ed455e20ffd712357b58455818691eacc1
data/.rubocop_todo.yml CHANGED
@@ -1,6 +1,6 @@
1
1
  # This configuration was generated by
2
2
  # `rubocop --auto-gen-config`
3
- # on 2025-10-04 11:33:46 UTC using RuboCop version 1.81.1.
3
+ # on 2025-10-12 09:15:38 UTC using RuboCop version 1.81.1.
4
4
  # The point is for the user to remove these configuration records
5
5
  # one by one as the offenses are removed from the code base.
6
6
  # Note that changes in the inspected code, or installation of new
@@ -18,22 +18,51 @@ Gemspec/RequiredRubyVersion:
18
18
  Exclude:
19
19
  - 'suma.gemspec'
20
20
 
21
- # Offense count: 67
21
+ # Offense count: 2
22
+ # This cop supports safe autocorrection (--autocorrect).
23
+ # Configuration parameters: EnforcedStyle, IndentationWidth.
24
+ # SupportedStyles: with_first_argument, with_fixed_indentation
25
+ Layout/ArgumentAlignment:
26
+ Exclude:
27
+ - 'spec/suma/cli/export_spec.rb'
28
+
29
+ # Offense count: 1
30
+ # This cop supports safe autocorrection (--autocorrect).
31
+ # Configuration parameters: EnforcedStyleAlignWith.
32
+ # SupportedStylesAlignWith: either, start_of_block, start_of_line
33
+ Layout/BlockAlignment:
34
+ Exclude:
35
+ - 'spec/suma/cli/export_spec.rb'
36
+
37
+ # Offense count: 1
38
+ # This cop supports safe autocorrection (--autocorrect).
39
+ Layout/BlockEndNewline:
40
+ Exclude:
41
+ - 'spec/suma/cli/export_spec.rb'
42
+
43
+ # Offense count: 2
44
+ # This cop supports safe autocorrection (--autocorrect).
45
+ # Configuration parameters: Width, AllowedPatterns.
46
+ Layout/IndentationWidth:
47
+ Exclude:
48
+ - 'spec/suma/cli/export_spec.rb'
49
+
50
+ # Offense count: 74
22
51
  # This cop supports safe autocorrection (--autocorrect).
23
52
  # Configuration parameters: Max, AllowHeredoc, AllowURI, AllowQualifiedName, URISchemes, IgnoreCopDirectives, AllowedPatterns, SplitStrings.
24
53
  # URISchemes: http, https
25
54
  Layout/LineLength:
26
55
  Exclude:
27
- - 'lib/suma/cli.rb'
28
56
  - 'lib/suma/cli/build.rb'
29
57
  - 'lib/suma/cli/validate.rb'
58
+ - 'lib/suma/collection_manifest.rb'
30
59
  - 'lib/suma/jsdai/figure.rb'
31
60
  - 'lib/suma/jsdai/figure_image.rb'
32
61
  - 'lib/suma/processor.rb'
33
62
  - 'lib/suma/schema_attachment.rb'
34
- - 'lib/suma/schema_collection.rb'
35
63
  - 'lib/suma/schema_document.rb'
36
64
  - 'lib/suma/thor_ext.rb'
65
+ - 'spec/suma/cli/export_spec.rb'
37
66
  - 'spec/suma/cli/extract_terms_spec.rb'
38
67
  - 'spec/suma/cli/validate_ascii_spec.rb'
39
68
  - 'spec/suma/jsdai/figure_spec.rb'
@@ -50,6 +79,11 @@ Lint/DuplicateMethods:
50
79
  Exclude:
51
80
  - 'lib/suma/express_schema.rb'
52
81
 
82
+ # Offense count: 1
83
+ Lint/IneffectiveAccessModifier:
84
+ Exclude:
85
+ - 'lib/suma/cli/export.rb'
86
+
53
87
  # Offense count: 9
54
88
  # Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes, Max.
55
89
  Metrics/AbcSize:
@@ -74,25 +108,58 @@ Metrics/PerceivedComplexity:
74
108
  Exclude:
75
109
  - 'lib/suma/jsdai/figure_image.rb'
76
110
 
77
- # Offense count: 4
111
+ # Offense count: 6
78
112
  # Configuration parameters: EnforcedStyle, CheckMethodNames, CheckSymbols, AllowedIdentifiers, AllowedPatterns.
79
113
  # SupportedStyles: snake_case, normalcase, non_integer
80
114
  # AllowedIdentifiers: TLS1_1, TLS1_2, capture3, iso8601, rfc1123_date, rfc822, rfc2822, rfc3339, x86_64
81
115
  Naming/VariableNumber:
82
116
  Exclude:
117
+ - 'spec/suma/cli/export_spec.rb'
83
118
  - 'spec/suma/cli/validate_ascii_spec.rb'
84
119
 
85
- # Offense count: 1
120
+ # Offense count: 2
86
121
  # Configuration parameters: MinSize.
87
122
  Performance/CollectionLiteralInLoop:
88
123
  Exclude:
124
+ - 'spec/suma/cli/export_spec.rb'
89
125
  - 'spec/suma/cli_spec.rb'
90
126
 
91
- # Offense count: 24
127
+ # Offense count: 2
128
+ # Configuration parameters: Prefixes, AllowedPatterns.
129
+ # Prefixes: when, with, without
130
+ RSpec/ContextWording:
131
+ Exclude:
132
+ - 'spec/suma/cli/export_spec.rb'
133
+
134
+ # Offense count: 37
92
135
  # Configuration parameters: CountAsOne.
93
136
  RSpec/ExampleLength:
94
137
  Max: 44
95
138
 
96
- # Offense count: 21
139
+ # Offense count: 2
140
+ # Configuration parameters: Max, AllowedIdentifiers, AllowedPatterns.
141
+ RSpec/IndexedLet:
142
+ Exclude:
143
+ - 'spec/suma/cli/export_spec.rb'
144
+
145
+ # Offense count: 33
97
146
  RSpec/MultipleExpectations:
98
147
  Max: 12
148
+
149
+ # Offense count: 1
150
+ # Configuration parameters: CustomTransform, IgnoreMethods, IgnoreMetadata.
151
+ RSpec/SpecFilePathFormat:
152
+ Exclude:
153
+ - '**/spec/routing/**/*'
154
+ - 'spec/suma/cli/expressir_spec.rb'
155
+
156
+ # Offense count: 1
157
+ # This cop supports safe autocorrection (--autocorrect).
158
+ # Configuration parameters: EnforcedStyle, ProceduralMethods, FunctionalMethods, AllowedMethods, AllowedPatterns, AllowBracesOnProceduralOneLiners, BracesRequiredMethods.
159
+ # SupportedStyles: line_count_based, semantic, braces_for_chaining, always_braces
160
+ # ProceduralMethods: benchmark, bm, bmbm, create, each_with_object, measure, new, realtime, tap, with_object
161
+ # FunctionalMethods: let, let!, subject, watch
162
+ # AllowedMethods: lambda, proc, it
163
+ Style/BlockDelimiters:
164
+ Exclude:
165
+ - 'spec/suma/cli/export_spec.rb'
data/README.adoc CHANGED
@@ -138,8 +138,9 @@ $ suma validate SUBCOMMAND [options]
138
138
  ----
139
139
 
140
140
  Subcommands:
141
- - `links` - Validate EXPRESS links
142
- - `ascii` - Check for non-ASCII characters in EXPRESS files
141
+
142
+ `links`:: Validate EXPRESS links
143
+ `ascii`:: Check for non-ASCII characters in EXPRESS files
143
144
 
144
145
  ==== Links subcommand
145
146
 
@@ -548,6 +549,317 @@ Sequential position:: assigned as the numbered href in both SVG and `svgmap`
548
549
  list
549
550
 
550
551
 
552
+ === Export schemas command
553
+
554
+ ==== General
555
+
556
+ The `suma export` command exports EXPRESS schemas from manifest files and/or
557
+ standalone EXPRESS files to a specified output directory.
558
+
559
+ This command is useful for extracting plain or annotated EXPRESS schemas for
560
+ distribution or further processing.
561
+
562
+ [source,sh]
563
+ ----
564
+ $ suma export -o OUTPUT_DIR FILE1 [FILE2 FILE3 ...]
565
+ ----
566
+
567
+ Parameters:
568
+
569
+ `FILE1 [FILE2 FILE3 ...]`:: One or more files to export. Each file can be:
570
+
571
+ An EXPRESS schema manifest file (`.yml`, `.yaml`)::: schemas listed in the
572
+ manifest will be exported
573
+
574
+ An standalone EXPRESS file (`.exp`)::: the schema will be exported directly
575
+
576
+ Options:
577
+
578
+ `--output=PATH`, `-o PATH`:: (required) Output directory path
579
+
580
+ `--[no-]annotations`:: Include annotations (remarks/comments) in exported schemas (default: false)
581
+
582
+ `--[no-]zip`:: Create ZIP archive of exported schemas (default: false)
583
+
584
+ ==== Behavior
585
+
586
+ The command exports schemas based on the input file types:
587
+
588
+ **For EXPRESS schema manifest files:**
589
+
590
+ * Schemas are exported while preserving the directory structure from the manifest file paths
591
+ * Schemas under `resources/` are exported to `OUTPUT/resources/`
592
+ * Schemas under `modules/` are exported to `OUTPUT/modules/`
593
+ * Other categories (`business_object_models/`, `core_model/`) follow the same pattern
594
+
595
+ **For standalone EXPRESS files:**
596
+
597
+ * Schemas are exported directly to the output directory root
598
+ * Output filename format: `{schema_id}.exp`
599
+
600
+ By default, schemas are exported without annotations (plain EXPRESS). Use the
601
+ `--annotations` flag to include remarks and comments.
602
+
603
+ The `--zip` flag creates a ZIP archive in addition to the directory output,
604
+ named `OUTPUT.zip`.
605
+
606
+ [example]
607
+ .To export schemas from a manifest file
608
+ [source,sh]
609
+ ----
610
+ $ bundle exec suma export -o express-files schemas-srl.yml
611
+ # => generates express-files/ directory with plain EXPRESS schemas
612
+ ----
613
+
614
+ [example]
615
+ .To export a single plain schema
616
+ [source,sh]
617
+ ----
618
+ $ bundle exec suma export -o express-files geometry_schema.exp
619
+ # => generates express-files/geometry_schema.exp
620
+ ----
621
+
622
+ [example]
623
+ .To export multiple plain schemas
624
+ [source,sh]
625
+ ----
626
+ $ bundle exec suma export -o express-files schema1.exp schema2.exp schema3.exp
627
+ # => generates express-files/{schema1,schema2,schema3}.exp
628
+ ----
629
+
630
+ [example]
631
+ .To export from multiple manifest files
632
+ [source,sh]
633
+ ----
634
+ $ bundle exec suma export -o express-files schemas-srl.yml additional-schemas.yml
635
+ # => exports schemas from both manifest files
636
+ ----
637
+
638
+ [example]
639
+ .To export mix of manifest and plain schemas
640
+ [source,sh]
641
+ ----
642
+ $ bundle exec suma export -o express-files schemas-srl.yml geometry_schema.exp
643
+ # => exports schemas from manifest with directory structure
644
+ # plus geometry_schema.exp at the root
645
+ ----
646
+
647
+ [example]
648
+ .To export schemas with annotations
649
+ [source,sh]
650
+ ----
651
+ $ bundle exec suma export -o express-files --annotations schemas-srl.yml
652
+ # => generates express-files/ directory with annotated EXPRESS schemas
653
+ ----
654
+
655
+ [example]
656
+ .To export and create ZIP archive
657
+ [source,sh]
658
+ ----
659
+ $ bundle exec suma export -o express-files --zip schemas-srl.yml
660
+ # => generates both:
661
+ # - express-files/ directory
662
+ # - express-files.zip archive
663
+ ----
664
+
665
+ [example]
666
+ .To export with all options
667
+ [source,sh]
668
+ ----
669
+ $ bundle exec suma export -o express-files \
670
+ --annotations \
671
+ --zip \
672
+ schemas-srl.yml geometry_schema.exp
673
+ # => generates annotated schemas in both directory and ZIP format
674
+ ----
675
+
676
+ ==== Output structure
677
+
678
+ The exported directory structure depends on the input file types:
679
+
680
+ [source]
681
+ ----
682
+ express-files/
683
+ ├── geometry_schema.exp # standalone EXPRESS file (at root)
684
+ ├── topology_schema.exp # standalone EXPRESS file (at root)
685
+ ├── resources/ # from manifest files
686
+ │ ├── action_schema/
687
+ │ │ └── action_schema.exp
688
+ │ └── ...
689
+ ├── modules/ # from manifest files
690
+ │ ├── activity/
691
+ │ │ ├── arm.exp
692
+ │ │ └── mim.exp
693
+ │ └── ...
694
+ ├── business_object_models/ # from manifest files
695
+ │ └── ...
696
+ └── core_model/ # from manifest files
697
+ └── ...
698
+ ----
699
+
700
+
701
+ === Expressir command
702
+
703
+ ==== General
704
+
705
+ The `suma expressir` command provides access to all Expressir commands for
706
+ working with EXPRESS schemas. This includes documentation coverage analysis,
707
+ schema formatting, validation, and other EXPRESS-related operations.
708
+
709
+ Expressir is a comprehensive toolkit for EXPRESS schema processing that Suma
710
+ integrates to provide additional functionality beyond Suma's core features.
711
+
712
+ [source,sh]
713
+ ----
714
+ $ suma expressir SUBCOMMAND ...ARGS [options]
715
+ ----
716
+
717
+ Available subcommands:
718
+
719
+ `coverage`:: Check documentation coverage of EXPRESS schemas
720
+ `format`:: Pretty print EXPRESS schemas
721
+ `clean`:: Strip remarks and prettify EXPRESS schemas
722
+ `validate`:: Validate EXPRESS schemas
723
+ `benchmark`:: Benchmark schema loading performance
724
+ `version`:: Display expressir version
725
+
726
+ ==== Coverage subcommand
727
+
728
+ The `coverage` subcommand analyzes EXPRESS schemas to determine documentation
729
+ coverage, reporting how many EXPRESS objects (entities, constants, functions,
730
+ types, etc.) have been documented with Annotated EXPRESS.
731
+
732
+ This addresses the need to measure and improve documentation quality in EXPRESS
733
+ schema collections.
734
+
735
+ [source,sh]
736
+ ----
737
+ $ suma expressir coverage PATH [PATH...] [options]
738
+ ----
739
+
740
+ Parameters:
741
+
742
+ `PATH`:: One or more paths to analyze. Each path can be:
743
+ +
744
+ --
745
+ * An EXPRESS schema manifest file (`.yml`, `.yaml`)
746
+ * A single EXPRESS file (`.exp`)
747
+ * A directory containing EXPRESS files
748
+ --
749
+
750
+ Options:
751
+
752
+ `--format=FORMAT`:: Output format: `text` (default), `json`, or `yaml`
753
+ `--output=FILE`:: Output file path for JSON/YAML formats (defaults to
754
+ `coverage_report.json` or `coverage_report.yaml`)
755
+ `--exclude=TYPES`:: Comma-separated list of EXPRESS entity types to exclude
756
+ from coverage analysis (e.g., `TYPE,CONSTANT,TYPE:SELECT`)
757
+ `--ignore-files=FILE`:: Path to YAML file containing array of file patterns
758
+ to ignore from overall coverage calculation
759
+
760
+ [example]
761
+ .To analyze documentation coverage of a schema collection
762
+ [source,sh]
763
+ ----
764
+ $ suma expressir coverage schemas-srl.yml
765
+ ----
766
+
767
+ .To analyze coverage and export as JSON
768
+ [source,sh]
769
+ ----
770
+ $ suma expressir coverage schemas-srl.yml --format=json --output=coverage.json
771
+ ----
772
+
773
+ .To analyze with exclusions
774
+ [source,sh]
775
+ ----
776
+ $ suma expressir coverage schemas/ --exclude=TYPE:ENUMERATION,PARAMETER
777
+ ----
778
+
779
+ .To analyze multiple sources
780
+ [source,sh]
781
+ ----
782
+ $ suma expressir coverage schemas-srl.yml additional-schemas.yml
783
+ ----
784
+
785
+ The coverage report includes:
786
+
787
+ * Overall coverage statistics (total entities, documented entities, percentage)
788
+ * Per-file coverage details
789
+ * Per-directory coverage summaries
790
+ * List of undocumented entities by type and name
791
+
792
+ ==== Format subcommand
793
+
794
+ The `format` subcommand pretty-prints EXPRESS schemas.
795
+
796
+ [source,sh]
797
+ ----
798
+ $ suma expressir format PATH
799
+ ----
800
+
801
+ Parameters:
802
+
803
+ `PATH`:: Path to the EXPRESS schema file to format
804
+
805
+ [example]
806
+ .To format an EXPRESS schema
807
+ [source,sh]
808
+ ----
809
+ $ suma expressir format schema.exp
810
+ ----
811
+
812
+ ==== Clean subcommand
813
+
814
+ The `clean` subcommand strips remarks and prettifies EXPRESS schemas.
815
+
816
+ [source,sh]
817
+ ----
818
+ $ suma expressir clean PATH [options]
819
+ ----
820
+
821
+ Parameters:
822
+
823
+ `PATH`:: Path to the EXPRESS schema file to clean
824
+
825
+ Options:
826
+
827
+ `--output=FILE`:: Output file path (defaults to stdout)
828
+
829
+ [example]
830
+ .To clean an EXPRESS schema and save to file
831
+ [source,sh]
832
+ ----
833
+ $ suma expressir clean schema.exp --output=cleaned_schema.exp
834
+ ----
835
+
836
+ ==== Validate subcommand
837
+
838
+ The `validate` subcommand validates EXPRESS schemas for syntactic correctness.
839
+
840
+ [source,sh]
841
+ ----
842
+ $ suma expressir validate PATH [PATH...]
843
+ ----
844
+
845
+ Parameters:
846
+
847
+ `PATH`:: One or more paths to EXPRESS schema files or directories to validate
848
+
849
+ [example]
850
+ .To validate EXPRESS schemas
851
+ [source,sh]
852
+ ----
853
+ $ suma expressir validate schema.exp
854
+ $ suma expressir validate schemas/
855
+ ----
856
+
857
+ ==== Additional information
858
+
859
+ For complete documentation of all Expressir commands and options, see the
860
+ https://github.com/lutaml/expressir[Expressir documentation].
861
+
862
+
551
863
  == Usage: Ruby
552
864
 
553
865
  === General
@@ -0,0 +1,110 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "thor"
4
+ require_relative "../thor_ext"
5
+
6
+ module Suma
7
+ module Cli
8
+ # Export command for exporting EXPRESS schemas from a manifest
9
+ class Export < Thor
10
+ desc "export *FILES",
11
+ "Export EXPRESS schemas from manifest files or " \
12
+ "standalone EXPRESS files"
13
+ option :output, type: :string, aliases: "-o", required: true,
14
+ desc: "Output directory path"
15
+ option :annotations, type: :boolean, default: false,
16
+ desc: "Include annotations (remarks/comments)"
17
+ option :zip, type: :boolean, default: false,
18
+ desc: "Create ZIP archive of exported schemas"
19
+
20
+ def export(*files)
21
+ require_relative "../schema_exporter"
22
+ require_relative "../utils"
23
+ require "expressir"
24
+
25
+ validate_files(files)
26
+ run(files, options)
27
+ end
28
+
29
+ private
30
+
31
+ def validate_files(files)
32
+ if files.empty?
33
+ raise ArgumentError, "At least one file must be specified"
34
+ end
35
+
36
+ files.each do |file|
37
+ unless File.exist?(file)
38
+ raise Errno::ENOENT, "Specified file `#{file}` not found."
39
+ end
40
+ end
41
+ end
42
+
43
+ def run(files, options)
44
+ schemas = load_schemas_from_files(files)
45
+
46
+ exporter = SchemaExporter.new(
47
+ schemas: schemas,
48
+ output_path: options[:output],
49
+ options: {
50
+ annotations: options[:annotations],
51
+ create_zip: options[:zip],
52
+ },
53
+ )
54
+
55
+ exporter.export
56
+ end
57
+
58
+ def load_schemas_from_files(files)
59
+ all_schemas = []
60
+
61
+ files.each do |file|
62
+ all_schemas += process_file(file)
63
+ end
64
+
65
+ all_schemas
66
+ end
67
+
68
+ def process_file(file)
69
+ case File.extname(file).downcase
70
+ when ".yml", ".yaml"
71
+ load_manifest_schemas(file)
72
+ when ".exp"
73
+ [create_schema_from_exp_file(file)]
74
+ else
75
+ raise ArgumentError, "Unsupported file type: #{file}. " \
76
+ "Only .yml, .yaml, and .exp files are " \
77
+ "supported."
78
+ end
79
+ end
80
+
81
+ def load_manifest_schemas(file)
82
+ manifest = Expressir::SchemaManifest.from_file(file)
83
+ manifest.schemas
84
+ end
85
+
86
+ def create_schema_from_exp_file(exp_file)
87
+ # Create a schema object from a standalone EXPRESS file
88
+ # The id will be determined during parsing
89
+ StandaloneSchema.new(
90
+ id: nil,
91
+ path: File.expand_path(exp_file),
92
+ )
93
+ end
94
+
95
+ # Simple schema class for standalone EXPRESS files
96
+ class StandaloneSchema
97
+ attr_accessor :id, :path
98
+
99
+ def initialize(id:, path:)
100
+ @id = id
101
+ @path = path
102
+ end
103
+ end
104
+
105
+ def self.exit_on_failure?
106
+ true
107
+ end
108
+ end
109
+ end
110
+ end
data/lib/suma/cli.rb CHANGED
@@ -3,6 +3,7 @@
3
3
  require "thor"
4
4
  require_relative "thor_ext"
5
5
  require_relative "cli/validate"
6
+ require "expressir/cli"
6
7
 
7
8
  module Suma
8
9
  module Cli
@@ -15,10 +16,9 @@ module Suma
15
16
  option :compile, type: :boolean, default: true,
16
17
  desc: "Compile or skip compile of collection"
17
18
  option :schemas_all_path, type: :string, aliases: "-s",
18
- desc: "Generate file that contains all schemas in the collection."
19
+ desc: "Generate file that contains all " \
20
+ "schemas in the collection."
19
21
  def build(_site_manifest)
20
- # # If no arguments, add an empty array to ensure the default command is triggered
21
- # args = [] if args.empty?
22
22
  require_relative "cli/build"
23
23
  Cli::Build.start
24
24
  end
@@ -60,9 +60,26 @@ module Suma
60
60
  Cli::ConvertJsdai.start
61
61
  end
62
62
 
63
+ desc "export *FILES",
64
+ "Export EXPRESS schemas from manifest files or " \
65
+ "standalone EXPRESS files"
66
+ option :output, type: :string, aliases: "-o", required: true,
67
+ desc: "Output directory path"
68
+ option :annotations, type: :boolean, default: false,
69
+ desc: "Include annotations (remarks/comments)"
70
+ option :zip, type: :boolean, default: false,
71
+ desc: "Create ZIP archive of exported schemas"
72
+ def export(*_files)
73
+ require_relative "cli/export"
74
+ Cli::Export.start
75
+ end
76
+
63
77
  desc "validate SUBCOMMAND ...ARGS", "Validate express documents"
64
78
  subcommand "validate", Cli::Validate
65
79
 
80
+ desc "expressir SUBCOMMAND ...ARGS", "Expressir commands"
81
+ subcommand "expressir", Expressir::Cli
82
+
66
83
  def self.exit_on_failure?
67
84
  true
68
85
  end
@@ -36,12 +36,25 @@ module Suma
36
36
  model.entry = CollectionManifest.from_yaml(value.to_yaml)
37
37
  end
38
38
 
39
+ # Recursively exports schema configuration by traversing collection manifests.
40
+ #
41
+ # This method builds an EXPRESS Schema Manifest (Expressir::SchemaManifest) by:
42
+ # 1. Starting with an empty or existing Expressir::SchemaManifest
43
+ # 2. Recursively traversing child entries to collect schemas
44
+ # 3. Using Expressir::SchemaManifest#concat to combine manifests
45
+ #
46
+ # The actual schema manifest operations (creation, concatenation, serialization)
47
+ # are handled by Expressir's SchemaManifest class, keeping the logic DRY.
48
+ #
49
+ # @param path [String] Base path for resolving relative schema paths
50
+ # @return [Expressir::SchemaManifest] Combined schema manifest
39
51
  def export_schema_config(path)
40
52
  export_config = @schema_config || Expressir::SchemaManifest.new
41
53
  return export_config unless entry
42
54
 
43
55
  entry.each do |x|
44
56
  child_config = x.export_schema_config(path)
57
+ # Use Expressir's concat method to combine schema manifests
45
58
  export_config.concat(child_config) if child_config
46
59
  end
47
60
 
@@ -6,19 +6,20 @@ require "expressir"
6
6
 
7
7
  module Suma
8
8
  class ExpressSchema
9
- attr_accessor :path, :id, :parsed, :output_path
9
+ attr_accessor :path, :id, :parsed, :output_path, :is_standalone_file
10
10
 
11
- def initialize(id:, path:, output_path:)
11
+ def initialize(id:, path:, output_path:, is_standalone_file: false)
12
12
  @path = Pathname.new(path).expand_path
13
13
  @id = id
14
14
  @output_path = output_path
15
+ @is_standalone_file = is_standalone_file
15
16
  end
16
17
 
17
18
  def type
18
- case @path.to_s
19
- when %r{.*/resources/.*}
19
+ path_str = @path.to_s
20
+ if path_str.include?("/resources/")
20
21
  "resources"
21
- when %r{.*/modules/.*}
22
+ elsif path_str.include?("/modules/")
22
23
  "modules"
23
24
  else
24
25
  "unknown_type"
@@ -39,17 +40,35 @@ module Suma
39
40
  end
40
41
 
41
42
  def filename_plain
42
- File.join(@output_path, type, id, File.basename(@path))
43
+ ensure_id_loaded
44
+ build_output_filename
43
45
  end
44
46
 
45
- def save_exp
47
+ def ensure_id_loaded
48
+ parsed unless @id
49
+ end
50
+
51
+ def build_output_filename
52
+ if @is_standalone_file
53
+ # For standalone files, output directly to output_path
54
+ File.join(@output_path, "#{@id}.exp")
55
+ else
56
+ # For manifest schemas, preserve directory structure
57
+ # Note: @output_path already contains the category (resources/modules)
58
+ File.join(@output_path, @id, File.basename(@path))
59
+ end
60
+ end
61
+
62
+ def save_exp(with_annotations: false)
46
63
  relative_path = Pathname.new(filename_plain).relative_path_from(Dir.pwd)
47
- Utils.log "Save plain schema: #{relative_path}"
64
+ schema_type = with_annotations ? "annotated" : "plain"
65
+ Utils.log "Save #{schema_type} schema: #{relative_path}"
48
66
 
49
67
  # return if File.exist?(filename_plain)
50
68
  FileUtils.mkdir_p(File.dirname(filename_plain))
51
69
 
52
- File.write(filename_plain, to_plain)
70
+ content = with_annotations ? parsed.to_s(no_remarks: false) : to_plain
71
+ File.write(filename_plain, content)
53
72
  end
54
73
  end
55
74
  end
@@ -15,12 +15,15 @@ module Suma
15
15
  def run(metanorma_yaml_path:, schemas_all_path:, compile:,
16
16
  output_directory: "_site")
17
17
  Utils.log "Current directory: #{Dir.getwd}, writing #{schemas_all_path}..."
18
+
19
+ # Generate EXPRESS Schema Manifest by traversing Metanorma Site Manifest
20
+ # This uses Expressir::SchemaManifest for all manifest operations
18
21
  collection_config = export_schema_config(metanorma_yaml_path,
19
22
  schemas_all_path)
20
23
 
21
24
  unless compile
22
25
  Utils.log "No compile option set. Skipping schema compilation."
23
- nil
26
+ return nil
24
27
  end
25
28
 
26
29
  Utils.log "Compiling schema collection..."
@@ -33,6 +36,18 @@ output_directory: "_site")
33
36
 
34
37
  private
35
38
 
39
+ # Generates EXPRESS Schema Manifest from Metanorma Site Manifest structure.
40
+ #
41
+ # This method:
42
+ # 1. Reads the Metanorma site manifest to discover collection files
43
+ # 2. Traverses collection manifests to find individual schemas.yaml files
44
+ # 3. Uses Expressir::SchemaManifest to aggregate and manage schema entries
45
+ # 4. Saves the unified schema manifest using Expressir's to_file method
46
+ #
47
+ # @param metanorma_yaml_path [String] Path to Metanorma site manifest
48
+ # @param schemas_all_path [String] Output path for unified schema manifest
49
+ # @return [CollectionConfig] The loaded collection configuration
50
+ # rubocop:disable Metrics/MethodLength
36
51
  def export_schema_config(metanorma_yaml_path, schemas_all_path)
37
52
  # This reads the metanorma.yml file
38
53
  site_config = Suma::SiteConfig::Config.from_file(metanorma_yaml_path)
@@ -43,13 +58,17 @@ output_directory: "_site")
43
58
  collection_config.path = collection_config_path
44
59
  collection_config.manifest.expand_schemas_only("schema_docs")
45
60
 
61
+ # Recursively traverse collection manifests to build unified schema manifest
62
+ # Uses Expressir::SchemaManifest methods (concat, to_file) for operations
46
63
  exported_schema_config = collection_config.manifest.export_schema_config(schemas_all_path)
47
64
  exported_schema_config.path = schemas_all_path
48
65
 
66
+ # Save using Expressir's SchemaManifest#to_file method
49
67
  exported_schema_config.to_file
50
68
 
51
69
  collection_config
52
70
  end
71
+ # rubocop:enable Metrics/MethodLength
53
72
 
54
73
  def compile_schema(schemas_all_path, collection_config)
55
74
  # now get rid of the source documents for schema sources
@@ -3,13 +3,14 @@
3
3
  require_relative "express_schema"
4
4
  require_relative "schema_attachment"
5
5
  require_relative "schema_document"
6
+ require_relative "schema_exporter"
6
7
  require "expressir"
7
8
  require_relative "utils"
8
9
 
9
10
  module Suma
10
11
  class SchemaCollection
11
- attr_accessor :config, :schemas, :docs, :output_path_docs, :output_path_schemas,
12
- :manifest
12
+ attr_accessor :config, :schemas, :docs, :output_path_docs,
13
+ :output_path_schemas, :manifest
13
14
 
14
15
  def initialize(config: nil, config_yaml: nil, output_path_docs: nil,
15
16
  output_path_schemas: nil, manifest: nil)
@@ -17,7 +18,9 @@ module Suma
17
18
  @docs = {}
18
19
  @schema_name_to_docs = {}
19
20
  @output_path_docs = Pathname.new(output_path_docs || Dir.pwd).expand_path
20
- @output_path_schemas = Pathname.new(output_path_schemas || Dir.pwd).expand_path
21
+ @output_path_schemas = Pathname.new(
22
+ output_path_schemas || Dir.pwd,
23
+ ).expand_path
21
24
  @config = config
22
25
  @config ||= config_yaml && Expressir::SchemaManifest.from_file(config_yaml)
23
26
  @manifest = manifest
@@ -62,11 +65,17 @@ module Suma
62
65
  end
63
66
  end
64
67
 
68
+ # rubocop:disable Metrics/MethodLength
65
69
  def compile
66
70
  finalize
67
- schemas.each_pair do |_schema_id, entry|
68
- entry.save_exp
69
- end
71
+
72
+ # Use SchemaExporter for schema export
73
+ exporter = SchemaExporter.new(
74
+ schemas: @config.schemas,
75
+ output_path: @output_path_schemas,
76
+ options: { annotations: false },
77
+ )
78
+ exporter.export
70
79
 
71
80
  docs.each_pair do |_schema_id, entry|
72
81
  entry.compile
@@ -98,5 +107,6 @@ module Suma
98
107
  # end
99
108
  # pp results
100
109
  end
110
+ # rubocop:enable Metrics/MethodLength
101
111
  end
102
112
  end
@@ -0,0 +1,112 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "express_schema"
4
+ require_relative "utils"
5
+ require "fileutils"
6
+
7
+ module Suma
8
+ # SchemaExporter exports EXPRESS schemas from a manifest
9
+ # with configurable options for annotations and ZIP packaging
10
+ class SchemaExporter
11
+ attr_reader :schemas, :output_path, :options
12
+
13
+ def initialize(schemas:, output_path:, options: {})
14
+ @schemas = schemas
15
+ @output_path = Pathname.new(output_path).expand_path
16
+ @options = default_options.merge(options)
17
+ end
18
+
19
+ def export
20
+ Utils.log "Exporting schemas to: #{output_path}"
21
+
22
+ export_to_directory(schemas)
23
+ create_zip_archive if options[:create_zip]
24
+
25
+ Utils.log "Export complete!"
26
+ end
27
+
28
+ private
29
+
30
+ def default_options
31
+ {
32
+ annotations: false,
33
+ create_zip: false,
34
+ structure: :preserve,
35
+ }
36
+ end
37
+
38
+ def export_to_directory(schemas)
39
+ schemas.each do |schema|
40
+ export_single_schema(schema)
41
+ end
42
+ end
43
+
44
+ def export_single_schema(schema)
45
+ # Check if this is a standalone EXPRESS file
46
+ # (not from a manifest structure)
47
+ is_standalone_file = schema.is_a?(Cli::Export::StandaloneSchema)
48
+ schema_output_path = determine_output_path(schema, is_standalone_file)
49
+
50
+ express_schema = ExpressSchema.new(
51
+ id: schema.id,
52
+ path: schema.path.to_s,
53
+ output_path: schema_output_path,
54
+ is_standalone_file: is_standalone_file,
55
+ )
56
+
57
+ express_schema.save_exp(with_annotations: options[:annotations])
58
+ end
59
+
60
+ def determine_output_path(schema, is_standalone_file)
61
+ if is_standalone_file
62
+ # For standalone files, output directly to the root
63
+ output_path.to_s
64
+ else
65
+ # For manifest schemas, preserve directory structure
66
+ category = categorize_schema(schema)
67
+ output_path.join(category).to_s
68
+ end
69
+ end
70
+
71
+ # rubocop:disable Metrics/MethodLength
72
+ def categorize_schema(schema)
73
+ path = schema.path.to_s
74
+
75
+ # Check if this is from a manifest structure or a standalone EXPRESS file
76
+ case path
77
+ when %r{/resources/}
78
+ "resources"
79
+ when %r{/modules/}
80
+ "modules"
81
+ when %r{/business_object_models/}
82
+ "business_object_models"
83
+ when %r{/core_model/}
84
+ "core_model"
85
+ else
86
+ # standalone EXPRESS file not from a manifest structure
87
+ "standalone"
88
+ end
89
+ end
90
+ # rubocop:enable Metrics/MethodLength
91
+
92
+ # rubocop:disable Metrics/MethodLength
93
+ def create_zip_archive
94
+ require "zip"
95
+
96
+ zip_path = "#{output_path}.zip"
97
+ Utils.log "Creating ZIP archive: #{zip_path}"
98
+
99
+ Zip::File.open(zip_path, Zip::File::CREATE) do |zipfile|
100
+ Dir.glob("#{output_path}/**/*").each do |file|
101
+ next if File.directory?(file)
102
+
103
+ relative_path = file.sub("#{output_path}/", "")
104
+ zipfile.add(relative_path, file)
105
+ end
106
+ end
107
+
108
+ Utils.log "ZIP archive created: #{zip_path}"
109
+ end
110
+ # rubocop:enable Metrics/MethodLength
111
+ end
112
+ end
data/lib/suma/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Suma
4
- VERSION = "0.1.22"
4
+ VERSION = "0.1.24"
5
5
  end
data/suma.gemspec CHANGED
@@ -39,6 +39,7 @@ Gem::Specification.new do |spec| # rubocop:disable Metrics/BlockLength
39
39
  spec.add_dependency "metanorma-cli"
40
40
  spec.add_dependency "plurimath"
41
41
  spec.add_dependency "ruby-progressbar"
42
+ spec.add_dependency "rubyzip", "~> 2.3"
42
43
  spec.add_dependency "table_tennis"
43
44
  spec.add_dependency "thor", ">= 0.20"
44
45
  spec.metadata["rubygems_mfa_required"] = "true"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: suma
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.22
4
+ version: 0.1.24
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-10-05 00:00:00.000000000 Z
11
+ date: 2025-10-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: expressir
@@ -94,6 +94,20 @@ dependencies:
94
94
  - - ">="
95
95
  - !ruby/object:Gem::Version
96
96
  version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rubyzip
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '2.3'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '2.3'
97
111
  - !ruby/object:Gem::Dependency
98
112
  name: table_tennis
99
113
  requirement: !ruby/object:Gem::Requirement
@@ -149,6 +163,7 @@ files:
149
163
  - lib/suma/cli.rb
150
164
  - lib/suma/cli/build.rb
151
165
  - lib/suma/cli/convert_jsdai.rb
166
+ - lib/suma/cli/export.rb
152
167
  - lib/suma/cli/extract_terms.rb
153
168
  - lib/suma/cli/generate_schemas.rb
154
169
  - lib/suma/cli/reformat.rb
@@ -166,6 +181,7 @@ files:
166
181
  - lib/suma/schema_attachment.rb
167
182
  - lib/suma/schema_collection.rb
168
183
  - lib/suma/schema_document.rb
184
+ - lib/suma/schema_exporter.rb
169
185
  - lib/suma/site_config.rb
170
186
  - lib/suma/thor_ext.rb
171
187
  - lib/suma/utils.rb