hati-rails-api 0.1.0.beta1
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/LICENSE +21 -0
- data/README.md +237 -0
- data/hati-rails-api.gemspec +39 -0
- data/lib/generators/hati_rails_api/context_generator.rb +270 -0
- data/lib/hati_rails_api/context/configuration/configuration.rb +50 -0
- data/lib/hati_rails_api/context/configuration/domain_configuration.rb +55 -0
- data/lib/hati_rails_api/context/core/error_handler.rb +76 -0
- data/lib/hati_rails_api/context/core/loader.rb +54 -0
- data/lib/hati_rails_api/context/core/migration.rb +68 -0
- data/lib/hati_rails_api/context/core/public_api.rb +114 -0
- data/lib/hati_rails_api/context/core.rb +18 -0
- data/lib/hati_rails_api/context/extensions/string_extensions.rb +11 -0
- data/lib/hati_rails_api/context/generators/base_generator.rb +33 -0
- data/lib/hati_rails_api/context/generators/domain_generator.rb +219 -0
- data/lib/hati_rails_api/context/generators/file_generation.rb +72 -0
- data/lib/hati_rails_api/context/generators/generator.rb +212 -0
- data/lib/hati_rails_api/context/generators/layer_component_generator.rb +88 -0
- data/lib/hati_rails_api/context/generators/model_endpoint_generator.rb +97 -0
- data/lib/hati_rails_api/context/generators/operation_generator.rb +182 -0
- data/lib/hati_rails_api/context/layers/operation_layer.rb +45 -0
- data/lib/hati_rails_api/context/layers/standard_layer.rb +44 -0
- data/lib/hati_rails_api/context/managers/rollback_manager.rb +123 -0
- data/lib/hati_rails_api/context/shared/content_generators.rb +125 -0
- data/lib/hati_rails_api/context/shared/layer_factory.rb +37 -0
- data/lib/hati_rails_api/context.rb +30 -0
- data/lib/hati_rails_api/errors/unsupported_operation_error.rb +11 -0
- data/lib/hati_rails_api/macro/serializer_macro.rb +13 -0
- data/lib/hati_rails_api/response_handler.rb +71 -0
- data/lib/hati_rails_api/version.rb +5 -0
- data/lib/hati_rails_api.rb +15 -0
- metadata +121 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module HatiRailsApi
|
|
4
|
+
module Context
|
|
5
|
+
module Core
|
|
6
|
+
# Comprehensive error handling module for the context system
|
|
7
|
+
# Provides consistent error handling and user-friendly messages
|
|
8
|
+
module ErrorHandler
|
|
9
|
+
# Custom errors for the context system
|
|
10
|
+
class ContextError < StandardError; end
|
|
11
|
+
class ConfigurationError < ContextError; end
|
|
12
|
+
class GenerationError < ContextError; end
|
|
13
|
+
class RollbackError < ContextError; end
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
# Wraps operations with comprehensive error handling
|
|
18
|
+
#
|
|
19
|
+
# @param operation_name [String] Name of the operation for logging
|
|
20
|
+
# @yield The block to execute with error handling
|
|
21
|
+
# @return [Object] The result of the block
|
|
22
|
+
# @raise [ContextError] Various context-specific errors
|
|
23
|
+
def with_error_handling(operation_name = caller_locations(1, 1)[0].label)
|
|
24
|
+
yield
|
|
25
|
+
rescue ArgumentError => e
|
|
26
|
+
handle_argument_error(e, operation_name)
|
|
27
|
+
rescue StandardError => e
|
|
28
|
+
handle_standard_error(e, operation_name)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Handle argument errors with user-friendly messages
|
|
32
|
+
def handle_argument_error(error, operation_name)
|
|
33
|
+
puts "Invalid arguments for #{operation_name}: #{error.message}"
|
|
34
|
+
puts 'Please check the documentation for correct usage'
|
|
35
|
+
raise ConfigurationError, error.message
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Handle standard errors with context information
|
|
39
|
+
def handle_standard_error(error, operation_name)
|
|
40
|
+
puts "Error during #{operation_name}: #{error.message}"
|
|
41
|
+
puts "Location: #{error.backtrace&.first}"
|
|
42
|
+
|
|
43
|
+
case operation_name
|
|
44
|
+
when /configure/
|
|
45
|
+
puts 'Check your configuration syntax and required parameters'
|
|
46
|
+
raise ConfigurationError, error.message
|
|
47
|
+
when /generate/
|
|
48
|
+
puts 'Verify your generation block and file permissions'
|
|
49
|
+
raise GenerationError, error.message
|
|
50
|
+
when /rollback/
|
|
51
|
+
puts 'Check if the timestamp exists and files are accessible'
|
|
52
|
+
raise RollbackError, error.message
|
|
53
|
+
else
|
|
54
|
+
raise ContextError, error.message
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Log successful operations
|
|
59
|
+
def log_success(operation, details = nil)
|
|
60
|
+
puts "#{operation} completed successfully"
|
|
61
|
+
puts "#{details}" if details
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Log warnings
|
|
65
|
+
def log_warning(message)
|
|
66
|
+
puts "Warning: #{message}"
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Log information
|
|
70
|
+
def log_info(message)
|
|
71
|
+
puts "#{message}"
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module HatiRailsApi
|
|
4
|
+
module Context
|
|
5
|
+
module Core
|
|
6
|
+
# Handles loading of all context components in the correct dependency order
|
|
7
|
+
# Ensures proper initialization and prevents circular dependencies
|
|
8
|
+
module Loader
|
|
9
|
+
# Load all components in the correct order
|
|
10
|
+
def self.load_all_components
|
|
11
|
+
load_extensions
|
|
12
|
+
load_configurations
|
|
13
|
+
load_shared_components
|
|
14
|
+
load_layers
|
|
15
|
+
load_generators
|
|
16
|
+
load_managers
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private_class_method def self.load_extensions
|
|
20
|
+
require_relative '../extensions/string_extensions'
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private_class_method def self.load_configurations
|
|
24
|
+
require_relative '../configuration/configuration'
|
|
25
|
+
require_relative '../configuration/domain_configuration'
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private_class_method def self.load_shared_components
|
|
29
|
+
require_relative '../shared/layer_factory'
|
|
30
|
+
require_relative '../shared/content_generators'
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private_class_method def self.load_layers
|
|
34
|
+
require_relative '../layers/standard_layer'
|
|
35
|
+
require_relative '../layers/operation_layer'
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private_class_method def self.load_generators
|
|
39
|
+
require_relative '../generators/base_generator'
|
|
40
|
+
require_relative '../generators/file_generation'
|
|
41
|
+
require_relative '../generators/model_endpoint_generator'
|
|
42
|
+
require_relative '../generators/layer_component_generator'
|
|
43
|
+
require_relative '../generators/operation_generator'
|
|
44
|
+
require_relative '../generators/domain_generator'
|
|
45
|
+
require_relative '../generators/generator'
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private_class_method def self.load_managers
|
|
49
|
+
require_relative '../managers/rollback_manager'
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module HatiRailsApi
|
|
4
|
+
module Context
|
|
5
|
+
# Base class for context migrations
|
|
6
|
+
# Provides Rails-like migration interface for context generation
|
|
7
|
+
class Migration
|
|
8
|
+
def initialize
|
|
9
|
+
@generator = nil
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# Domain definition method
|
|
13
|
+
#
|
|
14
|
+
# @param name [Symbol] Domain name
|
|
15
|
+
# @param block [Proc] Domain configuration block
|
|
16
|
+
def domain(name, &block)
|
|
17
|
+
ensure_generator
|
|
18
|
+
@generator.domain(name, &block)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Model generation method
|
|
22
|
+
#
|
|
23
|
+
# @param names [Array, Symbol] Model names to generate
|
|
24
|
+
def model(names)
|
|
25
|
+
ensure_generator
|
|
26
|
+
Array(names).each { |name| @generator.model(name) }
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Endpoint generation method
|
|
30
|
+
#
|
|
31
|
+
# @param names [Array, Symbol] Endpoint names to generate
|
|
32
|
+
def endpoint(names)
|
|
33
|
+
ensure_generator
|
|
34
|
+
Array(names).each { |name| @generator.endpoint(name) }
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Execute the migration - called automatically after change
|
|
38
|
+
def execute
|
|
39
|
+
return unless @generator
|
|
40
|
+
|
|
41
|
+
@generator.execute
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Method to be overridden by migration classes
|
|
45
|
+
def change
|
|
46
|
+
raise NotImplementedError, 'Migration classes must implement the change method'
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Run the migration (calls change then execute)
|
|
50
|
+
def run
|
|
51
|
+
change
|
|
52
|
+
execute
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
def ensure_generator
|
|
58
|
+
return if @generator
|
|
59
|
+
|
|
60
|
+
# Get the global configuration
|
|
61
|
+
config = HatiRailsApi::Context.instance_variable_get(:@global_config)
|
|
62
|
+
|
|
63
|
+
# Create a new generator instance with force option
|
|
64
|
+
@generator = HatiRailsApi::Context::Generator.new(config, force: true)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'error_handler'
|
|
4
|
+
|
|
5
|
+
module HatiRailsApi
|
|
6
|
+
module Context
|
|
7
|
+
module Core
|
|
8
|
+
# Public API module providing all external-facing methods
|
|
9
|
+
# Includes comprehensive error handling and validation
|
|
10
|
+
module PublicAPI
|
|
11
|
+
include ErrorHandler
|
|
12
|
+
|
|
13
|
+
# Global configuration storage
|
|
14
|
+
attr_accessor :global_config
|
|
15
|
+
|
|
16
|
+
# Configure the context system with global settings
|
|
17
|
+
#
|
|
18
|
+
# @param block [Proc] Configuration block
|
|
19
|
+
# @return [Configuration] The configuration object
|
|
20
|
+
# @raise [ArgumentError] If no block is provided
|
|
21
|
+
#
|
|
22
|
+
# @example
|
|
23
|
+
# Context.configure do |config|
|
|
24
|
+
# config.base_path 'app/contexts'
|
|
25
|
+
# config.model path: 'app/models', base: 'ApplicationRecord'
|
|
26
|
+
# end
|
|
27
|
+
def configure(&block)
|
|
28
|
+
raise ArgumentError, 'Configuration block is required' unless block_given?
|
|
29
|
+
|
|
30
|
+
with_error_handling do
|
|
31
|
+
@global_config = Configuration.new
|
|
32
|
+
@global_config.instance_eval(&block)
|
|
33
|
+
@global_config
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Generate files based on configuration
|
|
38
|
+
#
|
|
39
|
+
# @param force [Boolean] Force overwrite existing files
|
|
40
|
+
# @param block [Proc] Generation block
|
|
41
|
+
# @return [Boolean] True if generation was successful
|
|
42
|
+
# @raise [ArgumentError] If no block is provided
|
|
43
|
+
#
|
|
44
|
+
# @example
|
|
45
|
+
# Context.generate do |ctx|
|
|
46
|
+
# ctx.domain :user do |domain|
|
|
47
|
+
# domain.operation { |op| op.component [:create, :update] }
|
|
48
|
+
# domain.endpoint enabled: true
|
|
49
|
+
# end
|
|
50
|
+
# end
|
|
51
|
+
def generate(force: false, &block)
|
|
52
|
+
raise ArgumentError, 'Generation block is required' unless block_given?
|
|
53
|
+
|
|
54
|
+
with_error_handling do
|
|
55
|
+
generator = Generator.new(@global_config, force: force)
|
|
56
|
+
generator.instance_eval(&block)
|
|
57
|
+
generator.execute
|
|
58
|
+
true
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Rollback generated files by timestamp or last generation
|
|
63
|
+
#
|
|
64
|
+
# @param timestamp [String, nil] Specific timestamp to rollback, or nil for last
|
|
65
|
+
# @return [Boolean] True if rollback was successful
|
|
66
|
+
#
|
|
67
|
+
# @example
|
|
68
|
+
# Context.rollback('20240101120000') # specific timestamp
|
|
69
|
+
# Context.rollback # last generation
|
|
70
|
+
def rollback(timestamp = nil)
|
|
71
|
+
with_error_handling do
|
|
72
|
+
rollback_manager = RollbackManager.new
|
|
73
|
+
|
|
74
|
+
if timestamp
|
|
75
|
+
rollback_manager.rollback_by_timestamp?(timestamp)
|
|
76
|
+
else
|
|
77
|
+
rollback_manager.rollback_last?
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# List all tracked generations with detailed information
|
|
83
|
+
#
|
|
84
|
+
# @return [Boolean] True if listing was successful
|
|
85
|
+
#
|
|
86
|
+
# @example
|
|
87
|
+
# Context.list_generations
|
|
88
|
+
def list_generations
|
|
89
|
+
with_error_handling do
|
|
90
|
+
RollbackManager.new.list_generations
|
|
91
|
+
true
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Reset the global configuration
|
|
96
|
+
#
|
|
97
|
+
# @return [Boolean] True if reset was successful
|
|
98
|
+
def reset_configuration
|
|
99
|
+
with_error_handling do
|
|
100
|
+
@global_config = nil
|
|
101
|
+
true
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Check if the system is configured
|
|
106
|
+
#
|
|
107
|
+
# @return [Boolean] True if configured
|
|
108
|
+
def configured?
|
|
109
|
+
!@global_config.nil?
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Core components
|
|
4
|
+
require_relative 'core/loader'
|
|
5
|
+
require_relative 'core/public_api'
|
|
6
|
+
require_relative 'core/error_handler'
|
|
7
|
+
require_relative 'core/migration'
|
|
8
|
+
|
|
9
|
+
module HatiRailsApi
|
|
10
|
+
module Context
|
|
11
|
+
# Core module containing all essential components
|
|
12
|
+
# Provides a clean separation of concerns and proper error handling
|
|
13
|
+
module Core
|
|
14
|
+
# Initialize the core system
|
|
15
|
+
Loader.load_all_components
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Simple string extension for camelize if ActiveSupport is not available
|
|
4
|
+
class String
|
|
5
|
+
def camelize
|
|
6
|
+
return self if empty?
|
|
7
|
+
|
|
8
|
+
# Handle underscored strings - capitalize all words
|
|
9
|
+
split('_').map(&:capitalize).join
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'file_generation'
|
|
4
|
+
|
|
5
|
+
module HatiRailsApi
|
|
6
|
+
module Context
|
|
7
|
+
# Base generator class providing common functionality
|
|
8
|
+
# Follows DRY principle - shared functionality for all generators
|
|
9
|
+
class BaseGenerator
|
|
10
|
+
include FileGeneration
|
|
11
|
+
|
|
12
|
+
TIMESTAMP_PATTERN = '%Y%m%d%H%M%S'
|
|
13
|
+
|
|
14
|
+
attr_reader :config, :generated_files, :timestamp, :force
|
|
15
|
+
|
|
16
|
+
def initialize(config = nil, force: false)
|
|
17
|
+
@config = config
|
|
18
|
+
@generated_files = []
|
|
19
|
+
@timestamp = Time.now.strftime(TIMESTAMP_PATTERN)
|
|
20
|
+
@force = force
|
|
21
|
+
@override_all = nil
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def override_all_state(value = nil)
|
|
27
|
+
return @override_all if value.nil?
|
|
28
|
+
|
|
29
|
+
@override_all = value
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'base_generator'
|
|
4
|
+
require_relative '../shared/layer_factory'
|
|
5
|
+
require_relative 'file_generation'
|
|
6
|
+
require_relative 'model_endpoint_generator'
|
|
7
|
+
require_relative 'layer_component_generator'
|
|
8
|
+
require_relative 'operation_generator'
|
|
9
|
+
|
|
10
|
+
module HatiRailsApi
|
|
11
|
+
module Context
|
|
12
|
+
# Domain generator class for handling domain-specific generation
|
|
13
|
+
# Follows Single Responsibility Principle - handles domain context generation
|
|
14
|
+
class DomainGenerator
|
|
15
|
+
include LayerFactory
|
|
16
|
+
include FileGeneration
|
|
17
|
+
include ModelEndpointGenerator
|
|
18
|
+
include LayerComponentGenerator
|
|
19
|
+
include OperationGenerator
|
|
20
|
+
|
|
21
|
+
DEFAULT_DOMAIN_STATE = { enabled: false }.freeze
|
|
22
|
+
CONTEXT_PATH = 'app/contexts'
|
|
23
|
+
HATI_OPERATION_BASE = 'hati_operation/base'
|
|
24
|
+
DEFAULT_OPTIONS = {}.freeze
|
|
25
|
+
DEFAULT_FILES = [].freeze
|
|
26
|
+
|
|
27
|
+
attr_reader :name, :config, :layers, :options
|
|
28
|
+
|
|
29
|
+
def initialize(
|
|
30
|
+
name:,
|
|
31
|
+
config:,
|
|
32
|
+
options: DEFAULT_OPTIONS,
|
|
33
|
+
generated_files: DEFAULT_FILES,
|
|
34
|
+
force: false,
|
|
35
|
+
override_all: nil
|
|
36
|
+
)
|
|
37
|
+
@name = name
|
|
38
|
+
@config = config
|
|
39
|
+
@options = options
|
|
40
|
+
@generated_files = generated_files
|
|
41
|
+
@force = force
|
|
42
|
+
@override_all = override_all
|
|
43
|
+
|
|
44
|
+
@domain_model = DEFAULT_DOMAIN_STATE.dup
|
|
45
|
+
@domain_endpoint = DEFAULT_DOMAIN_STATE.dup
|
|
46
|
+
|
|
47
|
+
@layers = {}
|
|
48
|
+
@ctx_reference = nil
|
|
49
|
+
|
|
50
|
+
setup_layers
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def operation(&block)
|
|
54
|
+
@layers[:operation] = create_operation_layer(&block)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def layer(name, &block)
|
|
58
|
+
@layers[name.to_sym] = create_standard_layer(name, &block)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def model(enabled_or_options = true, **options)
|
|
62
|
+
@domain_model =
|
|
63
|
+
case enabled_or_options
|
|
64
|
+
when false
|
|
65
|
+
DEFAULT_DOMAIN_STATE
|
|
66
|
+
when Hash
|
|
67
|
+
{ enabled: true, options: enabled_or_options }
|
|
68
|
+
else
|
|
69
|
+
{ enabled: true, options: options }
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def endpoint(enabled_or_components = true, components: [], **options)
|
|
74
|
+
# Handle different argument patterns:
|
|
75
|
+
# endpoint true
|
|
76
|
+
# endpoint enabled: true
|
|
77
|
+
# endpoint [:comp1, :comp2]
|
|
78
|
+
# endpoint enabled: true, components: [:comp1]
|
|
79
|
+
|
|
80
|
+
enabled =
|
|
81
|
+
case enabled_or_components
|
|
82
|
+
when Array
|
|
83
|
+
true
|
|
84
|
+
when Hash
|
|
85
|
+
enabled_or_components.fetch(:enabled, true)
|
|
86
|
+
else
|
|
87
|
+
enabled_or_components
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
components_to_use =
|
|
91
|
+
case enabled_or_components
|
|
92
|
+
when Array
|
|
93
|
+
enabled_or_components
|
|
94
|
+
else
|
|
95
|
+
components
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
@domain_endpoint = create_endpoint_state(enabled, components_to_use, options)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def generate
|
|
102
|
+
generate_domain_model if @domain_model[:enabled]
|
|
103
|
+
generate_domain_endpoint if @domain_endpoint[:enabled]
|
|
104
|
+
generate_layers
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def context_reference=(ctx)
|
|
108
|
+
@ctx_reference = ctx
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
private
|
|
112
|
+
|
|
113
|
+
def generate_layers
|
|
114
|
+
base_path = File.join(@config&.base_path || CONTEXT_PATH, @name.to_s)
|
|
115
|
+
|
|
116
|
+
# Safety check: remove file if contexts exists as file instead of directory
|
|
117
|
+
contexts_root = @config&.base_path || CONTEXT_PATH
|
|
118
|
+
File.delete(contexts_root) if File.exist?(contexts_root) && !File.directory?(contexts_root)
|
|
119
|
+
|
|
120
|
+
FileUtils.mkdir_p(base_path)
|
|
121
|
+
|
|
122
|
+
@layers.each do |layer_name, layer_config|
|
|
123
|
+
generate_layer(
|
|
124
|
+
base_path: base_path,
|
|
125
|
+
layer_name: layer_name,
|
|
126
|
+
layer_config: layer_config
|
|
127
|
+
)
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def setup_layers
|
|
132
|
+
if options[:layers]
|
|
133
|
+
copy_default_layers
|
|
134
|
+
setup_specific_layers
|
|
135
|
+
else
|
|
136
|
+
copy_default_layers
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def setup_specific_layers
|
|
141
|
+
requested_layers = options[:layers]
|
|
142
|
+
requested_components = options[:components]
|
|
143
|
+
|
|
144
|
+
setup_requested_layers(requested_layers, requested_components)
|
|
145
|
+
cleanup_unrequested_layers(requested_layers)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def setup_requested_layers(layers, components)
|
|
149
|
+
layers.each do |layer_name|
|
|
150
|
+
if @layers[layer_name]
|
|
151
|
+
update_existing_layer(layer_name, components)
|
|
152
|
+
else
|
|
153
|
+
create_new_layer(layer_name, components)
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def update_existing_layer(name, components)
|
|
159
|
+
layer = @layers[name]
|
|
160
|
+
layer.component(components) if components
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def create_new_layer(name, components)
|
|
164
|
+
@layers[name] =
|
|
165
|
+
if name == :operation
|
|
166
|
+
create_operation_layer_with_components(components)
|
|
167
|
+
else
|
|
168
|
+
create_standard_layer_with_components(name, components)
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def create_operation_layer_with_components(components)
|
|
173
|
+
OperationLayer.new.tap do |layer|
|
|
174
|
+
layer.base(HATI_OPERATION_BASE)
|
|
175
|
+
layer.component(components) if components
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def create_standard_layer_with_components(name, components)
|
|
180
|
+
StandardLayer.new(name).tap do |layer|
|
|
181
|
+
layer.base("application_#{name}")
|
|
182
|
+
layer.component(components) if components
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def cleanup_unrequested_layers(requested_layers)
|
|
187
|
+
@layers.select! { |name, _| requested_layers.include?(name) }
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def copy_default_layers
|
|
191
|
+
return unless @config&.domain_config&.layers
|
|
192
|
+
|
|
193
|
+
@config.domain_config.layers.each do |name, layer|
|
|
194
|
+
@layers[name] = layer.dup
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def method_missing(method_name, ...)
|
|
199
|
+
result = create_dynamic_layer(method_name, ...)
|
|
200
|
+
return super unless result
|
|
201
|
+
|
|
202
|
+
@layers[method_name.to_sym] = result
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def respond_to_missing?(_method_name, _include_private = false)
|
|
206
|
+
true
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def create_endpoint_state(enabled, components, options)
|
|
210
|
+
case enabled
|
|
211
|
+
when false then DEFAULT_DOMAIN_STATE
|
|
212
|
+
when true then { enabled: true, options: options, explicit_components: components.empty? ? nil : components }
|
|
213
|
+
when Array then { enabled: true, options: options, explicit_components: enabled }
|
|
214
|
+
when Hash then { enabled: true, options: enabled, explicit_components: nil }
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'fileutils'
|
|
4
|
+
|
|
5
|
+
module HatiRailsApi
|
|
6
|
+
module Context
|
|
7
|
+
# Shared concern for file generation operations
|
|
8
|
+
# Eliminates duplicate file handling code across generators
|
|
9
|
+
module FileGeneration
|
|
10
|
+
ALL_RGX = [/^a/, /all/, /^a=all$/].freeze
|
|
11
|
+
SKIP_RGX = [/^s/, /skip/, /^s=skip$/].freeze
|
|
12
|
+
YES_RGX = [/^y/, /yes$/].freeze
|
|
13
|
+
NO_RGX = [/^n/, /no$/].freeze
|
|
14
|
+
|
|
15
|
+
def generate_file?(file_path, content)
|
|
16
|
+
FileUtils.mkdir_p(File.dirname(file_path))
|
|
17
|
+
|
|
18
|
+
if create_file?(file_path)
|
|
19
|
+
File.write(file_path, content)
|
|
20
|
+
@generated_files << file_path
|
|
21
|
+
puts "Created: #{file_path}"
|
|
22
|
+
true
|
|
23
|
+
else
|
|
24
|
+
puts "Skipped: #{file_path} (already exists or user declined)"
|
|
25
|
+
false
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def create_file?(file_path)
|
|
30
|
+
return true unless File.exist?(file_path)
|
|
31
|
+
return true if @force
|
|
32
|
+
return override_all? unless override_all?.nil?
|
|
33
|
+
|
|
34
|
+
prompt_for_override(file_path)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def override_all?
|
|
38
|
+
return @override_all_ref.call(:get) if shared_override_state?
|
|
39
|
+
|
|
40
|
+
@override_all
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def shared_override_state?
|
|
44
|
+
respond_to?(:use_shared_override_state?) && use_shared_override_state?
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def prompt_for_override(file_path)
|
|
48
|
+
print "File '#{file_path}' already exists. Override? (y/N/a=all/s=skip all): "
|
|
49
|
+
handle_override_response($stdin.gets&.chomp&.downcase || 'n')
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def handle_override_response(response)
|
|
53
|
+
case response
|
|
54
|
+
when *ALL_RGX then self.override_all_state = true
|
|
55
|
+
when *SKIP_RGX then self.override_all_state = false
|
|
56
|
+
when *YES_RGX then true
|
|
57
|
+
else false
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def override_all_state=(value)
|
|
62
|
+
use_shared = respond_to?(:use_shared_override_state?) && use_shared_override_state?
|
|
63
|
+
|
|
64
|
+
use_shared ? @override_all_ref&.call(value) : @override_all = value
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def use_shared_override_state?
|
|
68
|
+
@override_all_ref&.respond_to?(:call)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|