Class: Autotype::DiscoveryProfile
- Inherits:
-
Object
- Object
- Autotype::DiscoveryProfile
- Defined in:
- lib/autotype/discovery_profile.rb
Overview
Infers framework wiring, entity layout, and type hints from Ruby source and the filesystem. Optional autotype.yml supplies skip globs and overrides when heuristics are wrong — nothing else is required.
Constant Summary collapse
- PORT_CALLS =
%i[input output].freeze
- OPTION_CALLS =
%i[option].freeze
- FIELD_CALLS =
%i[field].freeze
- HANDLER_METHOD_CANDIDATES =
%i[process handle call perform run execute].freeze
- DISPATCH_PARAM_CANDIDATES =
%i[from source port input_name].freeze
- EMIT_MESSAGES =
%i[emit publish send emit_entity].freeze
- EMIT_PORT_KEYWORDS =
%i[to port output].freeze
Instance Attribute Summary collapse
-
#root ⇒ Object
readonly
Returns the value of attribute root.
Class Method Summary collapse
Instance Method Summary collapse
- #config_hash ⇒ Object
- #entity_define_receiver?(receiver_slice) ⇒ Boolean
- #finalize!(collectors) ⇒ Object
- #framework_self_fallbacks ⇒ Object
-
#initialize(root: Dir.pwd, overrides: {}, config_path: nil) ⇒ DiscoveryProfile
constructor
A new instance of DiscoveryProfile.
- #locate_type_file(type_name) ⇒ Object
- #member_type_hints ⇒ Object
- #metadata_from(collector) ⇒ Object
- #option_type_hints ⇒ Object
- #output_emit ⇒ Object
- #path ⇒ Object
- #pipeline_class?(collector, owner) ⇒ Boolean
- #port_wiring ⇒ Object
- #prepare_search!(paths) ⇒ Object
- #side_effect_methods ⇒ Object
- #skipped_files ⇒ Object
- #structured_type_prefixes ⇒ Object
- #visit_call_node(collector, node) ⇒ Object
Constructor Details
#initialize(root: Dir.pwd, overrides: {}, config_path: nil) ⇒ DiscoveryProfile
Returns a new instance of DiscoveryProfile.
37 38 39 40 41 42 43 44 45 |
# File 'lib/autotype/discovery_profile.rb', line 37 def initialize(root: Dir.pwd, overrides: {}, config_path: nil) @root = File.(root) @overrides = stringify_keys(overrides) @config_path = config_path @search_roots = Set.new([@root]) @port_dsl_owners = Set.new @referenced_constants = Set.new reset_inference! end |
Instance Attribute Details
#root ⇒ Object (readonly)
Returns the value of attribute root.
47 48 49 |
# File 'lib/autotype/discovery_profile.rb', line 47 def root @root end |
Class Method Details
.find(start_dir = Dir.pwd) ⇒ Object
23 24 25 26 27 28 29 30 31 32 33 34 35 |
# File 'lib/autotype/discovery_profile.rb', line 23 def self.find(start_dir = Dir.pwd) dir = File.(start_dir) loop do candidate = File.join(dir, "autotype.yml") return candidate if File.file?(candidate) parent = File.dirname(dir) break if parent == dir dir = parent end nil end |
.load_overrides(path, root: File.dirname(path)) ⇒ Object
18 19 20 21 |
# File 'lib/autotype/discovery_profile.rb', line 18 def self.load_overrides(path, root: File.dirname(path)) raw = YAML.safe_load_file(path, aliases: true) || {} new(root: root, overrides: raw, config_path: path) end |
Instance Method Details
#config_hash ⇒ Object
103 104 105 |
# File 'lib/autotype/discovery_profile.rb', line 103 def config_hash @config_hash end |
#entity_define_receiver?(receiver_slice) ⇒ Boolean
120 121 122 123 124 125 126 |
# File 'lib/autotype/discovery_profile.rb', line 120 def entity_define_receiver?(receiver_slice) return false unless receiver_slice receiver_slice == "Entity" || receiver_slice == "Data" || receiver_slice.end_with?("::Entity") end |
#finalize!(collectors) ⇒ Object
61 62 63 64 65 66 67 68 69 70 71 72 73 |
# File 'lib/autotype/discovery_profile.rb', line 61 def finalize!(collectors) methods = collectors.flat_map(&:methods) ports = merge_ports(collectors) @port_wiring ||= infer_port_wiring(methods, ports) @output_emit ||= infer_output_emit(methods, ports) @config_hash ||= infer_config_hash(methods, collectors) @side_effect_methods ||= infer_side_effect_methods(methods, ports) @structured_type_prefixes = infer_structured_prefixes(collectors, ports) if @structured_type_prefixes.empty? @framework_self_fallbacks = infer_self_fallbacks(methods, ports) if @framework_self_fallbacks.empty? collectors.each { collect_referenced_constants(_1) } @finalized = true end |
#framework_self_fallbacks ⇒ Object
107 108 109 |
# File 'lib/autotype/discovery_profile.rb', line 107 def framework_self_fallbacks @framework_self_fallbacks || {} end |
#locate_type_file(type_name) ⇒ Object
159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 |
# File 'lib/autotype/discovery_profile.rb', line 159 def locate_type_file(type_name) return @type_path_cache[type_name] if @type_path_cache.key?(type_name) explicit = explicit_locator(type_name) return cache_type_path(type_name, explicit) if explicit basename = type_name.split("::").last snake = underscore(basename) candidates = @search_roots.flat_map do |search_root| Dir.glob(File.join(search_root, "**", "#{snake}.rb")) end.uniq path = candidates.find { defines_type?(_1, type_name, basename) } cache_type_path(type_name, path) end |
#member_type_hints ⇒ Object
79 80 81 |
# File 'lib/autotype/discovery_profile.rb', line 79 def member_type_hints @member_type_hints ||= parse_type_map(@overrides.dig("types", "members")) end |
#metadata_from(collector) ⇒ Object
144 145 146 147 148 149 150 151 152 153 154 155 156 157 |
# File 'lib/autotype/discovery_profile.rb', line 144 def (collector) InferenceMetadata.from_collector( collector, member_type_hints: member_type_hints, option_type_hints: option_type_hints, side_effect_methods: side_effect_methods, structured_type_prefixes: structured_type_prefixes, port_wiring: port_wiring, output_emit: output_emit, config_hash: config_hash, framework_self_fallbacks: framework_self_fallbacks, type_locators: [method(:locate_type_file)] ) end |
#option_type_hints ⇒ Object
83 84 85 |
# File 'lib/autotype/discovery_profile.rb', line 83 def option_type_hints @option_type_hints ||= parse_type_map(@overrides.dig("types", "options")) end |
#output_emit ⇒ Object
99 100 101 |
# File 'lib/autotype/discovery_profile.rb', line 99 def output_emit @output_emit end |
#path ⇒ Object
49 50 51 |
# File 'lib/autotype/discovery_profile.rb', line 49 def path @config_path end |
#pipeline_class?(collector, owner) ⇒ Boolean
111 112 113 114 115 116 117 118 |
# File 'lib/autotype/discovery_profile.rb', line 111 def pipeline_class?(collector, owner) return false if collector.singleton_context? || collector.namespace_empty? return true if @port_dsl_owners.include?(owner) superclass = collector.superclass_for(owner) superclass == "Actor" || superclass&.end_with?("::Actor") end |
#port_wiring ⇒ Object
95 96 97 |
# File 'lib/autotype/discovery_profile.rb', line 95 def port_wiring @port_wiring end |
#prepare_search!(paths) ⇒ Object
53 54 55 56 57 58 59 |
# File 'lib/autotype/discovery_profile.rb', line 53 def prepare_search!(paths) paths.each do |file| next unless File.file?(file) @search_roots << find_project_root(File.dirname(File.(file))) end end |
#side_effect_methods ⇒ Object
87 88 89 |
# File 'lib/autotype/discovery_profile.rb', line 87 def side_effect_methods @side_effect_methods || Set.new end |
#skipped_files ⇒ Object
75 76 77 |
# File 'lib/autotype/discovery_profile.rb', line 75 def skipped_files Array(@overrides.fetch("skip", [])).to_set end |
#structured_type_prefixes ⇒ Object
91 92 93 |
# File 'lib/autotype/discovery_profile.rb', line 91 def structured_type_prefixes @structured_type_prefixes || [] end |
#visit_call_node(collector, node) ⇒ Object
128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 |
# File 'lib/autotype/discovery_profile.rb', line 128 def visit_call_node(collector, node) return unless node.receiver.nil? return if collector.singleton_context? owner = collector.current_owner if port_call?(node.name) record_port(collector, node) @port_dsl_owners << owner elsif option_call?(node.name) record_option(collector, node) @port_dsl_owners << owner elsif field_call?(node.name) && collector.structured_field_context? record_field(collector, node) end end |