Class: FHIR::Boot::Generator
- Inherits:
-
Object
- Object
- FHIR::Boot::Generator
- Defined in:
- lib/fhir_models/bootstrap/generator.rb
Instance Attribute Summary collapse
-
#defn ⇒ Object
Returns the value of attribute defn.
-
#lib ⇒ Object
Returns the value of attribute lib.
-
#missing_expansions ⇒ Object
Returns the value of attribute missing_expansions.
-
#missing_required_expansion ⇒ Object
Returns the value of attribute missing_required_expansion.
-
#templates ⇒ Object
templates keeps track of all the templates in context within a given StructureDefinition.
Instance Method Summary collapse
- #cap_first(string) ⇒ Object
- #generate_class(hierarchy, structure_def, top_level = false) ⇒ Object
- #generate_class_files(folder = @lib, structure_defs = []) ⇒ Object
- #generate_metadata ⇒ Object
- #generate_resources ⇒ Object
- #generate_types ⇒ Object
-
#initialize(auto_setup: true) ⇒ Generator
constructor
A new instance of Generator.
- #setup ⇒ Object
Constructor Details
#initialize(auto_setup: true) ⇒ Generator
Returns a new instance of Generator.
10 11 12 13 14 15 16 |
# File 'lib/fhir_models/bootstrap/generator.rb', line 10 def initialize(auto_setup: true) # load the valueset expansions @defn = FHIR::Definitions # templates is an array @templates = [] setup if auto_setup end |
Instance Attribute Details
#defn ⇒ Object
Returns the value of attribute defn.
5 6 7 |
# File 'lib/fhir_models/bootstrap/generator.rb', line 5 def defn @defn end |
#lib ⇒ Object
Returns the value of attribute lib.
4 5 6 |
# File 'lib/fhir_models/bootstrap/generator.rb', line 4 def lib @lib end |
#missing_expansions ⇒ Object
Returns the value of attribute missing_expansions.
8 9 10 |
# File 'lib/fhir_models/bootstrap/generator.rb', line 8 def missing_expansions @missing_expansions end |
#missing_required_expansion ⇒ Object
Returns the value of attribute missing_required_expansion.
8 9 10 |
# File 'lib/fhir_models/bootstrap/generator.rb', line 8 def missing_required_expansion @missing_required_expansion end |
#templates ⇒ Object
templates keeps track of all the templates in context within a given StructureDefinition
7 8 9 |
# File 'lib/fhir_models/bootstrap/generator.rb', line 7 def templates @templates end |
Instance Method Details
#cap_first(string) ⇒ Object
94 95 96 97 98 |
# File 'lib/fhir_models/bootstrap/generator.rb', line 94 def cap_first(string) t = String.new(string) t[0] = t[0].upcase t end |
#generate_class(hierarchy, structure_def, top_level = false) ⇒ Object
100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 |
# File 'lib/fhir_models/bootstrap/generator.rb', line 100 def generate_class(hierarchy, structure_def, top_level = false) type_name = structure_def['id'] constrained_type = structure_def['type'] path_type = type_name path_type = constrained_type if constrained_type template = FHIR::Boot::Template.new([type_name], top_level) template.hierarchy = hierarchy template.kind = structure_def['kind'] return template if structure_def['snapshot'].nil? || structure_def['snapshot']['element'].nil? multiple_data_types = {} # examine the snapshot.elements... move the Element and BackboneElements, # and their children, into separate StructureDefiniton hashes and process as # child templates. child_templates = [] structure_def['snapshot']['element'].each do |element| # skip the first element next if element['path'] == path_type next unless element['type'] unique_types = element['type'].map { |t| t['code'] }.uniq if unique_types.include?('Element') || unique_types.include?('BackboneElement') child_templates << element['path'] end end # now build the child templates... child_templates.each do |child_name| child_fixed_name = cap_first(child_name.gsub("#{type_name}.", '')) next if child_fixed_name.include?('.') child_def = { 'id' => child_fixed_name, 'snapshot' => { 'element' => [] } } # Copy the element definitions for the child structure structure_def['snapshot']['element'].each do |element| child_def['snapshot']['element'] << element.clone if element['path'].start_with?("#{child_name}.") end # Remove the child elements child_paths = child_def['snapshot']['element'].map { |e| e['path'] } # child_paths = child_paths.drop(1) structure_def['snapshot']['element'].keep_if do |element| !child_paths.include?(element['path']) end # Rename the child paths child_def['snapshot']['element'].each do |element| element['path'] = element['path'].gsub(child_name, child_fixed_name) end # add the child child_hierarchy = hierarchy + [child_fixed_name] child_klass = generate_class(child_hierarchy, child_def) template.templates << child_klass @templates << child_klass end # Process the remaining attributes (none of which are Elements or BackboneElements) structure_def['snapshot']['element'].each do |element| # skip the first element next if element['path'] == path_type field_base_name = element['path'].gsub("#{path_type}.", '') # If the element has a type, treat it as a datatype or resource # If not, treat it as a reference to an already declared internal class if !element['type'].nil? # profiles contains a list of profiles if the datatype is Reference or Extension profiles = [] element['type'].select { |t| t['code'] == 'Reference' || t['code'] == 'Extension' }.each do |data_type| profiles << data_type['targetProfile'] end profiles.reject!(&:nil?) profiles.flatten! # Calculate fields that have multiple data types if element['type'].length > 1 fieldname = field_base_name.gsub('[x]', '') unique_types = element['type'].map { |t| t['code'] }.uniq multiple_data_types[fieldname] = unique_types if unique_types.length > 1 end # generate a field for each valid datatype... this is for things like Resource.attribute[x] element['type'].map { |t| t['code'] }.uniq.each do |data_type| data_type = 'string' unless data_type capitalized = cap_first(data_type) fieldname = field_base_name.gsub('[x]', capitalized) field = FHIR::Field.new(fieldname) field.path = element['path'].gsub(path_type, type_name) field.type = data_type field.type = 'Extension' if field.path.end_with?('extension') field.type_profiles = profiles if data_type == 'Reference' || data_type == 'Extension' field.min = element['min'] field.max = element['max'] field.max = field.max.to_i field.max = '*' if element['max'] == '*' if %w[code Coding CodeableConcept].include?(data_type) && element['binding'] field.binding = element['binding'] field.binding['uri'] = field.binding['valueSet'] field.binding.delete('valueSet') field.binding.delete('description') field.binding.delete('extension') # set the actual code list binding_uri = field.binding['uri'] binding_uri = binding_uri[0..-7] if binding_uri&.end_with?('|4.0.0') codes = @defn.get_codes(binding_uri) field.valid_codes = codes unless codes.nil? if field.valid_codes.empty? && field.binding['uri'] && !binding_uri.end_with?('bcp47') && !binding_uri.end_with?('bcp13.txt') FHIR.logger.warn " MISSING EXPANSION -- #{field.path} #{field.min}..#{field.max}: #{binding_uri} (#{field.binding['strength']})" @missing_expansions = true @missing_required_expansion = (field.binding['strength'] == 'required') unless @missing_required_expansion end elsif %w[Element BackboneElement].include?(data_type) # This is a nested structure or class field.type = "#{hierarchy.join('::')}::#{cap_first(field.name)}" end template.fields << field end else # If there is no data type, treat the type as a reference to an already declared internal class field = FHIR::Field.new(field_base_name) field.path = element['path'].gsub(path_type, type_name) field.type = element['contentReference'] field.type = field.type[1..-1] if field.type[0] == '#' if hierarchy.last == field.type # reference to self field.type = hierarchy.join('::').to_s else # reference to contained template klass = @templates.select { |x| x.hierarchy.last == field.type }.first field.type = if !klass.nil? # the template/child class was declared somewhere else in this class hierarchy klass.hierarchy.join('::') else # the template/child is a direct ancester (it isn't in @templates yet because it is being defined now) field.type.split('.').map { |x| cap_first(x) }.join('::') end end field.min = element['min'] field.max = element['max'] field.max = field.max.to_i field.max = '*' if element['max'] == '*' template.fields << field end end template.constants['MULTIPLE_TYPES'] = multiple_data_types unless multiple_data_types.empty? template end |
#generate_class_files(folder = @lib, structure_defs = []) ⇒ Object
80 81 82 83 84 85 86 87 88 89 90 91 92 |
# File 'lib/fhir_models/bootstrap/generator.rb', line 80 def generate_class_files(folder = @lib, structure_defs = []) structure_defs.each do |structure_def| @templates.clear type_name = structure_def['id'] template = generate_class([type_name], structure_def, true) params = @defn.search_parameters(type_name) template.constants['SEARCH_PARAMS'] = params unless params.nil? filename = File.join(folder, "#{type_name}.rb") file = File.open(filename, 'w:UTF-8') file.write(template.to_s) file.close end end |
#generate_metadata ⇒ Object
30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
# File 'lib/fhir_models/bootstrap/generator.rb', line 30 def template = FHIR::Boot::Template.new([], true) primitives = @defn.primitive_types hash = {} primitives.each do |p| field = FHIR::Field.new field.name = nil # try to find the element that describes the value type = p['snapshot']['element'].select { |e| e['path'].end_with?('.value') }.first['type'].first # try to find the JSON data type ext = type['_code']['extension'].find { |e| e['url'] == 'http://hl7.org/fhir/StructureDefinition/structuredefinition-json-type' } field.type = ext ? ext['valueString'] : 'string' # try to find a regex if type['extension'] ext = type['extension'].find { |e| e['url'] == 'http://hl7.org/fhir/StructureDefinition/regex' } field.regex = ext['valueString'] if ext end hash[p['id']] = field.serialize end template.constants['PRIMITIVES'] = hash template.constants['TYPES'] = @defn.complex_types.map { |t| t['id'] } # resources template.constants['RESOURCES'] = @defn.resource_definitions.map { |r| r['id'] } filename = File.join(@lib, 'fhir', 'metadata.rb') file = File.open(filename, 'w:UTF-8') file.write(template.to_s) file.close end |
#generate_resources ⇒ Object
75 76 77 78 |
# File 'lib/fhir_models/bootstrap/generator.rb', line 75 def generate_resources folder = File.join @lib, 'fhir', 'resources' generate_class_files(folder, @defn.resource_definitions) end |
#generate_types ⇒ Object
67 68 69 70 71 72 73 |
# File 'lib/fhir_models/bootstrap/generator.rb', line 67 def generate_types folder = File.join @lib, 'fhir', 'types' # complex data types start with an uppercase letter # and we'll filter out profiles on types (for example, Age is a profile on Quantity) complex_types = @defn.complex_types generate_class_files(folder, complex_types) end |
#setup ⇒ Object
18 19 20 21 22 23 24 25 26 27 28 |
# File 'lib/fhir_models/bootstrap/generator.rb', line 18 def setup # make folders for generated content if they do not exist @lib = File. '..', File.dirname(File.absolute_path(__FILE__)) Dir.mkdir(File.join(@lib, 'fhir')) unless Dir.exist?(File.join(@lib, 'fhir')) Dir.mkdir(File.join(@lib, 'fhir', 'types')) unless Dir.exist?(File.join(@lib, 'fhir', 'types')) Dir.mkdir(File.join(@lib, 'fhir', 'resources')) unless Dir.exist?(File.join(@lib, 'fhir', 'resources')) # delete previously generated folder contents Dir.glob(File.join(@lib, 'fhir', '*')).each { |f| File.delete(f) unless File.directory?(f) } Dir.glob(File.join(@lib, 'fhir', '**', '*')).each { |f| File.delete(f) unless File.directory?(f) } end |