tla-sbuilder 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (210) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +328 -0
  3. data/VERSION +1 -0
  4. data/bin/sbuilder.rb +5 -0
  5. data/lib/cli/cli-customer.rb +420 -0
  6. data/lib/cli/cli-example.rb +92 -0
  7. data/lib/cli/cli-pet.rb +767 -0
  8. data/lib/cli/cli-text.rb +226 -0
  9. data/lib/cli/cli.rb +298 -0
  10. data/lib/sbuilder.rb +52 -0
  11. data/lib/sbuilder/constants.rb +72 -0
  12. data/lib/sbuilder/controller.rb +798 -0
  13. data/lib/sbuilder/default-sbuilder.yaml +372 -0
  14. data/lib/sbuilder/domain.rb +124 -0
  15. data/lib/sbuilder/domain_cardinality.rb +37 -0
  16. data/lib/sbuilder/domain_value.rb +81 -0
  17. data/lib/sbuilder/exception.rb +27 -0
  18. data/lib/sbuilder/extension_loader.rb +721 -0
  19. data/lib/sbuilder/factory.rb +234 -0
  20. data/lib/sbuilder/model.rb +356 -0
  21. data/lib/sbuilder/mustache/template.rb +125 -0
  22. data/lib/sbuilder/mustache/template_reader.rb +206 -0
  23. data/lib/sbuilder/mustache/template_reader_context.rb +371 -0
  24. data/lib/sbuilder/param_set.rb +132 -0
  25. data/lib/sbuilder/param_set_db.rb +20 -0
  26. data/lib/sbuilder/param_set_def.rb +57 -0
  27. data/lib/sbuilder/param_set_if.rb +68 -0
  28. data/lib/sbuilder/param_set_loader.rb +77 -0
  29. data/lib/sbuilder/param_set_loader_swagger.rb +424 -0
  30. data/lib/sbuilder/param_set_step.rb +62 -0
  31. data/lib/sbuilder/param_sets.rb +54 -0
  32. data/lib/sbuilder/parameter.rb +97 -0
  33. data/lib/sbuilder/parameter_container.rb +72 -0
  34. data/lib/sbuilder/parameter_dom.rb +70 -0
  35. data/lib/sbuilder/parameter_ref.rb +71 -0
  36. data/lib/sbuilder/resolver.rb +78 -0
  37. data/lib/sbuilder/resolver_loader.rb +79 -0
  38. data/lib/sbuilder/resolver_loader_yaml.rb +103 -0
  39. data/lib/sbuilder/resolver_rule.rb +36 -0
  40. data/lib/sbuilder/resolver_rule_match.rb +55 -0
  41. data/lib/sbuilder/resolver_rule_ref.rb +37 -0
  42. data/lib/utils/hash_inject.rb +12 -0
  43. data/lib/utils/logger.rb +80 -0
  44. data/lib/utils/netio.rb +58 -0
  45. data/lib/utils/string_inject.rb +10 -0
  46. data/lib/utils/version.rb +13 -0
  47. data/mustache/cfg/const_def.mustache +8 -0
  48. data/mustache/cfg/const_run.mustache +3 -0
  49. data/mustache/cfg/invariant-infrastructure-service.mustache +4 -0
  50. data/mustache/cfg/macro_run.mustache +6 -0
  51. data/mustache/cfg/module_footer.mustache +0 -0
  52. data/mustache/cfg/module_header.mustache +7 -0
  53. data/mustache/data-model-dump.mustache +19 -0
  54. data/mustache/data-model-footer.mustache +5 -0
  55. data/mustache/data-model-header.mustache +16 -0
  56. data/mustache/definition_types.mustache +40 -0
  57. data/mustache/domains.mustache +20 -0
  58. data/mustache/domains_assign.mustache +22 -0
  59. data/mustache/domains_run.mustache +21 -0
  60. data/mustache/extend/extend_assumptions.mustache +7 -0
  61. data/mustache/extend/extend_const.mustache +5 -0
  62. data/mustache/extend/extend_implementation.mustache +9 -0
  63. data/mustache/extend/extend_invariant.mustache +7 -0
  64. data/mustache/extend/extend_invariant_cfg.mustache +7 -0
  65. data/mustache/extend/extend_macros.mustache +19 -0
  66. data/mustache/extend/extend_operations.mustache +9 -0
  67. data/mustache/extend/extend_state.mustache +9 -0
  68. data/mustache/infrastructure-service-init.mustache +36 -0
  69. data/mustache/infrastructure-service-variables.mustache +10 -0
  70. data/mustache/interface_processes.mustache +38 -0
  71. data/mustache/interface_stubs_dummy.mustache +13 -0
  72. data/mustache/interface_types.mustache +52 -0
  73. data/mustache/markdown-header.mustache +24 -0
  74. data/mustache/markdown-toc.mustache +13 -0
  75. data/mustache/name_definition_type.mustache +5 -0
  76. data/mustache/name_domain.mustache +5 -0
  77. data/mustache/name_domain_value.mustache +5 -0
  78. data/mustache/name_domain_value_prefix.mustache +5 -0
  79. data/mustache/name_interface_response_type.mustache +6 -0
  80. data/mustache/name_interface_type.mustache +6 -0
  81. data/mustache/name_parameter_definition.mustache +5 -0
  82. data/mustache/name_parameter_type.mustache +6 -0
  83. data/mustache/name_process.mustache +6 -0
  84. data/mustache/name_type_invariant.mustache +5 -0
  85. data/mustache/name_variable.mustache +6 -0
  86. data/mustache/operator-infrastructure-service.mustache +13 -0
  87. data/mustache/possibility/module_extends.mustache +1 -0
  88. data/mustache/possibility/module_footer.mustache +1 -0
  89. data/mustache/possibility/module_header.mustache +8 -0
  90. data/mustache/possibility/possibility_definition.mustache +12 -0
  91. data/mustache/possibility/possibility_directive.mustache +1 -0
  92. data/mustache/possibility/possility_setup.mustache +28 -0
  93. data/mustache/setup/module_footer.mustache +1 -0
  94. data/mustache/setup/module_header.mustache +9 -0
  95. data/mustache/setup/operator_run.mustache +7 -0
  96. data/mustache/setup/operator_tick.mustache +2 -0
  97. data/mustache/setup/steps_run.mustache +22 -0
  98. data/mustache/setup/steps_run_bind_rule.mustache +51 -0
  99. data/mustache/setup/steps_run_bind_set.mustache +37 -0
  100. data/mustache/setup/steps_run_parameterBind.mustache +80 -0
  101. data/mustache/setup/steps_run_parameterExact.mustache +79 -0
  102. data/mustache/state_type_invariant-infrastructure-service.mustache +49 -0
  103. data/mustache/state_type_invariant.mustache +17 -0
  104. data/mustache/state_type_invariant_cfg.mustache +18 -0
  105. data/mustache/state_variables.mustache +20 -0
  106. data/mustache/tla/const_def.mustache +5 -0
  107. data/mustache/tla/const_run.mustache +3 -0
  108. data/mustache/tla/macro-infrastructure-service.mustache +14 -0
  109. data/mustache/tla/macro_run.mustache +40 -0
  110. data/mustache/tla/module_footer.mustache +2 -0
  111. data/mustache/tla/module_header.mustache +9 -0
  112. data/mustache/tla/operator_run.mustache +8 -0
  113. data/mustache/tla/operators-infrastructure-service.mustache +12 -0
  114. data/mustache/tla/plc_define_footer.mustache +1 -0
  115. data/mustache/tla/plc_define_header.mustache +1 -0
  116. data/mustache/tla/plc_define_run.mustache +59 -0
  117. data/mustache/tla/plc_footer.mustache +2 -0
  118. data/mustache/tla/plc_header.mustache +2 -0
  119. data/mustache/tla/plc_run_state.mustache +12 -0
  120. data/mustache/tla/plc_tail.mustache +8 -0
  121. data/mustache/tla/plc_translation.mustache +2 -0
  122. data/resources/schema/json_schema/draft-04.json +150 -0
  123. data/resources/schema/swagger/2.0/schema.json +1591 -0
  124. data/src-extend/README +2 -0
  125. data/src-extend/extend/extend_assumptions.mustache +7 -0
  126. data/src-extend/extend/extend_const.mustache +5 -0
  127. data/src-extend/extend/extend_implementation.mustache +9 -0
  128. data/src-extend/extend/extend_invariant.mustache +11 -0
  129. data/src-extend/extend/extend_invariant_cfg.mustache +7 -0
  130. data/src-extend/extend/extend_macros.mustache +19 -0
  131. data/src-extend/extend/extend_operations.mustache +9 -0
  132. data/src-extend/extend/extend_state.mustache +9 -0
  133. data/src-extend/extend_app/assumption +20 -0
  134. data/src-extend/extend_app/correctness +19 -0
  135. data/src-extend/extend_app/correctness.cfg +9 -0
  136. data/src-extend/extend_app/infrastructure +25 -0
  137. data/src-extend/extend_app/interface +11 -0
  138. data/src-extend/extend_app/operator +18 -0
  139. data/src-extend/extend_app/possibility +16 -0
  140. data/src-extend/extend_app/service +33 -0
  141. data/src-extend/extend_app/state +16 -0
  142. data/src-extend/extend_app/transaction +22 -0
  143. data/src/pet/assumption +29 -0
  144. data/src/pet/assumption_address_domains.tla +12 -0
  145. data/src/pet/assumption_domains.tla +16 -0
  146. data/src/pet/assumption_generic.tla +8 -0
  147. data/src/pet/assumption_id_domains.tla +2 -0
  148. data/src/pet/assumption_owner_domains.tla +14 -0
  149. data/src/pet/assumption_pet_domains.tla +16 -0
  150. data/src/pet/assumption_tag_domains.tla +13 -0
  151. data/src/pet/correctness +24 -0
  152. data/src/pet/correctness.cfg +9 -0
  153. data/src/pet/correctness_coherent_owner_address.tla +6 -0
  154. data/src/pet/correctness_pet_name.tla +4 -0
  155. data/src/pet/correctness_ref_tag.tla +13 -0
  156. data/src/pet/correctness_type_invariants.tla +12 -0
  157. data/src/pet/correctness_unique_pet.tla +3 -0
  158. data/src/pet/correctness_unique_tag.tla +3 -0
  159. data/src/pet/docs/Petstore.md +117 -0
  160. data/src/pet/extend/extend_assumptions.mustache +7 -0
  161. data/src/pet/extend/extend_implementation.mustache +9 -0
  162. data/src/pet/extend/extend_invariant.mustache +11 -0
  163. data/src/pet/extend/extend_invariant_cfg.mustache +7 -0
  164. data/src/pet/extend/extend_macros.mustache +19 -0
  165. data/src/pet/extend/extend_operations.mustache +9 -0
  166. data/src/pet/extend/extend_state.mustache +9 -0
  167. data/src/pet/infrastructure +25 -0
  168. data/src/pet/infrastructure_id_get.tla +24 -0
  169. data/src/pet/interface +12 -0
  170. data/src/pet/interface_delete_pet.tla +5 -0
  171. data/src/pet/interface_get_pet.tla +4 -0
  172. data/src/pet/interface_post_pet.tla +5 -0
  173. data/src/pet/interface_post_tag.tla +5 -0
  174. data/src/pet/interface_put_tag.tla +3 -0
  175. data/src/pet/operator +30 -0
  176. data/src/pet/operator_find_tag_by_owner_name.tla +1 -0
  177. data/src/pet/operator_get_pet.tla +4 -0
  178. data/src/pet/operator_get_pet_by_tag.tla +4 -0
  179. data/src/pet/operator_get_tag.tla +10 -0
  180. data/src/pet/operator_new_owner.tla +3 -0
  181. data/src/pet/operator_new_pet.tla +13 -0
  182. data/src/pet/operator_new_tag.tla +3 -0
  183. data/src/pet/operator_next_pet_id.tla +3 -0
  184. data/src/pet/operator_responses.tla +8 -0
  185. data/src/pet/operator_tag_exists.tla +2 -0
  186. data/src/pet/operator_tag_owner_validated.tla +2 -0
  187. data/src/pet/operator_tag_referenced.tla +4 -0
  188. data/src/pet/operator_valid_owner.tla +17 -0
  189. data/src/pet/operator_valid_pet.tla +6 -0
  190. data/src/pet/operator_valid_tag.tla +5 -0
  191. data/src/pet/possibility +18 -0
  192. data/src/pet/possibility_at_least_two_tags.tla +12 -0
  193. data/src/pet/possibility_invalid_tag_address.tla +8 -0
  194. data/src/pet/service +35 -0
  195. data/src/pet/service_pet_delete.tla +11 -0
  196. data/src/pet/service_pet_get.tla +27 -0
  197. data/src/pet/service_pet_post.tla +78 -0
  198. data/src/pet/service_tag_post.tla +53 -0
  199. data/src/pet/service_tag_put.tla +82 -0
  200. data/src/pet/state +16 -0
  201. data/src/pet/state_infra.tla +6 -0
  202. data/src/pet/state_pet.tla +5 -0
  203. data/src/pet/state_tag_id.tla +2 -0
  204. data/src/pet/transaction +23 -0
  205. data/src/pet/transaction_delete_pet.tla +13 -0
  206. data/src/pet/transaction_enter_pet.tla +13 -0
  207. data/src/pet/transaction_enter_tag.tla +56 -0
  208. data/src/pet/transaction_error.tla +23 -0
  209. data/tla-sbuilder.gemspec +43 -0
  210. metadata +353 -0
@@ -0,0 +1,52 @@
1
+
2
+ require_relative "utils/hash_inject"
3
+ require_relative "utils/string_inject"
4
+
5
+ require_relative "utils/version"
6
+ require_relative "utils/logger"
7
+ require_relative "utils/netio"
8
+
9
+ require_relative "sbuilder/constants.rb"
10
+
11
+ require_relative "sbuilder/exception.rb"
12
+
13
+ require_relative "sbuilder/domain.rb"
14
+ require_relative "sbuilder/domain_value.rb"
15
+ require_relative "sbuilder/domain_cardinality.rb"
16
+
17
+ require_relative "sbuilder/parameter.rb"
18
+ require_relative "sbuilder/parameter_container.rb"
19
+ require_relative "sbuilder/parameter_dom.rb"
20
+ require_relative "sbuilder/parameter_ref.rb"
21
+
22
+
23
+ require_relative "sbuilder/param_set.rb"
24
+ require_relative "sbuilder/param_set_db.rb"
25
+ require_relative "sbuilder/param_set_def.rb"
26
+ require_relative "sbuilder/param_set_if.rb"
27
+ require_relative "sbuilder/param_set_step.rb"
28
+
29
+ require_relative "sbuilder/param_sets.rb"
30
+
31
+ require_relative "sbuilder/resolver_rule.rb"
32
+ require_relative "sbuilder/resolver_rule_ref.rb"
33
+ require_relative "sbuilder/resolver_rule_match.rb"
34
+ require_relative "sbuilder/resolver.rb"
35
+
36
+ require_relative "sbuilder/resolver_loader.rb"
37
+ require_relative "sbuilder/resolver_loader_yaml.rb"
38
+
39
+ require_relative "sbuilder/param_set_loader.rb"
40
+ require_relative "sbuilder/param_set_loader_swagger.rb"
41
+
42
+ require_relative "sbuilder/model.rb"
43
+
44
+ require_relative "sbuilder/extension_loader.rb"
45
+
46
+ require_relative "sbuilder/factory.rb"
47
+ require_relative "sbuilder/controller.rb"
48
+
49
+
50
+ require_relative "sbuilder/mustache/template_reader"
51
+ require_relative "sbuilder/mustache/template_reader_context"
52
+ require_relative "sbuilder/mustache/template"
@@ -0,0 +1,72 @@
1
+ module Sbuilder
2
+
3
+ class Constants
4
+
5
+ # supported domain types
6
+ TYPE_DOMAIN = "domain"
7
+ TYPE_VALUE_DOMAIN = "value-domain"
8
+ TYPE_CARDINALITY_DOMAIN = "cardinality-domain"
9
+
10
+ # supported extension loader types
11
+ EXTENSION_LOADER_YAML = "default-yaml"
12
+
13
+ # supported paramter set types
14
+ SWAGGER = "swagger"
15
+ SWAGGER_DEFINITION="definition"
16
+ PARAM_SET_STEPS = "steps"
17
+
18
+ # supported mapper types
19
+ MAPPER_YAML = "resolver_yaml"
20
+
21
+ # supported mapper_rules
22
+ MAPPER_RULE_MATCH = "match"
23
+ MAPPER_RULE_REF = "ref"
24
+
25
+ # supported paremters
26
+ PARAMETER = "param"
27
+ PARAMETER_REF = "param-ref"
28
+
29
+ # direcotory where to manual source files are
30
+ SRC_DIR = "src"
31
+
32
+ # Default template generation
33
+ # Notice directory names end with slash indicating that they are directories (and not gem-names)
34
+
35
+ TEMPLATE_GEM='tla-sbuilder'
36
+ TEMPLATE_PATH='mustache/'
37
+
38
+ SRC_PATH="#{SRC_DIR}/" # i.e. src_dir with ending '/', see option --templates
39
+
40
+ # default log-file
41
+ LOGFILE="sbuilder.log"
42
+
43
+ # configurations
44
+ CNF_DIR = "cnf"
45
+
46
+ # main configuration file
47
+ CNF_FILE="sbuilder.yaml"
48
+
49
+ # directory to cache loaded files
50
+ CACHE_DIR = "tmp"
51
+
52
+ # direcotory where to output rendered templates
53
+ GEN_DIR = "gen"
54
+
55
+ # direcotory where to output build results
56
+ BUILD_DIR = "build"
57
+
58
+ # namespace
59
+ TYPE_NAMESPACE_DOM = "d_"
60
+ TYPE_NAMESPACE_REF = "t_"
61
+
62
+ # Location of resources
63
+ RESOURCES_DIR = File.expand_path '../../../resources/', __FILE__
64
+ SWAGGER_SCHEMA = File.expand_path 'schema/swagger/2.0/schema.json', RESOURCES_DIR
65
+ JSON_SCHEMA = File.expand_path 'schema/json_schema/draft-04.json', RESOURCES_DIR
66
+
67
+ # Default options
68
+ OPT_VERBOSITY = 1 # output leve
69
+
70
+ end
71
+
72
+ end
@@ -0,0 +1,798 @@
1
+ require 'forwardable'
2
+
3
+ module Sbuilder
4
+
5
+ class Controller
6
+
7
+
8
+
9
+ extend Forwardable # for easy delegation
10
+
11
+ attr_reader :factory # factory pattern
12
+ attr_reader :mappers # array of loader 'Resolver' objects
13
+
14
+ # model parts
15
+ attr_reader :model # which contain values loaded
16
+ def_delegators :model, :extendDomain, :extendInterface, :interfaceEncountered, :definitionEncountered,
17
+ :domainEncountered, :templateData, :getInterface, :extendStep
18
+
19
+ def_delegators :factory, :createParameter, :createParamSet
20
+
21
+
22
+
23
+ # ------------------------------------------------------------------
24
+ # validate configuration properties
25
+
26
+
27
+ DEFAULT_PREFERENCES = {
28
+ "debug-output" => true, # print debug output e.g. in interface processes
29
+ }
30
+
31
+
32
+ @@validSetups_required = [ "setupDirectory", ]
33
+ @@validSetups_allowed = @@validSetups_required + [ 'extensions', "desc", "preferences", "possibilities" ]
34
+
35
+ @@extensionLoaderDef_required = ['type' ]
36
+ @@extensionLoaderDef_oneof = ["url", "file"]
37
+ @@extensionLoaderDef_allowed = @@extensionLoaderDef_required + @@extensionLoaderDef_oneof
38
+
39
+ @@interfaceLoaderDef_required = ['type',]
40
+ @@interfaceLoaderDef_oneof = [ 'url', "file", ]
41
+ @@interfaceLoaderDef_allowed = @@interfaceLoaderDef_required + ["cache", "infrastructureServices" ] + @@interfaceLoaderDef_oneof
42
+
43
+
44
+ @@validGenerateConfigs = ["output", "inputs"]
45
+ @@allowedGenerateConfigs = @@validGenerateConfigs + ['category']
46
+ @@validGenerateDef_required = [ "template", "desc", "modelData" ]
47
+ @@validGenerateDef_allowed = @@validGenerateDef_required + [ "templateParameters" ]
48
+ # ------------------------------------------------------------------
49
+ # mixer
50
+ PROGNAME = "Controller" # progname for logger
51
+ include Sbuilder::Utils::MyLogger # mix logger
52
+
53
+
54
+ # ------------------------------------------------------------------
55
+ # constructore
56
+
57
+ def initialize( factory, options = {} )
58
+
59
+ @logger = getLogger( PROGNAME, options )
60
+ @logger.info( "#{__method__} initialized, options=#{options}" )
61
+
62
+ # init configs
63
+ @opts = { :gen_dir => options[:gen_dir] || Sbuilder::Constants::GEN_DIR,
64
+ # :src_dir => options[:src_dir] || Sbuilder::Constants::SRC_DIR,
65
+ :cnf_dir => options[:cnf_dir] || Sbuilder::Constants::CNF_DIR,
66
+ :cache_dir => options[:cache_dir] || Sbuilder::Constants::CACHE_DIR,
67
+ :cnf_file => options[:cnf_file] || Sbuilder::Constants::CNF_FILE,
68
+ :verbosity => options[:verbosity].nil? ? Sbuilder::Constants::OPT_VERBOSITY : options[:verbosity],
69
+ :filter_src => options[:filter_src] || false,
70
+ :templates => options[:templates],
71
+ } #.merge( options[PROGNAME] || {} )
72
+ @factory = factory
73
+ start
74
+ end
75
+
76
+ # @param possibilities [String:Array] possibilities defined in setup
77
+ def start
78
+ # init state
79
+ @mappers = []
80
+ @model = factory.createModel()
81
+
82
+ end
83
+
84
+ # ------------------------------------------------------------------
85
+ # build controller: load (domain) resolvers
86
+
87
+ def loadResolvers( arrOfmappersTypePath )
88
+ !arrOfmappersTypePath.nil? && arrOfmappersTypePath.each do |mapperDef|
89
+ @logger.debug( "#{__method__} mapperDef=#{mapperDef} " )
90
+
91
+ # find correct loader based on type
92
+ loader = factory.getResolverLoader( getResolverDefType( mapperDef ) )
93
+
94
+ # load an put array
95
+ loaded = loader.load( getFileUrlOrPath( mapperDef ), self )
96
+ # @mappers= mappers.concat(loaded) if loaded && loaded.any?
97
+ @logger.info( "#{__method__} @mappers.length=#{@mappers.length}" )
98
+ @logger.debug( "mappers=#{mappers.map { |mapper| mapper.name }}")
99
+ end
100
+ @logger.info( "#{__method__} loaded #{mappers.size} mappers" )
101
+ end
102
+
103
+ # controllers gets informed that mapper is created
104
+ def mapperCreated( mapper )
105
+ @mappers << mapper
106
+ end
107
+
108
+ def findResolver( name )
109
+ @mappers.select{ |mapper| mapper.name == name }.first
110
+ end
111
+
112
+ # ------------------------------------------------------------------
113
+ # load extensions
114
+
115
+ # create load && add let it load
116
+ def loadExtensions( arrayOfExtensionDefs )
117
+
118
+ arrayOfExtensionDefs && arrayOfExtensionDefs.each do |extensionLoaderDef|
119
+ validateProperties( extensionLoaderDef, @@extensionLoaderDef_required, @@extensionLoaderDef_allowed )
120
+ validateOne( extensionLoaderDef, @@extensionLoaderDef_oneof )
121
+
122
+
123
+ @logger.info( "#{__method__} extensionLoaderDef=#{extensionLoaderDef}" )
124
+ loader = factory.getExtensionLoader( extensionLoaderDef['type'] )
125
+ begin
126
+ loader.load( getFileUrlOrPath( extensionLoaderDef ), self )
127
+ rescue Exception => ee
128
+ msg = "Error #{ee} caused by #{ee.backtrace.join("\n")} when loading '#{extensionLoaderDef['url']}'\n\n"
129
+ @logger.error( "#{__method__} #{msg}" )
130
+ raise ControllerException.new msg
131
+ end
132
+ end
133
+ # domainLoader = factory.get
134
+ end
135
+
136
+ # ------------------------------------------------------------------
137
+ # load interfaces
138
+
139
+ def loadInterfaces( arrOfparamsetTypePath )
140
+ !arrOfparamsetTypePath.nil? && arrOfparamsetTypePath.each do |interfaceParamsetDef|
141
+
142
+ validateProperties( interfaceParamsetDef, @@interfaceLoaderDef_required, @@interfaceLoaderDef_allowed )
143
+ validateOne( interfaceParamsetDef, @@interfaceLoaderDef_oneof )
144
+
145
+
146
+ # find correct loader based on type & configure it
147
+ loader = factory.getParamSetLoader( getParamsetType( interfaceParamsetDef ))
148
+ loader.configure( interfaceParamsetDef )
149
+ # load an put array
150
+ loader.load( self, getFileUrlOrPath( interfaceParamsetDef ), getParamsetCacheFilePath(interfaceParamsetDef) )
151
+ # @paramSets = paramSets.concat( loaded ) if loaded && loaded.any?
152
+ end
153
+ end
154
+
155
+ # # accept parameters set to model
156
+ # def interfaceEncountered( paramSet )
157
+ # model.interfaceEncountered( paramSet )
158
+ # end
159
+
160
+ # def paramSets
161
+ # model.paramSets
162
+ # end
163
+
164
+ # ------------------------------------------------------------------
165
+ # manages domains
166
+
167
+ # # called when loading mappers for each domain encountered, return domain
168
+ # def domainEncountered( domainName )
169
+ # model.domainEncountered( domainName )
170
+ # end
171
+
172
+ # # delegate to domains
173
+ # def domains
174
+ # model.domains
175
+ # end
176
+
177
+
178
+ # load paremSets and mappers
179
+ def load
180
+ @logger.info( "#{__method__} load started" )
181
+ loadResolvers( getResolversConfig )
182
+ loadInterfaces( getInterfaceConfig )
183
+ end
184
+
185
+
186
+ def extend
187
+ loadExtensions( getExtensionConfigs )
188
+ end
189
+
190
+ # ------------------------------------------------------------------
191
+ # iterate setups
192
+
193
+ def setup( setupDirectory=nil )
194
+
195
+ @logger.info( "#{__method__} getSetups=#{getSetups.join( " ------------ " )}, #{getSetups.length}" )
196
+ generated=false
197
+
198
+ # deep down makes a new call to getSetups --> interation fails
199
+ setups = Marshal.load( Marshal.dump( getSetups ))
200
+ setups.each_with_index do |setupDef,i|
201
+
202
+ validateProperties( setupDef, @@validSetups_required, @@validSetups_allowed )
203
+ @logger.info( "#{__method__} -->next" ) if setupDirectory && (setupDef['setupDirectory'] != setupDirectory)
204
+ next if setupDirectory && setupDef['setupDirectory'] != setupDirectory
205
+
206
+ # process one setupDirectory
207
+ @logger.info( "#{__method__} start to generate setupDirectory=#{setupDirectory} for #{setupDef}" )
208
+
209
+ output( 1, "------------------------------------------------------------------" ) if isOptFilterSrc
210
+ output( 1, "generate setup: #{setupDef['setupDirectory']}" )
211
+ generate( setupDef['setupDirectory'], setupDef['setupDirectory'], setupDef['extensions'], setupDef['possibilities'] )
212
+ @logger.info( "#{__method__} done to generate setupDirectory=#{setupDirectory} for #{setupDef}" )
213
+ output( 2, "\n" )
214
+
215
+ generated=true
216
+
217
+ end
218
+
219
+ raise ControllerException.new <<-EOS if !generated && setupDirectory
220
+
221
+ Setup #{setupDirectory} not found - Nothing generated
222
+
223
+ Valid setups: #{getSetups.map {|setupDef| setupDef['setupDirectory']}.join(',')}
224
+
225
+ EOS
226
+
227
+
228
+ end
229
+
230
+
231
+ # ------------------------------------------------------------------
232
+ # resolve
233
+ def findResolverForParameterSet( paramSet )
234
+ mappers.each do |mapper|
235
+ if paramSet.matchesWithParamSet( mapper.matcher ) then
236
+ return mapper
237
+ end
238
+ end
239
+ return nil
240
+ end
241
+
242
+ def resolveDomains
243
+ @logger.info( "#{__method__} resolve started" )
244
+ model.paramSets.each do |paramSet|
245
+ # @logger.info( "#{__method__} paramSet id=#{paramSet.getId}" )
246
+ resolver = findResolverForParameterSet( paramSet )
247
+ if resolver.nil? then
248
+ msg = "Could not locate resolver for paramSet #{paramSet.getId}"
249
+ @logger.error( "#{__method__} #{msg}" )
250
+ raise msg
251
+ end
252
+ @logger.info( "#{__method__} resolved resolver.matcher #{resolver.matcher} for paramset id #{paramSet.getId}" )
253
+ # delegate action
254
+ begin
255
+ resolver.resolveDomains( paramSet, model )
256
+ rescue ResolverException => e
257
+ msg = "Error '#{e}' when mapping domains for parameter set '#{paramSet.getId}'"
258
+ puts msg
259
+ @logger.error( "#{__method__} #{msg}" )
260
+ raise
261
+ end
262
+ end
263
+
264
+ end
265
+
266
+ def resolve
267
+ resolveDomains
268
+ end
269
+
270
+ # ------------------------------------------------------------------
271
+ # start, load, resolve, extend && output
272
+ #
273
+ # @param setupName [String] name of setup, currently = extensionDirectory
274
+ # @param extensionDirectory [String] directory where setup generated
275
+ # @arrayOfExtensionDefs [Hash:Array] of extenstions to load
276
+
277
+ def generate( setupName="default", extensionDirectory="default", arrayOfExtensionDefs=[], possibilities=[] )
278
+ @logger.info( "#{__method__} setupName=#{setupName}, extensionDirectory=#{extensionDirectory}" )
279
+ @logger.debug( "#{__method__} arrayOfExtensionDefs=#{arrayOfExtensionDefs}, possibilities=#{possibilities}" )
280
+
281
+ # init state
282
+ start
283
+
284
+ # load configs
285
+ load
286
+
287
+ # resolve domain
288
+ resolve
289
+
290
+ # extensions
291
+ loadExtensions( arrayOfExtensionDefs )
292
+
293
+ # config model - before config rendering
294
+ model.setPossibilities( possibilities )
295
+
296
+ # config && get renderes
297
+ mustache = prepareRender( setupName )
298
+
299
+ # path to directory where setup output is done
300
+ directoryPath = prepareSetupDir( extensionDirectory )
301
+
302
+ # output 'normal templates' - one generate def/one output
303
+ generateCase( setupName, directoryPath, nil, mustache )
304
+
305
+ # For 'possibities': map array of possibilities to array
306
+ # of filenames created using an output -property on
307
+ # 'generate' definition
308
+ possibilities && generateCase( setupName, directoryPath, 'possibility', mustache ) do |output|
309
+ # use output as template rendering
310
+ possibilities.map do |p|
311
+ {
312
+ :possibility => p,
313
+ :outputFile => mustache.render_str( output, { "possibility" => p } ),
314
+ }
315
+ end # map
316
+ end
317
+
318
+ end
319
+
320
+
321
+ # Ensure that directory 'extensionDirectory' exists under
322
+ # 'getGenerateDir'
323
+ #
324
+ # @return [String] directoryPath where extension should be written
325
+ def prepareSetupDir( extensionDirectory )
326
+ # Ensure that 'extensionDirectory' exists
327
+ raise ControllerException.new "Directory #{getGenerateDir} does not exist" unless File.exist?( getGenerateDir )
328
+ # directory for setupName create automatically
329
+ directoryPath = "#{getGenerateDir}/#{extensionDirectory}"
330
+ Dir.mkdir( directoryPath ) unless File.exist?( directoryPath )
331
+ return directoryPath
332
+
333
+ end
334
+
335
+ # @return [Sbuilder::Template] mustache template generator
336
+ def prepareRender( setupName )
337
+
338
+ # access template generator from factory
339
+ reader = factory.getTemplateReader( self )
340
+ # configure reader if it is ready to accept 'setupName'
341
+ reader.setSetupName( setupName ) if reader.respond_to?( :setSetupName )
342
+ # pass reader to template generator
343
+ mustache = factory.getTemplate( reader )
344
+
345
+ return mustache
346
+ end
347
+
348
+ # Output setup for 'setupName' to 'extensionDirectory'.
349
+ #
350
+ # @param setupName [String] identitying setup to process
351
+ #
352
+ # @param directoryPath [String] path to directory under which
353
+ # outputting files
354
+ #
355
+ # @param generateCategory [Nil|String] choose templates in
356
+ # 'generateCategory', Nil = no category defined. Intended
357
+ # usage for to generate possibitilies
358
+ #
359
+ # @param mustache [Sbuilder::Template] generator to render
360
+ #
361
+ # @param blk [Block] to use indirection in output files stuff
362
+ #
363
+ def generateCase( setupName, directoryPath, generateCategory, mustache, &blk )
364
+
365
+ @logger.info( "#{__method__} directoryPath=#{directoryPath}" )
366
+
367
+ # configs in 'generate' -property with correct 'category'
368
+ generateConfigs = getGenerateConfig( generateCategory )
369
+
370
+ # create all files defined in 'generateConfigs'
371
+ generateConfigs.each do |generateFile|
372
+
373
+ # validate generate configuration
374
+ validateProperties( generateFile, @@validGenerateConfigs, @@allowedGenerateConfigs )
375
+
376
+ # use 'output' config or indirection using blk
377
+ outputDefinitions = blk ?
378
+ yield( generateFile['output'] ) :
379
+ [
380
+ {
381
+ :possibility => nil,
382
+ :outputFile => generateFile['output'],
383
+ }
384
+ ]
385
+ @logger.debug( "#{__method__}: outputDefinitions=#{outputDefinitions}" )
386
+
387
+ # one config in 'generateFile['output']' may result to sevaral files (possiblity)
388
+ outputDefinitions.each do |outputDefinition|
389
+
390
+ # set model data - to be able to access in template
391
+ model.setPossibility( outputDefinition[:possibility] )
392
+
393
+ # Ensure directory for 'outputFile' exists
394
+ outputFile = outputDefinition[:outputFile]
395
+ outputPath = "#{directoryPath}/#{outputFile}"
396
+ # create sub directory for output if it does not exist
397
+ outputDirectory = File.dirname( outputPath )
398
+ Dir.mkdir( outputDirectory ) unless File.exist?( outputDirectory )
399
+
400
+ # header for file
401
+ output( 2, "--> generate: #{outputPath}" )
402
+
403
+ # open file and output in 'generateTemplate'
404
+ f = File.open( outputPath, "w" )
405
+ begin
406
+ # one output file get generates using multiple 'inputs'
407
+ generateFile['inputs'].each do |generateDef|
408
+ generateTemplate( setupName, outputPath, f, generateDef, mustache )
409
+ end
410
+ ensure
411
+ f.close
412
+ end
413
+ output( 3, "\n" )
414
+ end # each outputFiles
415
+
416
+ end
417
+
418
+ end
419
+
420
+ # Expand template 'generateDef["template"]' using 'templateData'
421
+ #
422
+ # Create templateData to pass to rendering: (CONFIG,META,
423
+ # PARAMETERS,PREFERENCES, filter), render template(s) named in
424
+ # 'generateDef.template', output to 'fileHandle'.
425
+ #
426
+ # @param outputPath [String] path of file to output
427
+ #
428
+ # @param f [File] file handle (for 'outputPath' to output )
429
+ #
430
+ # @param mustache [Sbuilder::Template] mustache template generator
431
+ #
432
+ # @param generateDef [Hash] configuration
433
+ # {:template,:desc,:modelData,:templateParameters} controlling
434
+ # output
435
+ #
436
+ def generateTemplate( setupName, outputPath, fileHandle, generateDef, mustache )
437
+
438
+ # validate configuration
439
+ validateProperties( generateDef, @@validGenerateDef_required, @@validGenerateDef_allowed )
440
+
441
+ # access data to output - and filter it
442
+ renderData = templateData( generateDef['modelData'] )
443
+ # e.g. prune interfaces not used in setup
444
+ renderData = setupFilter( generateDef['modelData'], renderData )
445
+
446
+ # Iterate each 'template' in one 'generateDef' array (most often just one template)
447
+ templates = generateDef['template'].is_a?( Array ) ? generateDef['template'] : [ generateDef['template'] ]
448
+
449
+ # Support paramters to template
450
+ templateParameters = generateDef['templateParameters'].nil? ? {} : generateDef['templateParameters']
451
+
452
+ # optional parameters added in generate -section
453
+ renderData['PARAMETERS'] = templateParameters
454
+
455
+ # META-data added to data to render
456
+ templates.each do |template|
457
+
458
+ renderData['CONFIG'] = @opts
459
+
460
+ renderData['META'] =
461
+ {
462
+ :desc => generateDef['desc'],
463
+ :modelData => generateDef['modelData'],
464
+ :template => template,
465
+ :version => Sbuilder::version,
466
+ :model_version => Sbuilder::model_version,
467
+ :timestamp => Time.new.strftime( "%Y-%m-%d %H:%M:%S" ),
468
+ :setup => setupName,
469
+ :outputDirectory => File.dirname( outputPath ),
470
+ :outputFile => File.basename( outputPath ),
471
+ :outputModule => File.basename( outputPath, ".*" ),
472
+ }
473
+
474
+ renderData['ACTIONS'] = {
475
+ :cat => lambda do |path_template|
476
+ path = mustache.render_str( path_template, renderData )
477
+ File.readlines( path.strip ).join('')
478
+ end
479
+ }
480
+
481
+ # support 'PREFERENCES'
482
+ renderData.merge!( getPreferencesData(setupName ))
483
+
484
+ # finally render i.e. create string to output
485
+ str = mustache.to_str( template, renderData )
486
+
487
+ # output to file
488
+ @logger.info( "#{__method__} templateName=#{template}" )
489
+ output( 3, " ---> #{generateDef['desc']} (#{template})" )
490
+ fileHandle.puts str
491
+
492
+ end # templates.each
493
+
494
+ end
495
+
496
+
497
+
498
+ # Decide whether 'dataElement' should be passed to template
499
+ # rendering.
500
+ #
501
+ # Implementation: Default 'true', however when
502
+ # 'options[:filter_src]' && 'modelData' == "interface" show only
503
+ # if included in 'getSetupStepInterfaceOperations'
504
+ #
505
+ #
506
+ # @param modelData [String] type of data (see model#templateData)
507
+ #
508
+ # @param d [Hash] data element, content depends on 'modelData'
509
+ #
510
+ # @return [Boolean] true when show the elmement
511
+ def setupFilter( modelData, renderData )
512
+ case modelData
513
+ when "interfaces"
514
+ return renderData unless isOptFilterSrc
515
+ # show only interfaces_operations referenced in 'steps'
516
+ interface_operations = getSetupStepInterfaceOperations
517
+ renderData['interfaces'] = renderData['interfaces'].select{ |d| interface_operations.include?( d[:interface_operation] ) }
518
+ @logger.info( "#{__method__}: filtered renderData['interfaces']=#{renderData['interfaces'].map{|i| i[:interface_operation]}}" )
519
+ return renderData
520
+ else
521
+ # default is to show all
522
+ renderData
523
+ end
524
+ end
525
+
526
+ # Read model#templateData('steps') and return array interface
527
+ # operations reference in model steps.
528
+ #
529
+ # @return [String:Array] of interface operation names
530
+ def getSetupStepInterfaceOperations
531
+ interface_operations = {}
532
+ model.templateData( "steps" )['steps'].each {|s| interface_operations[s[:interface_operation]] = true }
533
+ ret = interface_operations.keys
534
+ @logger.info( "#{__method__}: interface_operations=#{ret}" )
535
+ return ret
536
+ end
537
+
538
+ # Return names defined in mode (definitions)
539
+ #
540
+ # @return [String:Array] of names in define in model
541
+ def getModelNames
542
+ ret = []
543
+
544
+ # definition map to types 't_:defintion_name'
545
+ ret += templateData( "definitions" )['definitions'].map { |d| "t_#{d[:definition_name]}"}
546
+
547
+ # domain --> 'd_:domain_name'
548
+ ret += templateData( "domains" )['domains'].map { |d| "d_#{d[:domain_name]}"}
549
+
550
+ @logger.debug( "#{__method__} ret=#{ret.join(',')}" )
551
+ ret
552
+ end
553
+
554
+ # Return entry_points such that
555
+ # interfaces[steps.interface_operation ==
556
+ # intertafaces.interface_operation].implementation
557
+ #
558
+ # @return [String:Array] names of interface[:implementation]
559
+ #
560
+ def getEntryPoints
561
+ setup_operations = {}
562
+
563
+ steps = model.templateData( "steps" )['steps']
564
+ interfaces = model.templateData( "interfaces" )['interfaces']
565
+
566
+ steps.each { |s| setup_operations[s[:interface_operation]] = true }
567
+ @logger.debug( "#{__method__}: setup_operations= #{setup_operations}" )
568
+ entry_points = interfaces.
569
+ select {|i| setup_operations[i[:interface_operation]] }.
570
+ map { |i| i[:implementation] }
571
+ @logger.info( "#{__method__}: entry_points= #{entry_points}" )
572
+ entry_points
573
+
574
+ end
575
+
576
+ # Return possibilities for current setup.
577
+ #
578
+ # @return [String:Array] of possibiliy operator names
579
+ def getSetupPossibilities
580
+ setupPossis = model.possibilities
581
+ @logger.info( "#{__method__}: setupPossis= #{setupPossis}" )
582
+ return setupPossis
583
+ end
584
+
585
+ # create data to pass to template generateion
586
+ # def templateData
587
+ # return model.templateData
588
+ # end
589
+
590
+ # ------------------------------------------------------------------
591
+ # configuration
592
+
593
+
594
+ # cache '@controller_config' read 'getConfigFilePath' and 'getInternalConfigFilePath', merge 'generate'
595
+ def getConfig
596
+
597
+ return @controller_config if @controller_config
598
+
599
+ @logger.info( "#{__method__} loading config from #{getConfigFilePath}" )
600
+ yaml_lines = Sbuilder::Utils::NetIo.read_lines( getConfigFilePath )
601
+ @controller_config = YAML.load( yaml_lines )
602
+ # allow empty generate section
603
+ @controller_config = {} if @controller_config.nil?
604
+ @controller_config['generate'] = [] unless @controller_config['generate']
605
+ @logger.debug( "#{__method__} config in #{getConfigFilePath} controller_config= #{@controller_config.to_yaml}" )
606
+
607
+ yaml_lines = Sbuilder::Utils::NetIo.read_lines( getInternalConfigFilePath )
608
+ internal_config = YAML.load( yaml_lines )
609
+ internal_config['generate'] = [] unless internal_config['generate']
610
+ @logger.debug( "#{__method__} internal_config= #{internal_config.to_yaml}" )
611
+
612
+ @logger.debug( "#{__method__} config in #{getInternalConfigFilePath} internal_config= #{internal_config.to_yaml}" )
613
+ @controller_config['generate'] = @controller_config['generate'] + internal_config['generate']
614
+ @logger.debug( "#{__method__} @controller_config=#{@controller_config.to_yaml}" )
615
+ @controller_config
616
+ end
617
+
618
+ def getSetups
619
+ getConfig()['setups']
620
+ end
621
+
622
+ # @param setupName [String] name of setup to get
623
+ # @return [Hash] hash of set in sbuilder configuration
624
+ def getSetup( setupName )
625
+ setup = getSetups.select { |setup| setup['setupDirectory'] == setupName }.first
626
+ @logger.debug( "#{__method__} setupName=#{setupName}, setup=#{setup} " )
627
+ setup
628
+ end
629
+
630
+
631
+ def getPreferencesData( setupName )
632
+ renderData = {}
633
+ renderData['PREFERENCES'] = getPreferencesWithDefaults( setupName )
634
+ return renderData
635
+ end
636
+
637
+ # Get prefecnces with default setttings
638
+ # @return preferences [Hash] with values for all known preferences
639
+ def getPreferencesWithDefaults( setupName )
640
+ DEFAULT_PREFERENCES.merge( getPreferences ).merge( getSetupPrefences( setupName ))
641
+ end
642
+
643
+ # Access preferences in main configuration file 'sbuilder.yaml'
644
+ #
645
+ # @return preferences [Hash] possible empty (but definitely
646
+ # non-nil)
647
+ def getPreferences
648
+ getConfig()['preferences'] || {}
649
+ end
650
+
651
+ # @param setupName [String] name of setup in 'setups' propertys
652
+ # @return [Hash] preferences in setup[setupName] or {}
653
+ def getSetupPrefences( setupName )
654
+ getSetup(setupName)['preferences'] || {}
655
+ end
656
+
657
+ # Currently uses first element in template as code repostirory
658
+ # @return [String] path to code repository
659
+ def getCodeRepository
660
+ getOpt( :templates ).first
661
+ end
662
+
663
+ private
664
+
665
+ # path to configuration file
666
+ def getConfigFilePath()
667
+ "#{getOpt(:cnf_dir)}/#{getOpt(:cnf_file)}"
668
+ end
669
+
670
+ # path to interal configuration file
671
+ def getInternalConfigFilePath()
672
+ File.join File.dirname( __FILE__), "default-sbuilder.yaml"
673
+ end
674
+
675
+ def getInterfaceConfig
676
+ getConfig()['interfaces']
677
+ end
678
+
679
+ def getExtensionConfigs
680
+ getConfig()['extensions']
681
+ end
682
+
683
+
684
+ def getResolversConfig
685
+ getConfig()['resolvers']
686
+ end
687
+
688
+ # @param generateCategory [Nil|String] choose templates in
689
+ # 'generateCategory', @return [Hash:Array] of {'output', 'inputs'}
690
+ # defining a file to render using templates in 'inputs'
691
+ def getGenerateConfig( generateCategory )
692
+ ret = getConfig()['generate']
693
+ if ret.nil? then
694
+ warn <<-EOS
695
+ Could not locate section 'generate' in sbuilder configuration
696
+
697
+
698
+ #{getConfig().to_yaml}
699
+ EOS
700
+ return []
701
+ end
702
+
703
+ # Screen category
704
+ ret.select { |c| c['category'] == generateCategory }
705
+ end
706
+
707
+ # return uri whero load from
708
+ def getFileUrlOrPath( paramSetDef )
709
+ @logger.info( "#{__method__} paramSetDef=#{paramSetDef}" )
710
+ if paramSetDef['file'] then
711
+ return "#{getOpt(:cnf_dir)}/#{paramSetDef['file']}"
712
+ elsif paramSetDef['url'] then
713
+ return paramSetDef['url']
714
+ else
715
+ raise "Param set definition #{paramSetDef} should defined 'file' or 'url'"
716
+ end
717
+
718
+ end
719
+
720
+ # return optional cacheFilePath from paramSetDef, nil if no 'cache' -property
721
+ def getParamsetCacheFilePath( paramSetDef )
722
+ return "#{getOpt(:cache_dir)}/#{paramSetDef['cache']}" if paramSetDef['cache'] && getOpt(:cache_dir)
723
+ return paramSetDef['cache'] if paramSetDef['cache']
724
+ return nil
725
+ end
726
+
727
+ def getParamsetType( paramSetDef )
728
+ paramSetDef['type']
729
+ end
730
+
731
+ # def getResolverDefPath( mapperDef )
732
+ # if mapperDef['file'] then
733
+ # return "#{getOpt(:cnf_dir)}/#{mapperDef['file']}"
734
+ # elsif mapperDef['url'] then
735
+ # return mapperDef['url']
736
+ # else
737
+ # raise "Resolver definition #{mapperDef} should defined 'file' or 'url'"
738
+ # end
739
+ # end
740
+
741
+ def getResolverDefType( resolverDef )
742
+ resolverDef['type']
743
+ end
744
+
745
+ def getGenerateDir
746
+ getOpt(:gen_dir)
747
+ end
748
+
749
+
750
+ # return option for a key
751
+ def getOpt( optKey )
752
+ @opts[ optKey ]
753
+ end
754
+
755
+ # @return [boolean] true when should filter specifcation model
756
+ def isOptFilterSrc
757
+ getOpt( :filter_src )
758
+ end
759
+
760
+ # ------------------------------------------------------------------
761
+ # output
762
+
763
+ def output( level, msg, nl=true )
764
+ if nl
765
+ puts msg if output_show( level )
766
+ elsif output_show( level )
767
+ print msg
768
+ $stdout.flush
769
+ end
770
+ end
771
+
772
+ # @return [Boolean] true if output on level
773
+ def output_show( level )
774
+ level <= @opts[:verbosity]
775
+ end
776
+
777
+ # validate 'defintionHash' all 'required'/only 'allowed' props set
778
+ private def validateProperties( defintionHash, required, allowed=nil )
779
+
780
+ allowed = required unless allowed
781
+
782
+ missingProps = required - defintionHash.keys
783
+ raise ControllerException.new "Missing properties #{missingProps} in #{defintionHash} - required #{required}" if missingProps.any?
784
+
785
+ invalidProps = defintionHash.keys - allowed
786
+ raise ControllerException.new "Unknown properties #{invalidProps} in #{defintionHash} - allowed #{allowed}" if invalidProps.any?
787
+
788
+ end
789
+
790
+ private def validateOne( defintionHash, propList )
791
+ raise ControllerException.new "Must give one #{propList} in #{defintionHash}" unless propList.select {|prop| defintionHash.has_key?(prop) }.length == 1
792
+ end
793
+
794
+
795
+
796
+ end # class
797
+
798
+ end