summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/puppet.rb58
-rw-r--r--lib/puppet/application.rb35
-rw-r--r--lib/puppet/application/agent.rb17
-rw-r--r--lib/puppet/application/apply.rb19
-rw-r--r--lib/puppet/application/doc.rb15
-rw-r--r--lib/puppet/application/master.rb36
-rw-r--r--lib/puppet/application/queue.rb2
-rw-r--r--lib/puppet/application/resource.rb1
-rw-r--r--lib/puppet/configurer.rb41
-rw-r--r--lib/puppet/configurer/downloader.rb21
-rw-r--r--lib/puppet/configurer/downloader_factory.rb34
-rw-r--r--lib/puppet/configurer/plugin_handler.rb27
-rw-r--r--lib/puppet/defaults.rb384
-rw-r--r--lib/puppet/environments.rb8
-rw-r--r--lib/puppet/external/nagios/base.rb2
-rw-r--r--lib/puppet/external/pson/pure/generator.rb9
-rw-r--r--lib/puppet/face/ca.rb7
-rw-r--r--lib/puppet/face/file/download.rb7
-rw-r--r--lib/puppet/face/file/store.rb2
-rw-r--r--lib/puppet/face/instrumentation_data.rb3
-rw-r--r--lib/puppet/face/instrumentation_listener.rb3
-rw-r--r--lib/puppet/face/instrumentation_probe.rb3
-rw-r--r--lib/puppet/face/module/build.rb4
-rw-r--r--lib/puppet/face/module/generate.rb32
-rw-r--r--lib/puppet/face/module/install.rb7
-rw-r--r--lib/puppet/face/module/uninstall.rb7
-rw-r--r--lib/puppet/face/module/upgrade.rb14
-rw-r--r--lib/puppet/face/node/clean.rb2
-rw-r--r--lib/puppet/face/parser.rb106
-rw-r--r--lib/puppet/feature/base.rb30
-rw-r--r--lib/puppet/feature/cfacter.rb14
-rw-r--r--lib/puppet/feature/pe_license.rb4
-rw-r--r--lib/puppet/file_bucket/dipper.rb31
-rw-r--r--lib/puppet/file_bucket/file.rb83
-rw-r--r--lib/puppet/file_serving/configuration/parser.rb6
-rw-r--r--lib/puppet/file_system.rb2
-rw-r--r--lib/puppet/file_system/file19.rb41
-rw-r--r--lib/puppet/file_system/file19windows.rb1
-rw-r--r--lib/puppet/file_system/tempfile.rb20
-rw-r--r--lib/puppet/file_system/uniquefile.rb190
-rw-r--r--lib/puppet/forge.rb41
-rw-r--r--lib/puppet/forge/errors.rb11
-rw-r--r--lib/puppet/forge/repository.rb16
-rw-r--r--lib/puppet/functions.rb31
-rw-r--r--lib/puppet/functions/assert_type.rb37
-rw-r--r--lib/puppet/functions/each.rb111
-rw-r--r--lib/puppet/functions/epp.rb54
-rw-r--r--lib/puppet/functions/filter.rb113
-rw-r--r--lib/puppet/functions/inline_epp.rb88
-rw-r--r--lib/puppet/functions/map.rb97
-rw-r--r--lib/puppet/functions/match.rb102
-rw-r--r--lib/puppet/functions/reduce.rb94
-rw-r--r--lib/puppet/functions/slice.rb126
-rw-r--r--lib/puppet/functions/with.rb23
-rw-r--r--lib/puppet/indirector/catalog/compiler.rb8
-rw-r--r--lib/puppet/indirector/data_binding/hiera.rb47
-rw-r--r--lib/puppet/indirector/facts/couch.rb4
-rw-r--r--lib/puppet/indirector/facts/facter.rb117
-rw-r--r--lib/puppet/indirector/file_bucket_file/file.rb9
-rw-r--r--lib/puppet/indirector/hiera.rb48
-rw-r--r--lib/puppet/indirector/indirection.rb2
-rw-r--r--lib/puppet/indirector/request.rb12
-rw-r--r--lib/puppet/indirector/resource/ral.rb2
-rw-r--r--lib/puppet/indirector/rest.rb10
-rw-r--r--lib/puppet/loaders.rb1
-rw-r--r--lib/puppet/module.rb3
-rw-r--r--lib/puppet/module_tool.rb2
-rw-r--r--lib/puppet/module_tool/applications/application.rb9
-rw-r--r--lib/puppet/module_tool/applications/builder.rb69
-rw-r--r--lib/puppet/module_tool/applications/uninstaller.rb5
-rw-r--r--lib/puppet/module_tool/applications/unpacker.rb14
-rw-r--r--lib/puppet/module_tool/applications/upgrader.rb36
-rw-r--r--lib/puppet/module_tool/dependency.rb12
-rw-r--r--lib/puppet/module_tool/errors/shared.rb2
-rw-r--r--lib/puppet/module_tool/errors/upgrader.rb20
-rw-r--r--lib/puppet/module_tool/installed_modules.rb7
-rw-r--r--lib/puppet/module_tool/metadata.rb56
-rw-r--r--lib/puppet/module_tool/modulefile.rb2
-rw-r--r--lib/puppet/module_tool/skeleton/templates/generator/Gemfile7
-rw-r--r--lib/puppet/module_tool/skeleton/templates/generator/manifests/init.pp.erb2
-rw-r--r--lib/puppet/module_tool/skeleton/templates/generator/spec/spec_helper.rb18
-rw-r--r--lib/puppet/module_tool/tar/mini.rb22
-rw-r--r--lib/puppet/network/http.rb5
-rw-r--r--lib/puppet/network/http/api/v1.rb4
-rw-r--r--lib/puppet/network/http/api/v2/environments.rb16
-rw-r--r--lib/puppet/network/http/connection.rb122
-rw-r--r--lib/puppet/network/http/factory.rb44
-rw-r--r--lib/puppet/network/http/handler.rb18
-rw-r--r--lib/puppet/network/http/nocache_pool.rb21
-rw-r--r--lib/puppet/network/http/pool.rb120
-rw-r--r--lib/puppet/network/http/rack/rest.rb4
-rw-r--r--lib/puppet/network/http/session.rb17
-rw-r--r--lib/puppet/network/http/site.rb39
-rw-r--r--lib/puppet/network/http/webrick/rest.rb15
-rw-r--r--lib/puppet/network/http_pool.rb7
-rw-r--r--lib/puppet/node.rb25
-rw-r--r--lib/puppet/node/environment.rb44
-rw-r--r--lib/puppet/parser/ast.rb1
-rw-r--r--lib/puppet/parser/ast/collection.rb4
-rw-r--r--lib/puppet/parser/ast/collexpr.rb4
-rw-r--r--lib/puppet/parser/ast/node.rb5
-rw-r--r--lib/puppet/parser/ast/pops_bridge.rb55
-rw-r--r--lib/puppet/parser/ast/tag.rb24
-rw-r--r--lib/puppet/parser/compiler.rb104
-rw-r--r--lib/puppet/parser/e4_parser_adapter.rb4
-rw-r--r--lib/puppet/parser/e_parser_adapter.rb119
-rw-r--r--lib/puppet/parser/files.rb109
-rw-r--r--lib/puppet/parser/functions.rb47
-rw-r--r--lib/puppet/parser/functions/assert_type.rb31
-rw-r--r--lib/puppet/parser/functions/collect.rb15
-rw-r--r--lib/puppet/parser/functions/contain.rb20
-rw-r--r--lib/puppet/parser/functions/create_resources.rb6
-rw-r--r--lib/puppet/parser/functions/digest.rb5
-rw-r--r--lib/puppet/parser/functions/each.rb153
-rw-r--r--lib/puppet/parser/functions/epp.rb22
-rw-r--r--lib/puppet/parser/functions/file.rb32
-rw-r--r--lib/puppet/parser/functions/filter.rb120
-rw-r--r--lib/puppet/parser/functions/include.rb36
-rw-r--r--lib/puppet/parser/functions/inline_epp.rb21
-rw-r--r--lib/puppet/parser/functions/lookup.rb2
-rw-r--r--lib/puppet/parser/functions/map.rb113
-rw-r--r--lib/puppet/parser/functions/match.rb28
-rw-r--r--lib/puppet/parser/functions/reduce.rb167
-rw-r--r--lib/puppet/parser/functions/require.rb18
-rw-r--r--lib/puppet/parser/functions/search.rb7
-rw-r--r--lib/puppet/parser/functions/select.rb15
-rw-r--r--lib/puppet/parser/functions/slice.rb138
-rw-r--r--lib/puppet/parser/functions/template.rb17
-rw-r--r--lib/puppet/parser/functions/with.rb21
-rw-r--r--lib/puppet/parser/lexer.rb2
-rw-r--r--lib/puppet/parser/parser_factory.rb54
-rw-r--r--lib/puppet/parser/resource.rb16
-rw-r--r--lib/puppet/parser/scope.rb100
-rw-r--r--lib/puppet/pops.rb20
-rw-r--r--lib/puppet/pops/adapters.rb3
-rw-r--r--lib/puppet/pops/binder/bindings_checker.rb8
-rw-r--r--lib/puppet/pops/binder/bindings_factory.rb12
-rw-r--r--lib/puppet/pops/binder/bindings_label_provider.rb2
-rw-r--r--lib/puppet/pops/binder/bindings_loader.rb4
-rw-r--r--lib/puppet/pops/binder/bindings_model.rb249
-rw-r--r--lib/puppet/pops/binder/bindings_model_dumper.rb2
-rw-r--r--lib/puppet/pops/binder/bindings_model_meta.rb215
-rw-r--r--lib/puppet/pops/binder/injector.rb18
-rw-r--r--lib/puppet/pops/binder/key_factory.rb4
-rw-r--r--lib/puppet/pops/binder/lookup.rb20
-rw-r--r--lib/puppet/pops/binder/producers.rb21
-rw-r--r--lib/puppet/pops/evaluator/access_operator.rb64
-rw-r--r--lib/puppet/pops/evaluator/callable_mismatch_describer.rb175
-rw-r--r--lib/puppet/pops/evaluator/callable_signature.rb3
-rw-r--r--lib/puppet/pops/evaluator/closure.rb186
-rw-r--r--lib/puppet/pops/evaluator/compare_operator.rb48
-rw-r--r--lib/puppet/pops/evaluator/epp_evaluator.rb31
-rw-r--r--lib/puppet/pops/evaluator/evaluator_impl.rb506
-rw-r--r--lib/puppet/pops/evaluator/relationship_operator.rb5
-rw-r--r--lib/puppet/pops/evaluator/runtime3_support.rb139
-rw-r--r--lib/puppet/pops/functions/dispatch.rb13
-rw-r--r--lib/puppet/pops/functions/dispatcher.rb171
-rw-r--r--lib/puppet/pops/issue_reporter.rb20
-rw-r--r--lib/puppet/pops/issues.rb117
-rw-r--r--lib/puppet/pops/loader/base_loader.rb6
-rw-r--r--lib/puppet/pops/loader/loader.rb2
-rw-r--r--lib/puppet/pops/loader/loader_paths.rb23
-rw-r--r--lib/puppet/pops/loader/ruby_function_instantiator.rb2
-rw-r--r--lib/puppet/pops/loader/ruby_legacy_function_instantiator.rb109
-rw-r--r--lib/puppet/pops/loader/static_loader.rb14
-rw-r--r--lib/puppet/pops/model/ast_transformer.rb28
-rw-r--r--lib/puppet/pops/model/factory.rb82
-rw-r--r--lib/puppet/pops/model/model.rb656
-rw-r--r--lib/puppet/pops/model/model_label_provider.rb6
-rw-r--r--lib/puppet/pops/model/model_meta.rb576
-rw-r--r--lib/puppet/pops/model/model_tree_dumper.rb32
-rw-r--r--lib/puppet/pops/parser/egrammar.ra430
-rw-r--r--lib/puppet/pops/parser/eparser.rb2870
-rw-r--r--lib/puppet/pops/parser/evaluating_parser.rb90
-rw-r--r--lib/puppet/pops/parser/lexer.rb753
-rw-r--r--lib/puppet/pops/parser/lexer2.rb24
-rw-r--r--lib/puppet/pops/parser/lexer_support.rb6
-rw-r--r--lib/puppet/pops/parser/locator.rb2
-rw-r--r--lib/puppet/pops/parser/makefile6
-rw-r--r--lib/puppet/pops/parser/parser_support.rb80
-rw-r--r--lib/puppet/pops/patterns.rb20
-rw-r--r--lib/puppet/pops/semantic_error.rb2
-rw-r--r--lib/puppet/pops/types/class_loader.rb37
-rw-r--r--lib/puppet/pops/types/type_calculator.rb275
-rw-r--r--lib/puppet/pops/types/type_factory.rb132
-rw-r--r--lib/puppet/pops/types/type_parser.rb38
-rw-r--r--lib/puppet/pops/types/types.rb675
-rw-r--r--lib/puppet/pops/types/types_meta.rb223
-rw-r--r--lib/puppet/pops/utils.rb30
-rw-r--r--lib/puppet/pops/validation/checker3_1.rb558
-rw-r--r--lib/puppet/pops/validation/checker4_0.rb282
-rw-r--r--lib/puppet/pops/validation/validator_factory_3_1.rb31
-rw-r--r--lib/puppet/pops/validation/validator_factory_4_0.rb1
-rw-r--r--lib/puppet/pops/visitor.rb103
-rw-r--r--lib/puppet/provider/exec.rb11
-rw-r--r--lib/puppet/provider/file/windows.rb7
-rw-r--r--lib/puppet/provider/group/windows_adsi.rb20
-rw-r--r--lib/puppet/provider/nameservice/directoryservice.rb7
-rw-r--r--lib/puppet/provider/package/apt.rb6
-rw-r--r--lib/puppet/provider/package/gem.rb10
-rw-r--r--lib/puppet/provider/package/openbsd.rb91
-rw-r--r--lib/puppet/provider/package/pacman.rb33
-rw-r--r--lib/puppet/provider/package/rpm.rb12
-rw-r--r--lib/puppet/provider/package/sun.rb6
-rw-r--r--lib/puppet/provider/package/windows.rb7
-rw-r--r--lib/puppet/provider/package/windows/exe_package.rb2
-rw-r--r--lib/puppet/provider/package/windows/msi_package.rb2
-rw-r--r--lib/puppet/provider/package/windows/package.rb14
-rw-r--r--lib/puppet/provider/package/yum.rb10
-rw-r--r--lib/puppet/provider/package/zypper.rb6
-rw-r--r--lib/puppet/provider/parsedfile.rb18
-rw-r--r--lib/puppet/provider/scheduled_task/win32_taskscheduler.rb18
-rw-r--r--lib/puppet/provider/service/freebsd.rb24
-rw-r--r--lib/puppet/provider/service/init.rb5
-rw-r--r--lib/puppet/provider/service/launchd.rb3
-rw-r--r--lib/puppet/provider/service/openbsd.rb15
-rw-r--r--lib/puppet/provider/ssh_authorized_key/parsed.rb4
-rw-r--r--lib/puppet/provider/sshkey/parsed.rb5
-rw-r--r--lib/puppet/provider/user/user_role_add.rb9
-rw-r--r--lib/puppet/provider/user/windows_adsi.rb16
-rw-r--r--lib/puppet/provider/zone/solaris.rb2
-rw-r--r--lib/puppet/reference/metaparameter.rb14
-rw-r--r--lib/puppet/reports/store.rb13
-rw-r--r--lib/puppet/resource.rb86
-rw-r--r--lib/puppet/resource/catalog.rb23
-rw-r--r--lib/puppet/resource/type.rb27
-rw-r--r--lib/puppet/settings.rb169
-rw-r--r--lib/puppet/settings/array_setting.rb17
-rw-r--r--lib/puppet/settings/base_setting.rb35
-rw-r--r--lib/puppet/settings/environment_conf.rb36
-rw-r--r--lib/puppet/settings/file_setting.rb10
-rw-r--r--lib/puppet/settings/priority_setting.rb10
-rw-r--r--lib/puppet/ssl.rb1
-rw-r--r--lib/puppet/ssl/certificate_authority.rb21
-rw-r--r--lib/puppet/ssl/certificate_authority/autosign_command.rb3
-rw-r--r--lib/puppet/ssl/host.rb5
-rw-r--r--lib/puppet/ssl/inventory.rb17
-rw-r--r--lib/puppet/ssl/validator/default_validator.rb1
-rw-r--r--lib/puppet/ssl/validator/no_validator.rb3
-rw-r--r--lib/puppet/transaction.rb31
-rw-r--r--lib/puppet/transaction/resource_harness.rb19
-rw-r--r--lib/puppet/type.rb120
-rw-r--r--lib/puppet/type/exec.rb44
-rw-r--r--lib/puppet/type/file.rb72
-rw-r--r--lib/puppet/type/file/content.rb6
-rw-r--r--lib/puppet/type/file/mode.rb15
-rw-r--r--lib/puppet/type/file/source.rb5
-rw-r--r--lib/puppet/type/group.rb2
-rw-r--r--lib/puppet/type/mount.rb4
-rw-r--r--lib/puppet/type/resources.rb94
-rw-r--r--lib/puppet/type/ssh_authorized_key.rb70
-rw-r--r--lib/puppet/type/sshkey.rb2
-rw-r--r--lib/puppet/type/user.rb35
-rw-r--r--lib/puppet/type/yumrepo.rb67
-rw-r--r--lib/puppet/type/zone.rb9
-rw-r--r--lib/puppet/util.rb119
-rw-r--r--lib/puppet/util/autoload.rb4
-rw-r--r--lib/puppet/util/colors.rb80
-rw-r--r--lib/puppet/util/command_line.rb25
-rw-r--r--lib/puppet/util/execution.rb70
-rw-r--r--lib/puppet/util/feature.rb19
-rw-r--r--lib/puppet/util/filetype.rb8
-rw-r--r--lib/puppet/util/http_proxy.rb31
-rw-r--r--lib/puppet/util/lockfile.rb2
-rw-r--r--lib/puppet/util/log/destinations.rb10
-rw-r--r--lib/puppet/util/logging.rb57
-rw-r--r--lib/puppet/util/pidlock.rb16
-rw-r--r--lib/puppet/util/posix.rb52
-rw-r--r--lib/puppet/util/profiler.rb26
-rw-r--r--lib/puppet/util/profiler/aggregate.rb85
-rw-r--r--lib/puppet/util/profiler/around_profiler.rb67
-rw-r--r--lib/puppet/util/profiler/logging.rb23
-rw-r--r--lib/puppet/util/profiler/none.rb8
-rw-r--r--lib/puppet/util/profiler/wall_clock.rb13
-rw-r--r--lib/puppet/util/rdoc.rb9
-rw-r--r--lib/puppet/util/rdoc/parser/puppet_parser_core.rb2
-rw-r--r--lib/puppet/util/suidmanager.rb9
-rw-r--r--lib/puppet/util/tagging.rb13
-rw-r--r--lib/puppet/util/windows.rb17
-rw-r--r--lib/puppet/util/windows/access_control_list.rb8
-rw-r--r--lib/puppet/util/windows/adsi.rb (renamed from lib/puppet/util/adsi.rb)136
-rw-r--r--lib/puppet/util/windows/api_types.rb255
-rw-r--r--lib/puppet/util/windows/com.rb224
-rw-r--r--lib/puppet/util/windows/error.rb77
-rw-r--r--lib/puppet/util/windows/file.rb386
-rw-r--r--lib/puppet/util/windows/process.rb470
-rw-r--r--lib/puppet/util/windows/registry.rb14
-rw-r--r--lib/puppet/util/windows/root_certs.rb25
-rw-r--r--lib/puppet/util/windows/security.rb733
-rw-r--r--lib/puppet/util/windows/sid.rb116
-rw-r--r--lib/puppet/util/windows/string.rb2
-rw-r--r--lib/puppet/util/windows/taskscheduler.rb1241
-rw-r--r--lib/puppet/util/windows/user.rb298
-rw-r--r--lib/puppet/vendor.rb4
-rw-r--r--lib/puppet/vendor/load_pathspec.rb1
-rw-r--r--lib/puppet/vendor/load_rgen.rb1
-rw-r--r--lib/puppet/vendor/pathspec/CHANGELOG.md2
-rw-r--r--lib/puppet/vendor/pathspec/LICENSE201
-rw-r--r--lib/puppet/vendor/pathspec/PUPPET_README.md6
-rw-r--r--lib/puppet/vendor/pathspec/README.md53
-rw-r--r--lib/puppet/vendor/pathspec/lib/pathspec.rb121
-rw-r--r--lib/puppet/vendor/pathspec/lib/pathspec/gitignorespec.rb275
-rw-r--r--lib/puppet/vendor/pathspec/lib/pathspec/regexspec.rb17
-rw-r--r--lib/puppet/vendor/pathspec/lib/pathspec/spec.rb14
-rw-r--r--lib/puppet/vendor/require_vendored.rb2
-rw-r--r--lib/puppet/vendor/rgen/CHANGELOG197
-rw-r--r--lib/puppet/vendor/rgen/MIT-LICENSE20
-rw-r--r--lib/puppet/vendor/rgen/PUPPET_README.md6
-rw-r--r--lib/puppet/vendor/rgen/README.rdoc78
-rw-r--r--lib/puppet/vendor/rgen/Rakefile41
-rw-r--r--lib/puppet/vendor/rgen/TODO41
-rw-r--r--lib/puppet/vendor/rgen/anounce.txt61
-rw-r--r--lib/puppet/vendor/rgen/design_rationale.txt71
-rw-r--r--lib/puppet/vendor/rgen/lib/ea_support/ea_support.rb54
-rw-r--r--lib/puppet/vendor/rgen/lib/ea_support/id_store.rb32
-rw-r--r--lib/puppet/vendor/rgen/lib/ea_support/uml13_ea_metamodel.rb562
-rw-r--r--lib/puppet/vendor/rgen/lib/ea_support/uml13_ea_metamodel_ext.rb45
-rw-r--r--lib/puppet/vendor/rgen/lib/ea_support/uml13_ea_metamodel_generator.rb43
-rw-r--r--lib/puppet/vendor/rgen/lib/ea_support/uml13_ea_to_uml13.rb103
-rw-r--r--lib/puppet/vendor/rgen/lib/ea_support/uml13_to_uml13_ea.rb89
-rw-r--r--lib/puppet/vendor/rgen/lib/metamodels/uml13_metamodel.rb559
-rw-r--r--lib/puppet/vendor/rgen/lib/metamodels/uml13_metamodel_ext.rb26
-rw-r--r--lib/puppet/vendor/rgen/lib/mmgen/metamodel_generator.rb20
-rw-r--r--lib/puppet/vendor/rgen/lib/mmgen/mm_ext/ecore_mmgen_ext.rb91
-rw-r--r--lib/puppet/vendor/rgen/lib/mmgen/mmgen.rb28
-rw-r--r--lib/puppet/vendor/rgen/lib/mmgen/templates/annotations.tpl37
-rw-r--r--lib/puppet/vendor/rgen/lib/mmgen/templates/metamodel_generator.tpl172
-rw-r--r--lib/puppet/vendor/rgen/lib/rgen/array_extensions.rb45
-rw-r--r--lib/puppet/vendor/rgen/lib/rgen/ecore/ecore.rb218
-rw-r--r--lib/puppet/vendor/rgen/lib/rgen/ecore/ecore_builder_methods.rb81
-rw-r--r--lib/puppet/vendor/rgen/lib/rgen/ecore/ecore_ext.rb69
-rw-r--r--lib/puppet/vendor/rgen/lib/rgen/ecore/ecore_interface.rb47
-rw-r--r--lib/puppet/vendor/rgen/lib/rgen/ecore/ecore_to_ruby.rb167
-rw-r--r--lib/puppet/vendor/rgen/lib/rgen/ecore/ruby_to_ecore.rb91
-rw-r--r--lib/puppet/vendor/rgen/lib/rgen/environment.rb129
-rw-r--r--lib/puppet/vendor/rgen/lib/rgen/fragment/dump_file_cache.rb63
-rw-r--r--lib/puppet/vendor/rgen/lib/rgen/fragment/fragmented_model.rb140
-rw-r--r--lib/puppet/vendor/rgen/lib/rgen/fragment/model_fragment.rb289
-rw-r--r--lib/puppet/vendor/rgen/lib/rgen/instantiator/abstract_instantiator.rb66
-rw-r--r--lib/puppet/vendor/rgen/lib/rgen/instantiator/abstract_xml_instantiator.rb66
-rw-r--r--lib/puppet/vendor/rgen/lib/rgen/instantiator/default_xml_instantiator.rb117
-rw-r--r--lib/puppet/vendor/rgen/lib/rgen/instantiator/ecore_xml_instantiator.rb169
-rw-r--r--lib/puppet/vendor/rgen/lib/rgen/instantiator/json_instantiator.rb126
-rw-r--r--lib/puppet/vendor/rgen/lib/rgen/instantiator/json_parser.rb331
-rw-r--r--lib/puppet/vendor/rgen/lib/rgen/instantiator/json_parser.y94
-rw-r--r--lib/puppet/vendor/rgen/lib/rgen/instantiator/nodebased_xml_instantiator.rb137
-rw-r--r--lib/puppet/vendor/rgen/lib/rgen/instantiator/qualified_name_resolver.rb97
-rw-r--r--lib/puppet/vendor/rgen/lib/rgen/instantiator/reference_resolver.rb128
-rw-r--r--lib/puppet/vendor/rgen/lib/rgen/instantiator/resolution_helper.rb47
-rw-r--r--lib/puppet/vendor/rgen/lib/rgen/instantiator/xmi11_instantiator.rb168
-rw-r--r--lib/puppet/vendor/rgen/lib/rgen/metamodel_builder.rb224
-rw-r--r--lib/puppet/vendor/rgen/lib/rgen/metamodel_builder/builder_extensions.rb556
-rw-r--r--lib/puppet/vendor/rgen/lib/rgen/metamodel_builder/builder_runtime.rb174
-rw-r--r--lib/puppet/vendor/rgen/lib/rgen/metamodel_builder/constant_order_helper.rb89
-rw-r--r--lib/puppet/vendor/rgen/lib/rgen/metamodel_builder/data_types.rb77
-rw-r--r--lib/puppet/vendor/rgen/lib/rgen/metamodel_builder/intermediate/annotation.rb30
-rw-r--r--lib/puppet/vendor/rgen/lib/rgen/metamodel_builder/intermediate/feature.rb168
-rw-r--r--lib/puppet/vendor/rgen/lib/rgen/metamodel_builder/mm_multiple.rb23
-rw-r--r--lib/puppet/vendor/rgen/lib/rgen/metamodel_builder/module_extension.rb42
-rw-r--r--lib/puppet/vendor/rgen/lib/rgen/model_builder.rb32
-rw-r--r--lib/puppet/vendor/rgen/lib/rgen/model_builder/builder_context.rb334
-rw-r--r--lib/puppet/vendor/rgen/lib/rgen/model_builder/model_serializer.rb225
-rw-r--r--lib/puppet/vendor/rgen/lib/rgen/model_builder/reference_resolver.rb156
-rw-r--r--lib/puppet/vendor/rgen/lib/rgen/serializer/json_serializer.rb121
-rw-r--r--lib/puppet/vendor/rgen/lib/rgen/serializer/opposite_reference_filter.rb18
-rw-r--r--lib/puppet/vendor/rgen/lib/rgen/serializer/qualified_name_provider.rb47
-rw-r--r--lib/puppet/vendor/rgen/lib/rgen/serializer/xmi11_serializer.rb116
-rw-r--r--lib/puppet/vendor/rgen/lib/rgen/serializer/xmi20_serializer.rb71
-rw-r--r--lib/puppet/vendor/rgen/lib/rgen/serializer/xml_serializer.rb98
-rw-r--r--lib/puppet/vendor/rgen/lib/rgen/template_language.rb297
-rw-r--r--lib/puppet/vendor/rgen/lib/rgen/template_language/directory_template_container.rb83
-rw-r--r--lib/puppet/vendor/rgen/lib/rgen/template_language/output_handler.rb87
-rw-r--r--lib/puppet/vendor/rgen/lib/rgen/template_language/template_container.rb234
-rw-r--r--lib/puppet/vendor/rgen/lib/rgen/template_language/template_helper.rb26
-rw-r--r--lib/puppet/vendor/rgen/lib/rgen/transformer.rb475
-rw-r--r--lib/puppet/vendor/rgen/lib/rgen/util/auto_class_creator.rb61
-rw-r--r--lib/puppet/vendor/rgen/lib/rgen/util/cached_glob.rb67
-rw-r--r--lib/puppet/vendor/rgen/lib/rgen/util/file_cache_map.rb124
-rw-r--r--lib/puppet/vendor/rgen/lib/rgen/util/file_change_detector.rb84
-rw-r--r--lib/puppet/vendor/rgen/lib/rgen/util/method_delegation.rb114
-rw-r--r--lib/puppet/vendor/rgen/lib/rgen/util/model_comparator.rb68
-rw-r--r--lib/puppet/vendor/rgen/lib/rgen/util/model_comparator_base.rb142
-rw-r--r--lib/puppet/vendor/rgen/lib/rgen/util/model_dumper.rb29
-rw-r--r--lib/puppet/vendor/rgen/lib/rgen/util/name_helper.rb42
-rw-r--r--lib/puppet/vendor/rgen/lib/rgen/util/pattern_matcher.rb329
-rw-r--r--lib/puppet/vendor/rgen/lib/transformers/ecore_to_uml13.rb79
-rw-r--r--lib/puppet/vendor/rgen/lib/transformers/uml13_to_ecore.rb127
-rw-r--r--lib/puppet/vendor/rgen/test/array_extensions_test.rb64
-rw-r--r--lib/puppet/vendor/rgen/test/ea_instantiator_test.rb35
-rw-r--r--lib/puppet/vendor/rgen/test/ea_serializer_test.rb23
-rw-r--r--lib/puppet/vendor/rgen/test/ecore_self_test.rb54
-rw-r--r--lib/puppet/vendor/rgen/test/environment_test.rb90
-rw-r--r--lib/puppet/vendor/rgen/test/json_test.rb171
-rw-r--r--lib/puppet/vendor/rgen/test/metamodel_builder_test.rb1482
-rw-r--r--lib/puppet/vendor/rgen/test/metamodel_from_ecore_test.rb57
-rw-r--r--lib/puppet/vendor/rgen/test/metamodel_order_test.rb131
-rw-r--r--lib/puppet/vendor/rgen/test/metamodel_roundtrip_test.rb98
-rw-r--r--lib/puppet/vendor/rgen/test/metamodel_roundtrip_test/TestModel.rb70
-rw-r--r--lib/puppet/vendor/rgen/test/metamodel_roundtrip_test/houseMetamodel.ecore42
-rw-r--r--lib/puppet/vendor/rgen/test/metamodel_roundtrip_test/houseMetamodel_from_ecore.rb44
-rw-r--r--lib/puppet/vendor/rgen/test/metamodel_roundtrip_test/using_builtin_types.ecore9
-rw-r--r--lib/puppet/vendor/rgen/test/method_delegation_test.rb178
-rw-r--r--lib/puppet/vendor/rgen/test/model_builder/builder_context_test.rb59
-rw-r--r--lib/puppet/vendor/rgen/test/model_builder/builder_test.rb242
-rw-r--r--lib/puppet/vendor/rgen/test/model_builder/ecore_original.rb163
-rw-r--r--lib/puppet/vendor/rgen/test/model_builder/ecore_original_regenerated.rb163
-rw-r--r--lib/puppet/vendor/rgen/test/model_builder/reference_resolver_test.rb156
-rw-r--r--lib/puppet/vendor/rgen/test/model_builder/serializer_test.rb94
-rw-r--r--lib/puppet/vendor/rgen/test/model_builder/statemachine_metamodel.rb42
-rw-r--r--lib/puppet/vendor/rgen/test/model_builder/test_model/statemachine1.rb23
-rw-r--r--lib/puppet/vendor/rgen/test/model_builder_test.rb6
-rw-r--r--lib/puppet/vendor/rgen/test/model_fragment_test.rb30
-rw-r--r--lib/puppet/vendor/rgen/test/output_handler_test.rb58
-rw-r--r--lib/puppet/vendor/rgen/test/qualified_name_provider_test.rb48
-rw-r--r--lib/puppet/vendor/rgen/test/qualified_name_resolver_test.rb102
-rw-r--r--lib/puppet/vendor/rgen/test/reference_resolver_test.rb117
-rw-r--r--lib/puppet/vendor/rgen/test/rgen_test.rb26
-rw-r--r--lib/puppet/vendor/rgen/test/template_language_test.rb163
-rw-r--r--lib/puppet/vendor/rgen/test/template_language_test/expected_result1.txt29
-rw-r--r--lib/puppet/vendor/rgen/test/template_language_test/expected_result2.txt9
-rw-r--r--lib/puppet/vendor/rgen/test/template_language_test/expected_result3.txt4
-rw-r--r--lib/puppet/vendor/rgen/test/template_language_test/indentStringTestDefaultIndent.out1
-rw-r--r--lib/puppet/vendor/rgen/test/template_language_test/indentStringTestTabIndent.out1
-rw-r--r--lib/puppet/vendor/rgen/test/template_language_test/templates/callback_indent_test/a.tpl12
-rw-r--r--lib/puppet/vendor/rgen/test/template_language_test/templates/callback_indent_test/b.tpl5
-rw-r--r--lib/puppet/vendor/rgen/test/template_language_test/templates/code/array.tpl11
-rw-r--r--lib/puppet/vendor/rgen/test/template_language_test/templates/content/author.tpl7
-rw-r--r--lib/puppet/vendor/rgen/test/template_language_test/templates/content/chapter.tpl5
-rw-r--r--lib/puppet/vendor/rgen/test/template_language_test/templates/define_local_test/local.tpl8
-rw-r--r--lib/puppet/vendor/rgen/test/template_language_test/templates/define_local_test/test.tpl8
-rw-r--r--lib/puppet/vendor/rgen/test/template_language_test/templates/evaluate_test/test.tpl7
-rw-r--r--lib/puppet/vendor/rgen/test/template_language_test/templates/indent_string_test.tpl12
-rw-r--r--lib/puppet/vendor/rgen/test/template_language_test/templates/index/c/cmod.tpl1
-rw-r--r--lib/puppet/vendor/rgen/test/template_language_test/templates/index/chapter.tpl3
-rw-r--r--lib/puppet/vendor/rgen/test/template_language_test/templates/no_backslash_r_test.tpl5
-rw-r--r--lib/puppet/vendor/rgen/test/template_language_test/templates/no_indent_test/no_indent.tpl3
-rw-r--r--lib/puppet/vendor/rgen/test/template_language_test/templates/no_indent_test/sub1/no_indent.tpl3
-rw-r--r--lib/puppet/vendor/rgen/test/template_language_test/templates/no_indent_test/test.tpl24
-rw-r--r--lib/puppet/vendor/rgen/test/template_language_test/templates/no_indent_test/test2.tpl13
-rw-r--r--lib/puppet/vendor/rgen/test/template_language_test/templates/no_indent_test/test3.tpl10
-rw-r--r--lib/puppet/vendor/rgen/test/template_language_test/templates/null_context_test.tpl17
-rw-r--r--lib/puppet/vendor/rgen/test/template_language_test/templates/root.tpl31
-rw-r--r--lib/puppet/vendor/rgen/test/template_language_test/templates/template_resolution_test/sub1.tpl9
-rw-r--r--lib/puppet/vendor/rgen/test/template_language_test/templates/template_resolution_test/sub1/sub1.tpl3
-rw-r--r--lib/puppet/vendor/rgen/test/template_language_test/templates/template_resolution_test/test.tpl4
-rw-r--r--lib/puppet/vendor/rgen/test/template_language_test/testout.txt29
-rw-r--r--lib/puppet/vendor/rgen/test/testmodel/class_model_checker.rb119
-rw-r--r--lib/puppet/vendor/rgen/test/testmodel/ea_testmodel.eapbin0 -> 1251328 bytes
-rw-r--r--lib/puppet/vendor/rgen/test/testmodel/ea_testmodel.xml1029
-rw-r--r--lib/puppet/vendor/rgen/test/testmodel/ea_testmodel_partial.xml317
-rw-r--r--lib/puppet/vendor/rgen/test/testmodel/ecore_model_checker.rb101
-rw-r--r--lib/puppet/vendor/rgen/test/testmodel/manual_testmodel.xml22
-rw-r--r--lib/puppet/vendor/rgen/test/testmodel/object_model_checker.rb67
-rw-r--r--lib/puppet/vendor/rgen/test/transformer_test.rb254
-rw-r--r--lib/puppet/vendor/rgen/test/util/file_cache_map_test.rb99
-rw-r--r--lib/puppet/vendor/rgen/test/util/pattern_matcher_test.rb97
-rw-r--r--lib/puppet/vendor/rgen/test/util_test.rb5
-rw-r--r--lib/puppet/vendor/rgen/test/xml_instantiator_test.rb160
-rw-r--r--lib/puppet/vendor/rgen/test/xml_instantiator_test/simple_ecore_model_checker.rb94
-rw-r--r--lib/puppet/vendor/rgen/test/xml_instantiator_test/simple_xmi_ecore_instantiator.rb53
-rw-r--r--lib/puppet/vendor/rgen/test/xml_instantiator_test/simple_xmi_metamodel.rb49
-rw-r--r--lib/puppet/vendor/rgen/test/xml_instantiator_test/simple_xmi_to_ecore.rb75
-rw-r--r--lib/puppet/vendor/safe_yaml/PUPPET_README.md6
-rw-r--r--lib/puppet/vendor/semantic/PUPPET_README.md6
-rw-r--r--lib/puppet/version.rb2
465 files changed, 31070 insertions, 8239 deletions
diff --git a/lib/puppet.rb b/lib/puppet.rb
index bf54dd764..2a52e9887 100644
--- a/lib/puppet.rb
+++ b/lib/puppet.rb
@@ -40,6 +40,27 @@ module Puppet
# the hash that determines how our system behaves
@@settings = Puppet::Settings.new
+ # Note: It's important that these accessors (`self.settings`, `self.[]`) are
+ # defined before we try to load any "features" (which happens a few lines below),
+ # because the implementation of the features loading may examine the values of
+ # settings.
+ def self.settings
+ @@settings
+ end
+
+ # Get the value for a setting
+ #
+ # @param [Symbol] param the setting to retrieve
+ #
+ # @api public
+ def self.[](param)
+ if param == :debug
+ return Puppet::Util::Log.level == :debug
+ else
+ return @@settings[param]
+ end
+ end
+
# The services running in this process.
@services ||= []
@@ -58,19 +79,6 @@ module Puppet
@@settings.define_settings(section, hash)
end
- # Get the value for a setting
- #
- # @param [Symbol] param the setting to retrieve
- #
- # @api public
- def self.[](param)
- if param == :debug
- return Puppet::Util::Log.level == :debug
- else
- return @@settings[param]
- end
- end
-
# setting access and stuff
def self.[]=(param,value)
@@settings[param] = value
@@ -88,11 +96,6 @@ module Puppet
end
end
- def self.settings
- @@settings
- end
-
-
def self.run_mode
# This sucks (the existence of this method); there are a lot of places in our code that branch based the value of
# "run mode", but there used to be some really confusing code paths that made it almost impossible to determine
@@ -184,14 +187,21 @@ module Puppet
loaders = Puppet::Environments::Directories.from_path(environments, modulepath)
# in case the configured environment (used for the default sometimes)
# doesn't exist
- loaders << Puppet::Environments::StaticPrivate.new(
- Puppet::Node::Environment.create(Puppet[:environment].to_sym,
- [],
- Puppet::Node::Environment::NO_MANIFEST))
+ default_environment = Puppet[:environment].to_sym
+ if default_environment == :production
+ loaders << Puppet::Environments::StaticPrivate.new(
+ Puppet::Node::Environment.create(Puppet[:environment].to_sym,
+ [],
+ Puppet::Node::Environment::NO_MANIFEST))
+ end
end
{
- :environments => Puppet::Environments::Cached.new(*loaders)
+ :environments => Puppet::Environments::Cached.new(*loaders),
+ :http_pool => proc {
+ require 'puppet/network/http'
+ Puppet::Network::HTTP::NoCachePool.new
+ }
}
end
@@ -199,7 +209,7 @@ module Puppet
# initialization where the {base_context} bindings are put in place
# @api private
def self.bootstrap_context
- root_environment = Puppet::Node::Environment.create(:'*root*', [], '')
+ root_environment = Puppet::Node::Environment.create(:'*root*', [], Puppet::Node::Environment::NO_MANIFEST)
{
:current_environment => root_environment,
:root_environment => root_environment
diff --git a/lib/puppet/application.rb b/lib/puppet/application.rb
index 9f533b12d..1d9611093 100644
--- a/lib/puppet/application.rb
+++ b/lib/puppet/application.rb
@@ -354,7 +354,19 @@ class Application
end
Puppet.push_context(Puppet.base_context(Puppet.settings), "Update for application settings (#{self.class.run_mode})")
- configured_environment = Puppet.lookup(:environments).get(Puppet[:environment])
+ # This use of configured environment is correct, this is used to establish
+ # the defaults for an application that does not override, or where an override
+ # has not been made from the command line.
+ #
+ configured_environment_name = Puppet[:environment]
+ if self.class.run_mode.name != :agent
+ configured_environment = Puppet.lookup(:environments).get(configured_environment_name)
+ if configured_environment.nil?
+ fail(Puppet::Environments::EnvironmentNotFound, configured_environment_name)
+ end
+ else
+ configured_environment = Puppet::Node::Environment.remote(configured_environment_name)
+ end
configured_environment = configured_environment.override_from_commandline(Puppet.settings)
# Setup a new context using the app's configuration
@@ -368,6 +380,7 @@ class Application
exit_on_fail("parse application options") { plugin_hook('parse_options') { parse_options } }
exit_on_fail("prepare for execution") { plugin_hook('setup') { setup } }
exit_on_fail("configure routes from #{Puppet[:route_file]}") { configure_indirector_routes }
+ exit_on_fail("log runtime debug info") { log_runtime_environment }
exit_on_fail("run") { plugin_hook('run_command') { run_command } }
end
@@ -419,6 +432,26 @@ class Application
end
end
+ # Output basic information about the runtime environment for debugging
+ # purposes.
+ #
+ # @api public
+ #
+ # @param extra_info [Hash{String => #to_s}] a flat hash of extra information
+ # to log. Intended to be passed to super by subclasses.
+ # @return [void]
+ def log_runtime_environment(extra_info=nil)
+ runtime_info = {
+ 'puppet_version' => Puppet.version,
+ 'ruby_version' => RUBY_VERSION,
+ 'run_mode' => self.class.run_mode.name,
+ }
+ runtime_info['default_encoding'] = Encoding.default_external if RUBY_VERSION >= '1.9.3'
+ runtime_info.merge!(extra_info) unless extra_info.nil?
+
+ Puppet.debug 'Runtime environment: ' + runtime_info.map{|k,v| k + '=' + v.to_s}.join(', ')
+ end
+
def parse_options
# Create an option parser
option_parser = OptionParser.new(self.class.banner)
diff --git a/lib/puppet/application/agent.rb b/lib/puppet/application/agent.rb
index 7c7c11f4e..d13a3f983 100644
--- a/lib/puppet/application/agent.rb
+++ b/lib/puppet/application/agent.rb
@@ -91,11 +91,11 @@ similar), or run interactively for testing purposes.
USAGE
-----
-puppet agent [--certname <name>] [-D|--daemonize|--no-daemonize]
- [-d|--debug] [--detailed-exitcodes] [--digest <digest>] [--disable [message]] [--enable]
- [--fingerprint] [-h|--help] [-l|--logdest syslog|<file>|console]
- [--no-client] [--noop] [-o|--onetime] [-t|--test]
- [-v|--verbose] [-V|--version] [-w|--waitforcert <seconds>]
+puppet agent [--certname <NAME>] [-D|--daemonize|--no-daemonize]
+ [-d|--debug] [--detailed-exitcodes] [--digest <DIGEST>] [--disable [MESSAGE]] [--enable]
+ [--fingerprint] [-h|--help] [-l|--logdest syslog|eventlog|<FILE>|console]
+ [--masterport <PORT>] [--no-client] [--noop] [-o|--onetime] [-t|--test]
+ [-v|--verbose] [-V|--version] [-w|--waitforcert <SECONDS>]
DESCRIPTION
@@ -226,9 +226,10 @@ generated by running puppet agent with '--genconfig'.
Print this help message
* --logdest:
- Where to send messages. Choose between syslog, the console, and a log
- file. Defaults to sending messages to syslog, or the console if
- debugging or verbosity is enabled.
+ Where to send log messages. Choose between 'syslog' (the POSIX syslog
+ service), 'eventlog' (the Windows Event Log), 'console', or the path to a log
+ file. If debugging or verbosity is enabled, this defaults to 'console'.
+ Otherwise, it defaults to 'syslog' on POSIX systems and 'eventlog' on Windows.
* --masterport:
The port on which to contact the puppet master.
diff --git a/lib/puppet/application/apply.rb b/lib/puppet/application/apply.rb
index 22f29558b..f7f85fcc5 100644
--- a/lib/puppet/application/apply.rb
+++ b/lib/puppet/application/apply.rb
@@ -1,5 +1,6 @@
require 'puppet/application'
require 'puppet/configurer'
+require 'puppet/util/profiler/aggregate'
class Puppet::Application::Apply < Puppet::Application
@@ -42,7 +43,8 @@ Applies a standalone Puppet manifest to the local system.
USAGE
-----
puppet apply [-h|--help] [-V|--version] [-d|--debug] [-v|--verbose]
- [-e|--execute] [--detailed-exitcodes] [-l|--logdest <file>] [--noop]
+ [-e|--execute] [--detailed-exitcodes] [-L|--loadclasses]
+ [-l|--logdest syslog|eventlog|<FILE>|console] [--noop]
[--catalog <catalog>] [--write-catalog-summary] <file>
@@ -93,8 +95,9 @@ configuration options can also be generated by running puppet with
all of those classes to be set in your puppet manifest.
* --logdest:
- Where to send messages. Choose between syslog, the console, and a log
- file. Defaults to sending messages to the console.
+ Where to send log messages. Choose between 'syslog' (the POSIX syslog
+ service), 'eventlog' (the Windows Event Log), 'console', or the path to a log
+ file. Defaults to 'console'.
* --noop:
Use 'noop' mode where Puppet runs in a no-op or dry-run mode. This
@@ -187,7 +190,7 @@ Copyright (c) 2011 Puppet Labs, LLC Licensed under the Apache 2.0 License
configured_environment.override_with(:manifest => manifest) :
configured_environment
- Puppet.override(:environments => Puppet::Environments::Static.new(apply_environment)) do
+ Puppet.override({:current_environment => apply_environment}, "For puppet apply") do
# Find our Node
unless node = Puppet::Node.indirection.find(Puppet[:node_name_value])
raise "Could not find node #{Puppet[:node_name_value]}"
@@ -239,6 +242,12 @@ Copyright (c) 2011 Puppet Labs, LLC Licensed under the Apache 2.0 License
exit(1)
end
end
+
+ ensure
+ if @profiler
+ Puppet::Util::Profiler.remove_profiler(@profiler)
+ @profiler.shutdown
+ end
end
# Enable all of the most common test options.
@@ -266,7 +275,7 @@ Copyright (c) 2011 Puppet Labs, LLC Licensed under the Apache 2.0 License
set_log_level
if Puppet[:profile]
- Puppet::Util::Profiler.current = Puppet::Util::Profiler::WallClock.new(Puppet.method(:debug), "apply")
+ @profiler = Puppet::Util::Profiler.add_profiler(Puppet::Util::Profiler::Aggregate.new(Puppet.method(:debug), "apply"))
end
end
diff --git a/lib/puppet/application/doc.rb b/lib/puppet/application/doc.rb
index 866619231..e45d4d0e0 100644
--- a/lib/puppet/application/doc.rb
+++ b/lib/puppet/application/doc.rb
@@ -59,12 +59,10 @@ SYNOPSIS
Generates a reference for all Puppet types. Largely meant for internal
Puppet Labs use.
-WARNING: RDoc support is only available under Ruby 1.8.7 and earlier.
-
USAGE
-----
-puppet doc [-a|--all] [-h|--help] [-o|--outputdir <rdoc-outputdir>]
+puppet doc [-a|--all] [-h|--help] [-l|--list] [-o|--outputdir <rdoc-outputdir>]
[-m|--mode text|pdf|rdoc] [-r|--reference <reference-name>]
[--charset <charset>] [<manifest-file>]
@@ -84,11 +82,6 @@ can be changed with the 'outputdir' option.
If the command is run with the name of a manifest file as an argument,
puppet doc will output a single manifest's documentation on stdout.
-WARNING: RDoc support is only available under Ruby 1.8.7 and earlier.
-The internal API used to support manifest documentation has changed
-radically in newer versions, and support is not yet available for
-using those versions of RDoc.
-
OPTIONS
-------
@@ -164,16 +157,16 @@ HELP
end
def run_command
- return[:rdoc].include?(options[:mode]) ? send(options[:mode]) : other
+ return [:rdoc].include?(options[:mode]) ? send(options[:mode]) : other
end
def rdoc
exit_code = 0
files = []
unless @manifest
- env = Puppet.lookup(:environments).get(Puppet[:environment])
+ env = Puppet.lookup(:current_environment)
files += env.modulepath
- files << ::File.dirname(env.manifest)
+ files << ::File.dirname(env.manifest) if env.manifest != Puppet::Node::Environment::NO_MANIFEST
end
files += command_line.args
Puppet.info "scanning: #{files.inspect}"
diff --git a/lib/puppet/application/master.rb b/lib/puppet/application/master.rb
index 0c6a780a7..a238ff4b5 100644
--- a/lib/puppet/application/master.rb
+++ b/lib/puppet/application/master.rb
@@ -40,8 +40,8 @@ default.
USAGE
-----
puppet master [-D|--daemonize|--no-daemonize] [-d|--debug] [-h|--help]
- [-l|--logdest <file>|console|syslog] [-v|--verbose] [-V|--version]
- [--compile <node-name>]
+ [-l|--logdest syslog|<FILE>|console] [-v|--verbose] [-V|--version]
+ [--compile <NODE-NAME>]
DESCRIPTION
@@ -82,9 +82,9 @@ generated by running puppet master with '--genconfig'.
Print this help message.
* --logdest:
- Where to send messages. Choose between syslog, the console, and a log
- file. Defaults to sending messages to syslog, or the console if
- debugging or verbosity is enabled.
+ Where to send log messages. Choose between 'syslog' (the POSIX syslog
+ service), 'console', or the path to a log file. If debugging or verbosity is
+ enabled, this defaults to 'console'. Otherwise, it defaults to 'syslog'.
* --masterport:
The port on which to listen for traffic.
@@ -162,7 +162,6 @@ Copyright (c) 2012 Puppet Labs, LLC Licensed under the Apache 2.0 License
end
def compile
- Puppet::Util::Log.newdestination :console
begin
unless catalog = Puppet::Resource::Catalog.indirection.find(options[:node])
raise "Could not compile catalog for #{options[:node]}"
@@ -202,21 +201,24 @@ Copyright (c) 2012 Puppet Labs, LLC Licensed under the Apache 2.0 License
end
def setup_logs
- # Handle the logging settings.
- if options[:debug] or options[:verbose]
- if options[:debug]
- Puppet::Util::Log.level = :debug
- else
- Puppet::Util::Log.level = :info
- end
+ set_log_level
- unless Puppet[:daemonize] or options[:rack]
+ if !options[:setdest]
+ if options[:node]
+ # We are compiling a catalog for a single node with '--compile' and logging
+ # has not already been configured via '--logdest' so log to the console.
+ Puppet::Util::Log.newdestination(:console)
+ elsif !(Puppet[:daemonize] or options[:rack])
+ # We are running a webrick master which has been explicitly foregrounded
+ # and '--logdest' has not been passed, assume users want to see logging
+ # and log to the console.
Puppet::Util::Log.newdestination(:console)
- options[:setdest] = true
+ else
+ # No explicit log destination has been given with '--logdest' and we're
+ # either a daemonized webrick master or running under rack, log to syslog.
+ Puppet::Util::Log.newdestination(:syslog)
end
end
-
- Puppet::Util::Log.newdestination(:syslog) unless options[:setdest]
end
def setup_terminuses
diff --git a/lib/puppet/application/queue.rb b/lib/puppet/application/queue.rb
index 1efca9be2..33155fc7f 100644
--- a/lib/puppet/application/queue.rb
+++ b/lib/puppet/application/queue.rb
@@ -53,7 +53,7 @@ them in order. THIS FEATURE IS DEPRECATED; use PuppetDB instead.
USAGE
-----
-puppet queue [-d|--debug] [-v|--verbose]
+puppet queue [-d|--debug] [--help] [-v|--verbose] [--version]
DESCRIPTION
diff --git a/lib/puppet/application/resource.rb b/lib/puppet/application/resource.rb
index c03181671..4a3a4491a 100644
--- a/lib/puppet/application/resource.rb
+++ b/lib/puppet/application/resource.rb
@@ -6,7 +6,6 @@ class Puppet::Application::Resource < Puppet::Application
def preinit
@extra_params = []
- Facter.loadfacts
end
option("--debug","-d")
diff --git a/lib/puppet/configurer.rb b/lib/puppet/configurer.rb
index 4638f611c..bfd4b518b 100644
--- a/lib/puppet/configurer.rb
+++ b/lib/puppet/configurer.rb
@@ -8,9 +8,9 @@ require 'securerandom'
class Puppet::Configurer
require 'puppet/configurer/fact_handler'
require 'puppet/configurer/plugin_handler'
+ require 'puppet/configurer/downloader_factory'
include Puppet::Configurer::FactHandler
- include Puppet::Configurer::PluginHandler
# For benchmarking
include Puppet::Util
@@ -44,13 +44,14 @@ class Puppet::Configurer
end
end
- def initialize
+ def initialize(factory = Puppet::Configurer::DownloaderFactory.new)
Puppet.settings.use(:main, :ssl, :agent)
@running = false
@splayed = false
@environment = Puppet[:environment]
@transaction_uuid = SecureRandom.uuid
+ @handler = Puppet::Configurer::PluginHandler.new(factory)
end
# Get the remote catalog, yo. Returns nil if no catalog can be found.
@@ -125,6 +126,17 @@ class Puppet::Configurer
# This just passes any options on to the catalog,
# which accepts :tags and :ignoreschedules.
def run(options = {})
+ pool = Puppet::Network::HTTP::Pool.new(Puppet[:http_keepalive_timeout])
+ begin
+ Puppet.override(:http_pool => pool) do
+ run_internal(options)
+ end
+ ensure
+ pool.close
+ end
+ end
+
+ def run_internal(options)
# We create the report pre-populated with default settings for
# environment and transaction_uuid very early, this is to ensure
# they are sent regardless of any catalog compilation failures or
@@ -146,6 +158,13 @@ class Puppet::Configurer
if node = Puppet::Node.indirection.find(Puppet[:node_name_value],
:environment => @environment, :ignore_cache => true, :transaction_uuid => @transaction_uuid,
:fail_on_404 => true)
+
+ # If we have deserialized a node from a rest call, we want to set
+ # an environment instance as a simple 'remote' environment reference.
+ if !node.has_environment_instance? && node.environment_name
+ node.environment = Puppet::Node::Environment.remote(node.environment_name)
+ end
+
if node.environment.to_s != @environment
Puppet.warning "Local environment: \"#{@environment}\" doesn't match server specified node environment \"#{node.environment}\", switching agent to \"#{node.environment}\"."
@environment = node.environment.to_s
@@ -161,6 +180,18 @@ class Puppet::Configurer
end
end
+ current_environment = Puppet.lookup(:current_environment)
+ local_node_environment =
+ if current_environment.name == @environment.intern
+ current_environment
+ else
+ Puppet::Node::Environment.create(@environment,
+ current_environment.modulepath,
+ current_environment.manifest,
+ current_environment.config_version)
+ end
+ Puppet.push_context({:current_environment => local_node_environment}, "Local node environment for configurer transaction")
+
query_options = get_facts(options) unless query_options
# get_facts returns nil during puppet apply
@@ -204,7 +235,9 @@ class Puppet::Configurer
Puppet::Util::Log.close(report)
send_report(report)
+ Puppet.pop_context
end
+ private :run_internal
def send_report(report)
puts report.summary if Puppet[:summarize]
@@ -263,4 +296,8 @@ class Puppet::Configurer
Puppet.log_exception(detail, "Could not retrieve catalog from remote server: #{detail}")
return nil
end
+
+ def download_plugins(remote_environment_for_plugins)
+ @handler.download_plugins(remote_environment_for_plugins)
+ end
end
diff --git a/lib/puppet/configurer/downloader.rb b/lib/puppet/configurer/downloader.rb
index 13424d6e1..319546b03 100644
--- a/lib/puppet/configurer/downloader.rb
+++ b/lib/puppet/configurer/downloader.rb
@@ -10,23 +10,20 @@ class Puppet::Configurer::Downloader
files = []
begin
- ::Timeout.timeout(Puppet[:configtimeout]) do
- catalog.apply do |trans|
- trans.changed?.find_all do |resource|
- yield resource if block_given?
- files << resource[:path]
- end
+ catalog.apply do |trans|
+ trans.changed?.each do |resource|
+ yield resource if block_given?
+ files << resource[:path]
end
end
- rescue Puppet::Error, Timeout::Error => detail
+ rescue Puppet::Error => detail
Puppet.log_exception(detail, "Could not retrieve #{name}: #{detail}")
end
-
files
end
- def initialize(name, path, source, ignore = nil, environment = nil)
- @name, @path, @source, @ignore, @environment = name, path, source, ignore, environment
+ def initialize(name, path, source, ignore = nil, environment = nil, source_permissions = :ignore)
+ @name, @path, @source, @ignore, @environment, @source_permissions = name, path, source, ignore, environment, source_permissions
end
def catalog
@@ -44,14 +41,12 @@ class Puppet::Configurer::Downloader
private
- require 'sys/admin' if Puppet.features.microsoft_windows?
-
def default_arguments
defargs = {
:path => path,
:recurse => true,
:source => source,
- :source_permissions => :ignore,
+ :source_permissions => @source_permissions,
:tag => name,
:purge => true,
:force => true,
diff --git a/lib/puppet/configurer/downloader_factory.rb b/lib/puppet/configurer/downloader_factory.rb
new file mode 100644
index 000000000..65aac8b3d
--- /dev/null
+++ b/lib/puppet/configurer/downloader_factory.rb
@@ -0,0 +1,34 @@
+require 'puppet/configurer'
+
+# Factory for <tt>Puppet::Configurer::Downloader</tt> objects.
+#
+# Puppet's pluginsync facilities can be used to download modules
+# and external facts, each with a different destination directory
+# and related settings.
+#
+# @api private
+#
+class Puppet::Configurer::DownloaderFactory
+ def create_plugin_downloader(environment)
+ plugin_downloader = Puppet::Configurer::Downloader.new(
+ "plugin",
+ Puppet[:plugindest],
+ Puppet[:pluginsource],
+ Puppet[:pluginsignore],
+ environment
+ )
+ end
+
+ def create_plugin_facts_downloader(environment)
+ source_permissions = Puppet.features.microsoft_windows? ? :ignore : :use
+
+ plugin_fact_downloader = Puppet::Configurer::Downloader.new(
+ "pluginfacts",
+ Puppet[:pluginfactdest],
+ Puppet[:pluginfactsource],
+ Puppet[:pluginsignore],
+ environment,
+ source_permissions
+ )
+ end
+end
diff --git a/lib/puppet/configurer/plugin_handler.rb b/lib/puppet/configurer/plugin_handler.rb
index 2e14fdb3c..908e49dfd 100644
--- a/lib/puppet/configurer/plugin_handler.rb
+++ b/lib/puppet/configurer/plugin_handler.rb
@@ -1,25 +1,20 @@
# Break out the code related to plugins. This module is
# just included into the agent, but having it here makes it
# easier to test.
-module Puppet::Configurer::PluginHandler
+require 'puppet/configurer'
+
+class Puppet::Configurer::PluginHandler
+ def initialize(factory)
+ @factory = factory
+ end
+
# Retrieve facts from the central server.
def download_plugins(environment)
- plugin_downloader = Puppet::Configurer::Downloader.new(
- "plugin",
- Puppet[:plugindest],
- Puppet[:pluginsource],
- Puppet[:pluginsignore],
- environment
- )
+ plugin_downloader = @factory.create_plugin_downloader(environment)
+
if Puppet.features.external_facts?
- plugin_fact_downloader = Puppet::Configurer::Downloader.new(
- "pluginfacts",
- Puppet[:pluginfactdest],
- Puppet[:pluginfactsource],
- Puppet[:pluginsignore],
- environment
- )
- plugin_fact_downloader.evaluate
+ plugin_fact_downloader = @factory.create_plugin_facts_downloader(environment)
+ plugin_fact_downloader.evaluate
end
plugin_downloader.evaluate
diff --git a/lib/puppet/defaults.rb b/lib/puppet/defaults.rb
index 4dcc07f06..b2cb92975 100644
--- a/lib/puppet/defaults.rb
+++ b/lib/puppet/defaults.rb
@@ -16,6 +16,21 @@ module Puppet
AS_DURATION = %q{This setting can be a time interval in seconds (30 or 30s), minutes (30m), hours (6h), days (2d), or years (5y).}
STORECONFIGS_ONLY = %q{This setting is only used by the ActiveRecord storeconfigs and inventory backends, which are deprecated.}
+ # This is defined first so that the facter implementation is replaced before other setting defaults are evaluated.
+ define_settings(:main,
+ :cfacter => {
+ :default => false,
+ :type => :boolean,
+ :desc => 'Whether or not to use the native facter (cfacter) implementation instead of the Ruby one (facter). Defaults to false.',
+ :hook => proc do |value|
+ return unless value
+ raise ArgumentError, 'facter has already evaluated facts.' if Facter.instance_variable_get(:@collection)
+ raise ArgumentError, 'cfacter version 0.2.0 or later is not installed.' unless Puppet.features.cfacter?
+ CFacter.initialize
+ end
+ }
+ )
+
define_settings(:main,
:confdir => {
:default => nil,
@@ -48,7 +63,7 @@ module Puppet
:logdir => {
:default => nil,
:type => :directory,
- :mode => 0750,
+ :mode => "0750",
:owner => "service",
:group => "service",
:desc => "The directory in which to store log files",
@@ -57,7 +72,39 @@ module Puppet
:default => 'notice',
:type => :enum,
:values => ["debug","info","notice","warning","err","alert","emerg","crit"],
- :desc => "Default logging level",
+ :desc => "Default logging level for messages from Puppet. Allowed values are:
+
+ * debug
+ * info
+ * notice
+ * warning
+ * err
+ * alert
+ * emerg
+ * crit
+ ",
+ },
+ :disable_warnings => {
+ :default => [],
+ :type => :array,
+ :desc => "A comma-separated list of warning types to suppress. If large numbers
+ of warnings are making Puppet's logs too large or difficult to use, you
+ can temporarily silence them with this setting.
+
+ If you are preparing to upgrade Puppet to a new major version, you
+ should re-enable all warnings for a while.
+
+ Valid values for this setting are:
+
+ * `deprecations` --- disables deprecation warnings.",
+ :hook => proc do |value|
+ values = munge(value)
+ valid = %w[deprecations]
+ invalid = values - (values & valid)
+ if not invalid.empty?
+ raise ArgumentError, "Cannot disable unrecognized warning types #{invalid.inspect}. Valid values are #{valid.inspect}."
+ end
+ end
}
)
@@ -96,7 +143,7 @@ module Puppet
:statedir => {
:default => "$vardir/state",
:type => :directory,
- :mode => 01755,
+ :mode => "01755",
:desc => "The directory where Puppet state is stored. Generally,
this directory can be removed without causing harm (although it
might result in spurious service restarts)."
@@ -104,7 +151,7 @@ module Puppet
:rundir => {
:default => nil,
:type => :directory,
- :mode => 0755,
+ :mode => "0755",
:owner => "service",
:group => "service",
:desc => "Where Puppet PID files are kept."
@@ -213,8 +260,27 @@ module Puppet
http://docs.puppetlabs.com/puppet/latest/reference/environments.html",
:type => :path,
},
+ :always_cache_features => {
+ :type => :boolean,
+ :default => false,
+ :desc => <<-'EOT'
+ Affects how we cache attempts to load Puppet 'features'. If false, then
+ calls to `Puppet.features.<feature>?` will always attempt to load the
+ feature (which can be an expensive operation) unless it has already been
+ loaded successfully. This makes it possible for a single agent run to,
+ e.g., install a package that provides the underlying capabilities for
+ a feature, and then later load that feature during the same run (even if
+ the feature had been tested earlier and had not been available).
+
+ If this setting is set to true, then features will only be checked once,
+ and if they are not available, the negative result is cached and returned
+ for all subsequent attempts to load the feature. This behavior is almost
+ always appropriate for the server, and can result in a significant performance
+ improvement for features that are checked frequently.
+ EOT
+ },
:diff_args => {
- :default => default_diffargs,
+ :default => lambda { default_diffargs },
:desc => "Which arguments to pass to the diff command when printing differences between
files. The command to use can be chosen with the `diff` setting.",
},
@@ -336,18 +402,48 @@ module Puppet
:default => "$logdir/http.log",
:type => :file,
:owner => "root",
- :mode => 0640,
+ :mode => "0640",
:desc => "Where the puppet agent web server logs.",
},
:http_proxy_host => {
:default => "none",
:desc => "The HTTP proxy host to use for outgoing connections. Note: You
- may need to use a FQDN for the server hostname when using a proxy.",
+ may need to use a FQDN for the server hostname when using a proxy. Environment variable
+ http_proxy or HTTP_PROXY will override this value",
},
:http_proxy_port => {
:default => 3128,
:desc => "The HTTP proxy port to use for outgoing connections",
},
+ :http_proxy_user => {
+ :default => "none",
+ :desc => "The user name for an authenticated HTTP proxy. Requires the `http_proxy_host` setting.",
+ },
+ :http_proxy_password =>{
+ :default => "none",
+ :hook => proc do |value|
+ if Puppet.settings[:http_proxy_password] =~ /[@!# \/]/
+ raise "Passwords set in the http_proxy_password setting must be valid as part of a URL, and any reserved characters must be URL-encoded. We received: #{value}"
+ end
+ end,
+ :desc => "The password for the user of an authenticated HTTP proxy.
+ Requires the `http_proxy_user` setting.
+
+ Note that passwords must be valid when used as part of a URL. If a password
+ contains any characters with special meanings in URLs (as specified by RFC 3986
+ section 2.2), they must be URL-encoded. (For example, `#` would become `%23`.)",
+ },
+ :http_keepalive_timeout => {
+ :default => "4s",
+ :type => :duration,
+ :desc => "The maximum amount of time a persistent HTTP connection can remain idle in the connection pool, before it is closed. This timeout should be shorter than the keepalive timeout used on the HTTP server, e.g. Apache KeepAliveTimeout directive.
+ #{AS_DURATION}"
+ },
+ :http_debug => {
+ :default => false,
+ :type => :boolean,
+ :desc => "Whether to write HTTP request and responses to stderr. This should never be used in a production environment."
+ },
:filetimeout => {
:default => "15s",
:type => :duration,
@@ -356,10 +452,12 @@ module Puppet
a file (such as manifests or templates) has changed on disk. #{AS_DURATION}",
},
:environment_timeout => {
- :default => "5s",
+ :default => "3m",
:type => :ttl,
- :desc => "The time to live for a cached environment. The time is either given #{AS_DURATION}, or
- the word 'unlimited' which causes the environment to be cached until the master is restarted."
+ :desc => "The time to live for a cached environment.
+ #{AS_DURATION}
+ This setting can also be set to `unlimited`, which causes the environment to
+ be cached until the master is restarted."
},
:queue_type => {
:default => "stomp",
@@ -414,6 +512,7 @@ module Puppet
Setting a global value for config_version in puppet.conf is deprecated. Please set a
per-environment value in environment.conf instead. For more info, see
http://docs.puppetlabs.com/puppet/latest/reference/environments.html",
+ :deprecated => :allowed_on_commandline,
},
:zlib => {
:default => true,
@@ -442,7 +541,7 @@ module Puppet
:default => true,
:type => :boolean,
:desc => "Flatten fact values to strings using #to_s. Means you can't have arrays or
- hashes as fact values.",
+ hashes as fact values. (DEPRECATED) This option will be removed in Puppet 4.0.",
},
:trusted_node_data => {
:default => false,
@@ -468,6 +567,14 @@ module Puppet
:module_skeleton_dir => {
:default => '$module_working_dir/skeleton',
:desc => "The directory which the skeleton for module tool generate is stored.",
+ },
+ :forge_authorization => {
+ :default => nil,
+ :desc => "The authorization key to connect to the Puppet Forge. Leave blank for unauthorized or license based connections",
+ },
+ :module_groups => {
+ :default => nil,
+ :desc => "Extra module groups to request from the Puppet Forge",
}
)
@@ -477,9 +584,30 @@ module Puppet
# We have to downcase the fqdn, because the current ssl stuff (as oppsed to in master) doesn't have good facilities for
# manipulating naming.
:certname => {
- :default => Puppet::Settings.default_certname.downcase, :desc => "The name to use when handling certificates. Defaults
- to the fully qualified domain name.",
- :call_hook => :on_define_and_write, # Call our hook with the default value, so we're always downcased
+ :default => lambda { Puppet::Settings.default_certname.downcase },
+ :desc => "The name to use when handling certificates. When a node
+ requests a certificate from the CA puppet master, it uses the value of the
+ `certname` setting as its requested Subject CN.
+
+ This is the name used when managing a node's permissions in
+ [auth.conf](http://docs.puppetlabs.com/puppet/latest/reference/config_file_auth.html).
+ In most cases, it is also used as the node's name when matching
+ [node definitions](http://docs.puppetlabs.com/puppet/latest/reference/lang_node_definitions.html)
+ and requesting data from an ENC. (This can be changed with the `node_name_value`
+ and `node_name_fact` settings, although you should only do so if you have
+ a compelling reason.)
+
+ A node's certname is available in Puppet manifests as `$trusted['certname']`. (See
+ [Facts and Built-In Variables](http://docs.puppetlabs.com/puppet/latest/reference/lang_facts_and_builtin_vars.html)
+ for more details.)
+
+ * For best compatibility, you should limit the value of `certname` to
+ only use letters, numbers, periods, underscores, and dashes. (That is,
+ it should match `/\A[a-z0-9._-]+\Z/`.)
+ * The special value `ca` is reserved, and can't be used as the certname
+ for a normal node.
+
+ Defaults to the node's fully qualified domain name.",
:hook => proc { |value| raise(ArgumentError, "Certificate names must be lower case; see #1168") unless value == value.downcase }},
:certdnsnames => {
:default => '',
@@ -566,7 +694,7 @@ EOT
:certdir => {
:default => "$ssldir/certs",
:type => :directory,
- :mode => 0755,
+ :mode => "0755",
:owner => "service",
:group => "service",
:desc => "The certificate directory."
@@ -574,7 +702,7 @@ EOT
:ssldir => {
:default => "$confdir/ssl",
:type => :directory,
- :mode => 0771,
+ :mode => "0771",
:owner => "service",
:group => "service",
:desc => "Where SSL certificates are kept."
@@ -582,7 +710,7 @@ EOT
:publickeydir => {
:default => "$ssldir/public_keys",
:type => :directory,
- :mode => 0755,
+ :mode => "0755",
:owner => "service",
:group => "service",
:desc => "The public key directory."
@@ -590,7 +718,7 @@ EOT
:requestdir => {
:default => "$ssldir/certificate_requests",
:type => :directory,
- :mode => 0755,
+ :mode => "0755",
:owner => "service",
:group => "service",
:desc => "Where host certificate requests are stored."
@@ -598,7 +726,7 @@ EOT
:privatekeydir => {
:default => "$ssldir/private_keys",
:type => :directory,
- :mode => 0750,
+ :mode => "0750",
:owner => "service",
:group => "service",
:desc => "The private key directory."
@@ -606,7 +734,7 @@ EOT
:privatedir => {
:default => "$ssldir/private",
:type => :directory,
- :mode => 0750,
+ :mode => "0750",
:owner => "service",
:group => "service",
:desc => "Where the client stores private certificate information."
@@ -614,7 +742,7 @@ EOT
:passfile => {
:default => "$privatedir/password",
:type => :file,
- :mode => 0640,
+ :mode => "0640",
:owner => "service",
:group => "service",
:desc => "Where puppet agent stores the password for its private key.
@@ -623,7 +751,7 @@ EOT
:hostcsr => {
:default => "$ssldir/csr_$certname.pem",
:type => :file,
- :mode => 0644,
+ :mode => "0644",
:owner => "service",
:group => "service",
:desc => "Where individual hosts store and look for their certificate requests."
@@ -631,7 +759,7 @@ EOT
:hostcert => {
:default => "$certdir/$certname.pem",
:type => :file,
- :mode => 0644,
+ :mode => "0644",
:owner => "service",
:group => "service",
:desc => "Where individual hosts store and look for their certificates."
@@ -639,7 +767,7 @@ EOT
:hostprivkey => {
:default => "$privatekeydir/$certname.pem",
:type => :file,
- :mode => 0640,
+ :mode => "0640",
:owner => "service",
:group => "service",
:desc => "Where individual hosts store and look for their private key."
@@ -647,7 +775,7 @@ EOT
:hostpubkey => {
:default => "$publickeydir/$certname.pem",
:type => :file,
- :mode => 0644,
+ :mode => "0644",
:owner => "service",
:group => "service",
:desc => "Where individual hosts store and look for their public key."
@@ -655,14 +783,14 @@ EOT
:localcacert => {
:default => "$certdir/ca.pem",
:type => :file,
- :mode => 0644,
+ :mode => "0644",
:owner => "service",
:group => "service",
:desc => "Where each client stores the CA certificate."
},
:ssl_client_ca_auth => {
:type => :file,
- :mode => 0644,
+ :mode => "0644",
:owner => "service",
:group => "service",
:desc => "Certificate authorities who issue server certificates. SSL servers will not be
@@ -672,7 +800,7 @@ EOT
},
:ssl_server_ca_auth => {
:type => :file,
- :mode => 0644,
+ :mode => "0644",
:owner => "service",
:group => "service",
:desc => "Certificate authorities who issue client certificates. SSL clients will not be
@@ -683,7 +811,7 @@ EOT
:hostcrl => {
:default => "$ssldir/crl.pem",
:type => :file,
- :mode => 0644,
+ :mode => "0644",
:owner => "service",
:group => "service",
:desc => "Where the host's certificate revocation list can be found.
@@ -722,7 +850,7 @@ EOT
:type => :directory,
:owner => "service",
:group => "service",
- :mode => 0755,
+ :mode => "0755",
:desc => "The root directory for the certificate authority."
},
:cacert => {
@@ -730,7 +858,7 @@ EOT
:type => :file,
:owner => "service",
:group => "service",
- :mode => 0644,
+ :mode => "0644",
:desc => "The CA certificate."
},
:cakey => {
@@ -738,7 +866,7 @@ EOT
:type => :file,
:owner => "service",
:group => "service",
- :mode => 0640,
+ :mode => "0640",
:desc => "The CA private key."
},
:capub => {
@@ -746,7 +874,7 @@ EOT
:type => :file,
:owner => "service",
:group => "service",
- :mode => 0644,
+ :mode => "0644",
:desc => "The CA public key."
},
:cacrl => {
@@ -754,7 +882,7 @@ EOT
:type => :file,
:owner => "service",
:group => "service",
- :mode => 0644,
+ :mode => "0644",
:desc => "The certificate revocation list (CRL) for the CA. Will be used if present but otherwise ignored.",
},
:caprivatedir => {
@@ -762,7 +890,7 @@ EOT
:type => :directory,
:owner => "service",
:group => "service",
- :mode => 0750,
+ :mode => "0750",
:desc => "Where the CA stores private certificate information."
},
:csrdir => {
@@ -770,7 +898,7 @@ EOT
:type => :directory,
:owner => "service",
:group => "service",
- :mode => 0755,
+ :mode => "0755",
:desc => "Where the CA stores certificate requests"
},
:signeddir => {
@@ -778,7 +906,7 @@ EOT
:type => :directory,
:owner => "service",
:group => "service",
- :mode => 0755,
+ :mode => "0755",
:desc => "Where the CA stores signed certificates."
},
:capass => {
@@ -786,7 +914,7 @@ EOT
:type => :file,
:owner => "service",
:group => "service",
- :mode => 0640,
+ :mode => "0640",
:desc => "Where the CA stores the password for the private key."
},
:serial => {
@@ -794,7 +922,7 @@ EOT
:type => :file,
:owner => "service",
:group => "service",
- :mode => 0644,
+ :mode => "0644",
:desc => "Where the serial number for certificates is stored."
},
:autosign => {
@@ -848,7 +976,7 @@ EOT
:cert_inventory => {
:default => "$cadir/inventory.txt",
:type => :file,
- :mode => 0644,
+ :mode => "0644",
:owner => "service",
:group => "service",
:desc => "The inventory file. This is a text file to which the CA writes a
@@ -897,7 +1025,8 @@ EOT
:type => :directory,
:desc => "Used to build the default value of the `manifest` setting. Has no other purpose.
- This setting is deprecated."
+ This setting is deprecated.",
+ :deprecated => :completely,
},
:manifest => {
:default => "$manifestdir/site.pp",
@@ -911,6 +1040,45 @@ EOT
environment's `manifests` directory as the main manifest, you can set
`manifest` in environment.conf. For more info, see
http://docs.puppetlabs.com/puppet/latest/reference/environments.html",
+ :deprecated => :allowed_on_commandline,
+ },
+ :default_manifest => {
+ :default => "./manifests",
+ :type => :string,
+ :desc => "The default main manifest for directory environments. Any environment that
+ doesn't set the `manifest` setting in its `environment.conf` file will use
+ this manifest.
+
+ This setting's value can be an absolute or relative path. An absolute path
+ will make all environments default to the same main manifest; a relative
+ path will allow each environment to use its own manifest, and Puppet will
+ resolve the path relative to each environment's main directory.
+
+ In either case, the path can point to a single file or to a directory of
+ manifests to be evaluated in alphabetical order.",
+ :hook => proc do |value|
+ uninterpolated_value = self.value(true)
+ if uninterpolated_value =~ /\$environment/ || value =~ /\$environment/ then
+ raise(Puppet::Settings::ValidationError,
+ "You cannot interpolate '$environment' within the 'default_manifest' setting.")
+ end
+ end
+ },
+ :disable_per_environment_manifest => {
+ :default => false,
+ :type => :boolean,
+ :desc => "Whether to disallow an environment-specific main manifest. When set
+ to `true`, Puppet will use the manifest specified in the `default_manifest` setting
+ for all environments. If an environment specifies a different main manifest in its
+ `environment.conf` file, catalog requests for that environment will fail with an error.
+
+ This setting requires `default_manifest` to be set to an absolute path.",
+ :hook => proc do |value|
+ if value && !Pathname.new(Puppet[:default_manifest]).absolute?
+ raise(Puppet::Settings::ValidationError,
+ "The 'default_manifest' setting must be set to an absolute path when 'disable_per_environment_manifest' is true")
+ end
+ end,
},
:code => {
:default => "",
@@ -923,18 +1091,25 @@ EOT
:type => :file,
:owner => "service",
:group => "service",
- :mode => 0660,
- :desc => "Where puppet master logs. This is generally not used,
- since syslog is the default log destination."
+ :mode => "0660",
+ :desc => "This file is literally never used, although Puppet may create it
+ as an empty file. For more context, see the `puppetdlog` setting and
+ puppet master's `--logdest` command line option.
+
+ This setting is deprecated and will be removed in a future version of Puppet.",
+ :deprecated => :completely
},
:masterhttplog => {
:default => "$logdir/masterhttp.log",
:type => :file,
:owner => "service",
:group => "service",
- :mode => 0660,
+ :mode => "0660",
:create => true,
- :desc => "Where the puppet master web server logs."
+ :desc => "Where the puppet master web server saves its access log. This is
+ only used when running a WEBrick puppet master. When puppet master is
+ running under a Rack server like Passenger, that web server will have
+ its own logging behavior."
},
:masterport => {
:default => 8140,
@@ -954,7 +1129,7 @@ EOT
:bucketdir => {
:default => "$vardir/bucket",
:type => :directory,
- :mode => 0750,
+ :mode => "0750",
:owner => "service",
:group => "service",
:desc => "Where FileBucket files are stored."
@@ -998,6 +1173,7 @@ EOT
default modulepath of `<ACTIVE ENVIRONMENT'S MODULES DIR>:$basemodulepath`,
you can set `modulepath` in environment.conf. For more info, see
http://docs.puppetlabs.com/puppet/latest/reference/environments.html",
+ :deprecated => :allowed_on_commandline,
},
:ssl_client_header => {
:default => "HTTP_X_CLIENT_DN",
@@ -1031,14 +1207,14 @@ EOT
:type => :directory,
:owner => "service",
:group => "service",
- :mode => "750",
+ :mode => "0750",
:desc => "The directory in which YAML data is stored, usually in a subdirectory."},
:server_datadir => {
:default => "$vardir/server_data",
:type => :directory,
:owner => "service",
:group => "service",
- :mode => "750",
+ :mode => "0750",
:desc => "The directory in which serialized data is stored, usually in a subdirectory."},
:reports => {
:default => "store",
@@ -1059,7 +1235,7 @@ EOT
:reportdir => {
:default => "$vardir/reports",
:type => :directory,
- :mode => 0750,
+ :mode => "0750",
:owner => "service",
:group => "service",
:desc => "The directory in which to store reports. Each node gets
@@ -1089,7 +1265,7 @@ EOT
:rrddir => {
:type => :directory,
:default => "$vardir/rrd",
- :mode => 0750,
+ :mode => "0750",
:owner => "service",
:group => "service",
:desc => "The directory where RRD database files are stored.
@@ -1108,7 +1284,7 @@ EOT
:devicedir => {
:default => "$vardir/devices",
:type => :directory,
- :mode => "750",
+ :mode => "0750",
:desc => "The root directory of devices' $vardir.",
},
:deviceconfig => {
@@ -1143,13 +1319,13 @@ EOT
:default => "$statedir/localconfig",
:type => :file,
:owner => "root",
- :mode => 0660,
+ :mode => "0660",
:desc => "Where puppet agent caches the local configuration. An
extension indicating the cache format is added automatically."},
:statefile => {
:default => "$statedir/state.yaml",
:type => :file,
- :mode => 0660,
+ :mode => "0660",
:desc => "Where puppet agent and puppet master store state associated
with the running configuration. In the case of puppet master,
this file reflects the state discovered through interacting
@@ -1158,20 +1334,20 @@ EOT
:clientyamldir => {
:default => "$vardir/client_yaml",
:type => :directory,
- :mode => "750",
+ :mode => "0750",
:desc => "The directory in which client-side YAML data is stored."
},
:client_datadir => {
:default => "$vardir/client_data",
:type => :directory,
- :mode => "750",
+ :mode => "0750",
:desc => "The directory in which serialized data is stored on the client."
},
:classfile => {
:default => "$statedir/classes.txt",
:type => :file,
:owner => "root",
- :mode => 0640,
+ :mode => "0640",
:desc => "The file in which puppet agent stores a list of the classes
associated with the retrieved configuration. Can be loaded in
the separate `puppet` executable using the `--loadclasses`
@@ -1180,15 +1356,26 @@ EOT
:default => "$statedir/resources.txt",
:type => :file,
:owner => "root",
- :mode => 0640,
+ :mode => "0640",
:desc => "The file in which puppet agent stores a list of the resources
associated with the retrieved configuration." },
:puppetdlog => {
:default => "$logdir/puppetd.log",
:type => :file,
:owner => "root",
- :mode => 0640,
- :desc => "The log file for puppet agent. This is generally not used."
+ :mode => "0640",
+ :desc => "The fallback log file. This is only used when the `--logdest` option
+ is not specified AND Puppet is running on an operating system where both
+ the POSIX syslog service and the Windows Event Log are unavailable. (Currently,
+ no supported operating systems match that description.)
+
+ Despite the name, both puppet agent and puppet master will use this file
+ as the fallback logging destination.
+
+ For control over logging destinations, see the `--logdest` command line
+ option in the manual pages for puppet master, puppet agent, and puppet
+ apply. You can see man pages by running `puppet <SUBCOMMAND> --help`,
+ or read them online at http://docs.puppetlabs.com/references/latest/man/."
},
:server => {
:default => "puppet",
@@ -1200,7 +1387,7 @@ EOT
:desc => "Whether the server will search for SRV records in DNS for the current domain.",
},
:srv_domain => {
- :default => "#{Puppet::Settings.domain_fact}",
+ :default => lambda { Puppet::Settings.domain_fact },
:desc => "The domain which will be queried to find the SRV records of servers to use.",
},
:ignoreschedules => {
@@ -1389,7 +1576,7 @@ EOT
:clientbucketdir => {
:default => "$vardir/clientbucket",
:type => :directory,
- :mode => 0750,
+ :mode => "0750",
:desc => "Where FileBucket files are stored locally."
},
:configtimeout => {
@@ -1423,13 +1610,13 @@ EOT
:lastrunfile => {
:default => "$statedir/last_run_summary.yaml",
:type => :file,
- :mode => 0644,
+ :mode => "0644",
:desc => "Where puppet agent stores the last run report summary in yaml format."
},
:lastrunreport => {
:default => "$statedir/last_run_report.yaml",
:type => :file,
- :mode => 0640,
+ :mode => "0640",
:desc => "Where puppet agent stores the last run report in yaml format."
},
:graph => {
@@ -1575,7 +1762,7 @@ EOT
},
:reportfrom => {
- :default => "report@" + [Facter["hostname"].value,Facter["domain"].value].join("."),
+ :default => lambda { "report@#{Puppet::Settings.default_certname.downcase}" },
:desc => "The 'from' email address for the reports.",
},
@@ -1588,7 +1775,7 @@ EOT
:desc => "The TCP port through which to send email reports.",
},
:smtphelo => {
- :default => Facter["fqdn"].value,
+ :default => lambda { Facter.value 'fqdn' },
:desc => "The name by which we identify ourselves in SMTP HELO for reports.
If you send to a smtpserver which does strict HELO checking (as with Postfix's
`smtpd_helo_restrictions` access controls), you may need to ensure this resolves.",
@@ -1600,7 +1787,7 @@ EOT
:dblocation => {
:default => "$statedir/clientconfigs.sqlite3",
:type => :file,
- :mode => 0660,
+ :mode => "0660",
:owner => "service",
:group => "service",
:desc => "The sqlite database file. #{STORECONFIGS_ONLY}"
@@ -1651,7 +1838,7 @@ EOT
:railslog => {
:default => "$logdir/rails.log",
:type => :file,
- :mode => 0600,
+ :mode => "0600",
:owner => "service",
:group => "service",
:desc => "Where Rails-specific logs are sent. #{STORECONFIGS_ONLY}"
@@ -1825,7 +2012,8 @@ EOT
:desc => "Where Puppet looks for template files. Can be a list of colon-separated
directories.
- This setting is deprecated. Please put your templates in modules instead."
+ This setting is deprecated. Please put your templates in modules instead.",
+ :deprecated => :completely,
},
:allow_variables_with_dashes => {
@@ -1849,7 +2037,7 @@ EOT
language/'.pp' files). Available choices are `current` (the default)
and `future`.
- The `curent` parser means that the released version of the parser should
+ The `current` parser means that the released version of the parser should
be used.
The `future` parser is a "time travel to the future" allowing early
@@ -1859,72 +2047,28 @@ EOT
Available Since Puppet 3.2.
EOT
},
- :evaluator => {
- :default => "future",
- :hook => proc do |value|
- if !['future', 'current'].include?(value)
- raise "evaluator can only be set to 'future' or 'current', got '#{value}'"
- end
- end,
- :desc => <<-'EOT'
- Which evaluator to use when compiling Puppet manifests. Valid values
- are `current` and `future` (the default).
-
- **Note:** This setting is only used when `parser = future`. It allows
- testers to turn off the `future` evaluator when doing detailed tests and
- comparisons of the new compilation system.
-
- Evaluation is the second stage of catalog compilation. After the parser
- converts a manifest to a model of expressions, the evaluator processes
- each expression. (For example, a resource declaration signals the
- evaluator to add a resource to the catalog).
-
- The `future` parser and evaluator are slated to become default in Puppet
- 4. Their purpose is to add new features and improve consistency
- and reliability.
-
- Available Since Puppet 3.5.
- EOT
- },
- :biff => {
- :default => false,
- :type => :boolean,
- :hook => proc do |value|
- if Puppet.settings[:parser] != 'future'
- Puppet.settings.override_default(:parser, 'future')
- end
- if Puppet.settings[:evaluator] != 'future'
- Puppet.settings.override_default(:evaluator, 'future')
- end
- end,
- :desc => <<-EOT
- Turns on Biff the catalog builder, future parser, and future evaluator.
- This is an experimental feature - and this setting may go away before
- release of Pupet 3.6.
- EOT
- },
:max_errors => {
:default => 10,
:desc => <<-'EOT'
Sets the max number of logged/displayed parser validation errors in case
- multiple errors have been detected. A value of 0 is the same as value 1.
- The count is per manifest.
+ multiple errors have been detected. A value of 0 is the same as a value of 1; a
+ minimum of one error is always raised. The count is per manifest.
EOT
},
:max_warnings => {
:default => 10,
:desc => <<-'EOT'
Sets the max number of logged/displayed parser validation warnings in
- case multiple errors have been detected. A value of 0 is the same as
- value 1. The count is per manifest.
+ case multiple warnings have been detected. A value of 0 blocks logging of
+ warnings. The count is per manifest.
EOT
},
:max_deprecations => {
:default => 10,
:desc => <<-'EOT'
Sets the max number of logged/displayed parser validation deprecation
- warnings in case multiple errors have been detected. A value of 0 is the
- same as value 1. The count is per manifest.
+ warnings in case multiple deprecation warnings have been detected. A value of 0
+ blocks the logging of deprecation warnings. The count is per manifest.
EOT
},
:strict_variables => {
diff --git a/lib/puppet/environments.rb b/lib/puppet/environments.rb
index b77fc337c..9f7ac5c31 100644
--- a/lib/puppet/environments.rb
+++ b/lib/puppet/environments.rb
@@ -1,5 +1,13 @@
# @api private
module Puppet::Environments
+
+ class EnvironmentNotFound < Puppet::Error
+ def initialize(environment_name, original = nil)
+ environmentpath = Puppet[:environmentpath]
+ super("Could not find a directory environment named '#{environment_name}' anywhere in the path: #{environmentpath}. Does the directory exist?", original)
+ end
+ end
+
# @api private
module EnvironmentCreator
# Create an anonymous environment.
diff --git a/lib/puppet/external/nagios/base.rb b/lib/puppet/external/nagios/base.rb
index 0aa50b411..06f6987ab 100644
--- a/lib/puppet/external/nagios/base.rb
+++ b/lib/puppet/external/nagios/base.rb
@@ -303,7 +303,7 @@ class Nagios::Base
if value.is_a? Array
value.join(",").sub(';', '\;')
else
- value.sub(';', '\;')
+ value.to_s.sub(';', '\;')
end
]
}
diff --git a/lib/puppet/external/pson/pure/generator.rb b/lib/puppet/external/pson/pure/generator.rb
index bcf2fde2a..17c98d58c 100644
--- a/lib/puppet/external/pson/pure/generator.rb
+++ b/lib/puppet/external/pson/pure/generator.rb
@@ -321,14 +321,7 @@ module PSON
module Float
# Returns a PSON string representation for this Float number.
def to_pson(state = nil, *)
- case
- when infinite?
- if !state || state.allow_nan?
- to_s
- else
- raise GeneratorError, "#{self} not allowed in PSON"
- end
- when nan?
+ if infinite? || nan?
if !state || state.allow_nan?
to_s
else
diff --git a/lib/puppet/face/ca.rb b/lib/puppet/face/ca.rb
index 55475de87..9b7cc5eae 100644
--- a/lib/puppet/face/ca.rb
+++ b/lib/puppet/face/ca.rb
@@ -99,6 +99,7 @@ Puppet::Face.define(:ca, '0.1.0') do
end
action :destroy do
+ summary "Destroy named certificate or pending certificate request."
when_invoked do |host, options|
raise "Not a CA" unless Puppet::SSL::CertificateAuthority.ca?
unless ca = Puppet::SSL::CertificateAuthority.instance
@@ -111,6 +112,7 @@ Puppet::Face.define(:ca, '0.1.0') do
end
action :revoke do
+ summary "Add certificate to certificate revocation list."
when_invoked do |host, options|
raise "Not a CA" unless Puppet::SSL::CertificateAuthority.ca?
unless ca = Puppet::SSL::CertificateAuthority.instance
@@ -131,6 +133,7 @@ Puppet::Face.define(:ca, '0.1.0') do
end
action :generate do
+ summary "Generate a certificate for a named client."
option "--dns-alt-names NAMES" do
summary "Additional DNS names to add to the certificate request"
description Puppet.settings.setting(:dns_alt_names).desc
@@ -162,6 +165,7 @@ Puppet::Face.define(:ca, '0.1.0') do
end
action :sign do
+ summary "Sign an outstanding certificate request."
option("--[no-]allow-dns-alt-names") do
summary "Whether or not to accept DNS alt names in the certificate request"
end
@@ -186,6 +190,7 @@ Puppet::Face.define(:ca, '0.1.0') do
end
action :print do
+ summary "Print the full-text version of a host's certificate."
when_invoked do |host, options|
raise "Not a CA" unless Puppet::SSL::CertificateAuthority.ca?
unless ca = Puppet::SSL::CertificateAuthority.instance
@@ -198,6 +203,7 @@ Puppet::Face.define(:ca, '0.1.0') do
end
action :fingerprint do
+ summary "Print the DIGEST (defaults to the signing algorithm) fingerprint of a host's certificate."
option "--digest ALGORITHM" do
summary "The hash algorithm to use when displaying the fingerprint"
end
@@ -218,6 +224,7 @@ Puppet::Face.define(:ca, '0.1.0') do
end
action :verify do
+ summary "Verify the named certificate against the local CA certificate."
when_invoked do |host, options|
raise "Not a CA" unless Puppet::SSL::CertificateAuthority.ca?
unless ca = Puppet::SSL::CertificateAuthority.instance
diff --git a/lib/puppet/face/file/download.rb b/lib/puppet/face/file/download.rb
index aae318565..3ab28b151 100644
--- a/lib/puppet/face/file/download.rb
+++ b/lib/puppet/face/file/download.rb
@@ -23,8 +23,11 @@ Puppet::Face.define(:file, '0.0.1') do
if sum =~ /^puppet:\/\// # it's a puppet url
require 'puppet/file_serving'
require 'puppet/file_serving/content'
- raise "Could not find metadata for #{sum}" unless content = Puppet::FileServing::Content.indirection.find(sum)
- file = Puppet::FileBucket::File.new(content.content)
+ unless content = Puppet::FileServing::Content.indirection.find(sum)
+ raise "Could not find metadata for #{sum}"
+ end
+ pathname = Puppet::FileSystem.pathname(content.full_path())
+ file = Puppet::FileBucket::File.new(pathname)
else
tester = Object.new
tester.extend(Puppet::Util::Checksums)
diff --git a/lib/puppet/face/file/store.rb b/lib/puppet/face/file/store.rb
index dc43fee04..dc4c73b44 100644
--- a/lib/puppet/face/file/store.rb
+++ b/lib/puppet/face/file/store.rb
@@ -11,7 +11,7 @@ Puppet::Face.define(:file, '0.0.1') do
EOT
when_invoked do |path, options|
- file = Puppet::FileBucket::File.new(Puppet::FileSystem.binread(path))
+ file = Puppet::FileBucket::File.new(Puppet::FileSystem.pathname(path))
Puppet::FileBucket::File.indirection.terminus_class = :file
Puppet::FileBucket::File.indirection.save file
diff --git a/lib/puppet/face/instrumentation_data.rb b/lib/puppet/face/instrumentation_data.rb
index c091f5542..05eb4fdb6 100644
--- a/lib/puppet/face/instrumentation_data.rb
+++ b/lib/puppet/face/instrumentation_data.rb
@@ -5,9 +5,10 @@ Puppet::Indirector::Face.define(:instrumentation_data, '0.0.1') do
copyright "Puppet Labs", 2011
license "Apache 2 license; see COPYING"
- summary "Manage instrumentation listener accumulated data."
+ summary "Manage instrumentation listener accumulated data. DEPRECATED."
description <<-EOT
This subcommand allows to retrieve the various listener data.
+ (DEPRECATED) This subcommand will be removed in Puppet 4.0.
EOT
get_action(:destroy).summary "Invalid for this subcommand."
diff --git a/lib/puppet/face/instrumentation_listener.rb b/lib/puppet/face/instrumentation_listener.rb
index 53b61bef7..e11ef407f 100644
--- a/lib/puppet/face/instrumentation_listener.rb
+++ b/lib/puppet/face/instrumentation_listener.rb
@@ -5,9 +5,10 @@ Puppet::Indirector::Face.define(:instrumentation_listener, '0.0.1') do
copyright "Puppet Labs", 2011
license "Apache 2 license; see COPYING"
- summary "Manage instrumentation listeners."
+ summary "Manage instrumentation listeners. DEPRECATED."
description <<-EOT
This subcommand enables/disables or list instrumentation listeners.
+ (DEPRECATED) This subcommand will be removed in Puppet 4.0.
EOT
get_action(:destroy).summary "Invalid for this subcommand."
diff --git a/lib/puppet/face/instrumentation_probe.rb b/lib/puppet/face/instrumentation_probe.rb
index 840eac70a..b77f5e977 100644
--- a/lib/puppet/face/instrumentation_probe.rb
+++ b/lib/puppet/face/instrumentation_probe.rb
@@ -5,9 +5,10 @@ Puppet::Indirector::Face.define(:instrumentation_probe, '0.0.1') do
copyright "Puppet Labs", 2011
license "Apache 2 license; see COPYING"
- summary "Manage instrumentation probes."
+ summary "Manage instrumentation probes. Deprecated"
description <<-EOT
This subcommand enables/disables or list instrumentation listeners.
+ (DEPRECATED) This subcommand will be removed in Puppet 4.0.
EOT
get_action(:find).summary "Invalid for this subcommand."
diff --git a/lib/puppet/face/module/build.rb b/lib/puppet/face/module/build.rb
index f1d006a74..5ee8b2363 100644
--- a/lib/puppet/face/module/build.rb
+++ b/lib/puppet/face/module/build.rb
@@ -43,11 +43,11 @@ Puppet::Face.define(:module, '1.0.0') do
pwd = Dir.pwd
module_path = Puppet::ModuleTool.find_module_root(pwd)
if module_path.nil?
- raise "Unable to find module root at #{pwd} or parent directories"
+ raise "Unable to find metadata.json or Modulefile in module root #{pwd} or parent directories. See <http://links.puppetlabs.com/modulefile> for required file format."
end
else
unless Puppet::ModuleTool.is_module_root?(module_path)
- raise "Unable to find module root at #{module_path}"
+ raise "Unable to find metadata.json or Modulefile in module root #{module_path}. See <http://links.puppetlabs.com/modulefile> for required file format."
end
end
diff --git a/lib/puppet/face/module/generate.rb b/lib/puppet/face/module/generate.rb
index f25e504cb..9b88a8c65 100644
--- a/lib/puppet/face/module/generate.rb
+++ b/lib/puppet/face/module/generate.rb
@@ -54,7 +54,7 @@ Puppet::Face.define(:module, '1.0.0') do
"dependencies": [
{
"name": "puppetlabs-stdlib",
- "version_range": ">= 1.0.0"
+ "version_requirement": ">= 1.0.0"
}
]
}
@@ -114,7 +114,7 @@ Puppet::Face.define(:module, '1.0.0') do
'name' => name,
'version' => '0.1.0',
'dependencies' => [
- { :name => 'puppetlabs-stdlib', :version_range => '>= 1.0.0' }
+ { 'name' => 'puppetlabs-stdlib', 'version_requirement' => '>= 1.0.0' }
]
)
rescue ArgumentError
@@ -216,21 +216,29 @@ module Puppet::ModuleTool::Generate
Puppet.notice "Generating module at #{dest}..."
FileUtils.cp_r skeleton_path, dest
- populate_erb_templates(metadata, dest)
+ populate_templates(metadata, dest)
return dest
end
- def populate_erb_templates(metadata, destination)
- Puppet.notice "Populating ERB templates..."
+ def populate_templates(metadata, destination)
+ Puppet.notice "Populating templates..."
- templates = destination + '**/*.erb'
- Dir[templates.to_s].each do |erb|
- path = Pathname.new(erb)
- content = ERB.new(path.read).result(binding)
+ formatters = {
+ :erb => proc { |data, ctx| ERB.new(data).result(ctx) },
+ :template => proc { |data, _| data },
+ }
- target = path.parent + path.basename('.erb')
- target.open('w') { |f| f.write(content) }
- path.unlink
+ formatters.each do |type, block|
+ templates = destination + "**/*.#{type}"
+
+ Dir.glob(templates.to_s, File::FNM_DOTMATCH).each do |erb|
+ path = Pathname.new(erb)
+ content = block[path.read, binding]
+
+ target = path.parent + path.basename(".#{type}")
+ target.open('w') { |f| f.write(content) }
+ path.unlink
+ end
end
end
diff --git a/lib/puppet/face/module/install.rb b/lib/puppet/face/module/install.rb
index 929ff9db8..0644d0518 100644
--- a/lib/puppet/face/module/install.rb
+++ b/lib/puppet/face/module/install.rb
@@ -84,9 +84,10 @@ Puppet::Face.define(:module, '1.0.0') do
arguments "<name>"
option "--force", "-f" do
- summary "Force overwrite of existing module, if any."
+ summary "Force overwrite of existing module, if any. (Implies --ignore-dependencies.)"
description <<-EOT
Force overwrite of existing module, if any.
+ Implies --ignore-dependencies.
EOT
end
@@ -104,9 +105,9 @@ Puppet::Face.define(:module, '1.0.0') do
end
option "--ignore-dependencies" do
- summary "Do not attempt to install dependencies"
+ summary "Do not attempt to install dependencies. (Implied by --force.)"
description <<-EOT
- Do not attempt to install dependencies. (Implied by --force.)
+ Do not attempt to install dependencies. Implied by --force.
EOT
end
diff --git a/lib/puppet/face/module/uninstall.rb b/lib/puppet/face/module/uninstall.rb
index 60db2c2b7..2a5e675b2 100644
--- a/lib/puppet/face/module/uninstall.rb
+++ b/lib/puppet/face/module/uninstall.rb
@@ -40,6 +40,13 @@ Puppet::Face.define(:module, '1.0.0') do
EOT
end
+ option "--ignore-changes", "-c" do
+ summary "Ignore any local changes made. (Implied by --force.)"
+ description <<-EOT
+ Uninstall an installed module even if there are local changes to it. (Implied by --force.)
+ EOT
+ end
+
option "--version=" do
summary "The version of the module to uninstall"
description <<-EOT
diff --git a/lib/puppet/face/module/upgrade.rb b/lib/puppet/face/module/upgrade.rb
index fe9dde644..f671887e1 100644
--- a/lib/puppet/face/module/upgrade.rb
+++ b/lib/puppet/face/module/upgrade.rb
@@ -32,17 +32,25 @@ Puppet::Face.define(:module, '1.0.0') do
arguments "<name>"
option "--force", "-f" do
- summary "Force upgrade of an installed module."
+ summary "Force upgrade of an installed module. (Implies --ignore-dependencies.)"
description <<-EOT
Force the upgrade of an installed module even if there are local
changes or the possibility of causing broken dependencies.
+ Implies --ignore-dependencies.
EOT
end
option "--ignore-dependencies" do
- summary "Do not attempt to install dependencies"
+ summary "Do not attempt to install dependencies. (Implied by --force.)"
description <<-EOT
- Do not attempt to install dependencies. (Implied by --force.)
+ Do not attempt to install dependencies. Implied by --force.
+ EOT
+ end
+
+ option "--ignore-changes", "-c" do
+ summary "Ignore and overwrite any local changes made. (Implied by --force.)"
+ description <<-EOT
+ Upgrade an installed module even if there are local changes to it. (Implied by --force.)
EOT
end
diff --git a/lib/puppet/face/node/clean.rb b/lib/puppet/face/node/clean.rb
index 903e93819..f1c3c34b8 100644
--- a/lib/puppet/face/node/clean.rb
+++ b/lib/puppet/face/node/clean.rb
@@ -144,7 +144,7 @@ Puppet::Face.define(:node, '0.0.1') do
end
def environment
- @environment ||= Puppet.lookup(:environments).get(Puppet[:environment])
+ @environment ||= Puppet.lookup(:current_environment)
end
def type_is_ensurable(resource)
diff --git a/lib/puppet/face/parser.rb b/lib/puppet/face/parser.rb
index a3835bfa0..b3dec4ed7 100644
--- a/lib/puppet/face/parser.rb
+++ b/lib/puppet/face/parser.rb
@@ -15,6 +15,13 @@ Puppet::Face.define(:parser, '0.0.1') do
This action validates Puppet DSL syntax without compiling a catalog or
syncing any resources. If no manifest files are provided, it will
validate the default site manifest.
+
+ When validating with --parser current, the validation stops after the first
+ encountered issue.
+
+ When validating with --parser future, multiple issues per file are reported up
+ to the settings of max_error, and max_warnings. The processing stops
+ after having reported issues for the first encountered file with errors.
EOT
examples <<-'EOT'
Validate the default site manifest at /etc/puppet/manifests/site.pp:
@@ -44,21 +51,106 @@ Puppet::Face.define(:parser, '0.0.1') do
end
missing_files = []
files.each do |file|
- missing_files << file if ! Puppet::FileSystem.exist?(file)
- validate_manifest(file)
+ if Puppet::FileSystem.exist?(file)
+ validate_manifest(file)
+ else
+ missing_files << file
+ end
+ end
+ unless missing_files.empty?
+ raise Puppet::Error, "One or more file(s) specified did not exist:\n#{missing_files.collect {|f| " " * 3 + f + "\n"}}"
end
- raise Puppet::Error, "One or more file(s) specified did not exist:\n#{missing_files.collect {|f| " " * 3 + f + "\n"}}" if ! missing_files.empty?
nil
end
end
+
+ action (:dump) do
+ summary "Outputs a dump of the internal parse tree for debugging"
+ arguments "-e <source>| [<manifest> ...] "
+ returns "A dump of the resulting AST model unless there are syntax or validation errors."
+ description <<-'EOT'
+ This action parses and validates the Puppet DSL syntax without compiling a catalog
+ or syncing any resources. It automatically turns on the future parser for the parsing.
+
+ The command accepts one or more manifests (.pp) files, or an -e followed by the puppet
+ source text.
+ If no arguments are given, the stdin is read (unless it is attached to a terminal)
+
+ The output format of the dumped tree is not API, it may change from time to time.
+ EOT
+
+ option "--e <source>" do
+ default_to { nil }
+ summary "dump one source expression given on the command line."
+ end
+
+ option("--[no-]validate") do
+ summary "Whether or not to validate the parsed result, if no-validate only syntax errors are reported"
+ end
+
+ when_invoked do |*args|
+ require 'puppet/pops'
+ options = args.pop
+ if options[:e]
+ dump_parse(options[:e], 'command-line-string', options, false)
+ elsif args.empty?
+ if ! STDIN.tty?
+ dump_parse(STDIN.read, 'stdin', options, false)
+ else
+ raise Puppet::Error, "No input to parse given on command line or stdin"
+ end
+ else
+ missing_files = []
+ files = args
+ available_files = files.select do |file|
+ Puppet::FileSystem.exist?(file)
+ end
+ missing_files = files - available_files
+
+ dumps = available_files.collect do |file|
+ dump_parse(File.read(file), file, options)
+ end.join("")
+
+ if missing_files.empty?
+ dumps
+ else
+ dumps + "One or more file(s) specified did not exist:\n" + missing_files.collect { |f| " #{f}" }.join("\n")
+ end
+ end
+ end
+ end
+
+ def dump_parse(source, filename, options, show_filename = true)
+ output = ""
+ dumper = Puppet::Pops::Model::ModelTreeDumper.new
+ evaluating_parser = Puppet::Pops::Parser::EvaluatingParser.new
+ begin
+ if options[:validate]
+ parse_result = evaluating_parser.parse_string(source, filename)
+ else
+ # side step the assert_and_report step
+ parse_result = evaluating_parser.parser.parse_string(source)
+ end
+ if show_filename
+ output << "--- #{filename}"
+ end
+ output << dumper.dump(parse_result) << "\n"
+ rescue Puppet::ParseError => detail
+ if show_filename
+ Puppet.err("--- #{filename}")
+ end
+ Puppet.err(detail.message)
+ ""
+ end
+ end
+
# @api private
def validate_manifest(manifest = nil)
- configured_environment = Puppet.lookup(:environments).get(Puppet[:environment])
- validation_environment = manifest ?
- configured_environment.override_with(:manifest => manifest) :
- configured_environment
+ env = Puppet.lookup(:current_environment)
+ validation_environment = manifest ? env.override_with(:manifest => manifest) : env
+ validation_environment.check_for_reparse
validation_environment.known_resource_types.clear
rescue => detail
diff --git a/lib/puppet/feature/base.rb b/lib/puppet/feature/base.rb
index e30aed313..b410297fb 100644
--- a/lib/puppet/feature/base.rb
+++ b/lib/puppet/feature/base.rb
@@ -19,17 +19,13 @@ Puppet.features.add(:microsoft_windows) do
# ruby
require 'Win32API' # case matters in this require!
require 'win32ole'
- require 'win32/registry'
# gems
- require 'sys/admin'
require 'win32/process'
require 'win32/dir'
require 'win32/service'
- require 'win32/api'
- require 'win32/taskscheduler'
true
rescue LoadError => err
- warn "Cannot run on Microsoft Windows without the sys-admin, win32-process, win32-dir, win32-service and win32-taskscheduler gems: #{err}" unless Puppet.features.posix?
+ warn "Cannot run on Microsoft Windows without the win32-process, win32-dir and win32-service gems: #{err}" unless Puppet.features.posix?
end
end
@@ -76,13 +72,23 @@ Puppet.features.add(:manages_symlinks) do
if ! Puppet::Util::Platform.windows?
true
else
- begin
- require 'Win32API'
- Win32API.new('kernel32', 'CreateSymbolicLink', 'SSL', 'B')
- true
- rescue LoadError => err
- Puppet.debug("CreateSymbolicLink is not available")
- false
+ module WindowsSymlink
+ require 'ffi'
+ extend FFI::Library
+
+ def self.is_implemented
+ begin
+ ffi_lib :kernel32
+ attach_function :CreateSymbolicLinkW, [:lpwstr, :lpwstr, :dword], :win32_bool
+
+ true
+ rescue LoadError => err
+ Puppet.debug("CreateSymbolicLink is not available")
+ false
+ end
+ end
end
+
+ WindowsSymlink.is_implemented
end
end
diff --git a/lib/puppet/feature/cfacter.rb b/lib/puppet/feature/cfacter.rb
new file mode 100644
index 000000000..18924a951
--- /dev/null
+++ b/lib/puppet/feature/cfacter.rb
@@ -0,0 +1,14 @@
+require 'facter'
+require 'puppet/util/feature'
+
+Puppet.features.add :cfacter do
+ begin
+ require 'cfacter'
+
+ # The first release of cfacter didn't have the necessary interface to work with Puppet
+ # Therefore, if the version is 0.1.0, treat the feature as not present
+ CFacter.version != '0.1.0'
+ rescue LoadError
+ false
+ end
+end
diff --git a/lib/puppet/feature/pe_license.rb b/lib/puppet/feature/pe_license.rb
new file mode 100644
index 000000000..cdb66c19b
--- /dev/null
+++ b/lib/puppet/feature/pe_license.rb
@@ -0,0 +1,4 @@
+require 'puppet/util/feature'
+
+#Is the pe license library installed providing the ability to read licenses.
+Puppet.features.add(:pe_license, :libs => %{pe_license})
diff --git a/lib/puppet/file_bucket/dipper.rb b/lib/puppet/file_bucket/dipper.rb
index 33c800b47..b627ce02d 100644
--- a/lib/puppet/file_bucket/dipper.rb
+++ b/lib/puppet/file_bucket/dipper.rb
@@ -10,7 +10,7 @@ class Puppet::FileBucket::Dipper
attr_accessor :name
- # Create our bucket client
+ # Creates a bucket client
def initialize(hash = {})
# Emulate the XMLRPC client
server = hash[:Server]
@@ -32,13 +32,12 @@ class Puppet::FileBucket::Dipper
!! @local_path
end
- # Back up a file to our bucket
+ # Backs up a file to the file bucket
def backup(file)
file_handle = Puppet::FileSystem.pathname(file)
raise(ArgumentError, "File #{file} does not exist") unless Puppet::FileSystem.exist?(file_handle)
- contents = Puppet::FileSystem.binread(file_handle)
begin
- file_bucket_file = Puppet::FileBucket::File.new(contents, :bucket_path => @local_path)
+ file_bucket_file = Puppet::FileBucket::File.new(file_handle, :bucket_path => @local_path)
files_original_path = absolutize_path(file)
dest_path = "#{@rest_path}#{file_bucket_file.name}/#{files_original_path}"
file_bucket_path = "#{@rest_path}#{file_bucket_file.checksum_type}/#{file_bucket_file.checksum_data}/#{files_original_path}"
@@ -57,21 +56,26 @@ class Puppet::FileBucket::Dipper
end
end
- # Retrieve a file by sum.
+ # Retrieves a file by sum.
def getfile(sum)
+ get_bucket_file(sum).to_s
+ end
+
+ # Retrieves a FileBucket::File by sum.
+ def get_bucket_file(sum)
source_path = "#{@rest_path}#{@checksum_type}/#{sum}"
file_bucket_file = Puppet::FileBucket::File.indirection.find(source_path, :bucket_path => @local_path)
raise Puppet::Error, "File not found" unless file_bucket_file
- file_bucket_file.to_s
+ file_bucket_file
end
- # Restore the file
- def restore(file,sum)
+ # Restores the file
+ def restore(file, sum)
restore = true
file_handle = Puppet::FileSystem.pathname(file)
if Puppet::FileSystem.exist?(file_handle)
- cursum = @digest.call(Puppet::FileSystem.binread(file_handle))
+ cursum = Puppet::FileBucket::File.new(file_handle).checksum_data()
# if the checksum has changed...
# this might be extra effort
@@ -81,8 +85,8 @@ class Puppet::FileBucket::Dipper
end
if restore
- if newcontents = getfile(sum)
- newsum = @digest.call(newcontents)
+ if newcontents = get_bucket_file(sum)
+ newsum = newcontents.checksum_data
changed = nil
if Puppet::FileSystem.exist?(file_handle) and ! Puppet::FileSystem.writable?(file_handle)
changed = Puppet::FileSystem.stat(file_handle).mode
@@ -90,7 +94,10 @@ class Puppet::FileBucket::Dipper
end
::File.open(file, ::File::WRONLY|::File::TRUNC|::File::CREAT) { |of|
of.binmode
- of.print(newcontents)
+ source_stream = newcontents.stream do |source_stream|
+ FileUtils.copy_stream(source_stream, of)
+ end
+ #of.print(newcontents)
}
::File.chmod(changed, file) if changed
else
diff --git a/lib/puppet/file_bucket/file.rb b/lib/puppet/file_bucket/file.rb
index cb8ecd699..9c9d2ebe3 100644
--- a/lib/puppet/file_bucket/file.rb
+++ b/lib/puppet/file_bucket/file.rb
@@ -11,7 +11,6 @@ class Puppet::FileBucket::File
extend Puppet::Indirector
indirects :file_bucket_file, :terminus_class => :selector
- attr :contents
attr :bucket_path
def self.supported_formats
@@ -28,8 +27,14 @@ class Puppet::FileBucket::File
end
def initialize(contents, options = {})
- raise ArgumentError.new("contents must be a String, got a #{contents.class}") unless contents.is_a?(String)
- @contents = contents
+ case contents
+ when String
+ @contents = StringContents.new(contents)
+ when Pathname
+ @contents = FileContents.new(contents)
+ else
+ raise ArgumentError.new("contents must be a String or Pathname, got a #{contents.class}")
+ end
@bucket_path = options.delete(:bucket_path)
@checksum_type = Puppet[:digest_algorithm].to_sym
@@ -38,12 +43,12 @@ class Puppet::FileBucket::File
# @return [Num] The size of the contents
def size
- contents.size
+ @contents.size()
end
# @return [IO] A stream that reads the contents
- def stream
- StringIO.new(contents)
+ def stream(&block)
+ @contents.stream(&block)
end
def checksum_type
@@ -55,12 +60,15 @@ class Puppet::FileBucket::File
end
def checksum_data
- algorithm = Puppet::Util::Checksums.method(@checksum_type)
- @checksum_data ||= algorithm.call(contents)
+ @checksum_data ||= @contents.checksum_data(@checksum_type)
end
def to_s
- contents
+ @contents.to_s
+ end
+
+ def contents
+ to_s
end
def name
@@ -72,7 +80,8 @@ class Puppet::FileBucket::File
end
def to_data_hash
- { "contents" => contents }
+ # Note that this serializes the entire data to a string and places it in a hash.
+ { "contents" => contents.to_s }
end
def self.from_data_hash(data)
@@ -91,4 +100,58 @@ class Puppet::FileBucket::File
self.from_data_hash(pson)
end
+ private
+
+ class StringContents
+ def initialize(content)
+ @contents = content;
+ end
+
+ def stream(&block)
+ s = StringIO.new(@contents)
+ begin
+ block.call(s)
+ ensure
+ s.close
+ end
+ end
+
+ def size
+ @contents.size
+ end
+
+ def checksum_data(base_method)
+ Puppet.info("Computing checksum on string")
+ Puppet::Util::Checksums.method(base_method).call(@contents)
+ end
+
+ def to_s
+ # This is not so horrible as for FileContent, but still possible to mutate the content that the
+ # checksum is based on... so semi horrible...
+ return @contents;
+ end
+ end
+
+ class FileContents
+ def initialize(path)
+ @path = path
+ end
+
+ def stream(&block)
+ Puppet::FileSystem.open(@path, nil, 'rb', &block)
+ end
+
+ def size
+ Puppet::FileSystem.size(@path)
+ end
+
+ def checksum_data(base_method)
+ Puppet.info("Computing checksum on file #{@path}")
+ Puppet::Util::Checksums.method(:"#{base_method}_file").call(@path)
+ end
+
+ def to_s
+ Puppet::FileSystem::binread(@path)
+ end
+ end
end
diff --git a/lib/puppet/file_serving/configuration/parser.rb b/lib/puppet/file_serving/configuration/parser.rb
index ccb6b3568..2ae8313b1 100644
--- a/lib/puppet/file_serving/configuration/parser.rb
+++ b/lib/puppet/file_serving/configuration/parser.rb
@@ -67,7 +67,7 @@ class Puppet::FileServing::Configuration::Parser
mount.info "allowing #{val} access"
mount.allow(val)
rescue Puppet::AuthStoreError => detail
- raise ArgumentError.new(detail.to_s, @count, @file)
+ raise ArgumentError.new("#{detail.to_s} in #{@file}, line #{@count}")
end
}
end
@@ -79,14 +79,14 @@ class Puppet::FileServing::Configuration::Parser
mount.info "denying #{val} access"
mount.deny(val)
rescue Puppet::AuthStoreError => detail
- raise ArgumentError.new(detail.to_s, @count, @file)
+ raise ArgumentError.new("#{detail.to_s} in #{@file}, line #{@count}")
end
}
end
# Create a new mount.
def newmount(name)
- raise ArgumentError, "#{@mounts[name]} is already mounted at #{name}", @count, @file if @mounts.include?(name)
+ raise ArgumentError.new("#{@mounts[name]} is already mounted at #{name} in #{@file}, line #{@count}") if @mounts.include?(name)
case name
when "modules"
mount = Mount::Modules.new(name)
diff --git a/lib/puppet/file_system.rb b/lib/puppet/file_system.rb
index 6db15ee37..8a37e7e30 100644
--- a/lib/puppet/file_system.rb
+++ b/lib/puppet/file_system.rb
@@ -3,7 +3,7 @@ module Puppet::FileSystem
require 'puppet/file_system/file_impl'
require 'puppet/file_system/memory_file'
require 'puppet/file_system/memory_impl'
- require 'puppet/file_system/tempfile'
+ require 'puppet/file_system/uniquefile'
# create instance of the file system implementation to use for the current platform
@impl = if RUBY_VERSION =~ /^1\.8/
diff --git a/lib/puppet/file_system/file19.rb b/lib/puppet/file_system/file19.rb
index fce9a6a82..8872ba6fc 100644
--- a/lib/puppet/file_system/file19.rb
+++ b/lib/puppet/file_system/file19.rb
@@ -2,4 +2,45 @@ class Puppet::FileSystem::File19 < Puppet::FileSystem::FileImpl
def binread(path)
path.binread
end
+
+ # Provide an encoding agnostic version of compare_stream
+ #
+ # The FileUtils implementation in Ruby 2.0+ was modified in a manner where
+ # it cannot properly compare File and StringIO instances. To sidestep that
+ # issue this method reimplements the faster 2.0 version that will correctly
+ # compare binary File and StringIO streams.
+ def compare_stream(path, stream)
+ open(path, 0, 'rb') do |this|
+ bsize = stream_blksize(this, stream)
+ sa = "".force_encoding('ASCII-8BIT')
+ sb = "".force_encoding('ASCII-8BIT')
+ begin
+ this.read(bsize, sa)
+ stream.read(bsize, sb)
+ return true if sa.empty? && sb.empty?
+ end while sa == sb
+ false
+ end
+ end
+
+ private
+ def stream_blksize(*streams)
+ streams.each do |s|
+ next unless s.respond_to?(:stat)
+ size = blksize(s.stat)
+ return size if size
+ end
+ default_blksize()
+ end
+
+ def blksize(st)
+ s = st.blksize
+ return nil unless s
+ return nil if s == 0
+ s
+ end
+
+ def default_blksize
+ 1024
+ end
end
diff --git a/lib/puppet/file_system/file19windows.rb b/lib/puppet/file_system/file19windows.rb
index 7ebba2cf4..7ae984f48 100644
--- a/lib/puppet/file_system/file19windows.rb
+++ b/lib/puppet/file_system/file19windows.rb
@@ -26,7 +26,6 @@ class Puppet::FileSystem::File19Windows < Puppet::FileSystem::File19
dest_exists = exist?(dest) # returns false on dangling symlink
dest_stat = Puppet::Util::Windows::File.stat(dest) if dest_exists
- dest_symlink = Puppet::Util::Windows::File.symlink?(dest)
# silent fail to preserve semantics of original FileUtils
return 0 if dest_exists && dest_stat.ftype == 'directory'
diff --git a/lib/puppet/file_system/tempfile.rb b/lib/puppet/file_system/tempfile.rb
deleted file mode 100644
index 6766a7aec..000000000
--- a/lib/puppet/file_system/tempfile.rb
+++ /dev/null
@@ -1,20 +0,0 @@
-require 'tempfile'
-
-class Puppet::FileSystem::Tempfile
-
- # Variation of Tempfile.open which ensures that the tempfile is closed and
- # unlinked before returning
- #
- # @param identifier [String] additional part of generated pathname
- # @yieldparam file [File] the temporary file object
- # @return result of the passed block
- # @api private
- def self.open(identifier)
- file = ::Tempfile.new(identifier)
-
- yield file
-
- ensure
- file.close!
- end
-end
diff --git a/lib/puppet/file_system/uniquefile.rb b/lib/puppet/file_system/uniquefile.rb
new file mode 100644
index 000000000..2b1311509
--- /dev/null
+++ b/lib/puppet/file_system/uniquefile.rb
@@ -0,0 +1,190 @@
+require 'puppet/file_system'
+require 'delegate'
+require 'tmpdir'
+
+# A class that provides `Tempfile`-like capabilities, but does not attempt to
+# manage the deletion of the file for you. API is identical to the
+# normal `Tempfile` class.
+#
+# @api public
+class Puppet::FileSystem::Uniquefile < DelegateClass(File)
+ # Convenience method which ensures that the file is closed and
+ # unlinked before returning
+ #
+ # @param identifier [String] additional part of generated pathname
+ # @yieldparam file [File] the temporary file object
+ # @return result of the passed block
+ # @api private
+ def self.open_tmp(identifier)
+ f = new(identifier)
+ yield f
+ ensure
+ if f
+ f.close!
+ end
+ end
+
+ def initialize(basename, *rest)
+ create_tmpname(basename, *rest) do |tmpname, n, opts|
+ mode = File::RDWR|File::CREAT|File::EXCL
+ perm = 0600
+ if opts
+ mode |= opts.delete(:mode) || 0
+ opts[:perm] = perm
+ perm = nil
+ else
+ opts = perm
+ end
+ self.class.locking(tmpname) do
+ @tmpfile = File.open(tmpname, mode, opts)
+ @tmpname = tmpname
+ end
+ @mode = mode & ~(File::CREAT|File::EXCL)
+ perm or opts.freeze
+ @opts = opts
+ end
+
+ super(@tmpfile)
+ end
+
+ # Opens or reopens the file with mode "r+".
+ def open
+ @tmpfile.close if @tmpfile
+ @tmpfile = File.open(@tmpname, @mode, @opts)
+ __setobj__(@tmpfile)
+ end
+
+ def _close
+ begin
+ @tmpfile.close if @tmpfile
+ ensure
+ @tmpfile = nil
+ end
+ end
+ protected :_close
+
+ def close(unlink_now=false)
+ if unlink_now
+ close!
+ else
+ _close
+ end
+ end
+
+ def close!
+ _close
+ unlink
+ end
+
+ def unlink
+ return unless @tmpname
+ begin
+ File.unlink(@tmpname)
+ rescue Errno::ENOENT
+ rescue Errno::EACCES
+ # may not be able to unlink on Windows; just ignore
+ return
+ end
+ @tmpname = nil
+ end
+ alias delete unlink
+
+ # Returns the full path name of the temporary file.
+ # This will be nil if #unlink has been called.
+ def path
+ @tmpname
+ end
+
+ private
+
+ def make_tmpname(prefix_suffix, n)
+ case prefix_suffix
+ when String
+ prefix = prefix_suffix
+ suffix = ""
+ when Array
+ prefix = prefix_suffix[0]
+ suffix = prefix_suffix[1]
+ else
+ raise ArgumentError, "unexpected prefix_suffix: #{prefix_suffix.inspect}"
+ end
+ t = Time.now.strftime("%Y%m%d")
+ path = "#{prefix}#{t}-#{$$}-#{rand(0x100000000).to_s(36)}"
+ path << "-#{n}" if n
+ path << suffix
+ end
+
+ def create_tmpname(basename, *rest)
+ if opts = try_convert_to_hash(rest[-1])
+ opts = opts.dup if rest.pop.equal?(opts)
+ max_try = opts.delete(:max_try)
+ opts = [opts]
+ else
+ opts = []
+ end
+ tmpdir, = *rest
+ if $SAFE > 0 and tmpdir.tainted?
+ tmpdir = '/tmp'
+ else
+ tmpdir ||= tmpdir()
+ end
+ n = nil
+ begin
+ path = File.expand_path(make_tmpname(basename, n), tmpdir)
+ yield(path, n, *opts)
+ rescue Errno::EEXIST
+ n ||= 0
+ n += 1
+ retry if !max_try or n < max_try
+ raise "cannot generate temporary name using `#{basename}' under `#{tmpdir}'"
+ end
+ path
+ end
+
+ def try_convert_to_hash(h)
+ begin
+ h.to_hash
+ rescue NoMethodError => e
+ nil
+ end
+ end
+
+ @@systmpdir ||= defined?(Etc.systmpdir) ? Etc.systmpdir : '/tmp'
+
+ def tmpdir
+ tmp = '.'
+ if $SAFE > 0
+ tmp = @@systmpdir
+ else
+ for dir in [ENV['TMPDIR'], ENV['TMP'], ENV['TEMP'], @@systmpdir, '/tmp']
+ if dir and stat = File.stat(dir) and stat.directory? and stat.writable?
+ tmp = dir
+ break
+ end rescue nil
+ end
+ File.expand_path(tmp)
+ end
+ end
+
+
+ class << self
+ # yields with locking for +tmpname+ and returns the result of the
+ # block.
+ def locking(tmpname)
+ lock = tmpname + '.lock'
+ mkdir(lock)
+ yield
+ ensure
+ rmdir(lock) if lock
+ end
+
+ def mkdir(*args)
+ Dir.mkdir(*args)
+ end
+
+ def rmdir(*args)
+ Dir.rmdir(*args)
+ end
+ end
+
+end \ No newline at end of file
diff --git a/lib/puppet/forge.rb b/lib/puppet/forge.rb
index d43114b80..a7cdd42a4 100644
--- a/lib/puppet/forge.rb
+++ b/lib/puppet/forge.rb
@@ -54,6 +54,9 @@ class Puppet::Forge < Semantic::Dependency::Source
def search(term)
matches = []
uri = "/v3/modules?query=#{URI.escape(term)}"
+ if Puppet[:module_groups]
+ uri += "&module_groups=#{Puppet[:module_groups]}"
+ end
while uri
response = make_http_request(uri)
@@ -63,7 +66,7 @@ class Puppet::Forge < Semantic::Dependency::Source
uri = result['pagination']['next']
matches.concat result['results']
else
- raise ResponseError.new(:uri => URI.parse(@host).merge(uri) , :input => term, :response => response)
+ raise ResponseError.new(:uri => URI.parse(@host).merge(uri), :response => response)
end
end
@@ -86,6 +89,9 @@ class Puppet::Forge < Semantic::Dependency::Source
def fetch(input)
name = input.tr('/', '-')
uri = "/v3/releases?module=#{name}"
+ if Puppet[:module_groups]
+ uri += "&module_groups=#{Puppet[:module_groups]}"
+ end
releases = []
while uri
@@ -94,7 +100,7 @@ class Puppet::Forge < Semantic::Dependency::Source
if response.code == '200'
response = JSON.parse(response.body)
else
- raise ResponseError.new(:uri => URI.parse(@host).merge(uri), :input => input, :response => response)
+ raise ResponseError.new(:uri => URI.parse(@host).merge(uri), :response => response)
end
releases.concat(process(response['results']))
@@ -119,9 +125,17 @@ class Puppet::Forge < Semantic::Dependency::Source
version = Semantic::Version.parse(meta['version'])
release = "#{name}@#{version}"
- dependencies = (meta['dependencies'] || [])
- dependencies.map! do |dep|
- Puppet::ModuleTool.parse_module_dependency(release, dep)[0..1]
+ if meta['dependencies']
+ dependencies = meta['dependencies'].collect do |dep|
+ begin
+ Puppet::ModuleTool::Metadata.new.add_dependency(dep['name'], dep['version_requirement'], dep['repository'])
+ Puppet::ModuleTool.parse_module_dependency(release, dep)[0..1]
+ rescue ArgumentError => e
+ raise ArgumentError, "Malformed dependency: #{dep['name']}. Exception was: #{e}"
+ end
+ end
+ else
+ dependencies = []
end
super(source, name, version, Hash[dependencies])
@@ -172,8 +186,11 @@ class Puppet::Forge < Semantic::Dependency::Source
end
def download(uri, destination)
- @source.make_http_request(uri, destination)
+ response = @source.make_http_request(uri, destination)
destination.flush and destination.close
+ unless response.code == '200'
+ raise Puppet::Forge::Errors::ResponseError.new(:uri => uri, :response => response)
+ end
end
def validate_checksum(file, checksum)
@@ -194,6 +211,16 @@ class Puppet::Forge < Semantic::Dependency::Source
private
def process(list)
- list.map { |release| ModuleRelease.new(self, release) }
+ l = list.map do |release|
+ metadata = release['metadata']
+ begin
+ ModuleRelease.new(self, release)
+ rescue ArgumentError => e
+ Puppet.warning "Cannot consider release #{metadata['name']}-#{metadata['version']}: #{e}"
+ false
+ end
+ end
+
+ l.select { |r| r }
end
end
diff --git a/lib/puppet/forge/errors.rb b/lib/puppet/forge/errors.rb
index 211c63fe8..54041f592 100644
--- a/lib/puppet/forge/errors.rb
+++ b/lib/puppet/forge/errors.rb
@@ -77,7 +77,6 @@ Could not connect to #{@uri}
# @option options [Net::HTTPResponse] :response The original HTTP response
def initialize(options)
@uri = options[:uri]
- @input = options[:input]
@message = options[:message]
response = options[:response]
@response = "#{response.code} #{response.message.strip}"
@@ -90,7 +89,7 @@ Could not connect to #{@uri}
rescue JSON::ParserError
end
- message = "Could not execute operation for '#{@input}'. Detail: "
+ message = "Request to Puppet Forge failed. Detail: "
message << @message << " / " if @message
message << @response << "."
super(message, original)
@@ -100,13 +99,13 @@ Could not connect to #{@uri}
#
# @return [String] the multiline version of the error message
def multiline
- message = <<-EOS
-Could not execute operation for '#{@input}'
+ message = <<-EOS.chomp
+Request to Puppet Forge failed.
The server being queried was #{@uri}
The HTTP response we received was '#{@response}'
EOS
- message << " The message we received said '#{@message}'\n" if @message
- message << " Check the author and module names are correct."
+ message << "\n The message we received said '#{@message}'" if @message
+ message
end
end
diff --git a/lib/puppet/forge/repository.rb b/lib/puppet/forge/repository.rb
index 9043f6bdc..a96faf309 100644
--- a/lib/puppet/forge/repository.rb
+++ b/lib/puppet/forge/repository.rb
@@ -52,6 +52,14 @@ class Puppet::Forge
return read_response(request, io)
end
+ def forge_authorization
+ if Puppet[:forge_authorization]
+ Puppet[:forge_authorization]
+ elsif Puppet.features.pe_license?
+ PELicense.load_license_key.authorization_token
+ end
+ end
+
def get_request_object(path)
headers = {
"User-Agent" => user_agent,
@@ -63,9 +71,13 @@ class Puppet::Forge
})
end
+ if forge_authorization
+ headers = headers.merge({"Authorization" => forge_authorization})
+ end
+
request = Net::HTTP::Get.new(URI.escape(path), headers)
- unless @uri.user.nil? || @uri.password.nil?
+ unless @uri.user.nil? || @uri.password.nil? || forge_authorization
request.basic_auth(@uri.user, @uri.password)
end
@@ -121,7 +133,7 @@ class Puppet::Forge
#
# @return [Net::HTTP::Proxy] object constructed from repo settings
def get_http_object
- proxy_class = Net::HTTP::Proxy(Puppet::Util::HttpProxy.http_proxy_host, Puppet::Util::HttpProxy.http_proxy_port)
+ proxy_class = Net::HTTP::Proxy(Puppet::Util::HttpProxy.http_proxy_host, Puppet::Util::HttpProxy.http_proxy_port, Puppet::Util::HttpProxy.http_proxy_user, Puppet::Util::HttpProxy.http_proxy_password)
proxy = proxy_class.new(@uri.host, @uri.port)
if @uri.scheme == 'https'
diff --git a/lib/puppet/functions.rb b/lib/puppet/functions.rb
index a15e166f6..965d7b8f1 100644
--- a/lib/puppet/functions.rb
+++ b/lib/puppet/functions.rb
@@ -80,7 +80,7 @@
#
# If nothing is specified, the number of arguments given to the function must
# be the same as the number of parameters, and all of the parameters are of
-# type 'Object'.
+# type 'Any'.
#
# To express that the last parameter captures the rest, the method
# `last_captures_rest` can be called. This indicates that the last parameter is
@@ -179,7 +179,7 @@ module Puppet::Functions
unless the_class.method_defined?(func_name)
raise ArgumentError, "Function Creation Error, cannot create a default dispatcher for function '#{func_name}', no method with this name found"
end
- object_signature(*min_max_param(the_class.instance_method(func_name)))
+ any_signature(*min_max_param(the_class.instance_method(func_name)))
end
# @api private
@@ -208,14 +208,14 @@ module Puppet::Functions
end
# Construct a signature consisting of Object type, with min, and max, and given names.
- # (there is only one type entry). Note that this signature is Object, not Optional[Object].
+ # (there is only one type entry).
#
# @api private
- def self.object_signature(from, to, names)
+ def self.any_signature(from, to, names)
# Construct the type for the signature
# Tuple[Object, from, to]
factory = Puppet::Pops::Types::TypeFactory
- [factory.callable(factory.object, from, to), names]
+ [factory.callable(factory.any, from, to), names]
end
# Function
@@ -288,10 +288,12 @@ module Puppet::Functions
def required_block_param(*type_and_name)
case type_and_name.size
when 0
- type = @all_callables
+ # the type must be an independent instance since it will be contained in another type
+ type = @all_callables.copy
name = 'block'
when 1
- type = @all_callables
+ # the type must be an independent instance since it will be contained in another type
+ type = @all_callables.copy
name = type_and_name[0]
when 2
type_string, name = type_and_name
@@ -300,12 +302,12 @@ module Puppet::Functions
raise ArgumentError, "block_param accepts max 2 arguments (type, name), got #{type_and_name.size}."
end
- unless type.is_a?(Puppet::Pops::Types::PCallableType)
- raise ArgumentError, "Expected PCallableType, got #{type.class}"
+ unless Puppet::Pops::Types::TypeCalculator.is_kind_of_callable?(type, false)
+ raise ArgumentError, "Expected PCallableType or PVariantType thereof, got #{type.class}"
end
- unless name.is_a?(String)
- raise ArgumentError, "Expected block_param name to be a String, got #{name.class}"
+ unless name.is_a?(String) || name.is_a?(Symbol)
+ raise ArgumentError, "Expected block_param name to be a String or Symbol, got #{name.class}"
end
if @block_type.nil?
@@ -329,7 +331,7 @@ module Puppet::Functions
# Specifies the min and max occurance of arguments (of the specified types)
# if something other than the exact count from the number of specified
- # types). The max value may be specified as -1 if an infinite number of
+ # types). The max value may be specified as :default if an infinite number of
# arguments are supported. When max is > than the number of specified
# types, the last specified type repeats.
#
@@ -527,6 +529,11 @@ module Puppet::Functions
#
# @api private
class InternalDispatchBuilder < DispatcherBuilder
+ def scope_param()
+ @injections << [:scope, 'scope', '', :dispatcher_internal]
+ # mark what should be picked for this position when dispatching
+ @weaving << [@injections.size()-1]
+ end
# TODO: is param name really needed? Perhaps for error messages? (it is unused now)
#
# @api private
diff --git a/lib/puppet/functions/assert_type.rb b/lib/puppet/functions/assert_type.rb
index 4e9f33fb1..7165dec70 100644
--- a/lib/puppet/functions/assert_type.rb
+++ b/lib/puppet/functions/assert_type.rb
@@ -1,39 +1,56 @@
# Returns the given value if it is an instance of the given type, and raises an error otherwise.
+# Optionally, if a block is given (accepting two parameters), it will be called instead of raising
+# an error. This to enable giving the user richer feedback, or to supply a default value.
#
# @example how to assert type
# # assert that `$b` is a non empty `String` and assign to `$a`
# $a = assert_type(String[1], $b)
#
+# @example using custom error message
+# $a = assert_type(String[1], $b) |$expected, $actual| { fail("The name cannot be empty") }
+#
+# @example using a warning and a default
+# $a = assert_type(String[1], $b) |$expected, $actual| { warning("Name is empty, using default") 'anonymous' }
+#
# See the documentation for "The Puppet Type System" for more information about types.
#
Puppet::Functions.create_function(:assert_type) do
dispatch :assert_type do
param 'Type', 'type'
- param 'Optional[Object]', 'value'
+ param 'Any', 'value'
+ optional_block_param 'Callable[Type, Type]', 'block'
end
dispatch :assert_type_s do
param 'String', 'type_string'
- param 'Optional[Object]', 'value'
+ param 'Any', 'value'
+ optional_block_param 'Callable[Type, Type]', 'block'
end
# @param type [Type] the type the value must be an instance of
- # @param value [Optional[Object]] the value to assert
+ # @param value [Object] the value to assert
#
- def assert_type(type, value)
+ def assert_type(type, value, block=nil)
unless Puppet::Pops::Types::TypeCalculator.instance?(type,value)
inferred_type = Puppet::Pops::Types::TypeCalculator.infer(value)
- # Do not give all the details - i.e. format as Integer, instead of Integer[n, n] for exact value, which
- # is just confusing. (OTOH: may need to revisit, or provide a better "type diff" output.
- #
- actual = Puppet::Pops::Types::TypeCalculator.generalize!(inferred_type)
- raise Puppet::ParseError, "assert_type(): Expected type #{type} does not match actual: #{actual}"
+ if block
+ # Give the inferred type to allow richer comparisson in the given block (if generalized
+ # information is lost).
+ #
+ value = block.call(nil, type, inferred_type)
+ else
+ # Do not give all the details - i.e. format as Integer, instead of Integer[n, n] for exact value, which
+ # is just confusing. (OTOH: may need to revisit, or provide a better "type diff" output.
+ #
+ actual = Puppet::Pops::Types::TypeCalculator.generalize!(inferred_type)
+ raise Puppet::ParseError, "assert_type(): Expected type #{type} does not match actual: #{actual}"
+ end
end
value
end
# @param type_string [String] the type the value must be an instance of given in String form
- # @param value [Optional[Object]] the value to assert
+ # @param value [Object] the value to assert
#
def assert_type_s(type_string, value)
t = Puppet::Pops::Types::TypeParser.new.parse(type_string)
diff --git a/lib/puppet/functions/each.rb b/lib/puppet/functions/each.rb
new file mode 100644
index 000000000..1b9ac937c
--- /dev/null
+++ b/lib/puppet/functions/each.rb
@@ -0,0 +1,111 @@
+# Applies a parameterized block to each element in a sequence of selected entries from the first
+# argument and returns the first argument.
+#
+# This function takes two mandatory arguments: the first should be an Array or a Hash or something that is
+# of enumerable type (integer, Integer range, or String), and the second
+# a parameterized block as produced by the puppet syntax:
+#
+# $a.each |$x| { ... }
+# each($a) |$x| { ... }
+#
+# When the first argument is an Array (or of enumerable type other than Hash), the parameterized block
+# should define one or two block parameters.
+# For each application of the block, the next element from the array is selected, and it is passed to
+# the block if the block has one parameter. If the block has two parameters, the first is the elements
+# index, and the second the value. The index starts from 0.
+#
+# $a.each |$index, $value| { ... }
+# each($a) |$index, $value| { ... }
+#
+# When the first argument is a Hash, the parameterized block should define one or two parameters.
+# When one parameter is defined, the iteration is performed with each entry as an array of `[key, value]`,
+# and when two parameters are defined the iteration is performed with key and value.
+#
+# $a.each |$entry| { ..."key ${$entry[0]}, value ${$entry[1]}" }
+# $a.each |$key, $value| { ..."key ${key}, value ${value}" }
+#
+# @example using each
+#
+# [1,2,3].each |$val| { ... } # 1, 2, 3
+# [5,6,7].each |$index, $val| { ... } # (0, 5), (1, 6), (2, 7)
+# {a=>1, b=>2, c=>3}].each |$val| { ... } # ['a', 1], ['b', 2], ['c', 3]
+# {a=>1, b=>2, c=>3}.each |$key, $val| { ... } # ('a', 1), ('b', 2), ('c', 3)
+# Integer[ 10, 20 ].each |$index, $value| { ... } # (0, 10), (1, 11) ...
+# "hello".each |$char| { ... } # 'h', 'e', 'l', 'l', 'o'
+# 3.each |$number| { ... } # 0, 1, 2
+#
+# @since 3.2 for Array and Hash
+# @since 3.5 for other enumerables
+# @note requires `parser = future`
+#
+Puppet::Functions.create_function(:each) do
+ dispatch :foreach_Hash_2 do
+ param 'Hash[Any, Any]', :hash
+ required_block_param 'Callable[2,2]', :block
+ end
+
+ dispatch :foreach_Hash_1 do
+ param 'Hash[Any, Any]', :hash
+ required_block_param 'Callable[1,1]', :block
+ end
+
+ dispatch :foreach_Enumerable_2 do
+ param 'Any', :enumerable
+ required_block_param 'Callable[2,2]', :block
+ end
+
+ dispatch :foreach_Enumerable_1 do
+ param 'Any', :enumerable
+ required_block_param 'Callable[1,1]', :block
+ end
+
+ def foreach_Hash_1(hash, pblock)
+ enumerator = hash.each_pair
+ hash.size.times do
+ pblock.call(nil, enumerator.next)
+ end
+ # produces the receiver
+ hash
+ end
+
+ def foreach_Hash_2(hash, pblock)
+ enumerator = hash.each_pair
+ hash.size.times do
+ pblock.call(nil, *enumerator.next)
+ end
+ # produces the receiver
+ hash
+ end
+
+ def foreach_Enumerable_1(enumerable, pblock)
+ enum = asserted_enumerable(enumerable)
+ begin
+ loop { pblock.call(nil, enum.next) }
+ rescue StopIteration
+ end
+ # produces the receiver
+ enumerable
+ end
+
+ def foreach_Enumerable_2(enumerable, pblock)
+ enum = asserted_enumerable(enumerable)
+ index = 0
+ begin
+ loop do
+ pblock.call(nil, index, enum.next)
+ index += 1
+ end
+ rescue StopIteration
+ end
+ # produces the receiver
+ enumerable
+ end
+
+ def asserted_enumerable(obj)
+ unless enum = Puppet::Pops::Types::Enumeration.enumerator(obj)
+ raise ArgumentError, ("#{self.class.name}(): wrong argument type (#{obj.class}; must be something enumerable.")
+ end
+ enum
+ end
+
+end
diff --git a/lib/puppet/functions/epp.rb b/lib/puppet/functions/epp.rb
new file mode 100644
index 000000000..baa0c1bed
--- /dev/null
+++ b/lib/puppet/functions/epp.rb
@@ -0,0 +1,54 @@
+# Evaluates an Embedded Puppet Template (EPP) file and returns the rendered text result as a String.
+#
+# The first argument to this function should be a `<MODULE NAME>/<TEMPLATE FILE>`
+# reference, which will load `<TEMPLATE FILE>` from a module's `templates`
+# directory. (For example, the reference `apache/vhost.conf.epp` will load the
+# file `<MODULES DIRECTORY>/apache/templates/vhost.conf.epp`.)
+#
+# The second argument is optional; if present, it should be a hash containing parameters for the
+# template. (See below.)
+#
+# EPP supports the following tags:
+#
+# * `<%= puppet expression %>` - This tag renders the value of the expression it contains.
+# * `<% puppet expression(s) %>` - This tag will execute the expression(s) it contains, but renders nothing.
+# * `<%# comment %>` - The tag and its content renders nothing.
+# * `<%%` or `%%>` - Renders a literal `<%` or `%>` respectively.
+# * `<%-` - Same as `<%` but suppresses any leading whitespace.
+# * `-%>` - Same as `%>` but suppresses any trailing whitespace on the same line (including line break).
+# * `<%- |parameters| -%>` - When placed as the first tag declares the template's parameters.
+#
+# File based EPP supports the following visibilities of variables in scope:
+#
+# * Global scope (i.e. top + node scopes) - global scope is always visible
+# * Global + all given arguments - if the EPP template does not declare parameters, and arguments are given
+# * Global + declared parameters - if the EPP declares parameters, given argument names must match
+#
+# EPP supports parameters by placing an optional parameter list as the very first element in the EPP. As an example,
+# `<%- |$x, $y, $z = 'unicorn'| -%>` when placed first in the EPP text declares that the parameters `x` and `y` must be
+# given as template arguments when calling `inline_epp`, and that `z` if not given as a template argument
+# defaults to `'unicorn'`. Template parameters are available as variables, e.g.arguments `$x`, `$y` and `$z` in the example.
+# Note that `<%-` must be used or any leading whitespace will be interpreted as text
+#
+# Arguments are passed to the template by calling `epp` with a Hash as the last argument, where parameters
+# are bound to values, e.g. `epp('...', {'x'=>10, 'y'=>20})`. Excess arguments may be given
+# (i.e. undeclared parameters) only if the EPP templates does not declare any parameters at all.
+# Template parameters shadow variables in outer scopes. File based epp does never have access to variables in the
+# scope where the `epp` function is called from.
+#
+# @see function inline_epp for examples of EPP
+# @since 3.5
+# @note Requires Future Parser
+Puppet::Functions.create_function(:epp, Puppet::Functions::InternalFunction) do
+
+ dispatch :epp do
+ scope_param()
+ param 'String', 'path'
+ param 'Hash[Pattern[/^\w+$/], Any]', 'parameters'
+ arg_count(1, 2)
+ end
+
+ def epp(scope, path, parameters = nil)
+ Puppet::Pops::Evaluator::EppEvaluator.epp(scope, path, scope.compiler.environment, parameters)
+ end
+end \ No newline at end of file
diff --git a/lib/puppet/functions/filter.rb b/lib/puppet/functions/filter.rb
new file mode 100644
index 000000000..0654d9c9c
--- /dev/null
+++ b/lib/puppet/functions/filter.rb
@@ -0,0 +1,113 @@
+# Applies a parameterized block to each element in a sequence of entries from the first
+# argument and returns an array or hash (same type as left operand for array/hash, and array for
+# other enumerable types) with the entries for which the block evaluates to `true`.
+#
+# This function takes two mandatory arguments: the first should be an Array, a Hash, or an
+# Enumerable object (integer, Integer range, or String),
+# and the second a parameterized block as produced by the puppet syntax:
+#
+# $a.filter |$x| { ... }
+# filter($a) |$x| { ... }
+#
+# When the first argument is something other than a Hash, the block is called with each entry in turn.
+# When the first argument is a Hash the entry is an array with `[key, value]`.
+#
+# @example Using filter with one parameter
+#
+# # selects all that end with berry
+# $a = ["raspberry", "blueberry", "orange"]
+# $a.filter |$x| { $x =~ /berry$/ } # rasberry, blueberry
+#
+# If the block defines two parameters, they will be set to `index, value` (with index starting at 0) for all
+# enumerables except Hash, and to `key, value` for a Hash.
+#
+# @example Using filter with two parameters
+#
+# # selects all that end with 'berry' at an even numbered index
+# $a = ["raspberry", "blueberry", "orange"]
+# $a.filter |$index, $x| { $index % 2 == 0 and $x =~ /berry$/ } # raspberry
+#
+# # selects all that end with 'berry' and value >= 1
+# $a = {"raspberry"=>0, "blueberry"=>1, "orange"=>1}
+# $a.filter |$key, $x| { $x =~ /berry$/ and $x >= 1 } # blueberry
+#
+# @since 3.4 for Array and Hash
+# @since 3.5 for other enumerables
+# @note requires `parser = future`
+#
+Puppet::Functions.create_function(:filter) do
+ dispatch :filter_Hash_2 do
+ param 'Hash[Any, Any]', :hash
+ required_block_param 'Callable[2,2]', :block
+ end
+
+ dispatch :filter_Hash_1 do
+ param 'Hash[Any, Any]', :hash
+ required_block_param 'Callable[1,1]', :block
+ end
+
+ dispatch :filter_Enumerable_2 do
+ param 'Any', :enumerable
+ required_block_param 'Callable[2,2]', :block
+ end
+
+ dispatch :filter_Enumerable_1 do
+ param 'Any', :enumerable
+ required_block_param 'Callable[1,1]', :block
+ end
+
+ def filter_Hash_1(hash, pblock)
+ result = hash.select {|x, y| pblock.call(self, [x, y]) }
+ # Ruby 1.8.7 returns Array
+ result = Hash[result] unless result.is_a? Hash
+ result
+ end
+
+ def filter_Hash_2(hash, pblock)
+ result = hash.select {|x, y| pblock.call(self, x, y) }
+ # Ruby 1.8.7 returns Array
+ result = Hash[result] unless result.is_a? Hash
+ result
+ end
+
+ def filter_Enumerable_1(enumerable, pblock)
+ result = []
+ index = 0
+ enum = asserted_enumerable(enumerable)
+ begin
+ loop do
+ it = enum.next
+ if pblock.call(nil, it) == true
+ result << it
+ end
+ end
+ rescue StopIteration
+ end
+ result
+ end
+
+ def filter_Enumerable_2(enumerable, pblock)
+ result = []
+ index = 0
+ enum = asserted_enumerable(enumerable)
+ begin
+ loop do
+ it = enum.next
+ if pblock.call(nil, index, it) == true
+ result << it
+ end
+ index += 1
+ end
+ rescue StopIteration
+ end
+ result
+ end
+
+ def asserted_enumerable(obj)
+ unless enum = Puppet::Pops::Types::Enumeration.enumerator(obj)
+ raise ArgumentError, ("#{self.class.name}(): wrong argument type (#{obj.class}; must be something enumerable.")
+ end
+ enum
+ end
+
+end
diff --git a/lib/puppet/functions/inline_epp.rb b/lib/puppet/functions/inline_epp.rb
new file mode 100644
index 000000000..31a966334
--- /dev/null
+++ b/lib/puppet/functions/inline_epp.rb
@@ -0,0 +1,88 @@
+# Evaluates an Embedded Puppet Template (EPP) string and returns the rendered text result as a String.
+#
+# EPP support the following tags:
+#
+# * `<%= puppet expression %>` - This tag renders the value of the expression it contains.
+# * `<% puppet expression(s) %>` - This tag will execute the expression(s) it contains, but renders nothing.
+# * `<%# comment %>` - The tag and its content renders nothing.
+# * `<%%` or `%%>` - Renders a literal `<%` or `%>` respectively.
+# * `<%-` - Same as `<%` but suppresses any leading whitespace.
+# * `-%>` - Same as `%>` but suppresses any trailing whitespace on the same line (including line break).
+# * `<%- |parameters| -%>` - When placed as the first tag declares the template's parameters.
+#
+# Inline EPP supports the following visibilities of variables in scope which depends on how EPP parameters
+# are used - see further below:
+#
+# * Global scope (i.e. top + node scopes) - global scope is always visible
+# * Global + Enclosing scope - if the EPP template does not declare parameters, and no arguments are given
+# * Global + all given arguments - if the EPP template does not declare parameters, and arguments are given
+# * Global + declared parameters - if the EPP declares parameters, given argument names must match
+#
+# EPP supports parameters by placing an optional parameter list as the very first element in the EPP. As an example,
+# `<%- |$x, $y, $z='unicorn'| -%>` when placed first in the EPP text declares that the parameters `x` and `y` must be
+# given as template arguments when calling `inline_epp`, and that `z` if not given as a template argument
+# defaults to `'unicorn'`. Template parameters are available as variables, e.g.arguments `$x`, `$y` and `$z` in the example.
+# Note that `<%-` must be used or any leading whitespace will be interpreted as text
+#
+# Arguments are passed to the template by calling `inline_epp` with a Hash as the last argument, where parameters
+# are bound to values, e.g. `inline_epp('...', {'x'=>10, 'y'=>20})`. Excess arguments may be given
+# (i.e. undeclared parameters) only if the EPP templates does not declare any parameters at all.
+# Template parameters shadow variables in outer scopes.
+#
+# Note: An inline template is best stated using a single-quoted string, or a heredoc since a double-quoted string
+# is subject to expression interpolation before the string is parsed as an EPP template. Here are examples
+# (using heredoc to define the EPP text):
+#
+# @example Various Examples using `inline_epp`
+#
+# # produces 'Hello local variable world!'
+# $x ='local variable'
+# inline_epptemplate(@(END:epp))
+# <%- |$x| -%>
+# Hello <%= $x %> world!
+# END
+#
+# # produces 'Hello given argument world!'
+# $x ='local variable world'
+# inline_epptemplate(@(END:epp), { x =>'given argument'})
+# <%- |$x| -%>
+# Hello <%= $x %> world!
+# END
+#
+# # produces 'Hello given argument world!'
+# $x ='local variable world'
+# inline_epptemplate(@(END:epp), { x =>'given argument'})
+# <%- |$x| -%>
+# Hello <%= $x %>!
+# END
+#
+# # results in error, missing value for y
+# $x ='local variable world'
+# inline_epptemplate(@(END:epp), { x =>'given argument'})
+# <%- |$x, $y| -%>
+# Hello <%= $x %>!
+# END
+#
+# # Produces 'Hello given argument planet'
+# $x ='local variable world'
+# inline_epptemplate(@(END:epp), { x =>'given argument'})
+# <%- |$x, $y=planet| -%>
+# Hello <%= $x %> <%= $y %>!
+# END
+#
+# @since 3.5
+# @note Requires Future Parser
+#
+Puppet::Functions.create_function(:inline_epp, Puppet::Functions::InternalFunction) do
+
+ dispatch :inline_epp do
+ scope_param()
+ param 'String', 'template'
+ param 'Hash[Pattern[/^\w+$/], Any]', 'parameters'
+ arg_count(1, 2)
+ end
+
+ def inline_epp(scope, template, parameters = nil)
+ Puppet::Pops::Evaluator::EppEvaluator.inline_epp(scope, template, parameters)
+ end
+end
diff --git a/lib/puppet/functions/map.rb b/lib/puppet/functions/map.rb
new file mode 100644
index 000000000..2141d1e81
--- /dev/null
+++ b/lib/puppet/functions/map.rb
@@ -0,0 +1,97 @@
+# Applies a parameterized block to each element in a sequence of entries from the first
+# argument and returns an array with the result of each invocation of the parameterized block.
+#
+# This function takes two mandatory arguments: the first should be an Array, Hash, or of Enumerable type
+# (integer, Integer range, or String), and the second a parameterized block as produced by the puppet syntax:
+#
+# $a.map |$x| { ... }
+# map($a) |$x| { ... }
+#
+# When the first argument `$a` is an Array or of enumerable type, the block is called with each entry in turn.
+# When the first argument is a hash the entry is an array with `[key, value]`.
+#
+# @example Using map with two arguments
+#
+# # Turns hash into array of values
+# $a.map |$x|{ $x[1] }
+#
+# # Turns hash into array of keys
+# $a.map |$x| { $x[0] }
+#
+# When using a block with 2 parameters, the element's index (starting from 0) for an array, and the key for a hash
+# is given to the block's first parameter, and the value is given to the block's second parameter.args.
+#
+# @example Using map with two arguments
+#
+# # Turns hash into array of values
+# $a.map |$key,$val|{ $val }
+#
+# # Turns hash into array of keys
+# $a.map |$key,$val|{ $key }
+#
+# @since 3.4 for Array and Hash
+# @since 3.5 for other enumerables, and support for blocks with 2 parameters
+# @note requires `parser = future`
+#
+Puppet::Functions.create_function(:map) do
+ dispatch :map_Hash_2 do
+ param 'Hash[Any, Any]', :hash
+ required_block_param 'Callable[2,2]', :block
+ end
+
+ dispatch :map_Hash_1 do
+ param 'Hash[Any, Any]', :hash
+ required_block_param 'Callable[1,1]', :block
+ end
+
+ dispatch :map_Enumerable_2 do
+ param 'Any', :enumerable
+ required_block_param 'Callable[2,2]', :block
+ end
+
+ dispatch :map_Enumerable_1 do
+ param 'Any', :enumerable
+ required_block_param 'Callable[1,1]', :block
+ end
+
+ def map_Hash_1(hash, pblock)
+ hash.map {|x, y| pblock.call(nil, [x, y]) }
+ end
+
+ def map_Hash_2(hash, pblock)
+ hash.map {|x, y| pblock.call(nil, x, y) }
+ end
+
+ def map_Enumerable_1(enumerable, pblock)
+ result = []
+ index = 0
+ enum = asserted_enumerable(enumerable)
+ begin
+ loop { result << pblock.call(nil, enum.next) }
+ rescue StopIteration
+ end
+ result
+ end
+
+ def map_Enumerable_2(enumerable, pblock)
+ result = []
+ index = 0
+ enum = asserted_enumerable(enumerable)
+ begin
+ loop do
+ result << pblock.call(nil, index, enum.next)
+ index = index +1
+ end
+ rescue StopIteration
+ end
+ result
+ end
+
+ def asserted_enumerable(obj)
+ unless enum = Puppet::Pops::Types::Enumeration.enumerator(obj)
+ raise ArgumentError, ("#{self.class.name}(): wrong argument type (#{obj.class}; must be something enumerable.")
+ end
+ enum
+ end
+
+end
diff --git a/lib/puppet/functions/match.rb b/lib/puppet/functions/match.rb
new file mode 100644
index 000000000..8808a29b6
--- /dev/null
+++ b/lib/puppet/functions/match.rb
@@ -0,0 +1,102 @@
+# Returns the match result of matching a String or Array[String] with one of:
+#
+# * Regexp
+# * String - transformed to a Regexp
+# * Pattern type
+# * Regexp type
+#
+# Returns An Array with the entire match at index 0, and each subsequent submatch at index 1-n.
+# If there was no match, nil (ie. undef) is returned. If the value to match is an Array, a array
+# with mapped match results is returned.
+#
+# @example matching
+# "abc123".match(/([a-z]+)[1-9]+/) # => ["abc"]
+# "abc123".match(/([a-z]+)([1-9]+)/) # => ["abc", "123"]
+#
+# See the documentation for "The Puppet Type System" for more information about types.
+# @since 3.7.0
+#
+Puppet::Functions.create_function(:match) do
+ dispatch :match do
+ param 'String', 'string'
+ param 'Variant[Any, Type]', 'pattern'
+ end
+
+ dispatch :enumerable_match do
+ param 'Array[String]', 'string'
+ param 'Variant[Any, Type]', 'pattern'
+ end
+
+ def initialize(closure_scope, loader)
+ super
+
+ # Make this visitor shared among all instantiations of this function since it is faster.
+ # This can be used because it is not possible to replace
+ # a puppet runtime (where this function is) without a reboot. If you model a function in a module after
+ # this class, use a regular instance variable instead to enable reloading of the module without reboot
+ #
+ @@match_visitor ||= Puppet::Pops::Visitor.new(self, "match", 1, 1)
+ end
+
+ # Matches given string against given pattern and returns an Array with matches.
+ # @param string [String] the string to match
+ # @param pattern [String, Regexp, Puppet::Pops::Types::PPatternType, Puppet::Pops::PRegexpType, Array] the pattern
+ # @return [Array<String>] matches where first match is the entire match, and index 1-n are captures from left to right
+ #
+ def match(string, pattern)
+ @@match_visitor.visit_this_1(self, pattern, string)
+ end
+
+ # Matches given Array[String] against given pattern and returns an Array with mapped match results.
+ #
+ # @param array [Array<String>] the array of strings to match
+ # @param pattern [String, Regexp, Puppet::Pops::Types::PPatternType, Puppet::Pops::PRegexpType, Array] the pattern
+ # @return [Array<Array<String, nil>>] Array with matches (see {#match}), non matching entries produce a nil entry
+ #
+ def enumerable_match(array, pattern)
+ array.map {|s| match(s, pattern) }
+ end
+
+ protected
+
+ def match_Object(obj, s)
+ msg = "match() expects pattern of T, where T is String, Regexp, Regexp[r], Pattern[p], or Array[T]. Got #{obj.class}"
+ raise ArgumentError, msg
+ end
+
+ def match_String(pattern_string, s)
+ do_match(s, Regexp.new(pattern_string))
+ end
+
+ def match_Regexp(regexp, s)
+ do_match(s, regexp)
+ end
+
+ def match_PRegexpType(regexp_t, s)
+ raise ArgumentError, "Given Regexp Type has no regular expression" unless regexp_t.pattern
+ do_match(s, regexp_t.regexp)
+ end
+
+ def match_PPatternType(pattern_t, s)
+ # Since we want the actual match result (not just a boolean), an iteration over
+ # Pattern's regular expressions is needed. (They are of PRegexpType)
+ result = nil
+ pattern_t.patterns.find {|pattern| result = match(s, pattern) }
+ result
+ end
+
+ # Returns the first matching entry
+ def match_Array(array, s)
+ result = nil
+ array.flatten.find {|entry| result = match(s, entry) }
+ result
+ end
+
+ private
+
+ def do_match(s, regexp)
+ if result = regexp.match(s)
+ result.to_a
+ end
+ end
+end
diff --git a/lib/puppet/functions/reduce.rb b/lib/puppet/functions/reduce.rb
new file mode 100644
index 000000000..5b54e41c5
--- /dev/null
+++ b/lib/puppet/functions/reduce.rb
@@ -0,0 +1,94 @@
+# Applies a parameterized block to each element in a sequence of entries from the first
+# argument (_the enumerable_) and returns the last result of the invocation of the parameterized block.
+#
+# This function takes two mandatory arguments: the first should be an Array, Hash, or something of
+# enumerable type, and the last a parameterized block as produced by the puppet syntax:
+#
+# $a.reduce |$memo, $x| { ... }
+# reduce($a) |$memo, $x| { ... }
+#
+# When the first argument is an Array or someting of an enumerable type, the block is called with each entry in turn.
+# When the first argument is a hash each entry is converted to an array with `[key, value]` before being
+# fed to the block. An optional 'start memo' value may be supplied as an argument between the array/hash
+# and mandatory block.
+#
+# $a.reduce(start) |$memo, $x| { ... }
+# reduce($a, start) |$memo, $x| { ... }
+#
+# If no 'start memo' is given, the first invocation of the parameterized block will be given the first and second
+# elements of the enumeration, and if the enumerable has fewer than 2 elements, the first
+# element is produced as the result of the reduction without invocation of the block.
+#
+# On each subsequent invocation, the produced value of the invoked parameterized block is given as the memo in the
+# next invocation.
+#
+# @example Using reduce
+#
+# # Reduce an array
+# $a = [1,2,3]
+# $a.reduce |$memo, $entry| { $memo + $entry }
+# #=> 6
+#
+# # Reduce hash values
+# $a = {a => 1, b => 2, c => 3}
+# $a.reduce |$memo, $entry| { [sum, $memo[1]+$entry[1]] }
+# #=> [sum, 6]
+#
+# # reverse a string
+# "abc".reduce |$memo, $char| { "$char$memo" }
+# #=>"cbe"
+#
+# It is possible to provide a starting 'memo' as an argument.
+#
+# @example Using reduce with given start 'memo'
+#
+# # Reduce an array
+# $a = [1,2,3]
+# $a.reduce(4) |$memo, $entry| { $memo + $entry }
+# #=> 10
+#
+# # Reduce hash values
+# $a = {a => 1, b => 2, c => 3}
+# $a.reduce([na, 4]) |$memo, $entry| { [sum, $memo[1]+$entry[1]] }
+# #=> [sum, 10]
+#
+# @example Using reduce with an Integer range
+#
+# Integer[1,4].reduce |$memo, $x| { $memo + $x }
+# #=> 10
+#
+# @since 3.2 for Array and Hash
+# @since 3.5 for additional enumerable types
+# @note requires `parser = future`.
+#
+Puppet::Functions.create_function(:reduce) do
+
+ dispatch :reduce_without_memo do
+ param 'Any', :enumerable
+ required_block_param 'Callable[2,2]', :block
+ end
+
+ dispatch :reduce_with_memo do
+ param 'Any', :enumerable
+ param 'Any', :memo
+ required_block_param 'Callable[2,2]', :block
+ end
+
+ def reduce_without_memo(enumerable, pblock)
+ enum = asserted_enumerable(enumerable)
+ enum.reduce {|memo, x| pblock.call(nil, memo, x) }
+ end
+
+ def reduce_with_memo(enumerable, given_memo, pblock)
+ enum = asserted_enumerable(enumerable)
+ enum.reduce(given_memo) {|memo, x| pblock.call(nil, memo, x) }
+ end
+
+ def asserted_enumerable(obj)
+ unless enum = Puppet::Pops::Types::Enumeration.enumerator(obj)
+ raise ArgumentError, ("#{self.class.name}(): wrong argument type (#{obj.class}; must be something enumerable.")
+ end
+ enum
+ end
+
+end
diff --git a/lib/puppet/functions/slice.rb b/lib/puppet/functions/slice.rb
new file mode 100644
index 000000000..ef3a2932a
--- /dev/null
+++ b/lib/puppet/functions/slice.rb
@@ -0,0 +1,126 @@
+# Applies a parameterized block to each _slice_ of elements in a sequence of selected entries from the first
+# argument and returns the first argument, or if no block is given returns a new array with a concatenation of
+# the slices.
+#
+# This function takes two mandatory arguments: the first, `$a`, should be an Array, Hash, or something of
+# enumerable type (integer, Integer range, or String), and the second, `$n`, the number of elements to include
+# in each slice. The optional third argument should be a a parameterized block as produced by the puppet syntax:
+#
+# $a.slice($n) |$x| { ... }
+# slice($a) |$x| { ... }
+#
+# The parameterized block should have either one parameter (receiving an array with the slice), or the same number
+# of parameters as specified by the slice size (each parameter receiving its part of the slice).
+# In case there are fewer remaining elements than the slice size for the last slice it will contain the remaining
+# elements. When the block has multiple parameters, excess parameters are set to undef for an array or
+# enumerable type, and to empty arrays for a Hash.
+#
+# $a.slice(2) |$first, $second| { ... }
+#
+# When the first argument is a Hash, each `key,value` entry is counted as one, e.g, a slice size of 2 will produce
+# an array of two arrays with key, and value.
+#
+# @example Using slice with Hash
+#
+# $a.slice(2) |$entry| { notice "first ${$entry[0]}, second ${$entry[1]}" }
+# $a.slice(2) |$first, $second| { notice "first ${first}, second ${second}" }
+#
+# When called without a block, the function produces a concatenated result of the slices.
+#
+# @example Using slice without a block
+#
+# slice([1,2,3,4,5,6], 2) # produces [[1,2], [3,4], [5,6]]
+# slice(Integer[1,6], 2) # produces [[1,2], [3,4], [5,6]]
+# slice(4,2) # produces [[0,1], [2,3]]
+# slice('hello',2) # produces [[h, e], [l, l], [o]]
+#
+# @since 3.2 for Array and Hash
+# @since 3.5 for additional enumerable types
+# @note requires `parser = future`.
+#
+Puppet::Functions.create_function(:slice) do
+ dispatch :slice_Hash do
+ param 'Hash[Any, Any]', :hash
+ param 'Integer[1, default]', :slize_size
+ optional_block_param
+ end
+
+ dispatch :slice_Enumerable do
+ param 'Any', :enumerable
+ param 'Integer[1, default]', :slize_size
+ optional_block_param
+ end
+
+ def slice_Hash(hash, slice_size, pblock = nil)
+ result = slice_Common(hash, slice_size, [], pblock)
+ pblock ? hash : result
+ end
+
+ def slice_Enumerable(enumerable, slice_size, pblock = nil)
+ enum = asserted_enumerable(enumerable)
+ result = slice_Common(enum, slice_size, nil, pblock)
+ pblock ? enumerable : result
+ end
+
+ def slice_Common(o, slice_size, filler, pblock)
+ serving_size = asserted_slice_serving_size(pblock, slice_size)
+
+ enumerator = o.each_slice(slice_size)
+ result = []
+ if serving_size == 1
+ begin
+ if pblock
+ loop do
+ pblock.call(nil, enumerator.next)
+ end
+ else
+ loop do
+ result << enumerator.next
+ end
+ end
+ rescue StopIteration
+ end
+ else
+ begin
+ loop do
+ a = enumerator.next
+ if a.size < serving_size
+ a = a.dup.fill(filler, a.length...serving_size)
+ end
+ pblock.call(nil, *a)
+ end
+ rescue StopIteration
+ end
+ end
+ if pblock
+ o
+ else
+ result
+ end
+ end
+
+ def asserted_slice_serving_size(pblock, slice_size)
+ if pblock
+ serving_size = pblock.last_captures_rest? ? slice_size : pblock.parameter_count
+ else
+ serving_size = 1
+ end
+ if serving_size == 0
+ raise ArgumentError, "slice(): block must define at least one parameter. Block has 0."
+ end
+ unless serving_size == 1 || serving_size == slice_size
+ raise ArgumentError, "slice(): block must define one parameter, or " +
+ "the same number of parameters as the given size of the slice (#{slice_size}). Block has #{serving_size}; "+
+ pblock.parameter_names.join(', ')
+ end
+ serving_size
+ end
+
+ def asserted_enumerable(obj)
+ unless enum = Puppet::Pops::Types::Enumeration.enumerator(obj)
+ raise ArgumentError, ("#{self.class.name}(): wrong argument type (#{obj.class}; must be something enumerable.")
+ end
+ enum
+ end
+
+end
diff --git a/lib/puppet/functions/with.rb b/lib/puppet/functions/with.rb
new file mode 100644
index 000000000..6dd51ec18
--- /dev/null
+++ b/lib/puppet/functions/with.rb
@@ -0,0 +1,23 @@
+# Call a lambda with the given arguments. Since the parameters of the lambda
+# are local to the lambda's scope, this can be used to create private sections
+# of logic in a class so that the variables are not visible outside of the
+# class.
+#
+# @example Using with
+#
+# # notices the array [1, 2, 'foo']
+# with(1, 2, 'foo') |$x, $y, $z| { notice [$x, $y, $z] }
+#
+# @since 3.7.0
+#
+Puppet::Functions.create_function(:with) do
+ dispatch :with do
+ param 'Any', 'arg'
+ arg_count(0, :default)
+ required_block_param
+ end
+
+ def with(*args)
+ args[-1].call({}, *args[0..-2])
+ end
+end
diff --git a/lib/puppet/indirector/catalog/compiler.rb b/lib/puppet/indirector/catalog/compiler.rb
index 0804d1819..6f4e2f3e4 100644
--- a/lib/puppet/indirector/catalog/compiler.rb
+++ b/lib/puppet/indirector/catalog/compiler.rb
@@ -17,7 +17,7 @@ class Puppet::Resource::Catalog::Compiler < Puppet::Indirector::Code
raise ArgumentError, "Facts but no fact format provided for #{request.key}"
end
- Puppet::Util::Profiler.profile("Found facts") do
+ Puppet::Util::Profiler.profile("Found facts", [:compiler, :find_facts]) do
# If the facts were encoded as yaml, then the param reconstitution system
# in Network::HTTP::Handler will automagically deserialize the value.
if text_facts.is_a?(Puppet::Node::Facts)
@@ -65,7 +65,7 @@ class Puppet::Resource::Catalog::Compiler < Puppet::Indirector::Code
end
def initialize
- Puppet::Util::Profiler.profile("Setup server facts for compiling") do
+ Puppet::Util::Profiler.profile("Setup server facts for compiling", [:compiler, :init_server_facts]) do
set_server_facts
end
end
@@ -90,7 +90,7 @@ class Puppet::Resource::Catalog::Compiler < Puppet::Indirector::Code
config = nil
benchmark(:notice, str) do
- Puppet::Util::Profiler.profile(str) do
+ Puppet::Util::Profiler.profile(str, [:compiler, :compile, node.environment, node.name]) do
begin
config = Puppet::Parser::Compiler.compile(node)
rescue Puppet::Error => detail
@@ -105,7 +105,7 @@ class Puppet::Resource::Catalog::Compiler < Puppet::Indirector::Code
# Turn our host name into a node object.
def find_node(name, environment, transaction_uuid)
- Puppet::Util::Profiler.profile("Found node information") do
+ Puppet::Util::Profiler.profile("Found node information", [:compiler, :find_node]) do
node = nil
begin
node = Puppet::Node.indirection.find(name, :environment => environment,
diff --git a/lib/puppet/indirector/data_binding/hiera.rb b/lib/puppet/indirector/data_binding/hiera.rb
index 7fbc63782..e6e609d55 100644
--- a/lib/puppet/indirector/data_binding/hiera.rb
+++ b/lib/puppet/indirector/data_binding/hiera.rb
@@ -1,50 +1,7 @@
-require 'puppet/indirector/code'
+require 'puppet/indirector/hiera'
require 'hiera/scope'
-class Puppet::DataBinding::Hiera < Puppet::Indirector::Code
+class Puppet::DataBinding::Hiera < Puppet::Indirector::Hiera
desc "Retrieve data using Hiera."
-
- def initialize(*args)
- if ! Puppet.features.hiera?
- raise "Hiera terminus not supported without hiera library"
- end
- super
- end
-
- if defined?(::Psych::SyntaxError)
- DataBindingExceptions = [::StandardError, ::Psych::SyntaxError]
- else
- DataBindingExceptions = [::StandardError]
- end
-
- def find(request)
- hiera.lookup(request.key, nil, Hiera::Scope.new(request.options[:variables]), nil, nil)
- rescue *DataBindingExceptions => detail
- raise Puppet::DataBinding::LookupError.new(detail.message, detail)
- end
-
- private
-
- def self.hiera_config
- hiera_config = Puppet.settings[:hiera_config]
- config = {}
-
- if Puppet::FileSystem.exist?(hiera_config)
- config = Hiera::Config.load(hiera_config)
- else
- Puppet.warning "Config file #{hiera_config} not found, using Hiera defaults"
- end
-
- config[:logger] = 'puppet'
- config
- end
-
- def self.hiera
- @hiera ||= Hiera.new(:config => hiera_config)
- end
-
- def hiera
- self.class.hiera
- end
end
diff --git a/lib/puppet/indirector/facts/couch.rb b/lib/puppet/indirector/facts/couch.rb
index 54980eb14..9cbd0a3dd 100644
--- a/lib/puppet/indirector/facts/couch.rb
+++ b/lib/puppet/indirector/facts/couch.rb
@@ -2,7 +2,9 @@ require 'puppet/node/facts'
require 'puppet/indirector/couch'
class Puppet::Node::Facts::Couch < Puppet::Indirector::Couch
- desc "Store facts in CouchDB. This should not be used with the inventory service;
+ desc "DEPRECATED. This terminus will be removed in Puppet 4.0.
+
+ Store facts in CouchDB. This should not be used with the inventory service;
it is for more obscure custom integrations. If you are wondering whether you
should use it, you shouldn't; use PuppetDB instead."
# Return the facts object or nil if there is no document
diff --git a/lib/puppet/indirector/facts/facter.rb b/lib/puppet/indirector/facts/facter.rb
index d704f91fd..7f65e3488 100644
--- a/lib/puppet/indirector/facts/facter.rb
+++ b/lib/puppet/indirector/facts/facter.rb
@@ -6,86 +6,73 @@ class Puppet::Node::Facts::Facter < Puppet::Indirector::Code
between Puppet and Facter. It's only `somewhat` abstract because it always
returns the local host's facts, regardless of what you attempt to find."
- private
-
- def self.reload_facter
- Facter.clear
- Facter.loadfacts
+ def destroy(facts)
+ raise Puppet::DevError, 'You cannot destroy facts in the code store; it is only used for getting facts from Facter'
end
- def self.load_fact_plugins
- # Add any per-module fact directories to the factpath
- module_fact_dirs = Puppet.lookup(:current_environment).modulepath.collect do |d|
- ["lib", "plugins"].map do |subdirectory|
- Dir.glob("#{d}/*/#{subdirectory}/facter")
- end
- end.flatten
- dirs = module_fact_dirs + Puppet[:factpath].split(File::PATH_SEPARATOR)
- dirs.uniq.each do |dir|
- load_facts_in_dir(dir)
- end
+ def save(facts)
+ raise Puppet::DevError, 'You cannot save facts to the code store; it is only used for getting facts from Facter'
end
- def self.setup_external_facts(request)
- # Add any per-module fact directories to the factpath
- external_facts_dirs = []
- request.environment.modules.each do |m|
- if m.has_external_facts?
- Puppet.info "Loading external facts from #{m.plugin_fact_directory}"
- external_facts_dirs << m.plugin_fact_directory
- end
- end
-
- # Add system external fact directory if it exists
- if File.directory?(Puppet[:pluginfactdest])
- external_facts_dirs << Puppet[:pluginfactdest]
- end
-
- # Add to facter config
- Facter.search_external external_facts_dirs
+ # Lookup a host's facts up in Facter.
+ def find(request)
+ Facter.reset
+ self.class.setup_external_search_paths(request) if Puppet.features.external_facts?
+ self.class.setup_search_paths(request)
+ result = Puppet::Node::Facts.new(request.key, Facter.to_hash)
+ result.add_local_facts
+ Puppet[:stringify_facts] ? result.stringify : result.sanitize
+ result
end
- def self.load_facts_in_dir(dir)
- return unless FileTest.directory?(dir)
+ private
- Dir.chdir(dir) do
- Dir.glob("*.rb").each do |file|
- fqfile = ::File.join(dir, file)
- begin
- Puppet.info "Loading facts in #{fqfile}"
- ::Timeout::timeout(Puppet[:configtimeout]) do
- load file
- end
- rescue SystemExit,NoMemoryError
- raise
- rescue Exception => detail
- Puppet.warning "Could not load fact file #{fqfile}: #{detail}"
+ def self.setup_search_paths(request)
+ # Add any per-module fact directories to facter's search path
+ dirs = request.environment.modulepath.collect do |dir|
+ ['lib', 'plugins'].map do |subdirectory|
+ Dir.glob("#{dir}/*/#{subdirectory}/facter")
+ end
+ end.flatten + Puppet[:factpath].split(File::PATH_SEPARATOR)
+
+ dirs = dirs.select do |dir|
+ next false unless FileTest.directory?(dir)
+
+ # Even through we no longer directly load facts in the terminus,
+ # print out each .rb in the facts directory as module
+ # developers may find that information useful for debugging purposes
+ if Puppet::Util::Log.sendlevel?(:info)
+ Puppet.info "Loading facts"
+ Dir.glob("#{dir}/*.rb").each do |file|
+ Puppet.debug "Loading facts from #{file}"
end
end
- end
- end
- public
+ true
+ end
- def destroy(facts)
- raise Puppet::DevError, "You cannot destroy facts in the code store; it is only used for getting facts from Facter"
+ Facter.search *dirs
end
- # Look a host's facts up in Facter.
- def find(request)
- self.class.setup_external_facts(request) if Puppet.features.external_facts?
- self.class.reload_facter
- self.class.load_fact_plugins
- result = Puppet::Node::Facts.new(request.key, Facter.to_hash)
-
- result.add_local_facts
- Puppet[:stringify_facts] ? result.stringify : result.sanitize
+ def self.setup_external_search_paths(request)
+ # Add any per-module external fact directories to facter's external search path
+ dirs = []
+ request.environment.modules.each do |m|
+ if m.has_external_facts?
+ dir = m.plugin_fact_directory
+ Puppet.debug "Loading external facts from #{dir}"
+ dirs << dir
+ end
+ end
- result
- end
+ # Add system external fact directory if it exists
+ if FileTest.directory?(Puppet[:pluginfactdest])
+ dir = Puppet[:pluginfactdest]
+ Puppet.debug "Loading external facts from #{dir}"
+ dirs << dir
+ end
- def save(facts)
- raise Puppet::DevError, "You cannot save facts to the code store; it is only used for getting facts from Facter"
+ Facter.search_external dirs
end
end
diff --git a/lib/puppet/indirector/file_bucket_file/file.rb b/lib/puppet/indirector/file_bucket_file/file.rb
index 14d9f1087..a356033b6 100644
--- a/lib/puppet/indirector/file_bucket_file/file.rb
+++ b/lib/puppet/indirector/file_bucket_file/file.rb
@@ -87,7 +87,10 @@ module Puppet::FileBucketFile
Puppet::FileSystem.touch(contents_file)
else
Puppet::FileSystem.open(contents_file, 0440, 'wb') do |of|
- of.write(bucket_file.contents)
+ # PUP-1044 writes all of the contents
+ bucket_file.stream() do |src|
+ FileUtils.copy_stream(src, of)
+ end
end
end
@@ -124,8 +127,8 @@ module Puppet::FileBucketFile
# @param contents_file [Object] Opaque file path
# @param bucket_file [IO]
def verify_identical_file!(contents_file, bucket_file)
- if bucket_file.contents.size == Puppet::FileSystem.size(contents_file)
- if Puppet::FileSystem.compare_stream(contents_file, bucket_file.stream)
+ if bucket_file.size == Puppet::FileSystem.size(contents_file)
+ if bucket_file.stream() {|s| Puppet::FileSystem.compare_stream(contents_file, s) }
Puppet.info "FileBucket got a duplicate file #{bucket_file.checksum}"
return
end
diff --git a/lib/puppet/indirector/hiera.rb b/lib/puppet/indirector/hiera.rb
new file mode 100644
index 000000000..a552d569b
--- /dev/null
+++ b/lib/puppet/indirector/hiera.rb
@@ -0,0 +1,48 @@
+require 'puppet/indirector/terminus'
+require 'hiera/scope'
+
+class Puppet::Indirector::Hiera < Puppet::Indirector::Terminus
+ def initialize(*args)
+ if ! Puppet.features.hiera?
+ raise "Hiera terminus not supported without hiera library"
+ end
+ super
+ end
+
+ if defined?(::Psych::SyntaxError)
+ DataBindingExceptions = [::StandardError, ::Psych::SyntaxError]
+ else
+ DataBindingExceptions = [::StandardError]
+ end
+
+ def find(request)
+ hiera.lookup(request.key, nil, Hiera::Scope.new(request.options[:variables]), nil, nil)
+ rescue *DataBindingExceptions => detail
+ raise Puppet::DataBinding::LookupError.new(detail.message, detail)
+ end
+
+ private
+
+ def self.hiera_config
+ hiera_config = Puppet.settings[:hiera_config]
+ config = {}
+
+ if Puppet::FileSystem.exist?(hiera_config)
+ config = Hiera::Config.load(hiera_config)
+ else
+ Puppet.warning "Config file #{hiera_config} not found, using Hiera defaults"
+ end
+
+ config[:logger] = 'puppet'
+ config
+ end
+
+ def self.hiera
+ @hiera ||= Hiera.new(:config => hiera_config)
+ end
+
+ def hiera
+ self.class.hiera
+ end
+end
+
diff --git a/lib/puppet/indirector/indirection.rb b/lib/puppet/indirector/indirection.rb
index a22f465ac..26a33543c 100644
--- a/lib/puppet/indirector/indirection.rb
+++ b/lib/puppet/indirector/indirection.rb
@@ -208,7 +208,7 @@ class Puppet::Indirector::Indirection
filtered = result
if terminus.respond_to?(:filter)
- Puppet::Util::Profiler.profile("Filtered result for #{self.name} #{request.key}") do
+ Puppet::Util::Profiler.profile("Filtered result for #{self.name} #{request.key}", [:indirector, :filter, self.name, request.key]) do
filtered = terminus.filter(result)
end
end
diff --git a/lib/puppet/indirector/request.rb b/lib/puppet/indirector/request.rb
index a67753f68..04c55f9a3 100644
--- a/lib/puppet/indirector/request.rb
+++ b/lib/puppet/indirector/request.rb
@@ -83,14 +83,20 @@ class Puppet::Indirector::Request
end
def environment
- @environment ||= Puppet.lookup(:environments).get(Puppet[:environment])
+ # If environment has not been set directly, we should use the application's
+ # current environment
+ @environment ||= Puppet.lookup(:current_environment)
end
def environment=(env)
- @environment = if env.is_a?(Puppet::Node::Environment)
+ @environment =
+ if env.is_a?(Puppet::Node::Environment)
env
+ elsif (current_environment = Puppet.lookup(:current_environment)).name == env
+ current_environment
else
- Puppet.lookup(:environments).get(env)
+ Puppet.lookup(:environments).get(env) ||
+ raise(Puppet::Environments::EnvironmentNotFound, env)
end
end
diff --git a/lib/puppet/indirector/resource/ral.rb b/lib/puppet/indirector/resource/ral.rb
index 5a366a329..350d722db 100644
--- a/lib/puppet/indirector/resource/ral.rb
+++ b/lib/puppet/indirector/resource/ral.rb
@@ -59,6 +59,6 @@ class Puppet::Resource::Ral < Puppet::Indirector::Code
end
def type( request )
- Puppet::Type.type(type_name(request)) or raise Puppet::Error, "Could not find type #{type}"
+ Puppet::Type.type(type_name(request)) or raise Puppet::Error, "Could not find type #{type_name(request)}"
end
end
diff --git a/lib/puppet/indirector/rest.rb b/lib/puppet/indirector/rest.rb
index 8c10c33e0..d1f2f4d05 100644
--- a/lib/puppet/indirector/rest.rb
+++ b/lib/puppet/indirector/rest.rb
@@ -114,7 +114,7 @@ class Puppet::Indirector::REST < Puppet::Indirector::Terminus
# that makes a user aware of the reason for the failure.
#
content_type, body = parse_response(response)
- msg = "Find #{uri_with_query_string} resulted in 404 with the message: #{body}"
+ msg = "Find #{elide(uri_with_query_string, 100)} resulted in 404 with the message: #{body}"
raise Puppet::Error, msg
else
nil
@@ -257,7 +257,11 @@ class Puppet::Indirector::REST < Puppet::Indirector::Terminus
nil
end
- def environment
- Puppet.lookup(:environments).get(Puppet[:environment])
+ def elide(string, length)
+ if Puppet::Util::Log.level == :debug || string.length <= length
+ string
+ else
+ string[0, length - 3] + "..."
+ end
end
end
diff --git a/lib/puppet/loaders.rb b/lib/puppet/loaders.rb
index e85b5e3fc..3d150bdcf 100644
--- a/lib/puppet/loaders.rb
+++ b/lib/puppet/loaders.rb
@@ -11,7 +11,6 @@ module Puppet
require 'puppet/pops/loader/null_loader'
require 'puppet/pops/loader/static_loader'
require 'puppet/pops/loader/ruby_function_instantiator'
- require 'puppet/pops/loader/ruby_legacy_function_instantiator'
require 'puppet/pops/loader/loader_paths'
require 'puppet/pops/loader/simple_environment_loader'
end
diff --git a/lib/puppet/module.rb b/lib/puppet/module.rb
index 422d66d87..3a3435c35 100644
--- a/lib/puppet/module.rb
+++ b/lib/puppet/module.rb
@@ -28,7 +28,8 @@ class Puppet::Module
# of +path+, return +nil+
def self.find(modname, environment = nil)
return nil unless modname
- env = Puppet.lookup(:environments).get(environment || Puppet[:environment])
+ # Unless a specific environment is given, use the current environment
+ env = environment ? Puppet.lookup(:environments).get(environment) : Puppet.lookup(:current_environment)
env.module(modname)
end
diff --git a/lib/puppet/module_tool.rb b/lib/puppet/module_tool.rb
index 8a34d26d1..8f462f6d8 100644
--- a/lib/puppet/module_tool.rb
+++ b/lib/puppet/module_tool.rb
@@ -149,6 +149,8 @@ module Puppet
elsif options[:environment].is_a?(Puppet::Node::Environment)
options[:environment]
elsif options[:environment]
+ # This use of looking up an environment is correct since it honours
+ # a reguest to get a particular environment via environment name.
Puppet.lookup(:environments).get(options[:environment])
else
Puppet.lookup(:current_environment)
diff --git a/lib/puppet/module_tool/applications/application.rb b/lib/puppet/module_tool/applications/application.rb
index d175f563c..c94990b56 100644
--- a/lib/puppet/module_tool/applications/application.rb
+++ b/lib/puppet/module_tool/applications/application.rb
@@ -40,6 +40,10 @@ module Puppet::ModuleTool
raise ArgumentError, "Could not determine module path"
end
+ if require_metadata && !Puppet::ModuleTool.is_module_root?(@path)
+ raise ArgumentError, "Unable to find metadata.json or Modulefile in module root at #{@path} See http://links.puppetlabs.com/modulefile for required file format."
+ end
+
modulefile_path = File.join(@path, 'Modulefile')
metadata_path = File.join(@path, 'metadata.json')
@@ -63,11 +67,6 @@ module Puppet::ModuleTool
Puppet::ModuleTool::ModulefileReader.evaluate(@metadata, modulefile_path)
end
- has_metadata = File.file?(modulefile_path) || File.file?(metadata_path)
- if !has_metadata && require_metadata
- raise ArgumentError, "No metadata found for module #{@path}"
- end
-
return @metadata
end
diff --git a/lib/puppet/module_tool/applications/builder.rb b/lib/puppet/module_tool/applications/builder.rb
index 30d40e968..edfd2c9ef 100644
--- a/lib/puppet/module_tool/applications/builder.rb
+++ b/lib/puppet/module_tool/applications/builder.rb
@@ -1,5 +1,7 @@
require 'fileutils'
require 'json'
+require 'puppet/file_system'
+require 'pathspec'
module Puppet::ModuleTool
module Applications
@@ -55,20 +57,77 @@ module Puppet::ModuleTool
FileUtils.mkdir(build_path)
end
+ def ignored_files
+ if @ignored_files
+ return @ignored_files
+ else
+ pmtignore = File.join(@path, '.pmtignore')
+ gitignore = File.join(@path, '.gitignore')
+
+ if File.file? pmtignore
+ @ignored_files = PathSpec.new File.read(pmtignore)
+ elsif File.file? gitignore
+ @ignored_files = PathSpec.new File.read(gitignore)
+ else
+ @ignored_files = PathSpec.new
+ end
+ end
+ end
+
def copy_contents
- Dir[File.join(@path, '*')].each do |path|
- case File.basename(path)
- when *Puppet::ModuleTool::ARTIFACTS
+ symlinks = []
+ Find.find(File.join(@path)) do |path|
+ # because Find.find finds the path itself
+ if path == @path
next
+ end
+
+ # Needed because pathspec looks for a trailing slash in the path to
+ # determine if a path is a directory
+ path = path.to_s + '/' if File.directory? path
+
+ # if it matches, then prune it with fire
+ unless ignored_files.match_paths([path], @path).empty?
+ Find.prune
+ end
+
+ # don't copy all the Puppet ARTIFACTS
+ rel = Pathname.new(path).relative_path_from(Pathname.new(@path))
+ case rel.to_s
+ when *Puppet::ModuleTool::ARTIFACTS
+ Find.prune
+ end
+
+ # make dir tree, copy files, and add symlinks to the symlinks list
+ dest = "#{build_path}/#{rel.to_s}"
+ if File.directory? path
+ FileUtils.mkdir dest, :mode => File.stat(path).mode
+ elsif Puppet::FileSystem.symlink? path
+ symlinks << path
else
- FileUtils.cp_r path, build_path, :preserve => true
+ FileUtils.cp path, dest, :preserve => true
+ end
+ end
+
+ # send a message about each symlink and raise an error if they exist
+ unless symlinks.empty?
+ symlinks.each do |s|
+ s = Pathname.new s
+ mpath = Pathname.new @path
+ Puppet.warning "Symlinks in modules are unsupported. Please investigate symlink #{s.relative_path_from mpath} -> #{s.realpath.relative_path_from mpath}."
end
+
+ raise Puppet::ModuleTool::Errors::ModuleToolError, "Found symlinks. Symlinks in modules are not allowed, please remove them."
end
end
def write_json
metadata_path = File.join(build_path, 'metadata.json')
+ if metadata.to_hash.include? 'checksums'
+ Puppet.warning "A 'checksums' field was found in metadata.json. This field will be ignored and can safely be removed."
+ end
+
# TODO: This may necessarily change the order in which the metadata.json
# file is packaged from what was written by the user. This is a
# regretable, but required for now.
@@ -77,7 +136,7 @@ module Puppet::ModuleTool
end
File.open(File.join(build_path, 'checksums.json'), 'w') do |f|
- f.write(PSON.pretty_generate(Checksums.new(@path)))
+ f.write(PSON.pretty_generate(Checksums.new(build_path)))
end
end
diff --git a/lib/puppet/module_tool/applications/uninstaller.rb b/lib/puppet/module_tool/applications/uninstaller.rb
index 01465cb65..46e5391be 100644
--- a/lib/puppet/module_tool/applications/uninstaller.rb
+++ b/lib/puppet/module_tool/applications/uninstaller.rb
@@ -11,6 +11,7 @@ module Puppet::ModuleTool
@installed = []
@suggestions = []
@environment = options[:environment_instance]
+ @ignore_changes = options[:force] || options[:ignore_changes]
end
def run
@@ -87,14 +88,14 @@ module Puppet::ModuleTool
def validate_module
mod = @installed.first
- if !@options[:force] && mod.has_metadata?
+ unless @ignore_changes
changes = begin
Puppet::ModuleTool::Applications::Checksummer.run(mod.path)
rescue ArgumentError
[]
end
- if !changes.empty?
+ if mod.has_metadata? && !changes.empty?
raise LocalChangesError,
:action => :uninstall,
:module_name => (mod.forge_name || mod.name).gsub('/', '-'),
diff --git a/lib/puppet/module_tool/applications/unpacker.rb b/lib/puppet/module_tool/applications/unpacker.rb
index 6673c9b12..8ffef3d64 100644
--- a/lib/puppet/module_tool/applications/unpacker.rb
+++ b/lib/puppet/module_tool/applications/unpacker.rb
@@ -1,6 +1,7 @@
require 'pathname'
require 'tmpdir'
require 'json'
+require 'puppet/file_system'
module Puppet::ModuleTool
module Applications
@@ -8,6 +9,7 @@ module Puppet::ModuleTool
def self.unpack(filename, target)
app = self.new(filename, :target_dir => target)
app.unpack
+ app.sanity_check
app.move_into(target)
end
@@ -28,6 +30,7 @@ module Puppet::ModuleTool
def run
unpack
+ sanity_check
module_dir = @module_path + module_name
move_into(module_dir)
@@ -37,6 +40,17 @@ module Puppet::ModuleTool
end
# @api private
+ # Error on symlinks and other junk
+ def sanity_check
+ symlinks = Dir.glob("#{tmpdir}/**/*", File::FNM_DOTMATCH).map { |f| Pathname.new(f) }.select {|p| Puppet::FileSystem.symlink? p}
+ tmpdirpath = Pathname.new tmpdir
+
+ symlinks.each do |s|
+ Puppet.warning "Symlinks in modules are unsupported. Please investigate symlink #{s.relative_path_from tmpdirpath}->#{s.realpath.relative_path_from tmpdirpath}."
+ end
+ end
+
+ # @api private
def unpack
begin
Puppet::ModuleTool::Tar.instance.unpack(@filename.to_s, tmpdir, [@module_path.stat.uid, @module_path.stat.gid].join(':'))
diff --git a/lib/puppet/module_tool/applications/upgrader.rb b/lib/puppet/module_tool/applications/upgrader.rb
index 304834bbf..d901f86a8 100644
--- a/lib/puppet/module_tool/applications/upgrader.rb
+++ b/lib/puppet/module_tool/applications/upgrader.rb
@@ -18,6 +18,7 @@ module Puppet::ModuleTool
@action = :upgrade
@environment = options[:environment_instance]
@name = name
+ @ignore_changes = forced? || options[:ignore_changes]
@ignore_dependencies = forced? || options[:ignore_dependencies]
Semantic::Dependency.add_source(installed_modules_source)
@@ -45,7 +46,7 @@ module Puppet::ModuleTool
raise MultipleInstalledError, results.merge(:module_name => name, :installed_modules => matching_modules)
end
- mod = installed_modules[name]
+ installed_release = installed_modules[name]
# `priority` is an attribute of a `Semantic::Dependency::Source`,
# which is delegated through `ModuleRelease` instances for the sake of
@@ -60,17 +61,17 @@ module Puppet::ModuleTool
# of behavior to be reasonably common in Semantic, we should probably
# see about implementing a `ModuleRelease#override_priority` method
# (or something similar).
- def mod.priority
+ def installed_release.priority
0
end
- mod = mod.mod
+ mod = installed_release.mod
results[:installed_version] = Semantic::Version.parse(mod.version)
dir = Pathname.new(mod.modulepath)
vstring = mod.version ? "v#{mod.version}" : '???'
Puppet.notice "Found '#{name}' (#{colorize(:cyan, vstring)}) in #{dir} ..."
- unless forced?
+ unless @ignore_changes
changes = Checksummer.run(mod.path) rescue []
if mod.has_metadata? && !changes.empty?
raise LocalChangesError,
@@ -83,6 +84,18 @@ module Puppet::ModuleTool
Puppet::Forge::Cache.clean
+ # Ensure that there is at least one candidate release available
+ # for the target package.
+ available_versions = module_repository.fetch(name)
+ if available_versions.empty?
+ raise NoCandidateReleasesError, results.merge(:module_name => name, :source => module_repository.host)
+ elsif results[:requested_version] != :latest
+ requested = Semantic::VersionRange.parse(results[:requested_version])
+ unless available_versions.any? {|m| requested.include? m.version}
+ raise NoCandidateReleasesError, results.merge(:module_name => name, :source => module_repository.host)
+ end
+ end
+
Puppet.notice "Downloading from #{module_repository.host} ..."
if @ignore_dependencies
graph = build_single_module_graph(name, version)
@@ -122,14 +135,6 @@ module Puppet::ModuleTool
end
end
- # Ensure that there is at least one candidate release available
- # for the target package.
- if graph.dependencies[name].empty? || graph.dependencies[name] == SortedSet.new([ installed_modules[name] ])
- if results[:requested_version] == :latest || !Semantic::VersionRange.parse(results[:requested_version]).include?(results[:installed_version])
- raise NoCandidateReleasesError, results.merge(:module_name => name, :source => module_repository.host)
- end
- end
-
begin
Puppet.info "Resolving dependencies ..."
releases = Semantic::Dependency.resolve(graph)
@@ -160,7 +165,7 @@ module Puppet::ModuleTool
child = releases.find { |x| x.name == name }
unless forced?
- if child.version <= results[:installed_version]
+ if child.version == results[:installed_version]
versions = graph.dependencies[name].map { |r| r.version }
newer_versions = versions.select { |v| v > results[:installed_version] }
@@ -170,6 +175,11 @@ module Puppet::ModuleTool
:installed_version => results[:installed_version],
:newer_versions => newer_versions,
:possible_culprits => installed_modules_source.fetched.reject { |x| x == name }
+ elsif child.version < results[:installed_version]
+ raise DowngradingUnsupportedError,
+ :module_name => name,
+ :requested_version => results[:requested_version],
+ :installed_version => results[:installed_version]
end
end
diff --git a/lib/puppet/module_tool/dependency.rb b/lib/puppet/module_tool/dependency.rb
index c213e55ce..2f0995e58 100644
--- a/lib/puppet/module_tool/dependency.rb
+++ b/lib/puppet/module_tool/dependency.rb
@@ -20,6 +20,18 @@ module Puppet::ModuleTool
@repository = repository ? Puppet::Forge::Repository.new(repository) : nil
end
+ # We override Object's ==, eql, and hash so we can more easily find identical
+ # dependencies.
+ def ==(o)
+ self.hash == o.hash
+ end
+
+ alias :eql? :==
+
+ def hash
+ [@full_module_name, @version_requirement, @repository].hash
+ end
+
def to_data_hash
result = { :name => @full_module_name }
result[:version_requirement] = @version_requirement if @version_requirement && ! @version_requirement.nil?
diff --git a/lib/puppet/module_tool/errors/shared.rb b/lib/puppet/module_tool/errors/shared.rb
index c49342834..a1e02827c 100644
--- a/lib/puppet/module_tool/errors/shared.rb
+++ b/lib/puppet/module_tool/errors/shared.rb
@@ -164,7 +164,7 @@ module Puppet::ModuleTool::Errors
message = []
message << "Could not #{@action} module '#{@module_name}' (#{vstring})"
message << " Installed module has had changes made locally"
- message << " Use `puppet module #{@action} --force` to #{@action} this module anyway"
+ message << " Use `puppet module #{@action} --ignore-changes` to #{@action} this module anyway"
message.join("\n")
end
end
diff --git a/lib/puppet/module_tool/errors/upgrader.rb b/lib/puppet/module_tool/errors/upgrader.rb
index 28c0304ed..0247f79ae 100644
--- a/lib/puppet/module_tool/errors/upgrader.rb
+++ b/lib/puppet/module_tool/errors/upgrader.rb
@@ -40,4 +40,24 @@ module Puppet::ModuleTool::Errors
message.join("\n")
end
end
+
+ class DowngradingUnsupportedError < UpgradeError
+ def initialize(options)
+ @module_name = options[:module_name]
+ @requested_version = options[:requested_version]
+ @installed_version = options[:installed_version]
+ @conditions = options[:conditions]
+ @action = options[:action]
+
+ super "Could not #{@action} '#{@module_name}' (#{vstring}); downgrades are not allowed"
+ end
+
+ def multiline
+ message = []
+ message << "Could not #{@action} module '#{@module_name}' (#{vstring})"
+ message << " Downgrading is not allowed."
+
+ message.join("\n")
+ end
+ end
end
diff --git a/lib/puppet/module_tool/installed_modules.rb b/lib/puppet/module_tool/installed_modules.rb
index 0e82b6bd0..de9421132 100644
--- a/lib/puppet/module_tool/installed_modules.rb
+++ b/lib/puppet/module_tool/installed_modules.rb
@@ -58,7 +58,12 @@ module Puppet::ModuleTool
@mod = mod
@metadata = mod.metadata
name = mod.forge_name.tr('/', '-')
- version = Semantic::Version.parse(mod.version)
+ begin
+ version = Semantic::Version.parse(mod.version)
+ rescue Semantic::Version::ValidationFailure => e
+ Puppet.warning "#{mod.name} (#{mod.path}) has an invalid version number (#{mod.version}). The version has been set to 0.0.0. If you are the maintainer for this module, please update the metadata.json with a valid Semantic Version (http://semver.org)."
+ version = Semantic::Version.parse("0.0.0")
+ end
release = "#{name}@#{version}"
super(source, name, version, {})
diff --git a/lib/puppet/module_tool/metadata.rb b/lib/puppet/module_tool/metadata.rb
index e625c3fdb..0237555ad 100644
--- a/lib/puppet/module_tool/metadata.rb
+++ b/lib/puppet/module_tool/metadata.rb
@@ -3,6 +3,7 @@ require 'puppet/module_tool'
require 'puppet/network/format_support'
require 'uri'
require 'json'
+require 'set'
module Puppet::ModuleTool
@@ -22,7 +23,7 @@ module Puppet::ModuleTool
'source' => '',
'project_page' => nil,
'issues_url' => nil,
- 'dependencies' => [].freeze,
+ 'dependencies' => Set.new.freeze,
}
def initialize
@@ -51,11 +52,41 @@ module Puppet::ModuleTool
process_name(data) if data['name']
process_version(data) if data['version']
process_source(data) if data['source']
+ merge_dependencies(data) if data['dependencies']
@data.merge!(data)
return self
end
+ # Validates the name and version_requirement for a dependency, then creates
+ # the Dependency and adds it.
+ # Returns the Dependency that was added.
+ def add_dependency(name, version_requirement=nil, repository=nil)
+ validate_name(name)
+ validate_version_range(version_requirement) if version_requirement
+
+ if dup = @data['dependencies'].find { |d| d.full_module_name == name && d.version_requirement != version_requirement }
+ raise ArgumentError, "Dependency conflict for #{full_module_name}: Dependency #{name} was given conflicting version requirements #{version_requirement} and #{dup.version_requirement}. Verify that there are no duplicates in the metadata.json or the Modulefile."
+ end
+
+ dep = Dependency.new(name, version_requirement, repository)
+ @data['dependencies'].add(dep)
+
+ dep
+ end
+
+ # Provides an accessor for the now defunct 'description' property. This
+ # addresses a regression in Puppet 3.6.x where previously valid templates
+ # refering to the 'description' property were broken.
+ # @deprecated
+ def description
+ @data['description']
+ end
+
+ def dependencies
+ @data['dependencies'].to_a
+ end
+
# Returns a hash of the module's metadata. Used by Puppet's automated
# serialization routines.
#
@@ -66,6 +97,8 @@ module Puppet::ModuleTool
alias :to_data_hash :to_hash
def to_json
+ data = @data.dup.merge('dependencies' => dependencies)
+
# This is used to simulate an ordered hash. In particular, some keys
# are promoted to the top of the serialized hash (while others are
# demoted) for human-friendliness.
@@ -73,12 +106,12 @@ module Puppet::ModuleTool
# This particularly works around the lack of ordered hashes in 1.8.7.
promoted_keys = %w[ name version author summary license source ]
demoted_keys = %w[ dependencies ]
- keys = @data.keys
+ keys = data.keys
keys -= promoted_keys
keys -= demoted_keys
contents = (promoted_keys + keys + demoted_keys).map do |k|
- value = (JSON.pretty_generate(@data[k]) rescue @data[k].to_json)
+ value = (JSON.pretty_generate(data[k]) rescue data[k].to_json)
"#{k.to_json}: #{value}"
end
@@ -127,6 +160,16 @@ module Puppet::ModuleTool
return
end
+ # Validates and parses the dependencies.
+ def merge_dependencies(data)
+ data['dependencies'].each do |dep|
+ add_dependency(dep['name'], dep['version_requirement'], dep['repository'])
+ end
+
+ # Clear dependencies so @data dependencies are not overwritten
+ data.delete 'dependencies'
+ end
+
# Validates that the given module name is both namespaced and well-formed.
def validate_name(name)
return if name =~ /\A[a-z0-9]+[-\/][a-z][a-z0-9_]*\Z/i
@@ -155,5 +198,12 @@ module Puppet::ModuleTool
err = "version string cannot be parsed as a valid Semantic Version"
raise ArgumentError, "Invalid 'version' field in metadata.json: #{err}"
end
+
+ # Validates that the version range can be parsed by Semantic.
+ def validate_version_range(version_range)
+ Semantic::VersionRange.parse(version_range)
+ rescue ArgumentError => e
+ raise ArgumentError, "Invalid 'version_range' field in metadata.json: #{e}"
+ end
end
end
diff --git a/lib/puppet/module_tool/modulefile.rb b/lib/puppet/module_tool/modulefile.rb
index 07f578312..31eb9c14c 100644
--- a/lib/puppet/module_tool/modulefile.rb
+++ b/lib/puppet/module_tool/modulefile.rb
@@ -42,7 +42,7 @@ module Puppet::ModuleTool
# optional +version_requirement+ (e.g. "0.1.0") and +repository+ (a URL
# string). Optional. Can be called multiple times to add many dependencies.
def dependency(name, version_requirement = nil, repository = nil)
- @metadata.to_hash['dependencies'] << Dependency.new(name, version_requirement, repository)
+ @metadata.add_dependency(name, version_requirement, repository)
end
# Set the source
diff --git a/lib/puppet/module_tool/skeleton/templates/generator/Gemfile b/lib/puppet/module_tool/skeleton/templates/generator/Gemfile
new file mode 100644
index 000000000..7bd34cda7
--- /dev/null
+++ b/lib/puppet/module_tool/skeleton/templates/generator/Gemfile
@@ -0,0 +1,7 @@
+source 'https://rubygems.org'
+
+puppetversion = ENV.key?('PUPPET_VERSION') ? "= #{ENV['PUPPET_VERSION']}" : ['>= 3.3']
+gem 'puppet', puppetversion
+gem 'puppetlabs_spec_helper', '>= 0.1.0'
+gem 'puppet-lint', '>= 0.3.2'
+gem 'facter', '>= 1.7.0'
diff --git a/lib/puppet/module_tool/skeleton/templates/generator/manifests/init.pp.erb b/lib/puppet/module_tool/skeleton/templates/generator/manifests/init.pp.erb
index d9d0df0b5..d7c5f4aa7 100644
--- a/lib/puppet/module_tool/skeleton/templates/generator/manifests/init.pp.erb
+++ b/lib/puppet/module_tool/skeleton/templates/generator/manifests/init.pp.erb
@@ -23,7 +23,7 @@
#
# === Examples
#
-# class { <%= metadata.name %>:
+# class { '<%= metadata.name %>':
# servers => [ 'pool.ntp.org', 'ntp.local.company.com' ],
# }
#
diff --git a/lib/puppet/module_tool/skeleton/templates/generator/spec/spec_helper.rb b/lib/puppet/module_tool/skeleton/templates/generator/spec/spec_helper.rb
index 5fda58875..2c6f56649 100644
--- a/lib/puppet/module_tool/skeleton/templates/generator/spec/spec_helper.rb
+++ b/lib/puppet/module_tool/skeleton/templates/generator/spec/spec_helper.rb
@@ -1,17 +1 @@
-dir = File.expand_path(File.dirname(__FILE__))
-$LOAD_PATH.unshift File.join(dir, 'lib')
-
-require 'mocha'
-require 'puppet'
-require 'rspec'
-require 'spec/autorun'
-
-Spec::Runner.configure do |config|
- config.mock_with :mocha
-end
-
-# We need this because the RAL uses 'should' as a method. This
-# allows us the same behaviour but with a different method name.
-class Object
- alias :must :should
-end
+require 'puppetlabs_spec_helper/module_spec_helper'
diff --git a/lib/puppet/module_tool/tar/mini.rb b/lib/puppet/module_tool/tar/mini.rb
index ef60e3717..4f1518247 100644
--- a/lib/puppet/module_tool/tar/mini.rb
+++ b/lib/puppet/module_tool/tar/mini.rb
@@ -1,13 +1,13 @@
class Puppet::ModuleTool::Tar::Mini
def unpack(sourcefile, destdir, _)
Zlib::GzipReader.open(sourcefile) do |reader|
- Archive::Tar::Minitar.unpack(reader, destdir) do |action, name, stats|
+ Archive::Tar::Minitar.unpack(reader, destdir, find_valid_files(reader)) do |action, name, stats|
case action
when :file_done
File.chmod(0444, "#{destdir}/#{name}")
when :dir, :file_start
validate_entry(destdir, name)
- Puppet.debug("extracting #{destdir}/#{name}")
+ Puppet.debug("Extracting: #{destdir}/#{name}")
end
end
end
@@ -21,6 +21,24 @@ class Puppet::ModuleTool::Tar::Mini
private
+ # Find all the valid files in tarfile.
+ #
+ # This check was mainly added to ignore 'x' and 'g' flags from the PAX
+ # standard but will also ignore any other non-standard tar flags.
+ # tar format info: http://pic.dhe.ibm.com/infocenter/zos/v1r13/index.jsp?topic=%2Fcom.ibm.zos.r13.bpxa500%2Ftaf.htm
+ # pax format info: http://pic.dhe.ibm.com/infocenter/zos/v1r13/index.jsp?topic=%2Fcom.ibm.zos.r13.bpxa500%2Fpxarchfm.htm
+ def find_valid_files(tarfile)
+ Archive::Tar::Minitar.open(tarfile).collect do |entry|
+ flag = entry.typeflag
+ if flag.nil? || flag =~ /[[:digit:]]/ && (0..7).include?(flag.to_i)
+ entry.name
+ else
+ Puppet.debug "Invalid tar flag '#{flag}' will not be extracted: #{entry.name}"
+ next
+ end
+ end
+ end
+
def validate_entry(destdir, path)
if Pathname.new(path).absolute?
raise Puppet::ModuleTool::Errors::InvalidPathInPackageError, :entry_path => path, :directory => destdir
diff --git a/lib/puppet/network/http.rb b/lib/puppet/network/http.rb
index a2712b8d5..68a345e63 100644
--- a/lib/puppet/network/http.rb
+++ b/lib/puppet/network/http.rb
@@ -11,5 +11,10 @@ module Puppet::Network::HTTP
require 'puppet/network/http/handler'
require 'puppet/network/http/response'
require 'puppet/network/http/request'
+ require 'puppet/network/http/site'
+ require 'puppet/network/http/session'
+ require 'puppet/network/http/factory'
+ require 'puppet/network/http/nocache_pool'
+ require 'puppet/network/http/pool'
require 'puppet/network/http/memory_response'
end
diff --git a/lib/puppet/network/http/api/v1.rb b/lib/puppet/network/http/api/v1.rb
index 54fa9af23..24b7d910f 100644
--- a/lib/puppet/network/http/api/v1.rb
+++ b/lib/puppet/network/http/api/v1.rb
@@ -110,12 +110,12 @@ class Puppet::Network::HTTP::API::V1
rendered_result = result
if result.respond_to?(:render)
- Puppet::Util::Profiler.profile("Rendered result in #{format}") do
+ Puppet::Util::Profiler.profile("Rendered result in #{format}", [:http, :v1_render, format]) do
rendered_result = result.render(format)
end
end
- Puppet::Util::Profiler.profile("Sent response") do
+ Puppet::Util::Profiler.profile("Sent response", [:http, :v1_response]) do
response.respond_with(200, format, rendered_result)
end
end
diff --git a/lib/puppet/network/http/api/v2/environments.rb b/lib/puppet/network/http/api/v2/environments.rb
index 6331be857..51a4e04f2 100644
--- a/lib/puppet/network/http/api/v2/environments.rb
+++ b/lib/puppet/network/http/api/v2/environments.rb
@@ -12,10 +12,24 @@ class Puppet::Network::HTTP::API::V2::Environments
[env.name, {
"settings" => {
"modulepath" => env.full_modulepath,
- "manifest" => env.manifest
+ "manifest" => env.manifest,
+ "environment_timeout" => timeout(env),
+ "config_version" => env.config_version || '',
}
}]
end]
}))
end
+
+ private
+
+ def timeout(env)
+ ttl = @env_loader.get_conf(env.name).environment_timeout
+ if ttl == 1.0 / 0.0 # INFINITY
+ "unlimited"
+ else
+ ttl
+ end
+ end
+
end
diff --git a/lib/puppet/network/http/connection.rb b/lib/puppet/network/http/connection.rb
index a2ce6eef3..8ffb1dda1 100644
--- a/lib/puppet/network/http/connection.rb
+++ b/lib/puppet/network/http/connection.rb
@@ -3,6 +3,7 @@ require 'puppet/ssl/host'
require 'puppet/ssl/configuration'
require 'puppet/ssl/validator'
require 'puppet/network/authentication'
+require 'puppet/network/http'
require 'uri'
module Puppet::Network::HTTP
@@ -29,8 +30,6 @@ module Puppet::Network::HTTP
:redirect_limit => 10,
}
- @@openssl_initialized = false
-
# Creates a new HTTP client connection to `host`:`port`.
# @param host [String] the host to which this client will connect to
# @param port [Fixnum] the port to which this client will connect to
@@ -60,6 +59,8 @@ module Puppet::Network::HTTP
@use_ssl = options[:use_ssl]
@verify = options[:verify]
@redirect_limit = options[:redirect_limit]
+ @site = Puppet::Network::HTTP::Site.new(@use_ssl ? 'https' : 'http', host, port)
+ @pool = Puppet.lookup(:http_pool)
end
# @!macro [new] common_options
@@ -119,59 +120,81 @@ module Puppet::Network::HTTP
# TODO: These are proxies for the Net::HTTP#request_* methods, which are
# almost the same as the "get", "post", etc. methods that we've ported above,
- # but they are able to accept a code block and will yield to it. For now
+ # but they are able to accept a code block and will yield to it, which is
+ # necessary to stream responses, e.g. file content. For now
# we're not funneling these proxy implementations through our #request
# method above, so they will not inherit the same error handling. In the
# future we may want to refactor these so that they are funneled through
# that method and do inherit the error handling.
def request_get(*args, &block)
- connection.request_get(*args, &block)
+ with_connection(@site) do |connection|
+ connection.request_get(*args, &block)
+ end
end
def request_head(*args, &block)
- connection.request_head(*args, &block)
+ with_connection(@site) do |connection|
+ connection.request_head(*args, &block)
+ end
end
def request_post(*args, &block)
- connection.request_post(*args, &block)
+ with_connection(@site) do |connection|
+ connection.request_post(*args, &block)
+ end
end
# end of Net::HTTP#request_* proxies
+ # The address to connect to.
def address
- connection.address
+ @site.host
end
+ # The port to connect to.
def port
- connection.port
+ @site.port
end
+ # Whether to use ssl
def use_ssl?
- connection.use_ssl?
+ @site.use_ssl?
end
private
def request_with_redirects(request, options)
current_request = request
- @redirect_limit.times do |redirection|
- apply_options_to(current_request, options)
+ current_site = @site
+ response = nil
- response = execute_request(current_request)
- return response unless [301, 302, 307].include?(response.code.to_i)
+ 0.upto(@redirect_limit) do |redirection|
+ return response if response
- # handle the redirection
- location = URI.parse(response['location'])
- @connection = initialize_connection(location.host, location.port, location.scheme == 'https')
+ with_connection(current_site) do |connection|
+ apply_options_to(current_request, options)
- # update to the current request path
- current_request = current_request.class.new(location.path)
- current_request.body = request.body
- request.each do |header, value|
- current_request[header] = value
+ current_response = execute_request(connection, current_request)
+
+ if [301, 302, 307].include?(current_response.code.to_i)
+
+ # handle the redirection
+ location = URI.parse(current_response['location'])
+ current_site = current_site.move_to(location)
+
+ # update to the current request path
+ current_request = current_request.class.new(location.path)
+ current_request.body = request.body
+ request.each do |header, value|
+ current_request[header] = value
+ end
+ else
+ response = current_response
+ end
end
# and try again...
end
+
raise RedirectionLimitExceededException, "Too many HTTP redirections for #{@host}:#{@port}"
end
@@ -181,17 +204,21 @@ module Puppet::Network::HTTP
end
end
- def connection
- @connection || initialize_connection(@host, @port, @use_ssl)
- end
-
- def execute_request(request)
+ def execute_request(connection, request)
response = connection.request(request)
# Check the peer certs and warn if they're nearing expiration.
warn_if_near_expiration(*@verify.peer_certs)
response
+ end
+
+ def with_connection(site, &block)
+ response = nil
+ @pool.with_connection(site, @verify) do |conn|
+ response = yield conn
+ end
+ response
rescue OpenSSL::SSL::SSLError => error
if error.message.include? "certificate verify failed"
msg = error.message
@@ -202,53 +229,12 @@ module Puppet::Network::HTTP
valid_certnames = [leaf_ssl_cert.name, *leaf_ssl_cert.subject_alt_names].uniq
msg = valid_certnames.length > 1 ? "one of #{valid_certnames.join(', ')}" : valid_certnames.first
- msg = "Server hostname '#{connection.address}' did not match server certificate; expected #{msg}"
+ msg = "Server hostname '#{site.host}' did not match server certificate; expected #{msg}"
raise Puppet::Error, msg, error.backtrace
else
raise
end
end
-
- def initialize_connection(host, port, use_ssl)
- args = [host, port]
- if Puppet[:http_proxy_host] == "none"
- args << nil << nil
- else
- args << Puppet[:http_proxy_host] << Puppet[:http_proxy_port]
- end
-
- @connection = create_connection(*args)
-
- # Pop open the http client a little; older versions of Net::HTTP(s) didn't
- # give us a reader for ca_file... Grr...
- class << @connection; attr_accessor :ca_file; end
-
- @connection.use_ssl = use_ssl
- # Use configured timeout (#1176)
- @connection.read_timeout = Puppet[:configtimeout]
- @connection.open_timeout = Puppet[:configtimeout]
-
- cert_setup
-
- @connection
- end
-
- # Use cert information from a Puppet client to set up the http object.
- def cert_setup
- # PUP-1411, make sure that openssl is initialized before we try to connect
- if ! @@openssl_initialized
- OpenSSL::SSL::SSLContext.new
- @@openssl_initialized = true
- end
-
- @verify.setup_connection(@connection)
- end
-
- # This method largely exists for testing purposes, so that we can
- # mock the actual HTTP connection.
- def create_connection(*args)
- Net::HTTP.new(*args)
- end
end
end
diff --git a/lib/puppet/network/http/factory.rb b/lib/puppet/network/http/factory.rb
new file mode 100644
index 000000000..8e60ad2c6
--- /dev/null
+++ b/lib/puppet/network/http/factory.rb
@@ -0,0 +1,44 @@
+require 'openssl'
+require 'net/http'
+
+# Factory for <tt>Net::HTTP</tt> objects.
+#
+# Encapsulates the logic for creating a <tt>Net::HTTP</tt> object based on the
+# specified {Puppet::Network::HTTP::Site Site} and puppet settings.
+#
+# @api private
+#
+class Puppet::Network::HTTP::Factory
+ @@openssl_initialized = false
+
+ def initialize
+ # PUP-1411, make sure that openssl is initialized before we try to connect
+ if ! @@openssl_initialized
+ OpenSSL::SSL::SSLContext.new
+ @@openssl_initialized = true
+ end
+ end
+
+ def create_connection(site)
+ Puppet.debug("Creating new connection for #{site}")
+
+ args = [site.host, site.port]
+ if Puppet[:http_proxy_host] == "none"
+ args << nil << nil
+ else
+ args << Puppet[:http_proxy_host] << Puppet[:http_proxy_port]
+ end
+
+ http = Net::HTTP.new(*args)
+ http.use_ssl = site.use_ssl?
+ # Use configured timeout (#1176)
+ http.read_timeout = Puppet[:configtimeout]
+ http.open_timeout = Puppet[:configtimeout]
+
+ if Puppet[:http_debug]
+ http.set_debug_output($stderr)
+ end
+
+ http
+ end
+end
diff --git a/lib/puppet/network/http/handler.rb b/lib/puppet/network/http/handler.rb
index 82e873ea0..a8aa1aaeb 100644
--- a/lib/puppet/network/http/handler.rb
+++ b/lib/puppet/network/http/handler.rb
@@ -6,6 +6,7 @@ require 'puppet/network/http/api/v1'
require 'puppet/network/authentication'
require 'puppet/network/rights'
require 'puppet/util/profiler'
+require 'puppet/util/profiler/aggregate'
require 'resolv'
module Puppet::Network::HTTP::Handler
@@ -55,10 +56,9 @@ module Puppet::Network::HTTP::Handler
response[Puppet::Network::HTTP::HEADER_PUPPET_VERSION] = Puppet.version
- configure_profiler(request_headers, request_params)
- warn_if_near_expiration(new_request.client_cert)
+ profiler = configure_profiler(request_headers, request_params)
- Puppet::Util::Profiler.profile("Processed request #{request_method} #{request_path}") do
+ Puppet::Util::Profiler.profile("Processed request #{request_method} #{request_path}", [:http, request_method, request_path]) do
if route = @routes.find { |route| route.matches?(new_request) }
route.process(new_request, new_response)
else
@@ -74,6 +74,9 @@ module Puppet::Network::HTTP::Handler
Puppet.err(http_e.message)
new_response.respond_with(http_e.status, "application/json", http_e.to_json)
ensure
+ if profiler
+ remove_profiler(profiler)
+ end
cleanup(request)
end
@@ -172,9 +175,12 @@ module Puppet::Network::HTTP::Handler
def configure_profiler(request_headers, request_params)
if (request_headers.has_key?(Puppet::Network::HTTP::HEADER_ENABLE_PROFILING.downcase) or Puppet[:profile])
- Puppet::Util::Profiler.current = Puppet::Util::Profiler::WallClock.new(Puppet.method(:debug), request_params.object_id)
- else
- Puppet::Util::Profiler.current = Puppet::Util::Profiler::NONE
+ Puppet::Util::Profiler.add_profiler(Puppet::Util::Profiler::Aggregate.new(Puppet.method(:debug), request_params.object_id))
end
end
+
+ def remove_profiler(profiler)
+ profiler.shutdown
+ Puppet::Util::Profiler.remove_profiler(profiler)
+ end
end
diff --git a/lib/puppet/network/http/nocache_pool.rb b/lib/puppet/network/http/nocache_pool.rb
new file mode 100644
index 000000000..d80e0409a
--- /dev/null
+++ b/lib/puppet/network/http/nocache_pool.rb
@@ -0,0 +1,21 @@
+# A pool that does not cache HTTP connections.
+#
+# @api private
+class Puppet::Network::HTTP::NoCachePool
+ def initialize(factory = Puppet::Network::HTTP::Factory.new)
+ @factory = factory
+ end
+
+ # Yields a <tt>Net::HTTP</tt> connection.
+ #
+ # @yieldparam http [Net::HTTP] An HTTP connection
+ def with_connection(site, verify, &block)
+ http = @factory.create_connection(site)
+ verify.setup_connection(http)
+ yield http
+ end
+
+ def close
+ # do nothing
+ end
+end
diff --git a/lib/puppet/network/http/pool.rb b/lib/puppet/network/http/pool.rb
new file mode 100644
index 000000000..b817b472b
--- /dev/null
+++ b/lib/puppet/network/http/pool.rb
@@ -0,0 +1,120 @@
+# A pool for persistent <tt>Net::HTTP</tt> connections. Connections are
+# stored in the pool indexed by their {Puppet::Network::HTTP::Site Site}.
+# Connections are borrowed from the pool, yielded to the caller, and
+# released back into the pool. If a connection is expired, it will be
+# closed either when a connection to that site is requested, or when
+# the pool is closed. The pool can store multiple connections to the
+# same site, and will be reused in MRU order.
+#
+# @api private
+#
+class Puppet::Network::HTTP::Pool
+ FIFTEEN_SECONDS = 15
+
+ attr_reader :factory
+
+ def initialize(keepalive_timeout = FIFTEEN_SECONDS)
+ @pool = {}
+ @factory = Puppet::Network::HTTP::Factory.new
+ @keepalive_timeout = keepalive_timeout
+ end
+
+ def with_connection(site, verify, &block)
+ reuse = true
+
+ http = borrow(site, verify)
+ begin
+ if http.use_ssl? && http.verify_mode != OpenSSL::SSL::VERIFY_PEER
+ reuse = false
+ end
+
+ yield http
+ rescue => detail
+ reuse = false
+ raise detail
+ ensure
+ if reuse
+ release(site, http)
+ else
+ close_connection(site, http)
+ end
+ end
+ end
+
+ def close
+ @pool.each_pair do |site, sessions|
+ sessions.each do |session|
+ close_connection(site, session.connection)
+ end
+ end
+ @pool.clear
+ end
+
+ # @api private
+ def pool
+ @pool
+ end
+
+ # Safely close a persistent connection.
+ #
+ # @api private
+ def close_connection(site, http)
+ Puppet.debug("Closing connection for #{site}")
+ http.finish
+ rescue => detail
+ Puppet.log_exception(detail, "Failed to close connection for #{site}: #{detail}")
+ end
+
+ # Borrow and take ownership of a persistent connection. If a new
+ # connection is created, it will be started prior to being returned.
+ #
+ # @api private
+ def borrow(site, verify)
+ @pool[site] = active_sessions(site)
+ session = @pool[site].shift
+ if session
+ Puppet.debug("Using cached connection for #{site}")
+ session.connection
+ else
+ http = @factory.create_connection(site)
+ verify.setup_connection(http)
+
+ Puppet.debug("Starting connection for #{site}")
+ http.start
+ http
+ end
+ end
+
+ # Release a connection back into the pool.
+ #
+ # @api private
+ def release(site, http)
+ expiration = Time.now + @keepalive_timeout
+ session = Puppet::Network::HTTP::Session.new(http, expiration)
+ Puppet.debug("Caching connection for #{site}")
+
+ sessions = @pool[site]
+ if sessions
+ sessions.unshift(session)
+ else
+ @pool[site] = [session]
+ end
+ end
+
+ # Returns an Array of sessions whose connections are not expired.
+ #
+ # @api private
+ def active_sessions(site)
+ now = Time.now
+
+ sessions = @pool[site] || []
+ sessions.select do |session|
+ if session.expired?(now)
+ close_connection(site, session.connection)
+ false
+ else
+ true
+ end
+ end
+ end
+end
diff --git a/lib/puppet/network/http/rack/rest.rb b/lib/puppet/network/http/rack/rest.rb
index 23d73bf93..3ca79c8ae 100644
--- a/lib/puppet/network/http/rack/rest.rb
+++ b/lib/puppet/network/http/rack/rest.rb
@@ -94,7 +94,9 @@ class Puppet::Network::HTTP::RackREST
if cert.nil? || cert.empty?
nil
else
- Puppet::SSL::Certificate.from_instance(OpenSSL::X509::Certificate.new(cert))
+ cert = Puppet::SSL::Certificate.from_instance(OpenSSL::X509::Certificate.new(cert))
+ warn_if_near_expiration(cert)
+ cert
end
end
diff --git a/lib/puppet/network/http/session.rb b/lib/puppet/network/http/session.rb
new file mode 100644
index 000000000..21136a12d
--- /dev/null
+++ b/lib/puppet/network/http/session.rb
@@ -0,0 +1,17 @@
+# An HTTP session that references a persistent HTTP connection and
+# an expiration time for the connection.
+#
+# @api private
+#
+class Puppet::Network::HTTP::Session
+ attr_reader :connection
+
+ def initialize(connection, expiration_time)
+ @connection = connection
+ @expiration_time = expiration_time
+ end
+
+ def expired?(now)
+ @expiration_time <= now
+ end
+end
diff --git a/lib/puppet/network/http/site.rb b/lib/puppet/network/http/site.rb
new file mode 100644
index 000000000..c219e6021
--- /dev/null
+++ b/lib/puppet/network/http/site.rb
@@ -0,0 +1,39 @@
+# Represents a site to which HTTP connections are made. It is a value
+# object, and is suitable for use in a hash. If two sites are equal,
+# then a persistent connection made to the first site, can be re-used
+# for the second.
+#
+# @api private
+#
+class Puppet::Network::HTTP::Site
+ attr_reader :scheme, :host, :port
+
+ def initialize(scheme, host, port)
+ @scheme = scheme
+ @host = host
+ @port = port.to_i
+ end
+
+ def addr
+ "#{@scheme}://#{@host}:#{@port.to_s}"
+ end
+ alias to_s addr
+
+ def ==(rhs)
+ (@scheme == rhs.scheme) && (@host == rhs.host) && (@port == rhs.port)
+ end
+
+ alias eql? ==
+
+ def hash
+ [@scheme, @host, @port].hash
+ end
+
+ def use_ssl?
+ @scheme == 'https'
+ end
+
+ def move_to(uri)
+ self.class.new(uri.scheme, uri.host, uri.port)
+ end
+end
diff --git a/lib/puppet/network/http/webrick/rest.rb b/lib/puppet/network/http/webrick/rest.rb
index 66987151a..ae0867dfb 100644
--- a/lib/puppet/network/http/webrick/rest.rb
+++ b/lib/puppet/network/http/webrick/rest.rb
@@ -7,6 +7,10 @@ class Puppet::Network::HTTP::WEBrickREST < WEBrick::HTTPServlet::AbstractServlet
include Puppet::Network::HTTP::Handler
+ def self.mutex
+ @mutex ||= Mutex.new
+ end
+
def initialize(server)
raise ArgumentError, "server is required" unless server
register([Puppet::Network::HTTP::API::V2.routes, Puppet::Network::HTTP::API::V1.routes])
@@ -26,9 +30,12 @@ class Puppet::Network::HTTP::WEBrickREST < WEBrick::HTTPServlet::AbstractServlet
params.merge(client_information(request))
end
- # WEBrick uses a service method to respond to requests. Simply delegate to the handler response method.
+ # WEBrick uses a service method to respond to requests. Simply delegate to
+ # the handler response method.
def service(request, response)
- process(request, response)
+ self.class.mutex.synchronize do
+ process(request, response)
+ end
end
def headers(request)
@@ -53,7 +60,9 @@ class Puppet::Network::HTTP::WEBrickREST < WEBrick::HTTPServlet::AbstractServlet
def client_cert(request)
if cert = request.client_cert
- Puppet::SSL::Certificate.from_instance(cert)
+ cert = Puppet::SSL::Certificate.from_instance(cert)
+ warn_if_near_expiration(cert)
+ cert
else
nil
end
diff --git a/lib/puppet/network/http_pool.rb b/lib/puppet/network/http_pool.rb
index 8c2783d36..5bab3803e 100644
--- a/lib/puppet/network/http_pool.rb
+++ b/lib/puppet/network/http_pool.rb
@@ -3,10 +3,9 @@ require 'puppet/network/http/connection'
module Puppet::Network; end
# This module contains the factory methods that should be used for getting a
-# {Puppet::Network::HTTP::Connection} instance.
-#
-# @note The name "HttpPool" is a misnomer, and a leftover of history, but we would
-# like to make this cache connections in the future.
+# {Puppet::Network::HTTP::Connection} instance. The pool may return a new
+# connection or a persistent cached connection, depending on the underlying
+# pool implementation in use.
#
# @api public
#
diff --git a/lib/puppet/node.rb b/lib/puppet/node.rb
index fbb57b2dd..d61d49385 100644
--- a/lib/puppet/node.rb
+++ b/lib/puppet/node.rb
@@ -13,7 +13,7 @@ class Puppet::Node
indirects :node, :terminus_setting => :node_terminus, :doc => "Where to find node information.
A node is composed of its name, its facts, and its environment."
- attr_accessor :name, :classes, :source, :ipaddress, :parameters, :trusted_data
+ attr_accessor :name, :classes, :source, :ipaddress, :parameters, :trusted_data, :environment_name
attr_reader :time, :facts
::PSON.register_document_type('Node',self)
@@ -24,7 +24,7 @@ class Puppet::Node
node = new(name)
node.classes = data['classes']
node.parameters = data['parameters']
- node.environment = data['environment']
+ node.environment_name = data['environment']
node
end
@@ -57,11 +57,20 @@ class Puppet::Node
def environment
if @environment
@environment
- elsif env = parameters["environment"]
- self.environment = env
- @environment
else
- Puppet.lookup(:environments).get(Puppet[:environment])
+ if env = parameters["environment"]
+ self.environment = env
+ elsif environment_name
+ self.environment = environment_name
+ else
+ # This should not be :current_environment, this is the default
+ # for a node when it has not specified its environment
+ # Tt will be used to establish what the current environment is.
+ #
+ self.environment = Puppet.lookup(:environments).get(Puppet[:environment])
+ end
+
+ @environment
end
end
@@ -73,6 +82,10 @@ class Puppet::Node
end
end
+ def has_environment_instance?
+ !@environment.nil?
+ end
+
def initialize(name, options = {})
raise ArgumentError, "Node names cannot be nil" unless name
@name = name
diff --git a/lib/puppet/node/environment.rb b/lib/puppet/node/environment.rb
index 4ad7c95da..5a0eed5e1 100644
--- a/lib/puppet/node/environment.rb
+++ b/lib/puppet/node/environment.rb
@@ -237,6 +237,27 @@ class Puppet::Node::Environment
# (optional)
attr_reader :config_version
+ # Checks to make sure that this environment did not have a manifest set in
+ # its original environment.conf if Puppet is configured with
+ # +disable_per_environment_manifest+ set true. If it did, the environment's
+ # modules may not function as intended by the original authors, and we may
+ # seek to halt a puppet compilation for a node in this environment.
+ #
+ # The only exception to this would be if the environment.conf manifest is an exact,
+ # uninterpolated match for the current +default_manifest+ setting.
+ #
+ # @return [Boolean] true if using directory environments, and
+ # Puppet[:disable_per_environment_manifest] is true, and this environment's
+ # original environment.conf had a manifest setting that is not the
+ # Puppet[:default_manifest].
+ # @api public
+ def conflicting_manifest_settings?
+ return false if Puppet[:environmentpath].empty? || !Puppet[:disable_per_environment_manifest]
+ environment_conf = Puppet.lookup(:environments).get_conf(name)
+ original_manifest = environment_conf.raw_setting(:manifest)
+ !original_manifest.nil? && !original_manifest.empty? && original_manifest != Puppet[:default_manifest]
+ end
+
# Return an environment-specific Puppet setting.
#
# @api public
@@ -432,11 +453,9 @@ class Puppet::Node::Environment
# This call does nothing unless files are being watched.
#
def check_for_reparse
- if watching?
- if (Puppet[:code] != @parsed_code) || (@known_resource_types && @known_resource_types.require_reparse?)
- @parsed_code = nil
- @known_resource_types = nil
- end
+ if (Puppet[:code] != @parsed_code) || (watching? && @known_resource_types && @known_resource_types.require_reparse?)
+ @parsed_code = nil
+ @known_resource_types = nil
end
end
@@ -479,6 +498,8 @@ class Puppet::Node::Environment
self.manifest == other.manifest
end
+ alias eql? ==
+
def hash
[self.class, name, full_modulepath, manifest].hash
end
@@ -529,9 +550,16 @@ class Puppet::Node::Environment
if file == NO_MANIFEST
Puppet::Parser::AST::Hostclass.new('')
elsif File.directory?(file)
- parse_results = Dir.entries(file).find_all { |f| f =~ /\.pp$/ }.sort.map do |pp_file|
- parser.file = File.join(file, pp_file)
- parser.parse
+ if Puppet[:parser] == 'future'
+ parse_results = Puppet::FileSystem::PathPattern.absolute(File.join(file, '**/*.pp')).glob.sort.map do | file_to_parse |
+ parser.file = file_to_parse
+ parser.parse
+ end
+ else
+ parse_results = Dir.entries(file).find_all { |f| f =~ /\.pp$/ }.sort.map do |file_to_parse|
+ parser.file = File.join(file, file_to_parse)
+ parser.parse
+ end
end
# Use a parser type specific merger to concatenate the results
Puppet::Parser::AST::Hostclass.new('', :code => Puppet::Parser::ParserFactory.code_merger.concatenate(parse_results))
diff --git a/lib/puppet/parser/ast.rb b/lib/puppet/parser/ast.rb
index b7e324687..ed2c66bba 100644
--- a/lib/puppet/parser/ast.rb
+++ b/lib/puppet/parser/ast.rb
@@ -125,6 +125,5 @@ require 'puppet/parser/ast/resource_override'
require 'puppet/parser/ast/resource_reference'
require 'puppet/parser/ast/resourceparam'
require 'puppet/parser/ast/selector'
-require 'puppet/parser/ast/tag'
require 'puppet/parser/ast/vardef'
require 'puppet/parser/code_merger'
diff --git a/lib/puppet/parser/ast/collection.rb b/lib/puppet/parser/ast/collection.rb
index 12f73281e..0f4b17500 100644
--- a/lib/puppet/parser/ast/collection.rb
+++ b/lib/puppet/parser/ast/collection.rb
@@ -15,6 +15,10 @@ class Puppet::Parser::AST
def evaluate(scope)
match, code = query && query.safeevaluate(scope)
+ if @type == 'class'
+ fail "Classes cannot be collected"
+ end
+
resource_type = scope.find_resource_type(@type)
fail "Resource type #{@type} doesn't exist" unless resource_type
newcoll = Puppet::Parser::Collector.new(scope, resource_type.name, match, code, self.form)
diff --git a/lib/puppet/parser/ast/collexpr.rb b/lib/puppet/parser/ast/collexpr.rb
index 9f266ed47..92f9cc10f 100644
--- a/lib/puppet/parser/ast/collexpr.rb
+++ b/lib/puppet/parser/ast/collexpr.rb
@@ -56,7 +56,7 @@ class CollExpr < AST::Branch
return match, code
end
- # Late binding evaluation of a collect expression (as done in 3x), but with proper Puppet Langauge
+ # Late binding evaluation of a collect expression (as done in 3x), but with proper Puppet Language
# semantics for equals and include
#
def evaluate4x(scope)
@@ -85,7 +85,7 @@ class CollExpr < AST::Branch
resource.tagged?(match2)
else
if resource[match1].is_a?(Array)
- @@compare_operator.include?(resource[match1], match2)
+ @@compare_operator.include?(resource[match1], match2, scope)
else
@@compare_operator.equals(resource[match1], match2)
end
diff --git a/lib/puppet/parser/ast/node.rb b/lib/puppet/parser/ast/node.rb
index b69a5c4e0..fd6443327 100644
--- a/lib/puppet/parser/ast/node.rb
+++ b/lib/puppet/parser/ast/node.rb
@@ -5,6 +5,11 @@ class Puppet::Parser::AST::Node < Puppet::Parser::AST::TopLevelConstruct
def initialize(names, context = {}, &ruby_code)
raise ArgumentError, "names should be an array" unless names.is_a? Array
+ if context[:parent]
+ msg = "Deprecation notice: Node inheritance is not supported in Puppet >= 4.0.0. See http://links.puppetlabs.com/puppet-node-inheritance-deprecation"
+ Puppet.puppet_deprecation_warning(msg, :key => "node-inheritance-#{names.join}", :file => context[:file], :line => context[:line])
+ end
+
@names = names
@context = context
@ruby_code = ruby_code
diff --git a/lib/puppet/parser/ast/pops_bridge.rb b/lib/puppet/parser/ast/pops_bridge.rb
index 77d06da9d..9c6a31744 100644
--- a/lib/puppet/parser/ast/pops_bridge.rb
+++ b/lib/puppet/parser/ast/pops_bridge.rb
@@ -16,7 +16,7 @@ class Puppet::Parser::AST::PopsBridge
def initialize args
super
- @@evaluator ||= Puppet::Pops::Parser::EvaluatingParser::Transitional.new()
+ @@evaluator ||= Puppet::Pops::Parser::EvaluatingParser.new()
end
def to_s
@@ -75,7 +75,7 @@ class Puppet::Parser::AST::PopsBridge
@program_model = program_model
@context = context
@ast_transformer ||= Puppet::Pops::Model::AstTransformer.new(@context[:file])
- @@evaluator ||= Puppet::Pops::Parser::EvaluatingParser::Transitional.new()
+ @@evaluator ||= Puppet::Pops::Parser::EvaluatingParser.new()
end
# This is the 3x API, the 3x AST searches through all code to find the instructions that can be instantiated.
@@ -117,16 +117,53 @@ class Puppet::Parser::AST::PopsBridge
# can thus reference all sorts of information. Here the value expression is wrapped in an AST Bridge to a Pops
# expression since the Pops side can not control the evaluation
if o.value
- [ o.name, NilAsUndefExpression.new(:value => o.value) ]
+ [o.name, NilAsUndefExpression.new(:value => o.value)]
else
- [ o.name ]
+ [o.name]
end
end
+ def create_type_map(definition)
+ result = {}
+ # No need to do anything if there are no parameters
+ return result unless definition.parameters.size > 0
+
+ # No need to do anything if there are no typed parameters
+ typed_parameters = definition.parameters.select {|p| p.type_expr }
+ return result if typed_parameters.empty?
+
+ # If there are typed parameters, they need to be evaluated to produce the corresponding type
+ # instances. This evaluation requires a scope. A scope is not available when doing deserialization
+ # (there is also no initialized evaluator). When running apply and test however, the environment is
+ # reused and we may reenter without a scope (which is fine). A debug message is then output in case
+ # there is the need to track down the odd corner case. See {#obtain_scope}.
+ #
+ if scope = obtain_scope
+ typed_parameters.each do |p|
+ result[p.name] = @@evaluator.evaluate(scope, p.type_expr)
+ end
+ end
+ result
+ end
+
+ # Obtains the scope or issues a warning if :global_scope is not bound
+ def obtain_scope
+ scope = Puppet.lookup(:global_scope) do
+ # This occurs when testing and when applying a catalog (there is no scope available then), and
+ # when running tests that run a partial setup.
+ # This is bad if the logic is trying to compile, but a warning can not be issues since it is a normal
+ # use case that there is no scope when requesting the type in order to just get the parameters.
+ Puppet.debug("Instantiating Resource with type checked parameters - scope is missing, skipping type checking.")
+ nil
+ end
+ scope
+ end
+
# Produces a hash with data for Definition and HostClass
def args_from_definition(o, modname)
args = {
:arguments => o.parameters.collect {|p| instantiate_Parameter(p) },
+ :argument_types => create_type_map(o),
:module_name => modname
}
unless is_nop?(o.body)
@@ -137,7 +174,7 @@ class Puppet::Parser::AST::PopsBridge
def instantiate_HostClassDefinition(o, modname)
args = args_from_definition(o, modname)
- args[:parent] = o.parent_class
+ args[:parent] = absolute_reference(o.parent_class)
Puppet::Resource::Type.new(:hostclass, o.name, @context.merge(args))
end
@@ -204,6 +241,12 @@ class Puppet::Parser::AST::PopsBridge
@ast_transformer.is_nop?(o)
end
+ def absolute_reference(ref)
+ if ref.nil? || ref.empty? || ref.start_with?('::')
+ ref
+ else
+ "::#{ref}"
+ end
+ end
end
-
end
diff --git a/lib/puppet/parser/ast/tag.rb b/lib/puppet/parser/ast/tag.rb
deleted file mode 100644
index 6f906a1c6..000000000
--- a/lib/puppet/parser/ast/tag.rb
+++ /dev/null
@@ -1,24 +0,0 @@
-require 'puppet/parser/ast/branch'
-
-class Puppet::Parser::AST
- # The code associated with a class. This is different from components
- # in that each class is a singleton -- only one will exist for a given
- # node.
- class Tag < AST::Branch
- @name = :class
- attr_accessor :type
-
- def evaluate(scope)
- types = @type.safeevaluate(scope)
-
- types = [types] unless types.is_a? Array
-
- types.each do |type|
- # Now set our class. We don't have to worry about checking
- # whether we've been evaluated because we're not evaluating
- # any code.
- scope.setclass(self.object_id, type)
- end
- end
- end
-end
diff --git a/lib/puppet/parser/compiler.rb b/lib/puppet/parser/compiler.rb
index 7f4e82298..5df2916fc 100644
--- a/lib/puppet/parser/compiler.rb
+++ b/lib/puppet/parser/compiler.rb
@@ -20,6 +20,17 @@ class Puppet::Parser::Compiler
$env_module_directories = nil
node.environment.check_for_reparse
+ if node.environment.conflicting_manifest_settings?
+ errmsg = [
+ "The 'disable_per_environment_manifest' setting is true, and this '#{node.environment}'",
+ "has an environment.conf manifest that conflicts with the 'default_manifest' setting.",
+ "Compilation has been halted in order to avoid running a catalog which may be using",
+ "unexpected manifests. For more information, see",
+ "http://docs.puppetlabs.com/puppet/latest/reference/environments.html",
+ ]
+ raise(Puppet::Error, errmsg.join(' '))
+ end
+
new(node).compile.to_resource
rescue => detail
message = "#{detail} on node #{node.name}"
@@ -107,25 +118,25 @@ class Puppet::Parser::Compiler
@catalog.environment_instance = environment
# Set the client's parameters into the top scope.
- Puppet::Util::Profiler.profile("Compile: Set node parameters") { set_node_parameters }
+ Puppet::Util::Profiler.profile("Compile: Set node parameters", [:compiler, :set_node_params]) { set_node_parameters }
- Puppet::Util::Profiler.profile("Compile: Created settings scope") { create_settings_scope }
+ Puppet::Util::Profiler.profile("Compile: Created settings scope", [:compiler, :create_settings_scope]) { create_settings_scope }
if is_binder_active?
# create injector, if not already created - this is for 3x that does not trigger
# lazy loading of injector via context
- Puppet::Util::Profiler.profile("Compile: Created injector") { injector }
+ Puppet::Util::Profiler.profile("Compile: Created injector", [:compiler, :create_injector]) { injector }
end
- Puppet::Util::Profiler.profile("Compile: Evaluated main") { evaluate_main }
+ Puppet::Util::Profiler.profile("Compile: Evaluated main", [:compiler, :evaluate_main]) { evaluate_main }
- Puppet::Util::Profiler.profile("Compile: Evaluated AST node") { evaluate_ast_node }
+ Puppet::Util::Profiler.profile("Compile: Evaluated AST node", [:compiler, :evaluate_ast_node]) { evaluate_ast_node }
- Puppet::Util::Profiler.profile("Compile: Evaluated node classes") { evaluate_node_classes }
+ Puppet::Util::Profiler.profile("Compile: Evaluated node classes", [:compiler, :evaluate_node_classes]) { evaluate_node_classes }
- Puppet::Util::Profiler.profile("Compile: Evaluated generators") { evaluate_generators }
+ Puppet::Util::Profiler.profile("Compile: Evaluated generators", [:compiler, :evaluate_generators]) { evaluate_generators }
- Puppet::Util::Profiler.profile("Compile: Finished catalog") { finish }
+ Puppet::Util::Profiler.profile("Compile: Finished catalog", [:compiler, :finish_catalog]) { finish }
fail_on_unevaluated
@@ -154,9 +165,6 @@ class Puppet::Parser::Compiler
# Return the node's environment.
def environment
- unless node.environment.is_a? Puppet::Node::Environment
- raise Puppet::DevError, "node #{node} has an invalid environment!"
- end
node.environment
end
@@ -204,25 +212,25 @@ class Puppet::Parser::Compiler
class_parameters = classes
classes = classes.keys
end
- classes.each do |name|
- # If we can find the class, then make a resource that will evaluate it.
- if klass = scope.find_hostclass(name, :assume_fqname => fqname)
-
- # If parameters are passed, then attempt to create a duplicate resource
- # so the appropriate error is thrown.
- if class_parameters
- resource = klass.ensure_in_catalog(scope, class_parameters[name] || {})
- else
- next if scope.class_scope(klass)
- resource = klass.ensure_in_catalog(scope)
- end
- # If they've disabled lazy evaluation (which the :include function does),
- # then evaluate our resource immediately.
- resource.evaluate unless lazy_evaluate
- else
- raise Puppet::Error, "Could not find class #{name} for #{node.name}"
+ hostclasses = classes.collect do |name|
+ scope.find_hostclass(name, :assume_fqname => fqname) or raise Puppet::Error, "Could not find class #{name} for #{node.name}"
+ end
+
+ if class_parameters
+ resources = ensure_classes_with_parameters(scope, hostclasses, class_parameters)
+ if !lazy_evaluate
+ resources.each(&:evaluate)
end
+
+ resources
+ else
+ already_included, newly_included = ensure_classes_without_parameters(scope, hostclasses)
+ if !lazy_evaluate
+ newly_included.each(&:evaluate)
+ end
+
+ already_included + newly_included
end
end
@@ -299,6 +307,27 @@ class Puppet::Parser::Compiler
private
+ def ensure_classes_with_parameters(scope, hostclasses, parameters)
+ hostclasses.collect do |klass|
+ klass.ensure_in_catalog(scope, parameters[klass.name] || {})
+ end
+ end
+
+ def ensure_classes_without_parameters(scope, hostclasses)
+ already_included = []
+ newly_included = []
+ hostclasses.each do |klass|
+ class_scope = scope.class_scope(klass)
+ if class_scope
+ already_included << class_scope.resource
+ else
+ newly_included << klass.ensure_in_catalog(scope)
+ end
+ end
+
+ [already_included, newly_included]
+ end
+
# If ast nodes are enabled, then see if we can find and evaluate one.
def evaluate_ast_node
return unless ast_nodes?
@@ -331,7 +360,7 @@ class Puppet::Parser::Compiler
# We have to iterate over a dup of the array because
# collections can delete themselves from the list, which
# changes its length and causes some collections to get missed.
- Puppet::Util::Profiler.profile("Evaluated collections") do
+ Puppet::Util::Profiler.profile("Evaluated collections", [:compiler, :evaluate_collections]) do
found_something = false
@collections.dup.each do |collection|
found_something = true if collection.evaluate
@@ -346,9 +375,9 @@ class Puppet::Parser::Compiler
# evaluate_generators loop.
def evaluate_definitions
exceptwrap do
- Puppet::Util::Profiler.profile("Evaluated definitions") do
+ Puppet::Util::Profiler.profile("Evaluated definitions", [:compiler, :evaluate_definitions]) do
!unevaluated_resources.each do |resource|
- Puppet::Util::Profiler.profile("Evaluated resource #{resource}") do
+ Puppet::Util::Profiler.profile("Evaluated resource #{resource}", [:compiler, :evaluate_resource, resource]) do
resource.evaluate
end
end.empty?
@@ -365,7 +394,7 @@ class Puppet::Parser::Compiler
loop do
done = true
- Puppet::Util::Profiler.profile("Iterated (#{count + 1}) on generators") do
+ Puppet::Util::Profiler.profile("Iterated (#{count + 1}) on generators", [:compiler, :iterate_on_generators]) do
# Call collections first, then definitions.
done = false if evaluate_collections
done = false if evaluate_definitions
@@ -553,10 +582,8 @@ class Puppet::Parser::Compiler
end
def create_settings_scope
- unless settings_type = environment.known_resource_types.hostclass("settings")
- settings_type = Puppet::Resource::Type.new :hostclass, "settings"
- environment.known_resource_types.add(settings_type)
- end
+ settings_type = Puppet::Resource::Type.new :hostclass, "settings"
+ environment.known_resource_types.add(settings_type)
settings_resource = Puppet::Parser::Resource.new("class", "settings", :scope => @topscope)
@@ -566,9 +593,10 @@ class Puppet::Parser::Compiler
scope = @topscope.class_scope(settings_type)
+ env = environment
Puppet.settings.each do |name, setting|
- next if name.to_s == "name"
- scope[name.to_s] = environment[name]
+ next if name == :name
+ scope[name.to_s] = env[name]
end
end
diff --git a/lib/puppet/parser/e4_parser_adapter.rb b/lib/puppet/parser/e4_parser_adapter.rb
index 01822db13..00911b5d5 100644
--- a/lib/puppet/parser/e4_parser_adapter.rb
+++ b/lib/puppet/parser/e4_parser_adapter.rb
@@ -19,8 +19,8 @@ class Puppet::Parser::E4ParserAdapter
@file_watcher = file_watcher || NullFileWatcher.new
@file = ''
@string = ''
- @use = :undefined
- @@evaluating_parser ||= Puppet::Pops::Parser::EvaluatingParser::Transitional.new()
+ @use = :unspecified
+ @@evaluating_parser ||= Puppet::Pops::Parser::EvaluatingParser.new()
end
def file=(file)
diff --git a/lib/puppet/parser/e_parser_adapter.rb b/lib/puppet/parser/e_parser_adapter.rb
deleted file mode 100644
index fe0e28b55..000000000
--- a/lib/puppet/parser/e_parser_adapter.rb
+++ /dev/null
@@ -1,119 +0,0 @@
-require 'puppet/pops'
-
-module Puppet; module Parser; end; end;
-# Adapts an egrammar/eparser to respond to the public API of the classic parser
-#
-class Puppet::Parser::EParserAdapter
-
- def initialize(classic_parser)
- @classic_parser = classic_parser
- @file = ''
- @string = ''
- @use = :undefined
- end
-
- def file=(file)
- @classic_parser.file = file
- @file = file
- @use = :file
- end
-
- def parse(string = nil)
- if @file =~ /\.rb$/
- return parse_ruby_file
- else
- self.string= string if string
- parser = Puppet::Pops::Parser::Parser.new()
- parse_result = if @use == :string
- parser.parse_string(@string)
- else
- parser.parse_file(@file)
- end
- # Compute the source_file to set in created AST objects (it was either given, or it may be unknown
- # if caller did not set a file and the present a string.
- #
- source_file = @file || "unknown-source-location"
-
- # Validate
- validate(parse_result)
- # Transform the result, but only if not nil
- parse_result = Puppet::Pops::Model::AstTransformer.new(source_file, @classic_parser).transform(parse_result) if parse_result
- if parse_result && !parse_result.is_a?(Puppet::Parser::AST::BlockExpression)
- # Need to transform again, if result is not wrapped in something iterable when handed off to
- # a new Hostclass as its code.
- parse_result = Puppet::Parser::AST::BlockExpression.new(:children => [parse_result]) if parse_result
- end
- end
-
- Puppet::Parser::AST::Hostclass.new('', :code => parse_result)
- end
-
- def validate(parse_result)
- # TODO: This is too many hoops to jump through... ugly API
- # could reference a ValidatorFactory.validator_3_1(acceptor) instead.
- # and let the factory abstract the rest.
- #
- return unless parse_result
-
- acceptor = Puppet::Pops::Validation::Acceptor.new
- validator = Puppet::Pops::Validation::ValidatorFactory_3_1.new().validator(acceptor)
- validator.validate(parse_result)
-
- max_errors = Puppet[:max_errors]
- max_warnings = Puppet[:max_warnings] + 1
- max_deprecations = Puppet[:max_deprecations] + 1
-
- # If there are warnings output them
- warnings = acceptor.warnings
- if warnings.size > 0
- formatter = Puppet::Pops::Validation::DiagnosticFormatterPuppetStyle.new
- emitted_w = 0
- emitted_dw = 0
- acceptor.warnings.each {|w|
- if w.severity == :deprecation
- # Do *not* call Puppet.deprecation_warning it is for internal deprecation, not
- # deprecation of constructs in manifests! (It is not designed for that purpose even if
- # used throughout the code base).
- #
- Puppet.warning(formatter.format(w)) if emitted_dw < max_deprecations
- emitted_dw += 1
- else
- Puppet.warning(formatter.format(w)) if emitted_w < max_warnings
- emitted_w += 1
- end
- break if emitted_w > max_warnings && emitted_dw > max_deprecations # but only then
- }
- end
-
- # If there were errors, report the first found. Use a puppet style formatter.
- errors = acceptor.errors
- if errors.size > 0
- formatter = Puppet::Pops::Validation::DiagnosticFormatterPuppetStyle.new
- if errors.size == 1 || max_errors <= 1
- # raise immediately
- raise Puppet::ParseError.new(formatter.format(errors[0]))
- end
- emitted = 0
- errors.each do |e|
- Puppet.err(formatter.format(e))
- emitted += 1
- break if emitted >= max_errors
- end
- warnings_message = warnings.size > 0 ? ", and #{warnings.size} warnings" : ""
- giving_up_message = "Found #{errors.size} errors#{warnings_message}. Giving up"
- exception = Puppet::ParseError.new(giving_up_message)
- exception.file = errors[0].file
- raise exception
- end
- end
-
- def string=(string)
- @classic_parser.string = string
- @string = string
- @use = :string
- end
-
- def parse_ruby_file
- @classic_parser.parse
- end
-end
diff --git a/lib/puppet/parser/files.rb b/lib/puppet/parser/files.rb
index 605bbeb69..81c523ffd 100644
--- a/lib/puppet/parser/files.rb
+++ b/lib/puppet/parser/files.rb
@@ -1,10 +1,10 @@
require 'puppet/module'
-module Puppet; module Parser; module Files
+module Puppet::Parser::Files
module_function
- # Return a list of manifests as absolute filenames matching the given
+ # Return a list of manifests as absolute filenames matching the given
# pattern.
#
# @param pattern [String] A reference for a file in a module. It is the format "<modulename>/<file glob>"
@@ -25,49 +25,93 @@ module Puppet; module Parser; module Files
[nil, []]
end
- # Find the concrete file denoted by +file+. If +file+ is absolute,
- # return it directly. Otherwise try to find relative to the +templatedir+
- # config param. If that fails try to find it as a template in a
- # module.
- # In all cases, an absolute path is returned, which does not
- # necessarily refer to an existing file
+ # Find the path to the given file selector. Files can be selected in
+ # one of two ways:
+ # * absolute path: the path is simply returned
+ # * modulename/filename selector: a file is found in the file directory
+ # of the named module.
+ #
+ # In the second case a nil is returned if there isn't a file found. In the
+ # first case (absolute path), there is no existence check done and so the
+ # path will be returned even if there isn't a file available.
+ #
+ # @param template [String] the file selector
+ # @param environment [Puppet::Node::Environment] the environment in which to search
+ # @return [String, nil] the absolute path to the file or nil if there is no file found
#
# @api private
- def find_template(template, environment)
- if template == File.expand_path(template)
- return template
- end
+ def find_file(file, environment)
+ if Puppet::Util.absolute_path?(file)
+ file
+ else
+ path, module_file = split_file_path(file)
+ mod = environment.module(path)
- if template_paths = templatepath(environment)
- # If we can find the template in :templatedir, we return that.
- template_paths.collect { |path|
- File::join(path, template)
- }.each do |f|
- return f if Puppet::FileSystem.exist?(f)
+ if module_file && mod
+ mod.file(module_file)
+ else
+ nil
end
end
+ end
- # check in the default template dir, if there is one
- if td_file = find_template_in_module(template, environment)
- return td_file
+ # Find the path to the given template selector. Templates can be selected in
+ # a number of ways:
+ # * absolute path: the path is simply returned
+ # * path relative to the templatepath setting: a file is found and the path
+ # is returned
+ # * modulename/filename selector: a file is found in the template directory
+ # of the named module.
+ #
+ # In the last two cases a nil is returned if there isn't a file found. In the
+ # first case (absolute path), there is no existence check done and so the
+ # path will be returned even if there isn't a file available.
+ #
+ # @param template [String] the template selector
+ # @param environment [Puppet::Node::Environment] the environment in which to search
+ # @return [String, nil] the absolute path to the template file or nil if there is no file found
+ #
+ # @api private
+ def find_template(template, environment)
+ if Puppet::Util.absolute_path?(template)
+ template
+ else
+ in_templatepath = find_template_in_templatepath(template, environment)
+ if in_templatepath
+ in_templatepath
+ else
+ find_template_in_module(template, environment)
+ end
end
+ end
- nil
+ # Templatepaths are deprecated functionality, this will be going away in
+ # Puppet 4.
+ #
+ # @api private
+ def find_template_in_templatepath(template, environment)
+ template_paths = templatepath(environment)
+ if template_paths
+ template_paths.collect do |path|
+ File::join(path, template)
+ end.find do |f|
+ Puppet::FileSystem.exist?(f)
+ end
+ else
+ nil
+ end
end
# @api private
def find_template_in_module(template, environment)
path, file = split_file_path(template)
+ mod = environment.module(path)
- # Because templates don't have an assumed template name, like manifests do,
- # we treat templates with no name as being templates in the main template
- # directory.
- return nil unless file
-
- if mod = environment.module(path) and t = mod.template(file)
- return t
+ if file && mod
+ mod.template(file)
+ else
+ nil
end
- nil
end
# Return an array of paths by splitting the +templatedir+ config
@@ -84,11 +128,10 @@ module Puppet; module Parser; module Files
# nil if the path is empty or absolute (starts with a /).
# @api private
def split_file_path(path)
- if path == "" or Puppet::Util.absolute_path?(path)
+ if path == "" || Puppet::Util.absolute_path?(path)
nil
else
path.split(File::SEPARATOR, 2)
end
end
-
-end; end; end
+end
diff --git a/lib/puppet/parser/functions.rb b/lib/puppet/parser/functions.rb
index 87bf5de1f..be104d810 100644
--- a/lib/puppet/parser/functions.rb
+++ b/lib/puppet/parser/functions.rb
@@ -17,8 +17,7 @@ module Puppet::Parser::Functions
#
# @api private
def self.reset
- @functions = Hash.new { |h,k| h[k] = {} }
- @modules = Hash.new
+ @modules = {}
# Runs a newfunction to create a function for each of the log levels
Puppet::Util::Log.levels.each do |level|
@@ -44,7 +43,21 @@ module Puppet::Parser::Functions
#
# @api private
def self.environment_module(env)
- @modules[env.name] ||= Module.new
+ @modules[env.name] ||= Module.new do
+ @metadata = {}
+
+ def self.all_function_info
+ @metadata
+ end
+
+ def self.get_function_info(name)
+ @metadata[name]
+ end
+
+ def self.add_function_info(name, info)
+ @metadata[name] = info
+ end
+ end
end
# Create a new Puppet DSL function.
@@ -120,12 +133,6 @@ module Puppet::Parser::Functions
#
# @api public
def self.newfunction(name, options = {}, &block)
- # Short circuit this call when 4x "biff" is in effect to allow the new loader system to load
- # and define the function a different way.
- #
- if Puppet[:biff]
- return Puppet::Pops::Loader::RubyLegacyFunctionInstantiator.legacy_newfunction(name, options, &block)
- end
name = name.intern
environment = options[:environment] || Puppet.lookup(:current_environment)
@@ -144,8 +151,10 @@ module Puppet::Parser::Functions
environment_module(environment).send(:define_method, real_fname, &block)
fname = "function_#{name}"
- environment_module(environment).send(:define_method, fname) do |*args|
- Puppet::Util::Profiler.profile("Called #{name}") do
+ env_module = environment_module(environment)
+
+ env_module.send(:define_method, fname) do |*args|
+ Puppet::Util::Profiler.profile("Called #{name}", [:functions, name]) do
if args[0].is_a? Array
if arity >= 0 and args[0].size != arity
raise ArgumentError, "#{name}(): Wrong number of arguments given (#{args[0].size} for #{arity})"
@@ -162,7 +171,8 @@ module Puppet::Parser::Functions
func = {:arity => arity, :type => ftype, :name => fname}
func[:doc] = options[:doc] if options[:doc]
- add_function(name, func, environment)
+ env_module.add_function_info(name, func)
+
func
end
@@ -239,17 +249,14 @@ module Puppet::Parser::Functions
private
def merged_functions(environment)
- @functions[Puppet.lookup(:root_environment)].merge(@functions[environment])
- end
+ root = environment_module(Puppet.lookup(:root_environment))
+ env = environment_module(environment)
- def get_function(name, environment)
- name = name.intern
- merged_functions(environment)[name]
+ root.all_function_info.merge(env.all_function_info)
end
- def add_function(name, func, environment)
- name = name.intern
- @functions[environment][name] = func
+ def get_function(name, environment)
+ environment_module(environment).get_function_info(name.intern) || environment_module(Puppet.lookup(:root_environment)).get_function_info(name.intern)
end
end
end
diff --git a/lib/puppet/parser/functions/assert_type.rb b/lib/puppet/parser/functions/assert_type.rb
new file mode 100644
index 000000000..577697420
--- /dev/null
+++ b/lib/puppet/parser/functions/assert_type.rb
@@ -0,0 +1,31 @@
+Puppet::Parser::Functions::newfunction(
+ :assert_type,
+ :type => :rvalue,
+ :arity => -3,
+ :doc => "Returns the given value if it is an instance of the given type, and raises an error otherwise.
+Optionally, if a block is given (accepting two parameters), it will be called instead of raising
+an error. This to enable giving the user richer feedback, or to supply a default value.
+
+Example: assert that `$b` is a non empty `String` and assign to `$a`:
+
+ $a = assert_type(String[1], $b)
+
+Example using custom error message:
+
+ $a = assert_type(String[1], $b) |$expected, $actual| {
+ fail('The name cannot be empty')
+ }
+
+Example, using a warning and a default:
+
+ $a = assert_type(String[1], $b) |$expected, $actual| {
+ warning('Name is empty, using default')
+ 'anonymous'
+ }
+
+See the documentation for 'The Puppet Type System' for more information about types.
+- since Puppet 3.7
+- requires future parser/evaluator
+") do |args|
+ function_fail(["assert_type() is only available when parser/evaluator future is in effect"])
+end
diff --git a/lib/puppet/parser/functions/collect.rb b/lib/puppet/parser/functions/collect.rb
deleted file mode 100644
index fea42a4df..000000000
--- a/lib/puppet/parser/functions/collect.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-Puppet::Parser::Functions::newfunction(
-:collect,
-:type => :rvalue,
-:arity => 2,
-:doc => <<-'ENDHEREDOC') do |args|
- The 'collect' function has been renamed to 'map'. Please update your manifests.
-
- The collect function is reserved for future use.
- - Removed as of 3.4
- - requires `parser = future`.
- ENDHEREDOC
-
- raise NotImplementedError,
- "The 'collect' function has been renamed to 'map'. Please update your manifests."
-end
diff --git a/lib/puppet/parser/functions/contain.rb b/lib/puppet/parser/functions/contain.rb
index 8eb514561..9c9447661 100644
--- a/lib/puppet/parser/functions/contain.rb
+++ b/lib/puppet/parser/functions/contain.rb
@@ -11,16 +11,26 @@ comma-separated list of class names.
A contained class will not be applied before the containing class is
begun, and will be finished before the containing class is finished.
+
+When the future parser is used, you must use the class's full name;
+relative names are no longer allowed. In addition to names in string form,
+you may also directly use Class and Resource Type values that are produced by
+the future parser's resource and relationship expressions.
"
) do |classes|
scope = self
- scope.function_include(classes)
+ # Make call patterns uniform and protected against nested arrays, also make
+ # names absolute if so desired.
+ classes = transform_and_assert_classnames(classes.is_a?(Array) ? classes.flatten : [classes])
+
+ containing_resource = scope.resource
- classes.each do |class_name|
- class_resource = scope.catalog.resource("Class", class_name)
- if ! scope.catalog.edge?(scope.resource, class_resource)
- scope.catalog.add_edge(scope.resource, class_resource)
+ # This is the same as calling the include function but faster and does not rely on the include
+ # function (which is a statement) to return something (it should not).
+ (compiler.evaluate_classes(classes, self, false) || []).each do |resource|
+ if ! scope.catalog.edge?(containing_resource, resource)
+ scope.catalog.add_edge(containing_resource, resource)
end
end
end
diff --git a/lib/puppet/parser/functions/create_resources.rb b/lib/puppet/parser/functions/create_resources.rb
index 1c3b910b0..d0ac165c2 100644
--- a/lib/puppet/parser/functions/create_resources.rb
+++ b/lib/puppet/parser/functions/create_resources.rb
@@ -73,6 +73,10 @@ Puppet::Parser::Functions::newfunction(:create_resources, :arity => -3, :doc =>
begin
resource.safeevaluate(self)
rescue Puppet::ParseError => internal_error
- raise internal_error.original
+ if internal_error.original.nil?
+ raise internal_error
+ else
+ raise internal_error.original
+ end
end
end
diff --git a/lib/puppet/parser/functions/digest.rb b/lib/puppet/parser/functions/digest.rb
new file mode 100644
index 000000000..8cd75c3fb
--- /dev/null
+++ b/lib/puppet/parser/functions/digest.rb
@@ -0,0 +1,5 @@
+require 'puppet/util/checksums'
+Puppet::Parser::Functions::newfunction(:digest, :type => :rvalue, :arity => 1, :doc => "Returns a hash value from a provided string using the digest_algorithm setting from the Puppet config file.") do |args|
+ algo = Puppet[:digest_algorithm]
+ Puppet::Util::Checksums.method(algo.intern).call args[0]
+end
diff --git a/lib/puppet/parser/functions/each.rb b/lib/puppet/parser/functions/each.rb
index 39d26ba38..3f4437eef 100644
--- a/lib/puppet/parser/functions/each.rb
+++ b/lib/puppet/parser/functions/each.rb
@@ -1,109 +1,48 @@
Puppet::Parser::Functions::newfunction(
-:each,
-:type => :rvalue,
-:arity => 2,
-:doc => <<-'ENDHEREDOC') do |args|
- Applies a parameterized block to each element in a sequence of selected entries from the first
- argument and returns the first argument.
-
- This function takes two mandatory arguments: the first should be an Array or a Hash or something that is
- of enumerable type (integer, Integer range, or String), and the second
- a parameterized block as produced by the puppet syntax:
-
- $a.each |$x| { ... }
- each($a) |$x| { ... }
-
- When the first argument is an Array (or of enumerable type other than Hash), the parameterized block
- should define one or two block parameters.
- For each application of the block, the next element from the array is selected, and it is passed to
- the block if the block has one parameter. If the block has two parameters, the first is the elements
- index, and the second the value. The index starts from 0.
-
- $a.each |$index, $value| { ... }
- each($a) |$index, $value| { ... }
-
- When the first argument is a Hash, the parameterized block should define one or two parameters.
- When one parameter is defined, the iteration is performed with each entry as an array of `[key, value]`,
- and when two parameters are defined the iteration is performed with key and value.
-
- $a.each |$entry| { ..."key ${$entry[0]}, value ${$entry[1]}" }
- $a.each |$key, $value| { ..."key ${key}, value ${value}" }
-
- *Examples*
-
- [1,2,3].each |$val| { ... } # 1, 2, 3
- [5,6,7].each |$index, $val| { ... } # (0, 5), (1, 6), (2, 7)
- {a=>1, b=>2, c=>3}].each |$val| { ... } # ['a', 1], ['b', 2], ['c', 3]
- {a=>1, b=>2, c=>3}.each |$key, $val| { ... } # ('a', 1), ('b', 2), ('c', 3)
- Integer[ 10, 20 ].each |$index, $value| { ... } # (0, 10), (1, 11) ...
- "hello".each |$char| { ... } # 'h', 'e', 'l', 'l', 'o'
- 3.each |$number| { ... } # 0, 1, 2
-
- - Since 3.2 for Array and Hash
- - Since 3.5 for other enumerables
- - requires `parser = future`.
- ENDHEREDOC
- require 'puppet/parser/ast/lambda'
-
- def foreach_Hash(o, scope, pblock, serving_size)
- enumerator = o.each_pair
- if serving_size == 1
- (o.size).times do
- pblock.call(scope, enumerator.next)
- end
- else
- (o.size).times do
- pblock.call(scope, *enumerator.next)
- end
- end
- end
-
- def foreach_Enumerator(enumerator, scope, pblock, serving_size)
- index = 0
- if serving_size == 1
- begin
- loop { pblock.call(scope, enumerator.next) }
- rescue StopIteration
- end
- else
- begin
- loop do
- pblock.call(scope, index, enumerator.next)
- index = index +1
- end
- rescue StopIteration
- end
- end
- end
-
- raise ArgumentError, ("each(): wrong number of arguments (#{args.length}; expected 2, got #{args.length})") if args.length != 2
- receiver = args[0]
- pblock = args[1]
- raise ArgumentError, ("each(): wrong argument type (#{args[1].class}; must be a parameterized block.") unless pblock.respond_to?(:puppet_lambda)
-
- serving_size = pblock.parameter_count
- if serving_size == 0
- raise ArgumentError, "each(): block must define at least one parameter; value. Block has 0."
- end
-
- case receiver
- when Hash
- if serving_size > 2
- raise ArgumentError, "each(): block must define at most two parameters; key, value. Block has #{serving_size}; "+
- pblock.parameter_names.join(', ')
- end
- foreach_Hash(receiver, self, pblock, serving_size)
- else
- if serving_size > 2
- raise ArgumentError, "each(): block must define at most two parameters; index, value. Block has #{serving_size}; "+
- pblock.parameter_names.join(', ')
- end
- enum = Puppet::Pops::Types::Enumeration.enumerator(receiver)
- unless enum
- raise ArgumentError, ("each(): wrong argument type (#{receiver.class}; must be something enumerable.")
- end
- foreach_Enumerator(enum, self, pblock, serving_size)
- end
- # each always produces the receiver
- receiver
+ :each,
+ :type => :rvalue,
+ :arity => -3,
+ :doc => <<-DOC
+Applies a parameterized block to each element in a sequence of selected entries from the first
+argument and returns the first argument.
+
+This function takes two mandatory arguments: the first should be an Array or a Hash or something that is
+of enumerable type (integer, Integer range, or String), and the second
+a parameterized block as produced by the puppet syntax:
+
+ $a.each |$x| { ... }
+ each($a) |$x| { ... }
+
+When the first argument is an Array (or of enumerable type other than Hash), the parameterized block
+should define one or two block parameters.
+For each application of the block, the next element from the array is selected, and it is passed to
+the block if the block has one parameter. If the block has two parameters, the first is the elements
+index, and the second the value. The index starts from 0.
+
+ $a.each |$index, $value| { ... }
+ each($a) |$index, $value| { ... }
+
+When the first argument is a Hash, the parameterized block should define one or two parameters.
+When one parameter is defined, the iteration is performed with each entry as an array of `[key, value]`,
+and when two parameters are defined the iteration is performed with key and value.
+
+ $a.each |$entry| { ..."key ${$entry[0]}, value ${$entry[1]}" }
+ $a.each |$key, $value| { ..."key ${key}, value ${value}" }
+
+Example using each:
+
+ [1,2,3].each |$val| { ... } # 1, 2, 3
+ [5,6,7].each |$index, $val| { ... } # (0, 5), (1, 6), (2, 7)
+ {a=>1, b=>2, c=>3}].each |$val| { ... } # ['a', 1], ['b', 2], ['c', 3]
+ {a=>1, b=>2, c=>3}.each |$key, $val| { ... } # ('a', 1), ('b', 2), ('c', 3)
+ Integer[ 10, 20 ].each |$index, $value| { ... } # (0, 10), (1, 11) ...
+ "hello".each |$char| { ... } # 'h', 'e', 'l', 'l', 'o'
+ 3.each |$number| { ... } # 0, 1, 2
+
+- since 3.2 for Array and Hash
+- since 3.5 for other enumerables
+- note requires `parser = future`
+DOC
+) do |args|
+ function_fail(["each() is only available when parser/evaluator future is in effect"])
end
diff --git a/lib/puppet/parser/functions/epp.rb b/lib/puppet/parser/functions/epp.rb
index 616d61422..5127aec71 100644
--- a/lib/puppet/parser/functions/epp.rb
+++ b/lib/puppet/parser/functions/epp.rb
@@ -1,7 +1,15 @@
Puppet::Parser::Functions::newfunction(:epp, :type => :rvalue, :arity => -2, :doc =>
"Evaluates an Embedded Puppet Template (EPP) file and returns the rendered text result as a String.
-EPP support the following tags:
+The first argument to this function should be a `<MODULE NAME>/<TEMPLATE FILE>`
+reference, which will load `<TEMPLATE FILE>` from a module's `templates`
+directory. (For example, the reference `apache/vhost.conf.epp` will load the
+file `<MODULES DIRECTORY>/apache/templates/vhost.conf.epp`.)
+
+The second argument is optional; if present, it should be a hash containing parameters for the
+template. (See below.)
+
+EPP supports the following tags:
* `<%= puppet expression %>` - This tag renders the value of the expression it contains.
* `<% puppet expression(s) %>` - This tag will execute the expression(s) it contains, but renders nothing.
@@ -9,7 +17,7 @@ EPP support the following tags:
* `<%%` or `%%>` - Renders a literal `<%` or `%>` respectively.
* `<%-` - Same as `<%` but suppresses any leading whitespace.
* `-%>` - Same as `%>` but suppresses any trailing whitespace on the same line (including line break).
-* `<%-( parameters )-%>` - When placed as the first tag declares the template's parameters.
+* `<%- |parameters| -%>` - When placed as the first tag declares the template's parameters.
File based EPP supports the following visibilities of variables in scope:
@@ -18,7 +26,7 @@ File based EPP supports the following visibilities of variables in scope:
* Global + declared parameters - if the EPP declares parameters, given argument names must match
EPP supports parameters by placing an optional parameter list as the very first element in the EPP. As an example,
-`<%- ($x, $y, $z='unicorn') -%>` when placed first in the EPP text declares that the parameters `x` and `y` must be
+`<%- |$x, $y, $z = 'unicorn'| -%>` when placed first in the EPP text declares that the parameters `x` and `y` must be
given as template arguments when calling `inline_epp`, and that `z` if not given as a template argument
defaults to `'unicorn'`. Template parameters are available as variables, e.g.arguments `$x`, `$y` and `$z` in the example.
Note that `<%-` must be used or any leading whitespace will be interpreted as text
@@ -31,11 +39,7 @@ scope where the `epp` function is called from.
- See function inline_epp for examples of EPP
- Since 3.5
-- Requires Future Parser") do |arguments|
- # Requires future parser
- unless Puppet[:parser] == "future"
- raise ArgumentError, "epp(): function is only available when --parser future is in effect"
- end
- Puppet::Pops::Evaluator::EppEvaluator.epp(self, arguments[0], self.compiler.environment, arguments[1])
+- Requires Future Parser") do |args|
+ function_fail(["epp() is only available when parser/evaluator future is in effect"])
end
diff --git a/lib/puppet/parser/functions/file.rb b/lib/puppet/parser/functions/file.rb
index 17401fc8b..cde496ab4 100644
--- a/lib/puppet/parser/functions/file.rb
+++ b/lib/puppet/parser/functions/file.rb
@@ -1,22 +1,30 @@
-# Returns the contents of a file
-
Puppet::Parser::Functions::newfunction(
:file, :arity => -2, :type => :rvalue,
- :doc => "Return the contents of a file. Multiple files
- can be passed, and the first file that exists will be read in."
+ :doc => "Loads a file from a module and returns its contents as a string.
+
+ The argument to this function should be a `<MODULE NAME>/<FILE>`
+ reference, which will load `<FILE>` from a module's `files`
+ directory. (For example, the reference `mysql/mysqltuner.pl` will load the
+ file `<MODULES DIRECTORY>/mysql/files/mysqltuner.pl`.)
+
+ This function can also accept:
+
+ * An absolute path, which can load a file from anywhere on disk.
+ * Multiple arguments, which will return the contents of the **first** file
+ found, skipping any files that don't exist.
+ "
) do |vals|
- ret = nil
+ path = nil
vals.each do |file|
- unless Puppet::Util.absolute_path?(file)
- raise Puppet::ParseError, "Files must be fully qualified"
- end
- if Puppet::FileSystem.exist?(file)
- ret = File.read(file)
+ found = Puppet::Parser::Files.find_file(file, compiler.environment)
+ if found && Puppet::FileSystem.exist?(found)
+ path = found
break
end
end
- if ret
- ret
+
+ if path
+ File.read(path)
else
raise Puppet::ParseError, "Could not find any files from #{vals.join(", ")}"
end
diff --git a/lib/puppet/parser/functions/filter.rb b/lib/puppet/parser/functions/filter.rb
index 5760a8a75..365de9fcf 100644
--- a/lib/puppet/parser/functions/filter.rb
+++ b/lib/puppet/parser/functions/filter.rb
@@ -1,100 +1,44 @@
-require 'puppet/parser/ast/lambda'
-
Puppet::Parser::Functions::newfunction(
-:filter,
-:type => :rvalue,
-:arity => 2,
-:doc => <<-'ENDHEREDOC') do |args|
- Applies a parameterized block to each element in a sequence of entries from the first
- argument and returns an array or hash (same type as left operand for array/hash, and array for
- other enumerable types) with the entries for which the block evaluates to `true`.
-
- This function takes two mandatory arguments: the first should be an Array, a Hash, or an
- Enumerable object (integer, Integer range, or String),
- and the second a parameterized block as produced by the puppet syntax:
-
- $a.filter |$x| { ... }
- filter($a) |$x| { ... }
-
- When the first argument is something other than a Hash, the block is called with each entry in turn.
- When the first argument is a Hash the entry is an array with `[key, value]`.
-
- *Examples*
+ :filter,
+ :arity => -3,
+ :doc => <<-DOC
+ Applies a parameterized block to each element in a sequence of entries from the first
+ argument and returns an array or hash (same type as left operand for array/hash, and array for
+ other enumerable types) with the entries for which the block evaluates to `true`.
- # selects all that end with berry
- $a = ["raspberry", "blueberry", "orange"]
- $a.filter |$x| { $x =~ /berry$/ } # rasberry, blueberry
+ This function takes two mandatory arguments: the first should be an Array, a Hash, or an
+ Enumerable object (integer, Integer range, or String),
+ and the second a parameterized block as produced by the puppet syntax:
- If the block defines two parameters, they will be set to `index, value` (with index starting at 0) for all
- enumerables except Hash, and to `key, value` for a Hash.
+ $a.filter |$x| { ... }
+ filter($a) |$x| { ... }
- *Examples*
+ When the first argument is something other than a Hash, the block is called with each entry in turn.
+ When the first argument is a Hash the entry is an array with `[key, value]`.
- # selects all that end with 'berry' at an even numbered index
- $a = ["raspberry", "blueberry", "orange"]
- $a.filter |$index, $x| { $index % 2 == 0 and $x =~ /berry$/ } # raspberry
+ Example Using filter with one parameter
- # selects all that end with 'berry' and value >= 1
- $a = {"raspberry"=>0, "blueberry"=>1, "orange"=>1}
- $a.filter |$key, $x| { $x =~ /berry$/ and $x >= 1 } # blueberry
+ # selects all that end with berry
+ $a = ["raspberry", "blueberry", "orange"]
+ $a.filter |$x| { $x =~ /berry$/ } # rasberry, blueberry
- - Since 3.4 for Array and Hash
- - Since 3.5 for other enumerables
- - requires `parser = future`
- ENDHEREDOC
+ If the block defines two parameters, they will be set to `index, value` (with index starting at 0) for all
+ enumerables except Hash, and to `key, value` for a Hash.
- def filter_Enumerator(enumerator, scope, pblock, serving_size)
- result = []
- index = 0
- if serving_size == 1
- begin
- loop { pblock.call(scope, it = enumerator.next) == true ? result << it : nil }
- rescue StopIteration
- end
- else
- begin
- loop do
- pblock.call(scope, index, it = enumerator.next) == true ? result << it : nil
- index = index +1
- end
- rescue StopIteration
- end
- end
- result
- end
+Example Using filter with two parameters
- receiver = args[0]
- pblock = args[1]
+ # selects all that end with 'berry' at an even numbered index
+ $a = ["raspberry", "blueberry", "orange"]
+ $a.filter |$index, $x| { $index % 2 == 0 and $x =~ /berry$/ } # raspberry
- raise ArgumentError, ("filter(): wrong argument type (#{pblock.class}; must be a parameterized block.") unless pblock.respond_to?(:puppet_lambda)
- serving_size = pblock.parameter_count
- if serving_size == 0
- raise ArgumentError, "filter(): block must define at least one parameter; value. Block has 0."
- end
+ # selects all that end with 'berry' and value >= 1
+ $a = {"raspberry"=>0, "blueberry"=>1, "orange"=>1}
+ $a.filter |$key, $x| { $x =~ /berry$/ and $x >= 1 } # blueberry
- case receiver
- when Hash
- if serving_size > 2
- raise ArgumentError, "filter(): block must define at most two parameters; key, value. Block has #{serving_size}; "+
- pblock.parameter_names.join(', ')
- end
- if serving_size == 1
- result = receiver.select {|x, y| pblock.call(self, [x, y]) }
- else
- result = receiver.select {|x, y| pblock.call(self, x, y) }
- end
- # Ruby 1.8.7 returns Array
- result = Hash[result] unless result.is_a? Hash
- result
- else
- if serving_size > 2
- raise ArgumentError, "filter(): block must define at most two parameters; index, value. Block has #{serving_size}; "+
- pblock.parameter_names.join(', ')
- end
- enum = Puppet::Pops::Types::Enumeration.enumerator(receiver)
- unless enum
- raise ArgumentError, ("filter(): wrong argument type (#{receiver.class}; must be something enumerable.")
- end
- filter_Enumerator(enum, self, pblock, serving_size)
- end
+- since 3.4 for Array and Hash
+- since 3.5 for other enumerables
+- note requires `parser = future`
+DOC
+) do |args|
+ function_fail(["filter() is only available when parser/evaluator future is in effect"])
end
diff --git a/lib/puppet/parser/functions/include.rb b/lib/puppet/parser/functions/include.rb
index 29ef45d40..2bebbba8b 100644
--- a/lib/puppet/parser/functions/include.rb
+++ b/lib/puppet/parser/functions/include.rb
@@ -17,31 +17,19 @@ and resource-like declarations with the same class.
The `include` function does not cause classes to be contained in the class
where they are declared. For that, see the `contain` function. It also
does not create a dependency relationship between the declared class and the
-surrounding class; for that, see the `require` function.") do |vals|
- if vals.is_a?(Array)
- # Protect against array inside array
- vals = vals.flatten
- else
- vals = [vals]
- end
+surrounding class; for that, see the `require` function.
- # The 'false' disables lazy evaluation.
- klasses = compiler.evaluate_classes(vals, self, false)
+When the future parser is used, you must use the class's full name;
+relative names are no longer allowed. In addition to names in string form,
+you may also directly use Class and Resource Type values that are produced by
+the future parser's resource and relationship expressions.
- missing = vals.find_all do |klass|
- ! klasses.include?(klass)
- end
+") do |vals|
- unless missing.empty?
- # Throw an error if we didn't evaluate all of the classes.
- str = "Could not find class"
- str += "es" if missing.length > 1
-
- str += " " + missing.join(", ")
-
- if n = namespaces and ! n.empty? and n != [""]
- str += " in namespaces #{@namespaces.join(", ")}"
- end
- self.fail Puppet::ParseError, str
- end
+ # Unify call patterns (if called with nested arrays), make names absolute if
+ # wanted and evaluate the classes
+ compiler.evaluate_classes(
+ transform_and_assert_classnames(
+ vals.is_a?(Array) ? vals.flatten : [vals]),
+ self, false)
end
diff --git a/lib/puppet/parser/functions/inline_epp.rb b/lib/puppet/parser/functions/inline_epp.rb
index cfc1597a1..1e8af8fe4 100644
--- a/lib/puppet/parser/functions/inline_epp.rb
+++ b/lib/puppet/parser/functions/inline_epp.rb
@@ -9,7 +9,7 @@ EPP support the following tags:
* `<%%` or `%%>` - Renders a literal `<%` or `%>` respectively.
* `<%-` - Same as `<%` but suppresses any leading whitespace.
* `-%>` - Same as `%>` but suppresses any trailing whitespace on the same line (including line break).
-* `<%-( parameters )-%>` - When placed as the first tag declares the template's parameters.
+* `<%- |parameters| -%>` - When placed as the first tag declares the template's parameters.
Inline EPP supports the following visibilities of variables in scope which depends on how EPP parameters
are used - see further below:
@@ -20,7 +20,7 @@ are used - see further below:
* Global + declared parameters - if the EPP declares parameters, given argument names must match
EPP supports parameters by placing an optional parameter list as the very first element in the EPP. As an example,
-`<%-( $x, $y, $z='unicorn' )-%>` when placed first in the EPP text declares that the parameters `x` and `y` must be
+`<%- |$x, $y, $z='unicorn'| -%>` when placed first in the EPP text declares that the parameters `x` and `y` must be
given as template arguments when calling `inline_epp`, and that `z` if not given as a template argument
defaults to `'unicorn'`. Template parameters are available as variables, e.g.arguments `$x`, `$y` and `$z` in the example.
Note that `<%-` must be used or any leading whitespace will be interpreted as text
@@ -37,43 +37,40 @@ is subject to expression interpolation before the string is parsed as an EPP tem
# produces 'Hello local variable world!'
$x ='local variable'
inline_epptemplate(@(END:epp))
- <%-( $x )-%>
+ <%- |$x| -%>
Hello <%= $x %> world!
END
# produces 'Hello given argument world!'
$x ='local variable world'
inline_epptemplate(@(END:epp), { x =>'given argument'})
- <%-( $x )-%>
+ <%- |$x| -%>
Hello <%= $x %> world!
END
# produces 'Hello given argument world!'
$x ='local variable world'
inline_epptemplate(@(END:epp), { x =>'given argument'})
- <%-( $x )-%>
+ <%- |$x| -%>
Hello <%= $x %>!
END
# results in error, missing value for y
$x ='local variable world'
inline_epptemplate(@(END:epp), { x =>'given argument'})
- <%-( $x, $y )-%>
+ <%- |$x, $y| -%>
Hello <%= $x %>!
END
# Produces 'Hello given argument planet'
$x ='local variable world'
inline_epptemplate(@(END:epp), { x =>'given argument'})
- <%-( $x, $y=planet)-%>
+ <%- |$x, $y=planet| -%>
Hello <%= $x %> <%= $y %>!
END
- Since 3.5
- Requires Future Parser") do |arguments|
- # Requires future parser
- unless Puppet[:parser] == "future"
- raise ArgumentError, "inline_epp(): function is only available when --parser future is in effect"
- end
- Puppet::Pops::Evaluator::EppEvaluator.inline_epp(self, arguments[0], arguments[1])
+
+ function_fail(["inline_epp() is only available when parser/evaluator future is in effect"])
end
diff --git a/lib/puppet/parser/functions/lookup.rb b/lib/puppet/parser/functions/lookup.rb
index 55d56f452..e36c4c378 100644
--- a/lib/puppet/parser/functions/lookup.rb
+++ b/lib/puppet/parser/functions/lookup.rb
@@ -138,7 +138,7 @@ If you want to make lookup return undef when no value was found instead of raisi
ENDHEREDOC
unless Puppet[:binder] || Puppet[:parser] == 'future'
- raise Puppet::ParseError, "The lookup function is only available with settings --binder true, or --parser future"
+ raise Puppet::ParseError, "The lookup function is only available with settings --binder true, or --parser future"
end
Puppet::Pops::Binder::Lookup.lookup(self, args)
end
diff --git a/lib/puppet/parser/functions/map.rb b/lib/puppet/parser/functions/map.rb
index 8bc9fd383..c2ca3aae6 100644
--- a/lib/puppet/parser/functions/map.rb
+++ b/lib/puppet/parser/functions/map.rb
@@ -1,96 +1,43 @@
-require 'puppet/parser/ast/lambda'
-
Puppet::Parser::Functions::newfunction(
-:map,
-:type => :rvalue,
-:arity => 2,
-:doc => <<-'ENDHEREDOC') do |args|
- Applies a parameterized block to each element in a sequence of entries from the first
- argument and returns an array with the result of each invocation of the parameterized block.
-
- This function takes two mandatory arguments: the first should be an Array, Hash, or of Enumerable type
- (integer, Integer range, or String), and the second a parameterized block as produced by the puppet syntax:
-
- $a.map |$x| { ... }
- map($a) |$x| { ... }
-
- When the first argument `$a` is an Array or of enumerable type, the block is called with each entry in turn.
- When the first argument is a hash the entry is an array with `[key, value]`.
-
- *Examples*
+ :map,
+ :type => :rvalue,
+ :arity => -3,
+ :doc => <<-DOC
+Applies a parameterized block to each element in a sequence of entries from the first
+argument and returns an array with the result of each invocation of the parameterized block.
- # Turns hash into array of values
- $a.map |$x|{ $x[1] }
+This function takes two mandatory arguments: the first should be an Array, Hash, or of Enumerable type
+(integer, Integer range, or String), and the second a parameterized block as produced by the puppet syntax:
- # Turns hash into array of keys
- $a.map |$x| { $x[0] }
+ $a.map |$x| { ... }
+ map($a) |$x| { ... }
- When using a block with 2 parameters, the element's index (starting from 0) for an array, and the key for a hash
- is given to the block's first parameter, and the value is given to the block's second parameter.args.
+When the first argument `$a` is an Array or of enumerable type, the block is called with each entry in turn.
+When the first argument is a hash the entry is an array with `[key, value]`.
- *Examples*
+Example Using map with two arguments
- # Turns hash into array of values
- $a.map |$key,$val|{ $val }
+ # Turns hash into array of values
+ $a.map |$x|{ $x[1] }
- # Turns hash into array of keys
- $a.map |$key,$val|{ $key }
+ # Turns hash into array of keys
+ $a.map |$x| { $x[0] }
- - Since 3.4 for Array and Hash
- - Since 3.5 for other enumerables, and support for blocks with 2 parameters
- - requires `parser = future`
- ENDHEREDOC
+When using a block with 2 parameters, the element's index (starting from 0) for an array, and the key for a hash
+is given to the block's first parameter, and the value is given to the block's second parameter.args.
- def map_Enumerator(enumerator, scope, pblock, serving_size)
- result = []
- index = 0
- if serving_size == 1
- begin
- loop { result << pblock.call(scope, enumerator.next) }
- rescue StopIteration
- end
- else
- begin
- loop do
- result << pblock.call(scope, index, enumerator.next)
- index = index +1
- end
- rescue StopIteration
- end
- end
- result
- end
+Example Using map with two arguments
- receiver = args[0]
- pblock = args[1]
+ # Turns hash into array of values
+ $a.map |$key,$val|{ $val }
- raise ArgumentError, ("map(): wrong argument type (#{pblock.class}; must be a parameterized block.") unless pblock.respond_to?(:puppet_lambda)
- serving_size = pblock.parameter_count
- if serving_size == 0
- raise ArgumentError, "map(): block must define at least one parameter; value. Block has 0."
- end
- case receiver
- when Hash
- if serving_size > 2
- raise ArgumentError, "map(): block must define at most two parameters; key, value.args Block has #{serving_size}; "+
- pblock.parameter_names.join(', ')
- end
- if serving_size == 1
- result = receiver.map {|x, y| pblock.call(self, [x, y]) }
- else
- result = receiver.map {|x, y| pblock.call(self, x, y) }
- end
- else
- if serving_size > 2
- raise ArgumentError, "map(): block must define at most two parameters; index, value. Block has #{serving_size}; "+
- pblock.parameter_names.join(', ')
- end
+ # Turns hash into array of keys
+ $a.map |$key,$val|{ $key }
- enum = Puppet::Pops::Types::Enumeration.enumerator(receiver)
- unless enum
- raise ArgumentError, ("map(): wrong argument type (#{receiver.class}; must be something enumerable.")
- end
- result = map_Enumerator(enum, self, pblock, serving_size)
- end
- result
+- since 3.4 for Array and Hash
+- since 3.5 for other enumerables, and support for blocks with 2 parameters
+- note requires `parser = future`
+DOC
+) do |args|
+ function_fail(["map() is only available when parser/evaluator future is in effect"])
end
diff --git a/lib/puppet/parser/functions/match.rb b/lib/puppet/parser/functions/match.rb
new file mode 100644
index 000000000..33bdf3e88
--- /dev/null
+++ b/lib/puppet/parser/functions/match.rb
@@ -0,0 +1,28 @@
+Puppet::Parser::Functions::newfunction(
+ :match,
+ :arity => 2,
+ :doc => <<-DOC
+Returns the match result of matching a String or Array[String] with one of:
+
+* Regexp
+* String - transformed to a Regexp
+* Pattern type
+* Regexp type
+
+Returns An Array with the entire match at index 0, and each subsequent submatch at index 1-n.
+If there was no match `undef` is returned. If the value to match is an Array, a array
+with mapped match results is returned.
+
+Example matching:
+
+ "abc123".match(/([a-z]+)[1-9]+/) # => ["abc"]
+ "abc123".match(/([a-z]+)([1-9]+)/) # => ["abc", "123"]
+
+See the documentation for "The Puppet Type System" for more information about types.
+
+- since 3.7.0
+- note requires future parser
+DOC
+) do |args|
+ function_fail(["match() is only available when parser/evaluator future is in effect"])
+end
diff --git a/lib/puppet/parser/functions/reduce.rb b/lib/puppet/parser/functions/reduce.rb
index 078ebc2e9..4fe90a239 100644
--- a/lib/puppet/parser/functions/reduce.rb
+++ b/lib/puppet/parser/functions/reduce.rb
@@ -1,100 +1,71 @@
Puppet::Parser::Functions::newfunction(
-:reduce,
-:type => :rvalue,
-:arity => -2,
-:doc => <<-'ENDHEREDOC') do |args|
- Applies a parameterized block to each element in a sequence of entries from the first
- argument (_the enumerable_) and returns the last result of the invocation of the parameterized block.
-
- This function takes two mandatory arguments: the first should be an Array, Hash, or something of
- enumerable type, and the last a parameterized block as produced by the puppet syntax:
-
- $a.reduce |$memo, $x| { ... }
- reduce($a) |$memo, $x| { ... }
-
- When the first argument is an Array or someting of an enumerable type, the block is called with each entry in turn.
- When the first argument is a hash each entry is converted to an array with `[key, value]` before being
- fed to the block. An optional 'start memo' value may be supplied as an argument between the array/hash
- and mandatory block.
-
- $a.reduce(start) |$memo, $x| { ... }
- reduce($a, start) |$memo, $x| { ... }
-
- If no 'start memo' is given, the first invocation of the parameterized block will be given the first and second
- elements of the enumeration, and if the enumerable has fewer than 2 elements, the first
- element is produced as the result of the reduction without invocation of the block.
-
- On each subsequent invocation, the produced value of the invoked parameterized block is given as the memo in the
- next invocation.
-
- *Examples*
-
- # Reduce an array
- $a = [1,2,3]
- $a.reduce |$memo, $entry| { $memo + $entry }
- #=> 6
-
- # Reduce hash values
- $a = {a => 1, b => 2, c => 3}
- $a.reduce |$memo, $entry| { [sum, $memo[1]+$entry[1]] }
- #=> [sum, 6]
-
- # reverse a string
- "abc".reduce |$memo, $char| { "$char$memo" }
- #=>"cbe"
-
- It is possible to provide a starting 'memo' as an argument.
-
- *Examples*
-
- # Reduce an array
- $a = [1,2,3]
- $a.reduce(4) |$memo, $entry| { $memo + $entry }
- #=> 10
-
- # Reduce hash values
- $a = {a => 1, b => 2, c => 3}
- $a.reduce([na, 4]) |$memo, $entry| { [sum, $memo[1]+$entry[1]] }
- #=> [sum, 10]
-
- *Examples*
-
- Integer[1,4].reduce |$memo, $x| { $memo + $x }
- #=> 10
-
- - Since 3.2 for Array and Hash
- - Since 3.5 for additional enumerable types
- - requires `parser = future`.
- ENDHEREDOC
-
- require 'puppet/parser/ast/lambda'
-
- case args.length
- when 2
- pblock = args[1]
- when 3
- pblock = args[2]
- else
- raise ArgumentError, ("reduce(): wrong number of arguments (#{args.length}; expected 2 or 3, got #{args.length})")
- end
- unless pblock.respond_to?(:puppet_lambda)
- raise ArgumentError, ("reduce(): wrong argument type (#{pblock.class}; must be a parameterized block.")
- end
- receiver = args[0]
- enum = Puppet::Pops::Types::Enumeration.enumerator(receiver)
- unless enum
- raise ArgumentError, ("reduce(): wrong argument type (#{receiver.class}; must be something enumerable.")
- end
-
- serving_size = pblock.parameter_count
- if serving_size != 2
- raise ArgumentError, "reduce(): block must define 2 parameters; memo, value. Block has #{serving_size}; "+
- pblock.parameter_names.join(', ')
- end
-
- if args.length == 3
- enum.reduce(args[1]) {|memo, x| pblock.call(self, memo, x) }
- else
- enum.reduce {|memo, x| pblock.call(self, memo, x) }
- end
+ :reduce,
+ :type => :rvalue,
+ :arity => -3,
+ :doc => <<-DOC
+Applies a parameterized block to each element in a sequence of entries from the first
+argument (_the enumerable_) and returns the last result of the invocation of the parameterized block.
+
+This function takes two mandatory arguments: the first should be an Array, Hash, or something of
+enumerable type, and the last a parameterized block as produced by the puppet syntax:
+
+ $a.reduce |$memo, $x| { ... }
+ reduce($a) |$memo, $x| { ... }
+
+When the first argument is an Array or someting of an enumerable type, the block is called with each entry in turn.
+When the first argument is a hash each entry is converted to an array with `[key, value]` before being
+fed to the block. An optional 'start memo' value may be supplied as an argument between the array/hash
+and mandatory block.
+
+ $a.reduce(start) |$memo, $x| { ... }
+ reduce($a, start) |$memo, $x| { ... }
+
+If no 'start memo' is given, the first invocation of the parameterized block will be given the first and second
+elements of the enumeration, and if the enumerable has fewer than 2 elements, the first
+element is produced as the result of the reduction without invocation of the block.
+
+On each subsequent invocation, the produced value of the invoked parameterized block is given as the memo in the
+next invocation.
+
+Example Using reduce
+
+ # Reduce an array
+ $a = [1,2,3]
+ $a.reduce |$memo, $entry| { $memo + $entry }
+ #=> 6
+
+ # Reduce hash values
+ $a = {a => 1, b => 2, c => 3}
+ $a.reduce |$memo, $entry| { [sum, $memo[1]+$entry[1]] }
+ #=> [sum, 6]
+
+ # reverse a string
+ "abc".reduce |$memo, $char| { "$char$memo" }
+ #=>"cbe"
+
+It is possible to provide a starting 'memo' as an argument.
+
+Example Using reduce with given start 'memo'
+
+ # Reduce an array
+ $a = [1,2,3]
+ $a.reduce(4) |$memo, $entry| { $memo + $entry }
+ #=> 10
+
+ # Reduce hash values
+ $a = {a => 1, b => 2, c => 3}
+ $a.reduce([na, 4]) |$memo, $entry| { [sum, $memo[1]+$entry[1]] }
+ #=> [sum, 10]
+
+Example Using reduce with an Integer range
+
+ Integer[1,4].reduce |$memo, $x| { $memo + $x }
+ #=> 10
+
+- since 3.2 for Array and Hash
+- since 3.5 for additional enumerable types
+- note requires `parser = future`.
+DOC
+) do |args|
+ function_fail(["reduce() is only available when parser/evaluator future is in effect"])
end
diff --git a/lib/puppet/parser/functions/require.rb b/lib/puppet/parser/functions/require.rb
index 819b82619..d6350fc4b 100644
--- a/lib/puppet/parser/functions/require.rb
+++ b/lib/puppet/parser/functions/require.rb
@@ -27,12 +27,20 @@ For instance the following manifest, with 'require' instead of 'include' would p
Note that this function only works with clients 0.25 and later, and it will
fail if used with earlier clients.
+When the future parser is used, you must use the class's full name;
+relative names are no longer allowed. In addition to names in string form,
+you may also directly use Class and Resource Type values that are produced by
+the future parser's resource and relationship expressions.
") do |vals|
- # Verify that the 'include' function is loaded
- method = Puppet::Parser::Functions.function(:include)
-
- send(method, vals)
- vals = [vals] unless vals.is_a?(Array)
+ # Make call patterns uniform and protected against nested arrays, also make
+ # names absolute if so desired.
+ vals = transform_and_assert_classnames(vals.is_a?(Array) ? vals.flatten : [vals])
+
+ # This is the same as calling the include function (but faster) since it again
+ # would otherwise need to perform the optional absolute name transformation
+ # (for no reason since they are already made absolute here).
+ #
+ compiler.evaluate_classes(vals, self, false)
vals.each do |klass|
# lookup the class in the scopes
diff --git a/lib/puppet/parser/functions/search.rb b/lib/puppet/parser/functions/search.rb
index 04c7579d7..8055e38b1 100644
--- a/lib/puppet/parser/functions/search.rb
+++ b/lib/puppet/parser/functions/search.rb
@@ -1,6 +1,11 @@
Puppet::Parser::Functions::newfunction(:search, :arity => -2, :doc => "Add another namespace for this class to search.
This allows you to create classes with sets of definitions and add
- those classes to another class's search path.") do |vals|
+ those classes to another class's search path.
+
+ Deprecated in Puppet 3.7.0, to be removed in Puppet 4.0.0.") do |vals|
+
+ Puppet.deprecation_warning("The 'search' function is deprecated. See http://links.puppetlabs.com/search-function-deprecation")
+
vals.each do |val|
add_namespace(val)
end
diff --git a/lib/puppet/parser/functions/select.rb b/lib/puppet/parser/functions/select.rb
deleted file mode 100644
index 93924f9d0..000000000
--- a/lib/puppet/parser/functions/select.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-Puppet::Parser::Functions::newfunction(
-:select,
-:type => :rvalue,
-:arity => 2,
-:doc => <<-'ENDHEREDOC') do |args|
- The 'select' function has been renamed to 'filter'. Please update your manifests.
-
- The select function is reserved for future use.
- - Removed as of 3.4
- - requires `parser = future`.
- ENDHEREDOC
-
- raise NotImplementedError,
- "The 'select' function has been renamed to 'filter'. Please update your manifests."
-end
diff --git a/lib/puppet/parser/functions/slice.rb b/lib/puppet/parser/functions/slice.rb
index bcc830f74..d57b99cff 100644
--- a/lib/puppet/parser/functions/slice.rb
+++ b/lib/puppet/parser/functions/slice.rb
@@ -1,116 +1,48 @@
Puppet::Parser::Functions::newfunction(
-:slice,
-:type => :rvalue,
-:arity => -2,
-:doc => <<-'ENDHEREDOC') do |args|
- Applies a parameterized block to each _slice_ of elements in a sequence of selected entries from the first
- argument and returns the first argument, or if no block is given returns a new array with a concatenation of
- the slices.
+ :slice,
+ :type => :rvalue,
+ :arity => -3,
+ :doc => <<-DOC
+Applies a parameterized block to each _slice_ of elements in a sequence of selected entries from the first
+argument and returns the first argument, or if no block is given returns a new array with a concatenation of
+the slices.
- This function takes two mandatory arguments: the first, `$a`, should be an Array, Hash, or something of
- enumerable type (integer, Integer range, or String), and the second, `$n`, the number of elements to include
- in each slice. The optional third argument should be a a parameterized block as produced by the puppet syntax:
+This function takes two mandatory arguments: the first, `$a`, should be an Array, Hash, or something of
+enumerable type (integer, Integer range, or String), and the second, `$n`, the number of elements to include
+in each slice. The optional third argument should be a a parameterized block as produced by the puppet syntax:
- $a.slice($n) |$x| { ... }
- slice($a) |$x| { ... }
+ $a.slice($n) |$x| { ... }
+ slice($a) |$x| { ... }
- The parameterized block should have either one parameter (receiving an array with the slice), or the same number
- of parameters as specified by the slice size (each parameter receiving its part of the slice).
- In case there are fewer remaining elements than the slice size for the last slice it will contain the remaining
- elements. When the block has multiple parameters, excess parameters are set to :undef for an array or
- enumerable type, and to empty arrays for a Hash.
+The parameterized block should have either one parameter (receiving an array with the slice), or the same number
+of parameters as specified by the slice size (each parameter receiving its part of the slice).
+In case there are fewer remaining elements than the slice size for the last slice it will contain the remaining
+elements. When the block has multiple parameters, excess parameters are set to undef for an array or
+enumerable type, and to empty arrays for a Hash.
- $a.slice(2) |$first, $second| { ... }
+ $a.slice(2) |$first, $second| { ... }
- When the first argument is a Hash, each `key,value` entry is counted as one, e.g, a slice size of 2 will produce
- an array of two arrays with key, and value.
+When the first argument is a Hash, each `key,value` entry is counted as one, e.g, a slice size of 2 will produce
+an array of two arrays with key, and value.
- $a.slice(2) |$entry| { notice "first ${$entry[0]}, second ${$entry[1]}" }
- $a.slice(2) |$first, $second| { notice "first ${first}, second ${second}" }
+Example Using slice with Hash
- When called without a block, the function produces a concatenated result of the slices.
+ $a.slice(2) |$entry| { notice "first ${$entry[0]}, second ${$entry[1]}" }
+ $a.slice(2) |$first, $second| { notice "first ${first}, second ${second}" }
- slice([1,2,3,4,5,6], 2) # produces [[1,2], [3,4], [5,6]]
- slice(Integer[1,6], 2) # produces [[1,2], [3,4], [5,6]]
- slice(4,2) # produces [[0,1], [2,3]]
- slice('hello',2) # produces [[h, e], [l, l], [o]]
+When called without a block, the function produces a concatenated result of the slices.
- - Since 3.2 for Array and Hash
- - Since 3.5 for additional enumerable types
- - requires `parser = future`.
- ENDHEREDOC
- require 'puppet/parser/ast/lambda'
- require 'puppet/parser/scope'
+Example Using slice without a block
- def each_Common(o, slice_size, filler, scope, pblock)
- serving_size = pblock ? pblock.parameter_count : 1
- if serving_size == 0
- raise ArgumentError, "slice(): block must define at least one parameter. Block has 0."
- end
- unless serving_size == 1 || serving_size == slice_size
- raise ArgumentError, "slice(): block must define one parameter, or " +
- "the same number of parameters as the given size of the slice (#{slice_size}). Block has #{serving_size}; "+
- pblock.parameter_names.join(', ')
- end
- enumerator = o.each_slice(slice_size)
- result = []
- if serving_size == 1
- begin
- if pblock
- loop do
- pblock.call(scope, enumerator.next)
- end
- else
- loop do
- result << enumerator.next
- end
- end
- rescue StopIteration
- end
- else
- begin
- loop do
- a = enumerator.next
- if a.size < serving_size
- a = a.dup.fill(filler, a.length...serving_size)
- end
- pblock.call(scope, *a)
- end
- rescue StopIteration
- end
- end
- if pblock
- o
- else
- result
- end
- end
+ slice([1,2,3,4,5,6], 2) # produces [[1,2], [3,4], [5,6]]
+ slice(Integer[1,6], 2) # produces [[1,2], [3,4], [5,6]]
+ slice(4,2) # produces [[0,1], [2,3]]
+ slice('hello',2) # produces [[h, e], [l, l], [o]]
- raise ArgumentError, ("slice(): wrong number of arguments (#{args.length}; must be 2 or 3)") unless args.length == 2 || args.length == 3
- if args.length >= 2
- begin
- slice_size = Puppet::Parser::Scope.number?(args[1])
- rescue
- raise ArgumentError, ("slice(): wrong argument type (#{args[1]}; must be number.")
- end
- end
- raise ArgumentError, ("slice(): wrong argument type (#{args[1]}; must be number.") unless slice_size
- raise ArgumentError, ("slice(): wrong argument value: #{slice_size}; is not a positive integer number > 0") unless slice_size.is_a?(Fixnum) && slice_size > 0
- receiver = args[0]
-
- # the block is optional, ok if nil, function then produces an array
- pblock = args[2]
- raise ArgumentError, ("slice(): wrong argument type (#{args[2].class}; must be a parameterized block.") unless pblock.respond_to?(:puppet_lambda) || args.length == 2
-
- case receiver
- when Hash
- each_Common(receiver, slice_size, [], self, pblock)
- else
- enum = Puppet::Pops::Types::Enumeration.enumerator(receiver)
- if enum.nil?
- raise ArgumentError, ("slice(): given type '#{tc.string(receiver)}' is not enumerable")
- end
- result = each_Common(enum, slice_size, :undef, self, pblock)
- pblock ? receiver : result
- end
+- since 3.2 for Array and Hash
+- since 3.5 for additional enumerable types
+- note requires `parser = future`.
+DOC
+) do |args|
+ function_fail(["slice() is only available when parser/evaluator future is in effect"])
end
diff --git a/lib/puppet/parser/functions/template.rb b/lib/puppet/parser/functions/template.rb
index 0bd5a5424..c789347ac 100644
--- a/lib/puppet/parser/functions/template.rb
+++ b/lib/puppet/parser/functions/template.rb
@@ -1,10 +1,17 @@
Puppet::Parser::Functions::newfunction(:template, :type => :rvalue, :arity => -2, :doc =>
- "Evaluate a template and return its value. See
- [the templating docs](http://docs.puppetlabs.com/guides/templating.html) for
- more information.
+ "Loads an ERB template from a module, evaluates it, and returns the resulting
+ value as a string.
- Note that if multiple templates are specified, their output is all
- concatenated and returned as the output of the function.") do |vals|
+ The argument to this function should be a `<MODULE NAME>/<TEMPLATE FILE>`
+ reference, which will load `<TEMPLATE FILE>` from a module's `templates`
+ directory. (For example, the reference `apache/vhost.conf.erb` will load the
+ file `<MODULES DIRECTORY>/apache/templates/vhost.conf.erb`.)
+
+ This function can also accept:
+
+ * An absolute path, which can load a template file from anywhere on disk.
+ * Multiple arguments, which will evaluate all of the specified templates and
+ return their outputs concatenated into a single string.") do |vals|
vals.collect do |file|
# Use a wrapper, so the template can't get access to the full
# Scope object.
diff --git a/lib/puppet/parser/functions/with.rb b/lib/puppet/parser/functions/with.rb
new file mode 100644
index 000000000..07beff6fd
--- /dev/null
+++ b/lib/puppet/parser/functions/with.rb
@@ -0,0 +1,21 @@
+Puppet::Parser::Functions::newfunction(
+ :with,
+ :type => :rvalue,
+ :arity => -1,
+ :doc => <<-DOC
+Call a lambda code block with the given arguments. Since the parameters of the lambda
+are local to the lambda's scope, this can be used to create private sections
+of logic in a class so that the variables are not visible outside of the
+class.
+
+Example:
+
+ # notices the array [1, 2, 'foo']
+ with(1, 2, 'foo') |$x, $y, $z| { notice [$x, $y, $z] }
+
+- since 3.7.0
+- note requires future parser
+DOC
+) do |args|
+ function_fail(["with() is only available when parser/evaluator future is in effect"])
+end
diff --git a/lib/puppet/parser/lexer.rb b/lib/puppet/parser/lexer.rb
index 986183241..de1249991 100644
--- a/lib/puppet/parser/lexer.rb
+++ b/lib/puppet/parser/lexer.rb
@@ -28,7 +28,7 @@ class Puppet::Parser::Lexer
# Make the lexer comply with newer API. It does not produce a pos...
nil
end
-
+
def lex_error msg
raise Puppet::LexError.new(msg)
end
diff --git a/lib/puppet/parser/parser_factory.rb b/lib/puppet/parser/parser_factory.rb
index 576bc8755..adc0e578a 100644
--- a/lib/puppet/parser/parser_factory.rb
+++ b/lib/puppet/parser/parser_factory.rb
@@ -9,13 +9,8 @@ module Puppet::Parser
class ParserFactory
# Produces a parser instance for the given environment
def self.parser(environment)
- case Puppet[:parser]
- when 'future'
- if Puppet[:evaluator] == 'future'
- evaluating_parser(environment)
- else
- eparser(environment)
- end
+ if Puppet[:parser] == 'future'
+ evaluating_parser(environment)
else
classic_parser(environment)
end
@@ -24,41 +19,35 @@ module Puppet::Parser
# Creates an instance of the classic parser.
#
def self.classic_parser(environment)
- require 'puppet/parser'
-
+ # avoid expensive require if already loaded
+ require 'puppet/parser' unless defined? Puppet::Parser::Parser
Puppet::Parser::Parser.new(environment)
end
- # Returns an instance of an EvaluatingParser
+ # Creates an instance of an E4ParserAdapter that adapts an
+ # EvaluatingParser to the 3x way of parsing.
+ #
def self.evaluating_parser(file_watcher)
# Since RGen is optional, test that it is installed
- @@asserted ||= false
- assert_rgen_installed() unless @@asserted
- @@asserted = true
- require 'puppet/parser/e4_parser_adapter'
- require 'puppet/pops/parser/code_merger'
+ assert_rgen_installed()
+ unless defined?(Puppet::Pops::Parser::E4ParserAdapter)
+ require 'puppet/parser/e4_parser_adapter'
+ require 'puppet/pops/parser/code_merger'
+ end
E4ParserAdapter.new(file_watcher)
end
- # Creates an instance of the expression based parser 'eparser'
+ # Asserts that RGen >= 0.6.6 is installed by checking that certain behavior is available.
+ # Note that this assert is expensive as it also requires puppet/pops (if not already loaded).
#
- def self.eparser(environment)
- # Since RGen is optional, test that it is installed
+ def self.assert_rgen_installed
@@asserted ||= false
- assert_rgen_installed() unless @@asserted
+ return if @@asserted
@@asserted = true
- require 'puppet/parser'
- require 'puppet/parser/e_parser_adapter'
- EParserAdapter.new(Puppet::Parser::Parser.new(environment))
- end
-
- private
-
- def self.assert_rgen_installed
begin
require 'rgen/metamodel_builder'
rescue LoadError
- raise Puppet::DevError.new("The gem 'rgen' version >= 0.6.1 is required when using the setting '--parser future'. Please install 'rgen'.")
+ raise Puppet::DevError.new("The gem 'rgen' version >= 0.7.0 is required when using the setting '--parser future'. Please install 'rgen'.")
end
# Since RGen is optional, there is nothing specifying its version.
# It is not installed in any controlled way, so not possible to use gems to check (it may be installed some other way).
@@ -70,19 +59,18 @@ module Puppet::Parser
container.left_expr = litstring
raise "no eContainer" if litstring.eContainer() != container
raise "no eContainingFeature" if litstring.eContainingFeature() != :left_expr
- rescue
- raise Puppet::DevError.new("The gem 'rgen' version >= 0.6.1 is required when using '--parser future'. An older version is installed, please update.")
+ rescue => e
+ # TODO: RGen can raise exceptions for other reasons!
+ raise Puppet::DevError.new("The gem 'rgen' version >= 0.7.0 is required when using '--parser future'. An older version is installed, please update.")
end
end
def self.code_merger
- if Puppet[:parser] == 'future' && Puppet[:evaluator] == 'future'
+ if Puppet[:parser] == 'future'
Puppet::Pops::Parser::CodeMerger.new
else
Puppet::Parser::CodeMerger.new
end
end
-
end
-
end
diff --git a/lib/puppet/parser/resource.rb b/lib/puppet/parser/resource.rb
index e17479ba6..cde664a33 100644
--- a/lib/puppet/parser/resource.rb
+++ b/lib/puppet/parser/resource.rb
@@ -180,8 +180,8 @@ class Puppet::Parser::Resource < Puppet::Resource
def to_hash
@parameters.inject({}) do |hash, ary|
param = ary[1]
- # Skip "undef" values.
- hash[param.name] = param.value if param.value != :undef
+ # Skip "undef" and nil values.
+ hash[param.name] = param.value if param.value != :undef && !param.value.nil?
hash
end
end
@@ -191,6 +191,17 @@ class Puppet::Parser::Resource < Puppet::Resource
copy_as_resource.to_ral
end
+ # Is the receiver tagged with the given tags?
+ # This match takes into account the tags that a resource will inherit from its container
+ # but have not been set yet.
+ # It does *not* take tags set via resource defaults as these will *never* be set on
+ # the resource itself since all resources always have tags that are automatically
+ # assigned.
+ #
+ def tagged?(*tags)
+ super || ((scope_resource = scope.resource) && scope_resource != self && scope_resource.tagged?(tags))
+ end
+
private
# Add default values from our definition.
@@ -227,7 +238,6 @@ class Puppet::Parser::Resource < Puppet::Resource
msg += " at #{fields.join(":")}"
end
msg += "; cannot redefine"
- Puppet.log_exception(ArgumentError.new(), msg)
raise Puppet::ParseError.new(msg, param.line, param.file)
end
diff --git a/lib/puppet/parser/scope.rb b/lib/puppet/parser/scope.rb
index bfdd67e79..c8cfa6060 100644
--- a/lib/puppet/parser/scope.rb
+++ b/lib/puppet/parser/scope.rb
@@ -30,6 +30,9 @@ class Puppet::Parser::Scope
attr_accessor :parent
attr_reader :namespaces
+ # Hash of hashes of default values per type name
+ attr_reader :defaults
+
# Add some alias methods that forward to the compiler, since we reference
# them frequently enough to justify the extra method call.
def_delegators :compiler, :catalog, :environment
@@ -108,7 +111,7 @@ class Puppet::Parser::Scope
def add_entries_to(target = {})
super
@symbols.each do |k, v|
- if v == :undef
+ if v == :undef || v.nil?
target.delete(k)
else
target[ k ] = v
@@ -318,9 +321,16 @@ class Puppet::Parser::Scope
end
# Collect all of the defaults set at any higher scopes.
- # This is a different type of lookup because it's additive --
- # it collects all of the defaults, with defaults in closer scopes
- # overriding those in later scopes.
+ # This is a different type of lookup because it's
+ # additive -- it collects all of the defaults, with defaults
+ # in closer scopes overriding those in later scopes.
+ #
+ # The lookupdefaults searches in the the order:
+ #
+ # * inherited
+ # * contained (recursive)
+ # * self
+ #
def lookupdefaults(type)
values = {}
@@ -391,7 +401,7 @@ class Puppet::Parser::Scope
def variable_not_found(name, reason=nil)
if Puppet[:strict_variables]
- if Puppet[:evaluator] == 'future' && Puppet[:parser] == 'future'
+ if Puppet[:parser] == 'future'
throw :undefined_variable
else
reason_msg = reason.nil? ? '' : "; #{reason}"
@@ -401,6 +411,7 @@ class Puppet::Parser::Scope
nil
end
end
+
# Retrieves the variable value assigned to the name given as an argument. The name must be a String,
# and namespace can be qualified with '::'. The value is looked up in this scope, its parent scopes,
# or in a specific visible named scope.
@@ -618,7 +629,7 @@ class Puppet::Parser::Scope
raise Puppet::ParseError, "Attempt to assign to a reserved variable name: '#{name}'"
end
- table = effective_symtable options[:ephemeral]
+ table = effective_symtable(options[:ephemeral])
if table.bound?(name)
if options[:append]
error = Puppet::ParseError.new("Cannot append, variable #{name} is defined in this scope")
@@ -631,11 +642,12 @@ class Puppet::Parser::Scope
end
if options[:append]
- table[name] = append_value(undef_as('', self[name]), value)
+ # produced result (value) is the resulting appended value, note: the table[]= does not return the value
+ table[name] = (value = append_value(undef_as('', self[name]), value))
else
table[name] = value
end
- table[name]
+ value
end
def set_trusted(hash)
@@ -643,6 +655,10 @@ class Puppet::Parser::Scope
end
def set_facts(hash)
+ # Remove _timestamp (it has an illegal datatype). It is not allowed to mutate the given hash
+ # since it contains the facts.
+ hash = hash.dup
+ hash.delete('_timestamp')
setvar('facts', deep_freeze(hash), :privileged => true)
end
@@ -675,15 +691,16 @@ class Puppet::Parser::Scope
# will be returned (irrespective of it being a match scope or a local scope).
#
# @param use_ephemeral [Boolean] whether the top most ephemeral (of any kind) should be used or not
- def effective_symtable use_ephemeral
+ def effective_symtable(use_ephemeral)
s = @ephemeral.last
- return s || @symtable if use_ephemeral
-
- # Why check if ephemeral is a Hash ??? Not needed, a hash cannot be a parent scope ???
- while s && !(s.is_a?(Hash) || s.is_local_scope?())
- s = s.parent
+ if use_ephemeral
+ return s || @symtable
+ else
+ while s && !s.is_local_scope?()
+ s = s.parent
+ end
+ s || @symtable
end
- s ? s : @symtable
end
# Sets the variable value of the name given as an argument to the given value. The value is
@@ -826,8 +843,61 @@ class Puppet::Parser::Scope
return [type, titles]
end
+ # Transforms references to classes to the form suitable for
+ # lookup in the compiler.
+ #
+ # Makes names passed in the names array absolute if they are relative
+ # Names are now made absolute if Puppet[:parser] == 'future', this will
+ # be the default behavior in Puppet 4.0
+ #
+ # Transforms Class[] and Resource[] type referenes to class name
+ # or raises an error if a Class[] is unspecific, if a Resource is not
+ # a 'class' resource, or if unspecific (no title).
+ #
+ # TODO: Change this for 4.0 to always make names absolute
+ #
+ # @param names [Array<String>] names to (optionally) make absolute
+ # @return [Array<String>] names after transformation
+ #
+ def transform_and_assert_classnames(names)
+ if Puppet[:parser] == 'future'
+ names.map do |name|
+ case name
+ when String
+ name.sub(/^([^:]{1,2})/, '::\1')
+
+ when Puppet::Resource
+ assert_class_and_title(name.type, name.title)
+ name.title.sub(/^([^:]{1,2})/, '::\1')
+
+ when Puppet::Pops::Types::PHostClassType
+ raise ArgumentError, "Cannot use an unspecific Class[] Type" unless name.class_name
+ name.class_name.sub(/^([^:]{1,2})/, '::\1')
+
+ when Puppet::Pops::Types::PResourceType
+ assert_class_and_title(name.type_name, name.title)
+ name.title.sub(/^([^:]{1,2})/, '::\1')
+ end
+ end
+ else
+ names
+ end
+ end
+
private
+ def assert_class_and_title(type_name, title)
+ if type_name.nil? || type_name == ''
+ raise ArgumentError, "Cannot use an unspecific Resource[] where a Resource['class', name] is expected"
+ end
+ unless type_name =~ /^[Cc]lass$/
+ raise ArgumentError, "Cannot use a Resource[#{type_name}] where a Resource['class', name] is expected"
+ end
+ if title.nil?
+ raise ArgumentError, "Cannot use an unspecific Resource['class'] where a Resource['class', name] is expected"
+ end
+ end
+
def extend_with_functions_module
root = Puppet.lookup(:root_environment)
extend Puppet::Parser::Functions.environment_module(root)
diff --git a/lib/puppet/pops.rb b/lib/puppet/pops.rb
index 3c06c5d71..744e58d24 100644
--- a/lib/puppet/pops.rb
+++ b/lib/puppet/pops.rb
@@ -29,14 +29,14 @@ module Puppet
require 'puppet/pops/model/model'
- module Types
- require 'puppet/pops/types/types'
- require 'puppet/pops/types/type_calculator'
- require 'puppet/pops/types/type_factory'
- require 'puppet/pops/types/type_parser'
- require 'puppet/pops/types/class_loader'
- require 'puppet/pops/types/enumeration'
- end
+ # (the Types module initializes itself)
+ require 'puppet/pops/types/types'
+ require 'puppet/pops/types/type_calculator'
+ require 'puppet/pops/types/type_factory'
+ require 'puppet/pops/types/type_parser'
+ require 'puppet/pops/types/class_loader'
+ require 'puppet/pops/types/enumeration'
+
module Model
require 'puppet/pops/model/tree_dumper'
@@ -84,15 +84,12 @@ module Puppet
require 'puppet/pops/parser/parser_support'
require 'puppet/pops/parser/locator'
require 'puppet/pops/parser/locatable'
- require 'puppet/pops/parser/lexer'
require 'puppet/pops/parser/lexer2'
require 'puppet/pops/parser/evaluating_parser'
require 'puppet/pops/parser/epp_parser'
end
module Validation
- require 'puppet/pops/validation/checker3_1'
- require 'puppet/pops/validation/validator_factory_3_1'
require 'puppet/pops/validation/checker4_0'
require 'puppet/pops/validation/validator_factory_4_0'
end
@@ -102,6 +99,7 @@ module Puppet
require 'puppet/pops/evaluator/runtime3_support'
require 'puppet/pops/evaluator/evaluator_impl'
require 'puppet/pops/evaluator/epp_evaluator'
+ require 'puppet/pops/evaluator/callable_mismatch_describer'
end
# Subsystem for puppet functions defined in ruby.
diff --git a/lib/puppet/pops/adapters.rb b/lib/puppet/pops/adapters.rb
index 294639172..05af1172f 100644
--- a/lib/puppet/pops/adapters.rb
+++ b/lib/puppet/pops/adapters.rb
@@ -69,7 +69,8 @@ module Puppet::Pops::Adapters
# @note This is an expensive operation
#
def line
- locator.line_for_offset(offset)
+ # Optimization: manual inlining of locator accessor since this method is frequently called
+ (@locator ||= find_locator(@adapted.eContainer)).line_for_offset(offset)
end
# Produces the position on the line of the given offset.
diff --git a/lib/puppet/pops/binder/bindings_checker.rb b/lib/puppet/pops/binder/bindings_checker.rb
index 6e436db72..11d97a6d2 100644
--- a/lib/puppet/pops/binder/bindings_checker.rb
+++ b/lib/puppet/pops/binder/bindings_checker.rb
@@ -13,7 +13,7 @@ class Puppet::Pops::Binder::BindingsChecker
def initialize(diagnostics_producer)
@@check_visitor ||= Puppet::Pops::Visitor.new(nil, "check", 0, 0)
@type_calculator = Puppet::Pops::Types::TypeCalculator.new()
- @expression_validator = Puppet::Pops::Validation::ValidatorFactory_3_1.new().checker(diagnostics_producer)
+ @expression_validator = Puppet::Pops::Validation::ValidatorFactory_4_0.new().checker(diagnostics_producer)
@acceptor = diagnostics_producer
end
@@ -31,14 +31,14 @@ class Puppet::Pops::Binder::BindingsChecker
# Performs binding validity check
# @api private
def check(b)
- @@check_visitor.visit_this(self, b)
+ @@check_visitor.visit_this_0(self, b)
end
# Checks that a binding has a producer and a type
# @api private
def check_Binding(b)
# Must have a type
- acceptor.accept(Issues::MISSING_TYPE, b) unless b.type.is_a?(Types::PObjectType)
+ acceptor.accept(Issues::MISSING_TYPE, b) unless b.type.is_a?(Types::PAnyType)
# Must have a producer
acceptor.accept(Issues::MISSING_PRODUCER, b) unless b.producer.is_a?(Bindings::ProducerDescriptor)
@@ -167,7 +167,7 @@ class Puppet::Pops::Binder::BindingsChecker
end
# @api private
- def check_PObjectType(t)
+ def check_PAnyType(t)
# Do nothing
end
diff --git a/lib/puppet/pops/binder/bindings_factory.rb b/lib/puppet/pops/binder/bindings_factory.rb
index ca01a7119..e3c4445a7 100644
--- a/lib/puppet/pops/binder/bindings_factory.rb
+++ b/lib/puppet/pops/binder/bindings_factory.rb
@@ -219,7 +219,7 @@ module Puppet::Pops::Binder::BindingsFactory
# @example creating a Hash with Integer key and Array[Integer] element type
# tc = type_factory
# type(tc.hash(tc.array_of(tc.integer), tc.integer)
- # @param type [Puppet::Pops::Types::PObjectType] the type to set for the binding
+ # @param type [Puppet::Pops::Types::PAnyType] the type to set for the binding
# @api public
#
def type(type)
@@ -284,7 +284,7 @@ module Puppet::Pops::Binder::BindingsFactory
end
# Sets the type of the binding to Array[T], where T is given.
- # @param t [Puppet::Pops::Types::PObjectType] the type of the elements of the array
+ # @param t [Puppet::Pops::Types::PAnyType] the type of the elements of the array
# @return [Puppet::Pops::Types::PArrayType] the type
# @api public
def array_of(t)
@@ -310,7 +310,7 @@ module Puppet::Pops::Binder::BindingsFactory
# Sets the type of the binding based on the given argument.
# @overload instance_of(t)
# The same as calling {#type} with `t`.
- # @param t [Puppet::Pops::Types::PObjectType] the type
+ # @param t [Puppet::Pops::Types::PAnyType] the type
# @overload instance_of(o)
# Infers the type from the given Ruby object and sets that as the type - i.e. "set the type
# of the binding to be that of the given data object".
@@ -318,7 +318,7 @@ module Puppet::Pops::Binder::BindingsFactory
# @overload instance_of(c)
# @param c [Class] the Class to base the type on.
# Sets the type based on the given ruby class. The result is one of the specific puppet types
- # if the class can be represented by a specific type, or the open ended PRubyType otherwise.
+ # if the class can be represented by a specific type, or the open ended PRuntimeType otherwise.
# @overload instance_of(s)
# The same as using a class, but instead of giving a class instance, the class is expressed using its fully
# qualified name. This method of specifying the type allows late binding (the class does not have to be loaded
@@ -695,7 +695,7 @@ module Puppet::Pops::Binder::BindingsFactory
end
# Creates a Producer that looks up a value.
- # @param type [Puppet::Pops::Types::PObjectType] the type to lookup
+ # @param type [Puppet::Pops::Types::PAnyType] the type to lookup
# @param name [String] the name to lookup
# @return [Puppet::Pops::Binder::Bindings::ProducerDescriptor] a producer description
# @api public
@@ -709,7 +709,7 @@ module Puppet::Pops::Binder::BindingsFactory
# Creates a Hash lookup producer that looks up a hash value, and then a key in the hash.
#
# @return [Puppet::Pops::Binder::Bindings::ProducerDescriptor] a producer description
- # @param type [Puppet::Pops::Types::PObjectType] the type to lookup (i.e. a Hash of some key/value type).
+ # @param type [Puppet::Pops::Types::PAnyType] the type to lookup (i.e. a Hash of some key/value type).
# @param name [String] the name to lookup
# @param key [Object] the key to lookup in the looked up hash (type should comply with given key type).
# @api public
diff --git a/lib/puppet/pops/binder/bindings_label_provider.rb b/lib/puppet/pops/binder/bindings_label_provider.rb
index ac10f7677..0d531c827 100644
--- a/lib/puppet/pops/binder/bindings_label_provider.rb
+++ b/lib/puppet/pops/binder/bindings_label_provider.rb
@@ -13,7 +13,7 @@ class Puppet::Pops::Binder::BindingsLabelProvider < Puppet::Pops::LabelProvider
@@label_visitor.visit(o)
end
- def label_PObjectType o ; "#{Puppet::Pops::Types::TypeFactory.label(o)}" end
+ def label_PAnyType o ; "#{Puppet::Pops::Types::TypeFactory.label(o)}" end
def label_ProducerDescriptor o ; "Producer" end
def label_NonCachingProducerDescriptor o ; "Non Caching Producer" end
def label_ConstantProducerDescriptor o ; "Producer['#{o.value}']" end
diff --git a/lib/puppet/pops/binder/bindings_loader.rb b/lib/puppet/pops/binder/bindings_loader.rb
index 84a4051b8..c57bb3cb6 100644
--- a/lib/puppet/pops/binder/bindings_loader.rb
+++ b/lib/puppet/pops/binder/bindings_loader.rb
@@ -10,8 +10,8 @@ class Puppet::Pops::Binder::BindingsLoader
# Returns a XXXXX given a fully qualified class name.
# Lookup of class is never relative to the calling namespace.
- # @param name [String, Array<String>, Array<Symbol>, Puppet::Pops::Types::PObjectType] A fully qualified
- # class name String (e.g. '::Foo::Bar', 'Foo::Bar'), a PObjectType, or a fully qualified name in Array form where each part
+ # @param name [String, Array<String>, Array<Symbol>, Puppet::Pops::Types::PAnyType] A fully qualified
+ # class name String (e.g. '::Foo::Bar', 'Foo::Bar'), a PAnyType, or a fully qualified name in Array form where each part
# is either a String or a Symbol, e.g. `%w{Puppetx Puppetlabs SomeExtension}`.
# @return [Class, nil] the looked up class or nil if no such class is loaded
# @raise ArgumentError If the given argument has the wrong type
diff --git a/lib/puppet/pops/binder/bindings_model.rb b/lib/puppet/pops/binder/bindings_model.rb
index 4d041fca1..9e935ae65 100644
--- a/lib/puppet/pops/binder/bindings_model.rb
+++ b/lib/puppet/pops/binder/bindings_model.rb
@@ -1,201 +1,68 @@
require 'rgen/metamodel_builder'
# The Bindings model is a model of Key to Producer mappings (bindings).
-# The central concept is that a Bindings is a nested structure of bindings.
-# A top level Bindings should be a NamedBindings (the name is used primarily
-# in error messages). A Key is a Type/Name combination.
-#
-# TODO: In this version, references to "any object" uses the class Object,
-# but this is only temporary. The intent is to use specific Puppet Objects
-# that are typed using the Puppet Type System (to enable serialization).
+# It is composed of a meta-model part (bindings_model_meta.rb), and
+# and implementation part (this file).
#
# @see Puppet::Pops::Binder::BindingsFactory The BindingsFactory for more details on how to create model instances.
# @api public
-module Puppet::Pops::Binder::Bindings
-
- # @abstract
- # @api public
- #
- class AbstractBinding < Puppet::Pops::Model::PopsObject
- abstract
- end
-
- # An abstract producer
- # @abstract
- # @api public
- #
- class ProducerDescriptor < Puppet::Pops::Model::PopsObject
- abstract
- contains_one_uni 'transformer', Puppet::Pops::Model::LambdaExpression
- end
-
- # All producers are singleton producers unless wrapped in a non caching producer
- # where each lookup produces a new instance. It is an error to have a nesting level > 1
- # and to nest a NonCachingProducerDescriptor.
- #
- # @api public
- #
- class NonCachingProducerDescriptor < ProducerDescriptor
- contains_one_uni 'producer', ProducerDescriptor
- end
-
- # Produces a constant value (i.e. something of {Puppet::Pops::Types::PDataType PDataType})
- # @api public
- #
- class ConstantProducerDescriptor < ProducerDescriptor
- # TODO: This should be a typed Puppet Object
- has_attr 'value', Object
- end
-
- # Produces a value by evaluating a Puppet DSL expression.
- # Note that the expression is not contained as it is part of a Puppet::Pops::Model::Program.
- # To include the expression in the serialization, the Program it is contained in must be
- # contained in the same serialization. This can be achieved by containing it in the
- # ContributedBindings that is the top of a BindingsModel produced and given to the Injector.
- #
- # @api public
- #
- class EvaluatingProducerDescriptor < ProducerDescriptor
- has_one 'expression', Puppet::Pops::Model::Expression
- end
-
- # An InstanceProducer creates an instance of the given class
- # Arguments are passed to the class' `new` operator in the order they are given.
- # @api public
- #
- class InstanceProducerDescriptor < ProducerDescriptor
- # TODO: This should be a typed Puppet Object ??
- has_many_attr 'arguments', Object, :upperBound => -1
- has_attr 'class_name', String
- end
-
- # A ProducerProducerDescriptor, describes that the produced instance is itself a Producer
- # that should be used to produce the value.
- # @api public
- #
- class ProducerProducerDescriptor < ProducerDescriptor
- contains_one_uni 'producer', ProducerDescriptor, :lowerBound => 1
- end
-
- # Produces a value by looking up another key (type/name)
- # @api public
- #
- class LookupProducerDescriptor < ProducerDescriptor
- contains_one_uni 'type', Puppet::Pops::Types::PObjectType
- has_attr 'name', String
- end
-
- # Produces a value by looking up another multibound key, and then looking up
- # the detail using a detail_key.
- # This is used to produce a specific service of a given type (such as a SyntaxChecker for the syntax "json").
- # @api public
- #
- class HashLookupProducerDescriptor < LookupProducerDescriptor
- has_attr 'key', String
- end
-
- # Produces a value by looking up each producer in turn. The first existing producer wins.
- # @api public
- #
- class FirstFoundProducerDescriptor < ProducerDescriptor
- contains_many_uni 'producers', LookupProducerDescriptor
- end
+module Puppet::Pops::Binder
+ require 'puppet/pops/binder/bindings_model_meta'
+
+ # TODO: See PUP-2978 for possible performance optimization
+
+ # Mix in implementation into the generated code
+ module Bindings
+ class BindingsModelObject
+ include Puppet::Pops::Visitable
+ include Puppet::Pops::Adaptable
+ include Puppet::Pops::Containment
+ end
+
+ class ConstantProducerDescriptor
+ module ClassModule
+ def setValue(v)
+ @value = v
+ end
+ def getValue()
+ @value
+ end
+ def value=(v)
+ @value = v
+ end
+ end
+ end
+
+ class NamedArgument
+ module ClassModule
+ def setValue(v)
+ @value = v
+ end
+ def getValue()
+ @value
+ end
+ def value=(v)
+ @value = v
+ end
+ end
+ end
+
+ class InstanceProducerDescriptor
+ module ClassModule
+ def addArguments(val, index =-1)
+ @arguments ||= []
+ @arguments.insert(index, val)
+ end
+ def removeArguments(val)
+ raise "unsupported operation"
+ end
+ def setArguments(values)
+ @arguments = []
+ values.each {|v| addArguments(v) }
+ end
+ end
+ end
- # @api public
- # @abstract
- class MultibindProducerDescriptor < ProducerDescriptor
- abstract
end
- # Used in a Multibind of Array type unless it has a producer. May explicitly be used as well.
- # @api public
- #
- class ArrayMultibindProducerDescriptor < MultibindProducerDescriptor
- end
-
- # Used in a Multibind of Hash type unless it has a producer. May explicitly be used as well.
- # @api public
- #
- class HashMultibindProducerDescriptor < MultibindProducerDescriptor
- end
-
- # Plays the role of "Hash[String, Object] entry" but with keys in defined order.
- #
- # @api public
- #
- class NamedArgument < Puppet::Pops::Model::PopsObject
- has_attr 'name', String, :lowerBound => 1
- has_attr 'value', Object, :lowerBound => 1
- end
-
- # Binds a type/name combination to a producer. Optionally marking the bindidng as being abstract, or being an
- # override of another binding. Optionally, the binding defines producer arguments passed to the producer when
- # it is created.
- #
- # @api public
- class Binding < AbstractBinding
- contains_one_uni 'type', Puppet::Pops::Types::PObjectType
- has_attr 'name', String
- has_attr 'override', Boolean
- has_attr 'abstract', Boolean
- has_attr 'final', Boolean
- # If set is a contribution in a multibind
- has_attr 'multibind_id', String, :lowerBound => 0
- # Invariant: Only multibinds may have lowerBound 0, all regular Binding must have a producer.
- contains_one_uni 'producer', ProducerDescriptor, :lowerBound => 0
- contains_many_uni 'producer_args', NamedArgument, :lowerBound => 0
- end
-
-
- # A multibinding is a binding other bindings can contribute to.
- #
- # @api public
- class Multibinding < Binding
- has_attr 'id', String
- end
-
- # A container of Binding instances
- # @api public
- #
- class Bindings < AbstractBinding
- contains_many_uni 'bindings', AbstractBinding
- end
-
- # The top level container of bindings can have a name (for error messages, logging, tracing).
- # May be nested.
- # @api public
- #
- class NamedBindings < Bindings
- has_attr 'name', String
- end
-
- # A named layer of bindings having the same priority.
- # @api public
- class NamedLayer < Puppet::Pops::Model::PopsObject
- has_attr 'name', String, :lowerBound => 1
- contains_many_uni 'bindings', NamedBindings
- end
-
- # A list of layers with bindings in descending priority order.
- # @api public
- #
- class LayeredBindings < Puppet::Pops::Model::PopsObject
- contains_many_uni 'layers', NamedLayer
- end
-
- # ContributedBindings is a named container of one or more NamedBindings.
- # The intent is that a bindings producer returns a ContributedBindings that identifies the contributor
- # as opposed to the names of the different set of bindings. The ContributorBindings name is typically
- # a technical name that indicates their source (a service).
- #
- # When EvaluatingProducerDescriptor is used, it holds a reference to an Expression. That expression
- # should be contained in the programs referenced in the ContributedBindings that contains that producer.
- # While the bindings model will still work if this is not the case, it will not serialize and deserialize
- # correctly.
- #
- # @api public
- #
- class ContributedBindings < NamedLayer
- contains_many_uni 'programs', Puppet::Pops::Model::Program
- end
end
diff --git a/lib/puppet/pops/binder/bindings_model_dumper.rb b/lib/puppet/pops/binder/bindings_model_dumper.rb
index 1abfd0675..98b2049fd 100644
--- a/lib/puppet/pops/binder/bindings_model_dumper.rb
+++ b/lib/puppet/pops/binder/bindings_model_dumper.rb
@@ -109,7 +109,7 @@ class Puppet::Pops::Binder::BindingsModelDumper < Puppet::Pops::Model::TreeDumpe
['lookup', do_dump(o.type), o.name]
end
- def dump_PObjectType o
+ def dump_PAnyType o
type_calculator.string(o)
end
diff --git a/lib/puppet/pops/binder/bindings_model_meta.rb b/lib/puppet/pops/binder/bindings_model_meta.rb
new file mode 100644
index 000000000..a4b874997
--- /dev/null
+++ b/lib/puppet/pops/binder/bindings_model_meta.rb
@@ -0,0 +1,215 @@
+require 'rgen/metamodel_builder'
+
+# The Bindings model is a model of Key to Producer mappings (bindings).
+# The central concept is that a Bindings is a nested structure of bindings.
+# A top level Bindings should be a NamedBindings (the name is used primarily
+# in error messages). A Key is a Type/Name combination.
+#
+# TODO: In this version, references to "any object" uses the class Object,
+# but this is only temporary. The intent is to use specific Puppet Objects
+# that are typed using the Puppet Type System (to enable serialization).
+#
+# @see Puppet::Pops::Binder::BindingsFactory The BindingsFactory for more details on how to create model instances.
+# @api public
+module Puppet::Pops::Binder::Bindings
+ extend RGen::MetamodelBuilder::ModuleExtension
+
+ # This declaration is used to overcome bugs in RGen. What is really wanted is an Opaque Object
+ # type that does not serialize its values, but such an type does not work when recreating the
+ # meta model from a dump.
+ # Instead, after loading the model, the generated code for type validation must be patched
+ #
+ FakeObject = String
+
+ # @abstract
+ # @api public
+ class BindingsModelObject < RGen::MetamodelBuilder::MMBase
+ abstract
+ end
+
+ # @abstract
+ # @api public
+ #
+ class AbstractBinding < BindingsModelObject
+ abstract
+ end
+
+ # An abstract producer
+ # @abstract
+ # @api public
+ #
+ class ProducerDescriptor < BindingsModelObject
+ abstract
+ contains_one_uni 'transformer', Puppet::Pops::Model::LambdaExpression
+ end
+ # All producers are singleton producers unless wrapped in a non caching producer
+ # where each lookup produces a new instance. It is an error to have a nesting level > 1
+ # and to nest a NonCachingProducerDescriptor.
+ #
+ # @api public
+ #
+ class NonCachingProducerDescriptor < ProducerDescriptor
+ contains_one_uni 'producer', ProducerDescriptor
+ end
+
+ # Produces a constant value (i.e. something of {Puppet::Pops::Types::PDataType PDataType})
+ # @api public
+ #
+ class ConstantProducerDescriptor < ProducerDescriptor
+ # TODO: This should be a typed Puppet Object
+ has_attr 'value', FakeObject
+ end
+
+ # Produces a value by evaluating a Puppet DSL expression.
+ # Note that the expression is not contained as it is part of a Puppet::Pops::Model::Program.
+ # To include the expression in the serialization, the Program it is contained in must be
+ # contained in the same serialization. This can be achieved by containing it in the
+ # ContributedBindings that is the top of a BindingsModel produced and given to the Injector.
+ #
+ # @api public
+ #
+ class EvaluatingProducerDescriptor < ProducerDescriptor
+ has_one 'expression', Puppet::Pops::Model::Expression
+ end
+
+ # An InstanceProducer creates an instance of the given class
+ # Arguments are passed to the class' `new` operator in the order they are given.
+ # @api public
+ #
+ class InstanceProducerDescriptor < ProducerDescriptor
+ # TODO: This should be a typed Puppet Object ??
+ has_many_attr 'arguments', FakeObject, :upperBound => -1
+ has_attr 'class_name', String
+ end
+
+ # A ProducerProducerDescriptor, describes that the produced instance is itself a Producer
+ # that should be used to produce the value.
+ # @api public
+ #
+ class ProducerProducerDescriptor < ProducerDescriptor
+ contains_one_uni 'producer', ProducerDescriptor, :lowerBound => 1
+ end
+
+ # Produces a value by looking up another key (type/name)
+ # @api public
+ #
+ class LookupProducerDescriptor < ProducerDescriptor
+ contains_one_uni 'type', Puppet::Pops::Types::PAnyType
+ has_attr 'name', String
+ end
+
+ # Produces a value by looking up another multibound key, and then looking up
+ # the detail using a detail_key.
+ # This is used to produce a specific service of a given type (such as a SyntaxChecker for the syntax "json").
+ # @api public
+ #
+ class HashLookupProducerDescriptor < LookupProducerDescriptor
+ has_attr 'key', String
+ end
+
+ # Produces a value by looking up each producer in turn. The first existing producer wins.
+ # @api public
+ #
+ class FirstFoundProducerDescriptor < ProducerDescriptor
+ contains_many_uni 'producers', LookupProducerDescriptor
+ end
+
+ # @api public
+ # @abstract
+ class MultibindProducerDescriptor < ProducerDescriptor
+ abstract
+ end
+
+ # Used in a Multibind of Array type unless it has a producer. May explicitly be used as well.
+ # @api public
+ #
+ class ArrayMultibindProducerDescriptor < MultibindProducerDescriptor
+ end
+
+ # Used in a Multibind of Hash type unless it has a producer. May explicitly be used as well.
+ # @api public
+ #
+ class HashMultibindProducerDescriptor < MultibindProducerDescriptor
+ end
+
+ # Plays the role of "Hash[String, Object] entry" but with keys in defined order.
+ #
+ # @api public
+ #
+ class NamedArgument < BindingsModelObject
+ has_attr 'name', String, :lowerBound => 1
+ has_attr 'value', FakeObject
+ end
+
+ # Binds a type/name combination to a producer. Optionally marking the bindidng as being abstract, or being an
+ # override of another binding. Optionally, the binding defines producer arguments passed to the producer when
+ # it is created.
+ #
+ # @api public
+ class Binding < AbstractBinding
+ contains_one_uni 'type', Puppet::Pops::Types::PAnyType
+ has_attr 'name', String
+ has_attr 'override', Boolean
+ has_attr 'abstract', Boolean
+ has_attr 'final', Boolean
+ # If set is a contribution in a multibind
+ has_attr 'multibind_id', String, :lowerBound => 0
+ # Invariant: Only multibinds may have lowerBound 0, all regular Binding must have a producer.
+ contains_one_uni 'producer', ProducerDescriptor, :lowerBound => 0
+ contains_many_uni 'producer_args', NamedArgument, :lowerBound => 0
+ end
+
+
+ # A multibinding is a binding other bindings can contribute to.
+ #
+ # @api public
+ class Multibinding < Binding
+ has_attr 'id', String
+ end
+
+ # A container of Binding instances
+ # @api public
+ #
+ class Bindings < AbstractBinding
+ contains_many_uni 'bindings', AbstractBinding
+ end
+
+ # The top level container of bindings can have a name (for error messages, logging, tracing).
+ # May be nested.
+ # @api public
+ #
+ class NamedBindings < Bindings
+ has_attr 'name', String
+ end
+
+ # A named layer of bindings having the same priority.
+ # @api public
+ class NamedLayer < BindingsModelObject
+ has_attr 'name', String, :lowerBound => 1
+ contains_many_uni 'bindings', NamedBindings
+ end
+
+ # A list of layers with bindings in descending priority order.
+ # @api public
+ #
+ class LayeredBindings < BindingsModelObject
+ contains_many_uni 'layers', NamedLayer
+ end
+
+ # ContributedBindings is a named container of one or more NamedBindings.
+ # The intent is that a bindings producer returns a ContributedBindings that identifies the contributor
+ # as opposed to the names of the different set of bindings. The ContributorBindings name is typically
+ # a technical name that indicates their source (a service).
+ #
+ # When EvaluatingProducerDescriptor is used, it holds a reference to an Expression. That expression
+ # should be contained in the programs referenced in the ContributedBindings that contains that producer.
+ # While the bindings model will still work if this is not the case, it will not serialize and deserialize
+ # correctly.
+ #
+ # @api public
+ #
+ class ContributedBindings < NamedLayer
+ contains_many_uni 'programs', Puppet::Pops::Model::Program
+ end
+
+end
diff --git a/lib/puppet/pops/binder/injector.rb b/lib/puppet/pops/binder/injector.rb
index 994394975..fb11c3cc1 100644
--- a/lib/puppet/pops/binder/injector.rb
+++ b/lib/puppet/pops/binder/injector.rb
@@ -205,7 +205,7 @@ class Puppet::Pops::Binder::Injector
# @overload lookup(scope, type, name = '')
# (see #lookup_type)
# @param scope [Puppet::Parser::Scope] the scope to use for evaluation
- # @param type [Puppet::Pops::Types::PObjectType] the type of what to lookup
+ # @param type [Puppet::Pops::Types::PAnyType] the type of what to lookup
# @param name [String] the name to use, defaults to empty string (for unnamed)
#
# @overload lookup(scope, name)
@@ -231,7 +231,7 @@ class Puppet::Pops::Binder::Injector
# Creates a key for the type/name combination using a KeyFactory. Specialization of the Data type are transformed
# to a Data key, and the result is type checked to conform with the given key.
#
- # @param type [Puppet::Pops::Types::PObjectType] the type to lookup as defined by Puppet::Pops::Types::TypeFactory
+ # @param type [Puppet::Pops::Types::PAnyType] the type to lookup as defined by Puppet::Pops::Types::TypeFactory
# @param name [String] the (optional for non `Data` types) name of the entry to lookup.
# The name may be an empty String (the default), but not nil. The name is required for lookup for subtypes of
# `Data`.
@@ -275,7 +275,7 @@ class Puppet::Pops::Binder::Injector
# @overload lookup_producer(scope, type, name = '')
# (see #lookup_type)
# @param scope [Puppet::Parser::Scope] the scope to use for evaluation
- # @param type [Puppet::Pops::Types::PObjectType], the type of what to lookup
+ # @param type [Puppet::Pops::Types::PAnyType], the type of what to lookup
# @param name [String], the name to use, defaults to empty string (for unnamed)
#
# @overload lookup_producer(scope, name)
@@ -363,9 +363,9 @@ module Private
if block
case block.arity
when 1
- block.call(:undef)
+ block.call(nil)
when 2
- block.call(scope, :undef)
+ block.call(scope, nil)
else
raise ArgumentError, "The block should have arity 1 or 2"
end
@@ -441,7 +441,7 @@ module Private
val = case args[ 0 ]
- when Puppet::Pops::Types::PObjectType
+ when Puppet::Pops::Types::PAnyType
lookup_type(scope, *args)
when String
@@ -562,7 +562,7 @@ module Private
#
def assistable_injected_class(key)
kt = key_factory.get_type(key)
- return nil unless kt.is_a?(Puppet::Pops::Types::PRubyType) && !key_factory.is_named?(key)
+ return nil unless kt.is_a?(Puppet::Pops::Types::PRuntimeType) && kt.runtime == :ruby && !key_factory.is_named?(key)
type_calculator.injectable_class(kt)
end
@@ -570,7 +570,7 @@ module Private
raise ArgumentError, "lookup_producer should be called with two or three arguments, got: #{args.size()+1}" unless args.size <= 2
p = case args[ 0 ]
- when Puppet::Pops::Types::PObjectType
+ when Puppet::Pops::Types::PAnyType
lookup_producer_type(scope, *args)
when String
@@ -634,7 +634,7 @@ module Private
# @api private
def transform(producer_descriptor, scope, entry)
- @@transform_visitor.visit_this(self, producer_descriptor, scope, entry)
+ @@transform_visitor.visit_this_2(self, producer_descriptor, scope, entry)
end
# Returns the produced instance
diff --git a/lib/puppet/pops/binder/key_factory.rb b/lib/puppet/pops/binder/key_factory.rb
index 0b45d4f02..3dccd5184 100644
--- a/lib/puppet/pops/binder/key_factory.rb
+++ b/lib/puppet/pops/binder/key_factory.rb
@@ -48,13 +48,13 @@ class Puppet::Pops::Binder::KeyFactory
# @api public
def is_data?(key)
- return false unless key.is_a?(Array) && key[0].is_a?(Puppet::Pops::Types::PObjectType)
+ return false unless key.is_a?(Array) && key[0].is_a?(Puppet::Pops::Types::PAnyType)
type_calculator.assignable?(type_calculator.data(), key[0])
end
# @api public
def is_ruby?(key)
- return key.is_a?(Array) && key[0].is_a?(Puppet::Pops::Types::PRubyType)
+ key.is_a?(Array) && key[0].is_a?(Puppet::Pops::Types::PRuntimeType) && key[0].runtime == :ruby
end
# Returns the type of the key
diff --git a/lib/puppet/pops/binder/lookup.rb b/lib/puppet/pops/binder/lookup.rb
index d44d5269c..07980ce62 100644
--- a/lib/puppet/pops/binder/lookup.rb
+++ b/lib/puppet/pops/binder/lookup.rb
@@ -78,7 +78,7 @@ class Puppet::Pops::Binder::Lookup
end
# unless a type is already given (future case), parse the type (or default 'Data'), fails if invalid type is given
- unless options[:type].is_a?(Puppet::Pops::Types::PAbstractType)
+ unless options[:type].is_a?(Puppet::Pops::Types::PAnyType)
options[:type] = type_parser.parse(options[:type] || 'Data')
end
@@ -159,17 +159,21 @@ class Puppet::Pops::Binder::Lookup
result_with_name[1]
end
+ # If a block is given it is called with :undef passed as 'nil' since the lookup function
+ # is available from 3x with --binder turned on, and the evaluation is always 4x.
+ # TODO PUPPET4: Simply pass the value
+ #
result = if pblock = options[:pblock]
result2 = case pblock.parameter_count
when 1
- pblock.call(scope, nil_as_undef(result))
+ pblock.call(scope, undef_as_nil(result))
when 2
- pblock.call(scope, result_with_name[ 0 ], nil_as_undef(result))
+ pblock.call(scope, result_with_name[ 0 ], undef_as_nil(result))
else
- pblock.call(scope, result_with_name[ 0 ], nil_as_undef(result), nil_as_undef(options[ :default ]))
+ pblock.call(scope, result_with_name[ 0 ], undef_as_nil(result), undef_as_nil(options[ :default ]))
end
- # if the given result was returned, there is not need to type-check it again
+ # if the given result was returned, there is no need to type-check it again
if !result2.equal?(result)
t = type_calculator.infer(undef_as_nil(result2))
if !type_calculator.assignable?(type, t)
@@ -185,7 +189,11 @@ class Puppet::Pops::Binder::Lookup
if is_nil_or_undef?(result) && !options[:accept_undef]
fail_lookup(names)
else
- nil_as_undef(result)
+ # Since the function may be used without future parser being in effect, nil is not handled in a good
+ # way, and should instead be turned into :undef.
+ # TODO PUPPET4: Simply return the result
+ #
+ Puppet[:parser] == 'future' ? result : nil_as_undef(result)
end
end
end
diff --git a/lib/puppet/pops/binder/producers.rb b/lib/puppet/pops/binder/producers.rb
index 98016f144..8302ebf4c 100644
--- a/lib/puppet/pops/binder/producers.rb
+++ b/lib/puppet/pops/binder/producers.rb
@@ -62,8 +62,7 @@ module Puppet::Pops::Binder::Producers
else
raise ArgumentError, "Transformer must be a LambdaExpression" unless transformer_lambda.is_a?(Puppet::Pops::Model::LambdaExpression)
raise ArgumentError, "Transformer lambda must take one argument; value." unless transformer_lambda.parameters.size() == 1
- # NOTE: This depends on Puppet 3 AST Lambda
- @transformer = Puppet::Pops::Model::AstTransformer.new().transform(transformer_lambda)
+ @transformer = Puppet::Pops::Parser::EvaluatingParser.new.closure(transformer_lambda, scope)
end
end
end
@@ -111,7 +110,6 @@ module Puppet::Pops::Binder::Producers
#
def do_transformation(scope, produced_value)
return produced_value unless transformer
- produced_value = :undef if produced_value.nil?
transformer.call(scope, produced_value)
end
end
@@ -299,14 +297,13 @@ module Puppet::Pops::Binder::Producers
#
def initialize(injector, binding, scope, options)
super
- expr = options[:expression]
- raise ArgumentError, "Option 'expression' must be given to an EvaluatingProducer." unless expr
- @expression = Puppet::Pops::Model::AstTransformer.new().transform(expr)
+ @expression = options[:expression]
+ raise ArgumentError, "Option 'expression' must be given to an EvaluatingProducer." unless @expression
end
# @api private
def internal_produce(scope)
- expression.evaluate(scope)
+ Puppet::Pops::Parser::EvaluatingParser.new.evaluate(scope, expression)
end
end
@@ -323,7 +320,7 @@ module Puppet::Pops::Binder::Producers
# @param binder [Puppet::Pops::Binder::Bindings::Binding, nil] The binding using this producer
# @param scope [Puppet::Parser::Scope] The scope to use for evaluation
# @option options [Puppet::Pops::Model::LambdaExpression] :transformer (nil) a transformer of produced value
- # @option options [Puppet::Pops::Types::PObjectType] :type The type to lookup
+ # @option options [Puppet::Pops::Types::PAnyType] :type The type to lookup
# @option options [String] :name ('') The name to lookup
# @api public
#
@@ -352,9 +349,9 @@ module Puppet::Pops::Binder::Producers
# @param binder [Puppet::Pops::Binder::Bindings::Binding, nil] The binding using this producer
# @param scope [Puppet::Parser::Scope] The scope to use for evaluation
# @option options [Puppet::Pops::Model::LambdaExpression] :transformer (nil) a transformer of produced value
- # @option options [Puppet::Pops::Types::PObjectType] :type The type to lookup
+ # @option options [Puppet::Pops::Types::PAnyType] :type The type to lookup
# @option options [String] :name ('') The name to lookup
- # @option options [Puppet::Pops::Types::PObjectType] :key The key to lookup in the hash
+ # @option options [Puppet::Pops::Types::PAnyType] :key The key to lookup in the hash
# @api public
#
def initialize(injector, binder, scope, options)
@@ -527,8 +524,8 @@ module Puppet::Pops::Binder::Producers
@contributions_key = injector.key_factory.multibind_contributions(binding.id)
end
- # @param expected [Array<Puppet::Pops::Types::PObjectType>, Puppet::Pops::Types::PObjectType] expected type or types
- # @param actual [Object, Puppet::Pops::Types::PObjectType> the actual value (or its type)
+ # @param expected [Array<Puppet::Pops::Types::PAnyType>, Puppet::Pops::Types::PAnyType] expected type or types
+ # @param actual [Object, Puppet::Pops::Types::PAnyType> the actual value (or its type)
# @return [String] a formatted string for inclusion as detail in an error message
# @api private
#
diff --git a/lib/puppet/pops/evaluator/access_operator.rb b/lib/puppet/pops/evaluator/access_operator.rb
index 29a981538..e849a2c4e 100644
--- a/lib/puppet/pops/evaluator/access_operator.rb
+++ b/lib/puppet/pops/evaluator/access_operator.rb
@@ -9,6 +9,7 @@ class Puppet::Pops::Evaluator::AccessOperator
Issues = Puppet::Pops::Issues
TYPEFACTORY = Puppet::Pops::Types::TypeFactory
+ EMPTY_STRING = ''.freeze
attr_reader :semantic
@@ -44,7 +45,7 @@ class Puppet::Pops::Evaluator::AccessOperator
k1 = k1 < 0 ? o.length + k1 : k1 # abs pos
# if k1 is outside, a length of 1 always produces an empty string
if k1 < 0
- ''
+ EMPTY_STRING
else
o[ k1, k2 ]
end
@@ -65,7 +66,7 @@ class Puppet::Pops::Evaluator::AccessOperator
fail(Puppet::Pops::Issues::BAD_STRING_SLICE_ARITY, @semantic.left_expr, {:actual => keys.size})
end
# Specified as: an index outside of range, or empty result == empty string
- (result.nil? || result.empty?) ? '' : result
+ (result.nil? || result.empty?) ? EMPTY_STRING : result
end
# Parameterizes a PRegexp Type with a pattern string or r ruby egexp
@@ -124,21 +125,10 @@ class Puppet::Pops::Evaluator::AccessOperator
# Does not flatten its keys to enable looking up with a structure
#
def access_Hash(o, scope, keys)
- # Look up key in hash, if key is nil or :undef, try alternate form before giving up.
- # This makes :undef and nil "be the same key". (The alternative is to always only write one or the other
- # in all hashes - that is much harder to guarantee since the Hash is a regular Ruby hash.
- #
+ # Look up key in hash, if key is nil, try alternate form (:undef) before giving up.
+ # This is done because the hash may have been produced by 3x logic and may thus contain :undef.
result = keys.collect do |k|
- o.fetch(k) do |key|
- case key
- when nil
- o[:undef]
- when :undef
- o[:nil]
- else
- nil
- end
- end
+ o.fetch(k) { |key| key.nil? ? o[:undef] : nil }
end
case result.size
when 0
@@ -163,7 +153,7 @@ class Puppet::Pops::Evaluator::AccessOperator
def access_PVariantType(o, scope, keys)
keys.flatten!
- assert_keys(keys, o, 1, INFINITY, Puppet::Pops::Types::PAbstractType)
+ assert_keys(keys, o, 1, INFINITY, Puppet::Pops::Types::PAnyType)
Puppet::Pops::Types::TypeFactory.variant(*keys)
end
@@ -176,7 +166,7 @@ class Puppet::Pops::Evaluator::AccessOperator
size_type = TYPEFACTORY.range(keys[-1], :default)
keys = keys[0, keys.size - 1]
end
- assert_keys(keys, o, 1, INFINITY, Puppet::Pops::Types::PAbstractType)
+ assert_keys(keys, o, 1, INFINITY, Puppet::Pops::Types::PAnyType)
t = Puppet::Pops::Types::TypeFactory.tuple(*keys)
# set size type, or nil for default (exactly 1)
t.size_type = size_type
@@ -237,7 +227,7 @@ class Puppet::Pops::Evaluator::AccessOperator
def bad_key_type_name(actual)
case actual
- when nil, :undef
+ when nil
'Undef'
when :default
'Default'
@@ -264,7 +254,7 @@ class Puppet::Pops::Evaluator::AccessOperator
def access_POptionalType(o, scope, keys)
keys.flatten!
if keys.size == 1
- unless keys[0].is_a?(Puppet::Pops::Types::PAbstractType)
+ unless keys[0].is_a?(Puppet::Pops::Types::PAnyType)
fail(Puppet::Pops::Issues::BAD_TYPE_SLICE_TYPE, @semantic.keys[0], {:base_type => 'Optional-Type', :actual => keys[0].class})
end
result = Puppet::Pops::Types::POptionalType.new()
@@ -278,7 +268,7 @@ class Puppet::Pops::Evaluator::AccessOperator
def access_PType(o, scope, keys)
keys.flatten!
if keys.size == 1
- unless keys[0].is_a?(Puppet::Pops::Types::PAbstractType)
+ unless keys[0].is_a?(Puppet::Pops::Types::PAnyType)
fail(Puppet::Pops::Issues::BAD_TYPE_SLICE_TYPE, @semantic.keys[0], {:base_type => 'Type-Type', :actual => keys[0].class})
end
result = Puppet::Pops::Types::PType.new()
@@ -289,11 +279,11 @@ class Puppet::Pops::Evaluator::AccessOperator
end
end
- def access_PRubyType(o, scope, keys)
+ def access_PRuntimeType(o, scope, keys)
keys.flatten!
- assert_keys(keys, o, 1, 1, String)
- # create ruby type based on name of class, not inference of key's type
- Puppet::Pops::Types::TypeFactory.ruby_type(keys[0])
+ assert_keys(keys, o, 2, 2, String, String)
+ # create runtime type based on runtime and name of class, (not inference of key's type)
+ Puppet::Pops::Types::TypeFactory.runtime(*keys)
end
def access_PIntegerType(o, scope, keys)
@@ -335,7 +325,7 @@ class Puppet::Pops::Evaluator::AccessOperator
def access_PHashType(o, scope, keys)
keys.flatten!
keys[0,2].each_with_index do |k, index|
- unless k.is_a?(Puppet::Pops::Types::PAbstractType)
+ unless k.is_a?(Puppet::Pops::Types::PAnyType)
fail(Puppet::Pops::Issues::BAD_TYPE_SLICE_TYPE, @semantic.keys[index], {:base_type => 'Hash-Type', :actual => k.class})
end
end
@@ -403,7 +393,7 @@ class Puppet::Pops::Evaluator::AccessOperator
fail(Puppet::Pops::Issues::BAD_TYPE_SLICE_ARITY, @semantic,
{:base_type => 'Array-Type', :min => 1, :max => 3, :actual => keys.size})
end
- unless keys[0].is_a?(Puppet::Pops::Types::PAbstractType)
+ unless keys[0].is_a?(Puppet::Pops::Types::PAnyType)
fail(Puppet::Pops::Issues::BAD_TYPE_SLICE_TYPE, @semantic.keys[0], {:base_type => 'Array-Type', :actual => keys[0].class})
end
result = Puppet::Pops::Types::PArrayType.new()
@@ -429,6 +419,17 @@ class Puppet::Pops::Evaluator::AccessOperator
end
end
+ # A Puppet::Resource represents either just a type (no title), or is a fully qualified type/title.
+ #
+ def access_Resource(o, scope, keys)
+ # To access a Puppet::Resource as if it was a PResourceType, simply infer it, and take the type of
+ # the parameterized meta type (i.e. Type[Resource[the_resource_type, the_resource_title]])
+ t = Puppet::Pops::Types::TypeCalculator.infer(o).type
+ # must map "undefined title" from resource to nil
+ t.title = nil if t.title == EMPTY_STRING
+ access(t, scope, *keys)
+ end
+
# A Resource can create a new more specific Resource type, and/or an array of resource types
# If the given type has title set, it can not be specified further.
# @example
@@ -464,6 +465,11 @@ class Puppet::Pops::Evaluator::AccessOperator
fail(Puppet::Pops::Issues::ILLEGAL_RESOURCE_SPECIALIZATION, blame, {:actual => type_name.class})
end
+ # type name must conform
+ if type_name !~ Puppet::Pops::Patterns::CLASSREF
+ fail(Puppet::Pops::Issues::ILLEGAL_CLASSREF, blamed, {:name=>type_name})
+ end
+
# The result is an array if multiple titles are given, or if titles are specified with an array
# (possibly multiple arrays, and nested arrays).
result_type_array = keys.size > 1 || keys[0].is_a?(Array)
@@ -503,7 +509,7 @@ class Puppet::Pops::Evaluator::AccessOperator
result = keys.each_with_index.map do |t, i|
unless t.is_a?(String) || t == :no_title
type_to_report = case t
- when nil, :undef
+ when nil
'Undef'
when :default
'Default'
@@ -569,7 +575,7 @@ class Puppet::Pops::Evaluator::AccessOperator
if name =~ Puppet::Pops::Patterns::NAME
ctype = Puppet::Pops::Types::PHostClassType.new()
# Remove leading '::' since all references are global, and 3x runtime does the wrong thing
- ctype.class_name = name.sub(/^::/, '')
+ ctype.class_name = name.sub(/^::/, EMPTY_STRING)
ctype
else
fail(Issues::ILLEGAL_NAME, @semantic.keys[i], {:name=>c})
diff --git a/lib/puppet/pops/evaluator/callable_mismatch_describer.rb b/lib/puppet/pops/evaluator/callable_mismatch_describer.rb
new file mode 100644
index 000000000..9d1108f8d
--- /dev/null
+++ b/lib/puppet/pops/evaluator/callable_mismatch_describer.rb
@@ -0,0 +1,175 @@
+# @api private
+module Puppet::Pops::Evaluator::CallableMismatchDescriber
+ # Produces a string with the difference between the given arguments and support signature(s).
+ #
+ # @param name [String] The name of the callable to describe
+ # @param args_type [Puppet::Pops::Types::Tuple] The tuple of argument types.
+ # @param supported_signatures [Array<Puppet::Pops::Types::Callable>] The available signatures that were available for calling.
+ #
+ # @api private
+ def self.diff_string(name, args_type, supported_signatures)
+ result = [ ]
+ if supported_signatures.size == 1
+ signature = supported_signatures[0]
+ params_type = signature.type.param_types
+ block_type = signature.type.block_type
+ params_names = signature.parameter_names
+ result << "expected:\n #{name}(#{signature_string(signature)}) - #{arg_count_string(signature.type)}"
+ else
+ result << "expected one of:\n"
+ result << supported_signatures.map do |signature|
+ params_type = signature.type.param_types
+ " #{name}(#{signature_string(signature)}) - #{arg_count_string(signature.type)}"
+ end.join("\n")
+ end
+
+ result << "\nactual:\n #{name}(#{arg_types_string(args_type)}) - #{arg_count_string(args_type)}"
+
+ result.join('')
+ end
+
+ private
+
+ # Produces a string for the signature(s)
+ #
+ # @api private
+ def self.signature_string(signature)
+ param_types = signature.type.param_types
+ block_type = signature.type.block_type
+ param_names = signature.parameter_names
+
+ from, to = param_types.size_range
+ if from == 0 && to == 0
+ # No parameters function
+ return ''
+ end
+
+ required_count = from
+ # there may be more names than there are types, and count needs to be subtracted from the count
+ # to make it correct for the last named element
+ adjust = max(0, param_names.size() -1)
+ last_range = [max(0, (from - adjust)), (to - adjust)]
+
+ types =
+ case param_types
+ when Puppet::Pops::Types::PTupleType
+ param_types.types
+ when Puppet::Pops::Types::PArrayType
+ [ param_types.element_type ]
+ end
+ tc = Puppet::Pops::Types::TypeCalculator
+
+ # join type with names (types are always present, names are optional)
+ # separate entries with comma
+ #
+ result =
+ if param_names.empty?
+ types.each_with_index.map {|t, index| tc.string(t) + opt_value_indicator(index, required_count, 0) }
+ else
+ limit = param_names.size
+ result = param_names.each_with_index.map do |name, index|
+ [tc.string(types[index] || types[-1]), name].join(' ') + opt_value_indicator(index, required_count, limit)
+ end
+ end.join(', ')
+
+ # Add {from, to} for the last type
+ # This works for both Array and Tuple since it describes the allowed count of the "last" type element
+ # for both. It does not show anything when the range is {1,1}.
+ #
+ result += range_string(last_range)
+
+ # If there is a block, include it with its own optional count {0,1}
+ case signature.type.block_type
+ when Puppet::Pops::Types::POptionalType
+ result << ', ' unless result == ''
+ result << "#{tc.string(signature.type.block_type.optional_type)} #{signature.block_name} {0,1}"
+ when Puppet::Pops::Types::PCallableType
+ result << ', ' unless result == ''
+ result << "#{tc.string(signature.type.block_type)} #{signature.block_name}"
+ when NilClass
+ # nothing
+ end
+ result
+ end
+
+ # Why oh why Ruby do you not have a standard Math.max ?
+ # @api private
+ def self.max(a, b)
+ a >= b ? a : b
+ end
+
+ # @api private
+ def self.opt_value_indicator(index, required_count, limit)
+ count = index + 1
+ (count > required_count && count < limit) ? '?' : ''
+ end
+
+ # @api private
+ def self.arg_count_string(args_type)
+ if args_type.is_a?(Puppet::Pops::Types::PCallableType)
+ size_range = args_type.param_types.size_range # regular parameters
+ adjust_range=
+ case args_type.block_type
+ when Puppet::Pops::Types::POptionalType
+ size_range[1] += 1
+ when Puppet::Pops::Types::PCallableType
+ size_range[0] += 1
+ size_range[1] += 1
+ when NilClass
+ # nothing
+ else
+ raise ArgumentError, "Internal Error, only nil, Callable, and Optional[Callable] supported by Callable block type"
+ end
+ else
+ size_range = args_type.size_range
+ end
+ "arg count #{range_string(size_range, false)}"
+ end
+
+ # @api private
+ def self.arg_types_string(args_type)
+ types =
+ case args_type
+ when Puppet::Pops::Types::PTupleType
+ last_range = args_type.repeat_last_range
+ args_type.types
+ when Puppet::Pops::Types::PArrayType
+ last_range = args_type.size_range
+ [ args_type.element_type ]
+ end
+ # stringify generalized versions or it will display Integer[10,10] for "10", String['the content'] etc.
+ # note that type must be copied since generalize is a mutating operation
+ tc = Puppet::Pops::Types::TypeCalculator
+ result = types.map { |t| tc.string(tc.generalize!(t.copy)) }.join(', ')
+
+ # Add {from, to} for the last type
+ # This works for both Array and Tuple since it describes the allowed count of the "last" type element
+ # for both. It does not show anything when the range is {1,1}.
+ #
+ result += range_string(last_range)
+ result
+ end
+
+ # Formats a range into a string of the form: `{from, to}`
+ #
+ # The following cases are optimized:
+ #
+ # * from and to are equal => `{from}`
+ # * from and to are both and 1 and squelch_one == true => `''`
+ # * from is 0 and to is 1 => `'?'`
+ # * to is INFINITY => `{from, }`
+ #
+ # @api private
+ def self.range_string(size_range, squelch_one = true)
+ from, to = size_range
+ if from == to
+ (squelch_one && from == 1) ? '' : "{#{from}}"
+ elsif to == Puppet::Pops::Types::INFINITY
+ "{#{from},}"
+ elsif from == 0 && to == 1
+ '?'
+ else
+ "{#{from},#{to}}"
+ end
+ end
+end
diff --git a/lib/puppet/pops/evaluator/callable_signature.rb b/lib/puppet/pops/evaluator/callable_signature.rb
index e953a4409..044fc533b 100644
--- a/lib/puppet/pops/evaluator/callable_signature.rb
+++ b/lib/puppet/pops/evaluator/callable_signature.rb
@@ -41,7 +41,7 @@ class Puppet::Pops::Evaluator::CallableSignature
# or Optional[Variant[Callable, ...]]. The Variant type is used when multiple signatures are acceptable.
# The Optional type is used when the block is optional.
#
- # @return [Puppet::Pops::Types::PAbstractType, nil] the expected type of a block given as the last parameter in a call.
+ # @return [Puppet::Pops::Types::PAnyType, nil] the expected type of a block given as the last parameter in a call.
#
# @api public
#
@@ -97,5 +97,4 @@ class Puppet::Pops::Evaluator::CallableSignature
def infinity?(x)
x == Puppet::Pops::Types::INFINITY
end
-
end
diff --git a/lib/puppet/pops/evaluator/closure.rb b/lib/puppet/pops/evaluator/closure.rb
index 974d77cd4..0aca525c1 100644
--- a/lib/puppet/pops/evaluator/closure.rb
+++ b/lib/puppet/pops/evaluator/closure.rb
@@ -30,46 +30,88 @@ class Puppet::Pops::Evaluator::Closure < Puppet::Pops::Evaluator::CallableSignat
# compatible with 3x AST::Lambda
# @api public
def call(scope, *args)
- @evaluator.call(self, args, @enclosing_scope)
+ variable_bindings = combine_values_with_parameters(args)
+
+ tc = Puppet::Pops::Types::TypeCalculator
+ final_args = tc.infer_set(parameters.inject([]) do |final_args, param|
+ if param.captures_rest
+ final_args.concat(variable_bindings[param.name])
+ else
+ final_args << variable_bindings[param.name]
+ end
+ end)
+
+ if tc.callable?(type, final_args)
+ @evaluator.evaluate_block_with_bindings(@enclosing_scope, variable_bindings, @model.body)
+ else
+ raise ArgumentError, "lambda called with mis-matched arguments\n#{Puppet::Pops::Evaluator::CallableMismatchDescriber.diff_string('lambda', final_args, [self])}"
+ end
end
# Call closure with argument assignment by name
- def call_by_name(scope, args_hash, spill_over = false)
- @evaluator.call_by_name(self, args_hash, @enclosing_scope, spill_over)
+ def call_by_name(scope, args_hash, enforce_parameters)
+ if enforce_parameters
+ if args_hash.size > parameters.size
+ raise ArgumentError, "Too many arguments: #{args_hash.size} for #{parameters.size}"
+ end
+
+ # associate values with parameters
+ scope_hash = {}
+ parameters.each do |p|
+ name = p.name
+ if (arg_value = args_hash[name]).nil?
+ # only set result of default expr if it is defined (it is otherwise not possible to differentiate
+ # between explicit undef and no default expression
+ unless p.value.nil?
+ scope_hash[name] = @evaluator.evaluate(p.value, @enclosing_scope)
+ end
+ else
+ scope_hash[name] = arg_value
+ end
+ end
+
+ missing = parameters.select { |p| !scope_hash.include?(p.name) }
+ if missing.any?
+ raise ArgumentError, "Too few arguments; no value given for required parameters #{missing.collect(&:name).join(" ,")}"
+ end
+
+ tc = Puppet::Pops::Types::TypeCalculator
+ final_args = tc.infer_set(parameter_names.collect { |param| scope_hash[param] })
+ if !tc.callable?(type, final_args)
+ raise ArgumentError, "lambda called with mis-matched arguments\n#{Puppet::Pops::Evaluator::CallableMismatchDescriber.diff_string('lambda', final_args, [self])}"
+ end
+ else
+ scope_hash = args_hash
+ end
+
+ @evaluator.evaluate_block_with_bindings(@enclosing_scope, scope_hash, @model.body)
end
- # incompatible with 3x except that it is an array of the same size
- def parameters()
- @model.parameters || []
+ def parameters
+ @model.parameters
end
# Returns the number of parameters (required and optional)
# @return [Integer] the total number of accepted parameters
def parameter_count
# yes, this is duplication of code, but it saves a method call
- (@model.parameters || []).size
- end
-
- # Returns the number of optional parameters.
- # @return [Integer] the number of optional accepted parameters
- def optional_parameter_count
- @model.parameters.count { |p| !p.value.nil? }
+ @model.parameters.size
end
# @api public
def parameter_names
- @model.parameters.collect {|p| p.name }
+ @model.parameters.collect(&:name)
end
# @api public
def type
- @callable || create_callable_type
+ @callable ||= create_callable_type
end
# @api public
def last_captures_rest?
- # TODO: No support for this yet
- false
+ last = @model.parameters[-1]
+ last && last.captures_rest
end
# @api public
@@ -80,28 +122,99 @@ class Puppet::Pops::Evaluator::Closure < Puppet::Pops::Evaluator::CallableSignat
private
+ def combine_values_with_parameters(args)
+ variable_bindings = {}
+
+ parameters.each_with_index do |parameter, index|
+ param_captures = parameter.captures_rest
+ default_expression = parameter.value
+
+ if index >= args.size
+ if default_expression
+ # not given, has default
+ value = @evaluator.evaluate(default_expression, @enclosing_scope)
+ if param_captures && !value.is_a?(Array)
+ # correct non array default value
+ value = [value]
+ end
+ else
+ # not given, does not have default
+ if param_captures
+ # default for captures rest is an empty array
+ value = []
+ else
+ @evaluator.fail(Puppet::Pops::Issues::MISSING_REQUIRED_PARAMETER, parameter, { :param_name => parameter.name })
+ end
+ end
+ else
+ given_argument = args[index]
+ if param_captures
+ # get excess arguments
+ value = args[(parameter_count-1)..-1]
+ # If the input was a single nil, or undef, and there is a default, use the default
+ # This supports :undef in case it was used in a 3x data structure and it is passed as an arg
+ #
+ if value.size == 1 && (given_argument.nil? || given_argument == :undef) && default_expression
+ value = @evaluator.evaluate(default_expression, scope)
+ # and ensure it is an array
+ value = [value] unless value.is_a?(Array)
+ end
+ else
+ value = given_argument
+ end
+ end
+
+ variable_bindings[parameter.name] = value
+ end
+
+ variable_bindings
+ end
+
def create_callable_type()
- t = Puppet::Pops::Types::PCallableType.new()
- tuple_t = Puppet::Pops::Types::PTupleType.new()
- # since closure lambdas are currently untyped, each parameter becomes Optional[Object]
- parameter_names.each do |name|
- # TODO: Change when Closure supports typed parameters
- tuple_t.addTypes(Puppet::Pops::Types::TypeFactory.optional_object())
+ types = []
+ range = [0, 0]
+ in_optional_parameters = false
+ parameters.each do |param|
+ type = if param.type_expr
+ @evaluator.evaluate(param.type_expr, @enclosing_scope)
+ else
+ Puppet::Pops::Types::TypeFactory.any()
+ end
+
+ if param.captures_rest && type.is_a?(Puppet::Pops::Types::PArrayType)
+ # An array on a slurp parameter is how a size range is defined for a
+ # slurp (Array[Integer, 1, 3] *$param). However, the callable that is
+ # created can't have the array in that position or else type checking
+ # will require the parameters to be arrays, which isn't what is
+ # intended. The array type contains the intended information and needs
+ # to be unpacked.
+ param_range = type.size_range
+ type = type.element_type
+ elsif param.captures_rest && !type.is_a?(Puppet::Pops::Types::PArrayType)
+ param_range = ANY_NUMBER_RANGE
+ elsif param.value
+ param_range = OPTIONAL_SINGLE_RANGE
+ else
+ param_range = REQUIRED_SINGLE_RANGE
+ end
+
+ types << type
+
+ if param_range[0] == 0
+ in_optional_parameters = true
+ elsif param_range[0] != 0 && in_optional_parameters
+ @evaluator.fail(Puppet::Pops::Issues::REQUIRED_PARAMETER_AFTER_OPTIONAL, param, { :param_name => param.name })
+ end
+
+ range[0] += param_range[0]
+ range[1] += param_range[1]
end
- # TODO: A Lambda can not currently declare varargs
- to = parameter_count
- from = to - optional_parameter_count
- if from != to
- size_t = Puppet::Pops::Types::PIntegerType.new()
- size_t.from = size
- size_t.to = size
- tuple_t.size_type = size_t
+ if range[1] == Puppet::Pops::Types::INFINITY
+ range[1] = :default
end
- t.param_types = tuple_t
- # TODO: A Lambda can not currently declare that it accepts a lambda, except as an explicit parameter
- # being a Callable
- t
+
+ Puppet::Pops::Types::TypeFactory.callable(*(types + range))
end
# Produces information about parameters compatible with a 4x Function (which can have multiple signatures)
@@ -109,4 +222,7 @@ class Puppet::Pops::Evaluator::Closure < Puppet::Pops::Evaluator::CallableSignat
[ self ]
end
+ ANY_NUMBER_RANGE = [0, Puppet::Pops::Types::INFINITY]
+ OPTIONAL_SINGLE_RANGE = [0, 1]
+ REQUIRED_SINGLE_RANGE = [1, 1]
end
diff --git a/lib/puppet/pops/evaluator/compare_operator.rb b/lib/puppet/pops/evaluator/compare_operator.rb
index 38575b5a4..900a717b6 100644
--- a/lib/puppet/pops/evaluator/compare_operator.rb
+++ b/lib/puppet/pops/evaluator/compare_operator.rb
@@ -9,10 +9,15 @@
class Puppet::Pops::Evaluator::CompareOperator
include Puppet::Pops::Utils
+ # Provides access to the Puppet 3.x runtime (scope, etc.)
+ # This separation has been made to make it easier to later migrate the evaluator to an improved runtime.
+ #
+ include Puppet::Pops::Evaluator::Runtime3Support
+
def initialize
@@equals_visitor ||= Puppet::Pops::Visitor.new(self, "equals", 1, 1)
@@compare_visitor ||= Puppet::Pops::Visitor.new(self, "cmp", 1, 1)
- @@include_visitor ||= Puppet::Pops::Visitor.new(self, "include", 1, 1)
+ @@include_visitor ||= Puppet::Pops::Visitor.new(self, "include", 2, 2)
@type_calculator = Puppet::Pops::Types::TypeCalculator.new()
end
@@ -27,8 +32,8 @@ class Puppet::Pops::Evaluator::CompareOperator
end
# Answers is b included in a
- def include?(a, b)
- @@include_visitor.visit_this_1(self, a, b)
+ def include?(a, b, scope)
+ @@include_visitor.visit_this_2(self, a, b, scope)
end
protected
@@ -110,50 +115,49 @@ class Puppet::Pops::Evaluator::CompareOperator
end
def equals_NilClass(a, b)
+ # :undef supported in case it is passed from a 3x data structure
b.nil? || b == :undef
end
def equals_Symbol(a, b)
+ # :undef supported in case it is passed from a 3x data structure
a == b || a == :undef && b.nil?
end
- def include_Object(a, b)
+ def include_Object(a, b, scope)
false
end
- def include_String(a, b)
+ def include_String(a, b, scope)
case b
when String
# subsstring search downcased
a.downcase.include?(b.downcase)
when Regexp
- # match (convert to boolean)
- !!(a =~ b)
+ matched = a.match(b) # nil, or MatchData
+ set_match_data(matched, scope) # creates ephemeral
+ !!matched # match (convert to boolean)
when Numeric
# convert string to number, true if ==
equals(a, b)
- when Puppet::Pops::Types::PStringType
- # is there a string in a string? (yes, each char is a string, and an empty string contains an empty string)
- true
else
- if b == Puppet::Pops::Types::PDataType || b == Puppet::Pops::Types::PObjectType
- # A String is Data and Object (but not of all subtypes of those types).
- true
- else
- false
- end
+ false
end
end
- def include_Array(a, b)
+ def include_Array(a, b, scope)
case b
when Regexp
+ matched = nil
a.each do |element|
next unless element.is_a? String
- return true if element =~ b
+ matched = element.match(b) # nil, or MatchData
+ break if matched
end
- return false
- when Puppet::Pops::Types::PAbstractType
+ # Always set match data, a "not found" should not keep old match data visible
+ set_match_data(matched, scope) # creates ephemeral
+ return !!matched
+ when Puppet::Pops::Types::PAnyType
a.each {|element| return true if @type_calculator.instance?(b, element) }
return false
else
@@ -162,7 +166,7 @@ class Puppet::Pops::Evaluator::CompareOperator
end
end
- def include_Hash(a, b)
- include?(a.keys, b)
+ def include_Hash(a, b, scope)
+ include?(a.keys, b, scope)
end
end
diff --git a/lib/puppet/pops/evaluator/epp_evaluator.rb b/lib/puppet/pops/evaluator/epp_evaluator.rb
index 31e59aea7..57a95bc71 100644
--- a/lib/puppet/pops/evaluator/epp_evaluator.rb
+++ b/lib/puppet/pops/evaluator/epp_evaluator.rb
@@ -3,14 +3,14 @@
class Puppet::Pops::Evaluator::EppEvaluator
def self.inline_epp(scope, epp_source, template_args = nil)
- unless epp_source.is_a? String
+ unless epp_source.is_a?(String)
raise ArgumentError, "inline_epp(): the first argument must be a String with the epp source text, got a #{epp_source.class}"
end
# Parse and validate the source
parser = Puppet::Pops::Parser::EvaluatingParser::EvaluatingEppParser.new
begin
- result = parser.parse_string(epp_source, 'inlined-epp-text')
+ result = parser.parse_string(epp_source, 'inlined-epp-text')
rescue Puppet::ParseError => e
raise ArgumentError, "inline_epp(): Invalid EPP: #{e.message}"
end
@@ -20,7 +20,7 @@ class Puppet::Pops::Evaluator::EppEvaluator
end
def self.epp(scope, file, env_name, template_args = nil)
- unless file.is_a? String
+ unless file.is_a?(String)
raise ArgumentError, "epp(): the first argument must be a String with the filename, got a #{file.class}"
end
@@ -34,7 +34,7 @@ class Puppet::Pops::Evaluator::EppEvaluator
# Parse and validate the source
parser = Puppet::Pops::Parser::EvaluatingParser::EvaluatingEppParser.new
begin
- result = parser.parse_file(template_file)
+ result = parser.parse_file(template_file)
rescue Puppet::ParseError => e
raise ArgumentError, "epp(): Invalid EPP: #{e.message}"
end
@@ -59,18 +59,19 @@ class Puppet::Pops::Evaluator::EppEvaluator
raise ArgumentError, "#{func_name}(): The EPP template contains illegal expressions (definitions)"
end
- see_scope = body.body.see_scope
- if see_scope && !template_args_set
- # no epp params and no arguments were given => inline_epp logic sees all local variables, epp all global
- closure_scope = use_global_scope_only ? scope.find_global_scope : scope
- spill_over = false
- else
- # no epp params or user provided arguments in a hash, epp logic only sees global + what was given
+ parameters_specified = body.body.parameters_specified
+ if parameters_specified || template_args_set
+ # no epp params or user provided arguments in a hash, epp() logic
+ # only sees global + what was given
closure_scope = scope.find_global_scope
- # given spill over if there are no params (e.g. replace closure scope by a new scope with the given args)
- spill_over = see_scope
+ enforce_parameters = parameters_specified
+ else
+ # no epp params and no arguments were given => inline_epp() logic
+ # sees all local variables, epp() all global
+ closure_scope = use_global_scope_only ? scope.find_global_scope : scope
+ enforce_parameters = true
end
- evaluated_result = parser.closure(body, closure_scope).call_by_name(scope, template_args, spill_over)
+ evaluated_result = parser.closure(body, closure_scope).call_by_name(scope, template_args, enforce_parameters)
evaluated_result
end
@@ -84,4 +85,4 @@ class Puppet::Pops::Evaluator::EppEvaluator
[template_args, true]
end
end
-end \ No newline at end of file
+end
diff --git a/lib/puppet/pops/evaluator/evaluator_impl.rb b/lib/puppet/pops/evaluator/evaluator_impl.rb
index fe60cc0d9..02098d674 100644
--- a/lib/puppet/pops/evaluator/evaluator_impl.rb
+++ b/lib/puppet/pops/evaluator/evaluator_impl.rb
@@ -34,6 +34,8 @@ class Puppet::Pops::Evaluator::EvaluatorImpl
# Refactor when support is dropped for Ruby 1.8.7.
#
INFINITY = 1.0 / 0.0
+ EMPTY_STRING = ''.freeze
+ COMMA_SEPARATOR = ', '.freeze
# Reference to Issues name space makes it easier to refer to issues
# (Issues are shared with the validator).
@@ -41,7 +43,7 @@ class Puppet::Pops::Evaluator::EvaluatorImpl
Issues = Puppet::Pops::Issues
def initialize
- @@eval_visitor ||= Puppet::Pops::Visitor.new(self, "eval", 1, 1)
+ @@eval_visitor ||= Puppet::Pops::Visitor.new(self, "eval", 1, 1)
@@lvalue_visitor ||= Puppet::Pops::Visitor.new(self, "lvalue", 1, 1)
@@assign_visitor ||= Puppet::Pops::Visitor.new(self, "assign", 3, 3)
@@string_visitor ||= Puppet::Pops::Visitor.new(self, "string", 1, 1)
@@ -61,23 +63,14 @@ class Puppet::Pops::Evaluator::EvaluatorImpl
@@type_calculator
end
- # Polymorphic evaluate - calls eval_TYPE
+ # Evaluates the given _target_ object in the given scope.
#
- # ## Polymorphic evaluate
- # Polymorphic evaluate calls a method on the format eval_TYPE where classname is the last
- # part of the class of the given _target_. A search is performed starting with the actual class, continuing
- # with each of the _target_ class's super classes until a matching method is found.
- #
- # # Description
- # Evaluates the given _target_ object in the given scope, optionally passing a block which will be
- # called with the result of the evaluation.
- #
- # @overload evaluate(target, scope, {|result| block})
+ # @overload evaluate(target, scope)
# @param target [Object] evaluation target - see methods on the pattern assign_TYPE for actual supported types.
# @param scope [Object] the runtime specific scope class where evaluation should take place
# @return [Object] the result of the evaluation
#
- # @api
+ # @api public
#
def evaluate(target, scope)
begin
@@ -96,152 +89,56 @@ class Puppet::Pops::Evaluator::EvaluatorImpl
end
end
- # Polymorphic assign - calls assign_TYPE
- #
- # ## Polymorphic assign
- # Polymorphic assign calls a method on the format assign_TYPE where TYPE is the last
- # part of the class of the given _target_. A search is performed starting with the actual class, continuing
- # with each of the _target_ class's super classes until a matching method is found.
- #
- # # Description
# Assigns the given _value_ to the given _target_. The additional argument _o_ is the instruction that
# produced the target/value tuple and it is used to set the origin of the result.
+ #
# @param target [Object] assignment target - see methods on the pattern assign_TYPE for actual supported types.
# @param value [Object] the value to assign to `target`
# @param o [Puppet::Pops::Model::PopsObject] originating instruction
# @param scope [Object] the runtime specific scope where evaluation should take place
#
- # @api
+ # @api private
#
def assign(target, value, o, scope)
@@assign_visitor.visit_this_3(self, target, value, o, scope)
end
+ # Computes a value that can be used as the LHS in an assignment.
+ # @param o [Object] the expression to evaluate as a left (assignable) entity
+ # @param scope [Object] the runtime specific scope where evaluation should take place
+ #
+ # @api private
+ #
def lvalue(o, scope)
@@lvalue_visitor.visit_this_1(self, o, scope)
end
+ # Produces a String representation of the given object _o_ as used in interpolation.
+ # @param o [Object] the expression of which a string representation is wanted
+ # @param scope [Object] the runtime specific scope where evaluation should take place
+ #
+ # @api public
+ #
def string(o, scope)
@@string_visitor.visit_this_1(self, o, scope)
end
- # Call a closure matching arguments by name - Can only be called with a Closure (for now), may be refactored later
- # to also handle other types of calls (function calls are also handled by CallNamedFunction and CallMethod, they
- # could create similar objects to Closure, wait until other types of defines are instantiated - they may behave
- # as special cases of calls - i.e. 'new').
+ # Evaluate a BlockExpression in a new scope with variables bound to the
+ # given values.
#
- # Call by name supports a "spill_over" mode where extra arguments in the given args_hash are introduced
- # as variables in the resulting scope.
+ # @param scope [Puppet::Parser::Scope] the parent scope
+ # @param variable_bindings [Hash{String => Object}] the variable names and values to bind (names are keys, bound values are values)
+ # @param block [Puppet::Pops::Model::BlockExpression] the sequence of expressions to evaluate in the new scope
#
- # @raise ArgumentError, if there are to many or too few arguments
- # @raise ArgumentError, if given closure is not a Puppet::Pops::Evaluator::Closure
- #
- def call_by_name(closure, args_hash, scope, spill_over = false)
- raise ArgumentError, "Can only call a Lambda" unless closure.is_a?(Puppet::Pops::Evaluator::Closure)
- pblock = closure.model
- parameters = pblock.parameters || []
-
- if !spill_over && args_hash.size > parameters.size
- raise ArgumentError, "Too many arguments: #{args_hash.size} for #{parameters.size}"
- end
-
- # associate values with parameters
- scope_hash = {}
- parameters.each do |p|
- scope_hash[p.name] = args_hash[p.name] || evaluate(p.value, scope)
- end
- missing = scope_hash.reduce([]) {|memo, entry| memo << entry[0] if entry[1].nil?; memo }
- unless missing.empty?
- optional = parameters.count { |p| !p.value.nil? }
- raise ArgumentError, "Too few arguments; no value given for required parameters #{missing.join(" ,")}"
- end
- if spill_over
- # all args from given hash should be used, nil entries replaced by default values should win
- scope_hash = args_hash.merge(scope_hash)
- end
-
- # Store the evaluated name => value associations in a new inner/local/ephemeral scope
- # (This is made complicated due to the fact that the implementation of scope is overloaded with
- # functionality and an inner ephemeral scope must be used (as opposed to just pushing a local scope
- # on a scope "stack").
-
- # Ensure variable exists with nil value if error occurs.
- # Some ruby implementations does not like creating variable on return
- result = nil
- begin
- scope_memo = get_scope_nesting_level(scope)
- # change to create local scope_from - cannot give it file and line - that is the place of the call, not
- # "here"
- create_local_scope_from(scope_hash, scope)
- result = evaluate(pblock.body, scope)
- ensure
- set_scope_nesting_level(scope, scope_memo)
- end
- result
- end
-
- # Call a closure - Can only be called with a Closure (for now), may be refactored later
- # to also handle other types of calls (function calls are also handled by CallNamedFunction and CallMethod, they
- # could create similar objects to Closure, wait until other types of defines are instantiated - they may behave
- # as special cases of calls - i.e. 'new')
- #
- # @raise ArgumentError, if there are to many or too few arguments
- # @raise ArgumentError, if given closure is not a Puppet::Pops::Evaluator::Closure
+ # @api private
#
- def call(closure, args, scope)
- raise ArgumentError, "Can only call a Lambda" unless closure.is_a?(Puppet::Pops::Evaluator::Closure)
- pblock = closure.model
- parameters = pblock.parameters || []
-
- raise ArgumentError, "Too many arguments: #{args.size} for #{parameters.size}" unless args.size <= parameters.size
-
- # associate values with parameters
- merged = parameters.zip(args)
- # calculate missing arguments
- missing = parameters.slice(args.size, parameters.size - args.size).select {|p| p.value.nil? }
- unless missing.empty?
- optional = parameters.count { |p| !p.value.nil? }
- raise ArgumentError, "Too few arguments; #{args.size} for #{optional > 0 ? ' min ' : ''}#{parameters.size - optional}"
- end
-
- evaluated = merged.collect do |m|
- # m can be one of
- # m = [Parameter{name => "name", value => nil], "given"]
- # | [Parameter{name => "name", value => Expression}, "given"]
- #
- # "given" is always an optional entry. If a parameter was provided then
- # the entry will be in the array, otherwise the m array will be a
- # single element.
- given_argument = m[1]
- argument_name = m[0].name
- default_expression = m[0].value
-
- value = if default_expression
- evaluate(default_expression, scope)
- else
- given_argument
- end
- [argument_name, value]
- end
-
- # Store the evaluated name => value associations in a new inner/local/ephemeral scope
- # (This is made complicated due to the fact that the implementation of scope is overloaded with
- # functionality and an inner ephemeral scope must be used (as opposed to just pushing a local scope
- # on a scope "stack").
-
- # Ensure variable exists with nil value if error occurs.
- # Some ruby implementations does not like creating variable on return
- result = nil
- begin
- scope_memo = get_scope_nesting_level(scope)
- # change to create local scope_from - cannot give it file and line - that is the place of the call, not
- # "here"
- create_local_scope_from(Hash[evaluated], scope)
- result = evaluate(pblock.body, scope)
- ensure
- set_scope_nesting_level(scope, scope_memo)
+ def evaluate_block_with_bindings(scope, variable_bindings, block_expr)
+ with_guarded_scope(scope) do
+ # change to create local scope_from - cannot give it file and line -
+ # that is the place of the call, not "here"
+ create_local_scope_from(variable_bindings, scope)
+ evaluate(block_expr, scope)
end
- result
end
protected
@@ -294,17 +191,12 @@ class Puppet::Pops::Evaluator::EvaluatorImpl
o
end
- # Allows nil to be used as a Nop.
- # Evaluates to nil
- # TODO: What is the difference between literal undef, nil, and nop?
- #
+ # Allows nil to be used as a Nop, Evaluates to nil
def eval_NilClass(o, scope)
nil
end
# Evaluates Nop to nil.
- # TODO: or is this the same as :undef
- # TODO: is this even needed as a separate instruction when there is a literal undef?
def eval_Nop(o, scope)
nil
end
@@ -315,12 +207,18 @@ class Puppet::Pops::Evaluator::EvaluatorImpl
o.value
end
+ # Reserved Words fail to evaluate
+ #
+ def eval_ReservedWord(o, scope)
+ fail(Puppet::Pops::Issues::RESERVED_WORD, o, {:word => o.word})
+ end
+
def eval_LiteralDefault(o, scope)
:default
end
def eval_LiteralUndef(o, scope)
- :undef # TODO: or just use nil for this?
+ nil
end
# A QualifiedReference (i.e. a capitalized qualified name such as Foo, or Foo::Bar) evaluates to a PType
@@ -337,6 +235,19 @@ class Puppet::Pops::Evaluator::EvaluatorImpl
- coerce_numeric(evaluate(o.expr, scope), o, scope)
end
+ def eval_UnfoldExpression(o, scope)
+ candidate = evaluate(o.expr, scope)
+ case candidate
+ when Array
+ candidate
+ when Hash
+ candidate.to_a
+ else
+ # turns anything else into an array (so result can be unfolded)
+ [candidate]
+ end
+ end
+
# Abstract evaluation, returns array [left, right] with the evaluated result of left_expr and
# right_expr
# @return <Array<Object, Object>> array with result of evaluating left and right expressions
@@ -356,41 +267,8 @@ class Puppet::Pops::Evaluator::EvaluatorImpl
name = lvalue(o.left_expr, scope)
value = evaluate(o.right_expr, scope)
- case o.operator
- when :'=' # regular assignment
+ if o.operator == :'='
assign(name, value, o, scope)
-
- when :'+='
- # if value does not exist and strict is on, looking it up fails, else it is nil or :undef
- existing_value = get_variable_value(name, o, scope)
- begin
- if existing_value.nil? || existing_value == :undef
- assign(name, value, o, scope)
- else
- # Delegate to calculate function to deal with check of LHS, and perform ´+´ as arithmetic or concatenation the
- # same way as ArithmeticExpression performs `+`.
- assign(name, calculate(existing_value, value, :'+', o.left_expr, o.right_expr, scope), o, scope)
- end
- rescue ArgumentError => e
- fail(Issues::APPEND_FAILED, o, {:message => e.message})
- end
-
- when :'-='
- # If an attempt is made to delete values from something that does not exists, the value is :undef (it is guaranteed to not
- # include any values the user wants deleted anyway :-)
- #
- # if value does not exist and strict is on, looking it up fails, else it is nil or :undef
- existing_value = get_variable_value(name, o, scope)
- begin
- if existing_value.nil? || existing_value == :undef
- assign(name, :undef, o, scope)
- else
- # Delegate to delete function to deal with check of LHS, and perform deletion
- assign(name, delete(get_variable_value(name, o, scope), value), o, scope)
- end
- rescue ArgumentError => e
- fail(Issues::APPEND_FAILED, o, {:message => e.message}, e)
- end
else
fail(Issues::UNSUPPORTED_OPERATOR, o, {:operator => o.operator})
end
@@ -403,7 +281,9 @@ class Puppet::Pops::Evaluator::EvaluatorImpl
# Handles binary expression where lhs and rhs are array/hash or numeric and operator is +, - , *, % / << >>
#
def eval_ArithmeticExpression(o, scope)
- left, right = eval_BinaryExpression(o, scope)
+ left = evaluate(o.left_expr, scope)
+ right = evaluate(o.right_expr, scope)
+
begin
result = calculate(left, right, o.operator, o.left_expr, o.right_expr, scope)
rescue ArgumentError => e
@@ -458,7 +338,7 @@ class Puppet::Pops::Evaluator::EvaluatorImpl
def eval_EppExpression(o, scope)
scope["@epp"] = []
evaluate(o.body, scope)
- result = scope["@epp"].join('')
+ result = scope["@epp"].join
result
end
@@ -493,11 +373,12 @@ class Puppet::Pops::Evaluator::EvaluatorImpl
# Evaluates <, <=, >, >=, and ==
#
def eval_ComparisonExpression o, scope
- left, right = eval_BinaryExpression o, scope
+ left = evaluate(o.left_expr, scope)
+ right = evaluate(o.right_expr, scope)
begin
# Left is a type
- if left.is_a?(Puppet::Pops::Types::PAbstractType)
+ if left.is_a?(Puppet::Pops::Types::PAnyType)
case o.operator
when :'=='
@@type_calculator.equals(left,right)
@@ -548,7 +429,7 @@ class Puppet::Pops::Evaluator::EvaluatorImpl
end
# Evaluates matching expressions with type, string or regexp rhs expression.
- # If RHS is a type, the =~ matches compatible (assignable?) type.
+ # If RHS is a type, the =~ matches compatible (instance? of) type.
#
# @example
# x =~ /abc.*/
@@ -559,21 +440,22 @@ class Puppet::Pops::Evaluator::EvaluatorImpl
# x =~ "${y}.*"
# @example
# [1,2,3] =~ Array[Integer[1,10]]
+ #
+ # Note that a string is not instance? of Regexp, only Regular expressions are.
+ # The Pattern type should instead be used as it is specified as subtype of String.
+ #
# @return [Boolean] if a match was made or not. Also sets $0..$n to matchdata in current scope.
#
def eval_MatchExpression o, scope
- left, pattern = eval_BinaryExpression o, scope
+ left = evaluate(o.left_expr, scope)
+ pattern = evaluate(o.right_expr, scope)
+
# matches RHS types as instance of for all types except a parameterized Regexp[R]
- if pattern.is_a?(Puppet::Pops::Types::PAbstractType)
- if pattern.is_a?(Puppet::Pops::Types::PRegexpType) && pattern.pattern
- # A qualified PRegexpType, get its ruby regexp
- pattern = pattern.regexp
- else
- # evaluate as instance?
- matched = @@type_calculator.instance?(pattern, left)
- # convert match result to Boolean true, or false
- return o.operator == :'=~' ? !!matched : !matched
- end
+ if pattern.is_a?(Puppet::Pops::Types::PAnyType)
+ # evaluate as instance? of type check
+ matched = @@type_calculator.instance?(pattern, left)
+ # convert match result to Boolean true, or false
+ return o.operator == :'=~' ? !!matched : !matched
end
begin
@@ -586,7 +468,7 @@ class Puppet::Pops::Evaluator::EvaluatorImpl
end
matched = pattern.match(left) # nil, or MatchData
- set_match_data(matched, o, scope) # creates ephemeral
+ set_match_data(matched,scope) # creates ephemeral
# convert match result to Boolean true, or false
o.operator == :'=~' ? !!matched : !matched
@@ -595,8 +477,9 @@ class Puppet::Pops::Evaluator::EvaluatorImpl
# Evaluates Puppet DSL `in` expression
#
def eval_InExpression o, scope
- left, right = eval_BinaryExpression o, scope
- @@compare_operator.include?(right, left)
+ left = evaluate(o.left_expr, scope)
+ right = evaluate(o.right_expr, scope)
+ @@compare_operator.include?(right, left, scope)
end
# @example
@@ -616,19 +499,19 @@ class Puppet::Pops::Evaluator::EvaluatorImpl
end
# Evaluates each entry of the literal list and creates a new Array
+ # Supports unfolding of entries
# @return [Array] with the evaluated content
#
def eval_LiteralList o, scope
- o.values.collect {|expr| evaluate(expr, scope)}
+ unfold([], o.values, scope)
end
# Evaluates each entry of the literal hash and creates a new Hash.
# @return [Hash] with the evaluated content
#
def eval_LiteralHash o, scope
- h = Hash.new
- o.entries.each {|entry| h[ evaluate(entry.key, scope)]= evaluate(entry.value, scope)}
- h
+ # optimized
+ o.entries.reduce({}) {|h,entry| h[evaluate(entry.key, scope)] = evaluate(entry.value, scope); h }
end
# Evaluates all statements and produces the last evaluated value
@@ -656,8 +539,17 @@ class Puppet::Pops::Evaluator::EvaluatorImpl
if o.options.find do |co|
# the first case option that matches
if co.values.find do |c|
- the_default = co.then_expr if c.is_a? Puppet::Pops::Model::LiteralDefault
- is_match?(test, evaluate(c, scope), c, scope)
+ case c
+ when Puppet::Pops::Model::LiteralDefault
+ the_default = co.then_expr
+ is_match?(test, evaluate(c, scope), c, scope)
+ when Puppet::Pops::Model::UnfoldExpression
+ # not ideal for error reporting, since it is not known which unfolded result
+ # that caused an error - the entire unfold expression is blamed (i.e. the var c, passed to is_match?)
+ evaluate(c, scope).any? {|v| is_match?(test, v, c, scope) }
+ else
+ is_match?(test, evaluate(c, scope), c, scope)
+ end
end
result = evaluate(co.then_expr, scope)
true # the option was picked
@@ -712,16 +604,123 @@ class Puppet::Pops::Evaluator::EvaluatorImpl
evaluate(o.body, scope)
end
- # Produces Array[PObjectType], an array of resource references
+ # Produces Array[PAnyType], an array of resource references
#
def eval_ResourceExpression(o, scope)
exported = o.exported
virtual = o.virtual
- type_name = evaluate(o.type_name, scope)
- o.bodies.map do |body|
- titles = [evaluate(body.title, scope)].flatten
- evaluated_parameters = body.operations.map {|op| evaluate(op, scope) }
- create_resources(o, scope, virtual, exported, type_name, titles, evaluated_parameters)
+
+ # Get the type name
+ type_name =
+ if (tmp_name = o.type_name).is_a?(Puppet::Pops::Model::QualifiedName)
+ tmp_name.value # already validated as a name
+ else
+ type_name_acceptable =
+ case o.type_name
+ when Puppet::Pops::Model::QualifiedReference
+ true
+ when Puppet::Pops::Model::AccessExpression
+ o.type_name.left_expr.is_a?(Puppet::Pops::Model::QualifiedReference)
+ end
+
+ evaluated_name = evaluate(tmp_name, scope)
+ unless type_name_acceptable
+ actual = type_calculator.generalize!(type_calculator.infer(evaluated_name)).to_s
+ fail(Puppet::Pops::Issues::ILLEGAL_RESOURCE_TYPE, o.type_name, {:actual => actual})
+ end
+
+ # must be a CatalogEntry subtype
+ case evaluated_name
+ when Puppet::Pops::Types::PHostClassType
+ unless evaluated_name.class_name.nil?
+ fail(Puppet::Pops::Issues::ILLEGAL_RESOURCE_TYPE, o.type_name, {:actual=> evaluated_name.to_s})
+ end
+ 'class'
+
+ when Puppet::Pops::Types::PResourceType
+ unless evaluated_name.title().nil?
+ fail(Puppet::Pops::Issues::ILLEGAL_RESOURCE_TYPE, o.type_name, {:actual=> evaluated_name.to_s})
+ end
+ evaluated_name.type_name # assume validated
+
+ else
+ actual = type_calculator.generalize!(type_calculator.infer(evaluated_name)).to_s
+ fail(Puppet::Pops::Issues::ILLEGAL_RESOURCE_TYPE, o.type_name, {:actual=>actual})
+ end
+ end
+
+ # This is a runtime check - the model is valid, but will have runtime issues when evaluated
+ # and storeconfigs is not set.
+ if(o.exported)
+ optionally_fail(Puppet::Pops::Issues::RT_NO_STORECONFIGS_EXPORT, o);
+ end
+
+ titles_to_body = {}
+ body_to_titles = {}
+ body_to_params = {}
+
+ # titles are evaluated before attribute operations
+ o.bodies.map do | body |
+ titles = evaluate(body.title, scope)
+
+ # Title may not be nil
+ # Titles may be given as an array, it is ok if it is empty, but not if it contains nil entries
+ # Titles may not be an empty String
+ # Titles must be unique in the same resource expression
+ # There may be a :default entry, its entries apply with lower precedence
+ #
+ if titles.nil?
+ fail(Puppet::Pops::Issues::MISSING_TITLE, body.title)
+ end
+ titles = [titles].flatten
+
+ # Check types of evaluated titles and duplicate entries
+ titles.each_with_index do |title, index|
+ if title.nil?
+ fail(Puppet::Pops::Issues::MISSING_TITLE_AT, body.title, {:index => index})
+
+ elsif !title.is_a?(String) && title != :default
+ actual = type_calculator.generalize!(type_calculator.infer(title)).to_s
+ fail(Puppet::Pops::Issues::ILLEGAL_TITLE_TYPE_AT, body.title, {:index => index, :actual => actual})
+
+ elsif title == EMPTY_STRING
+ fail(Puppet::Pops::Issues::EMPTY_STRING_TITLE_AT, body.title, {:index => index})
+
+ elsif titles_to_body[title]
+ fail(Puppet::Pops::Issues::DUPLICATE_TITLE, o, {:title => title})
+ end
+ titles_to_body[title] = body
+ end
+
+ # Do not create a real instance from the :default case
+ titles.delete(:default)
+
+ body_to_titles[body] = titles
+
+ # Store evaluated parameters in a hash associated with the body, but do not yet create resource
+ # since the entry containing :defaults may appear later
+ body_to_params[body] = body.operations.reduce({}) do |param_memo, op|
+ params = evaluate(op, scope)
+ params = [params] unless params.is_a?(Array)
+ params.each do |p|
+ if param_memo.include? p.name
+ fail(Puppet::Pops::Issues::DUPLICATE_ATTRIBUTE, o, {:attribute => p.name})
+ end
+ param_memo[p.name] = p
+ end
+ param_memo
+ end
+ end
+
+ # Titles and Operations have now been evaluated and resources can be created
+ # Each production is a PResource, and an array of all is produced as the result of
+ # evaluating the ResourceExpression.
+ #
+ defaults_hash = body_to_params[titles_to_body[:default]] || {}
+ o.bodies.map do | body |
+ titles = body_to_titles[body]
+ params = defaults_hash.merge(body_to_params[body] || {})
+ create_resources(o, scope, virtual, exported, type_name, titles, params.values)
end.flatten.compact
end
@@ -732,19 +731,35 @@ class Puppet::Pops::Evaluator::EvaluatorImpl
evaluated_resources
end
- # Produces 3x array of parameters
+ # Produces 3x parameter
def eval_AttributeOperation(o, scope)
create_resource_parameter(o, scope, o.attribute_name, evaluate(o.value_expr, scope), o.operator)
end
+ def eval_AttributesOperation(o, scope)
+ hashed_params = evaluate(o.expr, scope)
+ unless hashed_params.is_a?(Hash)
+ actual = type_calculator.generalize!(type_calculator.infer(hashed_params)).to_s
+ fail(Puppet::Pops::Issues::TYPE_MISMATCH, o.expr, {:expected => 'Hash', :actual => actual})
+ end
+ hashed_params.map { |k,v| create_resource_parameter(o, scope, k, v, :'=>') }
+ end
+
# Sets default parameter values for a type, produces the type
#
def eval_ResourceDefaultsExpression(o, scope)
- type_name = o.type_ref.value # a QualifiedName's string value
+ type = evaluate(o.type_ref, scope)
+ type_name =
+ if type.is_a?(Puppet::Pops::Types::PResourceType) && !type.type_name.nil? && type.title.nil?
+ type.type_name # assume it is a valid name
+ else
+ actual = type_calculator.generalize!(type_calculator.infer(type))
+ fail(Issues::ILLEGAL_RESOURCE_TYPE, o.type_ref, {:actual => actual})
+ end
evaluated_parameters = o.operations.map {|op| evaluate(op, scope) }
create_resource_defaults(o, scope, type_name, evaluated_parameters)
# Produce the type
- evaluate(o.type_ref, scope)
+ type
end
# Evaluates function call by name.
@@ -762,7 +777,9 @@ class Puppet::Pops::Evaluator::EvaluatorImpl
fail(Issues::ILLEGAL_EXPRESSION, o.functor_expr, {:feature=>'function name', :container => o})
end
name = o.functor_expr.value
- evaluated_arguments = o.arguments.collect {|arg| evaluate(arg, scope) }
+
+ evaluated_arguments = unfold([], o.arguments, scope)
+
# wrap lambda in a callable block if it is present
evaluated_arguments << Puppet::Pops::Evaluator::Closure.new(self, o.lambda, scope) if o.lambda
call_function(name, evaluated_arguments, o, scope)
@@ -780,7 +797,10 @@ class Puppet::Pops::Evaluator::EvaluatorImpl
fail(Issues::ILLEGAL_EXPRESSION, o.functor_expr, {:feature=>'function name', :container => o})
end
name = name.value # the string function name
- evaluated_arguments = [receiver] + (o.arguments || []).collect {|arg| evaluate(arg, scope) }
+
+ evaluated_arguments = unfold([receiver], o.arguments || [], scope)
+
+ # wrap lambda in a callable block if it is present
evaluated_arguments << Puppet::Pops::Evaluator::Closure.new(self, o.lambda, scope) if o.lambda
call_function(name, evaluated_arguments, o, scope)
end
@@ -794,14 +814,27 @@ class Puppet::Pops::Evaluator::EvaluatorImpl
#
with_guarded_scope(scope) do
test = evaluate(o.left_expr, scope)
+ the_default = nil
selected = o.selectors.find do |s|
- candidate = evaluate(s.matching_expr, scope)
- candidate == :default || is_match?(test, candidate, s.matching_expr, scope)
+ me = s.matching_expr
+ case me
+ when Puppet::Pops::Model::LiteralDefault
+ the_default = s.value_expr
+ false
+ when Puppet::Pops::Model::UnfoldExpression
+ # not ideal for error reporting, since it is not known which unfolded result
+ # that caused an error - the entire unfold expression is blamed (i.e. the var c, passed to is_match?)
+ evaluate(me, scope).any? {|v| is_match?(test, v, me, scope) }
+ else
+ is_match?(test, evaluate(me, scope), me, scope)
+ end
end
if selected
evaluate(selected.value_expr, scope)
+ elsif the_default
+ evaluate(the_default, scope)
else
- nil
+ fail(Issues::UNMATCHED_SELECTOR, o.left_expr, :param_value => test)
end
end
end
@@ -851,15 +884,13 @@ class Puppet::Pops::Evaluator::EvaluatorImpl
name = evaluate(o.expr, scope)
# Should be caught by validation, but make this explicit here as well, or mysterious evaluation issues
- # may occur.
+ # may occur for some evaluation use cases.
case name
when String
when Numeric
else
fail(Issues::ILLEGAL_VARIABLE_EXPRESSION, o.expr)
end
- # TODO: Check for valid variable name (Task for validator)
- # TODO: semantics of undefined variable in scope, this just returns what scope does == value or nil
get_variable_value(name, o, scope)
end
@@ -882,7 +913,6 @@ class Puppet::Pops::Evaluator::EvaluatorImpl
#
def eval_TextExpression o, scope
if o.expr.is_a?(Puppet::Pops::Model::QualifiedName)
- # TODO: formalize, when scope returns nil, vs error
string(get_variable_value(o.expr.value, o, scope), scope)
else
string(evaluate(o.expr, scope), scope)
@@ -894,27 +924,26 @@ class Puppet::Pops::Evaluator::EvaluatorImpl
end
def string_Symbol(o, scope)
- case o
- when :undef
- ''
+ if :undef == o # optimized comparison 1.44 vs 1.95
+ EMPTY_STRING
else
o.to_s
end
end
- def string_Array(o, scope)
- ['[', o.map {|e| string(e, scope)}.join(', '), ']'].join()
+ def string_Array(o, scope)
+ "[#{o.map {|e| string(e, scope)}.join(COMMA_SEPARATOR)}]"
end
def string_Hash(o, scope)
- ['{', o.map {|k,v| string(k, scope) + " => " + string(v, scope)}.join(', '), '}'].join()
+ "{#{o.map {|k,v| "#{string(k, scope)} => #{string(v, scope)}"}.join(COMMA_SEPARATOR)}}"
end
def string_Regexp(o, scope)
- ['/', o.source, '/'].join()
+ "/#{o.source}/"
end
- def string_PAbstractType(o, scope)
+ def string_PAnyType(o, scope)
@@type_calculator.string(o)
end
@@ -1041,9 +1070,9 @@ class Puppet::Pops::Evaluator::EvaluatorImpl
if right.is_a?(Regexp)
return false unless left.is_a? String
matched = right.match(left)
- set_match_data(matched, o, scope) # creates or clears ephemeral
+ set_match_data(matched, scope) # creates or clears ephemeral
!!matched # convert to boolean
- elsif right.is_a?(Puppet::Pops::Types::PAbstractType)
+ elsif right.is_a?(Puppet::Pops::Types::PAnyType)
# right is a type and left is not - check if left is an instance of the given type
# (The reverse is not terribly meaningful - computing which of the case options that first produces
# an instance of a given type).
@@ -1064,4 +1093,23 @@ class Puppet::Pops::Evaluator::EvaluatorImpl
end
end
+ # Maps the expression in the given array to their product except for UnfoldExpressions which are first unfolded.
+ # The result is added to the given result Array.
+ # @param result [Array] Where to add the result (may contain information to add to)
+ # @param array [Array[Puppet::Pops::Model::Expression] the expressions to map
+ # @param scope [Puppet::Parser::Scope] the scope to evaluate in
+ # @return [Array] the given result array with content added from the operation
+ #
+ def unfold(result, array, scope)
+ array.each do |x|
+ if x.is_a?(Puppet::Pops::Model::UnfoldExpression)
+ result.concat(evaluate(x, scope))
+ else
+ result << evaluate(x, scope)
+ end
+ end
+ result
+ end
+ private :unfold
+
end
diff --git a/lib/puppet/pops/evaluator/relationship_operator.rb b/lib/puppet/pops/evaluator/relationship_operator.rb
index 7ffd20c8c..866afa8ef 100644
--- a/lib/puppet/pops/evaluator/relationship_operator.rb
+++ b/lib/puppet/pops/evaluator/relationship_operator.rb
@@ -34,7 +34,8 @@ class Puppet::Pops::Evaluator::RelationshipOperator
@type_calculator = Puppet::Pops::Types::TypeCalculator.new()
@type_parser = Puppet::Pops::Types::TypeParser.new()
- @catalog_type = Puppet::Pops::Types::TypeFactory.catalog_entry()
+ tf = Puppet::Pops::Types::TypeFactory
+ @catalog_type = tf.variant(tf.catalog_entry, tf.type_type(tf.catalog_entry))
end
def transform(o, scope)
@@ -61,7 +62,7 @@ class Puppet::Pops::Evaluator::RelationshipOperator
# Types are what they are, just check the type
# @api private
- def transform_PAbstractType(o, scope)
+ def transform_PAnyType(o, scope)
assert_catalog_type(o, scope)
end
diff --git a/lib/puppet/pops/evaluator/runtime3_support.rb b/lib/puppet/pops/evaluator/runtime3_support.rb
index ed5c124d1..127c7d6f5 100644
--- a/lib/puppet/pops/evaluator/runtime3_support.rb
+++ b/lib/puppet/pops/evaluator/runtime3_support.rb
@@ -5,6 +5,8 @@
# @api private
module Puppet::Pops::Evaluator::Runtime3Support
+ NAME_SPACE_SEPARATOR = '::'.freeze
+
# Fails the evaluation of _semantic_ with a given issue.
#
# @param issue [Puppet::Pops::Issue] the issue to report
@@ -72,7 +74,11 @@ module Puppet::Pops::Evaluator::Runtime3Support
# Not ideal, scope should support numeric lookup directly instead.
# TODO: consider fixing scope
catch(:undefined_variable) {
- return scope.lookupvar(name.to_s)
+ x = scope.lookupvar(name.to_s)
+ # Must convert :undef back to nil - this can happen when an undefined variable is used in a
+ # parameter's default value expression - there nil must be :undef to work with the rest of 3x.
+ # Now that the value comes back to 4x it is changed to nil.
+ return (x == :undef) ? nil : x
}
# It is always ok to reference numeric variables even if they are not assigned. They are always undef
# if not set by a match expression.
@@ -95,7 +101,7 @@ module Puppet::Pops::Evaluator::Runtime3Support
scope.exist?(name.to_s)
end
- def set_match_data(match_data, o, scope)
+ def set_match_data(match_data, scope)
# See set_variable for rationale for not passing file and line to ephemeral_from.
# NOTE: The 3x scope adds one ephemeral(match) to its internal stack per match that succeeds ! It never
# clears anything. Thus a context that performs many matches will get very deep (there simply is no way to
@@ -185,11 +191,6 @@ module Puppet::Pops::Evaluator::Runtime3Support
# and convoluted path of evaluation.
# In order to do this in a way that is similar to 3.x two resources are created to be used as keys.
#
- #
- # TODO: logic that creates a PCatalogEntryType should resolve it to ensure it is loaded (to the best of known_resource_types knowledge).
- # If this is not done, the order in which things are done may be different? OTOH, it probably works anyway :-)
- # TODO: Not sure if references needs to be resolved via the scope?
- #
# And if that is not enough, a source/target may be a Collector (a baked query that will be evaluated by the
# compiler - it is simply passed through here for processing by the compiler at the right time).
#
@@ -229,22 +230,20 @@ module Puppet::Pops::Evaluator::Runtime3Support
end
def call_function(name, args, o, scope)
- # Call via 4x API if it is available, and the function exists
- #
- if loaders = Puppet.lookup(:loaders) {nil}
- # find the loader that loaded the code, or use the private_environment_loader (sees env + all modules)
- adapter = Puppet::Pops::Utils.find_adapter(o, Puppet::Pops::Adapters::LoaderAdapter)
- loader = adapter.nil? ? loaders.private_environment_loader : adapter.loader
- if loader && func = loader.load(:function, name)
- return func.call(scope, *args)
- end
+ # Call via 4x API if the function exists there
+ loaders = scope.compiler.loaders
+ # find the loader that loaded the code, or use the private_environment_loader (sees env + all modules)
+ adapter = Puppet::Pops::Utils.find_adapter(o, Puppet::Pops::Adapters::LoaderAdapter)
+ loader = adapter.nil? ? loaders.private_environment_loader : adapter.loader
+ if loader && func = loader.load(:function, name)
+ return func.call(scope, *args)
end
+ # Call via 3x API if function exists there
fail(Puppet::Pops::Issues::UNKNOWN_FUNCTION, o, {:name => name}) unless Puppet::Parser::Functions.function(name)
- # TODO: if Puppet[:biff] == true, then 3x functions should be called via loaders above
# Arguments must be mapped since functions are unaware of the new and magical creatures in 4x.
- # NOTE: Passing an empty string last converts :undef to empty string
+ # NOTE: Passing an empty string last converts nil/:undef to empty string
mapped_args = args.map {|a| convert(a, scope, '') }
result = scope.send("function_#{name}", mapped_args)
# Prevent non r-value functions from leaking their result (they are not written to care about this)
@@ -256,12 +255,15 @@ module Puppet::Pops::Evaluator::Runtime3Support
file, line = extract_file_line(o)
Puppet::Parser::Resource::Param.new(
:name => name,
+ # Here we must convert nil values to :undef for the 3x logic to work
:value => convert(value, scope, :undef), # converted to 3x since 4x supports additional objects / types
:source => scope.source, :line => line, :file => file,
:add => operator == :'+>'
)
end
+ CLASS_STRING = 'class'.freeze
+
def create_resources(o, scope, virtual, exported, type_name, resource_titles, evaluated_parameters)
# TODO: Unknown resource causes creation of Resource to fail with ArgumentError, should give
@@ -296,7 +298,7 @@ module Puppet::Pops::Evaluator::Runtime3Support
resource.resource_type.instantiate_resource(scope, resource)
end
scope.compiler.add_resource(scope, resource)
- scope.compiler.evaluate_classes([resource_title], scope, false, true) if fully_qualified_type == 'class'
+ scope.compiler.evaluate_classes([resource_title], scope, false, true) if fully_qualified_type == CLASS_STRING
# Turn the resource into a PType (a reference to a resource type)
# weed out nil's
resource_to_ptype(resource)
@@ -317,7 +319,7 @@ module Puppet::Pops::Evaluator::Runtime3Support
# Capitalizes each segment of a qualified name
#
def capitalize_qualified_name(name)
- name.split(/::/).map(&:capitalize).join('::')
+ name.split(/::/).map(&:capitalize).join(NAME_SPACE_SEPARATOR)
end
# Creates resource overrides for all resource type objects in evaluated_resources. The same set of
@@ -332,6 +334,9 @@ module Puppet::Pops::Evaluator::Runtime3Support
file, line = extract_file_line(o)
evaluated_resources.each do |r|
+ unless r.is_a?(Puppet::Pops::Types::PResourceType) && r.type_name != 'class'
+ fail(Puppet::Pops::Issues::ILLEGAL_OVERRIDEN_TYPE, o, {:actual => r} )
+ end
resource = Puppet::Parser::Resource.new(
r.type_name, r.title,
:parameters => evaluated_parameters,
@@ -360,15 +365,28 @@ module Puppet::Pops::Evaluator::Runtime3Support
def get_resource_parameter_value(scope, resource, parameter_name)
# This gets the parameter value, or nil (for both valid parameters and parameters that do not exist).
val = resource[parameter_name]
- if val.nil? && defaults = scope.lookupdefaults(resource.type)
- # NOTE: 3x resource keeps defaults as hash using symbol for name as key to Parameter which (again) holds
- # name and value.
- # NOTE: meta parameters that are unset ends up here, and there are no defaults for those encoded
- # in the defaults, they may receive hardcoded defaults later (e.g. 'tag').
- param = defaults[parameter_name.to_sym]
- # Some parameters (meta parameters like 'tag') does not return a param from which the value can be obtained
- # at all times. Instead, they return a nil param until a value has been set.
- val = param.nil? ? nil : param.value
+
+ # Sometimes the resource is a Puppet::Parser::Resource and sometimes it is
+ # a Puppet::Resource. The Puppet::Resource case occurs when puppet language
+ # is evaluated against an already completed catalog (where all instances of
+ # Puppet::Parser::Resource are converted to Puppet::Resource instances).
+ # Evaluating against an already completed catalog is really only found in
+ # the language specification tests, where the puppet language is used to
+ # test itself.
+ if resource.is_a?(Puppet::Parser::Resource)
+ # The defaults must be looked up in the scope where the resource was created (not in the given
+ # scope where the lookup takes place.
+ resource_scope = resource.scope
+ if val.nil? && resource_scope && defaults = resource_scope.lookupdefaults(resource.type)
+ # NOTE: 3x resource keeps defaults as hash using symbol for name as key to Parameter which (again) holds
+ # name and value.
+ # NOTE: meta parameters that are unset ends up here, and there are no defaults for those encoded
+ # in the defaults, they may receive hardcoded defaults later (e.g. 'tag').
+ param = defaults[parameter_name.to_sym]
+ # Some parameters (meta parameters like 'tag') does not return a param from which the value can be obtained
+ # at all times. Instead, they return a nil param until a value has been set.
+ val = param.nil? ? nil : param.value
+ end
end
val
end
@@ -381,7 +399,8 @@ module Puppet::Pops::Evaluator::Runtime3Support
def resource_to_ptype(resource)
nil if resource.nil?
- type_calculator.infer(resource)
+ # inference returns the meta type since the 3x Resource is an alternate way to describe a type
+ type_calculator.infer(resource).type
end
# This is the same type of "truth" as used in the current Puppet DSL.
@@ -390,8 +409,7 @@ module Puppet::Pops::Evaluator::Runtime3Support
# Is the value true? This allows us to control the definition of truth
# in one place.
case o
- when ''
- false
+ # Support :undef since it may come from a 3x structure
when :undef
false
else
@@ -423,6 +441,11 @@ module Puppet::Pops::Evaluator::Runtime3Support
undef_value
end
+ def convert_String(o, scope, undef_value)
+ # although wasteful, needed because user code may mutate these strings in Resources
+ o.frozen? ? o.dup : o
+ end
+
def convert_Object(o, scope, undef_value)
o
end
@@ -445,46 +468,53 @@ module Puppet::Pops::Evaluator::Runtime3Support
def convert_Symbol(o, scope, undef_value)
case o
+ # Support :undef since it may come from a 3x structure
when :undef
- undef_value # 3x wants :undef as empty string in function
+ undef_value # 3x wants undef as either empty string or :undef
else
o # :default, and all others are verbatim since they are new in future evaluator
end
end
- def convert_PAbstractType(o, scope, undef_value)
+ def convert_PAnyType(o, scope, undef_value)
o
end
- def convert_PResourceType(o,scope, undef_value)
- # Needs conversion by calling scope to resolve the name and possibly return a different name
- # Resolution can only be called with an array, and returns an array. Here there is only one name
- type, titles = scope.resolve_type_and_titles(o.type_name, [o.title])
- # Note: a title of nil makes Resource class throw error with information that is wrong
- Puppet::Resource.new(type, titles[0].nil? ? '' : titles[0] )
- end
+ def convert_PCatalogEntryType(o, scope, undef_value)
+ # Since 4x does not support dynamic scoping, all names are absolute and can be
+ # used as is (with some check/transformation/mangling between absolute/relative form
+ # due to Puppet::Resource's idiosyncratic behavior where some references must be
+ # absolute and others cannot be.
+ # Thus there is no need to call scope.resolve_type_and_titles to do dynamic lookup.
- def convert_PHostClassType(o, scope, undef_value)
- # Needs conversion by calling scope to resolve the name and possibly return a different name
- # Resolution can only be called with an array, and returns an array. Here there is only one name
- type, titles = scope.resolve_type_and_titles('class', [o.class_name])
- # Note: a title of nil makes Resource class throw error with information that is wrong
- Puppet::Resource.new(type, titles[0].nil? ? '' : titles[0] )
+ Puppet::Resource.new(*catalog_type_to_split_type_title(o))
end
private
# Produces an array with [type, title] from a PCatalogEntryType
- # Used to produce reference resource instances (used when 3x is operating on a resource).
+ # This method is used to produce the arguments for creation of reference resource instances
+ # (used when 3x is operating on a resource).
+ # Ensures that resources are *not* absolute.
#
def catalog_type_to_split_type_title(catalog_type)
- case catalog_type
+ split_type = catalog_type.is_a?(Puppet::Pops::Types::PType) ? catalog_type.type : catalog_type
+ case split_type
when Puppet::Pops::Types::PHostClassType
- return ['Class', catalog_type.class_name]
+ class_name = split_type.class_name
+ ['class', class_name.nil? ? nil : class_name.sub(/^::/, '')]
when Puppet::Pops::Types::PResourceType
- return [catalog_type.type_name, catalog_type.title]
+ type_name = split_type.type_name
+ title = split_type.title
+ if type_name =~ /^(::)?[Cc]lass/
+ ['class', title.nil? ? nil : title.sub(/^::/, '')]
+ else
+ # Ensure that title is '' if nil
+ # Resources with absolute name always results in error because tagging does not support leading ::
+ [type_name.nil? ? nil : type_name.sub(/^::/, ''), title.nil? ? '' : title]
+ end
else
- raise ArgumentError, "Cannot split the type #{catalog_type.class}, it is neither a PHostClassType, nor a PResourceClass."
+ raise ArgumentError, "Cannot split the type #{catalog_type.class}, it represents neither a PHostClassType, nor a PResourceType."
end
end
@@ -504,7 +534,6 @@ module Puppet::Pops::Evaluator::Runtime3Support
Puppet::Pops::Validation::DiagnosticProducer.new(
ExceptionRaisingAcceptor.new(), # Raises exception on all issues
SeverityProducer.new(), # All issues are errors
-# Puppet::Pops::Validation::SeverityProducer.new(), # All issues are errors
Puppet::Pops::Model::ModelLabelProvider.new())
end
@@ -521,6 +550,10 @@ module Puppet::Pops::Evaluator::Runtime3Support
else
p[Issues::EMPTY_RESOURCE_SPECIALIZATION] = :ignore
end
+
+ # Store config issues, ignore or warning
+ p[Issues::RT_NO_STORECONFIGS_EXPORT] = Puppet[:storeconfigs] ? :ignore : :warning
+ p[Issues::RT_NO_STORECONFIGS] = Puppet[:storeconfigs] ? :ignore : :warning
end
end
diff --git a/lib/puppet/pops/functions/dispatch.rb b/lib/puppet/pops/functions/dispatch.rb
index 2a6508e08..29a58e036 100644
--- a/lib/puppet/pops/functions/dispatch.rb
+++ b/lib/puppet/pops/functions/dispatch.rb
@@ -51,18 +51,23 @@ class Puppet::Pops::Functions::Dispatch < Puppet::Pops::Evaluator::CallableSigna
if injections.empty?
args
else
- injector = Puppet.lookup(:injector)
+ injector = nil # lazy lookup of injector Puppet.lookup(:injector)
weaving.map do |knit|
if knit.is_a?(Array)
injection_data = @injections[knit[0]]
- # inject
- if injection_data[3] == :producer
+ case injection_data[3]
+ when :dispatcher_internal
+ # currently only supports :scope injection
+ scope
+ when :producer
+ injector ||= Puppet.lookup(:injector)
injector.lookup_producer(scope, injection_data[0], injection_data[2])
else
+ injector ||= Puppet.lookup(:injector)
injector.lookup(scope, injection_data[0], injection_data[2])
end
else
- # pick that argument
+ # pick that argument (injection of static value)
args[knit]
end
end
diff --git a/lib/puppet/pops/functions/dispatcher.rb b/lib/puppet/pops/functions/dispatcher.rb
index a4f912dc4..f15ad373b 100644
--- a/lib/puppet/pops/functions/dispatcher.rb
+++ b/lib/puppet/pops/functions/dispatcher.rb
@@ -6,7 +6,7 @@
class Puppet::Pops::Functions::Dispatcher
attr_reader :dispatchers
-# @api private
+ # @api private
def initialize()
@dispatchers = [ ]
end
@@ -34,7 +34,7 @@ class Puppet::Pops::Functions::Dispatcher
if found
found.invoke(instance, calling_scope, args)
else
- raise ArgumentError, "function '#{instance.class.name}' called with mis-matched arguments\n#{diff_string(instance.class.name, actual)}"
+ raise ArgumentError, "function '#{instance.class.name}' called with mis-matched arguments\n#{Puppet::Pops::Evaluator::CallableMismatchDescriber.diff_string(instance.class.name, actual, @dispatchers)}"
end
end
@@ -67,171 +67,4 @@ class Puppet::Pops::Functions::Dispatcher
def signatures
@dispatchers
end
-
- private
-
- # Produces a string with the difference between the given arguments and support signature(s).
- #
- # @api private
- def diff_string(name, args_type)
- result = [ ]
- if @dispatchers.size < 2
- dispatch = @dispatchers[ 0 ]
- params_type = dispatch.type.param_types
- block_type = dispatch.type.block_type
- params_names = dispatch.param_names
- result << "expected:\n #{name}(#{signature_string(dispatch)}) - #{arg_count_string(dispatch.type)}"
- else
- result << "expected one of:\n"
- result << (@dispatchers.map do |d|
- params_type = d.type.param_types
- " #{name}(#{signature_string(d)}) - #{arg_count_string(d.type)}"
- end.join("\n"))
- end
- result << "\nactual:\n #{name}(#{arg_types_string(args_type)}) - #{arg_count_string(args_type)}"
- result.join('')
- end
-
- # Produces a string for the signature(s)
- #
- # @api private
- def signature_string(dispatch) # args_type, param_names
- param_types = dispatch.type.param_types
- block_type = dispatch.type.block_type
- param_names = dispatch.param_names
-
- from, to = param_types.size_range
- if from == 0 && to == 0
- # No parameters function
- return ''
- end
-
- required_count = from
- # there may be more names than there are types, and count needs to be subtracted from the count
- # to make it correct for the last named element
- adjust = max(0, param_names.size() -1)
- last_range = [max(0, (from - adjust)), (to - adjust)]
-
- types =
- case param_types
- when Puppet::Pops::Types::PTupleType
- param_types.types
- when Puppet::Pops::Types::PArrayType
- [ param_types.element_type ]
- end
- tc = Puppet::Pops::Types::TypeCalculator
-
- # join type with names (types are always present, names are optional)
- # separate entries with comma
- #
- result =
- if param_names.empty?
- types.each_with_index.map {|t, index| tc.string(t) + opt_value_indicator(index, required_count, 0) }
- else
- limit = param_names.size
- result = param_names.each_with_index.map do |name, index|
- [tc.string(types[index] || types[-1]), name].join(' ') + opt_value_indicator(index, required_count, limit)
- end
- end.join(', ')
-
- # Add {from, to} for the last type
- # This works for both Array and Tuple since it describes the allowed count of the "last" type element
- # for both. It does not show anything when the range is {1,1}.
- #
- result += range_string(last_range)
-
- # If there is a block, include it with its own optional count {0,1}
- case dispatch.type.block_type
- when Puppet::Pops::Types::POptionalType
- result << ', ' unless result == ''
- result << "#{tc.string(dispatch.type.block_type.optional_type)} #{dispatch.block_name} {0,1}"
- when Puppet::Pops::Types::PCallableType
- result << ', ' unless result == ''
- result << "#{tc.string(dispatch.type.block_type)} #{dispatch.block_name}"
- when NilClass
- # nothing
- end
- result
- end
-
- # Why oh why Ruby do you not have a standard Math.max ?
- # @api private
- def max(a, b)
- a >= b ? a : b
- end
-
- # @api private
- def opt_value_indicator(index, required_count, limit)
- count = index + 1
- (count > required_count && count < limit) ? '?' : ''
- end
-
- # @api private
- def arg_count_string(args_type)
- if args_type.is_a?(Puppet::Pops::Types::PCallableType)
- size_range = args_type.param_types.size_range # regular parameters
- adjust_range=
- case args_type.block_type
- when Puppet::Pops::Types::POptionalType
- size_range[1] += 1
- when Puppet::Pops::Types::PCallableType
- size_range[0] += 1
- size_range[1] += 1
- when NilClass
- # nothing
- else
- raise ArgumentError, "Internal Error, only nil, Callable, and Optional[Callable] supported by Callable block type"
- end
- else
- size_range = args_type.size_range
- end
- "arg count #{range_string(size_range, false)}"
- end
-
- # @api private
- def arg_types_string(args_type)
- types =
- case args_type
- when Puppet::Pops::Types::PTupleType
- last_range = args_type.repeat_last_range
- args_type.types
- when Puppet::Pops::Types::PArrayType
- last_range = args_type.size_range
- [ args_type.element_type ]
- end
- # stringify generalized versions or it will display Integer[10,10] for "10", String['the content'] etc.
- # note that type must be copied since generalize is a mutating operation
- tc = Puppet::Pops::Types::TypeCalculator
- result = types.map { |t| tc.string(tc.generalize!(t.copy)) }.join(', ')
-
- # Add {from, to} for the last type
- # This works for both Array and Tuple since it describes the allowed count of the "last" type element
- # for both. It does not show anything when the range is {1,1}.
- #
- result += range_string(last_range)
- result
- end
-
- # Formats a range into a string of the form: `{from, to}`
- #
- # The following cases are optimized:
- #
- # * from and to are equal => `{from}`
- # * from and to are both and 1 and squelch_one == true => `''`
- # * from is 0 and to is 1 => `'?'`
- # * to is INFINITY => `{from, }`
- #
- # @api private
- def range_string(size_range, squelch_one = true)
- from, to = size_range
- if from == to
- (squelch_one && from == 1) ? '' : "{#{from}}"
- elsif to == Puppet::Pops::Types::INFINITY
- "{#{from},}"
- elsif from == 0 && to == 1
- '?'
- else
- "{#{from},#{to}}"
- end
- end
end
diff --git a/lib/puppet/pops/issue_reporter.rb b/lib/puppet/pops/issue_reporter.rb
index cac2efcbc..02b173c14 100644
--- a/lib/puppet/pops/issue_reporter.rb
+++ b/lib/puppet/pops/issue_reporter.rb
@@ -1,17 +1,25 @@
class Puppet::Pops::IssueReporter
# @param acceptor [Puppet::Pops::Validation::Acceptor] the acceptor containing reported issues
- # @option options [String] :message (nil) A message text to use as prefix in a single Error message
- # @option options [Boolean] :emit_warnings (false) A message text to use as prefix in a single Error message
- # @option options [Boolean] :emit_errors (true) whether errors should be emitted or only given message
+ # @option options [String] :message (nil) A message text to use as prefix in
+ # a single Error message
+ # @option options [Boolean] :emit_warnings (false) whether warnings should be emitted
+ # @option options [Boolean] :emit_errors (true) whether errors should be
+ # emitted or only the given message
# @option options [Exception] :exception_class (Puppet::ParseError) The exception to raise
#
def self.assert_and_report(acceptor, options)
return unless acceptor
max_errors = Puppet[:max_errors]
- max_warnings = Puppet[:max_warnings] + 1
- max_deprecations = Puppet[:max_deprecations] + 1
+ max_warnings = Puppet[:max_warnings]
+ max_deprecations =
+ if Puppet[:disable_warnings].include?('deprecations')
+ 0
+ else
+ Puppet[:max_deprecations]
+ end
+
emit_warnings = options[:emit_warnings] || false
emit_errors = options[:emit_errors].nil? ? true : !!options[:emit_errors]
emit_message = options[:message]
@@ -35,7 +43,7 @@ class Puppet::Pops::IssueReporter
Puppet.warning(formatter.format(w)) if emitted_w < max_warnings
emitted_w += 1
end
- break if emitted_w > max_warnings && emitted_dw > max_deprecations # but only then
+ break if emitted_w >= max_warnings && emitted_dw >= max_deprecations # but only then
end
end
diff --git a/lib/puppet/pops/issues.rb b/lib/puppet/pops/issues.rb
index e37de30e8..ba53ddc0e 100644
--- a/lib/puppet/pops/issues.rb
+++ b/lib/puppet/pops/issues.rb
@@ -174,17 +174,9 @@ module Puppet::Pops::Issues
"Illegal attempt to assign to the numeric match result variable '$#{varname}'. Numeric variables are not assignable"
end
- APPEND_FAILED = issue :APPEND_FAILED, :message do
- "Append assignment += failed with error: #{message}"
- end
-
- DELETE_FAILED = issue :DELETE_FAILED, :message do
- "'Delete' assignment -= failed with error: #{message}"
- end
-
# parameters cannot have numeric names, clashes with match result variables
ILLEGAL_NUMERIC_PARAMETER = issue :ILLEGAL_NUMERIC_PARAMETER, :name do
- "The numeric parameter name '$#{varname}' cannot be used (clashes with numeric match result variables)"
+ "The numeric parameter name '$#{name}' cannot be used (clashes with numeric match result variables)"
end
# In certain versions of Puppet it may be allowed to assign to a not already assigned key
@@ -202,9 +194,20 @@ module Puppet::Pops::Issues
"Illegal attempt to assign to #{label.a_an(semantic)} via [index/key]. Not an assignable reference"
end
- # For unsupported operators (e.g. -= in puppet 3).
+ APPENDS_DELETES_NO_LONGER_SUPPORTED = hard_issue :APPENDS_DELETES_NO_LONGER_SUPPORTED, :operator do
+ "The operator '#{operator}' is no longer supported. See http://links.puppetlabs.com/remove-plus-equals"
+ end
+
+ # For unsupported operators (e.g. += and -= in puppet 4).
#
UNSUPPORTED_OPERATOR = hard_issue :UNSUPPORTED_OPERATOR, :operator do
+ "The operator '#{operator}' is not supported."
+ end
+
+ # For operators that are not supported in specific contexts (e.g. '* =>' in
+ # resource defaults)
+ #
+ UNSUPPORTED_OPERATOR_IN_CONTEXT = hard_issue :UNSUPPORTED_OPERATOR_IN_CONTEXT, :operator do
"The operator '#{operator}' in #{label.a_an(semantic)} is not supported."
end
@@ -292,8 +295,7 @@ module Puppet::Pops::Issues
"Illegal expression. #{label.a_an_uc(semantic)} is unacceptable as #{feature} in #{label.a_an(container)}"
end
- # Issues when an expression is used where it is not legal.
- # E.g. an arithmetic expression where a hostname is expected.
+ # Issues when a variable is not a NAME
#
ILLEGAL_VARIABLE_EXPRESSION = hard_issue :ILLEGAL_VARIABLE_EXPRESSION do
"Illegal variable expression. #{label.a_an_uc(semantic)} did not produce a variable name (String or Numeric)."
@@ -319,10 +321,6 @@ module Puppet::Pops::Issues
"Attempt to use unsupported range in #{label.a_an(semantic)}, #{count} values given for max 1"
end
- DEPRECATED_NAME_AS_TYPE = issue :DEPRECATED_NAME_AS_TYPE, :name do
- "Resource references should now be capitalized. The given '#{name}' does not have the correct form"
- end
-
ILLEGAL_RELATIONSHIP_OPERAND_TYPE = issue :ILLEGAL_RELATIONSHIP_OPERAND_TYPE, :operand do
"Illegal relationship operand, can not form a relationship with #{label.a_an(operand)}. A Catalog type is required."
end
@@ -411,15 +409,24 @@ module Puppet::Pops::Issues
"Illegal Class name in class reference. #{label.a_an_uc(name)} cannot be used where a String is expected"
end
- # Issues when an expression is used where it is not legal.
- # E.g. an arithmetic expression where a hostname is expected.
- #
ILLEGAL_DEFINITION_NAME = hard_issue :ILLEGAL_DEFINTION_NAME, :name do
"Unacceptable name. The name '#{name}' is unacceptable as the name of #{label.a_an(semantic)}"
end
- NON_NAMESPACED_FUNCTION = hard_issue :NON_NAMESPACED_FUNCTION, :name do
- "A Puppet Function must be defined within a module name-space. The name '#{name}' is unacceptable."
+ CAPTURES_REST_NOT_LAST = hard_issue :CAPTURES_REST_NOT_LAST, :param_name do
+ "Parameter $#{param_name} is not last, and has 'captures rest'"
+ end
+
+ CAPTURES_REST_NOT_SUPPORTED = hard_issue :CAPTURES_REST_NOT_SUPPORTED, :container, :param_name do
+ "Parameter $#{param_name} has 'captures rest' - not supported in #{label.a_an(container)}"
+ end
+
+ REQUIRED_PARAMETER_AFTER_OPTIONAL = hard_issue :REQUIRED_PARAMETER_AFTER_OPTIONAL, :param_name do
+ "Parameter $#{param_name} is required but appears after optional parameters"
+ end
+
+ MISSING_REQUIRED_PARAMETER = hard_issue :MISSING_REQUIRED_PARAMETER, :param_name do
+ "Parameter $#{param_name} is required but no value was given"
end
NOT_NUMERIC = issue :NOT_NUMERIC, :value do
@@ -442,6 +449,34 @@ module Puppet::Pops::Issues
"Resource type not found: #{type_name.capitalize}"
end
+ ILLEGAL_RESOURCE_TYPE = hard_issue :ILLEGAL_RESOURCE_TYPE, :actual do
+ "Illegal Resource Type expression, expected result to be a type name, or untitled Resource, got #{actual}"
+ end
+
+ DUPLICATE_TITLE = issue :DUPLICATE_TITLE, :title do
+ "The title '#{title}' has already been used in this resource expression"
+ end
+
+ DUPLICATE_ATTRIBUTE = issue :DUPLICATE_ATTRIBUE, :attribute do
+ "The attribute '#{attribute}' has already been set in this resource body"
+ end
+
+ MISSING_TITLE = hard_issue :MISSING_TITLE do
+ "Missing title. The title expression resulted in undef"
+ end
+
+ MISSING_TITLE_AT = hard_issue :MISSING_TITLE_AT, :index do
+ "Missing title at index #{index}. The title expression resulted in an undef title"
+ end
+
+ ILLEGAL_TITLE_TYPE_AT = hard_issue :ILLEGAL_TITLE_TYPE_AT, :index, :actual do
+ "Illegal title type at index #{index}. Expected String, got #{actual}"
+ end
+
+ EMPTY_STRING_TITLE_AT = hard_issue :EMPTY_STRING_TITLE_AT, :index do
+ "Empty string title at #{index}. Title strings must have a length greater than zero."
+ end
+
UNKNOWN_RESOURCE = issue :UNKNOWN_RESOURCE, :type_name, :title do
"Resource not found: #{type_name.capitalize}['#{title}']"
end
@@ -470,4 +505,44 @@ module Puppet::Pops::Issues
DISCONTINUED_IMPORT = hard_issue :DISCONTINUED_IMPORT do
"Use of 'import' has been discontinued in favor of a manifest directory. See http://links.puppetlabs.com/puppet-import-deprecation"
end
+
+ IDEM_EXPRESSION_NOT_LAST = issue :IDEM_EXPRESSION_NOT_LAST do
+ "This #{label.label(semantic)} is not productive. A non productive construct may only be placed last in a block/sequence"
+ end
+
+ IDEM_NOT_ALLOWED_LAST = hard_issue :IDEM_NOT_ALLOWED_LAST, :container do
+ "This #{label.label(semantic)} is not productive. #{label.a_an_uc(container)} can not end with a non productive construct"
+ end
+
+ RESERVED_WORD = hard_issue :RESERVED_WORD, :word do
+ "Use of reserved word: #{word}, must be quoted if intended to be a String value"
+ end
+
+ RESERVED_TYPE_NAME = hard_issue :RESERVED_TYPE_NAME, :name do
+ "The name: '#{name}' is already defined by Puppet and can not be used as the name of #{label.a_an(semantic)}."
+ end
+
+ UNMATCHED_SELECTOR = hard_issue :UNMATCHED_SELECTOR, :param_value do
+ "No matching entry for selector parameter with value '#{param_value}'"
+ end
+
+ ILLEGAL_NODE_INHERITANCE = issue :ILLEGAL_NODE_INHERITANCE do
+ "Node inheritance is not supported in Puppet >= 4.0.0. See http://links.puppetlabs.com/puppet-node-inheritance-deprecation"
+ end
+
+ ILLEGAL_OVERRIDEN_TYPE = issue :ILLEGAL_OVERRIDEN_TYPE, :actual do
+ "Resource Override can only operate on resources, got: #{label.label(actual)}"
+ end
+
+ RESERVED_PARAMETER = hard_issue :RESERVED_PARAMETER, :container, :param_name do
+ "The parameter $#{param_name} redefines a built in parameter in #{label.the(container)}"
+ end
+
+ TYPE_MISMATCH = hard_issue :TYPE_MISMATCH, :expected, :actual do
+ "Expected value of type #{expected}, got #{actual}"
+ end
+
+ MULTIPLE_ATTRIBUTES_UNFOLD = hard_issue :MULTIPLE_ATTRIBUTES_UNFOLD do
+ "Unfolding of attributes from Hash can only be used once per resource body"
+ end
end
diff --git a/lib/puppet/pops/loader/base_loader.rb b/lib/puppet/pops/loader/base_loader.rb
index f7b34ece7..cd916865b 100644
--- a/lib/puppet/pops/loader/base_loader.rb
+++ b/lib/puppet/pops/loader/base_loader.rb
@@ -65,7 +65,7 @@ class Puppet::Pops::Loader::BaseLoader < Puppet::Pops::Loader::Loader
#
def promote_entry(named_entry)
typed_name = named_entry.typed_name
- if entry = @named_values[typed_name] then fail_redefined(entry); end
+ if entry = @named_values[typed_name] then fail_redefine(entry); end
@named_values[typed_name] = named_entry
end
@@ -73,7 +73,7 @@ class Puppet::Pops::Loader::BaseLoader < Puppet::Pops::Loader::Loader
def fail_redefine(entry)
origin_info = entry.origin ? " Originally set at #{origin_label(entry.origin)}." : "unknown location"
- raise ArgumentError, "Attempt to redefine entity '#{entry.typed_name}' originally set at #{origin_label(origin)}.#{origin_info}"
+ raise ArgumentError, "Attempt to redefine entity '#{entry.typed_name}' originally set at #{origin_info}"
end
# TODO: Should not really be here?? - TODO: A Label provider ? semantics for the URI?
@@ -84,7 +84,7 @@ class Puppet::Pops::Loader::BaseLoader < Puppet::Pops::Loader::Loader
elsif origin.respond_to?(:uri)
origin.uri.to_s
else
- nil
+ origin
end
end
diff --git a/lib/puppet/pops/loader/loader.rb b/lib/puppet/pops/loader/loader.rb
index 256fc373e..37c912c2d 100644
--- a/lib/puppet/pops/loader/loader.rb
+++ b/lib/puppet/pops/loader/loader.rb
@@ -127,7 +127,7 @@ class Puppet::Pops::Loader::Loader
attr_reader :origin
def initialize(typed_name, value, origin)
- @name = typed_name
+ @typed_name = typed_name
@value = value
@origin = origin
freeze()
diff --git a/lib/puppet/pops/loader/loader_paths.rb b/lib/puppet/pops/loader/loader_paths.rb
index a431a4801..09bb7e5b0 100644
--- a/lib/puppet/pops/loader/loader_paths.rb
+++ b/lib/puppet/pops/loader/loader_paths.rb
@@ -11,18 +11,11 @@ module Puppet::Pops::Loader::LoaderPaths
# and existence checks). The smart paths in the array appear in precedence order. The returned array may be
# mutated.
#
- def self.relative_paths_for_type(type, loader) #, start_index_in_name)
+ def self.relative_paths_for_type(type, loader)
result =
- case type # typed_name.type
+ case type
when :function
- if Puppet[:biff] == true
- [FunctionPath4x.new(loader), FunctionPath3x.new(loader)]
- else
[FunctionPath4x.new(loader)]
- end
-
- # when :xxx # TODO: Add all other types
-
else
# unknown types, simply produce an empty result; no paths to check, nothing to find... move along...
[]
@@ -93,18 +86,6 @@ module Puppet::Pops::Loader::LoaderPaths
end
end
- class FunctionPath3x < RubySmartPath
- FUNCTION_PATH_3X = File.join('lib', 'puppet', 'parser', 'functions')
-
- def relative_path
- FUNCTION_PATH_3X
- end
-
- def instantiator()
- Puppet::Pops::Loader::RubyLegacyFunctionInstantiator
- end
- end
-
# SmartPaths
# ===
# Holds effective SmartPath instances per type
diff --git a/lib/puppet/pops/loader/ruby_function_instantiator.rb b/lib/puppet/pops/loader/ruby_function_instantiator.rb
index 6c2b716cf..de372ab27 100644
--- a/lib/puppet/pops/loader/ruby_function_instantiator.rb
+++ b/lib/puppet/pops/loader/ruby_function_instantiator.rb
@@ -16,7 +16,7 @@ class Puppet::Pops::Loader::RubyFunctionInstantiator
unless ruby_code_string.is_a?(String) && ruby_code_string =~ /Puppet\:\:Functions\.create_function/
raise ArgumentError, "The code loaded from #{source_ref} does not seem to be a Puppet 4x API function - no create_function call."
end
- created = eval(ruby_code_string)
+ created = eval(ruby_code_string, nil, source_ref, 1)
unless created.is_a?(Class)
raise ArgumentError, "The code loaded from #{source_ref} did not produce a Function class when evaluated. Got '#{created.class}'"
end
diff --git a/lib/puppet/pops/loader/ruby_legacy_function_instantiator.rb b/lib/puppet/pops/loader/ruby_legacy_function_instantiator.rb
deleted file mode 100644
index 589b05c05..000000000
--- a/lib/puppet/pops/loader/ruby_legacy_function_instantiator.rb
+++ /dev/null
@@ -1,109 +0,0 @@
-# The RubyLegacyFunctionInstantiator loads a 3x function and turns it into a 4x function
-# that is called with 3x semantics (values are transformed to be 3x compliant).
-#
-# The code is loaded from a string obtained by reading the 3x function ruby code into a string
-# and then passing it to the loaders class method `create`. When Puppet[:biff] == true, the
-# 3x Puppet::Parser::Function.newfunction method relays back to this function loader's
-# class method legacy_newfunction which creates a Puppet::Functions class wrapping the
-# 3x function's block into a method in a function class derived from Puppet::Function.
-# This class is then returned, and the Legacy loader continues the same way as it does
-# for a 4x function.
-#
-# TODO: Wrapping of Scope
-# The 3x function expects itself to be Scope. It passes itself as scope to other parts of the runtime,
-# it expects to find all sorts of information in itself, get/set variables, get compiler, get environment
-# etc.
-# TODO: Transformation of arguments to 3x compliant objects
-#
-class Puppet::Pops::Loader::RubyLegacyFunctionInstantiator
-
- # Produces an instance of the Function class with the given typed_name, or fails with an error if the
- # given ruby source does not produce this instance when evaluated.
- #
- # @param loader [Puppet::Pops::Loader::Loader] The loader the function is associated with
- # @param typed_name [Puppet::Pops::Loader::TypedName] the type / name of the function to load
- # @param source_ref [URI, String] a reference to the source / origin of the ruby code to evaluate
- # @param ruby_code_string [String] ruby code in a string
- #
- # @return [Puppet::Pops::Functions.Function] - an instantiated function with global scope closure associated with the given loader
- #
- def self.create(loader, typed_name, source_ref, ruby_code_string)
- # Old Ruby API supports calling a method via ::
- # this must also be checked as well as call with '.'
- #
- unless ruby_code_string.is_a?(String) && ruby_code_string =~ /Puppet\:\:Parser\:\:Functions(?:\.|\:\:)newfunction/
- raise ArgumentError, "The code loaded from #{source_ref} does not seem to be a Puppet 3x API function - no newfunction call."
- end
-
- # The evaluation of the 3x function creation source should result in a call to the legacy_newfunction
- #
- created = eval(ruby_code_string)
- unless created.is_a?(Class)
- raise ArgumentError, "The code loaded from #{source_ref} did not produce a Function class when evaluated. Got '#{created.class}'"
- end
- unless created.name.to_s == typed_name.name()
- raise ArgumentError, "The code loaded from #{source_ref} produced mis-matched name, expected '#{typed_name.name}', got #{created.name}"
- end
- # create the function instance - it needs closure (scope), and loader (i.e. where it should start searching for things
- # when calling functions etc.
- # It should be bound to global scope
-
- # TODO: Cheating wrt. scope - assuming it is found in the context
- closure_scope = Puppet.lookup(:global_scope) { {} }
- created.new(closure_scope, loader)
- end
-
- # This is a new implementation of the method that is used in 3x to create a function.
- # The arguments are the same as those passed to Puppet::Parser::Functions.newfunction, hence its
- # deviation from regular method naming practice.
- #
- def self.legacy_newfunction(name, options, &block)
-
- # 3x api allows arity to be specified, if unspecified it is 0 or more arguments
- # arity >= 0, is an exact count
- # airty < 0 is the number of required arguments -1 (i.e. -1 is 0 or more)
- # (there is no upper cap, there is no support for optional values, or defaults)
- #
- arity = options[:arity] || -1
- if arity >= 0
- min_arg_count = arity
- max_arg_count = arity
- else
- min_arg_count = (arity + 1).abs
- # infinity
- max_arg_count = :default
- end
-
- # Create a 4x function wrapper around the 3x Function
- created_function_class = Puppet::Functions.create_function(name) do
- # define a method on the new Function class with the same name as the function, but
- # padded with __ because the function may represent a ruby method with the same name that
- # expects to have inherited from Kernel, and then Object.
- # (This can otherwise lead to infinite recursion, or that an ArgumentError is raised).
- #
- __name__ = :"__#{name}__"
- define_method(__name__, &block)
-
- # Define the method that is called from dispatch - this method just changes a call
- # with multiple unknown arguments to passing all in an array (since this is expected in the 3x API).
- # We want the call to be checked for type and number of arguments so cannot call the function
- # defined by the block directly since it is defined to take a single argument.
- #
- define_method(:__relay__call__) do |*args|
- # dup the args since the function may destroy them
- # TODO: Should convert arguments to 3x, now :undef is send to the function
- send(__name__, args.dup)
- end
-
- # Define a dispatch that performs argument type/count checking
- #
- dispatch :__relay__call__ do
- # Use Puppet Type Object (not Optional[Object] since the 3x API passes undef as empty string).
- param 'Object', 'args'
- # Specify arg count (transformed from 3x function arity specification).
- arg_count(min_arg_count, max_arg_count)
- end
- end
- created_function_class
- end
-end
diff --git a/lib/puppet/pops/loader/static_loader.rb b/lib/puppet/pops/loader/static_loader.rb
index 27cfbe462..50b6a15f9 100644
--- a/lib/puppet/pops/loader/static_loader.rb
+++ b/lib/puppet/pops/loader/static_loader.rb
@@ -49,14 +49,24 @@ class Puppet::Pops::Loader::StaticLoader < Puppet::Pops::Loader::Loader
# Logs per the specified level, outputs formatted information for arrays, hashes etc.
# Overrides the implementation in Function that uses dispatching. This is not needed here
- # since it accepts 0-n Optional[Object]
+ # since it accepts 0-n Object.
#
define_method(:call) do |scope, *vals|
# NOTE: 3x, does this: vals.join(" ")
# New implementation uses the evaluator to get proper formatting per type
# TODO: uses a fake scope (nil) - fix when :scopes are available via settings
mapped = vals.map {|v| Puppet::Pops::Evaluator::EvaluatorImpl.new.string(v, nil) }
- Puppet.send(level, mapped.join(" "))
+
+ # Bypass Puppet.<level> call since it picks up source from "self" which is not applicable in the 4x
+ # Function API.
+ # TODO: When a function can obtain the file, line, pos of the call merge those in (3x supports
+ # options :file, :line. (These were never output when calling the 3x logging functions since
+ # 3x scope does not know about the calling location at that detailed level, nor do they
+ # appear in a report to stdout/error when included). Now, the output simply uses scope (like 3x)
+ # as this is good enough, but does not reflect the true call-stack, but is a rough estimate
+ # of where the logging call originates from).
+ #
+ Puppet::Util::Log.create({:level => level, :source => scope, :message => mapped.join(" ")})
end
end
diff --git a/lib/puppet/pops/model/ast_transformer.rb b/lib/puppet/pops/model/ast_transformer.rb
index 4016cc266..2898534dd 100644
--- a/lib/puppet/pops/model/ast_transformer.rb
+++ b/lib/puppet/pops/model/ast_transformer.rb
@@ -519,23 +519,7 @@ class Puppet::Pops::Model::AstTransformer
# the "titles" to be an ASTArray.
#
def transform_ResourceOverrideExpression(o)
- resource_ref = o.resources
- raise "Unacceptable expression for resource override" unless resource_ref.is_a? Model::AccessExpression
-
- type = case resource_ref.left_expr
- when Model::QualifiedName
- # This is deprecated "Resource references should now be capitalized" - this is caught elsewhere
- resource_ref.left_expr.value
- when Model::QualifiedReference
- resource_ref.left_expr.value
- else
- raise "Unacceptable expression for resource override; need NAME or CLASSREF"
- end
-
- result_ref = ast o, AST::ResourceReference, :type => type, :title => transform(resource_ref.keys)
-
- # title is one or more expressions, if more than one it should be an ASTArray
- ast o, AST::ResourceOverride, :object => result_ref, :parameters => transform(o.operations)
+ raise "Unsupported transformation - use the new evaluator"
end
# Parameter is a parameter in a definition of some kind.
@@ -614,22 +598,18 @@ class Puppet::Pops::Model::AstTransformer
end
def transform_ResourceBody(o)
- # expects AST, AST::ASTArray of AST
- ast o, AST::ResourceInstance, :title => transform(o.title), :parameters => transform(o.operations)
+ raise "Unsupported transformation - use the new evaluator"
end
def transform_ResourceDefaultsExpression(o)
- ast o, AST::ResourceDefaults, :type => o.type_ref.value, :parameters => transform(o.operations)
+ raise "Unsupported transformation - use the new evaluator"
end
# Transformation of ResourceExpression requires calling a method on the resulting
# AST::Resource if it is virtual or exported
#
def transform_ResourceExpression(o)
- raise "Unacceptable type name expression" unless o.type_name.is_a? Model::QualifiedName
- resource = ast o, AST::Resource, :type => o.type_name.value, :instances => transform(o.bodies)
- resource.send("#{o.form}=", true) unless o.form == :regular
- resource
+ raise "Unsupported transformation - use the new evaluator"
end
# Transformation of SelectorExpression is limited to certain types of expressions.
diff --git a/lib/puppet/pops/model/factory.rb b/lib/puppet/pops/model/factory.rb
index 43eab49a3..cf77d9185 100644
--- a/lib/puppet/pops/model/factory.rb
+++ b/lib/puppet/pops/model/factory.rb
@@ -70,6 +70,11 @@ class Puppet::Pops::Model::Factory
o
end
+ def build_AttributesOperation(o, value)
+ o.expr = build(value)
+ o
+ end
+
def build_AccessExpression(o, left, *keys)
o.left_expr = to_ops(left)
keys.each {|expr| o.addKeys(to_ops(expr)) }
@@ -146,6 +151,11 @@ class Puppet::Pops::Model::Factory
o
end
+ def build_ReservedWord(o, name)
+ o.word = name
+ o
+ end
+
def build_KeyedEntry(o, k, v)
o.key = to_ops(k)
o.value = to_ops(v)
@@ -329,6 +339,10 @@ class Puppet::Pops::Model::Factory
o
end
+ def build_TokenValue(o)
+ raise "Factory can not deal with a Lexer Token. Got token: #{o}. Probably caused by wrong index in grammar val[n]."
+ end
+
# Puppet::Pops::Model::Factory helpers
def f_build_unary(klazz, expr)
Puppet::Pops::Model::Factory.new(build(klazz.new, expr))
@@ -369,6 +383,8 @@ class Puppet::Pops::Model::Factory
def minus(); f_build_unary(Model::UnaryMinusExpression, self); end
+ def unfold(); f_build_unary(Model::UnfoldExpression, self); end
+
def text(); f_build_unary(Model::TextExpression, self); end
def var(); f_build_unary(Model::VariableExpression, self); end
@@ -483,7 +499,18 @@ class Puppet::Pops::Model::Factory
Puppet::Pops::Adapters::SourcePosAdapter.adapt(current)
end
- # Returns symbolic information about an expected share of a resource expression given the LHS of a resource expr.
+ # Sets the form of the resource expression (:regular (the default), :virtual, or :exported).
+ # Produces true if the expression was a resource expression, false otherwise.
+ #
+ def self.set_resource_form(expr, form)
+ expr = expr.current if expr.is_a?(Puppet::Pops::Model::Factory)
+ # Note: Validation handles illegal combinations
+ return false unless expr.is_a?(Puppet::Pops::Model::AbstractResource)
+ expr.form = form
+ return true
+ end
+
+ # Returns symbolic information about an expected shape of a resource expression given the LHS of a resource expr.
#
# * `name { }` => `:resource`, create a resource of the given type
# * `Name { }` => ':defaults`, set defaults for the referenced type
@@ -498,7 +525,12 @@ class Puppet::Pops::Model::Factory
when Model::QualifiedReference
:defaults
when Model::AccessExpression
- :override
+ # if Resource[e], then it is not resource specific
+ if expr.left_expr.is_a?(Model::QualifiedReference) && expr.left_expr.value == 'resource' && expr.keys.size == 1
+ :defaults
+ else
+ :override
+ end
when 'class'
:class
else
@@ -511,6 +543,8 @@ class Puppet::Pops::Model::Factory
def self.minus(o); new(o).minus; end
+ def self.unfold(o); new(o).unfold; end
+
def self.var(o); new(o).var; end
def self.block(*args); new(Model::BlockExpression, *args); end
@@ -539,7 +573,6 @@ class Puppet::Pops::Model::Factory
def self.HASH(entries); new(Model::LiteralHash, *entries); end
- # TODO_HEREDOC
def self.HEREDOC(name, expr); new(Model::HeredocExpression, name, expr); end
def self.SUBLOCATE(token, expr) new(Model::SubLocatedExpression, token, expr); end
@@ -550,6 +583,19 @@ class Puppet::Pops::Model::Factory
def self.NODE(hosts, parent, body); new(Model::NodeDefinition, hosts, parent, body); end
+
+ # Parameters
+
+ # Mark parameter as capturing the rest of arguments
+ def captures_rest()
+ current.captures_rest = true
+ end
+
+ # Set Expression that should evaluate to the parameter's type
+ def type_expr(o)
+ current.type_expr = to_ops(o)
+ end
+
# Creates a QualifiedName representation of o, unless o already represents a QualifiedName in which
# case it is returned.
#
@@ -582,13 +628,18 @@ class Puppet::Pops::Model::Factory
end
def self.EPP(parameters, body)
- see_scope = false
- params = parameters
if parameters.nil?
params = []
- see_scope = true
+ parameters_specified = false
+ else
+ params = parameters
+ parameters_specified = true
end
- LAMBDA(params, new(Model::EppExpression, see_scope, body))
+ LAMBDA(params, new(Model::EppExpression, parameters_specified, body))
+ end
+
+ def self.RESERVED(name)
+ new(Model::ReservedWord, name)
end
# TODO: This is the same a fqn factory method, don't know if callers to fqn and QNAME can live with the
@@ -643,6 +694,10 @@ class Puppet::Pops::Model::Factory
new(Model::AttributeOperation, name, op, expr)
end
+ def self.ATTRIBUTES_OP(expr)
+ new(Model::AttributesOperation, expr)
+ end
+
def self.CALL_NAMED(name, rval_required, argument_list)
unless name.kind_of?(Model::PopsObject)
name = Puppet::Pops::Model::Factory.fqn(name) unless name.is_a?(Puppet::Pops::Model::Factory)
@@ -712,6 +767,7 @@ class Puppet::Pops::Model::Factory
'realize' => true,
'include' => true,
'contain' => true,
+ 'tag' => true,
'debug' => true,
'info' => true,
@@ -763,8 +819,9 @@ class Puppet::Pops::Model::Factory
# Transforms a left expression followed by an untitled resource (in the form of attribute_operations)
# @param left [Factory, Expression] the lhs followed what may be a hash
def self.transform_resource_wo_title(left, attribute_ops)
+ # Returning nil means accepting the given as a potential resource expression
return nil unless attribute_ops.is_a? Array
-# return nil if attribute_ops.find { |ao| ao.operator == :'+>' }
+ return nil unless left.current.is_a?(Puppet::Pops::Model::QualifiedName)
keyed_entries = attribute_ops.map do |ao|
return nil if ao.operator == :'+>'
KEY_ENTRY(ao.attribute_name, ao.value_expr)
@@ -819,8 +876,8 @@ class Puppet::Pops::Model::Factory
x
end
- def build_EppExpression(o, see_scope, body)
- o.see_scope = see_scope
+ def build_EppExpression(o, parameters_specified, body)
+ o.parameters_specified = parameters_specified
b = f_build_body(body)
o.body = b.current if b
o
@@ -833,6 +890,7 @@ class Puppet::Pops::Model::Factory
# Creates a String literal, unless the symbol is one of the special :undef, or :default
# which instead creates a LiterlUndef, or a LiteralDefault.
+ # Supports :undef because nil creates a no-op instruction.
def build_Symbol(o)
case o
when :undef
@@ -975,4 +1033,8 @@ class Puppet::Pops::Model::Factory
end
end.join(''))
end
+
+ def to_s
+ Puppet::Pops::Model::ModelTreeDumper.new.dump(self)
+ end
end
diff --git a/lib/puppet/pops/model/model.rb b/lib/puppet/pops/model/model.rb
index b186aca3f..9e2a079b4 100644
--- a/lib/puppet/pops/model/model.rb
+++ b/lib/puppet/pops/model/model.rb
@@ -1,606 +1,114 @@
#
-# The Puppet Pops Metamodel
+# The Puppet Pops Metamodel Implementation
#
-# This module contains a formal description of the Puppet Pops (*P*uppet *OP*eration instruction*S*).
-# It describes a Metamodel containing DSL instructions, a description of PuppetType and related
-# classes needed to evaluate puppet logic.
-# The metamodel resembles the existing AST model, but it is a semantic model of instructions and
-# the types that they operate on rather than an Abstract Syntax Tree, although closely related.
-#
-# The metamodel is anemic (has no behavior) except basic datatype and type
-# assertions and reference/containment assertions.
-# The metamodel is also a generalized description of the Puppet DSL to enable the
-# same metamodel to be used to express Puppet DSL models (instances) with different semantics as
-# the language evolves.
-#
-# The metamodel is concretized by a validator for a particular version of
-# the Puppet DSL language.
-#
-# This metamodel is expressed using RGen.
+# The Puppet Pops Metamodel consists of two parts; the metamodel expressed with RGen in model_meta.rb,
+# and this file which mixes in implementation details.
#
require 'rgen/metamodel_builder'
+require 'rgen/ecore/ecore'
+require 'rgen/ecore/ecore_ext'
+require 'rgen/ecore/ecore_to_ruby'
-module Puppet::Pops::Model
- extend RGen::MetamodelBuilder::ModuleExtension
-
- # A base class for modeled objects that makes them Visitable, and Adaptable.
- #
- class PopsObject < RGen::MetamodelBuilder::MMBase
- include Puppet::Pops::Visitable
- include Puppet::Pops::Adaptable
- include Puppet::Pops::Containment
- abstract
- end
-
- # A Positioned object has an offset measured in an opaque unit (representing characters) from the start
- # of a source text (starting
- # from 0), and a length measured in the same opaque unit. The resolution of the opaque unit requires the
- # aid of a Locator instance that knows about the measure. This information is stored in the model's
- # root node - a Program.
- #
- # The offset and length are optional if the source of the model is not from parsed text.
- #
- class Positioned < PopsObject
- abstract
- has_attr 'offset', Integer
- has_attr 'length', Integer
- end
-
- # @abstract base class for expressions
- class Expression < Positioned
- abstract
- end
-
- # A Nop - the "no op" expression.
- # @note not really needed since the evaluator can evaluate nil with the meaning of NoOp
- # @todo deprecate? May be useful if there is the need to differentiate between nil and Nop when transforming model.
- #
- class Nop < Expression
- end
-
- # A binary expression is abstract and has a left and a right expression. The order of evaluation
- # and semantics are determined by the concrete subclass.
- #
- class BinaryExpression < Expression
- abstract
- #
- # @!attribute [rw] left_expr
- # @return [Expression]
- contains_one_uni 'left_expr', Expression, :lowerBound => 1
- contains_one_uni 'right_expr', Expression, :lowerBound => 1
- end
-
- # An unary expression is abstract and contains one expression. The semantics are determined by
- # a concrete subclass.
- #
- class UnaryExpression < Expression
- abstract
- contains_one_uni 'expr', Expression, :lowerBound => 1
- end
-
- # A class that simply evaluates to the contained expression.
- # It is of value in order to preserve user entered parentheses in transformations, and
- # transformations from model to source.
- #
- class ParenthesizedExpression < UnaryExpression; end
-
- # A boolean not expression, reversing the truth of the unary expr.
- #
- class NotExpression < UnaryExpression; end
-
- # An arithmetic expression reversing the polarity of the numeric unary expr.
- #
- class UnaryMinusExpression < UnaryExpression; end
-
- # An assignment expression assigns a value to the lval() of the left_expr.
- #
- class AssignmentExpression < BinaryExpression
- has_attr 'operator', RGen::MetamodelBuilder::DataTypes::Enum.new([:'=', :'+=', :'-=']), :lowerBound => 1
- end
-
- # An arithmetic expression applies an arithmetic operator on left and right expressions.
- #
- class ArithmeticExpression < BinaryExpression
- has_attr 'operator', RGen::MetamodelBuilder::DataTypes::Enum.new([:'+', :'-', :'*', :'%', :'/', :'<<', :'>>' ]), :lowerBound => 1
- end
-
- # A relationship expression associates the left and right expressions
- #
- class RelationshipExpression < BinaryExpression
- has_attr 'operator', RGen::MetamodelBuilder::DataTypes::Enum.new([:'->', :'<-', :'~>', :'<~']), :lowerBound => 1
- end
-
- # A binary expression, that accesses the value denoted by right in left. i.e. typically
- # expressed concretely in a language as left[right].
- #
- class AccessExpression < Expression
- contains_one_uni 'left_expr', Expression, :lowerBound => 1
- contains_many_uni 'keys', Expression, :lowerBound => 1
- end
-
- # A comparison expression compares left and right using a comparison operator.
- #
- class ComparisonExpression < BinaryExpression
- has_attr 'operator', RGen::MetamodelBuilder::DataTypes::Enum.new([:'==', :'!=', :'<', :'>', :'<=', :'>=' ]), :lowerBound => 1
- end
-
- # A match expression matches left and right using a matching operator.
- #
- class MatchExpression < BinaryExpression
- has_attr 'operator', RGen::MetamodelBuilder::DataTypes::Enum.new([:'!~', :'=~']), :lowerBound => 1
- end
-
- # An 'in' expression checks if left is 'in' right
- #
- class InExpression < BinaryExpression; end
-
- # A boolean expression applies a logical connective operator (and, or) to left and right expressions.
- #
- class BooleanExpression < BinaryExpression
- abstract
- end
-
- # An and expression applies the logical connective operator and to left and right expression
- # and does not evaluate the right expression if the left expression is false.
- #
- class AndExpression < BooleanExpression; end
-
- # An or expression applies the logical connective operator or to the left and right expression
- # and does not evaluate the right expression if the left expression is true
- #
- class OrExpression < BooleanExpression; end
-
- # A literal list / array containing 0:M expressions.
- #
- class LiteralList < Expression
- contains_many_uni 'values', Expression
- end
-
- # A Keyed entry has a key and a value expression. It is typically used as an entry in a Hash.
- #
- class KeyedEntry < Positioned
- contains_one_uni 'key', Expression, :lowerBound => 1
- contains_one_uni 'value', Expression, :lowerBound => 1
- end
-
- # A literal hash is a collection of KeyedEntry objects
- #
- class LiteralHash < Expression
- contains_many_uni 'entries', KeyedEntry
- end
-
- # A block contains a list of expressions
- #
- class BlockExpression < Expression
- contains_many_uni 'statements', Expression
- end
+module Puppet::Pops
+ require 'puppet/pops/model/model_meta'
- # A case option entry in a CaseStatement
- #
- class CaseOption < Expression
- contains_many_uni 'values', Expression, :lowerBound => 1
- contains_one_uni 'then_expr', Expression, :lowerBound => 1
- end
+ # TODO: See PUP-2978 for possible performance optimization
- # A case expression has a test, a list of options (multi values => block map).
- # One CaseOption may contain a LiteralDefault as value. This option will be picked if nothing
- # else matched.
- #
- class CaseExpression < Expression
- contains_one_uni 'test', Expression, :lowerBound => 1
- contains_many_uni 'options', CaseOption
- end
-
- # A query expression is an expression that is applied to some collection.
- # The contained optional expression may contain different types of relational expressions depending
- # on what the query is applied to.
- #
- class QueryExpression < Expression
- abstract
- contains_one_uni 'expr', Expression, :lowerBound => 0
- end
+ # Mix in implementation into the generated code
+ module Model
- # An exported query is a special form of query that searches for exported objects.
- #
- class ExportedQuery < QueryExpression
- end
-
- # A virtual query is a special form of query that searches for virtual objects.
- #
- class VirtualQuery < QueryExpression
- end
-
- # An attribute operation sets or appends a value to a named attribute.
- #
- class AttributeOperation < Positioned
- has_attr 'attribute_name', String, :lowerBound => 1
- has_attr 'operator', RGen::MetamodelBuilder::DataTypes::Enum.new([:'=>', :'+>', ]), :lowerBound => 1
- contains_one_uni 'value_expr', Expression, :lowerBound => 1
- end
-
- # An object that collects stored objects from the central cache and returns
- # them to the current host. Operations may optionally be applied.
- #
- class CollectExpression < Expression
- contains_one_uni 'type_expr', Expression, :lowerBound => 1
- contains_one_uni 'query', QueryExpression, :lowerBound => 1
- contains_many_uni 'operations', AttributeOperation
- end
-
- class Parameter < Positioned
- has_attr 'name', String, :lowerBound => 1
- contains_one_uni 'value', Expression
- end
-
- # Abstract base class for definitions.
- #
- class Definition < Expression
- abstract
- end
-
- # Abstract base class for named and parameterized definitions.
- class NamedDefinition < Definition
- abstract
- has_attr 'name', String, :lowerBound => 1
- contains_many_uni 'parameters', Parameter
- contains_one_uni 'body', Expression
- end
-
- # A resource type definition (a 'define' in the DSL).
- #
- class ResourceTypeDefinition < NamedDefinition
- end
-
- # A node definition matches hosts using Strings, or Regular expressions. It may inherit from
- # a parent node (also using a String or Regular expression).
- #
- class NodeDefinition < Definition
- contains_one_uni 'parent', Expression
- contains_many_uni 'host_matches', Expression, :lowerBound => 1
- contains_one_uni 'body', Expression
- end
-
- class LocatableExpression < Expression
- has_many_attr 'line_offsets', Integer
- has_attr 'locator', Object, :lowerBound => 1, :transient => true
+ class PopsObject
+ include Puppet::Pops::Visitable
+ include Puppet::Pops::Adaptable
+ include Puppet::Pops::Containment
+ end
- module ClassModule
- # Go through the gymnastics of making either value or pattern settable
- # with synchronization to the other form. A derived value cannot be serialized
- # and we want to serialize the pattern. When recreating the object we need to
- # recreate it from the pattern string.
- # The below sets both values if one is changed.
- #
- def locator
- unless result = getLocator
- setLocator(result = Puppet::Pops::Parser::Locator.locator(source_text, source_ref(), line_offsets))
+ class LocatableExpression
+ module ClassModule
+ # Go through the gymnastics of making either value or pattern settable
+ # with synchronization to the other form. A derived value cannot be serialized
+ # and we want to serialize the pattern. When recreating the object we need to
+ # recreate it from the pattern string.
+ # The below sets both values if one is changed.
+ #
+ def locator
+ unless result = getLocator
+ setLocator(result = Puppet::Pops::Parser::Locator.locator(source_text, source_ref(), line_offsets))
+ end
+ result
end
- result
end
end
- end
-
- # Contains one expression which has offsets reported virtually (offset against the Program's
- # overall locator).
- #
- class SubLocatedExpression < Expression
- contains_one_uni 'expr', Expression, :lowerBound => 1
-
- # line offset index for contained expressions
- has_many_attr 'line_offsets', Integer
-
- # Number of preceding lines (before the line_offsets)
- has_attr 'leading_line_count', Integer
-
- # The offset of the leading source line (i.e. size of "left margin").
- has_attr 'leading_line_offset', Integer
-
- # The locator for the sub-locatable's children (not for the sublocator itself)
- # The locator is not serialized and is recreated on demand from the indexing information
- # in self.
- #
- has_attr 'locator', Object, :lowerBound => 1, :transient => true
-
- module ClassModule
- def locator
- unless result = getLocator
- # Adapt myself to get the Locator for me
- adapter = Puppet::Pops::Adapters::SourcePosAdapter.adapt(self)
- # Get the program (root), and deal with case when not contained in a program
- program = eAllContainers.find {|c| c.is_a?(Program) }
- source_ref = program.nil? ? '' : program.source_ref
-
- # An outer locator is needed since SubLocator only deals with offsets. This outer locator
- # has 0,0 as origin.
- outer_locator = Puppet::Pops::Parser::Locator.locator(adpater.extract_text, source_ref, line_offsets)
- # Create a sublocator that describes an offset from the outer
- # NOTE: the offset of self is the same as the sublocator's leading_offset
- result = Puppet::Pops::Parser::Locator::SubLocator.new(outer_locator,
- leading_line_count, offset, leading_line_offset)
- setLocator(result)
+ class SubLocatedExpression
+ module ClassModule
+ def locator
+ unless result = getLocator
+ # Adapt myself to get the Locator for me
+ adapter = Puppet::Pops::Adapters::SourcePosAdapter.adapt(self)
+ # Get the program (root), and deal with case when not contained in a program
+ program = eAllContainers.find {|c| c.is_a?(Program) }
+ source_ref = program.nil? ? '' : program.source_ref
+
+ # An outer locator is needed since SubLocator only deals with offsets. This outer locator
+ # has 0,0 as origin.
+ outer_locator = Puppet::Pops::Parser::Locator.locator(adpater.extract_text, source_ref, line_offsets)
+
+ # Create a sublocator that describes an offset from the outer
+ # NOTE: the offset of self is the same as the sublocator's leading_offset
+ result = Puppet::Pops::Parser::Locator::SubLocator.new(outer_locator,
+ leading_line_count, offset, leading_line_offset)
+ setLocator(result)
+ end
+ result
end
- result
end
end
- end
-
- # A heredoc is a wrapper around a LiteralString or a ConcatenatedStringExpression with a specification
- # of syntax. The expectation is that "syntax" has meaning to a validator. A syntax of nil or '' means
- # "unspecified syntax".
- #
- class HeredocExpression < Expression
- has_attr 'syntax', String
- contains_one_uni 'text_expr', Expression, :lowerBound => 1
- end
-
- # A class definition
- #
- class HostClassDefinition < NamedDefinition
- has_attr 'parent_class', String
- end
-
- # i.e {|parameters| body }
- class LambdaExpression < Expression
- contains_many_uni 'parameters', Parameter
- contains_one_uni 'body', Expression
- end
-
- # If expression. If test is true, the then_expr part should be evaluated, else the (optional)
- # else_expr. An 'elsif' is simply an else_expr = IfExpression, and 'else' is simply else == Block.
- # a 'then' is typically a Block.
- #
- class IfExpression < Expression
- contains_one_uni 'test', Expression, :lowerBound => 1
- contains_one_uni 'then_expr', Expression, :lowerBound => 1
- contains_one_uni 'else_expr', Expression
- end
-
- # An if expression with boolean reversed test.
- #
- class UnlessExpression < IfExpression
- end
-
- # An abstract call.
- #
- class CallExpression < Expression
- abstract
- # A bit of a crutch; functions are either procedures (void return) or has an rvalue
- # this flag tells the evaluator that it is a failure to call a function that is void/procedure
- # where a value is expected.
- #
- has_attr 'rval_required', Boolean, :defaultValueLiteral => "false"
- contains_one_uni 'functor_expr', Expression, :lowerBound => 1
- contains_many_uni 'arguments', Expression
- contains_one_uni 'lambda', Expression
- end
-
- # A function call where the functor_expr should evaluate to something callable.
- #
- class CallFunctionExpression < CallExpression; end
-
- # A function call where the given functor_expr should evaluate to the name
- # of a function.
- #
- class CallNamedFunctionExpression < CallExpression; end
-
- # A method/function call where the function expr is a NamedAccess and with support for
- # an optional lambda block
- #
- class CallMethodExpression < CallExpression
- end
-
- # Abstract base class for literals.
- #
- class Literal < Expression
- abstract
- end
- # A literal value is an abstract value holder. The type of the contained value is
- # determined by the concrete subclass.
- #
- class LiteralValue < Literal
- abstract
- end
-
- # A Regular Expression Literal.
- #
- class LiteralRegularExpression < LiteralValue
- has_attr 'value', Object, :lowerBound => 1, :transient => true
- has_attr 'pattern', String, :lowerBound => 1
-
- module ClassModule
- # Go through the gymnastics of making either value or pattern settable
- # with synchronization to the other form. A derived value cannot be serialized
- # and we want to serialize the pattern. When recreating the object we need to
- # recreate it from the pattern string.
- # The below sets both values if one is changed.
- #
- def value= regexp
- setValue regexp
- setPattern regexp.to_s
- end
+ class LiteralRegularExpression
+ module ClassModule
+ # Go through the gymnastics of making either value or pattern settable
+ # with synchronization to the other form. A derived value cannot be serialized
+ # and we want to serialize the pattern. When recreating the object we need to
+ # recreate it from the pattern string.
+ # The below sets both values if one is changed.
+ #
+ def value= regexp
+ setValue regexp
+ setPattern regexp.to_s
+ end
- def pattern= regexp_string
- setPattern regexp_string
- setValue Regexp.new(regexp_string)
+ def pattern= regexp_string
+ setPattern regexp_string
+ setValue Regexp.new(regexp_string)
+ end
end
end
- end
-
- # A Literal String
- #
- class LiteralString < LiteralValue
- has_attr 'value', String, :lowerBound => 1
- end
-
- class LiteralNumber < LiteralValue
- abstract
- end
-
- # A literal number has a radix of decimal (10), octal (8), or hex (16) to enable string conversion with the input radix.
- # By default, a radix of 10 is used.
- #
- class LiteralInteger < LiteralNumber
- has_attr 'radix', Integer, :lowerBound => 1, :defaultValueLiteral => "10"
- has_attr 'value', Integer, :lowerBound => 1
- end
-
- class LiteralFloat < LiteralNumber
- has_attr 'value', Float, :lowerBound => 1
- end
-
- # The DSL `undef`.
- #
- class LiteralUndef < Literal; end
-
- # The DSL `default`
- class LiteralDefault < Literal; end
-
- # DSL `true` or `false`
- class LiteralBoolean < LiteralValue
- has_attr 'value', Boolean, :lowerBound => 1
- end
-
- # A text expression is an interpolation of an expression. If the embedded expression is
- # a QualifiedName, it is taken as a variable name and resolved. All other expressions are evaluated.
- # The result is transformed to a string.
- #
- class TextExpression < UnaryExpression; end
-
- # An interpolated/concatenated string. The contained segments are expressions. Verbatim sections
- # should be LiteralString instances, and interpolated expressions should either be
- # TextExpression instances (if QualifiedNames should be turned into variables), or any other expression
- # if such treatment is not needed.
- #
- class ConcatenatedString < Expression
- contains_many_uni 'segments', Expression
- end
-
- # A DSL NAME (one or multiple parts separated by '::').
- #
- class QualifiedName < LiteralValue
- has_attr 'value', String, :lowerBound => 1
- end
-
- # A DSL CLASSREF (one or multiple parts separated by '::' where (at least) the first part starts with an upper case letter).
- #
- class QualifiedReference < LiteralValue
- has_attr 'value', String, :lowerBound => 1
- end
-
- # A Variable expression looks up value of expr (some kind of name) in scope.
- # The expression is typically a QualifiedName, or QualifiedReference.
- #
- class VariableExpression < UnaryExpression; end
-
- # Epp start
- class EppExpression < Expression
- has_attr 'see_scope', Boolean
- contains_one_uni 'body', Expression
- end
-
- # A string to render
- class RenderStringExpression < LiteralString
- end
-
- # An expression to evluate and render
- class RenderExpression < UnaryExpression
- end
-
- # A resource body describes one resource instance
- #
- class ResourceBody < Positioned
- contains_one_uni 'title', Expression
- contains_many_uni 'operations', AttributeOperation
- end
-
- # An abstract resource describes the form of the resource (regular, virtual or exported)
- # and adds convenience methods to ask if it is virtual or exported.
- # All derived classes may not support all forms, and these needs to be validated
- #
- class AbstractResource < Expression
- abstract
- has_attr 'form', RGen::MetamodelBuilder::DataTypes::Enum.new([:regular, :virtual, :exported ]), :lowerBound => 1, :defaultValueLiteral => "regular"
- has_attr 'virtual', Boolean, :derived => true
- has_attr 'exported', Boolean, :derived => true
-
- module ClassModule
- def virtual_derived
- form == :virtual || form == :exported
- end
+ class AbstractResource
+ module ClassModule
+ def virtual_derived
+ form == :virtual || form == :exported
+ end
- def exported_derived
- form == :exported
+ def exported_derived
+ form == :exported
+ end
end
end
- end
-
- # A resource expression is used to instantiate one or many resource. Resources may optionally
- # be virtual or exported, an exported resource is always virtual.
- #
- class ResourceExpression < AbstractResource
- contains_one_uni 'type_name', Expression, :lowerBound => 1
- contains_many_uni 'bodies', ResourceBody
- end
-
- # A resource defaults sets defaults for a resource type. This class inherits from AbstractResource
- # but does only support the :regular form (this is intentional to be able to produce better error messages
- # when illegal forms are applied to a model.
- #
- class ResourceDefaultsExpression < AbstractResource
- contains_one_uni 'type_ref', QualifiedReference
- contains_many_uni 'operations', AttributeOperation
- end
-
- # A resource override overrides already set values.
- #
- class ResourceOverrideExpression < Expression
- contains_one_uni 'resources', Expression, :lowerBound => 1
- contains_many_uni 'operations', AttributeOperation
- end
-
- # A selector entry describes a map from matching_expr to value_expr.
- #
- class SelectorEntry < Positioned
- contains_one_uni 'matching_expr', Expression, :lowerBound => 1
- contains_one_uni 'value_expr', Expression, :lowerBound => 1
- end
-
- # A selector expression represents a mapping from a left_expr to a matching SelectorEntry.
- #
- class SelectorExpression < Expression
- contains_one_uni 'left_expr', Expression, :lowerBound => 1
- contains_many_uni 'selectors', SelectorEntry
- end
-
- # A named access expression looks up a named part. (e.g. $a.b)
- #
- class NamedAccessExpression < BinaryExpression; end
-
- # A Program is the top level construct returned by the parser
- # it contains the parsed result in the body, and has a reference to the full source text,
- # and its origin. The line_offset's is an array with the start offset of each line.
- #
- class Program < PopsObject
- contains_one_uni 'body', Expression
- has_many 'definitions', Definition
- has_attr 'source_text', String
- has_attr 'source_ref', String
- has_many_attr 'line_offsets', Integer
- has_attr 'locator', Object, :lowerBound => 1, :transient => true
-
- module ClassModule
- def locator
- unless result = getLocator
- setLocator(result = Puppet::Pops::Parser::Locator.locator(source_text, source_ref(), line_offsets))
+ class Program < PopsObject
+ module ClassModule
+ def locator
+ unless result = getLocator
+ setLocator(result = Puppet::Pops::Parser::Locator.locator(source_text, source_ref(), line_offsets))
+ end
+ result
end
- result
end
end
end
+
end
diff --git a/lib/puppet/pops/model/model_label_provider.rb b/lib/puppet/pops/model/model_label_provider.rb
index 979aa4bc5..543d83d49 100644
--- a/lib/puppet/pops/model/model_label_provider.rb
+++ b/lib/puppet/pops/model/model_label_provider.rb
@@ -50,6 +50,7 @@ class Puppet::Pops::Model::ModelLabelProvider < Puppet::Pops::LabelProvider
def label_VariableExpression o ; "Variable" end
def label_TextExpression o ; "Expression in Interpolated String" end
def label_UnaryMinusExpression o ; "Unary Minus" end
+ def label_UnfoldExpression o ; "Unfold" end
def label_BlockExpression o ; "Block Expression" end
def label_ConcatenatedString o ; "Double Quoted String" end
def label_HeredocExpression o ; "'@(#{o.syntax})' expression" end
@@ -83,7 +84,8 @@ class Puppet::Pops::Model::ModelLabelProvider < Puppet::Pops::LabelProvider
def label_Hash o ; "Hash" end
def label_QualifiedName o ; "Name" end
def label_QualifiedReference o ; "Type-Name" end
- def label_PAbstractType o ; "#{Puppet::Pops::Types::TypeCalculator.string(o)}-Type" end
+ def label_PAnyType o ; "#{Puppet::Pops::Types::TypeCalculator.string(o)}-Type" end
+ def label_ReservedWord o ; "Reserved Word '#{o.word}'" end
def label_PResourceType o
if o.title
@@ -94,7 +96,7 @@ class Puppet::Pops::Model::ModelLabelProvider < Puppet::Pops::LabelProvider
end
def label_Class o
- if o <= Puppet::Pops::Types::PAbstractType
+ if o <= Puppet::Pops::Types::PAnyType
simple_name = o.name.split('::').last
simple_name[1..-5] + "-Type"
else
diff --git a/lib/puppet/pops/model/model_meta.rb b/lib/puppet/pops/model/model_meta.rb
new file mode 100644
index 000000000..246216aa9
--- /dev/null
+++ b/lib/puppet/pops/model/model_meta.rb
@@ -0,0 +1,576 @@
+#
+# The Puppet Pops Metamodel
+#
+# This module contains a formal description of the Puppet Pops (*P*uppet *OP*eration instruction*S*).
+# It describes a Metamodel containing DSL instructions, a description of PuppetType and related
+# classes needed to evaluate puppet logic.
+# The metamodel resembles the existing AST model, but it is a semantic model of instructions and
+# the types that they operate on rather than an Abstract Syntax Tree, although closely related.
+#
+# The metamodel is anemic (has no behavior) except basic datatype and type
+# assertions and reference/containment assertions.
+# The metamodel is also a generalized description of the Puppet DSL to enable the
+# same metamodel to be used to express Puppet DSL models (instances) with different semantics as
+# the language evolves.
+#
+# The metamodel is concretized by a validator for a particular version of
+# the Puppet DSL language.
+#
+# This metamodel is expressed using RGen.
+#
+
+require 'rgen/metamodel_builder'
+
+module Puppet::Pops::Model
+ extend RGen::MetamodelBuilder::ModuleExtension
+
+ # A base class for modeled objects that makes them Visitable, and Adaptable.
+ #
+ class PopsObject < RGen::MetamodelBuilder::MMBase
+ abstract
+ end
+
+ # A Positioned object has an offset measured in an opaque unit (representing characters) from the start
+ # of a source text (starting
+ # from 0), and a length measured in the same opaque unit. The resolution of the opaque unit requires the
+ # aid of a Locator instance that knows about the measure. This information is stored in the model's
+ # root node - a Program.
+ #
+ # The offset and length are optional if the source of the model is not from parsed text.
+ #
+ class Positioned < PopsObject
+ abstract
+ has_attr 'offset', Integer
+ has_attr 'length', Integer
+ end
+
+ # @abstract base class for expressions
+ class Expression < Positioned
+ abstract
+ end
+
+ # A Nop - the "no op" expression.
+ # @note not really needed since the evaluator can evaluate nil with the meaning of NoOp
+ # @todo deprecate? May be useful if there is the need to differentiate between nil and Nop when transforming model.
+ #
+ class Nop < Expression
+ end
+
+ # A binary expression is abstract and has a left and a right expression. The order of evaluation
+ # and semantics are determined by the concrete subclass.
+ #
+ class BinaryExpression < Expression
+ abstract
+ #
+ # @!attribute [rw] left_expr
+ # @return [Expression]
+ contains_one_uni 'left_expr', Expression, :lowerBound => 1
+ contains_one_uni 'right_expr', Expression, :lowerBound => 1
+ end
+
+ # An unary expression is abstract and contains one expression. The semantics are determined by
+ # a concrete subclass.
+ #
+ class UnaryExpression < Expression
+ abstract
+ contains_one_uni 'expr', Expression, :lowerBound => 1
+ end
+
+ # A class that simply evaluates to the contained expression.
+ # It is of value in order to preserve user entered parentheses in transformations, and
+ # transformations from model to source.
+ #
+ class ParenthesizedExpression < UnaryExpression; end
+
+ # A boolean not expression, reversing the truth of the unary expr.
+ #
+ class NotExpression < UnaryExpression; end
+
+ # An arithmetic expression reversing the polarity of the numeric unary expr.
+ #
+ class UnaryMinusExpression < UnaryExpression; end
+
+ # Unfolds an array (a.k.a 'splat')
+ class UnfoldExpression < UnaryExpression; end
+
+ OpAssignment = RGen::MetamodelBuilder::DataTypes::Enum.new(
+ :literals => [:'=', :'+=', :'-='],
+ :name => 'OpAssignment')
+
+ # An assignment expression assigns a value to the lval() of the left_expr.
+ #
+ class AssignmentExpression < BinaryExpression
+ has_attr 'operator', OpAssignment, :lowerBound => 1
+ end
+
+ OpArithmetic = RGen::MetamodelBuilder::DataTypes::Enum.new(
+ :literals => [:'+', :'-', :'*', :'%', :'/', :'<<', :'>>' ],
+ :name => 'OpArithmetic')
+
+ # An arithmetic expression applies an arithmetic operator on left and right expressions.
+ #
+ class ArithmeticExpression < BinaryExpression
+ has_attr 'operator', OpArithmetic, :lowerBound => 1
+ end
+
+ OpRelationship = RGen::MetamodelBuilder::DataTypes::Enum.new(
+ :literals => [:'->', :'<-', :'~>', :'<~'],
+ :name => 'OpRelationship')
+
+ # A relationship expression associates the left and right expressions
+ #
+ class RelationshipExpression < BinaryExpression
+ has_attr 'operator', OpRelationship, :lowerBound => 1
+ end
+
+ # A binary expression, that accesses the value denoted by right in left. i.e. typically
+ # expressed concretely in a language as left[right].
+ #
+ class AccessExpression < Expression
+ contains_one_uni 'left_expr', Expression, :lowerBound => 1
+ contains_many_uni 'keys', Expression, :lowerBound => 1
+ end
+
+ OpComparison = RGen::MetamodelBuilder::DataTypes::Enum.new(
+ :literals => [:'==', :'!=', :'<', :'>', :'<=', :'>=' ],
+ :name => 'OpComparison')
+
+ # A comparison expression compares left and right using a comparison operator.
+ #
+ class ComparisonExpression < BinaryExpression
+ has_attr 'operator', OpComparison, :lowerBound => 1
+ end
+
+ OpMatch = RGen::MetamodelBuilder::DataTypes::Enum.new(
+ :literals => [:'!~', :'=~'],
+ :name => 'OpMatch')
+
+ # A match expression matches left and right using a matching operator.
+ #
+ class MatchExpression < BinaryExpression
+ has_attr 'operator', OpMatch, :lowerBound => 1
+ end
+
+ # An 'in' expression checks if left is 'in' right
+ #
+ class InExpression < BinaryExpression; end
+
+ # A boolean expression applies a logical connective operator (and, or) to left and right expressions.
+ #
+ class BooleanExpression < BinaryExpression
+ abstract
+ end
+
+ # An and expression applies the logical connective operator and to left and right expression
+ # and does not evaluate the right expression if the left expression is false.
+ #
+ class AndExpression < BooleanExpression; end
+
+ # An or expression applies the logical connective operator or to the left and right expression
+ # and does not evaluate the right expression if the left expression is true
+ #
+ class OrExpression < BooleanExpression; end
+
+ # A literal list / array containing 0:M expressions.
+ #
+ class LiteralList < Expression
+ contains_many_uni 'values', Expression
+ end
+
+ # A Keyed entry has a key and a value expression. It is typically used as an entry in a Hash.
+ #
+ class KeyedEntry < Positioned
+ contains_one_uni 'key', Expression, :lowerBound => 1
+ contains_one_uni 'value', Expression, :lowerBound => 1
+ end
+
+ # A literal hash is a collection of KeyedEntry objects
+ #
+ class LiteralHash < Expression
+ contains_many_uni 'entries', KeyedEntry
+ end
+
+ # A block contains a list of expressions
+ #
+ class BlockExpression < Expression
+ contains_many_uni 'statements', Expression
+ end
+
+ # A case option entry in a CaseStatement
+ #
+ class CaseOption < Expression
+ contains_many_uni 'values', Expression, :lowerBound => 1
+ contains_one_uni 'then_expr', Expression, :lowerBound => 1
+ end
+
+ # A case expression has a test, a list of options (multi values => block map).
+ # One CaseOption may contain a LiteralDefault as value. This option will be picked if nothing
+ # else matched.
+ #
+ class CaseExpression < Expression
+ contains_one_uni 'test', Expression, :lowerBound => 1
+ contains_many_uni 'options', CaseOption
+ end
+
+ # A query expression is an expression that is applied to some collection.
+ # The contained optional expression may contain different types of relational expressions depending
+ # on what the query is applied to.
+ #
+ class QueryExpression < Expression
+ abstract
+ contains_one_uni 'expr', Expression, :lowerBound => 0
+ end
+
+ # An exported query is a special form of query that searches for exported objects.
+ #
+ class ExportedQuery < QueryExpression
+ end
+
+ # A virtual query is a special form of query that searches for virtual objects.
+ #
+ class VirtualQuery < QueryExpression
+ end
+
+ OpAttribute = RGen::MetamodelBuilder::DataTypes::Enum.new(
+ :literals => [:'=>', :'+>', ],
+ :name => 'OpAttribute')
+
+ class AbstractAttributeOperation < Positioned
+ end
+
+ # An attribute operation sets or appends a value to a named attribute.
+ #
+ class AttributeOperation < AbstractAttributeOperation
+ has_attr 'attribute_name', String, :lowerBound => 1
+ has_attr 'operator', OpAttribute, :lowerBound => 1
+ contains_one_uni 'value_expr', Expression, :lowerBound => 1
+ end
+
+ # An attribute operation containing an expression that must evaluate to a Hash
+ #
+ class AttributesOperation < AbstractAttributeOperation
+ contains_one_uni 'expr', Expression, :lowerBound => 1
+ end
+
+ # An object that collects stored objects from the central cache and returns
+ # them to the current host. Operations may optionally be applied.
+ #
+ class CollectExpression < Expression
+ contains_one_uni 'type_expr', Expression, :lowerBound => 1
+ contains_one_uni 'query', QueryExpression, :lowerBound => 1
+ contains_many_uni 'operations', AttributeOperation
+ end
+
+ class Parameter < Positioned
+ has_attr 'name', String, :lowerBound => 1
+ contains_one_uni 'value', Expression
+ contains_one_uni 'type_expr', Expression, :lowerBound => 0
+ has_attr 'captures_rest', Boolean
+ end
+
+ # Abstract base class for definitions.
+ #
+ class Definition < Expression
+ abstract
+ end
+
+ # Abstract base class for named and parameterized definitions.
+ class NamedDefinition < Definition
+ abstract
+ has_attr 'name', String, :lowerBound => 1
+ contains_many_uni 'parameters', Parameter
+ contains_one_uni 'body', Expression
+ end
+
+ # A resource type definition (a 'define' in the DSL).
+ #
+ class ResourceTypeDefinition < NamedDefinition
+ end
+
+ # A node definition matches hosts using Strings, or Regular expressions. It may inherit from
+ # a parent node (also using a String or Regular expression).
+ #
+ class NodeDefinition < Definition
+ contains_one_uni 'parent', Expression
+ contains_many_uni 'host_matches', Expression, :lowerBound => 1
+ contains_one_uni 'body', Expression
+ end
+
+ class LocatableExpression < Expression
+ has_many_attr 'line_offsets', Integer
+ has_attr 'locator', Object, :lowerBound => 1, :transient => true
+ end
+
+ # Contains one expression which has offsets reported virtually (offset against the Program's
+ # overall locator).
+ #
+ class SubLocatedExpression < Expression
+ contains_one_uni 'expr', Expression, :lowerBound => 1
+
+ # line offset index for contained expressions
+ has_many_attr 'line_offsets', Integer
+
+ # Number of preceding lines (before the line_offsets)
+ has_attr 'leading_line_count', Integer
+
+ # The offset of the leading source line (i.e. size of "left margin").
+ has_attr 'leading_line_offset', Integer
+
+ # The locator for the sub-locatable's children (not for the sublocator itself)
+ # The locator is not serialized and is recreated on demand from the indexing information
+ # in self.
+ #
+ has_attr 'locator', Object, :lowerBound => 1, :transient => true
+ end
+
+ # A heredoc is a wrapper around a LiteralString or a ConcatenatedStringExpression with a specification
+ # of syntax. The expectation is that "syntax" has meaning to a validator. A syntax of nil or '' means
+ # "unspecified syntax".
+ #
+ class HeredocExpression < Expression
+ has_attr 'syntax', String
+ contains_one_uni 'text_expr', Expression, :lowerBound => 1
+ end
+
+ # A class definition
+ #
+ class HostClassDefinition < NamedDefinition
+ has_attr 'parent_class', String
+ end
+
+ # i.e {|parameters| body }
+ class LambdaExpression < Expression
+ contains_many_uni 'parameters', Parameter
+ contains_one_uni 'body', Expression
+ end
+
+ # If expression. If test is true, the then_expr part should be evaluated, else the (optional)
+ # else_expr. An 'elsif' is simply an else_expr = IfExpression, and 'else' is simply else == Block.
+ # a 'then' is typically a Block.
+ #
+ class IfExpression < Expression
+ contains_one_uni 'test', Expression, :lowerBound => 1
+ contains_one_uni 'then_expr', Expression, :lowerBound => 1
+ contains_one_uni 'else_expr', Expression
+ end
+
+ # An if expression with boolean reversed test.
+ #
+ class UnlessExpression < IfExpression
+ end
+
+ # An abstract call.
+ #
+ class CallExpression < Expression
+ abstract
+ # A bit of a crutch; functions are either procedures (void return) or has an rvalue
+ # this flag tells the evaluator that it is a failure to call a function that is void/procedure
+ # where a value is expected.
+ #
+ has_attr 'rval_required', Boolean, :defaultValueLiteral => "false"
+ contains_one_uni 'functor_expr', Expression, :lowerBound => 1
+ contains_many_uni 'arguments', Expression
+ contains_one_uni 'lambda', Expression
+ end
+
+ # A function call where the functor_expr should evaluate to something callable.
+ #
+ class CallFunctionExpression < CallExpression; end
+
+ # A function call where the given functor_expr should evaluate to the name
+ # of a function.
+ #
+ class CallNamedFunctionExpression < CallExpression; end
+
+ # A method/function call where the function expr is a NamedAccess and with support for
+ # an optional lambda block
+ #
+ class CallMethodExpression < CallExpression
+ end
+
+ # Abstract base class for literals.
+ #
+ class Literal < Expression
+ abstract
+ end
+
+ # A literal value is an abstract value holder. The type of the contained value is
+ # determined by the concrete subclass.
+ #
+ class LiteralValue < Literal
+ abstract
+ end
+
+ # A Regular Expression Literal.
+ #
+ class LiteralRegularExpression < LiteralValue
+ has_attr 'value', Object, :lowerBound => 1, :transient => true
+ has_attr 'pattern', String, :lowerBound => 1
+ end
+
+ # A Literal String
+ #
+ class LiteralString < LiteralValue
+ has_attr 'value', String, :lowerBound => 1
+ end
+
+ class LiteralNumber < LiteralValue
+ abstract
+ end
+
+ # A literal number has a radix of decimal (10), octal (8), or hex (16) to enable string conversion with the input radix.
+ # By default, a radix of 10 is used.
+ #
+ class LiteralInteger < LiteralNumber
+ has_attr 'radix', Integer, :lowerBound => 1, :defaultValueLiteral => "10"
+ has_attr 'value', Integer, :lowerBound => 1
+ end
+
+ class LiteralFloat < LiteralNumber
+ has_attr 'value', Float, :lowerBound => 1
+ end
+
+ # The DSL `undef`.
+ #
+ class LiteralUndef < Literal; end
+
+ # The DSL `default`
+ class LiteralDefault < Literal; end
+
+ # DSL `true` or `false`
+ class LiteralBoolean < LiteralValue
+ has_attr 'value', Boolean, :lowerBound => 1
+ end
+
+ # A text expression is an interpolation of an expression. If the embedded expression is
+ # a QualifiedName, it is taken as a variable name and resolved. All other expressions are evaluated.
+ # The result is transformed to a string.
+ #
+ class TextExpression < UnaryExpression; end
+
+ # An interpolated/concatenated string. The contained segments are expressions. Verbatim sections
+ # should be LiteralString instances, and interpolated expressions should either be
+ # TextExpression instances (if QualifiedNames should be turned into variables), or any other expression
+ # if such treatment is not needed.
+ #
+ class ConcatenatedString < Expression
+ contains_many_uni 'segments', Expression
+ end
+
+ # A DSL NAME (one or multiple parts separated by '::').
+ #
+ class QualifiedName < LiteralValue
+ has_attr 'value', String, :lowerBound => 1
+ end
+
+ # Represents a parsed reserved word
+ class ReservedWord < LiteralValue
+ has_attr 'word', String, :lowerBound => 1
+ end
+
+ # A DSL CLASSREF (one or multiple parts separated by '::' where (at least) the first part starts with an upper case letter).
+ #
+ class QualifiedReference < LiteralValue
+ has_attr 'value', String, :lowerBound => 1
+ end
+
+ # A Variable expression looks up value of expr (some kind of name) in scope.
+ # The expression is typically a QualifiedName, or QualifiedReference.
+ #
+ class VariableExpression < UnaryExpression; end
+
+ # Epp start
+ class EppExpression < Expression
+ # EPP can be specified without giving any parameter specification.
+ # However, the parameters of the lambda in that case are the empty
+ # array, which is the same as when the parameters are explicity
+ # specified as empty. This attribute tracks that difference.
+ has_attr 'parameters_specified', Boolean
+ contains_one_uni 'body', Expression
+ end
+
+ # A string to render
+ class RenderStringExpression < LiteralString
+ end
+
+ # An expression to evluate and render
+ class RenderExpression < UnaryExpression
+ end
+
+ # A resource body describes one resource instance
+ #
+ class ResourceBody < Positioned
+ contains_one_uni 'title', Expression
+ contains_many_uni 'operations', AbstractAttributeOperation
+ end
+
+ ResourceFormEnum = RGen::MetamodelBuilder::DataTypes::Enum.new(
+ :literals => [:regular, :virtual, :exported ],
+ :name => 'ResourceFormEnum')
+
+ # An abstract resource describes the form of the resource (regular, virtual or exported)
+ # and adds convenience methods to ask if it is virtual or exported.
+ # All derived classes may not support all forms, and these needs to be validated
+ #
+ class AbstractResource < Expression
+ abstract
+ has_attr 'form', ResourceFormEnum, :lowerBound => 1, :defaultValueLiteral => "regular"
+ has_attr 'virtual', Boolean, :derived => true
+ has_attr 'exported', Boolean, :derived => true
+ end
+
+ # A resource expression is used to instantiate one or many resource. Resources may optionally
+ # be virtual or exported, an exported resource is always virtual.
+ #
+ class ResourceExpression < AbstractResource
+ contains_one_uni 'type_name', Expression, :lowerBound => 1
+ contains_many_uni 'bodies', ResourceBody
+ end
+
+ # A resource defaults sets defaults for a resource type. This class inherits from AbstractResource
+ # but does only support the :regular form (this is intentional to be able to produce better error messages
+ # when illegal forms are applied to a model.
+ #
+ class ResourceDefaultsExpression < AbstractResource
+ contains_one_uni 'type_ref', Expression
+ contains_many_uni 'operations', AbstractAttributeOperation
+ end
+
+ # A resource override overrides already set values.
+ #
+ class ResourceOverrideExpression < AbstractResource
+ contains_one_uni 'resources', Expression, :lowerBound => 1
+ contains_many_uni 'operations', AbstractAttributeOperation
+ end
+
+ # A selector entry describes a map from matching_expr to value_expr.
+ #
+ class SelectorEntry < Positioned
+ contains_one_uni 'matching_expr', Expression, :lowerBound => 1
+ contains_one_uni 'value_expr', Expression, :lowerBound => 1
+ end
+
+ # A selector expression represents a mapping from a left_expr to a matching SelectorEntry.
+ #
+ class SelectorExpression < Expression
+ contains_one_uni 'left_expr', Expression, :lowerBound => 1
+ contains_many_uni 'selectors', SelectorEntry
+ end
+
+ # A named access expression looks up a named part. (e.g. $a.b)
+ #
+ class NamedAccessExpression < BinaryExpression; end
+
+ # A Program is the top level construct returned by the parser
+ # it contains the parsed result in the body, and has a reference to the full source text,
+ # and its origin. The line_offset's is an array with the start offset of each line.
+ #
+ class Program < PopsObject
+ contains_one_uni 'body', Expression
+ has_many 'definitions', Definition
+ has_attr 'source_text', String
+ has_attr 'source_ref', String
+ has_many_attr 'line_offsets', Integer
+ has_attr 'locator', Object, :lowerBound => 1, :transient => true
+ end
+end
diff --git a/lib/puppet/pops/model/model_tree_dumper.rb b/lib/puppet/pops/model/model_tree_dumper.rb
index 0366a421a..4455414c1 100644
--- a/lib/puppet/pops/model/model_tree_dumper.rb
+++ b/lib/puppet/pops/model/model_tree_dumper.rb
@@ -110,6 +110,10 @@ class Puppet::Pops::Model::ModelTreeDumper < Puppet::Pops::Model::TreeDumper
[o.attribute_name, o.operator, do_dump(o.value_expr)]
end
+ def dump_AttributesOperation o
+ ['* =>', do_dump(o.expr)]
+ end
+
def dump_LiteralList o
["[]"] + o.values.collect {|x| do_dump(x)}
end
@@ -182,8 +186,15 @@ class Puppet::Pops::Model::ModelTreeDumper < Puppet::Pops::Model::TreeDumper
['-', do_dump(o.expr)]
end
+ def dump_UnfoldExpression o
+ ['unfold', do_dump(o.expr)]
+ end
+
def dump_BlockExpression o
- ["block"] + o.statements.collect {|x| do_dump(x) }
+ result = ["block", :indent]
+ o.statements.each {|x| result << :break; result << do_dump(x) }
+ result << :dedent << :break
+ result
end
# Interpolated strings are shown as (cat seg0 seg1 ... segN)
@@ -238,7 +249,8 @@ class Puppet::Pops::Model::ModelTreeDumper < Puppet::Pops::Model::TreeDumper
end
def dump_ResourceOverrideExpression o
- result = ["override", do_dump(o.resources), :indent]
+ form = o.form == :regular ? '' : o.form.to_s + "-"
+ result = [form+"override", do_dump(o.resources), :indent]
o.operations.each do |p|
result << :break << do_dump(p)
end
@@ -246,11 +258,20 @@ class Puppet::Pops::Model::ModelTreeDumper < Puppet::Pops::Model::TreeDumper
result
end
+ def dump_ReservedWord o
+ [ 'reserved', o.word ]
+ end
+
# Produces parameters as name, or (= name value)
def dump_Parameter o
- name_part = "#{o.name}"
- if o.value
+ name_prefix = o.captures_rest ? '*' : ''
+ name_part = "#{name_prefix}#{o.name}"
+ if o.value && o.type_expr
+ ["=t", do_dump(o.type_expr), name_part, do_dump(o.value)]
+ elsif o.value
["=", name_part, do_dump(o.value)]
+ elsif o.type_expr
+ ["t", do_dump(o.type_expr), name_part]
else
name_part
end
@@ -345,7 +366,8 @@ class Puppet::Pops::Model::ModelTreeDumper < Puppet::Pops::Model::TreeDumper
end
def dump_ResourceDefaultsExpression o
- result = ["resource-defaults", do_dump(o.type_ref), :indent]
+ form = o.form == :regular ? '' : o.form.to_s + "-"
+ result = [form+"resource-defaults", do_dump(o.type_ref), :indent]
o.operations.each do |p|
result << :break << do_dump(p)
end
diff --git a/lib/puppet/pops/parser/egrammar.ra b/lib/puppet/pops/parser/egrammar.ra
index a1d639cc2..54b183a81 100644
--- a/lib/puppet/pops/parser/egrammar.ra
+++ b/lib/puppet/pops/parser/egrammar.ra
@@ -20,6 +20,7 @@ token NUMBER
token HEREDOC SUBLOCATE
token RENDER_STRING RENDER_EXPR EPP_START EPP_END EPP_END_TRIM
token FUNCTION
+token PRIVATE ATTR TYPE
token LOW
prechigh
@@ -28,15 +29,14 @@ prechigh
left PIPE
left LPAREN
left RPAREN
- left AT ATAT
left DOT
- left CALL
nonassoc EPP_START
left LBRACK LISTSTART
left RBRACK
left QMARK
left LCOLLECT LLCOLLECT
right NOT
+ nonassoc SPLAT
nonassoc UMINUS
left IN
left MATCH NOMATCH
@@ -47,13 +47,12 @@ prechigh
left GREATEREQUAL GREATERTHAN LESSTHAN LESSEQUAL
left AND
left OR
- right APPENDS DELETES EQUALS
left LBRACE
left SELBRACE
left RBRACE
+ right AT ATAT
+ right APPENDS DELETES EQUALS
left IN_EDGE OUT_EDGE IN_EDGE_SUB OUT_EDGE_SUB
- left TITLE_COLON
- left CASE_COLON
left FARROW
left COMMA
nonassoc RENDER_EXPR
@@ -62,44 +61,137 @@ prechigh
preclow
rule
-# Produces [Model::BlockExpression, Model::Expression, nil] depending on multiple statements, single statement or empty
+# Produces [Model::Program] with a body containing what was parsed
program
- : statements { result = create_program(Factory.block_or_expression(*val[0])) }
+ : statements { result = create_program(Factory.block_or_expression(*val[0])) }
| epp_expression { result = create_program(Factory.block_or_expression(*val[0])) }
- | nil
+ | { result = create_empty_program() }
# Produces a semantic model (non validated, but semantically adjusted).
statements
: syntactic_statements { result = transform_calls(val[0]) }
-# Change may have issues with nil; i.e. program is a sequence of nils/nops
-# Simplified from original which had validation for top level constructs - see statement rule
+# Collects sequence of elements into a list that the statements rule can transform
+# (Needed because language supports function calls without parentheses around arguments).
# Produces Array<Model::Expression>
+#
syntactic_statements
: syntactic_statement { result = [val[0]]}
| syntactic_statements SEMIC syntactic_statement { result = val[0].push val[2] }
| syntactic_statements syntactic_statement { result = val[0].push val[1] }
# Produce a single expression or Array of expression
+# This exists to handle multiple arguments to non parenthesized function call. If e is expression,
+# the a program can consists of e [e,e,e] where the first may be a name of a function to call.
+#
syntactic_statement
- : any_expression { result = val[0] }
- | syntactic_statement COMMA any_expression { result = aryfy(val[0]).push val[2] }
+ : assignment =LOW { result = val[0] }
+ | syntactic_statement COMMA assignment =LOW { result = aryfy(val[0]).push val[2] }
+
+# Assignment (is right recursive since assignment is right associative)
+assignment
+ : relationship =LOW
+ | relationship EQUALS assignment { result = val[0].set(val[2]) ; loc result, val[1] }
+ | relationship APPENDS assignment { result = val[0].plus_set(val[2]) ; loc result, val[1] }
+ | relationship DELETES assignment { result = val[0].minus_set(val[2]); loc result, val[1] }
+
+assignments
+ : assignment { result = [val[0]] }
+ | assignments COMMA assignment { result = val[0].push(val[2]) }
+
+relationship
+ : resource =LOW
+ | relationship IN_EDGE resource { result = val[0].relop(val[1][:value], val[2]); loc result, val[1] }
+ | relationship IN_EDGE_SUB resource { result = val[0].relop(val[1][:value], val[2]); loc result, val[1] }
+ | relationship OUT_EDGE resource { result = val[0].relop(val[1][:value], val[2]); loc result, val[1] }
+ | relationship OUT_EDGE_SUB resource { result = val[0].relop(val[1][:value], val[2]); loc result, val[1] }
+
+#-- RESOURCE
+#
+resource
+ : expression = LOW
-any_expression
- : relationship_expression
+ #---VIRTUAL
+ | AT resource {
+ result = val[1]
+ unless Factory.set_resource_form(result, :virtual)
+ # This is equivalent to a syntax error - additional semantic restrictions apply
+ error val[0], "Virtual (@) can only be applied to a Resource Expression"
+ end
+ # relocate the result
+ loc result, val[0], val[1]
+ }
-relationship_expression
- : resource_expression =LOW { result = val[0] }
- | relationship_expression IN_EDGE relationship_expression { result = val[0].relop(val[1][:value], val[2]); loc result, val[1] }
- | relationship_expression IN_EDGE_SUB relationship_expression { result = val[0].relop(val[1][:value], val[2]); loc result, val[1] }
- | relationship_expression OUT_EDGE relationship_expression { result = val[0].relop(val[1][:value], val[2]); loc result, val[1] }
- | relationship_expression OUT_EDGE_SUB relationship_expression { result = val[0].relop(val[1][:value], val[2]); loc result, val[1] }
+ #---EXPORTED
+ | ATAT resource {
+ result = val[1]
+ unless Factory.set_resource_form(result, :exported)
+ # This is equivalent to a syntax error - additional semantic restrictions apply
+ error val[0], "Exported (@@) can only be applied to a Resource Expression"
+ end
+ # relocate the result
+ loc result, val[0], val[1]
+ }
-#---EXPRESSION
+ #---RESOURCE TITLED 3x and 4x
+ | resource LBRACE expression COLON attribute_operations additional_resource_bodies RBRACE {
+ bodies = [Factory.RESOURCE_BODY(val[2], val[4])] + val[5]
+ result = Factory.RESOURCE(val[0], bodies)
+ loc result, val[0], val[6]
+ }
+
+ #---CLASS RESOURCE
+ | CLASS LBRACE resource_bodies endsemi RBRACE {
+ result = Factory.RESOURCE(Factory.fqn(token_text(val[0])), val[2])
+ loc result, val[0], val[4]
+ }
+
+ # --RESOURCE 3X Expression
+ # Handles both 3x overrides and defaults (i.e. single resource_body without title colon)
+ # Slated for possible deprecation since it requires transformation and mix static/evaluation check
+ #
+ | resource LBRACE attribute_operations endcomma RBRACE {
+ result = case Factory.resource_shape(val[0])
+ when :resource, :class
+ # This catches deprecated syntax.
+ # If the attribute operations does not include +>, then the found expression
+ # is actually a LEFT followed by LITERAL_HASH
+ #
+ unless tmp = transform_resource_wo_title(val[0], val[2])
+ error val[1], "Syntax error resource body without title or hash with +>"
+ end
+ tmp
+ when :defaults
+ Factory.RESOURCE_DEFAULTS(val[0], val[2])
+ when :override
+ # This was only done for override in original - TODO should it be here at all
+ Factory.RESOURCE_OVERRIDE(val[0], val[2])
+ else
+ error val[0], "Expression is not valid as a resource, resource-default, or resource-override"
+ end
+ loc result, val[0], val[4]
+ }
+
+ resource_body
+ : expression COLON attribute_operations endcomma { result = Factory.RESOURCE_BODY(val[0], val[2]) }
+
+ resource_bodies
+ : resource_body =HIGH { result = [val[0]] }
+ | resource_bodies SEMIC resource_body =HIGH { result = val[0].push val[2] }
+
+ # This is a rule for the intermediate state where RACC has seen enough tokens to understand that
+ # what is expressed is a Resource Expression, it now has to get to the finishing line
+ #
+ additional_resource_bodies
+ : endcomma { result = [] }
+ | endcomma SEMIC { result = [] }
+ | endcomma SEMIC resource_bodies endsemi { result = val[2] }
+
+#-- EXPRESSION
#
-# Produces Model::Expression
expression
- : higher_precedence
+ : primary_expression
+ | call_function_expression
| expression LBRACK expressions RBRACK =LBRACK { result = val[0][*val[2]] ; loc result, val[0], val[3] }
| expression IN expression { result = val[0].in val[2] ; loc result, val[1] }
| expression MATCH expression { result = val[0] =~ val[2] ; loc result, val[1] }
@@ -112,6 +204,7 @@ expression
| expression LSHIFT expression { result = val[0] << val[2] ; loc result, val[1] }
| expression RSHIFT expression { result = val[0] >> val[2] ; loc result, val[1] }
| MINUS expression =UMINUS { result = val[1].minus() ; loc result, val[0] }
+ | TIMES expression =SPLAT { result = val[1].unfold() ; loc result, val[0] }
| expression NOTEQUAL expression { result = val[0].ne val[2] ; loc result, val[1] }
| expression ISEQUAL expression { result = val[0] == val[2] ; loc result, val[1] }
| expression GREATERTHAN expression { result = val[0] > val[2] ; loc result, val[1] }
@@ -121,28 +214,22 @@ expression
| NOT expression { result = val[1].not ; loc result, val[0] }
| expression AND expression { result = val[0].and val[2] ; loc result, val[1] }
| expression OR expression { result = val[0].or val[2] ; loc result, val[1] }
- | expression EQUALS expression { result = val[0].set(val[2]) ; loc result, val[1] }
- | expression APPENDS expression { result = val[0].plus_set(val[2]) ; loc result, val[1] }
- | expression DELETES expression { result = val[0].minus_set(val[2]); loc result, val[1] }
| expression QMARK selector_entries { result = val[0].select(*val[2]) ; loc result, val[0] }
- | LPAREN expression RPAREN { result = val[1].paren() ; loc result, val[0] }
+ | LPAREN assignment RPAREN { result = val[1].paren() ; loc result, val[0] }
+
#---EXPRESSIONS
-# (e.g. argument list)
+# (i.e. "argument list")
#
# This expression list can not contain function calls without parentheses around arguments
# Produces Array<Model::Expression>
+#
expressions
: expression { result = [val[0]] }
| expressions COMMA expression { result = val[0].push(val[2]) }
-# These go through a chain of left recursion, ending with primary_expression
-higher_precedence
- : call_function_expression
-
primary_expression
- : literal_expression
- | variable
+ : variable
| call_method_with_lambda_expression
| collection_expression
| case_expression
@@ -152,61 +239,54 @@ primary_expression
| hostclass_expression
| node_definition_expression
| epp_render_expression
- | function_definition
-
-# Allways have the same value
-literal_expression
- : array
- | boolean
- | default
+ | reserved_word
+ | array
| hash
| regex
- | text_or_name
- | number
+ | quotedtext
| type
- | undef
+ | NUMBER { result = Factory.NUMBER(val[0][:value]) ; loc result, val[0] }
+ | BOOLEAN { result = Factory.literal(val[0][:value]) ; loc result, val[0] }
+ | DEFAULT { result = Factory.literal(:default) ; loc result, val[0] }
+ | UNDEF { result = Factory.literal(:undef) ; loc result, val[0] }
+ | NAME { result = Factory.QNAME_OR_NUMBER(val[0][:value]) ; loc result, val[0] }
-text_or_name
- : name { result = val[0] }
- | quotedtext { result = val[0] }
#---CALL FUNCTION
#
# Produces Model::CallNamedFunction
call_function_expression
- : primary_expression LPAREN expressions endcomma RPAREN {
+ : expression LPAREN assignments endcomma RPAREN {
result = Factory.CALL_NAMED(val[0], true, val[2])
loc result, val[0], val[4]
}
- | primary_expression LPAREN RPAREN {
+ | expression LPAREN RPAREN {
result = Factory.CALL_NAMED(val[0], true, [])
loc result, val[0], val[2]
}
- | primary_expression LPAREN expressions endcomma RPAREN lambda {
+ | expression LPAREN assignments endcomma RPAREN lambda {
result = Factory.CALL_NAMED(val[0], true, val[2])
loc result, val[0], val[4]
result.lambda = val[5]
}
- | primary_expression LPAREN RPAREN lambda {
+ | expression LPAREN RPAREN lambda {
result = Factory.CALL_NAMED(val[0], true, [])
loc result, val[0], val[2]
result.lambda = val[3]
}
- | primary_expression = LOW { result = val[0] }
#---CALL METHOD
#
call_method_with_lambda_expression
: call_method_expression =LOW { result = val[0] }
- | call_method_expression lambda { result = val[0]; val[0].lambda = val[1] }
+ | call_method_expression lambda { result = val[0]; val[0].lambda = val[1] }
call_method_expression
- : named_access LPAREN expressions RPAREN { result = Factory.CALL_METHOD(val[0], val[2]); loc result, val[1], val[3] }
+ : named_access LPAREN assignments RPAREN { result = Factory.CALL_METHOD(val[0], val[2]); loc result, val[1], val[3] }
| named_access LPAREN RPAREN { result = Factory.CALL_METHOD(val[0], []); loc result, val[1], val[3] }
| named_access =LOW { result = Factory.CALL_METHOD(val[0], []); loc result, val[0] }
- # TODO: It may be of value to access named elements of types too
named_access
: expression DOT NAME {
result = val[0].dot(Factory.fqn(val[2][:value]))
@@ -215,31 +295,25 @@ call_method_with_lambda_expression
#---LAMBDA
#
-# This is a temporary switch while experimenting with concrete syntax
-# One should be picked for inclusion in puppet.
-
-# Lambda with parameters to the left of the body
lambda
: lambda_parameter_list lambda_rest {
- result = Factory.LAMBDA(val[0], val[1])
-# loc result, val[1] # TODO
+ result = Factory.LAMBDA(val[0][:value], val[1][:value])
+ loc result, val[0][:start], val[1][:end]
}
lambda_rest
- : LBRACE statements RBRACE { result = val[1] }
- | LBRACE RBRACE { result = nil }
+ : LBRACE statements RBRACE { result = {:end => val[2], :value =>val[1] } }
+ | LBRACE RBRACE { result = {:end => val[1], :value => nil } }
+
-# Produces Array<Model::Parameter>
lambda_parameter_list
- : PIPE PIPE { result = [] }
- | PIPE parameters endcomma PIPE { result = val[1] }
+ : PIPE PIPE { result = {:start => val[0], :value => [] } }
+ | PIPE parameters endcomma PIPE { result = {:start => val[0], :value => val[1] } }
#---CONDITIONALS
-#
#--IF
#
-# Produces Model::IfExpression
if_expression
: IF if_part {
result = val[1]
@@ -274,8 +348,6 @@ if_expression
#--UNLESS
#
-# Changed from Puppet 3x where there is no else part on unless
-#
unless_expression
: UNLESS expression LBRACE statements RBRACE unless_else {
result = Factory.UNLESS(val[1], Factory.block_or_expression(*val[3]), val[5])
@@ -301,7 +373,6 @@ unless_expression
#--- CASE EXPRESSION
#
-# Produces Model::CaseExpression
case_expression
: CASE expression LBRACE case_options RBRACE {
result = Factory.CASE(val[1], *val[3])
@@ -311,20 +382,17 @@ case_expression
# Produces Array<Model::CaseOption>
case_options
: case_option { result = [val[0]] }
- | case_options case_option { result = val[0].push val[1] }
+ | case_options case_option { result = val[0].push val[1] }
# Produced Model::CaseOption (aka When)
case_option
- : expressions case_colon LBRACE statements RBRACE {
- result = Factory.WHEN(val[0], val[3])
- loc result, val[1], val[4]
- }
- | expressions case_colon LBRACE RBRACE = LOW {
- result = Factory.WHEN(val[0], nil)
- loc result, val[1], val[3]
+ : expressions COLON LBRACE options_statements RBRACE {
+ result = Factory.WHEN(val[0], val[3]); loc result, val[1], val[4]
}
- case_colon: COLON =CASE_COLON { result = val[0] }
+ options_statements
+ : nil
+ | statements
# This special construct is required or racc will produce the wrong result when the selector entry
# LHS is generalized to any expression (LBRACE looks like a hash). Thus it is not possible to write
@@ -348,108 +416,11 @@ case_expression
selector_entry
: expression FARROW expression { result = Factory.MAP(val[0], val[2]) ; loc result, val[1] }
-#---RESOURCE
-#
-# Produces [Model::ResourceExpression, Model::ResourceDefaultsExpression]
-
-# The resource expression parses a generalized syntax and then selects the correct
-# resulting model based on the combinatoin of the LHS and what follows.
-# It also handled exported and virtual resources, and the class case
-#
-resource_expression
- : expression =LOW {
- result = val[0]
- }
- | at expression LBRACE resourceinstances endsemi RBRACE {
- result = case Factory.resource_shape(val[1])
- when :resource, :class
- tmp = Factory.RESOURCE(Factory.fqn(token_text(val[1])), val[3])
- tmp.form = val[0]
- tmp
- when :defaults
- error val[1], "A resource default can not be virtual or exported"
- when :override
- error val[1], "A resource override can not be virtual or exported"
- else
- error val[1], "Expression is not valid as a resource, resource-default, or resource-override"
- end
- loc result, val[1], val[4]
- }
- | at expression LBRACE attribute_operations endcomma RBRACE {
- result = case Factory.resource_shape(val[1])
- when :resource, :class, :defaults, :override
- error val[1], "Defaults are not virtualizable"
- else
- error val[1], "Expression is not valid as a resource, resource-default, or resource-override"
- end
- }
- | expression LBRACE resourceinstances endsemi RBRACE {
- result = case Factory.resource_shape(val[0])
- when :resource, :class
- Factory.RESOURCE(Factory.fqn(token_text(val[0])), val[2])
- when :defaults
- error val[1], "A resource default can not specify a resource name"
- when :override
- error val[1], "A resource override does not allow override of name of resource"
- else
- error val[1], "Expression is not valid as a resource, resource-default, or resource-override"
- end
- loc result, val[0], val[4]
- }
- | expression LBRACE attribute_operations endcomma RBRACE {
- result = case Factory.resource_shape(val[0])
- when :resource, :class
- # This catches deprecated syntax.
- # If the attribute operations does not include +>, then the found expression
- # is actually a LEFT followed by LITERAL_HASH
- #
- unless tmp = transform_resource_wo_title(val[0], val[2])
- error val[1], "Syntax error resource body without title or hash with +>"
- end
- tmp
- when :defaults
- Factory.RESOURCE_DEFAULTS(val[0], val[2])
- when :override
- # This was only done for override in original - TODO shuld it be here at all
- Factory.RESOURCE_OVERRIDE(val[0], val[2])
- else
- error val[0], "Expression is not valid as a resource, resource-default, or resource-override"
- end
- loc result, val[0], val[4]
- }
- | at CLASS LBRACE resourceinstances endsemi RBRACE {
- result = Factory.RESOURCE(Factory.fqn(token_text(val[1])), val[3])
- result.form = val[0]
- loc result, val[1], val[5]
- }
- | CLASS LBRACE resourceinstances endsemi RBRACE {
- result = Factory.RESOURCE(Factory.fqn(token_text(val[0])), val[2])
- loc result, val[0], val[4]
- }
-
- resourceinst
- : expression title_colon attribute_operations endcomma { result = Factory.RESOURCE_BODY(val[0], val[2]) }
-
- title_colon : COLON =TITLE_COLON { result = val[0] }
-
- resourceinstances
- : resourceinst { result = [val[0]] }
- | resourceinstances SEMIC resourceinst { result = val[0].push val[2] }
-
- # Produces Symbol corresponding to resource form
- #
- at
- : AT { result = :virtual }
- | AT AT { result = :exported }
- | ATAT { result = :exported }
-
#---COLLECTION
#
# A Collection is a predicate applied to a set of objects with an implied context (used variables are
# attributes of the object.
-# i.e. this is equivalent for source.select(QUERY).apply(ATTRIBUTE_OPERATIONS)
-#
-# Produces Model::CollectExpression
+# i.e. this is equivalent to source.select(QUERY).apply(ATTRIBUTE_OPERATIONS)
#
collection_expression
: expression collect_query LBRACE attribute_operations endcomma RBRACE {
@@ -469,11 +440,7 @@ collection_expression
: nil
| expression
-#---ATTRIBUTE OPERATIONS
-#
-# (Not an expression)
-#
-# Produces Array<Model::AttributeOperation>
+#---ATTRIBUTE OPERATIONS (Not an expression)
#
attribute_operations
: { result = [] }
@@ -486,7 +453,7 @@ attribute_operations
attribute_name
: NAME
| keyword
- | BOOLEAN
+# | BOOLEAN
# In this version, illegal combinations are validated instead of producing syntax errors
# (Can give nicer error message "+> is not applicable to...")
@@ -501,6 +468,9 @@ attribute_operations
result = Factory.ATTRIBUTE_OP(val[0][:value], :'+>', val[2])
loc result, val[0], val[2]
}
+ | TIMES FARROW expression {
+ result = Factory.ATTRIBUTES_OP(val[2]) ; loc result, val[0], val[2]
+ }
#---DEFINE
#
@@ -557,13 +527,13 @@ hostclass_expression
# Produces Model::NodeDefinition
#
node_definition_expression
- : NODE hostnames nodeparent LBRACE statements RBRACE {
- result = add_definition(Factory.NODE(val[1], val[2], val[4]))
- loc result, val[0], val[5]
+ : NODE hostnames endcomma nodeparent LBRACE statements RBRACE {
+ result = add_definition(Factory.NODE(val[1], val[3], val[5]))
+ loc result, val[0], val[6]
}
- | NODE hostnames nodeparent LBRACE RBRACE {
- result = add_definition(Factory.NODE(val[1], val[2], nil))
- loc result, val[0], val[4]
+ | NODE hostnames endcomma nodeparent LBRACE RBRACE {
+ result = add_definition(Factory.NODE(val[1], val[3], nil))
+ loc result, val[0], val[5]
}
# Hostnames is not a list of names, it is a list of name matchers (including a Regexp).
@@ -578,14 +548,18 @@ node_definition_expression
# Produces a LiteralExpression (string, :default, or regexp)
# String with interpolation is validated for better error message
hostname
- : dotted_name { result = val[0] }
- | quotedtext { result = val[0] }
+ : dotted_name
+ | quotedtext
| DEFAULT { result = Factory.literal(:default); loc result, val[0] }
| regex
dotted_name
- : NAME { result = Factory.literal(val[0][:value]); loc result, val[0] }
- | dotted_name DOT NAME { result = Factory.concat(val[0], '.', val[2][:value]); loc result, val[0], val[2] }
+ : name_or_number { result = Factory.literal(val[0][:value]); loc result, val[0] }
+ | dotted_name DOT name_or_number { result = Factory.concat(val[0], '.', val[2][:value]); loc result, val[0], val[2] }
+
+ name_or_number
+ : NAME
+ | NUMBER
# Produces Expression, since hostname is an Expression
nodeparent
@@ -594,8 +568,7 @@ node_definition_expression
#---FUNCTION DEFINITION
#
-function_definition
- : FUNCTION { result = Factory.QNAME(val[0][:value]) ; loc result, val[0] }
+#function_definition
# For now the function word will just be reserved, in the future it will
# produce a function definition
# FUNCTION classname parameter_list LBRACE opt_statements RBRACE {
@@ -607,7 +580,7 @@ function_definition
# Produces String
# TODO: The error that "class" is not a valid classname is bad - classname rule is also used for other things
classname
- : NAME { result = val[0] }
+ : NAME
| CLASS { error val[0], "'class' is not a valid classname" }
# Produces Array<Model::Parameter>
@@ -623,32 +596,48 @@ parameters
# Produces Model::Parameter
parameter
+ : untyped_parameter
+ | typed_parameter
+
+untyped_parameter
+ : regular_parameter
+ | splat_parameter
+
+regular_parameter
: VARIABLE EQUALS expression { result = Factory.PARAM(val[0][:value], val[2]) ; loc result, val[0] }
| VARIABLE { result = Factory.PARAM(val[0][:value]); loc result, val[0] }
-#--RESTRICTED EXPRESSIONS
-# i.e. where one could have expected an expression, but the set is limited
+splat_parameter
+ : TIMES regular_parameter { result = val[1]; val[1].captures_rest() }
-## What is allowed RHS of match operators (see expression)
-#match_rvalue
-# : regex
-# | text_or_name
+typed_parameter
+ : parameter_type untyped_parameter { val[1].type_expr(val[0]) ; result = val[1] }
+
+parameter_type
+ : type { result = val[0] }
+ | type LBRACK expressions RBRACK { result = val[0][*val[2]] ; loc result, val[0], val[3] }
#--VARIABLE
#
variable
: VARIABLE { result = Factory.fqn(val[0][:value]).var ; loc result, val[0] }
+#---RESERVED WORDS
+#
+reserved_word
+ : FUNCTION { result = Factory.RESERVED(val[0][:value]) ; loc result, val[0] }
+ | PRIVATE { result = Factory.RESERVED(val[0][:value]) ; loc result, val[0] }
+ | TYPE { result = Factory.RESERVED(val[0][:value]) ; loc result, val[0] }
+ | ATTR { result = Factory.RESERVED(val[0][:value]) ; loc result, val[0] }
+
#---LITERALS (dynamic and static)
#
array
- : LBRACK expressions RBRACK { result = Factory.LIST(val[1]); loc result, val[0], val[2] }
- | LBRACK expressions COMMA RBRACK { result = Factory.LIST(val[1]); loc result, val[0], val[3] }
- | LBRACK RBRACK { result = Factory.literal([]) ; loc result, val[0] }
- | LISTSTART expressions RBRACK { result = Factory.LIST(val[1]); loc result, val[0], val[2] }
- | LISTSTART expressions COMMA RBRACK { result = Factory.LIST(val[1]); loc result, val[0], val[3] }
- | LISTSTART RBRACK { result = Factory.literal([]) ; loc result, val[0] }
+ : LISTSTART assignments endcomma RBRACK { result = Factory.LIST(val[1]); loc result, val[0], val[3] }
+ | LISTSTART RBRACK { result = Factory.literal([]) ; loc result, val[0] }
+ | LBRACK assignments endcomma RBRACK { result = Factory.LIST(val[1]); loc result, val[0], val[3] }
+ | LBRACK RBRACK { result = Factory.literal([]) ; loc result, val[0] }
hash
: LBRACE hashpairs RBRACE { result = Factory.HASH(val[1]); loc result, val[0], val[2] }
@@ -660,7 +649,7 @@ hash
| hashpairs COMMA hashpair { result = val[0].push val[2] }
hashpair
- : expression FARROW expression { result = Factory.KEY_ENTRY(val[0], val[2]); loc result, val[1] }
+ : assignment FARROW assignment { result = Factory.KEY_ENTRY(val[0], val[2]); loc result, val[1] }
quotedtext
: string
@@ -676,7 +665,7 @@ dqpre : DQPRE { result = Factory.literal(val[0][:valu
dqpost : DQPOST { result = Factory.literal(val[0][:value]); loc result, val[0] }
dqmid : DQMID { result = Factory.literal(val[0][:value]); loc result, val[0] }
dqrval : text_expression dqtail { result = [val[0]] + val[1] }
-text_expression : expression { result = Factory.TEXT(val[0]) }
+text_expression : assignment { result = Factory.TEXT(val[0]) }
dqtail
: dqpost { result = [val[0]] }
@@ -690,7 +679,11 @@ sublocated_text
| SUBLOCATE dq_string { result = Factory.SUBLOCATE(val[0], val[1]); loc result, val[0] }
epp_expression
- : EPP_START epp_parameters_list statements { result = Factory.EPP(val[1], val[2]); loc result, val[0] }
+ : EPP_START epp_parameters_list optional_statements { result = Factory.EPP(val[1], val[2]); loc result, val[0] }
+
+optional_statements
+ :
+ | statements
epp_parameters_list
: =LOW{ result = nil }
@@ -706,16 +699,7 @@ epp_end
: EPP_END
| EPP_END_TRIM
-number : NUMBER { result = Factory.NUMBER(val[0][:value]) ; loc result, val[0] }
-name : NAME { result = Factory.QNAME_OR_NUMBER(val[0][:value]) ; loc result, val[0] }
type : CLASSREF { result = Factory.QREF(val[0][:value]) ; loc result, val[0] }
-undef : UNDEF { result = Factory.literal(:undef); loc result, val[0] }
-default : DEFAULT { result = Factory.literal(:default); loc result, val[0] }
-
- # Assumes lexer produces a Boolean value for booleans, or this will go wrong and produce a literal string
- # with the text 'true'.
- #TODO: could be changed to a specific boolean literal factory method to prevent this possible glitch.
-boolean : BOOLEAN { result = Factory.literal(val[0][:value]) ; loc result, val[0] }
regex
: REGEX { result = Factory.literal(val[0][:value]); loc result, val[0] }
@@ -745,6 +729,10 @@ keyword
| OR
| UNDEF
| UNLESS
+ | TYPE
+ | ATTR
+ | FUNCTION
+ | PRIVATE
nil
: { result = nil}
diff --git a/lib/puppet/pops/parser/eparser.rb b/lib/puppet/pops/parser/eparser.rb
index 31b5bbee0..26ff778fe 100644
--- a/lib/puppet/pops/parser/eparser.rb
+++ b/lib/puppet/pops/parser/eparser.rb
@@ -20,7 +20,7 @@ module Puppet
module Parser
class Parser < Racc::Parser
-module_eval(<<'...end egrammar.ra/module_eval...', 'egrammar.ra', 765)
+module_eval(<<'...end egrammar.ra/module_eval...', 'egrammar.ra', 753)
# Make emacs happy
# Local Variables:
@@ -30,208 +30,220 @@ module_eval(<<'...end egrammar.ra/module_eval...', 'egrammar.ra', 765)
##### State transition tables begin ###
clist = [
-'59,62,241,278,60,53,316,55,-131,268,-219,-133,268,-228,227,227,117,267',
-'356,59,62,227,268,60,14,250,301,243,251,129,42,238,49,128,52,46,366',
-'50,72,68,331,44,71,47,48,279,275,69,13,261,-131,70,-219,-133,12,-228',
-'224,322,138,59,62,136,73,60,53,248,55,398,43,246,247,334,67,63,245,65',
-'66,64,59,62,51,73,60,14,54,351,129,350,238,42,128,49,63,52,46,129,50',
-'72,68,128,44,71,47,48,59,62,69,13,60,336,70,129,129,12,223,128,128,138',
-'59,62,136,73,60,53,351,55,350,43,263,264,338,67,63,77,65,66,234,59,62',
-'51,73,60,14,54,78,80,79,81,42,300,49,63,52,46,299,50,72,68,75,44,71',
-'47,48,277,129,69,13,117,128,70,253,252,12,343,344,345,138,59,62,136',
-'73,60,53,227,55,396,43,214,348,315,67,63,352,65,66,125,59,62,51,73,60',
-'14,54,354,293,190,275,42,277,49,63,52,46,275,50,72,68,362,44,71,47,48',
-'363,129,69,13,292,128,70,299,77,12,157,154,152,138,59,62,136,73,60,53',
-'373,55,394,43,244,291,375,67,63,277,65,66,277,130,275,51,73,378,14,54',
-'117,118,318,299,42,382,49,63,52,46,354,50,72,68,384,44,71,47,48,385',
-'386,69,13,387,388,70,114,390,12,391,392,319,77,59,62,74,73,60,53,399',
-'55,400,43,401,402,,67,63,82,65,66,,,,51,,,14,54,,,,105,42,109,49,104',
-'52,111,,50,72,68,,44,71,,,,,69,13,,,70,,,12,108,,,,59,62,,73,60,53,',
-'55,,43,,,,67,63,82,65,66,83,,,51,,,14,54,,,,105,42,109,49,104,52,111',
-',50,72,68,,44,71,,,,,69,13,,,70,,,12,108,,,,59,62,,73,60,53,,55,,43',
-',84,85,67,63,82,65,66,83,,,51,,,14,54,,,,105,42,109,49,104,52,111,,50',
-'72,68,,44,71,,,,,69,13,,,70,,,12,108,,,,59,62,,73,60,53,,55,,43,,84',
-'85,67,63,82,65,66,83,,,51,,,14,54,,,,105,42,109,49,104,52,111,,50,72',
-'68,,44,71,,,,,69,13,,,70,,,12,108,,,,59,62,,73,60,53,,55,,43,,,,67,63',
-'82,65,66,83,,,51,,,14,54,,,,105,42,109,49,104,52,46,,50,72,68,,44,71',
-'47,48,,,69,13,,,70,,,12,108,,,,59,62,,73,60,53,,55,,43,,84,85,67,63',
-'82,65,66,83,,,51,,,14,54,,,,105,42,109,49,104,52,111,,50,72,68,,44,71',
-',,,,69,13,,,70,,,12,108,,,,59,62,,73,60,53,,55,,43,,,,67,63,82,65,66',
-',,,51,,,14,54,,,,105,42,109,49,104,52,111,,50,72,68,,44,71,,,,,69,13',
-',,70,,,12,108,,,,59,62,,73,60,53,,55,,43,,,,67,63,82,65,66,,,,51,,,14',
-'54,,,,105,42,109,49,104,52,111,,50,72,68,,44,71,,,,,69,13,,,70,,,12',
-'108,,,,59,62,,73,60,53,,55,,43,,,,67,63,,65,66,,,,51,,,14,54,,,,,42',
-',49,,52,111,,50,72,68,,44,71,,,,,69,13,,,70,,,12,,,,,59,62,,73,60,53',
-',55,,43,,,,67,63,,65,66,,,,51,,,14,54,,,,,42,,49,,52,111,,50,72,68,',
-'44,71,,,,,69,13,,,70,,,12,,,,,59,62,,73,60,53,,55,,43,,,,67,63,,65,66',
-',,,51,,,14,54,,,,,42,,49,,52,124,,50,72,68,,44,71,,,,,69,13,,,70,,,12',
-',,,,59,62,,73,60,53,,55,,43,,,,67,63,,65,66,,,,51,,,14,54,,,,,42,,49',
-',52,111,,50,72,68,,44,71,,,,,69,13,,,70,,,12,,,,,59,62,,73,60,53,,55',
-',43,,,,67,63,,65,66,,,,51,,,14,54,,,,,42,,49,,52,111,,50,72,68,,44,71',
-',,,,69,13,,,70,,,12,,,,,59,62,,73,60,53,,55,,43,,,,67,63,,65,66,,,,51',
-',,14,54,,,,,42,,49,,52,111,,50,72,68,,44,71,,,,,69,13,,,70,,,12,,,,',
-'59,62,,73,60,53,,55,297,43,,,,67,63,,65,66,,,,51,,,14,54,,,,,42,,49',
-',52,46,,50,72,68,,44,71,47,48,,,69,13,,,70,,,12,,,,,59,62,,73,60,53',
-'141,55,,43,,,,67,63,,65,66,,,,51,,,14,54,,,,,42,,49,,52,111,,50,72,68',
-',44,71,,,,,69,13,,,70,,,12,,,,,59,62,,73,60,53,143,55,,43,,,,67,63,',
-'65,66,,,,51,,,14,54,,,,,42,,49,,52,111,,50,72,68,,44,71,,,,,69,13,,',
-'70,,,12,,,,,59,62,,73,60,53,,55,146,43,,,,67,63,,65,66,,,,51,,,14,54',
-',,,,42,,49,,52,111,,50,72,68,,44,71,,,,,69,13,,,70,,,12,,,,,59,62,,73',
-'60,53,,55,,43,,,,67,63,,65,66,,,,51,,,14,54,,,,,42,,49,,52,111,,50,72',
-'68,,44,71,,,,,69,13,,,70,,,12,,,,,59,62,,73,60,53,,55,303,43,,,,67,63',
-',65,66,,,,51,,,14,54,,,,,42,,49,,52,46,,50,72,68,,44,71,47,48,,,69,13',
-',,70,,,12,,,,,59,62,,73,60,53,,55,146,43,,,,67,63,,65,66,,,,51,,,14',
-'54,,,,,42,,49,,52,46,,50,72,68,,44,71,47,48,,,69,13,,,70,,,12,,,,,59',
-'62,,73,60,53,,156,,43,,,,67,63,,65,66,,,,51,,,14,54,,,,,42,,49,,52,111',
-',50,72,68,,44,71,,,,,69,13,,,70,,,12,,,,,59,62,,73,60,53,,55,372,43',
-',,,67,63,,65,66,,,,51,,,14,54,,,,,42,,49,,52,46,,50,72,68,,44,71,47',
-'48,,,69,13,,,70,,,12,,,,,59,62,,73,60,53,,55,,43,,,,67,63,,65,66,,,',
-'51,,,14,54,,,,,42,,49,,52,46,,50,72,68,,44,71,47,48,,,69,13,,,70,,,12',
-',,,,59,62,,73,60,53,,55,,43,,,,67,63,,65,66,,,,51,,,14,54,,,,,42,,49',
-',52,46,,50,72,68,,44,71,47,48,,,69,13,,,70,,,12,,,,,59,62,,73,60,53',
-',55,,43,,,,67,63,,65,66,,,,51,,,14,54,,,,,42,,49,,52,46,,50,72,68,,44',
-'71,47,48,,,69,13,,,70,,,12,,,,,59,62,,73,60,53,,55,,43,,,,67,63,,65',
-'66,,,,51,,,14,54,,,,,42,,49,,52,46,,50,72,68,,44,71,47,48,,,69,13,,',
-'70,,,12,,,,,59,62,,73,60,53,,55,,43,,,,67,63,,65,66,,,,51,,,14,54,,',
-',,42,,49,,52,46,,50,72,68,,44,71,47,48,,,69,13,,,70,,,12,,,,,59,62,',
-'73,60,53,,55,,43,,,,67,63,,65,66,,,,51,,,14,54,,,,,42,,49,,52,46,,50',
-'72,68,,44,71,47,48,,,69,13,,,70,,,12,,,,,59,62,,73,60,53,,55,,43,,,',
-'67,63,,65,66,,,,51,,,14,54,,,,,42,,49,,52,46,,50,72,68,,44,71,47,48',
-',,69,13,,,70,,,12,,,,,59,62,,73,60,53,,55,,43,,,,67,63,,65,66,,,,51',
-',,14,54,,,,,42,,49,,52,111,,50,72,68,,44,71,,,,,69,13,,,70,,,12,,,,',
-'59,62,,73,60,53,,55,,43,,,,67,63,,65,66,,,,51,,,14,54,,,,,42,,49,,52',
-'111,,50,72,68,,44,71,,,,,69,13,,,70,,,12,,,,,59,62,,73,60,53,,55,,43',
-',,,67,63,,65,66,,,,51,,,14,54,,,,,42,,49,,52,111,,50,72,68,,44,71,,',
-',,69,13,,,70,,,12,,,,,59,62,,73,60,53,,55,,43,,,,67,63,,65,66,,,,51',
-',,14,54,,,,,42,,49,,52,111,,50,72,68,,44,71,,,,,69,13,,,70,,,12,,,,',
-'59,62,,73,60,53,,55,,43,,,,67,63,,65,66,,,,51,,,14,54,,,,,42,,49,,52',
-'111,,50,72,68,,44,71,,,,,69,13,,,70,,,12,,,,,59,62,,73,60,53,,55,,43',
-',,,67,63,,65,66,,,,51,,,14,54,,,,,42,,49,,52,111,,50,72,68,,44,71,,',
-',,69,13,,,70,,,12,,,,,59,62,,73,60,53,,55,,43,,,,67,63,,65,66,,,,51',
-',,14,54,,,,,42,,49,,52,111,,50,72,68,,44,71,,,,,69,13,,,70,,,12,,,,',
-'59,62,,73,60,53,,55,,43,,,,67,63,,65,66,,,,51,,,14,54,,,,,42,,49,,52',
-'111,,50,72,68,,44,71,,,,,69,13,,,70,,,12,,,,,59,62,,73,60,53,,55,,43',
-',,,67,63,,65,66,,,,51,,,14,54,,,,,42,,49,,52,111,,50,72,68,,44,71,,',
-',,69,13,,,70,,,12,,,,,59,62,,73,60,53,,55,,43,,,,67,63,,65,66,,,,51',
-',,14,54,,,,,42,,49,,52,111,,50,72,68,,44,71,,,,,69,13,,,70,,,12,,,,',
-'59,62,,73,60,53,,55,,43,,,,67,63,,65,66,,,,51,,,14,54,,,,,42,,49,,52',
-'111,,50,72,68,,44,71,,,,,69,13,,,70,,,12,,,,,59,62,,73,60,53,,55,,43',
-',,,67,63,,65,66,,,,51,,,14,54,,,,,42,,49,,52,111,,50,72,68,,44,71,,',
-',,69,13,,,70,,,12,,,,,59,62,,73,60,53,,55,,43,,,,67,63,,65,66,,,,51',
-',,14,54,,,,,42,,49,,52,111,,50,72,68,,44,71,,,,,69,13,,,70,,,12,,,,',
-'59,62,,73,60,53,,55,,43,,,,67,63,,65,66,,,,51,,,14,54,,,,,42,,49,,52',
-'111,,50,72,68,,44,71,,,,,69,13,,,70,,,12,,,,,59,62,,73,60,53,,55,,43',
-',,,67,63,,65,66,,,,51,,,14,54,,,,,42,,49,,52,111,,50,72,68,,44,71,,',
-',,69,13,,,70,,,12,,,,,59,62,,73,60,53,,55,,43,,,,67,63,,65,66,,,,51',
-',,14,54,,,,,42,,49,,52,111,,50,72,68,,44,71,,,,,69,13,,,70,,,12,,,,',
-'59,62,,73,60,53,,55,,43,,,,67,63,,65,66,,,,51,,,14,54,,,,,42,,49,,52',
-'111,,50,72,68,,44,71,,,,,69,13,,,70,,,12,,,,,59,62,,73,60,53,,55,,43',
-',,,67,63,,65,66,,,,51,,,14,54,,,,,42,,49,,52,111,,50,72,68,,44,71,,',
-',,69,13,,,70,,,12,,,,,59,62,,73,60,53,,55,,43,,,,67,63,,65,66,,,,51',
-',,14,54,,,,,42,,49,,52,111,,50,72,68,,44,71,,,,,69,13,,,70,,,12,,,,',
-'59,62,,73,60,53,,55,,43,,,,67,63,,65,66,,,,51,,,14,54,,,,,42,,49,,52',
-'111,,50,72,68,,44,71,,,,,69,13,,,70,,,12,,,,,59,62,,73,60,53,,55,,43',
-',,,67,63,,65,66,,,,51,,,14,54,,,,,42,,49,,52,111,,50,72,68,,44,71,,',
-',,69,13,,,70,,,12,,,,,59,62,,73,60,53,,55,,43,,,,67,63,,65,66,,,,51',
-',,14,54,,,,,42,,49,,52,111,,50,72,68,,44,71,,,,,69,13,,,70,,,12,,,,',
-'59,62,,73,60,53,,55,,43,,,,67,63,,65,66,,,,51,,,14,54,,,,,42,,49,,52',
-'111,,50,72,68,,44,71,,,,,69,13,,,70,,,12,,,,,59,62,,73,60,53,,55,357',
-'43,,,189,67,63,,65,66,,,,51,,,14,54,,,,,42,,49,,52,111,,50,72,68,,44',
-'71,,,,,69,13,,,70,,,12,,,,,59,62,,73,60,53,,55,,43,,,,67,63,,65,66,',
-',,51,,,14,54,,,,,192,209,203,210,52,204,212,205,201,199,,194,207,,,',
-',69,13,213,208,206,,,12,,,,,59,62,,73,60,53,,55,211,193,,,,67,63,,65',
-'66,,,,51,,,14,54,,,,,42,,49,,52,111,,50,72,68,,44,71,,,,,69,13,,,70',
-',,12,,,,,59,62,,73,60,53,,55,,43,,,,67,63,,65,66,,,,51,,,14,54,,,,,42',
-',49,,52,111,,50,72,68,,44,71,,,,,69,13,,,70,,,12,,,,,59,62,,73,60,53',
-',55,,43,,,,67,63,,65,66,,,,51,,,14,54,,,,,42,,49,,52,111,,50,72,68,',
-'44,71,,,,,69,13,,,70,,,12,,,,,59,62,,73,60,53,,55,,43,,,,67,63,,65,66',
-',,,51,,,14,54,,,,,42,,49,,52,111,,50,72,68,,44,71,,,,,69,13,,,70,,,12',
-',,,,59,62,,73,60,53,,55,,43,,,,67,63,,65,66,,,,51,,,14,54,,,,,42,,49',
-',52,111,,50,72,68,,44,71,,,,,69,13,,,70,,,12,,,,,59,62,,73,60,53,,55',
-',43,,,,67,63,,65,66,,,,51,,,14,54,,,,,42,,49,,52,111,,50,72,68,,44,71',
-',,,,69,13,,,70,,,12,,,,,59,62,,73,60,53,,55,305,43,,,,67,63,82,65,66',
-',,,51,,,14,54,,,,105,42,109,49,104,52,46,,50,72,68,,44,71,47,48,,,69',
-'13,,,70,,,12,108,,,,,,,73,,,89,88,,43,,84,85,67,63,,65,66,83,59,62,51',
-',60,53,54,55,,,,,,,,,,90,,,,,,,14,221,,,,,42,,49,,52,111,,50,72,68,',
-'44,71,,,,,69,13,,,70,,,12,,,,,59,62,,73,60,53,,55,,43,,,,67,63,,65,66',
-',,,51,,,14,54,,,,,42,,49,,52,111,,50,72,68,,44,71,,,,,69,13,,,70,,,12',
-',,,,59,62,,73,60,53,,55,,43,,,,67,63,82,65,66,,,,51,,,14,54,,,,105,42',
-'109,49,104,52,111,,50,72,68,,44,71,,,,,69,13,,,70,,,12,108,,,,,,,73',
-',,89,88,,43,,84,85,67,63,,65,66,83,59,62,51,,60,53,54,55,,,,,,,,,,90',
-',,,,,,14,229,,,,,42,,49,,52,111,,50,72,68,,44,71,,,,,69,13,,,70,,,12',
-',,,,59,62,,73,60,53,,55,,43,,,,67,63,,65,66,,,,51,,,14,54,,,,,42,,49',
-',52,46,,50,72,68,,44,71,47,48,,,69,13,,,70,,,12,,,,,59,62,,73,60,53',
-',55,,43,,,,67,63,,65,66,,,,51,,,14,54,,,,,42,,49,,52,111,,50,72,68,',
-'44,71,,,,,69,13,,,70,,,12,,,,,59,62,,73,60,53,325,55,,43,,,,67,63,,65',
-'66,,,,51,,,14,54,,,,,42,,49,,52,111,,50,72,68,,44,71,,,,,69,13,,,70',
-',,12,,,,,59,62,,73,60,53,,55,,43,,,,67,63,,65,66,,,,51,,,14,54,,,,,42',
-',49,,52,111,,50,72,68,,44,71,,,,,69,13,,,70,,,12,,,,,59,62,,73,60,53',
-',55,,43,,,,67,63,,65,66,,,,51,,,14,54,,,,,42,,49,,52,111,,50,72,68,',
-'44,71,,,,,69,13,,,70,,,12,,,,,59,62,,73,60,53,,55,,43,,,,67,63,,65,66',
-',,,51,,,14,54,,,,,42,,49,,52,111,,50,72,68,,44,71,,,,,69,13,,,70,,,12',
-',,,,59,62,,73,60,53,324,55,,43,,,,67,63,,65,66,,,,51,,,14,54,,,,,42',
-',49,,52,111,,50,72,68,,44,71,,,,,69,13,,,70,,,12,,,,,59,62,,73,60,53',
-',55,,43,,,,67,63,,65,66,,,,51,,,14,54,,,,,42,,49,,52,111,,50,72,68,',
-'44,71,,,,,69,13,,,70,,,12,,,,,59,62,,73,60,53,,55,327,43,,,,67,63,,65',
-'66,,,,51,,,14,54,,,,,42,,49,,52,111,,50,72,68,,44,71,,,,,69,13,,,70',
-',,12,,,,,59,62,,73,60,53,,55,,43,,,,67,63,,65,66,,,,51,,,14,54,,,,,42',
-',49,,52,111,,50,72,68,,44,71,,,,,69,13,,,70,,,12,,,,,59,62,,73,60,53',
-',55,,43,,,,67,63,,65,66,,,,51,,,14,54,,,,,192,209,203,210,52,204,212',
-'205,201,199,,194,207,,,,,69,13,213,208,206,,,12,,,,,,,,73,,,,,211,193',
-',,,67,63,,65,66,82,,,51,,,,54,,101,102,103,98,93,105,,109,,104,,,94',
-'96,95,97,,,,,,,,,,,,,,,,108,,,,100,99,,,86,87,89,88,91,92,,84,85,,,',
-',82,83,106,,,249,,,,101,102,103,98,93,105,,109,,104,90,,94,96,95,97',
-',,,,,,,,,,,,,,,108,,,,100,99,,,86,87,89,88,91,92,82,84,85,,,249,,,83',
-'101,102,103,98,93,105,,109,,104,,,94,96,95,97,,90,,,,,,,,,,,,,,108,',
-',,100,99,,,86,87,89,88,91,92,,84,85,,,,,82,83,233,,,,,,,101,102,103',
-'98,93,105,,109,,104,90,,94,96,95,97,,,,,,,,,,,,,,,,108,,,,100,99,,,86',
-'87,89,88,91,92,82,84,85,,,,,,83,101,102,103,98,93,105,,109,,104,,,94',
-'96,95,97,,90,,,,,,,,,,,,,,108,,,,100,99,,,86,87,89,88,91,92,,84,85,',
-',,,82,83,232,,,,,,,101,102,103,98,93,105,,109,,104,90,,94,96,95,97,',
-',,,,,,,,,,,,,,108,,,,100,99,,,86,87,89,88,91,92,,84,85,,,,,82,83,231',
-',,,,,,101,102,103,98,93,105,,109,,104,90,,94,96,95,97,,,,,,,,,,,,,,',
-',108,,,,100,99,,,86,87,89,88,91,92,,84,85,,,,,82,83,230,,,,,,,101,102',
-'103,98,93,105,,109,,104,90,,94,96,95,97,,,,,,,,,,,,,,,,108,,,,100,99',
-',,86,87,89,88,91,92,82,84,85,,,,,,83,101,102,103,98,93,105,,109,,104',
-',219,94,96,95,97,,90,,,,,,,,,,,,,,108,,,,100,99,,,86,87,89,88,91,92',
-'82,84,85,,,,,,83,101,102,103,98,93,105,,109,,104,,,94,96,95,97,,90,',
-',,,,,,,,,,,,108,,,,100,99,,,86,87,89,88,91,92,82,84,85,,,,,,83,101,102',
-'103,98,93,105,,109,,104,263,264,94,96,95,97,,90,,,,,,,,,,,,,,108,,,',
-'100,99,,,86,87,89,88,91,92,82,84,85,,,,,,83,101,102,103,98,93,105,,109',
-',104,,,94,96,95,97,,90,,,,,,,,,,,,,,108,,,,100,99,,,86,87,89,88,91,92',
-'82,84,85,,,,,,83,101,102,103,98,93,105,,109,,104,,,94,96,95,97,,90,',
-',,,,,,,,,,,,108,,,,100,99,,,86,87,89,88,91,92,82,84,85,,,,,,83,101,102',
-'103,98,93,105,,109,,104,,,94,96,95,97,,90,,,,,,,,,,,,,,108,,,,100,99',
-',,86,87,89,88,91,92,82,84,85,,,,,,83,101,102,103,98,93,105,,109,,104',
-',,94,96,95,97,,90,,,,,,,,,,,,,,108,,,,100,99,,,86,87,89,88,91,92,82',
-'84,85,,,,,,83,101,102,103,98,93,105,,109,,104,,,94,96,95,97,,90,,,,',
-',,,,,,,,,108,,,,100,99,,,86,87,89,88,91,92,82,84,85,,,,,,83,101,102',
-'103,98,93,105,,109,,104,,,94,96,95,97,,90,,,,,,,,,,,,,,108,,,,100,99',
-',,86,87,89,88,91,92,82,84,85,,,,,,83,101,102,103,98,93,105,273,109,',
-'104,,,94,96,95,97,,90,,,,,,,,,,,,,,108,,,,100,99,,,86,87,89,88,91,92',
-',84,85,,,,,82,83,106,,,,,,,101,102,103,98,93,105,,109,82,104,90,,94',
-'96,95,97,,,,,,,105,,109,,104,,,,,108,,,,100,99,,,86,87,89,88,91,92,',
-'84,85,108,,,82,,83,,,86,87,89,88,,,,84,85,105,,109,82,104,83,90,,,,',
-',,,,,,105,,109,,104,,90,,,108,,,,,,,,86,87,89,88,,,,84,85,108,,,82,',
-'83,,,86,87,89,88,91,92,,84,85,105,,109,82,104,83,90,,,,,,,,,,93,105',
-',109,,104,,90,94,,108,,,,,,,,86,87,89,88,91,92,,84,85,108,,,,,83,,,86',
-'87,89,88,91,92,82,84,85,,,,,,83,90,,,,93,105,,109,82,104,,,94,,,,,90',
-',,,93,105,,109,,104,,,94,,108,,,,,,,,86,87,89,88,91,92,,84,85,108,,',
-',,83,,,86,87,89,88,91,92,82,84,85,,,,,,83,90,,,,93,105,,109,,104,,82',
-'94,,,,,90,,,,,,98,93,105,,109,,104,,108,94,96,95,97,,,,86,87,89,88,91',
-'92,,84,85,,,,108,,83,,,82,,,86,87,89,88,91,92,,84,85,98,93,105,90,109',
-'83,104,,82,94,96,95,97,,,,,101,102,103,98,93,105,90,109,,104,,108,94',
-'96,95,97,99,,,86,87,89,88,91,92,,84,85,,,,108,,83,,100,99,,,86,87,89',
-'88,91,92,82,84,85,,,269,90,,83,101,102,103,98,93,105,,109,,104,,,94',
-'96,95,97,,90,,,,,,,,,,,,,,108,,,,100,99,,,86,87,89,88,91,92,82,84,85',
-',,,,,83,101,102,103,98,93,105,,109,,104,,,94,96,95,97,,90,,,,,,,,,,',
-',,,108,,,,100,99,,,86,87,89,88,91,92,82,84,85,,,,,,83,101,102,103,98',
-'93,105,,109,,104,,,94,96,95,97,,90,,,,,,,,,,,,,,108,,,,100,99,,,86,87',
-'89,88,91,92,,84,85,,287,209,286,210,83,284,212,288,282,281,,283,285',
-',,,,,,213,208,289,90,287,209,286,210,,284,212,288,282,281,,283,285,',
-'211,290,,,,213,208,289,287,209,286,210,,284,212,288,282,281,,283,285',
-',,211,290,,,213,208,289,,,,,,,,,,,,,,,,211,290' ]
- racc_action_table = arr = ::Array.new(6559, nil)
+'58,61,388,275,59,53,317,54,-236,80,-238,-237,236,133,-129,-234,-239',
+'-225,256,255,318,392,278,101,18,104,278,99,100,333,42,373,45,237,47',
+'12,111,46,36,39,110,44,37,10,11,276,134,66,17,103,-236,38,-238,-237',
+'15,16,-129,-234,-239,-225,58,61,67,334,59,53,236,54,43,277,79,81,35',
+'62,278,64,65,63,107,66,48,49,51,50,18,111,52,237,79,110,42,236,45,79',
+'47,113,236,46,36,39,252,44,37,253,66,312,111,66,17,66,110,38,237,254',
+'15,16,369,237,368,111,58,61,67,110,59,53,229,54,43,340,111,265,35,62',
+'110,64,65,359,267,268,48,49,51,50,18,111,52,307,71,110,42,369,45,368',
+'47,12,236,46,36,39,69,44,37,10,11,342,273,66,17,66,329,38,58,61,15,16',
+'59,237,254,326,58,61,67,249,59,53,249,54,43,72,73,74,35,62,350,64,65',
+'351,273,274,48,49,51,50,18,353,52,248,247,356,42,316,45,312,47,12,361',
+'46,36,39,362,44,37,10,11,236,225,66,17,228,226,38,366,313,15,16,370',
+'372,75,77,76,78,67,312,249,225,379,79,43,381,299,273,35,62,79,64,65',
+'215,214,71,48,49,51,50,58,61,52,153,59,53,385,54,310,150,119,79,273',
+'148,391,306,119,302,120,395,372,397,398,399,18,58,61,119,402,59,42,403',
+'45,404,47,12,300,46,36,39,79,44,37,10,11,71,412,66,17,68,414,38,415',
+'416,15,16,302,,,,,,67,,133,,,130,43,,,,35,62,,64,65,,,,48,49,51,50,58',
+'61,52,67,59,53,,54,408,80,,,,134,62,,,,,,,,,101,18,104,,99,100,,42,',
+'45,,47,12,,46,36,39,,44,37,10,11,,,66,17,103,,38,,,15,16,,,,,58,61,67',
+',59,53,,54,43,,,81,35,62,,64,65,,,,48,49,51,50,18,,52,,,,42,,45,,47',
+'113,,46,36,39,,44,37,,,,,66,17,,,38,,,15,16,,,,,58,61,67,,59,53,,54',
+'43,,,,35,62,,64,65,,,,48,49,51,50,18,,52,,,,42,,45,,47,12,,46,36,39',
+',44,37,10,11,,,66,17,,,38,,,15,16,,,,,58,61,67,,59,53,,54,43,,,,35,62',
+',64,65,,,,48,49,51,50,18,,52,,,,42,,45,,47,12,,46,36,39,,44,37,10,11',
+',,66,17,,,38,,,15,16,,,,,,,67,,,,,,43,,,,35,62,,64,65,,,,48,49,51,50',
+'58,61,52,,59,53,,54,406,80,,,,,,,,,,,,,,101,18,104,,99,100,,42,,45,',
+'47,12,,46,36,39,,44,37,10,11,,,66,17,103,,38,,,15,16,,,,,58,61,67,,59',
+'53,,54,43,,,,35,62,,64,65,,,,48,49,51,50,18,,52,,,,42,,45,,47,113,,46',
+'36,39,,44,37,,,,,66,17,,,38,,,15,16,,,,,58,61,67,,59,53,,54,43,,,,35',
+'62,,64,65,,,,48,49,51,50,18,,52,,,,42,,45,,47,113,,46,36,39,,44,37,',
+',,,66,17,,,38,,,15,16,,,,,58,61,67,,59,53,,54,43,,,,35,62,,64,65,,,',
+'48,49,51,50,18,,52,,,,42,,45,,47,113,,46,36,39,,44,37,,,,,66,17,,,38',
+',,15,16,,,,,58,61,67,,59,53,,54,43,,,,35,62,,64,65,,,,48,49,51,50,18',
+',52,,,,42,,45,,47,12,,46,36,39,,44,37,10,11,,,66,17,,,38,,,15,16,,,',
+',,,67,,,,,,43,,,,35,62,,64,65,,,,48,49,51,50,58,61,52,,59,53,,54,401',
+'80,,,,,,,,,,,,,,101,18,104,,99,100,,42,,45,,47,12,,46,36,39,,44,37,10',
+'11,,,66,17,103,,38,,,15,16,,,,,58,61,67,,59,53,,54,43,,,,35,62,,64,65',
+',,,48,49,51,50,18,,52,,,,42,,45,,47,113,,46,36,39,,44,37,,,,,66,17,',
+',38,,,15,16,,,,,58,61,67,,59,53,,54,43,,,,35,62,,64,65,,,,48,49,51,50',
+'18,,52,,,,42,,45,,47,113,,46,36,39,,44,37,,,,,66,17,,,38,,,15,16,,,',
+',58,61,67,,59,53,,54,43,,,,35,62,,64,65,,,,48,49,51,50,18,,52,,,,42',
+',45,,47,113,,46,36,39,,44,37,,,,,66,17,,,38,,,15,16,,,,,58,61,67,,59',
+'53,,54,43,,,,35,62,,64,65,,,,48,49,51,50,18,,52,,,,42,,45,,47,113,,46',
+'36,39,,44,37,,,,,66,17,,,38,,,15,16,,,,,,,67,,,,,,43,,,,35,62,,64,65',
+',,,48,49,51,50,58,61,52,,59,53,,54,320,,,,,,,,,,,,,,,,18,58,61,,,59',
+'42,,45,,47,12,,46,36,39,,44,37,10,11,,,66,17,,,38,,,15,16,,,,,,,67,',
+'133,,,130,43,,,,35,62,,64,65,,,,48,49,51,50,58,61,52,67,59,53,,54,322',
+'80,,,,134,62,,,,,,,,,101,18,104,,99,100,,42,,45,,47,12,,46,36,39,,44',
+'37,10,11,,,66,17,103,,38,,,15,16,,,,,58,61,67,,59,53,137,54,43,,,,35',
+'62,,64,65,,,,48,49,51,50,18,,52,,,,42,,45,,47,12,,46,36,39,,44,37,10',
+'11,,,66,17,,,38,,,15,16,,,,,58,61,67,,59,53,139,54,43,,,,35,62,,64,65',
+',,,48,49,51,50,18,,52,,,,42,,45,,47,12,,46,36,39,,44,37,10,11,,,66,17',
+',,38,,,15,16,,,,,,,67,,,,,,43,,,,35,62,,64,65,,,,48,49,51,50,58,61,52',
+',59,53,,54,141,80,,,,,,,,,,,,,,101,18,104,,99,100,,42,,45,,47,12,,46',
+'36,39,,44,37,10,11,,,66,17,103,,38,,,15,16,,,,,58,61,67,,59,53,,54,43',
+',,,35,62,,64,65,,,,48,49,51,50,18,,52,,,,42,,45,,47,12,,46,36,39,,44',
+'37,10,11,,,66,17,,,38,,,15,16,,,,,58,61,67,,59,53,,54,43,,,,35,62,,64',
+'65,,,,48,49,51,50,18,,52,,,,42,,45,,47,12,,46,36,39,,44,37,10,11,,,66',
+'17,,,38,,,15,16,,,,,58,61,67,,59,53,,54,43,,,,35,62,,64,65,,,,48,49',
+'51,50,18,,52,,,,42,,45,,47,113,,46,36,39,,44,37,,,,,66,17,,,38,,,15',
+'16,,,,,58,61,67,,59,53,,152,43,,,,35,62,,64,65,,,,48,49,51,50,18,,52',
+',,,42,,45,,47,113,,46,36,39,,44,37,,,,,66,17,,,38,,,15,16,,,,,58,61',
+'67,,59,53,,54,43,,,,35,62,,64,65,,,,48,49,51,50,18,,52,,,,42,,45,,47',
+'113,,46,36,39,,44,37,,,,,66,17,,,38,,,15,16,,,,,58,61,67,,59,53,,54',
+'43,,,,35,62,,64,65,,,,48,49,51,50,18,,52,,,,42,,45,,47,12,,46,36,39',
+',44,37,10,11,,,66,17,,,38,,,15,16,,,,,58,61,67,,59,53,,54,43,,,,35,62',
+',64,65,,,,48,49,51,50,18,,52,,,,42,,45,,47,113,,46,36,39,,44,37,,,,',
+'66,17,,,38,,,15,16,,,,,58,61,67,,59,53,,54,43,,,,35,62,,64,65,,,,48',
+'49,51,50,18,,52,,,,42,,45,,47,12,,46,36,39,,44,37,10,11,,,66,17,,,38',
+',,15,16,,,,,58,61,67,,59,53,,54,43,,,,35,62,,64,65,,,,48,49,51,50,18',
+',52,,,,42,,45,,47,12,,46,36,39,,44,37,10,11,,,66,17,,,38,,,15,16,,,',
+',58,61,67,,59,53,,54,43,,,,35,62,,64,65,,,,48,49,51,50,18,,52,,,,42',
+',45,,47,12,,46,36,39,,44,37,10,11,,,66,17,,,38,,,15,16,,,,,58,61,67',
+',59,53,,54,43,,,,35,62,,64,65,,,,48,49,51,50,18,,52,,,,42,,45,,47,12',
+',46,36,39,,44,37,10,11,,,66,17,,,38,,,15,16,,,,,58,61,67,,59,53,,54',
+'43,,,,35,62,,64,65,,,,48,49,51,50,18,,52,,,,42,,45,,47,12,,46,36,39',
+',44,37,10,11,,,66,17,,,38,,,15,16,,,,,58,61,67,,59,53,,54,43,,,,35,62',
+',64,65,,,,48,49,51,50,18,,52,,,,42,,45,,47,12,,46,36,39,,44,37,10,11',
+',,66,17,,,38,,,15,16,,,,,58,61,67,,59,53,,54,43,,,,35,62,,64,65,,,,48',
+'49,51,50,18,,52,,,,42,,45,,47,12,,46,36,39,,44,37,10,11,,,66,17,,,38',
+',,15,16,,,,,58,61,67,,59,53,,54,43,,,,35,62,,64,65,,,,48,49,51,50,18',
+',52,,,,42,,45,,47,12,,46,36,39,,44,37,10,11,,,66,17,,,38,,,15,16,,,',
+',58,61,67,,59,53,,54,43,,,,35,62,,64,65,,,,48,49,51,50,18,,52,,,,169',
+'183,175,184,47,176,186,177,36,168,,171,166,,,,,66,17,187,182,167,,,15',
+'165,,,,,,,67,,,,,185,170,,,,35,62,,64,65,,,,178,179,181,180,58,61,52',
+',59,53,,54,,,,,,,,,,,,,,,,,18,,,,,,42,,45,,47,113,,46,36,39,,44,37,',
+',,,66,17,,,38,,,15,16,,,,,58,61,67,,59,53,,54,43,,,,35,62,,64,65,,,',
+'48,49,51,50,18,,52,,,,42,,45,,47,113,,46,36,39,,44,37,,,,,66,17,,,38',
+',,15,16,,,,,58,61,67,,59,53,,54,43,,,,35,62,,64,65,,,,48,49,51,50,18',
+',52,,,,42,,45,,47,113,,46,36,39,,44,37,,,,,66,17,,,38,,,15,16,,,,,58',
+'61,67,,59,53,,54,43,,,,35,62,,64,65,,,,48,49,51,50,18,,52,,,,42,,45',
+',47,113,,46,36,39,,44,37,,,,,66,17,,,38,,,15,16,,,,,58,61,67,,59,53',
+',54,43,,,,35,62,,64,65,,,,48,49,51,50,18,,52,,,,42,,45,,47,113,,46,36',
+'39,,44,37,,,,,66,17,,,38,,,15,16,,,,,58,61,67,,59,53,,54,43,,,,35,62',
+',64,65,,,,48,49,51,50,18,,52,,,,42,,45,,47,113,,46,36,39,,44,37,,,,',
+'66,17,,,38,,,15,16,,,,,58,61,67,,59,53,,54,43,,,,35,62,,64,65,,,,48',
+'49,51,50,18,,52,,,,42,,45,,47,113,,46,36,39,,44,37,,,,,66,17,,,38,,',
+'15,16,,,,,58,61,67,,59,53,,54,43,,,,35,62,,64,65,,,,48,49,51,50,18,',
+'52,,,,42,,45,,47,113,,46,36,39,,44,37,,,,,66,17,,,38,,,15,16,,,,,58',
+'61,67,,59,53,,54,43,,,,35,62,,64,65,,,,48,49,51,50,18,,52,,,,42,,45',
+',47,113,,46,36,39,,44,37,,,,,66,17,,,38,,,15,16,,,,,58,61,67,,59,53',
+',54,43,,,,35,62,,64,65,,,,48,49,51,50,18,,52,,,,42,,45,,47,113,,46,36',
+'39,,44,37,,,,,66,17,,,38,,,15,16,,,,,58,61,67,,59,53,,54,43,,,,35,62',
+',64,65,,,,48,49,51,50,18,,52,,,,42,,45,,47,113,,46,36,39,,44,37,,,,',
+'66,17,,,38,,,15,16,,,,,58,61,67,,59,53,,54,43,,,,35,62,,64,65,,,,48',
+'49,51,50,18,,52,,,,42,,45,,47,113,,46,36,39,,44,37,,,,,66,17,,,38,,',
+'15,16,,,,,58,61,67,,59,53,,54,43,,,,35,62,,64,65,,,,48,49,51,50,18,',
+'52,,,,42,,45,,47,113,,46,36,39,,44,37,,,,,66,17,,,38,,,15,16,,,,,58',
+'61,67,,59,53,,54,43,,,,35,62,,64,65,,,,48,49,51,50,18,,52,,,,42,,45',
+',47,113,,46,36,39,,44,37,,,,,66,17,,,38,,,15,16,,,,,58,61,67,,59,53',
+',54,43,,,,35,62,,64,65,,,,48,49,51,50,18,,52,,,,42,,45,,47,113,,46,36',
+'39,,44,37,,,,,66,17,,,38,,,15,16,,,,,58,61,67,,59,53,,54,43,,,,35,62',
+',64,65,,,,48,49,51,50,18,,52,,,,42,,45,,47,113,,46,36,39,,44,37,,,,',
+'66,17,,,38,,,15,16,,,,,58,61,67,,59,53,,54,43,,,,35,62,,64,65,,,,48',
+'49,51,50,18,,52,,,,42,,45,,47,113,,46,36,39,,44,37,,,,,66,17,,,38,,',
+'15,16,,,,,58,61,67,,59,53,,54,43,,,,35,62,,64,65,,,,48,49,51,50,18,',
+'52,,,,42,,45,,47,113,,46,36,39,,44,37,,,,,66,17,,,38,,,15,16,,,,,58',
+'61,67,,59,53,,54,43,,,,35,62,,64,65,,,,48,49,51,50,18,,52,,,,42,,45',
+',47,113,,46,36,39,,44,37,,,,,66,17,,,38,,,15,16,,,,,58,61,67,,59,53',
+',54,43,,,,35,62,,64,65,,,,48,49,51,50,18,,52,,,,42,,45,,47,113,,46,36',
+'39,,44,37,,,,,66,17,,,38,,,15,16,,,,,58,61,67,,59,53,,54,43,,,211,35',
+'62,,64,65,,,,48,49,51,50,18,213,52,,,,42,,45,,47,12,,46,36,39,,44,37',
+'10,11,,,66,17,,,38,,,15,16,,,,,58,61,67,,59,53,,54,43,,,,35,62,,64,65',
+',,,48,49,51,50,18,,52,,,,42,,45,,47,113,,46,36,39,,44,37,,,,,66,17,',
+',38,,,15,16,,,,,58,61,67,,59,53,,54,43,,,,35,62,,64,65,,,,48,49,51,50',
+'18,,52,,,,42,,45,,47,113,,46,36,39,,44,37,,,,,66,17,,,38,,,15,16,,,',
+',58,61,67,,59,53,,54,43,,,,35,62,,64,65,,,,48,49,51,50,18,,52,,,,42',
+',45,,47,113,,46,36,39,,44,37,,,,,66,17,,,38,,,15,16,,,,,58,61,67,,59',
+'53,,54,43,,,,35,62,,64,65,,,,48,49,51,50,18,,52,,,,42,,45,,47,113,,46',
+'36,39,,44,37,,,,,66,17,,,38,,,15,16,,,,,58,61,67,,59,53,,54,43,,274',
+',35,62,,64,65,,,,48,49,51,50,18,,52,,,,42,,45,,47,113,,46,36,39,,44',
+'37,,,,,66,17,,,38,,,15,16,,,,,58,61,67,,59,53,,54,43,,,,35,62,,64,65',
+',,,48,49,51,50,18,,52,,,,42,,45,,47,12,,46,36,39,,44,37,10,11,,,66,17',
+',,38,,,15,16,,,,,58,61,67,,59,53,,54,43,,,,35,62,,64,65,,,,48,49,51',
+'50,18,,52,,,,42,,45,,47,113,,46,36,39,,44,37,,,,,66,17,,,38,,,15,16',
+',,,,58,61,67,,59,53,,54,43,,,,35,62,,64,65,,,,48,49,51,50,18,,52,,,',
+'42,,45,,47,12,,46,36,39,,44,37,10,11,,,66,17,,,38,,,15,16,,,,,,,67,',
+',,,,43,,,,35,62,,64,65,,,,48,49,51,50,58,61,52,,59,53,,54,335,,,,,,',
+',,,,,,,,,18,58,61,,,59,42,,45,,47,12,,46,36,39,,44,37,10,11,,,66,17',
+',,38,,,15,16,,,,,,,67,,133,,,130,43,,,,35,62,,64,65,,,,48,49,51,50,58',
+'61,52,67,59,53,,54,374,,,,,134,62,,,,,,,,,,18,,,,,,42,,45,,47,113,,46',
+'36,39,,44,37,,,,,66,17,,,38,,,15,16,,,,,58,61,67,,59,53,,54,43,,,,35',
+'62,,64,65,,,,48,49,51,50,18,,52,,,,42,,45,,47,12,,46,36,39,,44,37,10',
+'11,,,66,17,,,38,,,15,16,,,,,58,61,67,,59,53,,54,43,,,,35,62,,64,65,',
+',,48,49,51,50,18,,52,,,,42,,45,,47,12,,46,36,39,,44,37,10,11,,,66,17',
+',,38,,,15,16,,,,,58,61,67,,59,53,,54,43,,,,35,62,,64,65,,,,48,49,51',
+'50,18,,52,,,,42,,45,,47,12,,46,36,39,,44,37,10,11,,,66,17,,,38,,,15',
+'16,,,,,58,61,67,,59,53,,54,43,,,,35,62,,64,65,,,,48,49,51,50,18,,52',
+',,,42,,45,,47,113,,46,36,39,,44,37,,,,,66,17,,,38,,,15,16,,,,,,,67,',
+',,,,43,,,,35,62,,64,65,,,,48,49,51,50,58,61,52,,59,53,,54,141,,,,,,',
+',,,,,,,,,18,,,,,,42,,45,,47,12,,46,36,39,,44,37,10,11,,,66,17,,,38,',
+',15,16,,,,,58,61,67,,59,53,,54,43,,,,35,62,,64,65,,,,48,49,51,50,18',
+'241,52,,,,42,,45,,47,12,,46,36,39,,44,37,10,11,,,66,17,,,38,,,15,16',
+',,,,58,61,67,,59,53,,54,43,,,,35,62,,64,65,,,,48,49,51,50,18,,52,,,',
+'42,,45,,47,12,,46,36,39,,44,37,10,11,,,66,17,,,38,,,15,16,,,,,58,61',
+'67,,59,53,,54,43,,,,35,62,,64,65,,,,48,49,51,50,18,,52,,,,42,,45,,47',
+'113,,46,36,39,,44,37,,,,,66,17,,,38,,,15,16,,,,,58,61,67,,59,53,,54',
+'43,,,,35,62,,64,65,,,,48,49,51,50,18,,52,,,,42,,45,,47,113,,46,36,39',
+',44,37,,,,,66,17,,,38,,,15,16,,,,,58,61,67,,59,53,,54,43,,,,35,62,,64',
+'65,,,,48,49,51,50,18,,52,,,,42,,45,,47,113,,46,36,39,,44,37,,,,,66,17',
+',,38,,,15,16,,,,,58,61,67,,59,53,,54,43,,,,35,62,,64,65,,,,48,49,51',
+'50,18,,52,,,,42,,45,,47,113,,46,36,39,,44,37,,,,,66,17,,,38,,,15,16',
+',,,,58,61,67,,59,53,,54,43,,,,35,62,,64,65,,,,48,49,51,50,18,,52,,,',
+'42,,45,,47,113,,46,36,39,,44,37,,,,,66,17,,,38,,,15,16,,,,,58,61,67',
+',59,53,,54,43,,,,35,62,,64,65,,,,48,49,51,50,18,,52,,,,42,,45,,47,113',
+',46,36,39,,44,37,,,,,66,17,,,38,,,15,16,,,,,58,61,67,,59,53,,54,43,',
+',,35,62,,64,65,,,,48,49,51,50,18,,52,,,,42,,45,,47,113,,46,36,39,,44',
+'37,,,,,66,17,,,38,,,15,16,,,,,,,67,,,,,,43,,,,35,62,,64,65,80,,,48,49',
+'51,50,,,52,,,96,91,101,,104,,99,100,,92,94,93,95,,58,61,,,59,,,,,,,',
+',,103,,,,98,97,,,84,85,87,86,89,90,,82,83,80,,244,,,81,,,133,,,130,96',
+'91,101,,104,,99,100,,92,94,93,95,,88,,,,,67,,,,,,,,,103,134,62,,98,97',
+',,84,85,87,86,89,90,,82,83,80,,243,,,81,,,,,,,96,91,101,,104,80,99,100',
+',92,94,93,95,,88,,,,,101,,104,,99,100,,,,103,,,,98,97,,,84,85,87,86',
+'89,90,,82,83,103,,,,,81,,,,,87,86,,,,82,83,80,,242,,,81,,,,88,,,96,91',
+'101,,104,80,99,100,,92,94,93,95,,88,,,,,101,,104,,99,100,,,,103,,,,98',
+'97,,80,84,85,87,86,89,90,,82,83,103,,96,91,101,81,104,,99,100,,92,94',
+'93,95,82,83,,,,,,81,,,,88,,,,103,,,,98,97,,80,84,85,87,86,89,90,,82',
+'83,,,96,91,101,81,104,,99,100,,92,94,93,95,,,,,,,,,,,,88,,,,103,,,,98',
+'97,,,84,85,87,86,89,90,,82,83,,,,,,81,80,,,,,,,,,,267,268,96,91,101',
+'303,104,80,99,100,88,92,94,93,95,,,,,,,101,,104,,99,100,,,,103,,,,98',
+'97,,,84,85,87,86,89,90,,82,83,103,,,,,81,,,84,85,87,86,,,,82,83,80,',
+',,,81,,,,88,,,96,91,101,,104,,99,100,,92,94,93,95,,88,,,,,,,,,,,,,,103',
+',,,98,97,,,84,85,87,86,89,90,80,82,83,,,279,,,81,,,,96,91,101,,104,80',
+'99,100,,92,94,93,95,,,,,88,,101,,104,,99,100,,,,103,,,,98,97,,80,84',
+'85,87,86,89,90,,82,83,103,,96,91,101,81,104,,99,100,,92,94,93,95,82',
+'83,,,,,,81,,,,88,,,,103,,,,,97,,80,84,85,87,86,89,90,,82,83,,,96,91',
+'101,81,104,,99,100,,92,94,93,95,,,,,,,,,,,,88,,,,103,,,,98,97,,,84,85',
+'87,86,89,90,80,82,83,,,,,,81,,,,96,91,101,271,104,80,99,100,,92,94,93',
+'95,,,,,88,,101,,104,,99,100,,,,103,,,,98,97,,80,84,85,87,86,89,90,,82',
+'83,103,,96,91,101,81,104,,99,100,,92,94,93,95,82,83,,,,,,81,,,,88,,',
+',103,,,,98,97,,80,84,85,87,86,89,90,,82,83,,,96,91,101,81,104,,99,100',
+',92,94,93,95,,,,,,,,,,,,88,,,,103,,,,98,97,,80,84,85,87,86,89,90,,82',
+'83,,,96,91,101,81,104,,99,100,,92,94,93,95,80,,,,,,,,,,,88,,91,101,103',
+'104,,99,100,80,92,,84,85,87,86,89,90,,82,83,,,101,,104,81,99,100,103',
+',,,,,,,84,85,87,86,89,90,,82,83,,88,,103,,81,,,,,,84,85,87,86,80,,,82',
+'83,,,,,,81,88,96,91,101,,104,,99,100,80,92,94,93,95,,,,,,,88,,,101,',
+'104,,99,100,103,,,,98,97,,,84,85,87,86,89,90,,82,83,,,,103,,81,,,,,',
+',,87,86,80,,,82,83,,,,,,81,88,96,91,101,,104,,99,100,80,92,94,93,95',
+',,,,,,88,,91,101,,104,,99,100,103,92,,,98,97,,,84,85,87,86,89,90,,82',
+'83,,,,103,,81,,,,,,84,85,87,86,89,90,80,82,83,,,,,,81,88,,,96,91,101',
+',104,80,99,100,,92,94,93,95,,,,,88,,101,,104,,99,100,,,,103,,,,98,97',
+',,84,85,87,86,89,90,,82,83,103,,,,,81,,,84,85,87,86,89,90,80,82,83,',
+',,,,81,,,,88,,101,,104,80,99,100,,,,,,,,,,88,91,101,,104,,99,100,,92',
+',103,,,,,,,,84,85,87,86,89,90,,82,83,103,,,,,81,,,84,85,87,86,89,90',
+'80,82,83,,,,,,81,,,,88,91,101,,104,,99,100,,92,,,,,,,,88,,,,,,,,,,,103',
+',,,,,,,84,85,87,86,89,90,,82,83,,,,,,81,,,291,183,290,184,,288,186,292',
+',285,,287,289,,,,,,88,187,182,293,,,,286,,,,,,,,,,,,185,294,,,,,,,,',
+',,,297,298,296,295,291,183,290,184,,288,186,292,,285,,287,289,,,,,,',
+'187,182,293,,,,286,,,,,,,,,,,,185,294,,,,,,,,,,,,297,298,296,295,291',
+'183,290,184,,288,186,292,,285,,287,289,,,,,,,187,182,293,,,,286,,,,',
+',,,,,,,185,294,,,,,,,,,,,,297,298,296,295,291,183,290,184,,288,186,292',
+',285,,287,289,,,,,,,187,182,293,,,,286,,,,,,,,,,,,185,294,,,,,,,,,,',
+',297,298,296,295' ]
+ racc_action_table = arr = ::Array.new(6809, nil)
idx = 0
clist.each do |str|
str.split(',', -1).each do |i|
@@ -241,231 +253,241 @@ clist = [
end
clist = [
-'0,0,132,202,0,0,238,0,199,306,207,201,228,206,154,238,40,164,306,241',
-'241,117,164,241,0,145,228,132,145,315,0,126,0,315,0,0,315,0,0,0,266',
-'0,0,0,0,202,235,0,0,154,199,0,207,201,0,206,117,244,241,385,385,241',
-'0,385,385,142,385,385,0,140,142,270,0,0,140,0,0,0,205,205,0,241,205',
-'385,0,348,204,348,131,385,204,385,241,385,385,49,385,385,385,49,385',
-'385,385,385,152,152,385,385,152,274,385,203,111,385,116,203,111,205',
-'5,5,205,385,5,5,303,5,303,385,331,331,276,385,385,158,385,385,124,243',
-'243,385,205,243,5,385,8,8,8,8,5,227,5,205,5,5,225,5,5,5,5,5,5,5,5,280',
-'124,5,5,221,124,5,150,150,5,294,296,298,243,384,384,243,5,384,384,299',
-'384,384,5,107,302,236,5,5,304,5,5,46,50,50,5,243,50,384,5,305,220,105',
-'309,384,310,384,243,384,384,311,384,384,384,312,384,384,384,384,313',
-'46,384,384,218,46,384,317,76,384,74,64,63,50,382,382,50,384,382,382',
-'330,382,382,384,134,216,333,384,384,196,384,384,335,47,195,384,50,342',
-'382,384,343,41,239,260,382,351,382,50,382,382,352,382,382,382,354,382',
-'382,382,382,355,359,382,382,360,361,382,39,367,382,368,371,240,6,189',
-'189,1,382,189,189,389,189,393,382,395,397,,382,382,167,382,382,,,,382',
-',,189,382,,,,167,189,167,189,167,189,189,,189,189,189,,189,189,,,,,189',
-'189,,,189,,,189,167,,,,12,12,,189,12,12,,12,,189,,,,189,189,170,189',
-'189,167,,,189,,,12,189,,,,170,12,170,12,170,12,12,,12,12,12,,12,12,',
-',,,12,12,,,12,,,12,170,,,,13,13,,12,13,13,,13,,12,,170,170,12,12,171',
-'12,12,170,,,12,,,13,12,,,,171,13,171,13,171,13,13,,13,13,13,,13,13,',
-',,,13,13,,,13,,,13,171,,,,14,14,,13,14,14,,14,,13,,171,171,13,13,166',
-'13,13,171,,,13,,,14,13,,,,166,14,166,14,166,14,14,,14,14,14,,14,14,',
-',,,14,14,,,14,,,14,166,,,,363,363,,14,363,363,,363,,14,,,,14,14,172',
-'14,14,166,,,14,,,363,14,,,,172,363,172,363,172,363,363,,363,363,363',
-',363,363,363,363,,,363,363,,,363,,,363,172,,,,350,350,,363,350,350,',
-'350,,363,,172,172,363,363,165,363,363,172,,,363,,,350,363,,,,165,350',
-'165,350,165,350,350,,350,350,350,,350,350,,,,,350,350,,,350,,,350,165',
-',,,192,192,,350,192,192,,192,,350,,,,350,350,112,350,350,,,,350,,,192',
-'350,,,,112,192,112,192,112,192,192,,192,192,192,,192,192,,,,,192,192',
-',,192,,,192,112,,,,42,42,,192,42,42,,42,,192,,,,192,192,110,192,192',
-',,,192,,,42,192,,,,110,42,110,42,110,42,42,,42,42,42,,42,42,,,,,42,42',
-',,42,,,42,110,,,,43,43,,42,43,43,,43,,42,,,,42,42,,42,42,,,,42,,,43',
-'42,,,,,43,,43,,43,43,,43,43,43,,43,43,,,,,43,43,,,43,,,43,,,,,44,44',
-',43,44,44,,44,,43,,,,43,43,,43,43,,,,43,,,44,43,,,,,44,,44,,44,44,,44',
-'44,44,,44,44,,,,,44,44,,,44,,,44,,,,,45,45,,44,45,45,,45,,44,,,,44,44',
-',44,44,,,,44,,,45,44,,,,,45,,45,,45,45,,45,45,45,,45,45,,,,,45,45,,',
-'45,,,45,,,,,193,193,,45,193,193,,193,,45,,,,45,45,,45,45,,,,45,,,193',
-'45,,,,,193,,193,,193,193,,193,193,193,,193,193,,,,,193,193,,,193,,,193',
-',,,,194,194,,193,194,194,,194,,193,,,,193,193,,193,193,,,,193,,,194',
-'193,,,,,194,,194,,194,194,,194,194,194,,194,194,,,,,194,194,,,194,,',
-'194,,,,,334,334,,194,334,334,,334,,194,,,,194,194,,194,194,,,,194,,',
-'334,194,,,,,334,,334,,334,334,,334,334,334,,334,334,,,,,334,334,,,334',
-',,334,,,,,223,223,,334,223,223,,223,223,334,,,,334,334,,334,334,,,,334',
-',,223,334,,,,,223,,223,,223,223,,223,223,223,,223,223,223,223,,,223',
-'223,,,223,,,223,,,,,53,53,,223,53,53,53,53,,223,,,,223,223,,223,223',
-',,,223,,,53,223,,,,,53,,53,,53,53,,53,53,53,,53,53,,,,,53,53,,,53,,',
-'53,,,,,54,54,,53,54,54,54,54,,53,,,,53,53,,53,53,,,,53,,,54,53,,,,,54',
-',54,,54,54,,54,54,54,,54,54,,,,,54,54,,,54,,,54,,,,,55,55,,54,55,55',
-',55,55,54,,,,54,54,,54,54,,,,54,,,55,54,,,,,55,,55,,55,55,,55,55,55',
-',55,55,,,,,55,55,,,55,,,55,,,,,61,61,,55,61,61,,61,,55,,,,55,55,,55',
-'55,,,,55,,,61,55,,,,,61,,61,,61,61,,61,61,61,,61,61,,,,,61,61,,,61,',
-',61,,,,,230,230,,61,230,230,,230,230,61,,,,61,61,,61,61,,,,61,,,230',
-'61,,,,,230,,230,,230,230,,230,230,230,,230,230,230,230,,,230,230,,,230',
-',,230,,,,,156,156,,230,156,156,,156,156,230,,,,230,230,,230,230,,,,230',
-',,156,230,,,,,156,,156,,156,156,,156,156,156,,156,156,156,156,,,156',
-'156,,,156,,,156,,,,,66,66,,156,66,66,,66,,156,,,,156,156,,156,156,,',
-',156,,,66,156,,,,,66,,66,,66,66,,66,66,66,,66,66,,,,,66,66,,,66,,,66',
-',,,,319,319,,66,319,319,,319,319,66,,,,66,66,,66,66,,,,66,,,319,66,',
-',,,319,,319,,319,319,,319,319,319,,319,319,319,319,,,319,319,,,319,',
-',319,,,,,75,75,,319,75,75,,75,,319,,,,319,319,,319,319,,,,319,,,75,319',
-',,,,75,,75,,75,75,,75,75,75,,75,75,75,75,,,75,75,,,75,,,75,,,,,318,318',
-',75,318,318,,318,,75,,,,75,75,,75,75,,,,75,,,318,75,,,,,318,,318,,318',
-'318,,318,318,318,,318,318,318,318,,,318,318,,,318,,,318,,,,,77,77,,318',
-'77,77,,77,,318,,,,318,318,,318,318,,,,318,,,77,318,,,,,77,,77,,77,77',
-',77,77,77,,77,77,77,77,,,77,77,,,77,,,77,,,,,78,78,,77,78,78,,78,,77',
-',,,77,77,,77,77,,,,77,,,78,77,,,,,78,,78,,78,78,,78,78,78,,78,78,78',
-'78,,,78,78,,,78,,,78,,,,,79,79,,78,79,79,,79,,78,,,,78,78,,78,78,,,',
-'78,,,79,78,,,,,79,,79,,79,79,,79,79,79,,79,79,79,79,,,79,79,,,79,,,79',
-',,,,80,80,,79,80,80,,80,,79,,,,79,79,,79,79,,,,79,,,80,79,,,,,80,,80',
-',80,80,,80,80,80,,80,80,80,80,,,80,80,,,80,,,80,,,,,81,81,,80,81,81',
-',81,,80,,,,80,80,,80,80,,,,80,,,81,80,,,,,81,,81,,81,81,,81,81,81,,81',
-'81,81,81,,,81,81,,,81,,,81,,,,,82,82,,81,82,82,,82,,81,,,,81,81,,81',
-'81,,,,81,,,82,81,,,,,82,,82,,82,82,,82,82,82,,82,82,,,,,82,82,,,82,',
-',82,,,,,83,83,,82,83,83,,83,,82,,,,82,82,,82,82,,,,82,,,83,82,,,,,83',
-',83,,83,83,,83,83,83,,83,83,,,,,83,83,,,83,,,83,,,,,84,84,,83,84,84',
-',84,,83,,,,83,83,,83,83,,,,83,,,84,83,,,,,84,,84,,84,84,,84,84,84,,84',
-'84,,,,,84,84,,,84,,,84,,,,,85,85,,84,85,85,,85,,84,,,,84,84,,84,84,',
-',,84,,,85,84,,,,,85,,85,,85,85,,85,85,85,,85,85,,,,,85,85,,,85,,,85',
-',,,,86,86,,85,86,86,,86,,85,,,,85,85,,85,85,,,,85,,,86,85,,,,,86,,86',
-',86,86,,86,86,86,,86,86,,,,,86,86,,,86,,,86,,,,,87,87,,86,87,87,,87',
-',86,,,,86,86,,86,86,,,,86,,,87,86,,,,,87,,87,,87,87,,87,87,87,,87,87',
-',,,,87,87,,,87,,,87,,,,,88,88,,87,88,88,,88,,87,,,,87,87,,87,87,,,,87',
-',,88,87,,,,,88,,88,,88,88,,88,88,88,,88,88,,,,,88,88,,,88,,,88,,,,,89',
-'89,,88,89,89,,89,,88,,,,88,88,,88,88,,,,88,,,89,88,,,,,89,,89,,89,89',
-',89,89,89,,89,89,,,,,89,89,,,89,,,89,,,,,90,90,,89,90,90,,90,,89,,,',
-'89,89,,89,89,,,,89,,,90,89,,,,,90,,90,,90,90,,90,90,90,,90,90,,,,,90',
-'90,,,90,,,90,,,,,91,91,,90,91,91,,91,,90,,,,90,90,,90,90,,,,90,,,91',
-'90,,,,,91,,91,,91,91,,91,91,91,,91,91,,,,,91,91,,,91,,,91,,,,,92,92',
-',91,92,92,,92,,91,,,,91,91,,91,91,,,,91,,,92,91,,,,,92,,92,,92,92,,92',
-'92,92,,92,92,,,,,92,92,,,92,,,92,,,,,93,93,,92,93,93,,93,,92,,,,92,92',
-',92,92,,,,92,,,93,92,,,,,93,,93,,93,93,,93,93,93,,93,93,,,,,93,93,,',
-'93,,,93,,,,,94,94,,93,94,94,,94,,93,,,,93,93,,93,93,,,,93,,,94,93,,',
-',,94,,94,,94,94,,94,94,94,,94,94,,,,,94,94,,,94,,,94,,,,,95,95,,94,95',
-'95,,95,,94,,,,94,94,,94,94,,,,94,,,95,94,,,,,95,,95,,95,95,,95,95,95',
-',95,95,,,,,95,95,,,95,,,95,,,,,96,96,,95,96,96,,96,,95,,,,95,95,,95',
-'95,,,,95,,,96,95,,,,,96,,96,,96,96,,96,96,96,,96,96,,,,,96,96,,,96,',
-',96,,,,,97,97,,96,97,97,,97,,96,,,,96,96,,96,96,,,,96,,,97,96,,,,,97',
-',97,,97,97,,97,97,97,,97,97,,,,,97,97,,,97,,,97,,,,,98,98,,97,98,98',
-',98,,97,,,,97,97,,97,97,,,,97,,,98,97,,,,,98,,98,,98,98,,98,98,98,,98',
-'98,,,,,98,98,,,98,,,98,,,,,99,99,,98,99,99,,99,,98,,,,98,98,,98,98,',
-',,98,,,99,98,,,,,99,,99,,99,99,,99,99,99,,99,99,,,,,99,99,,,99,,,99',
-',,,,100,100,,99,100,100,,100,,99,,,,99,99,,99,99,,,,99,,,100,99,,,,',
-'100,,100,,100,100,,100,100,100,,100,100,,,,,100,100,,,100,,,100,,,,',
-'101,101,,100,101,101,,101,,100,,,,100,100,,100,100,,,,100,,,101,100',
-',,,,101,,101,,101,101,,101,101,101,,101,101,,,,,101,101,,,101,,,101',
-',,,,102,102,,101,102,102,,102,,101,,,,101,101,,101,101,,,,101,,,102',
-'101,,,,,102,,102,,102,102,,102,102,102,,102,102,,,,,102,102,,,102,,',
-'102,,,,,103,103,,102,103,103,,103,,102,,,,102,102,,102,102,,,,102,,',
-'103,102,,,,,103,,103,,103,103,,103,103,103,,103,103,,,,,103,103,,,103',
-',,103,,,,,104,104,,103,104,104,,104,,103,,,,103,103,,103,103,,,,103',
-',,104,103,,,,,104,,104,,104,104,,104,104,104,,104,104,,,,,104,104,,',
-'104,,,104,,,,,307,307,,104,307,307,,307,307,104,,,104,104,104,,104,104',
-',,,104,,,307,104,,,,,307,,307,,307,307,,307,307,307,,307,307,,,,,307',
-'307,,,307,,,307,,,,,106,106,,307,106,106,,106,,307,,,,307,307,,307,307',
-',,,307,,,106,307,,,,,106,106,106,106,106,106,106,106,106,106,,106,106',
-',,,,106,106,106,106,106,,,106,,,,,300,300,,106,300,300,,300,106,106',
-',,,106,106,,106,106,,,,106,,,300,106,,,,,300,,300,,300,300,,300,300',
-'300,,300,300,,,,,300,300,,,300,,,300,,,,,108,108,,300,108,108,,108,',
-'300,,,,300,300,,300,300,,,,300,,,108,300,,,,,108,,108,,108,108,,108',
-'108,108,,108,108,,,,,108,108,,,108,,,108,,,,,109,109,,108,109,109,,109',
-',108,,,,108,108,,108,108,,,,108,,,109,108,,,,,109,,109,,109,109,,109',
-'109,109,,109,109,,,,,109,109,,,109,,,109,,,,,293,293,,109,293,293,,293',
-',109,,,,109,109,,109,109,,,,109,,,293,109,,,,,293,,293,,293,293,,293',
-'293,293,,293,293,,,,,293,293,,,293,,,293,,,,,279,279,,293,279,279,,279',
-',293,,,,293,293,,293,293,,,,293,,,279,293,,,,,279,,279,,279,279,,279',
-'279,279,,279,279,,,,,279,279,,,279,,,279,,,,,278,278,,279,278,278,,278',
-',279,,,,279,279,,279,279,,,,279,,,278,279,,,,,278,,278,,278,278,,278',
-'278,278,,278,278,,,,,278,278,,,278,,,278,,,,,231,231,,278,231,231,,231',
-'231,278,,,,278,278,168,278,278,,,,278,,,231,278,,,,168,231,168,231,168',
-'231,231,,231,231,231,,231,231,231,231,,,231,231,,,231,,,231,168,,,,',
-',,231,,,168,168,,231,,168,168,231,231,,231,231,168,114,114,231,,114',
-'114,231,114,,,,,,,,,,168,,,,,,,114,114,,,,,114,,114,,114,114,,114,114',
-'114,,114,114,,,,,114,114,,,114,,,114,,,,,275,275,,114,275,275,,275,',
-'114,,,,114,114,,114,114,,,,114,,,275,114,,,,,275,,275,,275,275,,275',
-'275,275,,275,275,,,,,275,275,,,275,,,275,,,,,269,269,,275,269,269,,269',
-',275,,,,275,275,169,275,275,,,,275,,,269,275,,,,169,269,169,269,169',
-'269,269,,269,269,269,,269,269,,,,,269,269,,,269,,,269,169,,,,,,,269',
-',,169,169,,269,,169,169,269,269,,269,269,169,118,118,269,,118,118,269',
-'118,,,,,,,,,,169,,,,,,,118,118,,,,,118,,118,,118,118,,118,118,118,,118',
-'118,,,,,118,118,,,118,,,118,,,,,153,153,,118,153,153,,153,,118,,,,118',
-'118,,118,118,,,,118,,,153,118,,,,,153,,153,,153,153,,153,153,153,,153',
-'153,153,153,,,153,153,,,153,,,153,,,,,232,232,,153,232,232,,232,,153',
-',,,153,153,,153,153,,,,153,,,232,153,,,,,232,,232,,232,232,,232,232',
-'232,,232,232,,,,,232,232,,,232,,,232,,,,,247,247,,232,247,247,247,247',
-',232,,,,232,232,,232,232,,,,232,,,247,232,,,,,247,,247,,247,247,,247',
-'247,247,,247,247,,,,,247,247,,,247,,,247,,,,,234,234,,247,234,234,,234',
-',247,,,,247,247,,247,247,,,,247,,,234,247,,,,,234,,234,,234,234,,234',
-'234,234,,234,234,,,,,234,234,,,234,,,234,,,,,268,268,,234,268,268,,268',
-',234,,,,234,234,,234,234,,,,234,,,268,234,,,,,268,,268,,268,268,,268',
-'268,268,,268,268,,,,,268,268,,,268,,,268,,,,,125,125,,268,125,125,,125',
-',268,,,,268,268,,268,268,,,,268,,,125,268,,,,,125,,125,,125,125,,125',
-'125,125,,125,125,,,,,125,125,,,125,,,125,,,,,245,245,,125,245,245,245',
-'245,,125,,,,125,125,,125,125,,,,125,,,245,125,,,,,245,,245,,245,245',
-',245,245,245,,245,245,,,,,245,245,,,245,,,245,,,,,256,256,,245,256,256',
-',256,,245,,,,245,245,,245,245,,,,245,,,256,245,,,,,256,,256,,256,256',
-',256,256,256,,256,256,,,,,256,256,,,256,,,256,,,,,251,251,,256,251,251',
-',251,251,256,,,,256,256,,256,256,,,,256,,,251,256,,,,,251,,251,,251',
-'251,,251,251,251,,251,251,,,,,251,251,,,251,,,251,,,,,249,249,,251,249',
-'249,,249,,251,,,,251,251,,251,251,,,,251,,,249,251,,,,,249,,249,,249',
-'249,,249,249,249,,249,249,,,,,249,249,,,249,,,249,,,,,233,233,,249,233',
-'233,,233,,249,,,,249,249,,249,249,,,,249,,,233,249,,,,,233,233,233,233',
-'233,233,233,233,233,233,,233,233,,,,,233,233,233,233,233,,,233,,,,,',
-',,233,,,,,233,233,,,,233,233,,233,233,139,,,233,,,,233,,139,139,139',
-'139,139,139,,139,,139,,,139,139,139,139,,,,,,,,,,,,,,,,139,,,,139,139',
-',,139,139,139,139,139,139,,139,139,,,,,265,139,265,,,265,,,,265,265',
-'265,265,265,265,,265,,265,139,,265,265,265,265,,,,,,,,,,,,,,,,265,,',
-',265,265,,,265,265,265,265,265,265,144,265,265,,,144,,,265,144,144,144',
-'144,144,144,,144,,144,,,144,144,144,144,,265,,,,,,,,,,,,,,144,,,,144',
-'144,,,144,144,144,144,144,144,,144,144,,,,,123,144,123,,,,,,,123,123',
-'123,123,123,123,,123,,123,144,,123,123,123,123,,,,,,,,,,,,,,,,123,,',
-',123,123,,,123,123,123,123,123,123,148,123,123,,,,,,123,148,148,148',
-'148,148,148,,148,,148,,,148,148,148,148,,123,,,,,,,,,,,,,,148,,,,148',
-'148,,,148,148,148,148,148,148,,148,148,,,,,122,148,122,,,,,,,122,122',
-'122,122,122,122,,122,,122,148,,122,122,122,122,,,,,,,,,,,,,,,,122,,',
-',122,122,,,122,122,122,122,122,122,,122,122,,,,,121,122,121,,,,,,,121',
-'121,121,121,121,121,,121,,121,122,,121,121,121,121,,,,,,,,,,,,,,,,121',
-',,,121,121,,,121,121,121,121,121,121,,121,121,,,,,119,121,119,,,,,,',
-'119,119,119,119,119,119,,119,,119,121,,119,119,119,119,,,,,,,,,,,,,',
-',,119,,,,119,119,,,119,119,119,119,119,119,113,119,119,,,,,,119,113',
-'113,113,113,113,113,,113,,113,,113,113,113,113,113,,119,,,,,,,,,,,,',
-',113,,,,113,113,,,113,113,113,113,113,113,155,113,113,,,,,,113,155,155',
-'155,155,155,155,,155,,155,,,155,155,155,155,,113,,,,,,,,,,,,,,155,,',
-',155,155,,,155,155,155,155,155,155,323,155,155,,,,,,155,323,323,323',
-'323,323,323,,323,,323,155,155,323,323,323,323,,155,,,,,,,,,,,,,,323',
-',,,323,323,,,323,323,323,323,323,323,326,323,323,,,,,,323,326,326,326',
-'326,326,326,,326,,326,,,326,326,326,326,,323,,,,,,,,,,,,,,326,,,,326',
-'326,,,326,326,326,326,326,326,332,326,326,,,,,,326,332,332,332,332,332',
-'332,,332,,332,,,332,332,332,332,,326,,,,,,,,,,,,,,332,,,,332,332,,,332',
-'332,332,332,332,332,215,332,332,,,,,,332,215,215,215,215,215,215,,215',
-',215,,,215,215,215,215,,332,,,,,,,,,,,,,,215,,,,215,215,,,215,215,215',
-'215,215,215,340,215,215,,,,,,215,340,340,340,340,340,340,,340,,340,',
-',340,340,340,340,,215,,,,,,,,,,,,,,340,,,,340,340,,,340,340,340,340',
-'340,340,341,340,340,,,,,,340,341,341,341,341,341,341,,341,,341,,,341',
-'341,341,341,,340,,,,,,,,,,,,,,341,,,,341,341,,,341,341,341,341,341,341',
-'347,341,341,,,,,,341,347,347,347,347,347,347,,347,,347,,,347,347,347',
-'347,,341,,,,,,,,,,,,,,347,,,,347,347,,,347,347,347,347,347,347,191,347',
-'347,,,,,,347,191,191,191,191,191,191,191,191,,191,,,191,191,191,191',
-',347,,,,,,,,,,,,,,191,,,,191,191,,,191,191,191,191,191,191,,191,191',
-',,,,11,191,11,,,,,,,11,11,11,11,11,11,,11,173,11,191,,11,11,11,11,,',
-',,,,173,,173,,173,,,,,11,,,,11,11,,,11,11,11,11,11,11,,11,11,173,,,174',
-',11,,,173,173,173,173,,,,173,173,174,,174,175,174,173,11,,,,,,,,,,,175',
-',175,,175,,173,,,174,,,,,,,,174,174,174,174,,,,174,174,175,,,176,,174',
-',,175,175,175,175,175,175,,175,175,176,,176,177,176,175,174,,,,,,,,',
-',177,177,,177,,177,,175,177,,176,,,,,,,,176,176,176,176,176,176,,176',
-'176,177,,,,,176,,,177,177,177,177,177,177,178,177,177,,,,,,177,176,',
-',,178,178,,178,179,178,,,178,,,,,177,,,,179,179,,179,,179,,,179,,178',
-',,,,,,,178,178,178,178,178,178,,178,178,179,,,,,178,,,179,179,179,179',
-'179,179,180,179,179,,,,,,179,178,,,,180,180,,180,,180,,181,180,,,,,179',
-',,,,,181,181,181,,181,,181,,180,181,181,181,181,,,,180,180,180,180,180',
-'180,,180,180,,,,181,,180,,,182,,,181,181,181,181,181,181,,181,181,182',
-'182,182,180,182,181,182,,183,182,182,182,182,,,,,183,183,183,183,183',
-'183,181,183,,183,,182,183,183,183,183,182,,,182,182,182,182,182,182',
-',182,182,,,,183,,182,,183,183,,,183,183,183,183,183,183,186,183,183',
-',,186,182,,183,186,186,186,186,186,186,,186,,186,,,186,186,186,186,',
-'183,,,,,,,,,,,,,,186,,,,186,186,,,186,186,186,186,186,186,185,186,186',
-',,,,,186,185,185,185,185,185,185,,185,,185,,,185,185,185,185,,186,,',
-',,,,,,,,,,,185,,,,185,185,,,185,185,185,185,185,185,184,185,185,,,,',
-',185,184,184,184,184,184,184,,184,,184,,,184,184,184,184,,185,,,,,,',
-',,,,,,,184,,,,184,184,,,184,184,184,184,184,184,,184,184,,277,277,277',
-'277,184,277,277,277,277,277,,277,277,,,,,,,277,277,277,184,272,272,272',
-'272,,272,272,272,272,272,,272,272,,277,277,,,,272,272,272,214,214,214',
-'214,,214,214,214,214,214,,214,214,,,272,272,,,214,214,214,,,,,,,,,,',
-',,,,,214,214' ]
- racc_action_check = arr = ::Array.new(6559, nil)
+'0,0,352,174,0,0,240,0,180,191,178,181,238,248,168,167,179,166,145,145',
+'240,365,323,191,0,191,365,191,191,250,0,323,0,238,0,0,113,0,0,0,113',
+'0,0,0,0,174,248,0,0,191,180,0,178,181,0,0,168,167,179,166,403,403,0',
+'251,403,403,312,403,0,189,161,191,0,0,189,0,0,0,12,312,0,0,0,0,403,45',
+'0,312,160,45,403,119,403,159,403,403,150,403,403,403,140,403,403,140',
+'119,264,12,403,403,150,12,403,119,269,403,403,320,150,320,175,4,4,403',
+'175,4,4,119,4,403,270,306,150,403,403,306,403,403,306,340,340,403,403',
+'403,403,4,176,403,225,154,176,4,366,4,366,4,4,225,4,4,4,4,4,4,4,4,272',
+'164,4,4,225,246,4,148,148,4,4,148,225,143,245,398,398,4,138,398,398',
+'136,398,4,7,7,7,4,4,280,4,4,282,284,286,4,4,4,4,398,301,4,128,126,304',
+'398,239,398,308,398,398,309,398,398,398,311,398,398,398,398,237,125',
+'398,398,118,116,398,319,236,398,398,321,322,7,7,7,7,398,230,212,108',
+'327,106,398,339,217,341,398,398,105,398,398,102,101,70,398,398,398,398',
+'228,228,398,68,228,228,349,228,228,63,351,162,355,62,360,223,213,220',
+'41,369,370,372,373,376,228,177,177,40,383,177,228,384,228,390,228,228',
+'219,228,228,228,8,228,228,228,228,5,400,228,228,1,405,228,407,409,228',
+'228,413,,,,,,228,,177,,,177,228,,,,228,228,,228,228,,,,228,228,228,228',
+'397,397,228,177,397,397,,397,397,192,,,,177,177,,,,,,,,,192,397,192',
+',192,192,,397,,397,,397,397,,397,397,397,,397,397,397,397,,,397,397',
+'192,,397,,,397,397,,,,,211,211,397,,211,211,,211,397,,,192,397,397,',
+'397,397,,,,397,397,397,397,211,,397,,,,211,,211,,211,211,,211,211,211',
+',211,211,,,,,211,211,,,211,,,211,211,,,,,10,10,211,,10,10,,10,211,,',
+',211,211,,211,211,,,,211,211,211,211,10,,211,,,,10,,10,,10,10,,10,10',
+'10,,10,10,10,10,,,10,10,,,10,,,10,10,,,,,11,11,10,,11,11,,11,10,,,,10',
+'10,,10,10,,,,10,10,10,10,11,,10,,,,11,,11,,11,11,,11,11,11,,11,11,11',
+'11,,,11,11,,,11,,,11,11,,,,,,,11,,,,,,11,,,,11,11,,11,11,,,,11,11,11',
+'11,395,395,11,,395,395,,395,395,114,,,,,,,,,,,,,,114,395,114,,114,114',
+',395,,395,,395,395,,395,395,395,,395,395,395,395,,,395,395,114,,395',
+',,395,395,,,,,15,15,395,,15,15,,15,395,,,,395,395,,395,395,,,,395,395',
+'395,395,15,,395,,,,15,,15,,15,15,,15,15,15,,15,15,,,,,15,15,,,15,,,15',
+'15,,,,,16,16,15,,16,16,,16,15,,,,15,15,,15,15,,,,15,15,15,15,16,,15',
+',,,16,,16,,16,16,,16,16,16,,16,16,,,,,16,16,,,16,,,16,16,,,,,17,17,16',
+',17,17,,17,16,,,,16,16,,16,16,,,,16,16,16,16,17,,16,,,,17,,17,,17,17',
+',17,17,17,,17,17,,,,,17,17,,,17,,,17,17,,,,,18,18,17,,18,18,,18,17,',
+',,17,17,,17,17,,,,17,17,17,17,18,,17,,,,18,,18,,18,18,,18,18,18,,18',
+'18,18,18,,,18,18,,,18,,,18,18,,,,,,,18,,,,,,18,,,,18,18,,18,18,,,,18',
+'18,18,18,379,379,18,,379,379,,379,379,115,,,,,,,,,,,,,,115,379,115,',
+'115,115,,379,,379,,379,379,,379,379,379,,379,379,379,379,,,379,379,115',
+',379,,,379,379,,,,,368,368,379,,368,368,,368,379,,,,379,379,,379,379',
+',,,379,379,379,379,368,,379,,,,368,,368,,368,368,,368,368,368,,368,368',
+',,,,368,368,,,368,,,368,368,,,,,42,42,368,,42,42,,42,368,,,,368,368',
+',368,368,,,,368,368,368,368,42,,368,,,,42,,42,,42,42,,42,42,42,,42,42',
+',,,,42,42,,,42,,,42,42,,,,,43,43,42,,43,43,,43,42,,,,42,42,,42,42,,',
+',42,42,42,42,43,,42,,,,43,,43,,43,43,,43,43,43,,43,43,,,,,43,43,,,43',
+',,43,43,,,,,44,44,43,,44,44,,44,43,,,,43,43,,43,43,,,,43,43,43,43,44',
+',43,,,,44,,44,,44,44,,44,44,44,,44,44,,,,,44,44,,,44,,,44,44,,,,,,,44',
+',,,,,44,,,,44,44,,44,44,,,,44,44,44,44,242,242,44,,242,242,,242,242',
+',,,,,,,,,,,,,,,242,46,46,,,46,242,,242,,242,242,,242,242,242,,242,242',
+'242,242,,,242,242,,,242,,,242,242,,,,,,,242,,46,,,46,242,,,,242,242',
+',242,242,,,,242,242,242,242,243,243,242,46,243,243,,243,243,190,,,,46',
+'46,,,,,,,,,190,243,190,,190,190,,243,,243,,243,243,,243,243,243,,243',
+'243,243,243,,,243,243,190,,243,,,243,243,,,,,52,52,243,,52,52,52,52',
+'243,,,,243,243,,243,243,,,,243,243,243,243,52,,243,,,,52,,52,,52,52',
+',52,52,52,,52,52,52,52,,,52,52,,,52,,,52,52,,,,,53,53,52,,53,53,53,53',
+'52,,,,52,52,,52,52,,,,52,52,52,52,53,,52,,,,53,,53,,53,53,,53,53,53',
+',53,53,53,53,,,53,53,,,53,,,53,53,,,,,,,53,,,,,,53,,,,53,53,,53,53,',
+',,53,53,53,53,54,54,53,,54,54,,54,54,112,,,,,,,,,,,,,,112,54,112,,112',
+'112,,54,,54,,54,54,,54,54,54,,54,54,54,54,,,54,54,112,,54,,,54,54,,',
+',,60,60,54,,60,60,,60,54,,,,54,54,,54,54,,,,54,54,54,54,60,,54,,,,60',
+',60,,60,60,,60,60,60,,60,60,60,60,,,60,60,,,60,,,60,60,,,,,356,356,60',
+',356,356,,356,60,,,,60,60,,60,60,,,,60,60,60,60,356,,60,,,,356,,356',
+',356,356,,356,356,356,,356,356,356,356,,,356,356,,,356,,,356,356,,,',
+',350,350,356,,350,350,,350,356,,,,356,356,,356,356,,,,356,356,356,356',
+'350,,356,,,,350,,350,,350,350,,350,350,350,,350,350,,,,,350,350,,,350',
+',,350,350,,,,,65,65,350,,65,65,,65,350,,,,350,350,,350,350,,,,350,350',
+'350,350,65,,350,,,,65,,65,,65,65,,65,65,65,,65,65,,,,,65,65,,,65,,,65',
+'65,,,,,244,244,65,,244,244,,244,65,,,,65,65,,65,65,,,,65,65,65,65,244',
+',65,,,,244,,244,,244,244,,244,244,244,,244,244,,,,,244,244,,,244,,,244',
+'244,,,,,69,69,244,,69,69,,69,244,,,,244,244,,244,244,,,,244,244,244',
+'244,69,,244,,,,69,,69,,69,69,,69,69,69,,69,69,69,69,,,69,69,,,69,,,69',
+'69,,,,,171,171,69,,171,171,,171,69,,,,69,69,,69,69,,,,69,69,69,69,171',
+',69,,,,171,,171,,171,171,,171,171,171,,171,171,,,,,171,171,,,171,,,171',
+'171,,,,,71,71,171,,71,71,,71,171,,,,171,171,,171,171,,,,171,171,171',
+'171,71,,171,,,,71,,71,,71,71,,71,71,71,,71,71,71,71,,,71,71,,,71,,,71',
+'71,,,,,72,72,71,,72,72,,72,71,,,,71,71,,71,71,,,,71,71,71,71,72,,71',
+',,,72,,72,,72,72,,72,72,72,,72,72,72,72,,,72,72,,,72,,,72,72,,,,,73',
+'73,72,,73,73,,73,72,,,,72,72,,72,72,,,,72,72,72,72,73,,72,,,,73,,73',
+',73,73,,73,73,73,,73,73,73,73,,,73,73,,,73,,,73,73,,,,,74,74,73,,74',
+'74,,74,73,,,,73,73,,73,73,,,,73,73,73,73,74,,73,,,,74,,74,,74,74,,74',
+'74,74,,74,74,74,74,,,74,74,,,74,,,74,74,,,,,75,75,74,,75,75,,75,74,',
+',,74,74,,74,74,,,,74,74,74,74,75,,74,,,,75,,75,,75,75,,75,75,75,,75',
+'75,75,75,,,75,75,,,75,,,75,75,,,,,76,76,75,,76,76,,76,75,,,,75,75,,75',
+'75,,,,75,75,75,75,76,,75,,,,76,,76,,76,76,,76,76,76,,76,76,76,76,,,76',
+'76,,,76,,,76,76,,,,,77,77,76,,77,77,,77,76,,,,76,76,,76,76,,,,76,76',
+'76,76,77,,76,,,,77,,77,,77,77,,77,77,77,,77,77,77,77,,,77,77,,,77,,',
+'77,77,,,,,78,78,77,,78,78,,78,77,,,,77,77,,77,77,,,,77,77,77,77,78,',
+'77,,,,78,,78,,78,78,,78,78,78,,78,78,78,78,,,78,78,,,78,,,78,78,,,,',
+'79,79,78,,79,79,,79,78,,,,78,78,,78,78,,,,78,78,78,78,79,,78,,,,79,79',
+'79,79,79,79,79,79,79,79,,79,79,,,,,79,79,79,79,79,,,79,79,,,,,,,79,',
+',,,79,79,,,,79,79,,79,79,,,,79,79,79,79,80,80,79,,80,80,,80,,,,,,,,',
+',,,,,,,,80,,,,,,80,,80,,80,80,,80,80,80,,80,80,,,,,80,80,,,80,,,80,80',
+',,,,81,81,80,,81,81,,81,80,,,,80,80,,80,80,,,,80,80,80,80,81,,80,,,',
+'81,,81,,81,81,,81,81,81,,81,81,,,,,81,81,,,81,,,81,81,,,,,82,82,81,',
+'82,82,,82,81,,,,81,81,,81,81,,,,81,81,81,81,82,,81,,,,82,,82,,82,82',
+',82,82,82,,82,82,,,,,82,82,,,82,,,82,82,,,,,83,83,82,,83,83,,83,82,',
+',,82,82,,82,82,,,,82,82,82,82,83,,82,,,,83,,83,,83,83,,83,83,83,,83',
+'83,,,,,83,83,,,83,,,83,83,,,,,84,84,83,,84,84,,84,83,,,,83,83,,83,83',
+',,,83,83,83,83,84,,83,,,,84,,84,,84,84,,84,84,84,,84,84,,,,,84,84,,',
+'84,,,84,84,,,,,85,85,84,,85,85,,85,84,,,,84,84,,84,84,,,,84,84,84,84',
+'85,,84,,,,85,,85,,85,85,,85,85,85,,85,85,,,,,85,85,,,85,,,85,85,,,,',
+'86,86,85,,86,86,,86,85,,,,85,85,,85,85,,,,85,85,85,85,86,,85,,,,86,',
+'86,,86,86,,86,86,86,,86,86,,,,,86,86,,,86,,,86,86,,,,,87,87,86,,87,87',
+',87,86,,,,86,86,,86,86,,,,86,86,86,86,87,,86,,,,87,,87,,87,87,,87,87',
+'87,,87,87,,,,,87,87,,,87,,,87,87,,,,,88,88,87,,88,88,,88,87,,,,87,87',
+',87,87,,,,87,87,87,87,88,,87,,,,88,,88,,88,88,,88,88,88,,88,88,,,,,88',
+'88,,,88,,,88,88,,,,,89,89,88,,89,89,,89,88,,,,88,88,,88,88,,,,88,88',
+'88,88,89,,88,,,,89,,89,,89,89,,89,89,89,,89,89,,,,,89,89,,,89,,,89,89',
+',,,,90,90,89,,90,90,,90,89,,,,89,89,,89,89,,,,89,89,89,89,90,,89,,,',
+'90,,90,,90,90,,90,90,90,,90,90,,,,,90,90,,,90,,,90,90,,,,,91,91,90,',
+'91,91,,91,90,,,,90,90,,90,90,,,,90,90,90,90,91,,90,,,,91,,91,,91,91',
+',91,91,91,,91,91,,,,,91,91,,,91,,,91,91,,,,,92,92,91,,92,92,,92,91,',
+',,91,91,,91,91,,,,91,91,91,91,92,,91,,,,92,,92,,92,92,,92,92,92,,92',
+'92,,,,,92,92,,,92,,,92,92,,,,,93,93,92,,93,93,,93,92,,,,92,92,,92,92',
+',,,92,92,92,92,93,,92,,,,93,,93,,93,93,,93,93,93,,93,93,,,,,93,93,,',
+'93,,,93,93,,,,,94,94,93,,94,94,,94,93,,,,93,93,,93,93,,,,93,93,93,93',
+'94,,93,,,,94,,94,,94,94,,94,94,94,,94,94,,,,,94,94,,,94,,,94,94,,,,',
+'95,95,94,,95,95,,95,94,,,,94,94,,94,94,,,,94,94,94,94,95,,94,,,,95,',
+'95,,95,95,,95,95,95,,95,95,,,,,95,95,,,95,,,95,95,,,,,96,96,95,,96,96',
+',96,95,,,,95,95,,95,95,,,,95,95,95,95,96,,95,,,,96,,96,,96,96,,96,96',
+'96,,96,96,,,,,96,96,,,96,,,96,96,,,,,97,97,96,,97,97,,97,96,,,,96,96',
+',96,96,,,,96,96,96,96,97,,96,,,,97,,97,,97,97,,97,97,97,,97,97,,,,,97',
+'97,,,97,,,97,97,,,,,98,98,97,,98,98,,98,97,,,,97,97,,97,97,,,,97,97',
+'97,97,98,,97,,,,98,,98,,98,98,,98,98,98,,98,98,,,,,98,98,,,98,,,98,98',
+',,,,99,99,98,,99,99,,99,98,,,,98,98,,98,98,,,,98,98,98,98,99,,98,,,',
+'99,,99,,99,99,,99,99,99,,99,99,,,,,99,99,,,99,,,99,99,,,,,100,100,99',
+',100,100,,100,99,,,99,99,99,,99,99,,,,99,99,99,99,100,100,99,,,,100',
+',100,,100,100,,100,100,100,,100,100,100,100,,,100,100,,,100,,,100,100',
+',,,,278,278,100,,278,278,,278,100,,,,100,100,,100,100,,,,100,100,100',
+'100,278,,100,,,,278,,278,,278,278,,278,278,278,,278,278,,,,,278,278',
+',,278,,,278,278,,,,,169,169,278,,169,169,,169,278,,,,278,278,,278,278',
+',,,278,278,278,278,169,,278,,,,169,,169,,169,169,,169,169,169,,169,169',
+',,,,169,169,,,169,,,169,169,,,,,103,103,169,,103,103,,103,169,,,,169',
+'169,,169,169,,,,169,169,169,169,103,,169,,,,103,,103,,103,103,,103,103',
+'103,,103,103,,,,,103,103,,,103,,,103,103,,,,,104,104,103,,104,104,,104',
+'103,,,,103,103,,103,103,,,,103,103,103,103,104,,103,,,,104,,104,,104',
+'104,,104,104,104,,104,104,,,,,104,104,,,104,,,104,104,,,,,165,165,104',
+',165,165,,165,104,,165,,104,104,,104,104,,,,104,104,104,104,165,,104',
+',,,165,,165,,165,165,,165,165,165,,165,165,,,,,165,165,,,165,,,165,165',
+',,,,249,249,165,,249,249,,249,165,,,,165,165,,165,165,,,,165,165,165',
+'165,249,,165,,,,249,,249,,249,249,,249,249,249,,249,249,249,249,,,249',
+'249,,,249,,,249,249,,,,,107,107,249,,107,107,,107,249,,,,249,249,,249',
+'249,,,,249,249,249,249,107,,249,,,,107,,107,,107,107,,107,107,107,,107',
+'107,,,,,107,107,,,107,,,107,107,,,,,326,326,107,,326,326,,326,107,,',
+',107,107,,107,107,,,,107,107,107,107,326,,107,,,,326,,326,,326,326,',
+'326,326,326,,326,326,326,326,,,326,326,,,326,,,326,326,,,,,,,326,,,',
+',,326,,,,326,326,,326,326,,,,326,326,326,326,253,253,326,,253,253,,253',
+'253,,,,,,,,,,,,,,,,253,247,247,,,247,253,,253,,253,253,,253,253,253',
+',253,253,253,253,,,253,253,,,253,,,253,253,,,,,,,253,,247,,,247,253',
+',,,253,253,,253,253,,,,253,253,253,253,324,324,253,247,324,324,,324',
+'324,,,,,247,247,,,,,,,,,,324,,,,,,324,,324,,324,324,,324,324,324,,324',
+'324,,,,,324,324,,,324,,,324,324,,,,,254,254,324,,254,254,,254,324,,',
+',324,324,,324,324,,,,324,324,324,324,254,,324,,,,254,,254,,254,254,',
+'254,254,254,,254,254,254,254,,,254,254,,,254,,,254,254,,,,,259,259,254',
+',259,259,,259,254,,,,254,254,,254,254,,,,254,254,254,254,259,,254,,',
+',259,,259,,259,259,,259,259,259,,259,259,259,259,,,259,259,,,259,,,259',
+'259,,,,,317,317,259,,317,317,,317,259,,,,259,259,,259,259,,,,259,259',
+'259,259,317,,259,,,,317,,317,,317,317,,317,317,317,,317,317,317,317',
+',,317,317,,,317,,,317,317,,,,,316,316,317,,316,316,,316,317,,,,317,317',
+',317,317,,,,317,317,317,317,316,,317,,,,316,,316,,316,316,,316,316,316',
+',316,316,,,,,316,316,,,316,,,316,316,,,,,,,316,,,,,,316,,,,316,316,',
+'316,316,,,,316,316,316,316,152,152,316,,152,152,,152,152,,,,,,,,,,,',
+',,,,152,,,,,,152,,152,,152,152,,152,152,152,,152,152,152,152,,,152,152',
+',,152,,,152,152,,,,,120,120,152,,120,120,,120,152,,,,152,152,,152,152',
+',,,152,152,152,152,120,120,152,,,,120,,120,,120,120,,120,120,120,,120',
+'120,120,120,,,120,120,,,120,,,120,120,,,,,149,149,120,,149,149,,149',
+'120,,,,120,120,,120,120,,,,120,120,120,120,149,,120,,,,149,,149,,149',
+'149,,149,149,149,,149,149,149,149,,,149,149,,,149,,,149,149,,,,,274',
+'274,149,,274,274,,274,149,,,,149,149,,149,149,,,,149,149,149,149,274',
+',149,,,,274,,274,,274,274,,274,274,274,,274,274,,,,,274,274,,,274,,',
+'274,274,,,,,275,275,274,,275,275,,275,274,,,,274,274,,274,274,,,,274',
+'274,274,274,275,,274,,,,275,,275,,275,275,,275,275,275,,275,275,,,,',
+'275,275,,,275,,,275,275,,,,,313,313,275,,313,313,,313,275,,,,275,275',
+',275,275,,,,275,275,275,275,313,,275,,,,313,,313,,313,313,,313,313,313',
+',313,313,,,,,313,313,,,313,,,313,313,,,,,276,276,313,,276,276,,276,313',
+',,,313,313,,313,313,,,,313,313,313,313,276,,313,,,,276,,276,,276,276',
+',276,276,276,,276,276,,,,,276,276,,,276,,,276,276,,,,,302,302,276,,302',
+'302,,302,276,,,,276,276,,276,276,,,,276,276,276,276,302,,276,,,,302',
+',302,,302,302,,302,302,302,,302,302,,,,,302,302,,,302,,,302,302,,,,',
+'279,279,302,,279,279,,279,302,,,,302,302,,302,302,,,,302,302,302,302',
+'279,,302,,,,279,,279,,279,279,,279,279,279,,279,279,,,,,279,279,,,279',
+',,279,279,,,,,170,170,279,,170,170,,170,279,,,,279,279,,279,279,,,,279',
+'279,279,279,170,,279,,,,170,,170,,170,170,,170,170,170,,170,170,,,,',
+'170,170,,,170,,,170,170,,,,,,,170,,,,,,170,,,,170,170,,170,170,346,',
+',170,170,170,170,,,170,,,346,346,346,,346,,346,346,,346,346,346,346',
+',329,329,,,329,,,,,,,,,,346,,,,346,346,,,346,346,346,346,346,346,,346',
+'346,124,,124,,,346,,,329,,,329,124,124,124,,124,,124,124,,124,124,124',
+'124,,346,,,,,329,,,,,,,,,124,329,329,,124,124,,,124,124,124,124,124',
+'124,,124,124,123,,123,,,124,,,,,,,123,123,123,,123,193,123,123,,123',
+'123,123,123,,124,,,,,193,,193,,193,193,,,,123,,,,123,123,,,123,123,123',
+'123,123,123,,123,123,193,,,,,123,,,,,193,193,,,,193,193,121,,121,,,193',
+',,,123,,,121,121,121,,121,195,121,121,,121,121,121,121,,193,,,,,195',
+',195,,195,195,,,,121,,,,121,121,,216,121,121,121,121,121,121,,121,121',
+'195,,216,216,216,121,216,,216,216,,216,216,216,216,195,195,,,,,,195',
+',,,121,,,,216,,,,216,216,,151,216,216,216,216,216,216,,216,216,,,151',
+'151,151,216,151,,151,151,,151,151,151,151,,,,,,,,,,,,216,,,,151,,,,151',
+'151,,,151,151,151,151,151,151,,151,151,,,,,,151,221,,,,,,,,,,151,151',
+'221,221,221,221,221,199,221,221,151,221,221,221,221,,,,,,,199,,199,',
+'199,199,,,,221,,,,221,221,,,221,221,221,221,221,221,,221,221,199,,,',
+',221,,,199,199,199,199,,,,199,199,9,,,,,199,,,,221,,,9,9,9,,9,,9,9,',
+'9,9,9,9,,199,,,,,,,,,,,,,,9,,,,9,9,,,9,9,9,9,9,9,208,9,9,,,208,,,9,',
+',,208,208,208,,208,196,208,208,,208,208,208,208,,,,,9,,196,,196,,196',
+'196,,,,208,,,,208,208,,207,208,208,208,208,208,208,,208,208,196,,207',
+'207,207,208,207,,207,207,,207,207,207,207,196,196,,,,,,196,,,,208,,',
+',207,,,,,207,,364,207,207,207,207,207,207,,207,207,,,364,364,364,207',
+'364,,364,364,,364,364,364,364,,,,,,,,,,,,207,,,,364,,,,364,364,,,364',
+'364,364,364,364,364,163,364,364,,,,,,364,,,,163,163,163,163,163,197',
+'163,163,,163,163,163,163,,,,,364,,197,,197,,197,197,,,,163,,,,163,163',
+',188,163,163,163,163,163,163,,163,163,197,,188,188,188,163,188,,188',
+'188,,188,188,188,188,197,197,,,,,,197,,,,163,,,,188,,,,188,188,,344',
+'188,188,188,188,188,188,,188,188,,,344,344,344,188,344,,344,344,,344',
+'344,344,344,,,,,,,,,,,,188,,,,344,,,,344,344,,206,344,344,344,344,344',
+'344,,344,344,,,206,206,206,344,206,,206,206,,206,206,206,206,205,,,',
+',,,,,,,344,,205,205,206,205,,205,205,198,205,,206,206,206,206,206,206',
+',206,206,,,198,,198,206,198,198,205,,,,,,,,205,205,205,205,205,205,',
+'205,205,,206,,198,,205,,,,,,198,198,198,198,345,,,198,198,,,,,,198,205',
+'345,345,345,,345,,345,345,194,345,345,345,345,,,,,,,198,,,194,,194,',
+'194,194,345,,,,345,345,,,345,345,345,345,345,345,,345,345,,,,194,,345',
+',,,,,,,194,194,347,,,194,194,,,,,,194,345,347,347,347,,347,,347,347',
+'203,347,347,347,347,,,,,,,194,,203,203,,203,,203,203,347,203,,,347,347',
+',,347,347,347,347,347,347,,347,347,,,,203,,347,,,,,,203,203,203,203',
+'203,203,348,203,203,,,,,,203,347,,,348,348,348,,348,200,348,348,,348',
+'348,348,348,,,,,203,,200,,200,,200,200,,,,348,,,,348,348,,,348,348,348',
+'348,348,348,,348,348,200,,,,,348,,,200,200,200,200,200,200,201,200,200',
+',,,,,200,,,,348,,201,,201,202,201,201,,,,,,,,,,200,202,202,,202,,202',
+'202,,202,,201,,,,,,,,201,201,201,201,201,201,,201,201,202,,,,,201,,',
+'202,202,202,202,202,202,204,202,202,,,,,,202,,,,201,204,204,,204,,204',
+'204,,204,,,,,,,,202,,,,,,,,,,,204,,,,,,,,204,204,204,204,204,204,,204',
+'204,,,,,,204,,,271,271,271,271,,271,271,271,,271,,271,271,,,,,,204,271',
+'271,271,,,,271,,,,,,,,,,,,271,271,,,,,,,,,,,,271,271,271,271,273,273',
+'273,273,,273,273,273,,273,,273,273,,,,,,,273,273,273,,,,273,,,,,,,,',
+',,,273,273,,,,,,,,,,,,273,273,273,273,303,303,303,303,,303,303,303,',
+'303,,303,303,,,,,,,303,303,303,,,,303,,,,,,,,,,,,303,303,,,,,,,,,,,',
+'303,303,303,303,215,215,215,215,,215,215,215,,215,,215,215,,,,,,,215',
+'215,215,,,,215,,,,,,,,,,,,215,215,,,,,,,,,,,,215,215,215,215' ]
+ racc_action_check = arr = ::Array.new(6809, nil)
idx = 0
clist.each do |str|
str.split(',', -1).each do |i|
@@ -475,431 +497,445 @@ clist = [
end
racc_action_pointer = [
- -2, 297, nil, nil, nil, 116, 281, nil, 79, nil,
- nil, 5901, 352, 411, 470, nil, nil, nil, nil, nil,
+ -2, 313, nil, nil, 118, 296, nil, 173, 295, 5793,
+ 466, 526, 69, nil, nil, 670, 730, 790, 850, nil,
nil, nil, nil, nil, nil, nil, nil, nil, nil, nil,
- nil, nil, nil, nil, nil, nil, nil, nil, nil, 262,
- -55, 237, 706, 765, 824, 883, 186, 210, nil, 58,
- 194, nil, nil, 1178, 1237, 1296, nil, nil, nil, nil,
- nil, 1355, nil, 158, 162, nil, 1532, nil, nil, nil,
- nil, nil, nil, nil, 232, 1650, 217, 1768, 1827, 1886,
- 1945, 2004, 2063, 2122, 2181, 2240, 2299, 2358, 2417, 2476,
- 2535, 2594, 2653, 2712, 2771, 2830, 2889, 2948, 3007, 3066,
- 3125, 3184, 3243, 3302, 3361, 164, 3479, 178, 3597, 3656,
- 716, 75, 657, 5354, 3970, nil, 105, -15, 4166, 5300,
- nil, 5239, 5178, 5063, 127, 4520, 5, nil, nil, nil,
- nil, 62, -11, nil, 225, nil, nil, nil, nil, 4887,
- 61, nil, 57, nil, 5002, 15, nil, nil, 5117, nil,
- 166, nil, 102, 4225, -22, 5408, 1473, nil, 120, nil,
- nil, nil, nil, nil, 9, 598, 480, 303, 3902, 4098,
- 362, 421, 539, 5918, 5961, 5978, 6021, 6038, 6092, 6109,
- 6163, 6183, 6228, 6248, 6410, 6356, 6302, nil, nil, 293,
- nil, 5840, 647, 942, 1001, 214, 238, nil, nil, -4,
- nil, -1, -9, 74, 49, 76, 1, -2, nil, nil,
- nil, nil, nil, nil, 6488, 5624, 199, nil, 202, nil,
- 191, 96, nil, 1119, nil, 141, nil, 133, -1, nil,
- 1414, 3892, 4284, 4815, 4402, 4, 151, nil, -21, 255,
- 284, 17, nil, 135, 16, 4579, nil, 4343, nil, 4756,
- nil, 4697, nil, nil, nil, nil, 4638, nil, nil, nil,
- 252, nil, nil, nil, nil, 4948, 30, nil, 4461, 4088,
- 58, nil, 6466, nil, 99, 4029, 120, 6443, 3833, 3774,
- 150, nil, nil, nil, nil, nil, nil, nil, nil, nil,
- nil, nil, nil, 3715, 146, nil, 164, nil, 104, 147,
- 3538, nil, 178, 91, 182, 170, -4, 3420, nil, 164,
- 195, 171, 207, 213, nil, -8, nil, 216, 1709, 1591,
- nil, nil, nil, 5462, nil, nil, 5516, nil, nil, nil,
- 171, 48, 5570, 238, 1060, 241, nil, nil, nil, nil,
- 5678, 5732, 249, 191, nil, nil, nil, 5786, 52, nil,
- 588, 258, 239, nil, 267, 272, nil, nil, nil, 272,
- 275, 276, nil, 529, nil, nil, nil, 262, 281, nil,
- nil, 282, nil, nil, nil, nil, nil, nil, nil, nil,
- nil, nil, 234, nil, 175, 57, nil, nil, nil, 291,
- nil, nil, nil, 293, nil, 295, nil, 296, nil, nil,
- nil, nil, nil ]
+ nil, nil, nil, nil, nil, nil, nil, nil, nil, nil,
+ 220, 256, 1054, 1114, 1174, 48, 1283, nil, nil, nil,
+ nil, nil, 1402, 1462, 1546, nil, nil, nil, nil, nil,
+ 1606, nil, 201, 202, nil, 1786, nil, nil, 267, 1906,
+ 246, 2026, 2086, 2146, 2206, 2266, 2326, 2386, 2446, 2506,
+ 2590, 2650, 2710, 2770, 2830, 2890, 2950, 3010, 3070, 3130,
+ 3190, 3250, 3310, 3370, 3430, 3490, 3550, 3610, 3670, 3730,
+ 3790, 217, 248, 3970, 4030, 245, 238, 4210, 219, nil,
+ nil, nil, 1550, -1, 614, 938, 203, nil, 220, 55,
+ 4822, 5562, nil, 5488, 5431, 200, 195, nil, 186, nil,
+ nil, nil, nil, nil, nil, nil, 173, nil, 170, nil,
+ 90, nil, nil, 166, nil, 14, nil, nil, 170, 4882,
+ 60, 5656, 4762, nil, 135, nil, nil, nil, nil, 84,
+ 79, 61, 266, 5995, 153, 4090, 5, 3, 2, 3910,
+ 5302, 1966, nil, nil, -9, 82, 108, 287, -2, 4,
+ -4, -1, nil, nil, nil, nil, nil, nil, 6042, 61,
+ 1346, 2, 350, 5505, 6253, 5579, 5864, 6012, 6181, 5736,
+ 6396, 6450, 6467, 6325, 6521, 6161, 6136, 5894, 5847, nil,
+ nil, 406, 231, 209, nil, 6723, 5609, 202, nil, 276,
+ 239, 5719, nil, 241, nil, 120, nil, nil, 262, nil,
+ 230, nil, nil, nil, nil, nil, 217, 189, -24, 204,
+ -7, nil, 1258, 1342, 1846, 170, 132, 4379, -28, 4150,
+ 21, 55, nil, 4354, 4498, nil, nil, nil, nil, 4558,
+ nil, nil, nil, nil, 92, nil, nil, nil, nil, 101,
+ 119, 6561, 155, 6615, 4942, 5002, 5122, nil, 3850, 5242,
+ 181, nil, 170, nil, 185, nil, 187, nil, nil, nil,
+ nil, nil, nil, nil, nil, nil, nil, nil, nil, nil,
+ nil, 195, 5182, 6669, 200, nil, 93, nil, 200, 206,
+ nil, 149, 30, 5062, nil, nil, 4678, 4618, nil, 222,
+ 83, 226, 204, 9, 4438, nil, 4270, 237, nil, 5405,
+ nil, nil, nil, nil, nil, nil, nil, nil, nil, 178,
+ 58, 238, nil, nil, 6089, 6233, 5374, 6305, 6379, 260,
+ 1726, 203, -8, nil, nil, 263, 1666, nil, nil, nil,
+ 251, nil, nil, nil, 5941, 13, 118, nil, 994, 274,
+ 251, nil, 276, 277, nil, nil, 277, nil, nil, 934,
+ nil, nil, nil, 282, 253, nil, nil, nil, nil, nil,
+ 287, nil, nil, nil, nil, 610, nil, 346, 178, nil,
+ 300, nil, nil, 58, nil, 304, nil, 306, nil, 307,
+ nil, nil, nil, 278, nil, nil, nil, nil ]
racc_action_default = [
- -230, -231, -1, -2, -3, -4, -5, -8, -10, -11,
- -16, -108, -231, -231, -231, -45, -46, -47, -48, -49,
- -50, -51, -52, -53, -54, -55, -56, -57, -58, -59,
- -60, -61, -62, -63, -64, -65, -66, -67, -68, -73,
- -74, -78, -231, -231, -231, -231, -231, -119, -121, -231,
- -231, -157, -167, -231, -231, -231, -180, -181, -182, -183,
- -184, -231, -186, -231, -197, -200, -231, -205, -206, -207,
- -208, -209, -210, -211, -231, -231, -7, -231, -231, -231,
- -231, -231, -231, -231, -231, -231, -231, -231, -231, -231,
- -231, -231, -231, -231, -231, -231, -231, -231, -231, -231,
- -231, -231, -231, -231, -231, -231, -128, -123, -230, -230,
- -28, -231, -35, -231, -231, -75, -231, -231, -231, -231,
- -85, -231, -231, -231, -231, -231, -230, -138, -158, -159,
- -120, -230, -230, -147, -149, -150, -151, -152, -153, -43,
- -231, -170, -231, -173, -231, -231, -176, -177, -190, -185,
- -231, -193, -231, -231, -231, -231, -231, 403, -6, -9,
- -12, -13, -14, -15, -231, -18, -19, -20, -21, -22,
- -23, -24, -25, -26, -27, -29, -30, -31, -32, -33,
- -34, -36, -37, -38, -39, -40, -231, -41, -103, -231,
- -79, -231, -223, -229, -217, -214, -212, -117, -129, -206,
- -132, -210, -231, -220, -218, -226, -208, -209, -216, -221,
- -222, -224, -225, -227, -128, -127, -231, -126, -231, -42,
- -212, -70, -80, -231, -83, -212, -163, -166, -231, -77,
- -231, -231, -231, -128, -231, -214, -230, -160, -231, -231,
- -231, -231, -155, -231, -231, -231, -168, -231, -171, -231,
- -174, -231, -187, -188, -189, -191, -231, -194, -195, -196,
- -212, -198, -201, -203, -204, -108, -231, -17, -231, -231,
- -212, -105, -128, -116, -231, -215, -231, -213, -231, -231,
- -212, -131, -133, -217, -218, -219, -220, -223, -226, -228,
- -229, -124, -125, -213, -231, -72, -231, -82, -231, -213,
- -231, -76, -231, -88, -231, -94, -231, -231, -98, -214,
- -212, -214, -231, -231, -141, -231, -161, -212, -230, -231,
- -148, -156, -154, -44, -169, -172, -179, -175, -178, -192,
- -231, -231, -107, -231, -213, -212, -111, -118, -112, -130,
- -134, -135, -231, -69, -81, -84, -164, -165, -88, -87,
- -231, -231, -94, -93, -231, -231, -102, -97, -99, -231,
- -231, -231, -114, -230, -142, -143, -144, -231, -231, -139,
- -140, -231, -146, -199, -202, -104, -106, -115, -122, -71,
- -86, -89, -231, -92, -231, -231, -109, -110, -113, -231,
- -162, -136, -145, -231, -91, -231, -96, -231, -101, -137,
- -90, -95, -100 ]
+ -3, -241, -1, -2, -4, -5, -8, -10, -16, -21,
+ -241, -241, -241, -33, -34, -241, -241, -241, -241, -61,
+ -62, -63, -64, -65, -66, -67, -68, -69, -70, -71,
+ -72, -73, -74, -75, -76, -77, -78, -79, -80, -81,
+ -86, -90, -241, -241, -241, -241, -241, -174, -175, -176,
+ -177, -178, -241, -241, -241, -189, -190, -191, -192, -193,
+ -241, -195, -241, -208, -211, -241, -216, -217, -241, -241,
+ -7, -241, -241, -241, -241, -241, -241, -241, -241, -126,
+ -241, -241, -241, -241, -241, -241, -241, -241, -241, -241,
+ -241, -241, -241, -241, -241, -241, -241, -241, -241, -241,
+ -241, -241, -121, -240, -240, -22, -23, -241, -240, -136,
+ -157, -158, -46, -241, -47, -54, -241, -87, -241, -241,
+ -241, -241, -97, -241, -241, -240, -218, -145, -147, -148,
+ -149, -150, -151, -153, -154, -14, -218, -180, -218, -182,
+ -241, -185, -186, -241, -194, -241, -199, -202, -241, -206,
+ -241, -241, -241, 418, -6, -9, -11, -12, -13, -17,
+ -18, -19, -20, -241, -218, -241, -79, -80, -81, -229,
+ -235, -223, -127, -130, -241, -226, -224, -232, -175, -176,
+ -177, -178, -222, -227, -228, -230, -231, -233, -59, -241,
+ -36, -37, -38, -39, -40, -41, -42, -43, -44, -45,
+ -48, -49, -50, -51, -52, -53, -55, -56, -241, -57,
+ -115, -241, -218, -83, -91, -126, -125, -241, -124, -241,
+ -220, -241, -28, -240, -159, -241, -58, -92, -241, -95,
+ -218, -162, -164, -165, -166, -167, -169, -241, -241, -172,
+ -241, -89, -241, -241, -241, -241, -240, -219, -241, -219,
+ -241, -241, -183, -241, -241, -196, -197, -198, -200, -241,
+ -203, -204, -205, -207, -218, -209, -212, -214, -215, -8,
+ -241, -126, -241, -219, -241, -241, -241, -35, -241, -241,
+ -218, -117, -241, -85, -218, -129, -241, -223, -224, -225,
+ -226, -229, -232, -234, -235, -236, -237, -238, -239, -122,
+ -123, -241, -221, -126, -241, -139, -241, -160, -218, -241,
+ -94, -241, -219, -241, -170, -171, -241, -241, -88, -241,
+ -100, -241, -106, -241, -241, -110, -240, -241, -155, -241,
+ -146, -152, -15, -179, -181, -184, -187, -188, -201, -241,
+ -241, -218, -26, -128, -133, -131, -132, -60, -119, -241,
+ -219, -82, -241, -25, -29, -218, -240, -140, -141, -142,
+ -241, -93, -96, -163, -168, -241, -100, -99, -241, -241,
+ -106, -105, -241, -241, -109, -111, -241, -137, -138, -241,
+ -156, -210, -213, -241, -30, -116, -118, -84, -120, -27,
+ -241, -161, -173, -98, -101, -241, -104, -241, -240, -134,
+ -241, -144, -24, -31, -135, -241, -103, -241, -108, -241,
+ -113, -114, -143, -220, -102, -107, -112, -32 ]
racc_goto_table = [
- 2, 115, 4, 131, 110, 112, 113, 149, 137, 274,
- 196, 262, 188, 135, 195, 225, 353, 236, 187, 368,
- 349, 320, 239, 321, 308, 160, 161, 162, 163, 216,
- 218, 159, 337, 235, 119, 121, 122, 123, 276, 76,
- 272, 355, 140, 142, 339, 139, 139, 144, 270, 312,
- 307, 381, 260, 148, 313, 364, 240, 222, 155, 346,
- 328, 257, 294, 383, 389, 380, 258, 298, 3, 255,
- 256, 164, 254, 151, 139, 165, 166, 167, 168, 169,
- 170, 171, 172, 173, 174, 175, 176, 177, 178, 179,
- 180, 181, 182, 183, 184, 185, 186, 271, 191, 358,
- 215, 215, 330, 220, 153, 1, 139, 228, nil, 158,
- 139, nil, 333, nil, nil, nil, nil, 191, 280, nil,
- nil, nil, 342, 359, nil, 361, nil, nil, 237, nil,
- nil, nil, nil, 237, 242, nil, 317, 310, nil, nil,
- nil, 309, 311, nil, nil, nil, nil, nil, 265, nil,
- nil, nil, 360, 259, nil, nil, 266, 131, nil, 367,
- nil, nil, nil, 137, nil, nil, nil, nil, 135, nil,
- nil, nil, nil, nil, nil, nil, 335, 377, nil, nil,
- nil, 186, 295, nil, 119, 121, 122, 374, nil, nil,
- nil, nil, nil, nil, nil, nil, nil, nil, nil, 137,
- nil, 137, 329, nil, 135, nil, 135, nil, nil, nil,
- nil, nil, nil, nil, nil, nil, nil, nil, nil, nil,
- nil, nil, nil, 296, 139, 191, 191, nil, nil, nil,
- 302, 304, nil, nil, nil, nil, nil, 323, 314, 323,
- nil, 326, 376, 144, nil, nil, nil, nil, 148, nil,
+ 2, 112, 114, 115, 117, 116, 224, 220, 125, 129,
+ 131, 189, 210, 144, 301, 266, 330, 230, 367, 325,
+ 376, 239, 409, 224, 246, 164, 324, 70, 121, 123,
+ 124, 280, 223, 394, 250, 343, 251, 105, 106, 135,
+ 135, 143, 227, 136, 138, 209, 371, 146, 264, 245,
+ 390, 151, 239, 217, 219, 354, 304, 357, 155, 156,
+ 157, 158, 272, 327, 393, 163, 188, 190, 191, 192,
+ 193, 194, 195, 196, 197, 198, 199, 200, 201, 202,
+ 203, 204, 205, 206, 207, 208, 383, 135, 331, 216,
+ 216, 212, 154, 221, 396, 363, 315, 314, 380, 375,
+ 336, 260, 159, 160, 161, 162, 261, 135, 3, 258,
+ 282, 240, 259, 257, 147, 149, 262, 1, nil, nil,
+ nil, 305, nil, 308, 281, nil, nil, 239, 311, nil,
+ nil, nil, nil, nil, nil, nil, nil, nil, 125, 269,
+ 129, 131, nil, nil, 328, nil, nil, nil, nil, 263,
+ nil, 114, 270, nil, nil, 121, 123, 124, nil, nil,
+ nil, 284, 339, nil, nil, nil, nil, nil, nil, nil,
+ nil, nil, nil, nil, nil, nil, nil, 283, 349, nil,
+ nil, nil, 352, nil, nil, nil, nil, nil, nil, nil,
+ nil, nil, nil, nil, nil, nil, nil, 208, nil, nil,
+ nil, nil, nil, nil, 382, nil, 360, 417, nil, nil,
+ 129, 131, 338, nil, 239, nil, nil, 341, nil, nil,
+ nil, nil, nil, nil, 378, nil, nil, nil, 309, nil,
+ 188, nil, nil, nil, nil, nil, 332, nil, nil, 384,
+ 143, 337, 319, 321, nil, nil, 146, 365, nil, 355,
+ nil, nil, nil, 389, 378, nil, nil, nil, nil, nil,
+ 344, 345, 346, 386, 347, 348, nil, nil, nil, 358,
nil, nil, nil, nil, nil, nil, nil, nil, nil, nil,
- 323, 332, nil, nil, nil, nil, nil, 191, nil, 365,
- 340, 341, nil, nil, nil, nil, nil, nil, nil, nil,
- nil, nil, nil, nil, nil, 323, nil, nil, nil, nil,
- nil, nil, 347, nil, nil, nil, nil, nil, nil, 139,
- nil, nil, nil, nil, 379, nil, nil, nil, nil, nil,
- nil, nil, nil, nil, nil, nil, nil, nil, nil, 371,
- 370, nil, nil, nil, nil, nil, 186, nil, nil, nil,
+ nil, nil, nil, nil, nil, nil, nil, nil, 221, nil,
+ nil, nil, 129, 131, nil, nil, 410, nil, nil, 364,
+ nil, nil, 188, 413, 332, nil, nil, nil, nil, nil,
+ 188, nil, nil, nil, nil, 387, nil, nil, nil, nil,
nil, nil, nil, nil, nil, nil, nil, nil, nil, nil,
- nil, nil, 119, nil, nil, nil, nil, nil, nil, nil,
+ nil, nil, nil, nil, nil, nil, 208, nil, nil, nil,
nil, nil, nil, nil, nil, nil, nil, nil, nil, nil,
- nil, nil, nil, nil, nil, 370, nil, nil, nil, nil,
+ nil, nil, nil, nil, 121, nil, nil, nil, nil, nil,
nil, nil, nil, nil, nil, nil, nil, nil, nil, nil,
- nil, nil, 393, nil, 395, 397 ]
+ nil, nil, nil, nil, nil, nil, nil, nil, nil, 400,
+ nil, nil, nil, nil, nil, nil, nil, nil, nil, 221,
+ nil, nil, nil, nil, nil, 405, nil, 407, 411 ]
racc_goto_check = [
- 2, 40, 4, 65, 10, 10, 10, 82, 32, 56,
- 57, 89, 52, 38, 55, 45, 48, 66, 13, 67,
- 47, 73, 66, 73, 50, 8, 8, 8, 8, 61,
- 61, 7, 58, 55, 10, 10, 10, 10, 39, 6,
- 59, 51, 12, 12, 62, 10, 10, 10, 53, 56,
- 49, 46, 45, 10, 69, 70, 72, 44, 10, 75,
- 77, 78, 39, 48, 67, 47, 79, 39, 3, 83,
- 84, 12, 86, 87, 10, 10, 10, 10, 10, 10,
+ 2, 10, 10, 10, 37, 6, 49, 13, 57, 35,
+ 34, 19, 50, 80, 14, 88, 65, 42, 44, 47,
+ 59, 36, 48, 49, 15, 11, 46, 5, 10, 10,
+ 10, 51, 58, 43, 15, 54, 15, 9, 9, 6,
+ 6, 6, 41, 8, 8, 20, 45, 6, 42, 58,
+ 59, 10, 36, 53, 53, 16, 61, 62, 6, 6,
+ 6, 6, 15, 64, 44, 10, 10, 10, 10, 10,
10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
- 10, 10, 10, 10, 10, 10, 10, 52, 10, 50,
- 10, 10, 39, 12, 88, 1, 10, 12, nil, 6,
- 10, nil, 39, nil, nil, nil, nil, 10, 57, nil,
- nil, nil, 39, 56, nil, 56, nil, nil, 4, nil,
- nil, nil, nil, 4, 4, nil, 45, 57, nil, nil,
- nil, 55, 55, nil, nil, nil, nil, nil, 10, nil,
- nil, nil, 39, 2, nil, nil, 2, 65, nil, 39,
- nil, nil, nil, 32, nil, nil, nil, nil, 38, nil,
- nil, nil, nil, nil, nil, nil, 57, 39, nil, nil,
- nil, 10, 40, nil, 10, 10, 10, 89, nil, nil,
- nil, nil, nil, nil, nil, nil, nil, nil, nil, 32,
- nil, 32, 82, nil, 38, nil, 38, nil, nil, nil,
+ 10, 10, 10, 10, 10, 10, 12, 6, 67, 10,
+ 10, 8, 5, 10, 45, 68, 69, 71, 65, 47,
+ 75, 76, 9, 9, 9, 9, 77, 6, 3, 81,
+ 15, 8, 82, 84, 85, 86, 87, 1, nil, nil,
+ nil, 49, nil, 42, 50, nil, nil, 36, 15, nil,
+ nil, nil, nil, nil, nil, nil, nil, nil, 57, 6,
+ 35, 34, nil, nil, 49, nil, nil, nil, nil, 2,
+ nil, 10, 2, nil, nil, 10, 10, 10, nil, nil,
+ nil, 11, 15, nil, nil, nil, nil, nil, nil, nil,
+ nil, nil, nil, nil, nil, nil, nil, 37, 15, nil,
+ nil, nil, 15, nil, nil, nil, nil, nil, nil, nil,
+ nil, nil, nil, nil, nil, nil, nil, 10, nil, nil,
+ nil, nil, nil, nil, 88, nil, 15, 14, nil, nil,
+ 35, 34, 80, nil, 36, nil, nil, 11, nil, nil,
+ nil, nil, nil, nil, 49, nil, nil, nil, 2, nil,
+ 10, nil, nil, nil, nil, nil, 6, nil, nil, 15,
+ 6, 6, 2, 2, nil, nil, 6, 19, nil, 11,
+ nil, nil, nil, 15, 49, nil, nil, nil, nil, nil,
+ 10, 10, 10, 50, 10, 10, nil, nil, nil, 57,
nil, nil, nil, nil, nil, nil, nil, nil, nil, nil,
- nil, nil, nil, 2, 10, 10, 10, nil, nil, nil,
- 2, 2, nil, nil, nil, nil, nil, 10, 4, 10,
- nil, 10, 52, 10, nil, nil, nil, nil, 10, nil,
+ nil, nil, nil, nil, nil, nil, nil, nil, 10, nil,
+ nil, nil, 35, 34, nil, nil, 49, nil, nil, 10,
+ nil, nil, 10, 13, 6, nil, nil, nil, nil, nil,
+ 10, nil, nil, nil, nil, 37, nil, nil, nil, nil,
nil, nil, nil, nil, nil, nil, nil, nil, nil, nil,
- 10, 10, nil, nil, nil, nil, nil, 10, nil, 65,
- 10, 10, nil, nil, nil, nil, nil, nil, nil, nil,
- nil, nil, nil, nil, nil, 10, nil, nil, nil, nil,
- nil, nil, 10, nil, nil, nil, nil, nil, nil, 10,
- nil, nil, nil, nil, 40, nil, nil, nil, nil, nil,
- nil, nil, nil, nil, nil, nil, nil, nil, nil, 2,
- 4, nil, nil, nil, nil, nil, 10, nil, nil, nil,
- nil, nil, nil, nil, nil, nil, nil, nil, nil, nil,
- nil, nil, 10, nil, nil, nil, nil, nil, nil, nil,
+ nil, nil, nil, nil, nil, nil, 10, nil, nil, nil,
nil, nil, nil, nil, nil, nil, nil, nil, nil, nil,
- nil, nil, nil, nil, nil, 4, nil, nil, nil, nil,
+ nil, nil, nil, nil, 10, nil, nil, nil, nil, nil,
nil, nil, nil, nil, nil, nil, nil, nil, nil, nil,
- nil, nil, 2, nil, 2, 2 ]
+ nil, nil, nil, nil, nil, nil, nil, nil, nil, 2,
+ nil, nil, nil, nil, nil, nil, nil, nil, nil, 10,
+ nil, nil, nil, nil, nil, 2, nil, 2, 2 ]
racc_goto_pointer = [
- nil, 105, 0, 68, 2, nil, 34, -46, -53, nil,
- -8, nil, -11, -86, nil, nil, nil, nil, nil, nil,
- nil, nil, nil, nil, nil, nil, nil, nil, nil, nil,
- nil, nil, -42, nil, nil, nil, nil, nil, -37, -158,
- -39, nil, nil, nil, -59, -102, -299, -283, -289, -182,
- -208, -265, -92, -141, nil, -92, -186, -96, -243, -151,
- nil, -79, -233, nil, nil, -46, -109, -299, nil, -182,
- -260, nil, -76, -220, nil, -240, nil, -191, -91, -86,
- nil, nil, -54, -81, -80, nil, -78, 10, 40, -144 ]
+ nil, 117, 0, 108, nil, 23, -13, nil, -9, 27,
+ -14, -54, -255, -100, -206, -102, -247, nil, nil, -69,
+ -54, nil, nil, nil, nil, nil, nil, nil, nil, nil,
+ nil, nil, nil, nil, -36, -37, -98, -36, nil, nil,
+ nil, -76, -102, -335, -302, -276, -218, -225, -376, -102,
+ -87, -180, nil, -50, -238, nil, nil, -37, -76, -306,
+ nil, -167, -249, nil, -183, -231, nil, -160, -217, -142,
+ nil, -140, nil, nil, nil, -153, -47, -42, nil, nil,
+ -47, -36, -33, nil, -32, 52, 52, -33, -136 ]
racc_goto_default = [
- nil, nil, 369, nil, 217, 5, 6, 7, 8, 9,
- 11, 10, 306, nil, 15, 39, 16, 17, 18, 19,
- 20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
- 30, 31, 32, 33, 34, 35, 36, 37, 38, nil,
- nil, 40, 41, 116, nil, nil, 120, nil, nil, nil,
- nil, nil, nil, nil, 45, nil, nil, nil, 197, nil,
- 107, nil, 198, 202, 200, 127, nil, nil, 126, nil,
- nil, 132, nil, 133, 134, 226, 145, 147, 56, 57,
- 58, 61, nil, nil, nil, 150, nil, nil, nil, nil ]
+ nil, nil, 377, nil, 4, 5, 6, 7, nil, 8,
+ 9, nil, nil, nil, nil, nil, 222, 13, 14, 323,
+ nil, 19, 20, 21, 22, 23, 24, 25, 26, 27,
+ 28, 29, 30, 31, 32, 33, 34, nil, 40, 41,
+ 118, nil, nil, 122, nil, nil, nil, nil, nil, 218,
+ nil, nil, 102, nil, 172, 174, 173, 109, nil, nil,
+ 108, nil, nil, 126, nil, 127, 128, 132, 231, 232,
+ 233, 234, 235, 238, 140, 142, 55, 56, 57, 60,
+ nil, nil, nil, 145, nil, nil, nil, nil, nil ]
racc_reduce_table = [
0, 0, :racc_error,
- 1, 91, :_reduce_1,
- 1, 91, :_reduce_2,
- 1, 91, :_reduce_none,
- 1, 92, :_reduce_4,
+ 1, 92, :_reduce_1,
+ 1, 92, :_reduce_2,
+ 0, 92, :_reduce_3,
+ 1, 93, :_reduce_4,
1, 95, :_reduce_5,
3, 95, :_reduce_6,
2, 95, :_reduce_7,
1, 96, :_reduce_8,
3, 96, :_reduce_9,
1, 97, :_reduce_none,
- 1, 98, :_reduce_11,
- 3, 98, :_reduce_12,
- 3, 98, :_reduce_13,
- 3, 98, :_reduce_14,
- 3, 98, :_reduce_15,
+ 3, 97, :_reduce_11,
+ 3, 97, :_reduce_12,
+ 3, 97, :_reduce_13,
+ 1, 99, :_reduce_14,
+ 3, 99, :_reduce_15,
+ 1, 98, :_reduce_none,
+ 3, 98, :_reduce_17,
+ 3, 98, :_reduce_18,
+ 3, 98, :_reduce_19,
+ 3, 98, :_reduce_20,
1, 100, :_reduce_none,
- 4, 100, :_reduce_17,
- 3, 100, :_reduce_18,
- 3, 100, :_reduce_19,
- 3, 100, :_reduce_20,
- 3, 100, :_reduce_21,
- 3, 100, :_reduce_22,
- 3, 100, :_reduce_23,
- 3, 100, :_reduce_24,
- 3, 100, :_reduce_25,
- 3, 100, :_reduce_26,
- 3, 100, :_reduce_27,
- 2, 100, :_reduce_28,
- 3, 100, :_reduce_29,
- 3, 100, :_reduce_30,
- 3, 100, :_reduce_31,
- 3, 100, :_reduce_32,
- 3, 100, :_reduce_33,
- 3, 100, :_reduce_34,
- 2, 100, :_reduce_35,
- 3, 100, :_reduce_36,
- 3, 100, :_reduce_37,
- 3, 100, :_reduce_38,
- 3, 100, :_reduce_39,
- 3, 100, :_reduce_40,
- 3, 100, :_reduce_41,
- 3, 100, :_reduce_42,
- 1, 102, :_reduce_43,
- 3, 102, :_reduce_44,
+ 2, 100, :_reduce_22,
+ 2, 100, :_reduce_23,
+ 7, 100, :_reduce_24,
+ 5, 100, :_reduce_25,
+ 5, 100, :_reduce_26,
+ 4, 107, :_reduce_27,
+ 1, 104, :_reduce_28,
+ 3, 104, :_reduce_29,
+ 1, 103, :_reduce_30,
+ 2, 103, :_reduce_31,
+ 4, 103, :_reduce_32,
1, 101, :_reduce_none,
- 1, 105, :_reduce_none,
- 1, 105, :_reduce_none,
- 1, 105, :_reduce_none,
- 1, 105, :_reduce_none,
- 1, 105, :_reduce_none,
- 1, 105, :_reduce_none,
- 1, 105, :_reduce_none,
- 1, 105, :_reduce_none,
- 1, 105, :_reduce_none,
- 1, 105, :_reduce_none,
- 1, 105, :_reduce_none,
- 1, 105, :_reduce_none,
- 1, 106, :_reduce_none,
- 1, 106, :_reduce_none,
- 1, 106, :_reduce_none,
- 1, 106, :_reduce_none,
- 1, 106, :_reduce_none,
- 1, 106, :_reduce_none,
- 1, 106, :_reduce_none,
- 1, 106, :_reduce_none,
- 1, 106, :_reduce_none,
- 1, 123, :_reduce_67,
- 1, 123, :_reduce_68,
- 5, 104, :_reduce_69,
- 3, 104, :_reduce_70,
- 6, 104, :_reduce_71,
- 4, 104, :_reduce_72,
- 1, 104, :_reduce_73,
- 1, 108, :_reduce_74,
- 2, 108, :_reduce_75,
- 4, 131, :_reduce_76,
- 3, 131, :_reduce_77,
- 1, 131, :_reduce_78,
- 3, 132, :_reduce_79,
- 2, 130, :_reduce_80,
- 3, 134, :_reduce_81,
- 2, 134, :_reduce_82,
- 2, 133, :_reduce_83,
- 4, 133, :_reduce_84,
- 2, 111, :_reduce_85,
- 5, 136, :_reduce_86,
- 4, 136, :_reduce_87,
- 0, 137, :_reduce_none,
- 2, 137, :_reduce_89,
- 4, 137, :_reduce_90,
- 3, 137, :_reduce_91,
- 6, 112, :_reduce_92,
- 5, 112, :_reduce_93,
- 0, 138, :_reduce_none,
- 4, 138, :_reduce_95,
- 3, 138, :_reduce_96,
- 5, 110, :_reduce_97,
- 1, 139, :_reduce_98,
- 2, 139, :_reduce_99,
- 5, 140, :_reduce_100,
- 4, 140, :_reduce_101,
- 1, 141, :_reduce_102,
- 1, 103, :_reduce_none,
- 4, 103, :_reduce_104,
- 1, 143, :_reduce_105,
- 3, 143, :_reduce_106,
- 3, 142, :_reduce_107,
- 1, 99, :_reduce_108,
- 6, 99, :_reduce_109,
- 6, 99, :_reduce_110,
- 5, 99, :_reduce_111,
- 5, 99, :_reduce_112,
- 6, 99, :_reduce_113,
- 5, 99, :_reduce_114,
- 4, 148, :_reduce_115,
- 1, 149, :_reduce_116,
- 1, 145, :_reduce_117,
- 3, 145, :_reduce_118,
- 1, 144, :_reduce_119,
- 2, 144, :_reduce_120,
- 1, 144, :_reduce_121,
- 6, 109, :_reduce_122,
- 2, 109, :_reduce_123,
- 3, 150, :_reduce_124,
- 3, 150, :_reduce_125,
- 1, 151, :_reduce_none,
- 1, 151, :_reduce_none,
- 0, 147, :_reduce_128,
- 1, 147, :_reduce_129,
- 3, 147, :_reduce_130,
- 1, 153, :_reduce_none,
+ 1, 101, :_reduce_none,
+ 4, 101, :_reduce_35,
+ 3, 101, :_reduce_36,
+ 3, 101, :_reduce_37,
+ 3, 101, :_reduce_38,
+ 3, 101, :_reduce_39,
+ 3, 101, :_reduce_40,
+ 3, 101, :_reduce_41,
+ 3, 101, :_reduce_42,
+ 3, 101, :_reduce_43,
+ 3, 101, :_reduce_44,
+ 3, 101, :_reduce_45,
+ 2, 101, :_reduce_46,
+ 2, 101, :_reduce_47,
+ 3, 101, :_reduce_48,
+ 3, 101, :_reduce_49,
+ 3, 101, :_reduce_50,
+ 3, 101, :_reduce_51,
+ 3, 101, :_reduce_52,
+ 3, 101, :_reduce_53,
+ 2, 101, :_reduce_54,
+ 3, 101, :_reduce_55,
+ 3, 101, :_reduce_56,
+ 3, 101, :_reduce_57,
+ 3, 101, :_reduce_58,
+ 1, 110, :_reduce_59,
+ 3, 110, :_reduce_60,
+ 1, 108, :_reduce_none,
+ 1, 108, :_reduce_none,
+ 1, 108, :_reduce_none,
+ 1, 108, :_reduce_none,
+ 1, 108, :_reduce_none,
+ 1, 108, :_reduce_none,
+ 1, 108, :_reduce_none,
+ 1, 108, :_reduce_none,
+ 1, 108, :_reduce_none,
+ 1, 108, :_reduce_none,
+ 1, 108, :_reduce_none,
+ 1, 108, :_reduce_none,
+ 1, 108, :_reduce_none,
+ 1, 108, :_reduce_none,
+ 1, 108, :_reduce_none,
+ 1, 108, :_reduce_none,
+ 1, 108, :_reduce_77,
+ 1, 108, :_reduce_78,
+ 1, 108, :_reduce_79,
+ 1, 108, :_reduce_80,
+ 1, 108, :_reduce_81,
+ 5, 109, :_reduce_82,
+ 3, 109, :_reduce_83,
+ 6, 109, :_reduce_84,
+ 4, 109, :_reduce_85,
+ 1, 113, :_reduce_86,
+ 2, 113, :_reduce_87,
+ 4, 129, :_reduce_88,
+ 3, 129, :_reduce_89,
+ 1, 129, :_reduce_90,
+ 3, 130, :_reduce_91,
+ 2, 128, :_reduce_92,
+ 3, 132, :_reduce_93,
+ 2, 132, :_reduce_94,
+ 2, 131, :_reduce_95,
+ 4, 131, :_reduce_96,
+ 2, 116, :_reduce_97,
+ 5, 134, :_reduce_98,
+ 4, 134, :_reduce_99,
+ 0, 135, :_reduce_none,
+ 2, 135, :_reduce_101,
+ 4, 135, :_reduce_102,
+ 3, 135, :_reduce_103,
+ 6, 117, :_reduce_104,
+ 5, 117, :_reduce_105,
+ 0, 136, :_reduce_none,
+ 4, 136, :_reduce_107,
+ 3, 136, :_reduce_108,
+ 5, 115, :_reduce_109,
+ 1, 137, :_reduce_110,
+ 2, 137, :_reduce_111,
+ 5, 138, :_reduce_112,
+ 1, 139, :_reduce_none,
+ 1, 139, :_reduce_none,
+ 1, 111, :_reduce_none,
+ 4, 111, :_reduce_116,
+ 1, 142, :_reduce_117,
+ 3, 142, :_reduce_118,
+ 3, 141, :_reduce_119,
+ 6, 114, :_reduce_120,
+ 2, 114, :_reduce_121,
+ 3, 143, :_reduce_122,
+ 3, 143, :_reduce_123,
+ 1, 144, :_reduce_none,
+ 1, 144, :_reduce_none,
+ 0, 102, :_reduce_126,
+ 1, 102, :_reduce_127,
+ 3, 102, :_reduce_128,
+ 1, 146, :_reduce_none,
+ 1, 146, :_reduce_none,
+ 3, 145, :_reduce_131,
+ 3, 145, :_reduce_132,
+ 3, 145, :_reduce_133,
+ 6, 118, :_reduce_134,
+ 7, 119, :_reduce_135,
+ 1, 151, :_reduce_136,
+ 1, 150, :_reduce_none,
+ 1, 150, :_reduce_none,
+ 1, 152, :_reduce_none,
+ 2, 152, :_reduce_140,
1, 153, :_reduce_none,
1, 153, :_reduce_none,
- 3, 152, :_reduce_134,
- 3, 152, :_reduce_135,
- 6, 113, :_reduce_136,
- 7, 114, :_reduce_137,
- 1, 158, :_reduce_138,
- 1, 157, :_reduce_none,
- 1, 157, :_reduce_none,
+ 7, 120, :_reduce_143,
+ 6, 120, :_reduce_144,
+ 1, 154, :_reduce_145,
+ 3, 154, :_reduce_146,
+ 1, 156, :_reduce_none,
+ 1, 156, :_reduce_none,
+ 1, 156, :_reduce_149,
+ 1, 156, :_reduce_none,
+ 1, 157, :_reduce_151,
+ 3, 157, :_reduce_152,
+ 1, 158, :_reduce_none,
+ 1, 158, :_reduce_none,
+ 1, 155, :_reduce_none,
+ 2, 155, :_reduce_156,
+ 1, 148, :_reduce_none,
+ 1, 148, :_reduce_158,
+ 1, 149, :_reduce_159,
+ 2, 149, :_reduce_160,
+ 4, 149, :_reduce_161,
+ 1, 133, :_reduce_162,
+ 3, 133, :_reduce_163,
+ 1, 159, :_reduce_none,
1, 159, :_reduce_none,
- 2, 159, :_reduce_142,
1, 160, :_reduce_none,
1, 160, :_reduce_none,
- 6, 115, :_reduce_145,
- 5, 115, :_reduce_146,
- 1, 161, :_reduce_147,
- 3, 161, :_reduce_148,
- 1, 163, :_reduce_149,
- 1, 163, :_reduce_150,
- 1, 163, :_reduce_151,
- 1, 163, :_reduce_none,
- 1, 164, :_reduce_153,
- 3, 164, :_reduce_154,
- 1, 162, :_reduce_none,
- 2, 162, :_reduce_156,
- 1, 117, :_reduce_157,
- 1, 155, :_reduce_158,
- 1, 155, :_reduce_159,
- 1, 156, :_reduce_160,
- 2, 156, :_reduce_161,
- 4, 156, :_reduce_162,
- 1, 135, :_reduce_163,
- 3, 135, :_reduce_164,
- 3, 165, :_reduce_165,
- 1, 165, :_reduce_166,
- 1, 107, :_reduce_167,
- 3, 118, :_reduce_168,
- 4, 118, :_reduce_169,
- 2, 118, :_reduce_170,
- 3, 118, :_reduce_171,
- 4, 118, :_reduce_172,
- 2, 118, :_reduce_173,
- 3, 121, :_reduce_174,
- 4, 121, :_reduce_175,
- 2, 121, :_reduce_176,
- 1, 166, :_reduce_177,
- 3, 166, :_reduce_178,
- 3, 167, :_reduce_179,
- 1, 128, :_reduce_none,
- 1, 128, :_reduce_none,
- 1, 128, :_reduce_none,
- 1, 168, :_reduce_183,
- 1, 168, :_reduce_184,
- 2, 169, :_reduce_185,
- 1, 171, :_reduce_186,
- 1, 173, :_reduce_187,
- 1, 174, :_reduce_188,
- 2, 172, :_reduce_189,
- 1, 175, :_reduce_190,
- 1, 176, :_reduce_191,
- 2, 176, :_reduce_192,
- 2, 170, :_reduce_193,
- 2, 177, :_reduce_194,
- 2, 177, :_reduce_195,
- 3, 93, :_reduce_196,
- 0, 178, :_reduce_197,
- 2, 178, :_reduce_198,
- 4, 178, :_reduce_199,
- 1, 116, :_reduce_200,
- 3, 116, :_reduce_201,
- 5, 116, :_reduce_202,
+ 3, 162, :_reduce_168,
+ 1, 162, :_reduce_169,
+ 2, 163, :_reduce_170,
+ 2, 161, :_reduce_171,
+ 1, 164, :_reduce_172,
+ 4, 164, :_reduce_173,
+ 1, 112, :_reduce_174,
+ 1, 122, :_reduce_175,
+ 1, 122, :_reduce_176,
+ 1, 122, :_reduce_177,
+ 1, 122, :_reduce_178,
+ 4, 123, :_reduce_179,
+ 2, 123, :_reduce_180,
+ 4, 123, :_reduce_181,
+ 2, 123, :_reduce_182,
+ 3, 124, :_reduce_183,
+ 4, 124, :_reduce_184,
+ 2, 124, :_reduce_185,
+ 1, 165, :_reduce_186,
+ 3, 165, :_reduce_187,
+ 3, 166, :_reduce_188,
+ 1, 126, :_reduce_none,
+ 1, 126, :_reduce_none,
+ 1, 126, :_reduce_none,
+ 1, 167, :_reduce_192,
+ 1, 167, :_reduce_193,
+ 2, 168, :_reduce_194,
+ 1, 170, :_reduce_195,
+ 1, 172, :_reduce_196,
+ 1, 173, :_reduce_197,
+ 2, 171, :_reduce_198,
+ 1, 174, :_reduce_199,
+ 1, 175, :_reduce_200,
+ 2, 175, :_reduce_201,
+ 2, 169, :_reduce_202,
+ 2, 176, :_reduce_203,
+ 2, 176, :_reduce_204,
+ 3, 94, :_reduce_205,
+ 0, 178, :_reduce_none,
+ 1, 178, :_reduce_none,
+ 0, 177, :_reduce_208,
+ 2, 177, :_reduce_209,
+ 4, 177, :_reduce_210,
+ 1, 121, :_reduce_211,
+ 3, 121, :_reduce_212,
+ 5, 121, :_reduce_213,
1, 179, :_reduce_none,
1, 179, :_reduce_none,
- 1, 124, :_reduce_205,
- 1, 127, :_reduce_206,
- 1, 125, :_reduce_207,
- 1, 126, :_reduce_208,
- 1, 120, :_reduce_209,
- 1, 119, :_reduce_210,
- 1, 122, :_reduce_211,
- 0, 129, :_reduce_none,
- 1, 129, :_reduce_213,
- 0, 146, :_reduce_none,
- 1, 146, :_reduce_none,
- 1, 154, :_reduce_none,
- 1, 154, :_reduce_none,
- 1, 154, :_reduce_none,
- 1, 154, :_reduce_none,
- 1, 154, :_reduce_none,
- 1, 154, :_reduce_none,
- 1, 154, :_reduce_none,
- 1, 154, :_reduce_none,
- 1, 154, :_reduce_none,
- 1, 154, :_reduce_none,
- 1, 154, :_reduce_none,
- 1, 154, :_reduce_none,
- 1, 154, :_reduce_none,
- 1, 154, :_reduce_none,
- 0, 94, :_reduce_230 ]
-
-racc_reduce_n = 231
-
-racc_shift_n = 403
+ 1, 127, :_reduce_216,
+ 1, 125, :_reduce_217,
+ 0, 106, :_reduce_none,
+ 1, 106, :_reduce_219,
+ 0, 105, :_reduce_none,
+ 1, 105, :_reduce_none,
+ 1, 147, :_reduce_none,
+ 1, 147, :_reduce_none,
+ 1, 147, :_reduce_none,
+ 1, 147, :_reduce_none,
+ 1, 147, :_reduce_none,
+ 1, 147, :_reduce_none,
+ 1, 147, :_reduce_none,
+ 1, 147, :_reduce_none,
+ 1, 147, :_reduce_none,
+ 1, 147, :_reduce_none,
+ 1, 147, :_reduce_none,
+ 1, 147, :_reduce_none,
+ 1, 147, :_reduce_none,
+ 1, 147, :_reduce_none,
+ 1, 147, :_reduce_none,
+ 1, 147, :_reduce_none,
+ 1, 147, :_reduce_none,
+ 1, 147, :_reduce_none,
+ 0, 140, :_reduce_240 ]
+
+racc_reduce_n = 241
+
+racc_shift_n = 418
racc_token_table = {
false => 0,
@@ -985,15 +1021,16 @@ racc_token_table = {
:EPP_END => 80,
:EPP_END_TRIM => 81,
:FUNCTION => 82,
- :LOW => 83,
- :HIGH => 84,
- :CALL => 85,
- :LISTSTART => 86,
- :MODULO => 87,
- :TITLE_COLON => 88,
- :CASE_COLON => 89 }
+ :PRIVATE => 83,
+ :ATTR => 84,
+ :TYPE => 85,
+ :LOW => 86,
+ :HIGH => 87,
+ :LISTSTART => 88,
+ :SPLAT => 89,
+ :MODULO => 90 }
-racc_nt_base = 90
+racc_nt_base = 91
racc_use_result_var = true
@@ -1097,30 +1134,35 @@ Racc_token_to_s_table = [
"EPP_END",
"EPP_END_TRIM",
"FUNCTION",
+ "PRIVATE",
+ "ATTR",
+ "TYPE",
"LOW",
"HIGH",
- "CALL",
"LISTSTART",
+ "SPLAT",
"MODULO",
- "TITLE_COLON",
- "CASE_COLON",
"$start",
"program",
"statements",
"epp_expression",
- "nil",
"syntactic_statements",
"syntactic_statement",
- "any_expression",
- "relationship_expression",
- "resource_expression",
+ "assignment",
+ "relationship",
+ "assignments",
+ "resource",
"expression",
- "higher_precedence",
+ "attribute_operations",
+ "additional_resource_bodies",
+ "resource_bodies",
+ "endsemi",
+ "endcomma",
+ "resource_body",
+ "primary_expression",
+ "call_function_expression",
"expressions",
"selector_entries",
- "call_function_expression",
- "primary_expression",
- "literal_expression",
"variable",
"call_method_with_lambda_expression",
"collection_expression",
@@ -1131,19 +1173,12 @@ Racc_token_to_s_table = [
"hostclass_expression",
"node_definition_expression",
"epp_render_expression",
- "function_definition",
+ "reserved_word",
"array",
- "boolean",
- "default",
"hash",
"regex",
- "text_or_name",
- "number",
- "type",
- "undef",
- "name",
"quotedtext",
- "endcomma",
+ "type",
"lambda",
"call_method_expression",
"named_access",
@@ -1155,15 +1190,10 @@ Racc_token_to_s_table = [
"unless_else",
"case_options",
"case_option",
- "case_colon",
+ "options_statements",
+ "nil",
"selector_entry",
"selector_entry_list",
- "at",
- "resourceinstances",
- "endsemi",
- "attribute_operations",
- "resourceinst",
- "title_colon",
"collect_query",
"optional_query",
"attribute_operation",
@@ -1179,7 +1209,13 @@ Racc_token_to_s_table = [
"nodeparent",
"hostname",
"dotted_name",
+ "name_or_number",
"parameter",
+ "untyped_parameter",
+ "typed_parameter",
+ "regular_parameter",
+ "splat_parameter",
+ "parameter_type",
"hashpairs",
"hashpair",
"string",
@@ -1193,6 +1229,7 @@ Racc_token_to_s_table = [
"dqtail",
"sublocated_text",
"epp_parameters_list",
+ "optional_statements",
"epp_end" ]
Racc_debug_parser = false
@@ -1201,23 +1238,28 @@ Racc_debug_parser = false
# reduce 0 omitted
-module_eval(<<'.,.,', 'egrammar.ra', 66)
+module_eval(<<'.,.,', 'egrammar.ra', 65)
def _reduce_1(val, _values, result)
result = create_program(Factory.block_or_expression(*val[0]))
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 67)
+module_eval(<<'.,.,', 'egrammar.ra', 66)
def _reduce_2(val, _values, result)
result = create_program(Factory.block_or_expression(*val[0]))
result
end
.,.,
-# reduce 3 omitted
+module_eval(<<'.,.,', 'egrammar.ra', 67)
+ def _reduce_3(val, _values, result)
+ result = create_empty_program()
+ result
+ end
+.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 72)
+module_eval(<<'.,.,', 'egrammar.ra', 71)
def _reduce_4(val, _values, result)
result = transform_calls(val[0])
result
@@ -1245,14 +1287,14 @@ module_eval(<<'.,.,', 'egrammar.ra', 80)
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 84)
+module_eval(<<'.,.,', 'egrammar.ra', 87)
def _reduce_8(val, _values, result)
result = val[0]
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 85)
+module_eval(<<'.,.,', 'egrammar.ra', 88)
def _reduce_9(val, _values, result)
result = aryfy(val[0]).push val[2]
result
@@ -1261,749 +1303,720 @@ module_eval(<<'.,.,', 'egrammar.ra', 85)
# reduce 10 omitted
-module_eval(<<'.,.,', 'egrammar.ra', 91)
+module_eval(<<'.,.,', 'egrammar.ra', 93)
def _reduce_11(val, _values, result)
- result = val[0]
+ result = val[0].set(val[2]) ; loc result, val[1]
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 92)
+module_eval(<<'.,.,', 'egrammar.ra', 94)
def _reduce_12(val, _values, result)
- result = val[0].relop(val[1][:value], val[2]); loc result, val[1]
+ result = val[0].plus_set(val[2]) ; loc result, val[1]
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 93)
+module_eval(<<'.,.,', 'egrammar.ra', 95)
def _reduce_13(val, _values, result)
- result = val[0].relop(val[1][:value], val[2]); loc result, val[1]
+ result = val[0].minus_set(val[2]); loc result, val[1]
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 94)
+module_eval(<<'.,.,', 'egrammar.ra', 98)
def _reduce_14(val, _values, result)
- result = val[0].relop(val[1][:value], val[2]); loc result, val[1]
+ result = [val[0]]
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 95)
+module_eval(<<'.,.,', 'egrammar.ra', 99)
def _reduce_15(val, _values, result)
- result = val[0].relop(val[1][:value], val[2]); loc result, val[1]
+ result = val[0].push(val[2])
result
end
.,.,
# reduce 16 omitted
-module_eval(<<'.,.,', 'egrammar.ra', 102)
+module_eval(<<'.,.,', 'egrammar.ra', 103)
def _reduce_17(val, _values, result)
- result = val[0][*val[2]] ; loc result, val[0], val[3]
+ result = val[0].relop(val[1][:value], val[2]); loc result, val[1]
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 103)
+module_eval(<<'.,.,', 'egrammar.ra', 104)
def _reduce_18(val, _values, result)
- result = val[0].in val[2] ; loc result, val[1]
+ result = val[0].relop(val[1][:value], val[2]); loc result, val[1]
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 104)
+module_eval(<<'.,.,', 'egrammar.ra', 105)
def _reduce_19(val, _values, result)
- result = val[0] =~ val[2] ; loc result, val[1]
+ result = val[0].relop(val[1][:value], val[2]); loc result, val[1]
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 105)
+module_eval(<<'.,.,', 'egrammar.ra', 106)
def _reduce_20(val, _values, result)
- result = val[0].mne val[2] ; loc result, val[1]
+ result = val[0].relop(val[1][:value], val[2]); loc result, val[1]
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 106)
- def _reduce_21(val, _values, result)
- result = val[0] + val[2] ; loc result, val[1]
- result
- end
-.,.,
+# reduce 21 omitted
-module_eval(<<'.,.,', 'egrammar.ra', 107)
+module_eval(<<'.,.,', 'egrammar.ra', 115)
def _reduce_22(val, _values, result)
- result = val[0] - val[2] ; loc result, val[1]
+ result = val[1]
+ unless Factory.set_resource_form(result, :virtual)
+ # This is equivalent to a syntax error - additional semantic restrictions apply
+ error val[0], "Virtual (@) can only be applied to a Resource Expression"
+ end
+ # relocate the result
+ loc result, val[0], val[1]
+
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 108)
+module_eval(<<'.,.,', 'egrammar.ra', 126)
def _reduce_23(val, _values, result)
- result = val[0] / val[2] ; loc result, val[1]
+ result = val[1]
+ unless Factory.set_resource_form(result, :exported)
+ # This is equivalent to a syntax error - additional semantic restrictions apply
+ error val[0], "Exported (@@) can only be applied to a Resource Expression"
+ end
+ # relocate the result
+ loc result, val[0], val[1]
+
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 109)
+module_eval(<<'.,.,', 'egrammar.ra', 137)
def _reduce_24(val, _values, result)
- result = val[0] * val[2] ; loc result, val[1]
+ bodies = [Factory.RESOURCE_BODY(val[2], val[4])] + val[5]
+ result = Factory.RESOURCE(val[0], bodies)
+ loc result, val[0], val[6]
+
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 110)
+module_eval(<<'.,.,', 'egrammar.ra', 144)
def _reduce_25(val, _values, result)
- result = val[0] % val[2] ; loc result, val[1]
+ result = Factory.RESOURCE(Factory.fqn(token_text(val[0])), val[2])
+ loc result, val[0], val[4]
+
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 111)
+module_eval(<<'.,.,', 'egrammar.ra', 153)
def _reduce_26(val, _values, result)
- result = val[0] << val[2] ; loc result, val[1]
+ result = case Factory.resource_shape(val[0])
+ when :resource, :class
+ # This catches deprecated syntax.
+ # If the attribute operations does not include +>, then the found expression
+ # is actually a LEFT followed by LITERAL_HASH
+ #
+ unless tmp = transform_resource_wo_title(val[0], val[2])
+ error val[1], "Syntax error resource body without title or hash with +>"
+ end
+ tmp
+ when :defaults
+ Factory.RESOURCE_DEFAULTS(val[0], val[2])
+ when :override
+ # This was only done for override in original - TODO should it be here at all
+ Factory.RESOURCE_OVERRIDE(val[0], val[2])
+ else
+ error val[0], "Expression is not valid as a resource, resource-default, or resource-override"
+ end
+ loc result, val[0], val[4]
+
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 112)
+module_eval(<<'.,.,', 'egrammar.ra', 175)
def _reduce_27(val, _values, result)
- result = val[0] >> val[2] ; loc result, val[1]
+ result = Factory.RESOURCE_BODY(val[0], val[2])
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 113)
+module_eval(<<'.,.,', 'egrammar.ra', 178)
def _reduce_28(val, _values, result)
- result = val[1].minus() ; loc result, val[0]
+ result = [val[0]]
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 114)
+module_eval(<<'.,.,', 'egrammar.ra', 179)
def _reduce_29(val, _values, result)
- result = val[0].ne val[2] ; loc result, val[1]
+ result = val[0].push val[2]
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 115)
+module_eval(<<'.,.,', 'egrammar.ra', 185)
def _reduce_30(val, _values, result)
- result = val[0] == val[2] ; loc result, val[1]
+ result = []
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 116)
+module_eval(<<'.,.,', 'egrammar.ra', 186)
def _reduce_31(val, _values, result)
- result = val[0] > val[2] ; loc result, val[1]
+ result = []
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 117)
+module_eval(<<'.,.,', 'egrammar.ra', 187)
def _reduce_32(val, _values, result)
- result = val[0] >= val[2] ; loc result, val[1]
+ result = val[2]
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 118)
- def _reduce_33(val, _values, result)
- result = val[0] < val[2] ; loc result, val[1]
- result
- end
-.,.,
+# reduce 33 omitted
-module_eval(<<'.,.,', 'egrammar.ra', 119)
- def _reduce_34(val, _values, result)
- result = val[0] <= val[2] ; loc result, val[1]
- result
- end
-.,.,
+# reduce 34 omitted
-module_eval(<<'.,.,', 'egrammar.ra', 120)
+module_eval(<<'.,.,', 'egrammar.ra', 194)
def _reduce_35(val, _values, result)
- result = val[1].not ; loc result, val[0]
+ result = val[0][*val[2]] ; loc result, val[0], val[3]
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 121)
+module_eval(<<'.,.,', 'egrammar.ra', 195)
def _reduce_36(val, _values, result)
- result = val[0].and val[2] ; loc result, val[1]
+ result = val[0].in val[2] ; loc result, val[1]
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 122)
+module_eval(<<'.,.,', 'egrammar.ra', 196)
def _reduce_37(val, _values, result)
- result = val[0].or val[2] ; loc result, val[1]
+ result = val[0] =~ val[2] ; loc result, val[1]
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 123)
+module_eval(<<'.,.,', 'egrammar.ra', 197)
def _reduce_38(val, _values, result)
- result = val[0].set(val[2]) ; loc result, val[1]
+ result = val[0].mne val[2] ; loc result, val[1]
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 124)
+module_eval(<<'.,.,', 'egrammar.ra', 198)
def _reduce_39(val, _values, result)
- result = val[0].plus_set(val[2]) ; loc result, val[1]
+ result = val[0] + val[2] ; loc result, val[1]
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 125)
+module_eval(<<'.,.,', 'egrammar.ra', 199)
def _reduce_40(val, _values, result)
- result = val[0].minus_set(val[2]); loc result, val[1]
+ result = val[0] - val[2] ; loc result, val[1]
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 126)
+module_eval(<<'.,.,', 'egrammar.ra', 200)
def _reduce_41(val, _values, result)
- result = val[0].select(*val[2]) ; loc result, val[0]
+ result = val[0] / val[2] ; loc result, val[1]
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 127)
+module_eval(<<'.,.,', 'egrammar.ra', 201)
def _reduce_42(val, _values, result)
- result = val[1].paren() ; loc result, val[0]
+ result = val[0] * val[2] ; loc result, val[1]
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 135)
+module_eval(<<'.,.,', 'egrammar.ra', 202)
def _reduce_43(val, _values, result)
- result = [val[0]]
+ result = val[0] % val[2] ; loc result, val[1]
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 136)
+module_eval(<<'.,.,', 'egrammar.ra', 203)
def _reduce_44(val, _values, result)
- result = val[0].push(val[2])
+ result = val[0] << val[2] ; loc result, val[1]
result
end
.,.,
-# reduce 45 omitted
-
-# reduce 46 omitted
-
-# reduce 47 omitted
-
-# reduce 48 omitted
-
-# reduce 49 omitted
-
-# reduce 50 omitted
-
-# reduce 51 omitted
-
-# reduce 52 omitted
-
-# reduce 53 omitted
-
-# reduce 54 omitted
-
-# reduce 55 omitted
-
-# reduce 56 omitted
-
-# reduce 57 omitted
-
-# reduce 58 omitted
-
-# reduce 59 omitted
-
-# reduce 60 omitted
-
-# reduce 61 omitted
+module_eval(<<'.,.,', 'egrammar.ra', 204)
+ def _reduce_45(val, _values, result)
+ result = val[0] >> val[2] ; loc result, val[1]
+ result
+ end
+.,.,
-# reduce 62 omitted
+module_eval(<<'.,.,', 'egrammar.ra', 205)
+ def _reduce_46(val, _values, result)
+ result = val[1].minus() ; loc result, val[0]
+ result
+ end
+.,.,
-# reduce 63 omitted
+module_eval(<<'.,.,', 'egrammar.ra', 206)
+ def _reduce_47(val, _values, result)
+ result = val[1].unfold() ; loc result, val[0]
+ result
+ end
+.,.,
-# reduce 64 omitted
+module_eval(<<'.,.,', 'egrammar.ra', 207)
+ def _reduce_48(val, _values, result)
+ result = val[0].ne val[2] ; loc result, val[1]
+ result
+ end
+.,.,
-# reduce 65 omitted
+module_eval(<<'.,.,', 'egrammar.ra', 208)
+ def _reduce_49(val, _values, result)
+ result = val[0] == val[2] ; loc result, val[1]
+ result
+ end
+.,.,
-# reduce 66 omitted
+module_eval(<<'.,.,', 'egrammar.ra', 209)
+ def _reduce_50(val, _values, result)
+ result = val[0] > val[2] ; loc result, val[1]
+ result
+ end
+.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 169)
- def _reduce_67(val, _values, result)
- result = val[0]
+module_eval(<<'.,.,', 'egrammar.ra', 210)
+ def _reduce_51(val, _values, result)
+ result = val[0] >= val[2] ; loc result, val[1]
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 170)
- def _reduce_68(val, _values, result)
- result = val[0]
+module_eval(<<'.,.,', 'egrammar.ra', 211)
+ def _reduce_52(val, _values, result)
+ result = val[0] < val[2] ; loc result, val[1]
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 178)
- def _reduce_69(val, _values, result)
- result = Factory.CALL_NAMED(val[0], true, val[2])
- loc result, val[0], val[4]
-
+module_eval(<<'.,.,', 'egrammar.ra', 212)
+ def _reduce_53(val, _values, result)
+ result = val[0] <= val[2] ; loc result, val[1]
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 182)
- def _reduce_70(val, _values, result)
- result = Factory.CALL_NAMED(val[0], true, [])
- loc result, val[0], val[2]
-
+module_eval(<<'.,.,', 'egrammar.ra', 213)
+ def _reduce_54(val, _values, result)
+ result = val[1].not ; loc result, val[0]
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 186)
- def _reduce_71(val, _values, result)
- result = Factory.CALL_NAMED(val[0], true, val[2])
- loc result, val[0], val[4]
- result.lambda = val[5]
-
+module_eval(<<'.,.,', 'egrammar.ra', 214)
+ def _reduce_55(val, _values, result)
+ result = val[0].and val[2] ; loc result, val[1]
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 191)
- def _reduce_72(val, _values, result)
- result = Factory.CALL_NAMED(val[0], true, [])
- loc result, val[0], val[2]
- result.lambda = val[3]
-
+module_eval(<<'.,.,', 'egrammar.ra', 215)
+ def _reduce_56(val, _values, result)
+ result = val[0].or val[2] ; loc result, val[1]
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 195)
- def _reduce_73(val, _values, result)
- result = val[0]
+module_eval(<<'.,.,', 'egrammar.ra', 216)
+ def _reduce_57(val, _values, result)
+ result = val[0].select(*val[2]) ; loc result, val[0]
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 200)
- def _reduce_74(val, _values, result)
- result = val[0]
+module_eval(<<'.,.,', 'egrammar.ra', 217)
+ def _reduce_58(val, _values, result)
+ result = val[1].paren() ; loc result, val[0]
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 201)
- def _reduce_75(val, _values, result)
- result = val[0]; val[0].lambda = val[1]
+module_eval(<<'.,.,', 'egrammar.ra', 227)
+ def _reduce_59(val, _values, result)
+ result = [val[0]]
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 204)
- def _reduce_76(val, _values, result)
- result = Factory.CALL_METHOD(val[0], val[2]); loc result, val[1], val[3]
+module_eval(<<'.,.,', 'egrammar.ra', 228)
+ def _reduce_60(val, _values, result)
+ result = val[0].push(val[2])
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 205)
+# reduce 61 omitted
+
+# reduce 62 omitted
+
+# reduce 63 omitted
+
+# reduce 64 omitted
+
+# reduce 65 omitted
+
+# reduce 66 omitted
+
+# reduce 67 omitted
+
+# reduce 68 omitted
+
+# reduce 69 omitted
+
+# reduce 70 omitted
+
+# reduce 71 omitted
+
+# reduce 72 omitted
+
+# reduce 73 omitted
+
+# reduce 74 omitted
+
+# reduce 75 omitted
+
+# reduce 76 omitted
+
+module_eval(<<'.,.,', 'egrammar.ra', 247)
def _reduce_77(val, _values, result)
- result = Factory.CALL_METHOD(val[0], []); loc result, val[1], val[3]
+ result = Factory.NUMBER(val[0][:value]) ; loc result, val[0]
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 206)
+module_eval(<<'.,.,', 'egrammar.ra', 248)
def _reduce_78(val, _values, result)
- result = Factory.CALL_METHOD(val[0], []); loc result, val[0]
+ result = Factory.literal(val[0][:value]) ; loc result, val[0]
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 211)
+module_eval(<<'.,.,', 'egrammar.ra', 249)
def _reduce_79(val, _values, result)
- result = val[0].dot(Factory.fqn(val[2][:value]))
- loc result, val[1], val[2]
-
+ result = Factory.literal(:default) ; loc result, val[0]
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 223)
+module_eval(<<'.,.,', 'egrammar.ra', 250)
def _reduce_80(val, _values, result)
- result = Factory.LAMBDA(val[0], val[1])
-# loc result, val[1] # TODO
-
+ result = Factory.literal(:undef) ; loc result, val[0]
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 228)
+module_eval(<<'.,.,', 'egrammar.ra', 251)
def _reduce_81(val, _values, result)
- result = val[1]
+ result = Factory.QNAME_OR_NUMBER(val[0][:value]) ; loc result, val[0]
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 229)
+module_eval(<<'.,.,', 'egrammar.ra', 260)
def _reduce_82(val, _values, result)
- result = nil
+ result = Factory.CALL_NAMED(val[0], true, val[2])
+ loc result, val[0], val[4]
+
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 233)
+module_eval(<<'.,.,', 'egrammar.ra', 264)
def _reduce_83(val, _values, result)
- result = []
+ result = Factory.CALL_NAMED(val[0], true, [])
+ loc result, val[0], val[2]
+
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 234)
+module_eval(<<'.,.,', 'egrammar.ra', 268)
def _reduce_84(val, _values, result)
- result = val[1]
+ result = Factory.CALL_NAMED(val[0], true, val[2])
+ loc result, val[0], val[4]
+ result.lambda = val[5]
+
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 244)
+module_eval(<<'.,.,', 'egrammar.ra', 273)
def _reduce_85(val, _values, result)
- result = val[1]
- loc(result, val[0], val[1])
+ result = Factory.CALL_NAMED(val[0], true, [])
+ loc result, val[0], val[2]
+ result.lambda = val[3]
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 251)
+module_eval(<<'.,.,', 'egrammar.ra', 281)
def _reduce_86(val, _values, result)
- result = Factory.IF(val[0], Factory.block_or_expression(*val[2]), val[4])
- loc(result, val[0], (val[4] ? val[4] : val[3]))
-
+ result = val[0]
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 255)
+module_eval(<<'.,.,', 'egrammar.ra', 282)
def _reduce_87(val, _values, result)
- result = Factory.IF(val[0], nil, val[3])
- loc(result, val[0], (val[3] ? val[3] : val[2]))
-
+ result = val[0]; val[0].lambda = val[1]
result
end
.,.,
-# reduce 88 omitted
+module_eval(<<'.,.,', 'egrammar.ra', 285)
+ def _reduce_88(val, _values, result)
+ result = Factory.CALL_METHOD(val[0], val[2]); loc result, val[1], val[3]
+ result
+ end
+.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 263)
+module_eval(<<'.,.,', 'egrammar.ra', 286)
def _reduce_89(val, _values, result)
- result = val[1]
- loc(result, val[0], val[1])
-
+ result = Factory.CALL_METHOD(val[0], []); loc result, val[1], val[3]
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 267)
+module_eval(<<'.,.,', 'egrammar.ra', 287)
def _reduce_90(val, _values, result)
- result = Factory.block_or_expression(*val[2])
- loc result, val[0], val[3]
-
+ result = Factory.CALL_METHOD(val[0], []); loc result, val[0]
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 271)
+module_eval(<<'.,.,', 'egrammar.ra', 291)
def _reduce_91(val, _values, result)
- result = nil # don't think a nop is needed here either
+ result = val[0].dot(Factory.fqn(val[2][:value]))
+ loc result, val[1], val[2]
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 280)
+module_eval(<<'.,.,', 'egrammar.ra', 299)
def _reduce_92(val, _values, result)
- result = Factory.UNLESS(val[1], Factory.block_or_expression(*val[3]), val[5])
- loc result, val[0], val[4]
+ result = Factory.LAMBDA(val[0][:value], val[1][:value])
+ loc result, val[0][:start], val[1][:end]
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 284)
+module_eval(<<'.,.,', 'egrammar.ra', 304)
def _reduce_93(val, _values, result)
- result = Factory.UNLESS(val[1], nil, nil)
- loc result, val[0], val[4]
-
+ result = {:end => val[2], :value =>val[1] }
result
end
.,.,
-# reduce 94 omitted
+module_eval(<<'.,.,', 'egrammar.ra', 305)
+ def _reduce_94(val, _values, result)
+ result = {:end => val[1], :value => nil }
+ result
+ end
+.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 294)
+module_eval(<<'.,.,', 'egrammar.ra', 309)
def _reduce_95(val, _values, result)
- result = Factory.block_or_expression(*val[2])
- loc result, val[0], val[3]
-
+ result = {:start => val[0], :value => [] }
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 298)
+module_eval(<<'.,.,', 'egrammar.ra', 310)
def _reduce_96(val, _values, result)
- result = nil # don't think a nop is needed here either
-
+ result = {:start => val[0], :value => val[1] }
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 306)
+module_eval(<<'.,.,', 'egrammar.ra', 318)
def _reduce_97(val, _values, result)
- result = Factory.CASE(val[1], *val[3])
- loc result, val[0], val[4]
+ result = val[1]
+ loc(result, val[0], val[1])
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 312)
+module_eval(<<'.,.,', 'egrammar.ra', 325)
def _reduce_98(val, _values, result)
- result = [val[0]]
+ result = Factory.IF(val[0], Factory.block_or_expression(*val[2]), val[4])
+ loc(result, val[0], (val[4] ? val[4] : val[3]))
+
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 313)
+module_eval(<<'.,.,', 'egrammar.ra', 329)
def _reduce_99(val, _values, result)
- result = val[0].push val[1]
- result
- end
-.,.,
-
-module_eval(<<'.,.,', 'egrammar.ra', 318)
- def _reduce_100(val, _values, result)
- result = Factory.WHEN(val[0], val[3])
- loc result, val[1], val[4]
+ result = Factory.IF(val[0], nil, val[3])
+ loc(result, val[0], (val[3] ? val[3] : val[2]))
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 322)
+# reduce 100 omitted
+
+module_eval(<<'.,.,', 'egrammar.ra', 337)
def _reduce_101(val, _values, result)
- result = Factory.WHEN(val[0], nil)
- loc result, val[1], val[3]
+ result = val[1]
+ loc(result, val[0], val[1])
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 326)
+module_eval(<<'.,.,', 'egrammar.ra', 341)
def _reduce_102(val, _values, result)
- result = val[0]
+ result = Factory.block_or_expression(*val[2])
+ loc result, val[0], val[3]
+
result
end
.,.,
-# reduce 103 omitted
-
-module_eval(<<'.,.,', 'egrammar.ra', 337)
- def _reduce_104(val, _values, result)
- result = val[1]
+module_eval(<<'.,.,', 'egrammar.ra', 345)
+ def _reduce_103(val, _values, result)
+ result = nil # don't think a nop is needed here either
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 342)
- def _reduce_105(val, _values, result)
- result = [val[0]]
+module_eval(<<'.,.,', 'egrammar.ra', 352)
+ def _reduce_104(val, _values, result)
+ result = Factory.UNLESS(val[1], Factory.block_or_expression(*val[3]), val[5])
+ loc result, val[0], val[4]
+
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 343)
- def _reduce_106(val, _values, result)
- result = val[0].push val[2]
+module_eval(<<'.,.,', 'egrammar.ra', 356)
+ def _reduce_105(val, _values, result)
+ result = Factory.UNLESS(val[1], nil, nil)
+ loc result, val[0], val[4]
+
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 348)
+# reduce 106 omitted
+
+module_eval(<<'.,.,', 'egrammar.ra', 366)
def _reduce_107(val, _values, result)
- result = Factory.MAP(val[0], val[2]) ; loc result, val[1]
+ result = Factory.block_or_expression(*val[2])
+ loc result, val[0], val[3]
+
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 360)
+module_eval(<<'.,.,', 'egrammar.ra', 370)
def _reduce_108(val, _values, result)
- result = val[0]
-
+ result = nil # don't think a nop is needed here either
+
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 363)
+module_eval(<<'.,.,', 'egrammar.ra', 377)
def _reduce_109(val, _values, result)
- result = case Factory.resource_shape(val[1])
- when :resource, :class
- tmp = Factory.RESOURCE(Factory.fqn(token_text(val[1])), val[3])
- tmp.form = val[0]
- tmp
- when :defaults
- error val[1], "A resource default can not be virtual or exported"
- when :override
- error val[1], "A resource override can not be virtual or exported"
- else
- error val[1], "Expression is not valid as a resource, resource-default, or resource-override"
- end
- loc result, val[1], val[4]
+ result = Factory.CASE(val[1], *val[3])
+ loc result, val[0], val[4]
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 378)
+module_eval(<<'.,.,', 'egrammar.ra', 383)
def _reduce_110(val, _values, result)
- result = case Factory.resource_shape(val[1])
- when :resource, :class, :defaults, :override
- error val[1], "Defaults are not virtualizable"
- else
- error val[1], "Expression is not valid as a resource, resource-default, or resource-override"
- end
-
+ result = [val[0]]
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 386)
+module_eval(<<'.,.,', 'egrammar.ra', 384)
def _reduce_111(val, _values, result)
- result = case Factory.resource_shape(val[0])
- when :resource, :class
- Factory.RESOURCE(Factory.fqn(token_text(val[0])), val[2])
- when :defaults
- error val[1], "A resource default can not specify a resource name"
- when :override
- error val[1], "A resource override does not allow override of name of resource"
- else
- error val[1], "Expression is not valid as a resource, resource-default, or resource-override"
- end
- loc result, val[0], val[4]
-
+ result = val[0].push val[1]
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 399)
+module_eval(<<'.,.,', 'egrammar.ra', 389)
def _reduce_112(val, _values, result)
- result = case Factory.resource_shape(val[0])
- when :resource, :class
- # This catches deprecated syntax.
- # If the attribute operations does not include +>, then the found expression
- # is actually a LEFT followed by LITERAL_HASH
- #
- unless tmp = transform_resource_wo_title(val[0], val[2])
- error val[1], "Syntax error resource body without title or hash with +>"
- end
- tmp
- when :defaults
- Factory.RESOURCE_DEFAULTS(val[0], val[2])
- when :override
- # This was only done for override in original - TODO shuld it be here at all
- Factory.RESOURCE_OVERRIDE(val[0], val[2])
- else
- error val[0], "Expression is not valid as a resource, resource-default, or resource-override"
- end
- loc result, val[0], val[4]
-
+ result = Factory.WHEN(val[0], val[3]); loc result, val[1], val[4]
+
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 420)
- def _reduce_113(val, _values, result)
- result = Factory.RESOURCE(Factory.fqn(token_text(val[1])), val[3])
- result.form = val[0]
- loc result, val[1], val[5]
-
- result
- end
-.,.,
+# reduce 113 omitted
-module_eval(<<'.,.,', 'egrammar.ra', 425)
- def _reduce_114(val, _values, result)
- result = Factory.RESOURCE(Factory.fqn(token_text(val[0])), val[2])
- loc result, val[0], val[4]
-
- result
- end
-.,.,
+# reduce 114 omitted
-module_eval(<<'.,.,', 'egrammar.ra', 430)
- def _reduce_115(val, _values, result)
- result = Factory.RESOURCE_BODY(val[0], val[2])
- result
- end
-.,.,
+# reduce 115 omitted
-module_eval(<<'.,.,', 'egrammar.ra', 432)
+module_eval(<<'.,.,', 'egrammar.ra', 405)
def _reduce_116(val, _values, result)
- result = val[0]
+ result = val[1]
+
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 435)
+module_eval(<<'.,.,', 'egrammar.ra', 410)
def _reduce_117(val, _values, result)
result = [val[0]]
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 436)
+module_eval(<<'.,.,', 'egrammar.ra', 411)
def _reduce_118(val, _values, result)
result = val[0].push val[2]
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 441)
+module_eval(<<'.,.,', 'egrammar.ra', 416)
def _reduce_119(val, _values, result)
- result = :virtual
+ result = Factory.MAP(val[0], val[2]) ; loc result, val[1]
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 442)
+module_eval(<<'.,.,', 'egrammar.ra', 426)
def _reduce_120(val, _values, result)
- result = :exported
- result
- end
-.,.,
-
-module_eval(<<'.,.,', 'egrammar.ra', 443)
- def _reduce_121(val, _values, result)
- result = :exported
- result
- end
-.,.,
-
-module_eval(<<'.,.,', 'egrammar.ra', 455)
- def _reduce_122(val, _values, result)
result = Factory.COLLECT(val[0], val[1], val[3])
loc result, val[0], val[5]
@@ -2011,8 +2024,8 @@ module_eval(<<'.,.,', 'egrammar.ra', 455)
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 459)
- def _reduce_123(val, _values, result)
+module_eval(<<'.,.,', 'egrammar.ra', 430)
+ def _reduce_121(val, _values, result)
result = Factory.COLLECT(val[0], val[1], [])
loc result, val[0], val[1]
@@ -2020,53 +2033,51 @@ module_eval(<<'.,.,', 'egrammar.ra', 459)
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 464)
- def _reduce_124(val, _values, result)
+module_eval(<<'.,.,', 'egrammar.ra', 435)
+ def _reduce_122(val, _values, result)
result = Factory.VIRTUAL_QUERY(val[1]) ; loc result, val[0], val[2]
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 465)
- def _reduce_125(val, _values, result)
+module_eval(<<'.,.,', 'egrammar.ra', 436)
+ def _reduce_123(val, _values, result)
result = Factory.EXPORTED_QUERY(val[1]) ; loc result, val[0], val[2]
result
end
.,.,
-# reduce 126 omitted
+# reduce 124 omitted
-# reduce 127 omitted
+# reduce 125 omitted
-module_eval(<<'.,.,', 'egrammar.ra', 478)
- def _reduce_128(val, _values, result)
+module_eval(<<'.,.,', 'egrammar.ra', 445)
+ def _reduce_126(val, _values, result)
result = []
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 479)
- def _reduce_129(val, _values, result)
+module_eval(<<'.,.,', 'egrammar.ra', 446)
+ def _reduce_127(val, _values, result)
result = [val[0]]
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 480)
- def _reduce_130(val, _values, result)
+module_eval(<<'.,.,', 'egrammar.ra', 447)
+ def _reduce_128(val, _values, result)
result = val[0].push(val[2])
result
end
.,.,
-# reduce 131 omitted
+# reduce 129 omitted
-# reduce 132 omitted
+# reduce 130 omitted
-# reduce 133 omitted
-
-module_eval(<<'.,.,', 'egrammar.ra', 496)
- def _reduce_134(val, _values, result)
+module_eval(<<'.,.,', 'egrammar.ra', 463)
+ def _reduce_131(val, _values, result)
result = Factory.ATTRIBUTE_OP(val[0][:value], :'=>', val[2])
loc result, val[0], val[2]
@@ -2074,8 +2085,8 @@ module_eval(<<'.,.,', 'egrammar.ra', 496)
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 500)
- def _reduce_135(val, _values, result)
+module_eval(<<'.,.,', 'egrammar.ra', 467)
+ def _reduce_132(val, _values, result)
result = Factory.ATTRIBUTE_OP(val[0][:value], :'+>', val[2])
loc result, val[0], val[2]
@@ -2083,8 +2094,16 @@ module_eval(<<'.,.,', 'egrammar.ra', 500)
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 510)
- def _reduce_136(val, _values, result)
+module_eval(<<'.,.,', 'egrammar.ra', 471)
+ def _reduce_133(val, _values, result)
+ result = Factory.ATTRIBUTES_OP(val[2]) ; loc result, val[0], val[2]
+
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 480)
+ def _reduce_134(val, _values, result)
result = add_definition(Factory.DEFINITION(classname(val[1][:value]), val[2], val[4]))
loc result, val[0], val[5]
# New lexer does not keep track of this, this is done in validation
@@ -2096,8 +2115,8 @@ module_eval(<<'.,.,', 'egrammar.ra', 510)
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 524)
- def _reduce_137(val, _values, result)
+module_eval(<<'.,.,', 'egrammar.ra', 494)
+ def _reduce_135(val, _values, result)
# Remove this class' name from the namestack as all nested classes have been parsed
namepop
result = add_definition(Factory.HOSTCLASS(classname(val[1][:value]), val[2], token_text(val[3]), val[5]))
@@ -2107,473 +2126,447 @@ module_eval(<<'.,.,', 'egrammar.ra', 524)
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 534)
- def _reduce_138(val, _values, result)
+module_eval(<<'.,.,', 'egrammar.ra', 504)
+ def _reduce_136(val, _values, result)
namestack(val[0][:value]) ; result = val[0]
result
end
.,.,
-# reduce 139 omitted
+# reduce 137 omitted
-# reduce 140 omitted
+# reduce 138 omitted
-# reduce 141 omitted
+# reduce 139 omitted
-module_eval(<<'.,.,', 'egrammar.ra', 543)
- def _reduce_142(val, _values, result)
+module_eval(<<'.,.,', 'egrammar.ra', 513)
+ def _reduce_140(val, _values, result)
result = val[1]
result
end
.,.,
-# reduce 143 omitted
+# reduce 141 omitted
-# reduce 144 omitted
+# reduce 142 omitted
-module_eval(<<'.,.,', 'egrammar.ra', 560)
- def _reduce_145(val, _values, result)
- result = add_definition(Factory.NODE(val[1], val[2], val[4]))
- loc result, val[0], val[5]
+module_eval(<<'.,.,', 'egrammar.ra', 530)
+ def _reduce_143(val, _values, result)
+ result = add_definition(Factory.NODE(val[1], val[3], val[5]))
+ loc result, val[0], val[6]
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 564)
- def _reduce_146(val, _values, result)
- result = add_definition(Factory.NODE(val[1], val[2], nil))
- loc result, val[0], val[4]
+module_eval(<<'.,.,', 'egrammar.ra', 534)
+ def _reduce_144(val, _values, result)
+ result = add_definition(Factory.NODE(val[1], val[3], nil))
+ loc result, val[0], val[5]
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 574)
- def _reduce_147(val, _values, result)
+module_eval(<<'.,.,', 'egrammar.ra', 544)
+ def _reduce_145(val, _values, result)
result = [result]
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 575)
- def _reduce_148(val, _values, result)
+module_eval(<<'.,.,', 'egrammar.ra', 545)
+ def _reduce_146(val, _values, result)
result = val[0].push(val[2])
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 580)
- def _reduce_149(val, _values, result)
- result = val[0]
- result
- end
-.,.,
+# reduce 147 omitted
-module_eval(<<'.,.,', 'egrammar.ra', 581)
- def _reduce_150(val, _values, result)
- result = val[0]
- result
- end
-.,.,
+# reduce 148 omitted
-module_eval(<<'.,.,', 'egrammar.ra', 582)
- def _reduce_151(val, _values, result)
+module_eval(<<'.,.,', 'egrammar.ra', 552)
+ def _reduce_149(val, _values, result)
result = Factory.literal(:default); loc result, val[0]
result
end
.,.,
-# reduce 152 omitted
+# reduce 150 omitted
-module_eval(<<'.,.,', 'egrammar.ra', 586)
- def _reduce_153(val, _values, result)
+module_eval(<<'.,.,', 'egrammar.ra', 556)
+ def _reduce_151(val, _values, result)
result = Factory.literal(val[0][:value]); loc result, val[0]
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 587)
- def _reduce_154(val, _values, result)
+module_eval(<<'.,.,', 'egrammar.ra', 557)
+ def _reduce_152(val, _values, result)
result = Factory.concat(val[0], '.', val[2][:value]); loc result, val[0], val[2]
result
end
.,.,
+# reduce 153 omitted
+
+# reduce 154 omitted
+
# reduce 155 omitted
-module_eval(<<'.,.,', 'egrammar.ra', 592)
+module_eval(<<'.,.,', 'egrammar.ra', 566)
def _reduce_156(val, _values, result)
result = val[1]
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 597)
- def _reduce_157(val, _values, result)
- result = Factory.QNAME(val[0][:value]) ; loc result, val[0]
- result
- end
-.,.,
+# reduce 157 omitted
-module_eval(<<'.,.,', 'egrammar.ra', 609)
+module_eval(<<'.,.,', 'egrammar.ra', 583)
def _reduce_158(val, _values, result)
- result = val[0]
+ error val[0], "'class' is not a valid classname"
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 610)
+module_eval(<<'.,.,', 'egrammar.ra', 587)
def _reduce_159(val, _values, result)
- error val[0], "'class' is not a valid classname"
+ result = []
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 614)
+module_eval(<<'.,.,', 'egrammar.ra', 588)
def _reduce_160(val, _values, result)
result = []
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 615)
+module_eval(<<'.,.,', 'egrammar.ra', 589)
def _reduce_161(val, _values, result)
- result = []
- result
- end
-.,.,
-
-module_eval(<<'.,.,', 'egrammar.ra', 616)
- def _reduce_162(val, _values, result)
result = val[1]
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 620)
- def _reduce_163(val, _values, result)
+module_eval(<<'.,.,', 'egrammar.ra', 593)
+ def _reduce_162(val, _values, result)
result = [val[0]]
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 621)
- def _reduce_164(val, _values, result)
+module_eval(<<'.,.,', 'egrammar.ra', 594)
+ def _reduce_163(val, _values, result)
result = val[0].push(val[2])
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 625)
- def _reduce_165(val, _values, result)
- result = Factory.PARAM(val[0][:value], val[2]) ; loc result, val[0]
- result
- end
-.,.,
+# reduce 164 omitted
-module_eval(<<'.,.,', 'egrammar.ra', 626)
- def _reduce_166(val, _values, result)
- result = Factory.PARAM(val[0][:value]); loc result, val[0]
- result
- end
-.,.,
+# reduce 165 omitted
-module_eval(<<'.,.,', 'egrammar.ra', 639)
- def _reduce_167(val, _values, result)
- result = Factory.fqn(val[0][:value]).var ; loc result, val[0]
- result
- end
-.,.,
+# reduce 166 omitted
-module_eval(<<'.,.,', 'egrammar.ra', 645)
+# reduce 167 omitted
+
+module_eval(<<'.,.,', 'egrammar.ra', 606)
def _reduce_168(val, _values, result)
- result = Factory.LIST(val[1]); loc result, val[0], val[2]
+ result = Factory.PARAM(val[0][:value], val[2]) ; loc result, val[0]
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 646)
+module_eval(<<'.,.,', 'egrammar.ra', 607)
def _reduce_169(val, _values, result)
- result = Factory.LIST(val[1]); loc result, val[0], val[3]
+ result = Factory.PARAM(val[0][:value]); loc result, val[0]
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 647)
+module_eval(<<'.,.,', 'egrammar.ra', 610)
def _reduce_170(val, _values, result)
- result = Factory.literal([]) ; loc result, val[0]
+ result = val[1]; val[1].captures_rest()
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 648)
+module_eval(<<'.,.,', 'egrammar.ra', 613)
def _reduce_171(val, _values, result)
- result = Factory.LIST(val[1]); loc result, val[0], val[2]
+ val[1].type_expr(val[0]) ; result = val[1]
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 649)
+module_eval(<<'.,.,', 'egrammar.ra', 616)
def _reduce_172(val, _values, result)
- result = Factory.LIST(val[1]); loc result, val[0], val[3]
+ result = val[0]
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 650)
+module_eval(<<'.,.,', 'egrammar.ra', 617)
def _reduce_173(val, _values, result)
- result = Factory.literal([]) ; loc result, val[0]
+ result = val[0][*val[2]] ; loc result, val[0], val[3]
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 653)
+module_eval(<<'.,.,', 'egrammar.ra', 622)
def _reduce_174(val, _values, result)
- result = Factory.HASH(val[1]); loc result, val[0], val[2]
+ result = Factory.fqn(val[0][:value]).var ; loc result, val[0]
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 654)
+module_eval(<<'.,.,', 'egrammar.ra', 627)
def _reduce_175(val, _values, result)
- result = Factory.HASH(val[1]); loc result, val[0], val[3]
+ result = Factory.RESERVED(val[0][:value]) ; loc result, val[0]
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 655)
+module_eval(<<'.,.,', 'egrammar.ra', 628)
def _reduce_176(val, _values, result)
- result = Factory.literal({}) ; loc result, val[0], val[3]
+ result = Factory.RESERVED(val[0][:value]) ; loc result, val[0]
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 658)
+module_eval(<<'.,.,', 'egrammar.ra', 629)
def _reduce_177(val, _values, result)
- result = [val[0]]
+ result = Factory.RESERVED(val[0][:value]) ; loc result, val[0]
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 659)
+module_eval(<<'.,.,', 'egrammar.ra', 630)
def _reduce_178(val, _values, result)
- result = val[0].push val[2]
+ result = Factory.RESERVED(val[0][:value]) ; loc result, val[0]
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 662)
+module_eval(<<'.,.,', 'egrammar.ra', 636)
def _reduce_179(val, _values, result)
- result = Factory.KEY_ENTRY(val[0], val[2]); loc result, val[1]
+ result = Factory.LIST(val[1]); loc result, val[0], val[3]
result
end
.,.,
-# reduce 180 omitted
-
-# reduce 181 omitted
-
-# reduce 182 omitted
-
-module_eval(<<'.,.,', 'egrammar.ra', 670)
- def _reduce_183(val, _values, result)
- result = Factory.literal(val[0][:value]) ; loc result, val[0]
+module_eval(<<'.,.,', 'egrammar.ra', 637)
+ def _reduce_180(val, _values, result)
+ result = Factory.literal([]) ; loc result, val[0]
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 671)
- def _reduce_184(val, _values, result)
- result = Factory.literal(val[0][:value]) ; loc result, val[0]
+module_eval(<<'.,.,', 'egrammar.ra', 638)
+ def _reduce_181(val, _values, result)
+ result = Factory.LIST(val[1]); loc result, val[0], val[3]
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 673)
- def _reduce_185(val, _values, result)
- result = Factory.string(val[0], *val[1]) ; loc result, val[0], val[1][-1]
+module_eval(<<'.,.,', 'egrammar.ra', 639)
+ def _reduce_182(val, _values, result)
+ result = Factory.literal([]) ; loc result, val[0]
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 674)
- def _reduce_186(val, _values, result)
- result = Factory.literal(val[0][:value]); loc result, val[0]
+module_eval(<<'.,.,', 'egrammar.ra', 642)
+ def _reduce_183(val, _values, result)
+ result = Factory.HASH(val[1]); loc result, val[0], val[2]
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 675)
- def _reduce_187(val, _values, result)
- result = Factory.literal(val[0][:value]); loc result, val[0]
+module_eval(<<'.,.,', 'egrammar.ra', 643)
+ def _reduce_184(val, _values, result)
+ result = Factory.HASH(val[1]); loc result, val[0], val[3]
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 676)
- def _reduce_188(val, _values, result)
- result = Factory.literal(val[0][:value]); loc result, val[0]
+module_eval(<<'.,.,', 'egrammar.ra', 644)
+ def _reduce_185(val, _values, result)
+ result = Factory.literal({}) ; loc result, val[0], val[3]
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 677)
- def _reduce_189(val, _values, result)
- result = [val[0]] + val[1]
+module_eval(<<'.,.,', 'egrammar.ra', 647)
+ def _reduce_186(val, _values, result)
+ result = [val[0]]
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 678)
- def _reduce_190(val, _values, result)
- result = Factory.TEXT(val[0])
+module_eval(<<'.,.,', 'egrammar.ra', 648)
+ def _reduce_187(val, _values, result)
+ result = val[0].push val[2]
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 681)
- def _reduce_191(val, _values, result)
- result = [val[0]]
+module_eval(<<'.,.,', 'egrammar.ra', 651)
+ def _reduce_188(val, _values, result)
+ result = Factory.KEY_ENTRY(val[0], val[2]); loc result, val[1]
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 682)
+# reduce 189 omitted
+
+# reduce 190 omitted
+
+# reduce 191 omitted
+
+module_eval(<<'.,.,', 'egrammar.ra', 659)
def _reduce_192(val, _values, result)
- result = [val[0]] + val[1]
+ result = Factory.literal(val[0][:value]) ; loc result, val[0]
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 685)
+module_eval(<<'.,.,', 'egrammar.ra', 660)
def _reduce_193(val, _values, result)
- result = Factory.HEREDOC(val[0][:value], val[1]); loc result, val[0]
+ result = Factory.literal(val[0][:value]) ; loc result, val[0]
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 688)
+module_eval(<<'.,.,', 'egrammar.ra', 662)
def _reduce_194(val, _values, result)
- result = Factory.SUBLOCATE(val[0], val[1]); loc result, val[0]
+ result = Factory.string(val[0], *val[1]) ; loc result, val[0], val[1][-1]
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 689)
+module_eval(<<'.,.,', 'egrammar.ra', 663)
def _reduce_195(val, _values, result)
- result = Factory.SUBLOCATE(val[0], val[1]); loc result, val[0]
+ result = Factory.literal(val[0][:value]); loc result, val[0]
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 692)
+module_eval(<<'.,.,', 'egrammar.ra', 664)
def _reduce_196(val, _values, result)
- result = Factory.EPP(val[1], val[2]); loc result, val[0]
+ result = Factory.literal(val[0][:value]); loc result, val[0]
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 695)
+module_eval(<<'.,.,', 'egrammar.ra', 665)
def _reduce_197(val, _values, result)
- result = nil
+ result = Factory.literal(val[0][:value]); loc result, val[0]
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 696)
+module_eval(<<'.,.,', 'egrammar.ra', 666)
def _reduce_198(val, _values, result)
- result = []
+ result = [val[0]] + val[1]
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 697)
+module_eval(<<'.,.,', 'egrammar.ra', 667)
def _reduce_199(val, _values, result)
- result = val[1]
+ result = Factory.TEXT(val[0])
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 700)
+module_eval(<<'.,.,', 'egrammar.ra', 670)
def _reduce_200(val, _values, result)
- result = Factory.RENDER_STRING(val[0][:value]); loc result, val[0]
+ result = [val[0]]
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 701)
+module_eval(<<'.,.,', 'egrammar.ra', 671)
def _reduce_201(val, _values, result)
- result = Factory.RENDER_EXPR(val[1]); loc result, val[0], val[2]
+ result = [val[0]] + val[1]
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 702)
+module_eval(<<'.,.,', 'egrammar.ra', 674)
def _reduce_202(val, _values, result)
- result = Factory.RENDER_EXPR(Factory.block_or_expression(*val[2])); loc result, val[0], val[4]
+ result = Factory.HEREDOC(val[0][:value], val[1]); loc result, val[0]
result
end
.,.,
-# reduce 203 omitted
-
-# reduce 204 omitted
-
-module_eval(<<'.,.,', 'egrammar.ra', 708)
- def _reduce_205(val, _values, result)
- result = Factory.NUMBER(val[0][:value]) ; loc result, val[0]
+module_eval(<<'.,.,', 'egrammar.ra', 677)
+ def _reduce_203(val, _values, result)
+ result = Factory.SUBLOCATE(val[0], val[1]); loc result, val[0]
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 709)
- def _reduce_206(val, _values, result)
- result = Factory.QNAME_OR_NUMBER(val[0][:value]) ; loc result, val[0]
+module_eval(<<'.,.,', 'egrammar.ra', 678)
+ def _reduce_204(val, _values, result)
+ result = Factory.SUBLOCATE(val[0], val[1]); loc result, val[0]
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 710)
- def _reduce_207(val, _values, result)
- result = Factory.QREF(val[0][:value]) ; loc result, val[0]
+module_eval(<<'.,.,', 'egrammar.ra', 681)
+ def _reduce_205(val, _values, result)
+ result = Factory.EPP(val[1], val[2]); loc result, val[0]
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 711)
+# reduce 206 omitted
+
+# reduce 207 omitted
+
+module_eval(<<'.,.,', 'egrammar.ra', 688)
def _reduce_208(val, _values, result)
- result = Factory.literal(:undef); loc result, val[0]
+ result = nil
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 712)
+module_eval(<<'.,.,', 'egrammar.ra', 689)
def _reduce_209(val, _values, result)
- result = Factory.literal(:default); loc result, val[0]
+ result = []
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 717)
+module_eval(<<'.,.,', 'egrammar.ra', 690)
def _reduce_210(val, _values, result)
- result = Factory.literal(val[0][:value]) ; loc result, val[0]
+ result = val[1]
result
end
.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 720)
+module_eval(<<'.,.,', 'egrammar.ra', 693)
def _reduce_211(val, _values, result)
- result = Factory.literal(val[0][:value]); loc result, val[0]
+ result = Factory.RENDER_STRING(val[0][:value]); loc result, val[0]
result
end
.,.,
-# reduce 212 omitted
+module_eval(<<'.,.,', 'egrammar.ra', 694)
+ def _reduce_212(val, _values, result)
+ result = Factory.RENDER_EXPR(val[1]); loc result, val[0], val[2]
+ result
+ end
+.,.,
-module_eval(<<'.,.,', 'egrammar.ra', 726)
+module_eval(<<'.,.,', 'egrammar.ra', 695)
def _reduce_213(val, _values, result)
- result = nil
+ result = Factory.RENDER_EXPR(Factory.block_or_expression(*val[2])); loc result, val[0], val[4]
result
end
.,.,
@@ -2582,13 +2575,28 @@ module_eval(<<'.,.,', 'egrammar.ra', 726)
# reduce 215 omitted
-# reduce 216 omitted
+module_eval(<<'.,.,', 'egrammar.ra', 701)
+ def _reduce_216(val, _values, result)
+ result = Factory.QREF(val[0][:value]) ; loc result, val[0]
+ result
+ end
+.,.,
-# reduce 217 omitted
+module_eval(<<'.,.,', 'egrammar.ra', 704)
+ def _reduce_217(val, _values, result)
+ result = Factory.literal(val[0][:value]); loc result, val[0]
+ result
+ end
+.,.,
# reduce 218 omitted
-# reduce 219 omitted
+module_eval(<<'.,.,', 'egrammar.ra', 710)
+ def _reduce_219(val, _values, result)
+ result = nil
+ result
+ end
+.,.,
# reduce 220 omitted
@@ -2610,8 +2618,28 @@ module_eval(<<'.,.,', 'egrammar.ra', 726)
# reduce 229 omitted
-module_eval(<<'.,.,', 'egrammar.ra', 749)
- def _reduce_230(val, _values, result)
+# reduce 230 omitted
+
+# reduce 231 omitted
+
+# reduce 232 omitted
+
+# reduce 233 omitted
+
+# reduce 234 omitted
+
+# reduce 235 omitted
+
+# reduce 236 omitted
+
+# reduce 237 omitted
+
+# reduce 238 omitted
+
+# reduce 239 omitted
+
+module_eval(<<'.,.,', 'egrammar.ra', 737)
+ def _reduce_240(val, _values, result)
result = nil
result
end
diff --git a/lib/puppet/pops/parser/evaluating_parser.rb b/lib/puppet/pops/parser/evaluating_parser.rb
index 22fe53720..596f549bc 100644
--- a/lib/puppet/pops/parser/evaluating_parser.rb
+++ b/lib/puppet/pops/parser/evaluating_parser.rb
@@ -45,11 +45,19 @@ class Puppet::Pops::Parser::EvaluatingParser
@acceptor = nil
end
+ # Create a closure that can be called in the given scope
+ def closure(model, scope)
+ Puppet::Pops::Evaluator::Closure.new(evaluator, model, scope)
+ end
+
def evaluate(scope, model)
return nil unless model
- ast = Puppet::Pops::Model::AstTransformer.new(@file_source, nil).transform(model)
- return nil unless ast
- ast.safeevaluate(scope)
+ evaluator.evaluate(model, scope)
+ end
+
+ def evaluator
+ @@evaluator ||= Puppet::Pops::Evaluator::EvaluatorImpl.new()
+ @@evaluator
end
def validate(parse_result)
@@ -63,7 +71,7 @@ class Puppet::Pops::Parser::EvaluatingParser
end
def validator(acceptor)
- Puppet::Pops::Validation::ValidatorFactory_3_1.new().validator(acceptor)
+ Puppet::Pops::Validation::ValidatorFactory_4_0.new().validator(acceptor)
end
def assert_and_report(parse_result)
@@ -73,52 +81,8 @@ class Puppet::Pops::Parser::EvaluatingParser
end
validation_result = validate(parse_result)
- max_errors = Puppet[:max_errors]
- max_warnings = Puppet[:max_warnings] + 1
- max_deprecations = Puppet[:max_deprecations] + 1
-
- # If there are warnings output them
- warnings = validation_result.warnings
- if warnings.size > 0
- formatter = Puppet::Pops::Validation::DiagnosticFormatterPuppetStyle.new
- emitted_w = 0
- emitted_dw = 0
- validation_result.warnings.each {|w|
- if w.severity == :deprecation
- # Do *not* call Puppet.deprecation_warning it is for internal deprecation, not
- # deprecation of constructs in manifests! (It is not designed for that purpose even if
- # used throughout the code base).
- #
- Puppet.warning(formatter.format(w)) if emitted_dw < max_deprecations
- emitted_dw += 1
- else
- Puppet.warning(formatter.format(w)) if emitted_w < max_warnings
- emitted_w += 1
- end
- break if emitted_w > max_warnings && emitted_dw > max_deprecations # but only then
- }
- end
-
- # If there were errors, report the first found. Use a puppet style formatter.
- errors = validation_result.errors
- if errors.size > 0
- formatter = Puppet::Pops::Validation::DiagnosticFormatterPuppetStyle.new
- if errors.size == 1 || max_errors <= 1
- # raise immediately
- raise Puppet::ParseError.new(formatter.format(errors[0]))
- end
- emitted = 0
- errors.each do |e|
- Puppet.err(formatter.format(e))
- emitted += 1
- break if emitted >= max_errors
- end
- warnings_message = warnings.size > 0 ? ", and #{warnings.size} warnings" : ""
- giving_up_message = "Found #{errors.size} errors#{warnings_message}. Giving up"
- exception = Puppet::ParseError.new(giving_up_message)
- exception.file = errors[0].file
- raise exception
- end
+ Puppet::Pops::IssueReporter.assert_and_report(validation_result,
+ :emit_warnings => true)
parse_result
end
@@ -168,31 +132,7 @@ class Puppet::Pops::Parser::EvaluatingParser
escaped << '"'
end
- # This is a temporary solution to making it possible to use the new evaluator. The main class
- # will eventually have this behavior instead of using transformation to Puppet 3.x AST
- class Transitional < Puppet::Pops::Parser::EvaluatingParser
-
- def evaluator
- @@evaluator ||= Puppet::Pops::Evaluator::EvaluatorImpl.new()
- @@evaluator
- end
-
- def evaluate(scope, model)
- return nil unless model
- evaluator.evaluate(model, scope)
- end
-
- def validator(acceptor)
- Puppet::Pops::Validation::ValidatorFactory_4_0.new().validator(acceptor)
- end
-
- # Create a closure that can be called in the given scope
- def closure(model, scope)
- Puppet::Pops::Evaluator::Closure.new(evaluator, model, scope)
- end
- end
-
- class EvaluatingEppParser < Transitional
+ class EvaluatingEppParser < Puppet::Pops::Parser::EvaluatingParser
def initialize()
@parser = Puppet::Pops::Parser::EppParser.new()
end
diff --git a/lib/puppet/pops/parser/lexer.rb b/lib/puppet/pops/parser/lexer.rb
deleted file mode 100644
index 97a330a56..000000000
--- a/lib/puppet/pops/parser/lexer.rb
+++ /dev/null
@@ -1,753 +0,0 @@
-# the scanner/lexer
-
-require 'forwardable'
-require 'strscan'
-require 'puppet'
-require 'puppet/util/methodhelper'
-
-module Puppet
- class LexError < RuntimeError; end
-end
-
-class Puppet::Pops::Parser::Lexer
- extend Forwardable
-
- attr_reader :file, :lexing_context, :token_queue
-
- attr_reader :locator
-
- attr_accessor :indefine
- alias :indefine? :indefine
-
- def lex_error msg
- raise Puppet::LexError.new(msg)
- end
-
- class Token
- ALWAYS_ACCEPTABLE = Proc.new { |context| true }
-
- include Puppet::Util::MethodHelper
-
- attr_accessor :regex, :name, :string, :skip, :skip_text
- alias skip? skip
-
- # @overload initialize(string)
- # @param string [String] a literal string token matcher
- # @param name [String] the token name (what it is known as in the grammar)
- # @param options [Hash] see {#set_options}
- # @overload initialize(regex)
- # @param regex [Regexp] a regular expression token text matcher
- # @param name [String] the token name (what it is known as in the grammar)
- # @param options [Hash] see {#set_options}
- #
- def initialize(string_or_regex, name, options = {})
- if string_or_regex.is_a?(String)
- @name, @string = name, string_or_regex
- @regex = Regexp.new(Regexp.escape(string_or_regex))
- else
- @name, @regex = name, string_or_regex
- end
-
- set_options(options)
- @acceptable_when = ALWAYS_ACCEPTABLE
- end
-
- # @return [String] human readable token reference; the String if literal, else the token name
- def to_s
- string or @name.to_s
- end
-
- # @return [Boolean] if the token is acceptable in the given context or not.
- # @param context [Hash] the lexing context
- #
- def acceptable?(context={})
- @acceptable_when.call(context)
- end
-
-
- # Defines when the token is able to match.
- # This provides context that cannot be expressed otherwise, such as feature flags.
- #
- # @param block [Proc] a proc that given a context returns a boolean
- def acceptable_when(block)
- @acceptable_when = block
- end
- end
-
- # Maintains a list of tokens.
- class TokenList
- extend Forwardable
-
- attr_reader :regex_tokens, :string_tokens
- def_delegator :@tokens, :[]
- # Adds a new token to the set of recognized tokens
- # @param name [String] the token name
- # @param regex [Regexp, String] source text token matcher, a litral string or regular expression
- # @param options [Hash] see {Token::set_options}
- # @param block [Proc] optional block set as the created tokens `convert` method
- # @raise [ArgumentError] if the token with the given name is already defined
- #
- def add_token(name, regex, options = {}, &block)
- raise(ArgumentError, "Token #{name} already exists") if @tokens.include?(name)
- token = Token.new(regex, name, options)
- @tokens[token.name] = token
- if token.string
- @string_tokens << token
- @tokens_by_string[token.string] = token
- else
- @regex_tokens << token
- end
-
- token.meta_def(:convert, &block) if block_given?
-
- token
- end
-
- # Creates an empty token list
- #
- def initialize
- @tokens = {}
- @regex_tokens = []
- @string_tokens = []
- @tokens_by_string = {}
- end
-
- # Look up a token by its literal (match) value, rather than name.
- # @param string [String, nil] the literal match string to obtain a {Token} for, or nil if it does not exist.
- def lookup(string)
- @tokens_by_string[string]
- end
-
- # Adds tokens from a hash where key is a matcher (literal string or regexp) and the
- # value is the token's name
- # @param hash [Hash<{String => Symbol}, Hash<{Regexp => Symbol}] map token text matcher to token name
- # @return [void]
- #
- def add_tokens(hash)
- hash.each do |regex, name|
- add_token(name, regex)
- end
- end
-
- # Sort literal (string-) tokens by length, so we know once we match, we're done.
- # This helps avoid the O(n^2) nature of token matching.
- # The tokens are sorted in place.
- # @return [void]
- def sort_tokens
- @string_tokens.sort! { |a, b| b.string.length <=> a.string.length }
- end
-
- # Yield each token name and value in turn.
- def each
- @tokens.each {|name, value| yield name, value }
- end
- end
-
- TOKENS = TokenList.new
- TOKENS.add_tokens(
- '[' => :LBRACK,
- ']' => :RBRACK,
- # '{' => :LBRACE, # Specialized to handle lambda and brace count
- # '}' => :RBRACE, # Specialized to handle brace count
- '(' => :LPAREN,
- ')' => :RPAREN,
- '=' => :EQUALS,
- '+=' => :APPENDS,
- '-=' => :DELETES,
- '==' => :ISEQUAL,
- '>=' => :GREATEREQUAL,
- '>' => :GREATERTHAN,
- '<' => :LESSTHAN,
- '<=' => :LESSEQUAL,
- '!=' => :NOTEQUAL,
- '!' => :NOT,
- ',' => :COMMA,
- '.' => :DOT,
- ':' => :COLON,
- '@' => :AT,
- '|' => :PIPE,
- '<<|' => :LLCOLLECT,
- '|>>' => :RRCOLLECT,
- '->' => :IN_EDGE,
- '<-' => :OUT_EDGE,
- '~>' => :IN_EDGE_SUB,
- '<~' => :OUT_EDGE_SUB,
- '<|' => :LCOLLECT,
- '|>' => :RCOLLECT,
- ';' => :SEMIC,
- '?' => :QMARK,
- '\\' => :BACKSLASH,
- '=>' => :FARROW,
- '+>' => :PARROW,
- '+' => :PLUS,
- '-' => :MINUS,
- '/' => :DIV,
- '*' => :TIMES,
- '%' => :MODULO,
- '<<' => :LSHIFT,
- '>>' => :RSHIFT,
- '=~' => :MATCH,
- '!~' => :NOMATCH,
- %r{((::){0,1}[A-Z][-\w]*)+} => :CLASSREF,
- "<string>" => :STRING,
- "<dqstring up to first interpolation>" => :DQPRE,
- "<dqstring between two interpolations>" => :DQMID,
- "<dqstring after final interpolation>" => :DQPOST,
- "<boolean>" => :BOOLEAN,
- "<select start>" => :SELBRACE # A QMARK followed by '{'
- )
-
- module Contextual
- QUOTE_TOKENS = [:DQPRE,:DQMID]
- REGEX_INTRODUCING_TOKENS = [:NODE,:LBRACE, :SELBRACE, :RBRACE,:MATCH,:NOMATCH,:COMMA]
-
- NOT_INSIDE_QUOTES = Proc.new do |context|
- !QUOTE_TOKENS.include? context[:after]
- end
-
- INSIDE_QUOTES = Proc.new do |context|
- QUOTE_TOKENS.include? context[:after]
- end
-
- IN_REGEX_POSITION = Proc.new do |context|
- REGEX_INTRODUCING_TOKENS.include? context[:after]
- end
-
-# DASHED_VARIABLES_ALLOWED = Proc.new do |context|
-# Puppet[:allow_variables_with_dashes]
-# end
-#
-# VARIABLE_AND_DASHES_ALLOWED = Proc.new do |context|
-# Contextual::DASHED_VARIABLES_ALLOWED.call(context) and TOKENS[:VARIABLE].acceptable?(context)
-# end
- end
-
- # Numbers are treated separately from names, so that they may contain dots.
- TOKENS.add_token :NUMBER, %r{\b(?:0[xX][0-9A-Fa-f]+|0?\d+(?:\.\d+)?(?:[eE]-?\d+)?)\b} do |lexer, value|
- lexer.assert_numeric(value)
- [TOKENS[:NAME], value]
- end
- TOKENS[:NUMBER].acceptable_when Contextual::NOT_INSIDE_QUOTES
-
- TOKENS.add_token :NAME, %r{((::)?[a-z0-9][-\w]*)(::[a-z0-9][-\w]*)*} do |lexer, value|
- # A name starting with a number must be a valid numeric string (not that
- # NUMBER token captures those names that do not comply with the name rule.
- if value =~ /^[0-9].*$/
- lexer.assert_numeric(value)
- end
-
- string_token = self
- # we're looking for keywords here
- if tmp = KEYWORDS.lookup(value)
- string_token = tmp
- if [:TRUE, :FALSE].include?(string_token.name)
- value = eval(value)
- string_token = TOKENS[:BOOLEAN]
- end
- end
- [string_token, value]
- end
- [:NAME, :CLASSREF].each do |name_token|
- TOKENS[name_token].acceptable_when Contextual::NOT_INSIDE_QUOTES
- end
-
- TOKENS.add_token :COMMENT, %r{#.*}, :skip => true do |lexer,value|
-# value.sub!(/# ?/,'')
- [self, ""]
- end
-
- TOKENS.add_token :MLCOMMENT, %r{/\*(.*?)\*/}m, :skip => true do |lexer, value|
-# value.sub!(/^\/\* ?/,'')
-# value.sub!(/ ?\*\/$/,'')
- [self, ""]
- end
-
- TOKENS.add_token :REGEX, %r{/[^/\n]*/} do |lexer, value|
- # Make sure we haven't matched an escaped /
- while value[-2..-2] == '\\'
- other = lexer.scan_until(%r{/})
- value += other
- end
- regex = value.sub(%r{\A/}, "").sub(%r{/\Z}, '').gsub("\\/", "/")
- [self, Regexp.new(regex)]
- end
- TOKENS[:REGEX].acceptable_when Contextual::IN_REGEX_POSITION
-
- TOKENS.add_token :RETURN, "\n", :skip => true, :skip_text => true
-
- TOKENS.add_token :SQUOTE, "'" do |lexer, value|
- [TOKENS[:STRING], lexer.slurpstring(value,["'"],:ignore_invalid_escapes).first ]
- end
-
- DQ_initial_token_types = {'$' => :DQPRE,'"' => :STRING}
- DQ_continuation_token_types = {'$' => :DQMID,'"' => :DQPOST}
-
- TOKENS.add_token :DQUOTE, /"/ do |lexer, value|
- lexer.tokenize_interpolated_string(DQ_initial_token_types)
- end
-
-
- # LBRACE needs look ahead to differentiate between '{' and a '{'
- # followed by a '|' (start of lambda) The racc grammar can only do one
- # token lookahead.
- #
- TOKENS.add_token :LBRACE, "{" do |lexer, value|
- lexer.lexing_context[:brace_count] += 1
- if lexer.lexing_context[:after] == :QMARK
- [TOKENS[:SELBRACE], value]
- else
- [TOKENS[:LBRACE], value]
- end
- end
-
- # RBRACE needs to differentiate between a regular brace that is part of
- # syntax and one that is the ending of a string interpolation.
- TOKENS.add_token :RBRACE, "}" do |lexer, value|
- context = lexer.lexing_context
- if context[:interpolation_stack].empty? || context[:brace_count] != context[:interpolation_stack][-1]
- context[:brace_count] -= 1
- [TOKENS[:RBRACE], value]
- else
- lexer.tokenize_interpolated_string(DQ_continuation_token_types)
- end
- end
-
- TOKENS.add_token :DOLLAR_VAR, %r{\$(::)?(\w+::)*\w+} do |lexer, value|
- [TOKENS[:VARIABLE],value[1..-1]]
- end
-
- TOKENS.add_token :VARIABLE, %r{(::)?(\w+::)*\w+} do |lexer, value|
- # If the varname (following $, or ${ is followed by (, it is a function call, and not a variable
- # reference.
- #
- if lexer.match?(%r{[ \t\r]*\(})
- # followed by ( is a function call
- [TOKENS[:NAME], value]
-
- elsif kwd_token = KEYWORDS.lookup(value)
- # true, false, if, unless, case, and undef are keywords that cannot be used as variables
- # but node, and several others are variables
- if [ :TRUE, :FALSE ].include?(kwd_token.name)
- [ TOKENS[:BOOLEAN], eval(value) ]
- elsif [ :IF, :UNLESS, :CASE, :UNDEF ].include?(kwd_token.name)
- [kwd_token, value]
- else
- [TOKENS[:VARIABLE], value]
- end
- else
- [TOKENS[:VARIABLE], value]
- end
-
- end
- TOKENS[:VARIABLE].acceptable_when Contextual::INSIDE_QUOTES
-
- TOKENS.sort_tokens
-
- @@pairs = {
- "{" => "}",
- "(" => ")",
- "[" => "]",
- "<|" => "|>",
- "<<|" => "|>>",
- "|" => "|"
- }
-
- KEYWORDS = TokenList.new
- KEYWORDS.add_tokens(
- "case" => :CASE,
- "class" => :CLASS,
- "default" => :DEFAULT,
- "define" => :DEFINE,
- # "import" => :IMPORT,
- "if" => :IF,
- "elsif" => :ELSIF,
- "else" => :ELSE,
- "inherits" => :INHERITS,
- "node" => :NODE,
- "and" => :AND,
- "or" => :OR,
- "undef" => :UNDEF,
- "false" => :FALSE,
- "true" => :TRUE,
- "in" => :IN,
- "unless" => :UNLESS
- )
-
- def clear
- initvars
- end
-
- def expected
- return nil if @expected.empty?
- name = @expected[-1]
- TOKENS.lookup(name) or lex_error "Internal Lexer Error: Could not find expected token #{name}"
- end
-
- # scan the whole file
- # basically just used for testing
- def fullscan
- array = []
-
- self.scan { |token, str|
- # Ignore any definition nesting problems
- @indefine = false
- array.push([token,str])
- }
- array
- end
-
- def file=(file)
- @file = file
- contents = Puppet::FileSystem.exist?(file) ? Puppet::FileSystem.read(file) : ""
- @scanner = StringScanner.new(contents.freeze)
- @locator = Puppet::Pops::Parser::Locator.locator(contents, file)
- end
-
- def_delegator :@token_queue, :shift, :shift_token
-
- def find_string_token
- # We know our longest string token is three chars, so try each size in turn
- # until we either match or run out of chars. This way our worst-case is three
- # tries, where it is otherwise the number of string token we have. Also,
- # the lookups are optimized hash lookups, instead of regex scans.
- #
- _scn = @scanner
- s = _scn.peek(3)
- token = TOKENS.lookup(s[0,3]) || TOKENS.lookup(s[0,2]) || TOKENS.lookup(s[0,1])
- unless token
- return [nil, nil]
- end
- [ token, _scn.scan(token.regex) ]
- end
-
- # Find the next token that matches a regex. We look for these first.
- def find_regex_token
- best_token = nil
- best_length = 0
-
- # I tried optimizing based on the first char, but it had
- # a slightly negative affect and was a good bit more complicated.
- _lxc = @lexing_context
- _scn = @scanner
- TOKENS.regex_tokens.each do |token|
- if length = _scn.match?(token.regex) and token.acceptable?(_lxc)
- # We've found a longer match
- if length > best_length
- best_length = length
- best_token = token
- end
- end
- end
-
- return best_token, _scn.scan(best_token.regex) if best_token
- end
-
- # Find the next token, returning the string and the token.
- def find_token
- shift_token || find_regex_token || find_string_token
- end
-
- MULTIBYTE = Puppet::Pops::Parser::Locator::MULTIBYTE
- SKIPPATTERN = MULTIBYTE ? %r{[[:blank:]\r]+} : %r{[ \t\r]+}
-
- def initialize
- initvars
- end
-
- def assert_numeric(value)
- if value =~ /^0[xX].*$/
- lex_error (positioned_message("Not a valid hex number #{value}")) unless value =~ /^0[xX][0-9A-Fa-f]+$/
- elsif value =~ /^0[^.].*$/
- lex_error(positioned_message("Not a valid octal number #{value}")) unless value =~ /^0[0-7]+$/
- else
- lex_error(positioned_message("Not a valid decimal number #{value}")) unless value =~ /0?\d+(?:\.\d+)?(?:[eE]-?\d+)?/
- end
- end
-
- def initvars
- @previous_token = nil
- @scanner = nil
- @file = nil
-
- # AAARRGGGG! okay, regexes in ruby are bloody annoying
- # no one else has "\n" =~ /\s/
-
- @namestack = []
- @token_queue = []
- @indefine = false
- @expected = []
- @lexing_context = {
- :after => nil,
- :start_of_line => true,
- :offset => 0, # byte offset before where token starts
- :end_offset => 0, # byte offset after scanned token
- :brace_count => 0, # nested depth of braces
- :interpolation_stack => [] # matching interpolation brace level
- }
- end
-
- # Make any necessary changes to the token and/or value.
- def munge_token(token, value)
- # A token may already have been munged (converted and positioned)
- #
- return token, value if value.is_a? Hash
-
- @scanner.skip(SKIPPATTERN) if token.skip_text
-
- return if token.skip
-
- token, value = token.convert(self, value) if token.respond_to?(:convert)
-
- return unless token
-
- return if token.skip
-
- # If the conversion performed the munging/positioning
- return token, value if value.is_a? Hash
-
- return token, positioned_value(value)
- end
-
- # Returns a hash with the current position in source based on the current lexing context
- #
- def positioned_value(value)
- {
- :value => value,
- :locator => @locator,
- :offset => @lexing_context[:offset],
- :end_offset => @lexing_context[:end_offset]
- }
- end
-
- def pos
- @locator.pos_on_line(@lexing_context[:offset])
- end
-
- # Handling the namespace stack
- def_delegator :@namestack, :pop, :namepop
-
- # This value might have :: in it, but we don't care -- it'll be handled
- # normally when joining, and when popping we want to pop this full value,
- # however long the namespace is.
- def_delegator :@namestack, :<<, :namestack
-
- # Collect the current namespace.
- def namespace
- @namestack.join("::")
- end
-
- def_delegator :@scanner, :rest
-
- LBRACE_CHAR = '{'
-
- # this is the heart of the lexer
- def scan
- _scn = @scanner
- #Puppet.debug("entering scan")
- lex_error "Internal Error: No string or file given to lexer to process." unless _scn
-
- # Skip any initial whitespace.
- _scn.skip(SKIPPATTERN)
- _lbrace = '{'.freeze # faster to compare against a frozen string in
-
- until token_queue.empty? and _scn.eos? do
- offset = _scn.pos
- matched_token, value = find_token
- end_offset = _scn.pos
-
- # error out if we didn't match anything at all
- lex_error "Could not match #{_scn.rest[/^(\S+|\s+|.*)/]}" unless matched_token
-
- newline = matched_token.name == :RETURN
-
- _lxc = @lexing_context
- _lxc[:start_of_line] = newline
- _lxc[:offset] = offset
- _lxc[:end_offset] = end_offset
-
- final_token, token_value = munge_token(matched_token, value)
- # update end position since munging may have moved the end offset
- _lxc[:end_offset] = _scn.pos
-
- unless final_token
- _scn.skip(SKIPPATTERN)
- next
- end
-
- _lxc[:after] = final_token.name unless newline
- if final_token.name == :DQPRE
- _lxc[:interpolation_stack] << _lxc[:brace_count]
- elsif final_token.name == :DQPOST
- _lxc[:interpolation_stack].pop
- end
-
- value = token_value[:value]
-
- _expected = @expected
- if match = @@pairs[value] and final_token.name != :DQUOTE and final_token.name != :SQUOTE
- _expected << match
- elsif exp = _expected[-1] and exp == value and final_token.name != :DQUOTE and final_token.name != :SQUOTE
- _expected.pop
- end
-
- yield [final_token.name, token_value]
-
- _prv = @previous_token
- if _prv
- namestack(value) if _prv.name == :CLASS and value != LBRACE_CHAR
-
- # TODO: Lexer has no business dealing with this - it is semantic
- if _prv.name == :DEFINE
- if indefine?
- msg = "Cannot nest definition #{value} inside #{@indefine}"
- self.indefine = false
- raise Puppet::ParseError, msg
- end
-
- @indefine = value
- end
- end
- @previous_token = final_token
- _scn.skip(SKIPPATTERN)
- end
- # Cannot reset @scanner to nil here - it is needed to answer questions about context after
- # completed parsing.
- # Seems meaningless to do this. Everything will be gc anyway.
- #@scanner = nil
-
- # This indicates that we're done parsing.
- yield [false,false]
- end
-
- def match? r
- @scanner.match?(r)
- end
-
- # Provide some limited access to the scanner, for those
- # tokens that need it.
- def_delegator :@scanner, :scan_until
-
- # we've encountered the start of a string...
- # slurp in the rest of the string and return it
- def slurpstring(terminators,escapes=%w{ \\ $ ' " r n t s }+["\n"],ignore_invalid_escapes=false)
- # we search for the next quote that isn't preceded by a
- # backslash; the caret is there to match empty strings
- last = @scanner.matched
- str = @scanner.scan_until(/([^\\]|^|[^\\])([\\]{2})*[#{terminators}]/) || lex_error(positioned_message("Unclosed quote after #{format_quote(last)} followed by '#{followed_by}'"))
- str.gsub!(/\\(.)/m) {
- ch = $1
- if escapes.include? ch
- case ch
- when 'r'; "\r"
- when 'n'; "\n"
- when 't'; "\t"
- when 's'; " "
- when "\n"; ''
- else ch
- end
- else
- Puppet.warning(positioned_message("Unrecognized escape sequence '\\#{ch}'")) unless ignore_invalid_escapes
- "\\#{ch}"
- end
- }
- [ str[0..-2],str[-1,1] ]
- end
-
- # Formats given message by appending file, line and position if available.
- def positioned_message msg
- result = [msg]
- result << "in file #{file}" if file
- result << "at line #{line}:#{pos}" if line
- result.join(" ")
- end
-
- # Returns "<eof>" if at end of input, else the following 5 characters with \n \r \t escaped
- def followed_by
- return "<eof>" if @scanner.eos?
- result = @scanner.rest[0,5] + "..."
- result.gsub!("\t", '\t')
- result.gsub!("\n", '\n')
- result.gsub!("\r", '\r')
- result
- end
-
- def format_quote q
- if q == "'"
- '"\'"'
- else
- "'#{q}'"
- end
- end
-
- def tokenize_interpolated_string(token_type,preamble='')
- # Expecting a (possibly empty) stretch of text terminated by end of string ", a variable $, or expression ${
- # The length of this part includes the start and terminating characters.
- value,terminator = slurpstring('"$')
-
- # Advanced after '{' if this is in expression ${} interpolation
- braced = terminator == '$' && @scanner.scan(/\{/)
- # make offset to end_ofset be the length of the pre expression string including its start and terminating chars
- lxc = @lexing_context
- lxc[:end_offset] = @scanner.pos
-
- token_queue << [TOKENS[token_type[terminator]],positioned_value(preamble+value)]
- variable_regex = if Puppet[:allow_variables_with_dashes]
- TOKENS[:VARIABLE_WITH_DASH].regex
- else
- TOKENS[:VARIABLE].regex
- end
- if terminator != '$' or braced
- return token_queue.shift
- end
-
- tmp_offset = @scanner.pos
- if var_name = @scanner.scan(variable_regex)
- lxc[:offset] = tmp_offset
- lxc[:end_offset] = @scanner.pos
- warn_if_variable_has_hyphen(var_name)
- # If the varname after ${ is followed by (, it is a function call, and not a variable
- # reference.
- #
- if braced && @scanner.match?(%r{[ \t\r]*\(})
- token_queue << [TOKENS[:NAME], positioned_value(var_name)]
- else
- token_queue << [TOKENS[:VARIABLE],positioned_value(var_name)]
- end
- lxc[:offset] = @scanner.pos
- tokenize_interpolated_string(DQ_continuation_token_types)
- else
- tokenize_interpolated_string(token_type, replace_false_start_with_text(terminator))
- end
- end
-
- def replace_false_start_with_text(appendix)
- last_token = token_queue.pop
- value = last_token.last
- if value.is_a? Hash
- value[:value] + appendix
- else
- value + appendix
- end
- end
-
- # just parse a string, not a whole file
- def string=(string, path='')
- @scanner = StringScanner.new(string.freeze)
- @locator = Puppet::Pops::Parser::Locator.locator(string, path)
- end
-
- def warn_if_variable_has_hyphen(var_name)
- if var_name.include?('-')
- Puppet.deprecation_warning("Using `-` in variable names is deprecated at #{file || '<string>'}:#{line}. See http://links.puppetlabs.com/puppet-hyphenated-variable-deprecation")
- end
- end
-
- # Returns the line number (starting from 1) for the current position
- # in the scanned text (at the end of the last produced, but not necessarily
- # consumed.
- #
- def line
- return 1 unless @lexing_context && locator
- locator.line_for_offset(@lexing_context[:end_offset])
- end
-end
diff --git a/lib/puppet/pops/parser/lexer2.rb b/lib/puppet/pops/parser/lexer2.rb
index aee0f6678..fda745f98 100644
--- a/lib/puppet/pops/parser/lexer2.rb
+++ b/lib/puppet/pops/parser/lexer2.rb
@@ -128,6 +128,9 @@ class Puppet::Pops::Parser::Lexer2
"in" => [:IN, 'in', 2],
"unless" => [:UNLESS, 'unless', 6],
"function" => [:FUNCTION, 'function', 8],
+ "type" => [:TYPE, 'type', 4],
+ "attr" => [:ATTR, 'attr', 4],
+ "private" => [:PRIVATE, 'private', 7],
}
KEYWORDS.each {|k,v| v[1].freeze; v.freeze }
KEYWORDS.freeze
@@ -278,7 +281,8 @@ class Puppet::Pops::Parser::Lexer2
# This is the lexer's main loop
until queue.empty? && scn.eos? do
if token = queue.shift || lex_token
- yield [ ctx[:after] = token[0], token[1] ]
+ ctx[:after] = token[0]
+ yield token
end
end
@@ -325,7 +329,7 @@ class Puppet::Pops::Parser::Lexer2
emit(TOKEN_COMMA, before)
when '['
- if ctx[:after] == :NAME && (before == 0 || scn.string[before-1,1] =~ /[[:blank:]\r\n]+/)
+ if (before == 0 || scn.string[before-1,1] =~ /[[:blank:]\r\n]+/)
emit(TOKEN_LISTSTART, before)
else
emit(TOKEN_LBRACK, before)
@@ -525,7 +529,7 @@ class Puppet::Pops::Parser::Lexer2
value = scn.scan(PATTERN_CLASSREF)
if value
after = scn.pos
- emit_completed([:CLASSREF, value, after-before], before)
+ emit_completed([:CLASSREF, value.freeze, after-before], before)
else
# move to faulty position ('::<uc-letter>' was ok)
scn.pos = scn.pos + 3
@@ -535,7 +539,7 @@ class Puppet::Pops::Parser::Lexer2
# NAME or error
value = scn.scan(PATTERN_NAME)
if value
- emit_completed([:NAME, value, scn.pos-before], before)
+ emit_completed([:NAME, value.freeze, scn.pos-before], before)
else
# move to faulty position ('::' was ok)
scn.pos = scn.pos + 2
@@ -548,7 +552,7 @@ class Puppet::Pops::Parser::Lexer2
when '$'
if value = scn.scan(PATTERN_DOLLAR_VAR)
- emit_completed([:VARIABLE, value[1..-1], scn.pos - before], before)
+ emit_completed([:VARIABLE, value[1..-1].freeze, scn.pos - before], before)
else
# consume the $ and let higher layer complain about the error instead of getting a syntax error
emit(TOKEN_VARIABLE_EMPTY, before)
@@ -560,14 +564,14 @@ class Puppet::Pops::Parser::Lexer2
interpolate_dq
when "'"
- emit_completed([:STRING, slurp_sqstring, before-scn.pos], before)
+ emit_completed([:STRING, slurp_sqstring.freeze, scn.pos - before], before)
when '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'
value = scn.scan(PATTERN_NUMBER)
if value
length = scn.pos - before
assert_numeric(value, length)
- emit_completed([:NUMBER, value, length], before)
+ emit_completed([:NUMBER, value.freeze, length], before)
else
# move to faulty position ([0-9] was ok)
scn.pos = scn.pos + 1
@@ -579,14 +583,14 @@ class Puppet::Pops::Parser::Lexer2
value = scn.scan(PATTERN_NAME)
# NAME or false start because followed by hyphen(s), underscore or word
if value && !scn.match?(/^-+\w/)
- emit_completed(KEYWORDS[value] || [:NAME, value, scn.pos - before], before)
+ emit_completed(KEYWORDS[value] || [:NAME, value.freeze, scn.pos - before], before)
else
# Restart and check entire pattern (for ease of detecting non allowed trailing hyphen)
scn.pos = before
value = scn.scan(PATTERN_BARE_WORD)
# If the WORD continues with :: it must be a correct fully qualified name
if value && !(fully_qualified = scn.match?(/::/))
- emit_completed([:WORD, value, scn.pos - before], before)
+ emit_completed([:WORD, value.freeze, scn.pos - before], before)
else
# move to faulty position ([a-z_] was ok)
scn.pos = scn.pos + 1
@@ -602,7 +606,7 @@ class Puppet::Pops::Parser::Lexer2
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'
value = scn.scan(PATTERN_CLASSREF)
if value
- emit_completed([:CLASSREF, value, scn.pos - before], before)
+ emit_completed([:CLASSREF, value.freeze, scn.pos - before], before)
else
# move to faulty position ([A-Z] was ok)
scn.pos = scn.pos + 1
diff --git a/lib/puppet/pops/parser/lexer_support.rb b/lib/puppet/pops/parser/lexer_support.rb
index c769255a5..5b296e49c 100644
--- a/lib/puppet/pops/parser/lexer_support.rb
+++ b/lib/puppet/pops/parser/lexer_support.rb
@@ -99,6 +99,12 @@ module Puppet::Pops::Parser::LexerSupport
end
end
+ def to_s
+ # This format is very compact and is intended for debugging output from racc parsser in
+ # debug mode. If this is made more elaborate the output from a debug run becomes very hard to read.
+ #
+ "'#{self[:value]} #{@token_array[0]}'"
+ end
# TODO: Make this comparable for testing
# vs symbolic, vs array with symbol and non hash, array with symbol and hash)
#
diff --git a/lib/puppet/pops/parser/locator.rb b/lib/puppet/pops/parser/locator.rb
index 526126aca..c46c38ee9 100644
--- a/lib/puppet/pops/parser/locator.rb
+++ b/lib/puppet/pops/parser/locator.rb
@@ -61,7 +61,7 @@ class Puppet::Pops::Parser::Locator
def char_offset(byte_offset)
end
- # Returns the length measured in number of characters from the given start and end reported offseta
+ # Returns the length measured in number of characters from the given start and end reported offset
def char_length(offset, end_offset)
end
diff --git a/lib/puppet/pops/parser/makefile b/lib/puppet/pops/parser/makefile
deleted file mode 100644
index 802382dd8..000000000
--- a/lib/puppet/pops/parser/makefile
+++ /dev/null
@@ -1,6 +0,0 @@
-
-eparser.rb: egrammar.ra
- racc -o$@ egrammar.ra
-
-egrammar.output: egrammar.ra
- racc -v -o$@ egrammar.ra
diff --git a/lib/puppet/pops/parser/parser_support.rb b/lib/puppet/pops/parser/parser_support.rb
index a351048d3..cb1c83aa2 100644
--- a/lib/puppet/pops/parser/parser_support.rb
+++ b/lib/puppet/pops/parser/parser_support.rb
@@ -43,21 +43,15 @@ class Puppet::Pops::Parser::Parser
# before evaluation-time.
#
def classname(name)
- [namespace, name].join("::").sub(/^::/, '')
+ [namespace, name].join('::').sub(/^::/, '')
end
-# # Reinitializes variables (i.e. creates a new lexer instance
-# #
-# def clear
-# initvars
-# end
-
# Raises a Parse error.
def error(value, message, options = {})
except = Puppet::ParseError.new(message)
except.line = options[:line] || value[:line]
- except.file = options[:file] || value[:file] # @lexer.file
- except.pos = options[:pos] || value[:pos] # @lexer.pos
+ except.file = options[:file] || value[:file]
+ except.pos = options[:pos] || value[:pos]
raise except
end
@@ -74,24 +68,12 @@ class Puppet::Pops::Parser::Parser
end
def initialize()
- # Since the parser is not responsible for importing (removed), and does not perform linking,
- # and there is no syntax that requires knowing if something referenced exists, it is safe
- # to assume that no environment is needed when parsing. (All that comes later).
- #
@lexer = Puppet::Pops::Parser::Lexer2.new
@namestack = []
@definitions = []
end
-# # Initializes the parser support by creating a new instance of {Puppet::Pops::Parser::Lexer}
-# # @return [void]
-# #
-# def initvars
-# end
-
- # This is a callback from the generated grammar (when an error occurs while parsing)
- # TODO Picks up origin information from the lexer, probably needs this from the caller instead
- # (for code strings, and when start line is not line 1 in a code string (or file), etc.)
+ # This is a callback from the generated parser (when an error occurs while parsing)
#
def on_error(token,value,stack)
if token == 0 # denotes end of file
@@ -99,8 +81,13 @@ class Puppet::Pops::Parser::Parser
else
value_at = "'#{value[:value]}'"
end
- error = "Syntax error at #{value_at}"
+ if @yydebug
+ error = "Syntax error at #{value_at}, token: #{token}"
+ else
+ error = "Syntax error at #{value_at}"
+ end
+ # Note, old parser had processing of "expected token here" - do not try to reinstate:
# The 'expected' is only of value at end of input, otherwise any parse error involving a
# start of a pair will be reported as expecting the close of the pair - e.g. "$x.each |$x {|", would
# report that "seeing the '{', the '}' is expected. That would be wrong.
@@ -111,9 +98,6 @@ class Puppet::Pops::Parser::Parser
# must be handled by the grammar. The lexer may have enqueued tokens far ahead - the lexer's opinion about this
# is not trustworthy.
#
-# if token == 0 && brace = @lexer.expected
-# error += "; expected '#{brace}'"
-# end
except = Puppet::ParseError.new(error)
if token != 0
@@ -138,7 +122,6 @@ class Puppet::Pops::Parser::Parser
end
# Mark the factory wrapped model object with location information
- # @todo the lexer produces :line for token, but no offset or length
# @return [Puppet::Pops::Model::Factory] the given factory
# @api private
#
@@ -146,15 +129,12 @@ class Puppet::Pops::Parser::Parser
factory.record_position(start_locateable, end_locateable)
end
- def heredoc_loc(factory, start_locateabke, end_locateable = nil)
- factory.record_heredoc_position(start_locatable, end_locatable)
- end
-
- # Associate documentation with the factory wrapped model object.
+ # Mark the factory wrapped heredoc model object with location information
# @return [Puppet::Pops::Model::Factory] the given factory
# @api private
- def doc factory, doc_string
- factory.doc = doc_string
+ #
+ def heredoc_loc(factory, start_locateabke, end_locateable = nil)
+ factory.record_heredoc_position(start_locatable, end_locatable)
end
def aryfy(o)
@@ -191,35 +171,37 @@ class Puppet::Pops::Parser::Parser
Factory.transform_resource_wo_title(left, resource)
end
- # If there are definitions that require initialization a Program is produced, else the body
+ # Creates a program with the given body.
+ #
def create_program(body)
locator = @lexer.locator
Factory.PROGRAM(body, definitions, locator)
end
+ # Creates an empty program with a single No-op at the input's EOF offset with 0 length.
+ #
+ def create_empty_program()
+ locator = @lexer.locator
+ no_op = Factory.literal(nil)
+ # Create a synthetic NOOP token at EOF offset with 0 size. The lexer does not produce an EOF token that is
+ # visible to the grammar rules. Creating this token is mainly to reuse the positioning logic as it
+ # expects a token decorated with location information.
+ token_sym, token = @lexer.emit_completed([:NOOP,'',0], locator.string.bytesize)
+ loc(no_op, token)
+ # Program with a Noop
+ program = Factory.PROGRAM(no_op, [], locator)
+ program
+ end
+
# Performs the parsing and returns the resulting model.
# The lexer holds state, and this is setup with {#parse_string}, or {#parse_file}.
#
- # TODO: Drop support for parsing a ruby file this way (should be done where it is decided
- # which file to load/run (i.e. loaders), and initial file to run
- # TODO: deal with options containing origin (i.e. parsing a string from externally known location).
- # TODO: should return the model, not a Hostclass
- #
# @api private
#
def _parse()
begin
@yydebug = false
main = yyparse(@lexer,:scan)
- # #Commented out now because this hides problems in the racc grammar while developing
- # # TODO include this when test coverage is good enough.
- # rescue Puppet::ParseError => except
- # except.line ||= @lexer.line
- # except.file ||= @lexer.file
- # except.pos ||= @lexer.pos
- # raise except
- # rescue => except
- # raise Puppet::ParseError.new(except.message, @lexer.file, @lexer.line, @lexer.pos, except)
end
return main
ensure
diff --git a/lib/puppet/pops/patterns.rb b/lib/puppet/pops/patterns.rb
index a2534774d..aa46d9d06 100644
--- a/lib/puppet/pops/patterns.rb
+++ b/lib/puppet/pops/patterns.rb
@@ -1,16 +1,18 @@
# The Patterns module contains common regular expression patters for the Puppet DSL language
module Puppet::Pops::Patterns
- # NUMERIC matches hex, octal, decimal, and floating point and captures three parts
- # 0 = entire matched number, leading and trailing whitespace included
- # 1 = hexadecimal number
- # 2 = non hex integer portion, possibly with leading 0 (octal)
- # 3 = floating point part, starts with ".", decimals and optional exponent
+ # NUMERIC matches hex, octal, decimal, and floating point and captures several parts
+ # 0 = entire matched number, leading and trailing whitespace and sign included
+ # 1 = sign, +, - or nothing
+ # 2 = entire numeric part
+ # 3 = hexadecimal number
+ # 4 = non hex integer portion, possibly with leading 0 (octal)
+ # 5 = floating point part, starts with ".", decimals and optional exponent
#
- # Thus, a hex number has group 1 value, an octal value has group 2 (if it starts with 0), and no group 3
- # and a floating point value has group 2 and group 3.
+ # Thus, a hex number has group 3 value, an octal value has group 4 (if it starts with 0), and no group 3
+ # and a floating point value has group 4 and group 5.
#
- NUMERIC = %r{^\s*(?:(0[xX][0-9A-Fa-f]+)|(0?\d+)((?:\.\d+)?(?:[eE]-?\d+)?))\s*$}
+ NUMERIC = %r{\A[[:blank:]]*([-+]?)[[:blank:]]*((0[xX][0-9A-Fa-f]+)|(0?\d+)((?:\.\d+)?(?:[eE]-?\d+)?))[[:blank:]]*\z}
# ILLEGAL_P3_1_HOSTNAME matches if a hostname contains illegal characters.
# This check does not prevent pathological names like 'a....b', '.....', "---". etc.
@@ -21,13 +23,11 @@ module Puppet::Pops::Patterns
# CLASSREF_EXT matches a class reference the same way as the lexer - i.e. the external source form
# where each part must start with a capital letter A-Z.
- # This name includes hyphen, which may be illegal in some cases.
#
CLASSREF_EXT = %r{\A((::){0,1}[A-Z][\w]*)+\z}
# CLASSREF matches a class reference the way it is represented internally in the
# model (i.e. in lower case).
- # This name includes hyphen, which may be illegal in some cases.
#
CLASSREF = %r{\A((::){0,1}[a-z][\w]*)+\z}
diff --git a/lib/puppet/pops/semantic_error.rb b/lib/puppet/pops/semantic_error.rb
index 3cfa120ba..58026025f 100644
--- a/lib/puppet/pops/semantic_error.rb
+++ b/lib/puppet/pops/semantic_error.rb
@@ -7,7 +7,7 @@ class Puppet::Pops::SemanticError < RuntimeError
# @param issue [Puppet::Pops::Issues::Issue] the issue describing the severity and message
# @param semantic [Puppet::Pops::Model::Locatable, nil] the expression causing the failure, or nil if unknown
- # @param options [Hash] an options hash with Symbol to valu mapping - these are the arguments to the issue
+ # @param options [Hash] an options hash with Symbol to value mapping - these are the arguments to the issue
#
def initialize(issue, semantic=nil, options = {})
@issue = issue
diff --git a/lib/puppet/pops/types/class_loader.rb b/lib/puppet/pops/types/class_loader.rb
index 0cd1b8c2f..1011f4715 100644
--- a/lib/puppet/pops/types/class_loader.rb
+++ b/lib/puppet/pops/types/class_loader.rb
@@ -9,8 +9,8 @@ class Puppet::Pops::Types::ClassLoader
# Returns a Class given a fully qualified class name.
# Lookup of class is never relative to the calling namespace.
- # @param name [String, Array<String>, Array<Symbol>, Puppet::Pops::Types::PObjectType] A fully qualified
- # class name String (e.g. '::Foo::Bar', 'Foo::Bar'), a PObjectType, or a fully qualified name in Array form where each part
+ # @param name [String, Array<String>, Array<Symbol>, Puppet::Pops::Types::PAnyType] A fully qualified
+ # class name String (e.g. '::Foo::Bar', 'Foo::Bar'), a PAnyType, or a fully qualified name in Array form where each part
# is either a String or a Symbol, e.g. `%w{Puppetx Puppetlabs SomeExtension}`.
# @return [Class, nil] the looked up class or nil if no such class is loaded
# @raise ArgumentError If the given argument has the wrong type
@@ -24,7 +24,7 @@ class Puppet::Pops::Types::ClassLoader
when Array
provide_from_name_path(name.join('::'), name)
- when Puppet::Pops::Types::PObjectType, Puppet::Pops::Types::PType
+ when Puppet::Pops::Types::PAnyType, Puppet::Pops::Types::PType
provide_from_type(name)
else
@@ -36,27 +36,38 @@ class Puppet::Pops::Types::ClassLoader
def self.provide_from_type(type)
case type
- when Puppet::Pops::Types::PRubyType
- provide_from_string(type.ruby_class)
+ when Puppet::Pops::Types::PRuntimeType
+ raise ArgumentError.new("Only Runtime type 'ruby' is supported, got #{type.runtime}") unless type.runtime == :ruby
+ provide_from_string(type.runtime_type_name)
when Puppet::Pops::Types::PBooleanType
# There is no other thing to load except this Enum meta type
RGen::MetamodelBuilder::MMBase::Boolean
when Puppet::Pops::Types::PType
- # TODO: PType should have a type argument (a PObjectType)
+ # TODO: PType should has a type argument (a PAnyType) so the Class' class could be returned
+ # (but this only matters in special circumstances when meta programming has been used).
Class
+ when Puppet::Pops::Type::POptionalType
+ # cannot make a distinction between optional and its type
+ provide_from_type(type.optional_type)
+
# Although not expected to be the first choice for getting a concrete class for these
# types, these are of value if the calling logic just has a reference to type.
#
- when Puppet::Pops::Types::PArrayType ; Array
- when Puppet::Pops::Types::PHashType ; Hash
- when Puppet::Pops::Types::PRegexpType ; Regexp
- when Puppet::Pops::Types::PIntegerType ; Integer
- when Puppet::Pops::Types::PStringType ; String
- when Puppet::Pops::Types::PFloatType ; Float
- when Puppet::Pops::Types::PNilType ; NilClass
+ when Puppet::Pops::Types::PArrayType ; Array
+ when Puppet::Pops::Types::PTupleType ; Array
+ when Puppet::Pops::Types::PHashType ; Hash
+ when Puppet::Pops::Types::PStructType ; Hash
+ when Puppet::Pops::Types::PRegexpType ; Regexp
+ when Puppet::Pops::Types::PIntegerType ; Integer
+ when Puppet::Pops::Types::PStringType ; String
+ when Puppet::Pops::Types::PPatternType ; String
+ when Puppet::Pops::Types::PEnumType ; String
+ when Puppet::Pops::Types::PFloatType ; Float
+ when Puppet::Pops::Types::PNilType ; NilClass
+ when Puppet::Pops::Types::PCallableType ; Proc
else
nil
end
diff --git a/lib/puppet/pops/types/type_calculator.rb b/lib/puppet/pops/types/type_calculator.rb
index 5df01a82a..644d007cd 100644
--- a/lib/puppet/pops/types/type_calculator.rb
+++ b/lib/puppet/pops/types/type_calculator.rb
@@ -67,16 +67,20 @@
# type by looking at the Ruby class of the types this is considered an implementation detail, and such checks should in general
# be performed by the type_calculator which implements the type system semantics.
#
-# The PRubyType
+# The PRuntimeType
# -------------
-# The PRubyType corresponds to a Ruby Class, except for the puppet types that are specialized (i.e. PRubyType should not be
-# used for Integer, String, etc. since there are specialized types for those).
-# When the type calculator deals with PRubyTypes and checks for assignability, it determines the "common ancestor class" of two classes.
-# This check is made based on the superclasses of the two classes being compared. In order to perform this, the classes must be present
-# (i.e. they are resolved from the string form in the PRubyType to a loaded, instantiated Ruby Class). In general this is not a problem,
-# since the question to produce the common super type for two objects means that the classes must be present or there would have been
-# no instances present in the first place. If however the classes are not present, the type calculator will fall back and state that
-# the two types at least have Object in common.
+# The PRuntimeType corresponds to a type in the runtime system (currently only supported runtime is 'ruby'). The
+# type has a runtime_type_name that corresponds to a Ruby Class name.
+# A Runtime[ruby] type can be used to describe any ruby class except for the puppet types that are specialized
+# (i.e. PRuntimeType should not be used for Integer, String, etc. since there are specialized types for those).
+# When the type calculator deals with PRuntimeTypes and checks for assignability, it determines the
+# "common ancestor class" of two classes.
+# This check is made based on the superclasses of the two classes being compared. In order to perform this, the
+# classes must be present (i.e. they are resolved from the string form in the PRuntimeType to a
+# loaded, instantiated Ruby Class). In general this is not a problem, since the question to produce the common
+# super type for two objects means that the classes must be present or there would have been
+# no instances present in the first place. If however the classes are not present, the type
+# calculator will fall back and state that the two types at least have Any in common.
#
# @see Puppet::Pops::Types::TypeFactory TypeFactory for how to create instances of types
# @see Puppet::Pops::Types::TypeParser TypeParser how to construct a type instance from a String
@@ -86,7 +90,7 @@
# -----
# The type calculator can be directly used via its class methods. If doing time critical work and doing many
# calls to the type calculator, it is more performant to create an instance and invoke the corresponding
-# instance methods. Note that inference is an expensive operation, rather than infering the same thing
+# instance methods. Note that inference is an expensive operation, rather than inferring the same thing
# several times, it is in general better to infer once and then copy the result if mutation to a more generic form is
# required.
#
@@ -113,7 +117,7 @@ class Puppet::Pops::Types::TypeCalculator
end
# Produces a String representation of the given type.
- # @param t [Puppet::Pops::Types::PAbstractType] the type to produce a string form
+ # @param t [Puppet::Pops::Types::PAnyType] the type to produce a string form
# @return [String] the type in string form
#
# @api public
@@ -178,7 +182,7 @@ class Puppet::Pops::Types::TypeCalculator
@data_t = Types::PDataType.new()
@scalar_t = Types::PScalarType.new()
@numeric_t = Types::PNumericType.new()
- @t = Types::PObjectType.new()
+ @t = Types::PAnyType.new()
# Data accepts a Tuple that has 0-infinity Data compatible entries (e.g. a Tuple equivalent to Array).
data_tuple = Types::PTupleType.new()
@@ -233,18 +237,18 @@ class Puppet::Pops::Types::TypeCalculator
# A class is injectable if it has a special *assisted inject* class method called `inject` taking
# an injector and a scope as argument, or if it has a zero args `initialize` method.
#
- # @param klazz [Class, PRubyType] the class/type to check if it is injectable
+ # @param klazz [Class, PRuntimeType] the class/type to check if it is injectable
# @return [Class, nil] the injectable Class, or nil if not injectable
# @api public
#
def injectable_class(klazz)
# Handle case when we get a PType instead of a class
- if klazz.is_a?(Types::PRubyType)
+ if klazz.is_a?(Types::PRuntimeType)
klazz = Puppet::Pops::Types::ClassLoader.provide(klazz)
end
- # data types can not be injected (check again, it is not safe to assume that given RubyType klazz arg was ok)
- return false unless type(klazz).is_a?(Types::PRubyType)
+ # data types can not be injected (check again, it is not safe to assume that given RubyRuntime klazz arg was ok)
+ return false unless type(klazz).is_a?(Types::PRuntimeType)
if (klazz.respond_to?(:inject) && klazz.method(:inject).arity() == -4) || klazz.instance_method(:initialize).arity() == 0
klazz
else
@@ -265,6 +269,8 @@ class Puppet::Pops::Types::TypeCalculator
if t2.is_a?(Class)
t2 = type(t2)
end
+ # Unit can be assigned to anything
+ return true if t2.class == Types::PUnitType
@@assignable_visitor.visit_this_1(self, t, t2)
end
@@ -277,18 +283,18 @@ class Puppet::Pops::Types::TypeCalculator
# Answers, does the given callable accept the arguments given in args (an array or a tuple)
#
def callable?(callable, args)
- return false if !callable.is_a?(Types::PCallableType)
+ return false if !self.class.is_kind_of_callable?(callable)
# Note that polymorphism is for the args type, the callable is always a callable
@@callable_visitor.visit_this_1(self, args, callable)
end
# Answers if the two given types describe the same type
def equals(left, right)
- return false unless left.is_a?(Types::PAbstractType) && right.is_a?(Types::PAbstractType)
+ return false unless left.is_a?(Types::PAnyType) && right.is_a?(Types::PAnyType)
# Types compare per class only - an extra test must be made if the are mutually assignable
# to find all types that represent the same type of instance
#
- left == right || (assignable?(right, left) && assignable?(left, right))
+ left == right || (assignable?(right, left) && assignable?(left, right))
end
# Answers 'what is the Puppet Type corresponding to the given Ruby class'
@@ -326,8 +332,7 @@ class Puppet::Pops::Types::TypeCalculator
type.key_type = Types::PScalarType.new()
type.element_type = Types::PDataType.new()
else
- type = Types::PRubyType.new()
- type.ruby_class = c.name
+ type = Types::PRuntimeType.new(:runtime => :ruby, :runtime_type_name => c.name)
end
type
end
@@ -391,17 +396,24 @@ class Puppet::Pops::Types::TypeCalculator
end
def instance_of_Object(t, o)
- # Undef is Undef and Object, but nothing else when checking instance?
- return false if (o.nil? || o == :undef) && t.class != Types::PObjectType
+ # Undef is Undef and Any, but nothing else when checking instance?
+ return false if (o.nil?) && t.class != Types::PAnyType
assignable?(t, infer(o))
end
+ # Anything is an instance of Unit
+ # @api private
+ def instance_of_PUnitType(t, o)
+ true
+ end
+
def instance_of_PArrayType(t, o)
return false unless o.is_a?(Array)
return false unless o.all? {|element| instance_of(t.element_type, element) }
size_t = t.size_type || @collection_default_size_t
size_t2 = size_as_type(o)
- assignable?(size_t, size_t2)
+ # optimize by calling directly
+ assignable_PIntegerType(size_t, size_t2)
end
def instance_of_PTupleType(t, o)
@@ -432,7 +444,8 @@ class Puppet::Pops::Types::TypeCalculator
return false unless o.keys.all? {|key| instance_of(key_t, key) } && o.values.all? {|value| instance_of(element_t, value) }
size_t = t.size_type || @collection_default_size_t
size_t2 = size_as_type(o)
- assignable?(size_t, size_t2)
+ # optimize by calling directly
+ assignable_PIntegerType(size_t, size_t2)
end
def instance_of_PDataType(t, o)
@@ -440,11 +453,11 @@ class Puppet::Pops::Types::TypeCalculator
end
def instance_of_PNilType(t, o)
- return o.nil? || o == :undef
+ return o.nil?
end
def instance_of_POptionalType(t, o)
- return true if (o.nil? || o == :undef)
+ return true if (o.nil?)
instance_of(t.optional_type, o)
end
@@ -471,7 +484,7 @@ class Puppet::Pops::Types::TypeCalculator
# @api public
#
def is_ptype?(t)
- return t.is_a?(Types::PAbstractType)
+ return t.is_a?(Types::PAnyType)
end
# Answers if t represents the puppet type PNilType
@@ -490,6 +503,7 @@ class Puppet::Pops::Types::TypeCalculator
def common_type(t1, t2)
raise ArgumentError, 'two types expected' unless (is_ptype?(t1) || is_pnil?(t1)) && (is_ptype?(t2) || is_pnil?(t2))
+ # TODO: This is not right since Scalar U Undef is Any
# if either is nil, the common type is the other
if is_pnil?(t1)
return t2
@@ -497,6 +511,13 @@ class Puppet::Pops::Types::TypeCalculator
return t1
end
+ # If either side is Unit, it is the other type
+ if t1.is_a?(Types::PUnitType)
+ return t2
+ elsif t2.is_a?(Types::PUnitType)
+ return t1
+ end
+
# Simple case, one is assignable to the other
if assignable?(t1, t2)
return t1
@@ -566,7 +587,7 @@ class Puppet::Pops::Types::TypeCalculator
if t1.is_a?(Types::PPatternType) && t2.is_a?(Types::PPatternType)
t = Types::PPatternType.new()
# must make copies since patterns are contained types, not data-types
- t.patterns = (t1.patterns | t2.patterns).map {|p| p.copy }
+ t.patterns = (t1.patterns | t2.patterns).map(&:copy)
return t
end
@@ -580,7 +601,7 @@ class Puppet::Pops::Types::TypeCalculator
if t1.is_a?(Types::PVariantType) && t2.is_a?(Types::PVariantType)
# The common type is one that complies with either set
t = Types::PVariantType.new
- t.types = (t1.types | t2.types).map {|opt_t| opt_t.copy }
+ t.types = (t1.types | t2.types).map(&:copy)
return t
end
@@ -616,11 +637,13 @@ class Puppet::Pops::Types::TypeCalculator
return type
end
- if t1.is_a?(Types::PRubyType) && t2.is_a?(Types::PRubyType)
- if t1.ruby_class == t2.ruby_class
+ # If both are Runtime types
+ if t1.is_a?(Types::PRuntimeType) && t2.is_a?(Types::PRuntimeType)
+ if t1.runtime == t2.runtime && t1.runtime_type_name == t2.runtime_type_name
return t1
end
# finding the common super class requires that names are resolved to class
+ # NOTE: This only supports runtime type of :ruby
c1 = Types::ClassLoader.provide_from_type(t1)
c2 = Types::ClassLoader.provide_from_type(t2)
if c1 && c2
@@ -628,18 +651,16 @@ class Puppet::Pops::Types::TypeCalculator
superclasses(c1).each do|c1_super|
c2_superclasses.each do |c2_super|
if c1_super == c2_super
- result = Types::PRubyType.new()
- result.ruby_class = c1_super.name
- return result
+ return Types::PRuntimeType.new(:runtime => :ruby, :runtime_type_name => c1_super.name)
end
end
end
end
end
- # If both are RubyObjects
- if common_pobject?(t1, t2)
- return Types::PObjectType.new()
+ # They better both be Any type, or the wrong thing was asked and nil is returned
+ if t1.is_a?(Types::PAnyType) && t2.is_a?(Types::PAnyType)
+ return Types::PAnyType.new()
end
end
@@ -701,15 +722,13 @@ class Puppet::Pops::Types::TypeCalculator
# @api private
def infer_Object(o)
- type = Types::PRubyType.new()
- type.ruby_class = o.class.name
- type
+ Types::PRuntimeType.new(:runtime => :ruby, :runtime_type_name => o.class.name)
end
# The type of all types is PType
# @api private
#
- def infer_PAbstractType(o)
+ def infer_PAnyType(o)
type = Types::PType.new()
type.type = o.copy
type
@@ -761,10 +780,16 @@ class Puppet::Pops::Types::TypeCalculator
Types::PNilType.new()
end
- # Inference of :undef as PNilType, all other are Ruby[Symbol]
+ # Inference of :default as PDefaultType, and all other are Ruby[Symbol]
# @api private
def infer_Symbol(o)
- o == :undef ? infer_NilClass(o) : infer_Object(o)
+ case o
+ when :default
+ Types::PDefaultType.new()
+
+ else
+ infer_Object(o)
+ end
end
# @api private
@@ -782,12 +807,14 @@ class Puppet::Pops::Types::TypeCalculator
#
def infer_Resource(o)
t = Types::PResourceType.new()
- t.type_name = o.type.to_s
+ t.type_name = o.type.to_s.downcase
# Only Puppet::Resource can have a title that is a symbol :undef, a PResource cannot.
# A mapping must be made to empty string. A nil value will result in an error later
title = o.title
- t.title = (title == :undef ? '' : title)
- t
+ t.title = (:undef == title ? '' : title)
+ type = Types::PType.new()
+ type.type = t
+ type
end
# @api private
@@ -848,7 +875,7 @@ class Puppet::Pops::Types::TypeCalculator
type = Types::PHashType.new()
if o.empty?
ktype = Types::PNilType.new()
- etype = Types::PNilType.new()
+ vtype = Types::PNilType.new()
else
ktype = Types::PVariantType.new()
ktype.types = o.keys.map() {|k| infer_set(k) }
@@ -856,7 +883,7 @@ class Puppet::Pops::Types::TypeCalculator
etype.types = o.values.map() {|e| infer_set(e) }
end
type.key_type = unwrap_single_variant(ktype)
- type.element_type = unwrap_single_variant(vtype)
+ type.element_type = unwrap_single_variant(etype)
type.size_type = size_as_type(o)
type
end
@@ -868,6 +895,7 @@ class Puppet::Pops::Types::TypeCalculator
possible_variant
end
end
+
# False in general type calculator
# @api private
def assignable_Object(t, t2)
@@ -875,8 +903,8 @@ class Puppet::Pops::Types::TypeCalculator
end
# @api private
- def assignable_PObjectType(t, t2)
- t2.is_a?(Types::PObjectType)
+ def assignable_PAnyType(t, t2)
+ t2.is_a?(Types::PAnyType)
end
# @api private
@@ -885,6 +913,18 @@ class Puppet::Pops::Types::TypeCalculator
t2.is_a?(Types::PNilType)
end
+ # Anything is assignable to a Unit type
+ # @api private
+ def assignable_PUnitType(t, t2)
+ true
+ end
+
+ # @api private
+ def assignable_PDefaultType(t, t2)
+ # Only default is assignable to default type
+ t2.is_a?(Types::PDefaultType)
+ end
+
# @api private
def assignable_PScalarType(t, t2)
t2.is_a?(Types::PScalarType)
@@ -941,7 +981,7 @@ class Puppet::Pops::Types::TypeCalculator
# A variant is assignable if all of its options are assignable to one of this type's options
return true if t == t2
t2.types.all? do |other|
- # if the other is a Variant, all if its options, but be assignable to one of this type's options
+ # if the other is a Variant, all of its options, but be assignable to one of this type's options
other = other.is_a?(Types::PDataType) ? @data_variant_t : other
if other.is_a?(Types::PVariantType)
assignable?(t, other)
@@ -966,7 +1006,7 @@ class Puppet::Pops::Types::TypeCalculator
end
# Assume no block was given - i.e. it is nil, and its type is PNilType
block_t = @nil_t
- if args_tuple.types.last.is_a?(Types::PCallableType)
+ if self.class.is_kind_of_callable?(args_tuple.types.last)
# a split is needed to make it possible to use required, optional, and varargs semantics
# of the tuple type.
#
@@ -978,16 +1018,41 @@ class Puppet::Pops::Types::TypeCalculator
end
# unless argument types match parameter types
return false unless assignable?(callable_t.param_types, args_tuple)
- # unless given block (or no block) matches expected block (or no block)
+ # can the given block be *called* with a signature requirement specified by callable_t?
assignable?(callable_t.block_type || @nil_t, block_t)
end
+ # @api private
+ def self.is_kind_of_callable?(t, optional = true)
+ case t
+ when Types::PCallableType
+ true
+ when Types::POptionalType
+ optional && is_kind_of_callable?(t.optional_type, optional)
+ when Types::PVariantType
+ t.types.all? {|t2| is_kind_of_callable?(t2, optional) }
+ else
+ false
+ end
+ end
+
+
def callable_PArrayType(args_array, callable_t)
return false unless assignable?(callable_t.param_types, args_array)
- # does not support calling with a block, but have to check that callable expects it
+ # does not support calling with a block, but have to check that callable is ok with missing block
assignable?(callable_t.block_type || @nil_t, @nil_t)
end
+ def callable_PNilType(nil_t, callable_t)
+ # if callable_t is Optional (or indeed PNilType), this means that 'missing callable' is accepted
+ assignable?(callable_t, nil_t)
+ end
+
+ def callable_PCallableType(given_callable_t, required_callable_t)
+ # If the required callable is euqal or more specific than the given, the given is callable
+ assignable?(required_callable_t, given_callable_t)
+ end
+
def max(a,b)
a >=b ? a : b
end
@@ -1004,12 +1069,14 @@ class Puppet::Pops::Types::TypeCalculator
size_t2 = t2.size_type || Puppet::Pops::Types::TypeFactory.range(*t2.size_range)
# not assignable if the number of types in t2 is outside number of types in t1
- return false unless assignable?(size_t, size_t2)
- max(t.types.size, t2.types.size).times do |index|
- return false unless assignable?((t.types[index] || t.types[-1]), (t2.types[index] || t2.types[-1]))
+ if assignable?(size_t, size_t2)
+ t2.types.size.times do |index|
+ return false unless assignable?((t.types[index] || t.types[-1]), t2.types[index])
+ end
+ return true
+ else
+ return false
end
- true
-
elsif t2.is_a?(Types::PArrayType)
t2_entry = t2.element_type
@@ -1064,7 +1131,7 @@ class Puppet::Pops::Types::TypeCalculator
# hash key type must be string of min 1 size
# hash value t must be assignable to each key
element_type = t2.element_type
- assignable?(size_t, size_t2) &&
+ assignable_PIntegerType(size_t, size_t2) &&
assignable?(@non_empty_string_t, t2.key_type) &&
h.all? {|k,v| assignable?(v, element_type) }
else
@@ -1085,9 +1152,14 @@ class Puppet::Pops::Types::TypeCalculator
# @api private
def assignable_PEnumType(t, t2)
return true if t == t2 || (t.values.empty? && (t2.is_a?(Types::PStringType) || t2.is_a?(Types::PEnumType)))
- if t2.is_a?(Types::PStringType)
+ case t2
+ when Types::PStringType
# if the set of strings are all found in the set of enums
t2.values.all? { |s| t.values.any? { |e| e == s }}
+ when Types::PVariantType
+ t2.types.all? {|variant_t| assignable_PEnumType(t, variant_t) }
+ when Types::PEnumType
+ t2.values.all? { |s| t.values.any? {|e| e == s }}
else
false
end
@@ -1105,11 +1177,11 @@ class Puppet::Pops::Types::TypeCalculator
when Types::PStringType
# true if size compliant
size_t2 = t2.size_type || @collection_default_size_t
- assignable?(size_t, size_t2)
+ assignable_PIntegerType(size_t, size_t2)
when Types::PPatternType
# true if size constraint is at least 0 to +Infinity (which is the same as the default)
- assignable?(size_t, @collection_default_size_t)
+ assignable_PIntegerType(size_t, @collection_default_size_t)
when Types::PEnumType
if t2.values
@@ -1140,7 +1212,14 @@ class Puppet::Pops::Types::TypeCalculator
# @api private
def assignable_PPatternType(t, t2)
return true if t == t2
- return false unless t2.is_a?(Types::PStringType) || t2.is_a?(Types::PEnumType)
+ case t2
+ when Types::PStringType, Types::PEnumType
+ values = t2.values
+ when Types::PVariantType
+ return t2.types.all? {|variant_t| assignable_PPatternType(t, variant_t) }
+ else
+ return false
+ end
if t2.values.empty?
# Strings / Enums (unknown which ones) cannot all match a pattern, but if there is no pattern it is ok
@@ -1176,12 +1255,16 @@ class Puppet::Pops::Types::TypeCalculator
return false unless t2.is_a?(Types::PCallableType)
# nil param_types means, any other Callable is assignable
return true if t.param_types.nil?
- return false unless assignable?(t.param_types, t2.param_types)
+
+ # NOTE: these tests are made in reverse as it is calling the callable that is constrained
+ # (it's lower bound), not its upper bound
+ return false unless assignable?(t2.param_types, t.param_types)
# names are ignored, they are just information
# Blocks must be compatible
this_block_t = t.block_type || @nil_t
that_block_t = t2.block_type || @nil_t
- assignable?(this_block_t, that_block_t)
+ assignable?(that_block_t, this_block_t)
+
end
# @api private
@@ -1190,20 +1273,20 @@ class Puppet::Pops::Types::TypeCalculator
case t2
when Types::PCollectionType
size_t2 = t2.size_type || @collection_default_size_t
- assignable?(size_t, size_t2)
+ assignable_PIntegerType(size_t, size_t2)
when Types::PTupleType
# compute the tuple's min/max size, and check if that size matches
from, to = size_range(t2.size_type)
t2s = Types::PIntegerType.new()
t2s.from = t2.types.size - 1 + from
t2s.to = t2.types.size - 1 + to
- assignable?(size_t, t2s)
+ assignable_PIntegerType(size_t, t2s)
when Types::PStructType
from = to = t2.elements.size
t2s = Types::PIntegerType.new()
t2s.from = from
t2s.to = to
- assignable?(size_t, t2s)
+ assignable_PIntegerType(size_t, t2s)
else
false
end
@@ -1310,14 +1393,18 @@ class Puppet::Pops::Types::TypeCalculator
t2.is_a?(Types::PDataType) || assignable?(@data_variant_t, t2)
end
- # Assignable if t2's ruby class is same or subclass of t1's ruby class
+ # Assignable if t2's has the same runtime and the runtime name resolves to
+ # a class that is the same or subclass of t1's resolved runtime type name
# @api private
- def assignable_PRubyType(t1, t2)
- return false unless t2.is_a?(Types::PRubyType)
- return true if t1.ruby_class.nil? # t1 is wider
- return false if t2.ruby_class.nil? # t1 not nil, so t2 can not be wider
- c1 = class_from_string(t1.ruby_class)
- c2 = class_from_string(t2.ruby_class)
+ def assignable_PRuntimeType(t1, t2)
+ return false unless t2.is_a?(Types::PRuntimeType)
+ return false unless t1.runtime == t2.runtime
+ return true if t1.runtime_type_name.nil? # t1 is wider
+ return false if t2.runtime_type_name.nil? # t1 not nil, so t2 can not be wider
+
+ # NOTE: This only supports Ruby, must change when/if the set of runtimes is expanded
+ c1 = class_from_string(t1.runtime_type_name)
+ c2 = class_from_string(t2.runtime_type_name)
return false unless c1.is_a?(Class) && c2.is_a?(Class)
!!(c2 <= c1)
end
@@ -1343,16 +1430,21 @@ class Puppet::Pops::Types::TypeCalculator
def string_String(t) ; t ; end
# @api private
- def string_PObjectType(t) ; "Object" ; end
+ def string_Symbol(t) ; t.to_s ; end
+
+ def string_PAnyType(t) ; "Any" ; end
# @api private
def string_PNilType(t) ; 'Undef' ; end
# @api private
+ def string_PDefaultType(t) ; 'Default' ; end
+
+ # @api private
def string_PBooleanType(t) ; "Boolean" ; end
# @api private
- def string_PScalarType(t) ; "Scalar" ; end
+ def string_PScalarType(t) ; "Scalar" ; end
# @api private
def string_PDataType(t) ; "Data" ; end
@@ -1444,7 +1536,8 @@ class Puppet::Pops::Types::TypeCalculator
else
range = range_array_part(t.param_types.size_type)
end
- types = t.param_types.types.map {|t2| string(t2) }
+ # translate to string, and skip Unit types
+ types = t.param_types.types.map {|t2| string(t2) unless t2.class == Types::PUnitType }.compact
params_part= types.join(', ')
@@ -1490,7 +1583,12 @@ class Puppet::Pops::Types::TypeCalculator
end
# @api private
- def string_PRubyType(t) ; "Ruby[#{string(t.ruby_class)}]" ; end
+ def string_PUnitType(t)
+ "Unit"
+ end
+
+ # @api private
+ def string_PRuntimeType(t) ; "Runtime[#{string(t.runtime)}, #{string(t.runtime_type_name)}]" ; end
# @api private
def string_PArrayType(t)
@@ -1522,9 +1620,9 @@ class Puppet::Pops::Types::TypeCalculator
def string_PResourceType(t)
if t.type_name
if t.title
- "#{t.type_name.capitalize}['#{t.title}']"
+ "#{capitalize_segments(t.type_name)}['#{t.title}']"
else
- "#{t.type_name.capitalize}"
+ capitalize_segments(t.type_name)
end
else
"Resource"
@@ -1569,9 +1667,15 @@ class Puppet::Pops::Types::TypeCalculator
private
+ NAME_SEGMENT_SEPARATOR = '::'.freeze
+
+ def capitalize_segments(s)
+ s.split(NAME_SEGMENT_SEPARATOR).map(&:capitalize).join(NAME_SEGMENT_SEPARATOR)
+ end
+
def class_from_string(str)
begin
- str.split('::').inject(Object) do |memo, name_segment|
+ str.split(NAME_SEGMENT_SEPARATOR).inject(Object) do |memo, name_segment|
memo.const_get(name_segment)
end
rescue NameError
@@ -1591,7 +1695,4 @@ class Puppet::Pops::Types::TypeCalculator
assignable?(@numeric_t, t1) && assignable?(@numeric_t, t2)
end
- def common_pobject?(t1, t2)
- assignable?(@t, t1) && assignable?(@t, t2)
- end
end
diff --git a/lib/puppet/pops/types/type_factory.rb b/lib/puppet/pops/types/type_factory.rb
index e01494f8f..45979dd96 100644
--- a/lib/puppet/pops/types/type_factory.rb
+++ b/lib/puppet/pops/types/type_factory.rb
@@ -18,8 +18,9 @@ module Puppet::Pops::Types::TypeFactory
#
def self.range(from, to)
t = Types::PIntegerType.new()
- t.from = from unless (from == :default || from == 'default')
- t.to = to unless (to == :default || to == 'default')
+ # optimize eq with symbol (faster when it is left)
+ t.from = from unless (:default == from || from == 'default')
+ t.to = to unless (:default == to || to == 'default')
t
end
@@ -28,8 +29,9 @@ module Puppet::Pops::Types::TypeFactory
#
def self.float_range(from, to)
t = Types::PFloatType.new()
- t.from = Float(from) unless from == :default || from.nil?
- t.to = Float(to) unless to == :default || to.nil?
+ # optimize eq with symbol (faster when it is left)
+ t.from = Float(from) unless :default == from || from.nil?
+ t.to = Float(to) unless :default == to || to.nil?
t
end
@@ -70,11 +72,6 @@ module Puppet::Pops::Types::TypeFactory
t
end
- # Convenience method to produce an Optional[Object] type
- def self.optional_object()
- optional(object())
- end
-
# Produces the Enum type, optionally with specific string values
# @api public
#
@@ -93,9 +90,10 @@ module Puppet::Pops::Types::TypeFactory
t
end
- # Produces the Struct type, either a non parameterized instance representing all structs (i.e. all hashes)
- # or a hash with a given set of keys of String type (names), bound to a value of a given type. Type may be
- # a Ruby Class, a Puppet Type, or an instance from which the type is inferred.
+ # Produces the Struct type, either a non parameterized instance representing
+ # all structs (i.e. all hashes) or a hash with a given set of keys of String
+ # type (names), bound to a value of a given type. Type may be a Ruby Class, a
+ # Puppet Type, or an instance from which the type is inferred.
#
def self.struct(name_type_hash = {})
t = Types::PStructType.new
@@ -124,15 +122,16 @@ module Puppet::Pops::Types::TypeFactory
Types::PBooleanType.new()
end
- # Produces the Object type
+ # Produces the Any type
# @api public
#
- def self.object()
- Types::PObjectType.new()
+ def self.any()
+ Types::PAnyType.new()
end
# Produces the Regexp type
- # @param pattern [Regexp, String, nil] (nil) The regular expression object or a regexp source string, or nil for bare type
+ # @param pattern [Regexp, String, nil] (nil) The regular expression object or
+ # a regexp source string, or nil for bare type
# @api public
#
def self.regexp(pattern = nil)
@@ -198,20 +197,18 @@ module Puppet::Pops::Types::TypeFactory
# use {#all_callables}.
#
# The params is a list of types, where the three last entries may be
- # optionally followed by min, max count, and a Callable which is taken as the block_type.
+ # optionally followed by min, max count, and a Callable which is taken as the
+ # block_type.
# If neither min or max are specified the parameters must match exactly.
# A min < params.size means that the difference are optional.
# If max > params.size means that the last type repeats.
# if max is :default, the max value is unbound (infinity).
- #
+ #
# Params are given as a sequence of arguments to {#type_of}.
#
def self.callable(*params)
- case params.last
- when Types::PCallableType
+ if Puppet::Pops::Types::TypeCalculator.is_kind_of_callable?(params.last)
last_callable = true
- when Types::POptionalType
- last_callable = true if params.last.optional_type.is_a?(Types::PCallableType)
end
block_t = last_callable ? params.pop : nil
@@ -226,6 +223,10 @@ module Puppet::Pops::Types::TypeFactory
types = params.map {|p| type_of(p) }
+ # If the specification requires types, and none were given, a Unit type is used
+ if types.empty? && !size_type.nil? && size_type.range[1] > 0
+ types << Types::PUnitType.new
+ end
# create a signature
callable_t = Types::PCallableType.new()
tuple_t = tuple(*types)
@@ -266,27 +267,41 @@ module Puppet::Pops::Types::TypeFactory
Types::PNilType.new()
end
+ # Creates an instance of the Default type
+ # @api public
+ def self.default()
+ Types::PDefaultType.new()
+ end
+
# Produces an instance of the abstract type PCatalogEntryType
def self.catalog_entry()
Types::PCatalogEntryType.new()
end
- # Produces a PResourceType with a String type_name
- # A PResourceType with a nil or empty name is compatible with any other PResourceType.
- # A PResourceType with a given name is only compatible with a PResourceType with the same name.
- # (There is no resource-type subtyping in Puppet (yet)).
+ # Produces a PResourceType with a String type_name A PResourceType with a nil
+ # or empty name is compatible with any other PResourceType. A PResourceType
+ # with a given name is only compatible with a PResourceType with the same
+ # name. (There is no resource-type subtyping in Puppet (yet)).
#
def self.resource(type_name = nil, title = nil)
type = Types::PResourceType.new()
type_name = type_name.type_name if type_name.is_a?(Types::PResourceType)
- type.type_name = type_name.downcase unless type_name.nil?
+ type_name = type_name.downcase unless type_name.nil?
+ type.type_name = type_name
+ unless type_name.nil? || type_name =~ Puppet::Pops::Patterns::CLASSREF
+ raise ArgumentError, "Illegal type name '#{type.type_name}'"
+ end
+ if type_name.nil? && !title.nil?
+ raise ArgumentError, "The type name cannot be nil, if title is given"
+ end
type.title = title
type
end
- # Produces PHostClassType with a string class_name.
- # A PHostClassType with nil or empty name is compatible with any other PHostClassType.
- # A PHostClassType with a given name is only compatible with a PHostClassType with the same name.
+ # Produces PHostClassType with a string class_name. A PHostClassType with
+ # nil or empty name is compatible with any other PHostClassType. A
+ # PHostClassType with a given name is only compatible with a PHostClassType
+ # with the same name.
#
def self.host_class(class_name = nil)
type = Types::PHostClassType.new()
@@ -296,7 +311,8 @@ module Puppet::Pops::Types::TypeFactory
type
end
- # Produces a type for Array[o] where o is either a type, or an instance for which a type is inferred.
+ # Produces a type for Array[o] where o is either a type, or an instance for
+ # which a type is inferred.
# @api public
#
def self.array_of(o)
@@ -305,7 +321,8 @@ module Puppet::Pops::Types::TypeFactory
type
end
- # Produces a type for Hash[Scalar, o] where o is either a type, or an instance for which a type is inferred.
+ # Produces a type for Hash[Scalar, o] where o is either a type, or an
+ # instance for which a type is inferred.
# @api public
#
def self.hash_of(value, key = scalar())
@@ -343,31 +360,33 @@ module Puppet::Pops::Types::TypeFactory
type
end
- # Produce a type corresponding to the class of given unless given is a String, Class or a PAbstractType.
- # When a String is given this is taken as a classname.
+ # Produce a type corresponding to the class of given unless given is a
+ # String, Class or a PAnyType. When a String is given this is taken as
+ # a classname.
#
def self.type_of(o)
if o.is_a?(Class)
@type_calculator.type(o)
- elsif o.is_a?(Types::PAbstractType)
+ elsif o.is_a?(Types::PAnyType)
o
elsif o.is_a?(String)
- type = Types::PRubyType.new()
- type.ruby_class = o
- type
+ Types::PRuntimeType.new(:runtime => :ruby, :runtime_type_name => o)
else
@type_calculator.infer_generic(o)
end
end
- # Produces a type for a class or infers a type for something that is not a class
+ # Produces a type for a class or infers a type for something that is not a
+ # class
# @note
# To get the type for the class' class use `TypeCalculator.infer(c)`
#
# @overload ruby(o)
- # @param o [Class] produces the type corresponding to the class (e.g. Integer becomes PIntegerType)
+ # @param o [Class] produces the type corresponding to the class (e.g.
+ # Integer becomes PIntegerType)
# @overload ruby(o)
- # @param o [Object] produces the type corresponding to the instance class (e.g. 3 becomes PIntegerType)
+ # @param o [Object] produces the type corresponding to the instance class
+ # (e.g. 3 becomes PIntegerType)
#
# @api public
#
@@ -375,32 +394,39 @@ module Puppet::Pops::Types::TypeFactory
if o.is_a?(Class)
@type_calculator.type(o)
else
- type = Types::PRubyType.new()
- type.ruby_class = o.class.name
- type
+ Types::PRuntimeType.new(:runtime => :ruby, :runtime_type_name => o.class.name)
end
end
- # Generic creator of a RubyType - allows creating the Ruby type with nil name, or String name.
- # Also see ruby(o) which performs inference, or mapps a Ruby Class to its name.
+ # Generic creator of a RuntimeType["ruby"] - allows creating the Ruby type
+ # with nil name, or String name. Also see ruby(o) which performs inference,
+ # or mapps a Ruby Class to its name.
#
def self.ruby_type(class_name = nil)
- type = Types::PRubyType.new()
- type.ruby_class = class_name
- type
+ Types::PRuntimeType.new(:runtime => :ruby, :runtime_type_name => class_name)
+ end
+
+ # Generic creator of a RuntimeType - allows creating the type with nil or
+ # String runtime_type_name. Also see ruby_type(o) and ruby(o).
+ #
+ def self.runtime(runtime=nil, runtime_type_name = nil)
+ runtime = runtime.to_sym if runtime.is_a?(String)
+ Types::PRuntimeType.new(:runtime => runtime, :runtime_type_name => runtime_type_name)
end
- # Sets the accepted size range of a collection if something other than the default 0 to Infinity
- # is wanted. The semantics for from/to are the same as for #range
+ # Sets the accepted size range of a collection if something other than the
+ # default 0 to Infinity is wanted. The semantics for from/to are the same as
+ # for #range
#
def self.constrain_size(collection_t, from, to)
collection_t.size_type = range(from, to)
collection_t
end
- # Returns true if the given type t is of valid range parameter type (integer or literal default).
+ # Returns true if the given type t is of valid range parameter type (integer
+ # or literal default).
def self.is_range_parameter?(t)
- t.is_a?(Integer) || t == 'default' || t == :default
+ t.is_a?(Integer) || t == 'default' || :default == t
end
end
diff --git a/lib/puppet/pops/types/type_parser.rb b/lib/puppet/pops/types/type_parser.rb
index 782598f2d..1b65e147e 100644
--- a/lib/puppet/pops/types/type_parser.rb
+++ b/lib/puppet/pops/types/type_parser.rb
@@ -24,7 +24,7 @@ class Puppet::Pops::Types::TypeParser
#
# @param string [String] a string with the type expressed in stringified form as produced by the
# {Puppet::Pops::Types::TypeCalculator#string TypeCalculator#string} method.
- # @return [Puppet::Pops::Types::PObjectType] a specialization of the PObjectType representing the type.
+ # @return [Puppet::Pops::Types::PAnyType] a specialization of the PAnyType representing the type.
#
# @api public
#
@@ -47,7 +47,7 @@ class Puppet::Pops::Types::TypeParser
def interpret(ast)
result = @type_transformer.visit_this_0(self, ast)
result = result.body if result.is_a?(Puppet::Pops::Model::Program)
- raise_invalid_type_specification_error unless result.is_a?(Puppet::Pops::Types::PAbstractType)
+ raise_invalid_type_specification_error unless result.is_a?(Puppet::Pops::Types::PAnyType)
result
end
@@ -76,6 +76,10 @@ class Puppet::Pops::Types::TypeParser
o.value
end
+ def interpret_LiteralRegularExpression(o)
+ o.value
+ end
+
# @api private
def interpret_String(o)
o
@@ -157,11 +161,13 @@ class Puppet::Pops::Types::TypeParser
TYPES.catalog_entry()
when "undef"
- # Should not be interpreted as Resource type
TYPES.undef()
- when "object"
- TYPES.object()
+ when "default"
+ TYPES.default()
+
+ when "any"
+ TYPES.any()
when "variant"
TYPES.variant()
@@ -169,8 +175,8 @@ class Puppet::Pops::Types::TypeParser
when "optional"
TYPES.optional()
- when "ruby"
- TYPES.ruby_type()
+ when "runtime"
+ TYPES.runtime()
when "type"
TYPES.type_type()
@@ -297,22 +303,22 @@ class Puppet::Pops::Types::TypeParser
when "enum"
# 1..m parameters being strings
- raise_invalid_parameters_error("Enum", "1 or more", parameters.size) unless parameters.size > 1
+ raise_invalid_parameters_error("Enum", "1 or more", parameters.size) unless parameters.size >= 1
TYPES.enum(*parameters)
when "pattern"
# 1..m parameters being strings or regular expressions
- raise_invalid_parameters_error("Pattern", "1 or more", parameters.size) unless parameters.size > 1
+ raise_invalid_parameters_error("Pattern", "1 or more", parameters.size) unless parameters.size >= 1
TYPES.pattern(*parameters)
when "variant"
# 1..m parameters being strings or regular expressions
- raise_invalid_parameters_error("Variant", "1 or more", parameters.size) unless parameters.size > 1
+ raise_invalid_parameters_error("Variant", "1 or more", parameters.size) unless parameters.size >= 1
TYPES.variant(*parameters)
when "tuple"
# 1..m parameters being types (last two optionally integer or literal default
- raise_invalid_parameters_error("Tuple", "1 or more", parameters.size) unless parameters.size > 1
+ raise_invalid_parameters_error("Tuple", "1 or more", parameters.size) unless parameters.size >= 1
length = parameters.size
if TYPES.is_range_parameter?(parameters[-2])
# min, max specification
@@ -400,7 +406,7 @@ class Puppet::Pops::Types::TypeParser
assert_type(parameters[0])
TYPES.optional(parameters[0])
- when "object", "data", "catalogentry", "boolean", "scalar", "undef", "numeric"
+ when "any", "data", "catalogentry", "boolean", "scalar", "undef", "numeric", "default"
raise_unparameterized_type_error(parameterized_ast.left_expr)
when "type"
@@ -410,9 +416,9 @@ class Puppet::Pops::Types::TypeParser
assert_type(parameters[0])
TYPES.type_type(parameters[0])
- when "ruby"
- raise_invalid_parameters_error("Ruby", "1", parameters.size) unless parameters.size == 1
- TYPES.ruby_type(parameters[0])
+ when "runtime"
+ raise_invalid_parameters_error("Runtime", "2", parameters.size) unless parameters.size == 2
+ TYPES.runtime(*parameters)
else
# It is a resource such a File['/tmp/foo']
@@ -427,7 +433,7 @@ class Puppet::Pops::Types::TypeParser
private
def assert_type(t)
- raise_invalid_type_specification_error unless t.is_a?(Puppet::Pops::Types::PObjectType)
+ raise_invalid_type_specification_error unless t.is_a?(Puppet::Pops::Types::PAnyType)
true
end
diff --git a/lib/puppet/pops/types/types.rb b/lib/puppet/pops/types/types.rb
index f6e374eb0..331357f80 100644
--- a/lib/puppet/pops/types/types.rb
+++ b/lib/puppet/pops/types/types.rb
@@ -1,506 +1,397 @@
require 'rgen/metamodel_builder'
# The Types model is a model of Puppet Language types.
-#
-# The exact relationship between types is not visible in this model wrt. the PDataType which is an abstraction
-# of Scalar, Array[Data], and Hash[Scalar, Data] nested to any depth. This means it is not possible to
-# infer the type by simply looking at the inheritance hierarchy. The {Puppet::Pops::Types::TypeCalculator} should
-# be used to answer questions about types. The {Puppet::Pops::Types::TypeFactory} should be used to create an instance
-# of a type whenever one is needed.
-#
-# The implementation of the Types model contains methods that are required for the type objects to behave as
-# expected when comparing them and using them as keys in hashes. (No other logic is, or should be included directly in
-# the model's classes).
+# It consists of two parts; the meta-model expressed using RGen (in types_meta.rb) and this file which
+# mixes in the implementation.
#
# @api public
#
-module Puppet::Pops::Types
- # Used as end in a range
- INFINITY = 1.0 / 0.0
- NEGATIVE_INFINITY = -INFINITY
-
- class PAbstractType < Puppet::Pops::Model::PopsObject
- abstract
- module ClassModule
- # Produce a deep copy of the type
- def copy
- Marshal.load(Marshal.dump(self))
- end
+module Puppet::Pops
+ require 'puppet/pops/types/types_meta'
- def hash
- self.class.hash
- end
+ # TODO: See PUP-2978 for possible performance optimization
- def ==(o)
- self.class == o.class
- end
+ # Mix in implementation part of the Bindings Module
+ module Types
+ # Used as end in a range
+ INFINITY = 1.0 / 0.0
+ NEGATIVE_INFINITY = -INFINITY
- alias eql? ==
-
- def to_s
- Puppet::Pops::Types::TypeCalculator.string(self)
- end
+ class TypeModelObject < RGen::MetamodelBuilder::MMBase
+ include Puppet::Pops::Visitable
+ include Puppet::Pops::Adaptable
+ include Puppet::Pops::Containment
end
- end
- # The type of types.
- # @api public
- class PType < PAbstractType
- contains_one_uni 'type', PAbstractType
- module ClassModule
- def hash
- [self.class, type].hash
- end
-
- def ==(o)
- self.class == o.class && type == o.type
- end
- end
- end
-
- # Base type for all types except {Puppet::Pops::Types::PType PType}, the type of types.
- # @api public
- class PObjectType < PAbstractType
+ class PAnyType < TypeModelObject
+ module ClassModule
+ # Produce a deep copy of the type
+ def copy
+ Marshal.load(Marshal.dump(self))
+ end
- module ClassModule
- end
+ def hash
+ self.class.hash
+ end
- end
+ def ==(o)
+ self.class == o.class
+ end
- # @api public
- class PNilType < PObjectType
- end
+ alias eql? ==
- # A flexible data type, being assignable to its subtypes as well as PArrayType and PHashType with element type assignable to PDataType.
- #
- # @api public
- class PDataType < PObjectType
- module ClassModule
- def ==(o)
- self.class == o.class ||
- o.class == PVariantType && o == Puppet::Pops::Types::TypeCalculator.data_variant()
+ def to_s
+ Puppet::Pops::Types::TypeCalculator.string(self)
+ end
end
end
- end
- # A flexible type describing an any? of other types
- # @api public
- class PVariantType < PObjectType
- contains_many_uni 'types', PAbstractType, :lowerBound => 1
-
- module ClassModule
+ class PType < PAnyType
+ module ClassModule
+ def hash
+ [self.class, type].hash
+ end
- def hash
- [self.class, Set.new(self.types)].hash
+ def ==(o)
+ self.class == o.class && type == o.type
+ end
end
+ end
- def ==(o)
- (self.class == o.class && Set.new(types) == Set.new(o.types)) ||
- (o.class == PDataType && self == Puppet::Pops::Types::TypeCalculator.data_variant())
+ class PDataType < PAnyType
+ module ClassModule
+ def ==(o)
+ self.class == o.class ||
+ o.class == PVariantType && o == Puppet::Pops::Types::TypeCalculator.data_variant()
+ end
end
end
- end
-
- # Type that is PDataType compatible, but is not a PCollectionType.
- # @api public
- class PScalarType < PObjectType
- end
- # A string type describing the set of strings having one of the given values
- #
- class PEnumType < PScalarType
- has_many_attr 'values', String, :lowerBound => 1
+ class PVariantType < PAnyType
+ module ClassModule
- module ClassModule
- def hash
- [self.class, Set.new(self.values)].hash
- end
+ def hash
+ [self.class, Set.new(self.types)].hash
+ end
- def ==(o)
- self.class == o.class && Set.new(values) == Set.new(o.values)
+ def ==(o)
+ (self.class == o.class && Set.new(types) == Set.new(o.types)) ||
+ (o.class == PDataType && self == Puppet::Pops::Types::TypeCalculator.data_variant())
+ end
end
end
- end
- # @api public
- class PNumericType < PScalarType
- end
+ class PEnumType < PScalarType
+ module ClassModule
+ def hash
+ [self.class, Set.new(self.values)].hash
+ end
- # @api public
- class PIntegerType < PNumericType
- has_attr 'from', Integer, :lowerBound => 0
- has_attr 'to', Integer, :lowerBound => 0
+ def ==(o)
+ self.class == o.class && Set.new(values) == Set.new(o.values)
+ end
+ end
+ end
- module ClassModule
- # The integer type is enumerable when it defines a range
- include Enumerable
+ class PIntegerType < PNumericType
+ module ClassModule
+ # The integer type is enumerable when it defines a range
+ include Enumerable
- # Returns Float.Infinity if one end of the range is unbound
- def size
- return INFINITY if from.nil? || to.nil?
- 1+(to-from).abs
- end
+ # Returns Float.Infinity if one end of the range is unbound
+ def size
+ return INFINITY if from.nil? || to.nil?
+ 1+(to-from).abs
+ end
- # Returns the range as an array ordered so the smaller number is always first.
- # The number may be Infinity or -Infinity.
- def range
- f = from || NEGATIVE_INFINITY
- t = to || INFINITY
- if f < t
- [f, t]
- else
- [t,f]
+ # Returns the range as an array ordered so the smaller number is always first.
+ # The number may be Infinity or -Infinity.
+ def range
+ f = from || NEGATIVE_INFINITY
+ t = to || INFINITY
+ if f < t
+ [f, t]
+ else
+ [t,f]
+ end
end
- end
- # Returns Enumerator if no block is given
- # Returns self if size is infinity (does not yield)
- def each
- return self.to_enum unless block_given?
- return nil if from.nil? || to.nil?
- if to < from
- from.downto(to) {|x| yield x }
- else
- from.upto(to) {|x| yield x }
+ # Returns Enumerator if no block is given
+ # Returns self if size is infinity (does not yield)
+ def each
+ return self.to_enum unless block_given?
+ return nil if from.nil? || to.nil?
+ if to < from
+ from.downto(to) {|x| yield x }
+ else
+ from.upto(to) {|x| yield x }
+ end
end
- end
- def hash
- [self.class, from, to].hash
- end
+ def hash
+ [self.class, from, to].hash
+ end
- def ==(o)
- self.class == o.class && from == o.from && to == o.to
+ def ==(o)
+ self.class == o.class && from == o.from && to == o.to
+ end
end
end
- end
- # @api public
- class PFloatType < PNumericType
- has_attr 'from', Float, :lowerBound => 0
- has_attr 'to', Float, :lowerBound => 0
-
- module ClassModule
- def hash
- [self.class, from, to].hash
- end
+ class PFloatType < PNumericType
+ module ClassModule
+ def hash
+ [self.class, from, to].hash
+ end
- def ==(o)
- self.class == o.class && from == o.from && to == o.to
+ def ==(o)
+ self.class == o.class && from == o.from && to == o.to
+ end
end
end
- end
-
- # @api public
- class PStringType < PScalarType
- has_many_attr 'values', String, :lowerBound => 0, :upperBound => -1, :unique => true
- contains_one_uni 'size_type', PIntegerType
- module ClassModule
+ class PStringType < PScalarType
+ module ClassModule
- def hash
- [self.class, self.size_type, Set.new(self.values)].hash
- end
+ def hash
+ [self.class, self.size_type, Set.new(self.values)].hash
+ end
- def ==(o)
- self.class == o.class && self.size_type == o.size_type && Set.new(values) == Set.new(o.values)
+ def ==(o)
+ self.class == o.class && self.size_type == o.size_type && Set.new(values) == Set.new(o.values)
+ end
end
end
- end
-
- # @api public
- class PRegexpType < PScalarType
- has_attr 'pattern', String, :lowerBound => 1
- has_attr 'regexp', Object, :derived => true
- module ClassModule
- def regexp_derived
- @_regexp = Regexp.new(pattern) unless @_regexp && @_regexp.source == pattern
- @_regexp
- end
+ class PRegexpType < PScalarType
+ module ClassModule
+ def regexp_derived
+ @_regexp = Regexp.new(pattern) unless @_regexp && @_regexp.source == pattern
+ @_regexp
+ end
- def hash
- [self.class, pattern].hash
- end
+ def hash
+ [self.class, pattern].hash
+ end
- def ==(o)
- self.class == o.class && pattern == o.pattern
+ def ==(o)
+ self.class == o.class && pattern == o.pattern
+ end
end
end
- end
- # Represents a subtype of String that narrows the string to those matching the patterns
- # If specified without a pattern it is basically the same as the String type.
- #
- # @api public
- class PPatternType < PScalarType
- contains_many_uni 'patterns', PRegexpType
+ class PPatternType < PScalarType
+ module ClassModule
- module ClassModule
-
- def hash
- [self.class, Set.new(patterns)].hash
- end
+ def hash
+ [self.class, Set.new(patterns)].hash
+ end
- def ==(o)
- self.class == o.class && Set.new(patterns) == Set.new(o.patterns)
+ def ==(o)
+ self.class == o.class && Set.new(patterns) == Set.new(o.patterns)
+ end
end
end
- end
-
- # @api public
- class PBooleanType < PScalarType
- end
- # @api public
- class PCollectionType < PObjectType
- contains_one_uni 'element_type', PAbstractType
- contains_one_uni 'size_type', PIntegerType
-
- module ClassModule
- # Returns an array with from (min) size to (max) size
- # A negative range value in from is
- def size_range
- return [0, INFINITY] if size_type.nil?
- f = size_type.from || 0
- t = size_type.to || INFINITY
- if f < t
- [f, t]
- else
- [t,f]
+ class PCollectionType < PAnyType
+ module ClassModule
+ # Returns an array with from (min) size to (max) size
+ def size_range
+ return [0, INFINITY] if size_type.nil?
+ f = size_type.from || 0
+ t = size_type.to || INFINITY
+ if f < t
+ [f, t]
+ else
+ [t,f]
+ end
end
- end
- def hash
- [self.class, element_type, size_type].hash
- end
+ def hash
+ [self.class, element_type, size_type].hash
+ end
- def ==(o)
- self.class == o.class && element_type == o.element_type && size_type == o.size_type
+ def ==(o)
+ self.class == o.class && element_type == o.element_type && size_type == o.size_type
+ end
end
end
- end
-
- class PStructElement < Puppet::Pops::Model::PopsObject
- has_attr 'name', String, :lowerBound => 1
- contains_one_uni 'type', PAbstractType
- module ClassModule
- def hash
- [self.class, type, name].hash
- end
+ class PStructElement < TypeModelObject
+ module ClassModule
+ def hash
+ [self.class, type, name].hash
+ end
- def ==(o)
- self.class == o.class && type == o.type && name == o.name
+ def ==(o)
+ self.class == o.class && type == o.type && name == o.name
+ end
end
end
- end
- # @api public
- class PStructType < PObjectType
- contains_many_uni 'elements', PStructElement, :lowerBound => 1
- has_attr 'hashed_elements', Object, :derived => true
- module ClassModule
- def hashed_elements_derived
- @_hashed ||= elements.reduce({}) {|memo, e| memo[e.name] = e.type; memo }
- @_hashed
- end
+ class PStructType < PAnyType
+ module ClassModule
+ def hashed_elements_derived
+ @_hashed ||= elements.reduce({}) {|memo, e| memo[e.name] = e.type; memo }
+ @_hashed
+ end
- def clear_hashed_elements
- @_hashed = nil
- end
+ def clear_hashed_elements
+ @_hashed = nil
+ end
- def hash
- [self.class, Set.new(elements)].hash
- end
+ def hash
+ [self.class, Set.new(elements)].hash
+ end
- def ==(o)
- self.class == o.class && hashed_elements == o.hashed_elements
+ def ==(o)
+ self.class == o.class && hashed_elements == o.hashed_elements
+ end
end
end
- end
- # @api public
- class PTupleType < PObjectType
- contains_many_uni 'types', PAbstractType, :lowerBound => 1
- # If set, describes min and max required of the given types - if max > size of
- # types, the last type entry repeats
- #
- contains_one_uni 'size_type', PIntegerType, :lowerBound => 0
-
- module ClassModule
- # Returns the number of elements accepted [min, max] in the tuple
- def size_range
- types_size = types.size
- size_type.nil? ? [types_size, types_size] : size_type.range
- end
+ class PTupleType < PAnyType
+ module ClassModule
+ # Returns the number of elements accepted [min, max] in the tuple
+ def size_range
+ types_size = types.size
+ size_type.nil? ? [types_size, types_size] : size_type.range
+ end
- # Returns the number of accepted occurrences [min, max] of the last type in the tuple
- # The defaults is [1,1]
- #
- def repeat_last_range
- types_size = types.size
- if size_type.nil?
- return [1, 1]
- end
- from, to = size_type.range()
- min = from - (types_size-1)
- min = min <= 0 ? 0 : min
- max = to - (types_size-1)
- [min, max]
- end
+ # Returns the number of accepted occurrences [min, max] of the last type in the tuple
+ # The defaults is [1,1]
+ #
+ def repeat_last_range
+ types_size = types.size
+ if size_type.nil?
+ return [1, 1]
+ end
+ from, to = size_type.range()
+ min = from - (types_size-1)
+ min = min <= 0 ? 0 : min
+ max = to - (types_size-1)
+ [min, max]
+ end
- def hash
- [self.class, size_type, Set.new(types)].hash
- end
+ def hash
+ [self.class, size_type, Set.new(types)].hash
+ end
- def ==(o)
- self.class == o.class && types == o.types && size_type == o.size_type
+ def ==(o)
+ self.class == o.class && types == o.types && size_type == o.size_type
+ end
end
end
- end
-
- class PCallableType < PObjectType
- # Types of parameters and required/optional count
- contains_one_uni 'param_types', PTupleType, :lowerBound => 1
- # Although being an abstract type reference, only PAbstractCallable, and Optional[Callable] are supported
- # If not set, the meaning is that block is not supported.
- #
- contains_one_uni 'block_type', PAbstractType, :lowerBound => 0
-
- module ClassModule
- # Returns the number of accepted arguments [min, max]
- def size_range
- param_types.size_range
- end
+ class PCallableType < PAnyType
+ module ClassModule
+ # Returns the number of accepted arguments [min, max]
+ def size_range
+ param_types.size_range
+ end
- # Returns the number of accepted arguments for the last parameter type [min, max]
- #
- def last_range
- param_types.repeat_last_range
- end
+ # Returns the number of accepted arguments for the last parameter type [min, max]
+ #
+ def last_range
+ param_types.repeat_last_range
+ end
- # Range [0,0], [0,1], or [1,1] for the block
- #
- def block_range
- case block_type
- when Puppet::Pops::Types::POptionalType
- [0,1]
- when Puppet::Pops::Types::PVariantType, Puppet::Pops::Types::PCallableType
- [1,1]
- else
- [0,0]
+ # Range [0,0], [0,1], or [1,1] for the block
+ #
+ def block_range
+ case block_type
+ when Puppet::Pops::Types::POptionalType
+ [0,1]
+ when Puppet::Pops::Types::PVariantType, Puppet::Pops::Types::PCallableType
+ [1,1]
+ else
+ [0,0]
+ end
end
- end
- def hash
- [self.class, Set.new(param_types), block_type].hash
- end
+ def hash
+ [self.class, Set.new(param_types), block_type].hash
+ end
- def ==(o)
- self.class == o.class && args_type == o.args_type && block_type == o.block_type
+ def ==(o)
+ self.class == o.class && args_type == o.args_type && block_type == o.block_type
+ end
end
end
- end
- # @api public
- class PArrayType < PCollectionType
- module ClassModule
- def hash
- [self.class, self.element_type, self.size_type].hash
- end
+ class PArrayType < PCollectionType
+ module ClassModule
+ def hash
+ [self.class, self.element_type, self.size_type].hash
+ end
- def ==(o)
- self.class == o.class && self.element_type == o.element_type && self.size_type == o.size_type
+ def ==(o)
+ self.class == o.class && self.element_type == o.element_type && self.size_type == o.size_type
+ end
end
end
- end
- # @api public
- class PHashType < PCollectionType
- contains_one_uni 'key_type', PAbstractType
- module ClassModule
- def hash
- [self.class, key_type, self.element_type, self.size_type].hash
- end
+ class PHashType < PCollectionType
+ module ClassModule
+ def hash
+ [self.class, key_type, self.element_type, self.size_type].hash
+ end
- def ==(o)
- self.class == o.class &&
- key_type == o.key_type &&
- self.element_type == o.element_type &&
- self.size_type == o.size_type
+ def ==(o)
+ self.class == o.class &&
+ key_type == o.key_type &&
+ self.element_type == o.element_type &&
+ self.size_type == o.size_type
+ end
end
end
- end
- # @api public
- class PRubyType < PObjectType
- has_attr 'ruby_class', String
- module ClassModule
- def hash
- [self.class, ruby_class].hash
- end
- def ==(o)
- self.class == o.class && ruby_class == o.ruby_class
+ class PRuntimeType < PAnyType
+ module ClassModule
+ def hash
+ [self.class, runtime, runtime_type_name].hash
+ end
+
+ def ==(o)
+ self.class == o.class && runtime == o.runtime && runtime_type_name == o.runtime_type_name
+ end
end
end
- end
-
- # Abstract representation of a type that can be placed in a Catalog.
- # @api public
- #
- class PCatalogEntryType < PObjectType
- end
- # Represents a (host-) class in the Puppet Language.
- # @api public
- #
- class PHostClassType < PCatalogEntryType
- has_attr 'class_name', String
- # contains_one_uni 'super_type', PHostClassType
- module ClassModule
- def hash
- [self.class, class_name].hash
- end
- def ==(o)
- self.class == o.class && class_name == o.class_name
+ class PHostClassType < PCatalogEntryType
+ module ClassModule
+ def hash
+ [self.class, class_name].hash
+ end
+ def ==(o)
+ self.class == o.class && class_name == o.class_name
+ end
end
end
- end
- # Represents a Resource Type in the Puppet Language
- # @api public
- #
- class PResourceType < PCatalogEntryType
- has_attr 'type_name', String
- has_attr 'title', String
- module ClassModule
- def hash
- [self.class, type_name, title].hash
- end
- def ==(o)
- self.class == o.class && type_name == o.type_name && title == o.title
+ class PResourceType < PCatalogEntryType
+ module ClassModule
+ def hash
+ [self.class, type_name, title].hash
+ end
+ def ==(o)
+ self.class == o.class && type_name == o.type_name && title == o.title
+ end
end
end
- end
- # Represents a type that accept PNilType instead of the type parameter
- # required_type - is a short hand for Variant[T, Undef]
- #
- class POptionalType < PAbstractType
- contains_one_uni 'optional_type', PAbstractType
- module ClassModule
- def hash
- [self.class, optional_type].hash
- end
+ class POptionalType < PAnyType
+ module ClassModule
+ def hash
+ [self.class, optional_type].hash
+ end
- def ==(o)
- self.class == o.class && optional_type == o.optional_type
+ def ==(o)
+ self.class == o.class && optional_type == o.optional_type
+ end
end
end
end
-
end
diff --git a/lib/puppet/pops/types/types_meta.rb b/lib/puppet/pops/types/types_meta.rb
new file mode 100644
index 000000000..3e7d80ad7
--- /dev/null
+++ b/lib/puppet/pops/types/types_meta.rb
@@ -0,0 +1,223 @@
+require 'rgen/metamodel_builder'
+
+# The Types model is a model of Puppet Language types.
+#
+# The exact relationship between types is not visible in this model wrt. the PDataType which is an abstraction
+# of Scalar, Array[Data], and Hash[Scalar, Data] nested to any depth. This means it is not possible to
+# infer the type by simply looking at the inheritance hierarchy. The {Puppet::Pops::Types::TypeCalculator} should
+# be used to answer questions about types. The {Puppet::Pops::Types::TypeFactory} should be used to create an instance
+# of a type whenever one is needed.
+#
+# The implementation of the Types model contains methods that are required for the type objects to behave as
+# expected when comparing them and using them as keys in hashes. (No other logic is, or should be included directly in
+# the model's classes).
+#
+# @api public
+#
+module Puppet::Pops::Types
+ extend RGen::MetamodelBuilder::ModuleExtension
+
+ class TypeModelObject < RGen::MetamodelBuilder::MMBase
+ abstract
+ end
+
+ # Base type for all types except {Puppet::Pops::Types::PType PType}, the type of types.
+ # @api public
+ #
+ class PAnyType < TypeModelObject
+ end
+
+ # The type of types.
+ # @api public
+ #
+ class PType < PAnyType
+ contains_one_uni 'type', PAnyType
+ end
+
+ # @api public
+ #
+ class PNilType < PAnyType
+ end
+
+
+ # A type private to the type system that describes "ignored type" - i.e. "I am what you are"
+ # @api private
+ #
+ class PUnitType < PAnyType
+ end
+
+ # @api public
+ #
+ class PDefaultType < PAnyType
+ end
+
+ # A flexible data type, being assignable to its subtypes as well as PArrayType and PHashType with element type assignable to PDataType.
+ #
+ # @api public
+ #
+ class PDataType < PAnyType
+ end
+
+ # A flexible type describing an any? of other types
+ # @api public
+ #
+ class PVariantType < PAnyType
+ contains_many_uni 'types', PAnyType, :lowerBound => 1
+ end
+
+ # Type that is PDataType compatible, but is not a PCollectionType.
+ # @api public
+ #
+ class PScalarType < PAnyType
+ end
+
+ # A string type describing the set of strings having one of the given values
+ # @api public
+ #
+ class PEnumType < PScalarType
+ has_many_attr 'values', String, :lowerBound => 1
+ end
+
+ # @api public
+ #
+ class PNumericType < PScalarType
+ end
+
+ # @api public
+ #
+ class PIntegerType < PNumericType
+ has_attr 'from', Integer, :lowerBound => 0
+ has_attr 'to', Integer, :lowerBound => 0
+ end
+
+ # @api public
+ #
+ class PFloatType < PNumericType
+ has_attr 'from', Float, :lowerBound => 0
+ has_attr 'to', Float, :lowerBound => 0
+ end
+
+ # @api public
+ #
+ class PStringType < PScalarType
+ has_many_attr 'values', String, :lowerBound => 0, :upperBound => -1, :unique => true
+ contains_one_uni 'size_type', PIntegerType
+ end
+
+ # @api public
+ #
+ class PRegexpType < PScalarType
+ has_attr 'pattern', String, :lowerBound => 1
+ has_attr 'regexp', Object, :derived => true
+ end
+
+ # Represents a subtype of String that narrows the string to those matching the patterns
+ # If specified without a pattern it is basically the same as the String type.
+ #
+ # @api public
+ #
+ class PPatternType < PScalarType
+ contains_many_uni 'patterns', PRegexpType
+ end
+
+ # @api public
+ #
+ class PBooleanType < PScalarType
+ end
+
+ # @api public
+ #
+ class PCollectionType < PAnyType
+ contains_one_uni 'element_type', PAnyType
+ contains_one_uni 'size_type', PIntegerType
+ end
+
+ # @api public
+ #
+ class PStructElement < TypeModelObject
+ has_attr 'name', String, :lowerBound => 1
+ contains_one_uni 'type', PAnyType
+ end
+
+ # @api public
+ #
+ class PStructType < PAnyType
+ contains_many_uni 'elements', PStructElement, :lowerBound => 1
+ has_attr 'hashed_elements', Object, :derived => true
+ end
+
+ # @api public
+ #
+ class PTupleType < PAnyType
+ contains_many_uni 'types', PAnyType, :lowerBound => 1
+ # If set, describes min and max required of the given types - if max > size of
+ # types, the last type entry repeats
+ #
+ contains_one_uni 'size_type', PIntegerType, :lowerBound => 0
+ end
+
+ # @api public
+ #
+ class PCallableType < PAnyType
+ # Types of parameters as a Tuple with required/optional count, or an Integer with min (required), max count
+ contains_one_uni 'param_types', PAnyType, :lowerBound => 1
+
+ # Although being an abstract type reference, only Callable, or all Callables wrapped in
+ # Optional or Variant are supported
+ # If not set, the meaning is that block is not supported.
+ #
+ contains_one_uni 'block_type', PAnyType, :lowerBound => 0
+ end
+
+ # @api public
+ #
+ class PArrayType < PCollectionType
+ end
+
+ # @api public
+ #
+ class PHashType < PCollectionType
+ contains_one_uni 'key_type', PAnyType
+ end
+
+ RuntimeEnum = RGen::MetamodelBuilder::DataTypes::Enum.new(
+ :name => 'RuntimeEnum',
+ :literals => [:'ruby', ])
+
+ # @api public
+ #
+ class PRuntimeType < PAnyType
+ has_attr 'runtime', RuntimeEnum, :lowerBound => 1
+ has_attr 'runtime_type_name', String
+ end
+
+ # Abstract representation of a type that can be placed in a Catalog.
+ # @api public
+ #
+ class PCatalogEntryType < PAnyType
+ end
+
+ # Represents a (host-) class in the Puppet Language.
+ # @api public
+ #
+ class PHostClassType < PCatalogEntryType
+ has_attr 'class_name', String
+ end
+
+ # Represents a Resource Type in the Puppet Language
+ # @api public
+ #
+ class PResourceType < PCatalogEntryType
+ has_attr 'type_name', String
+ has_attr 'title', String
+ end
+
+ # Represents a type that accept PNilType instead of the type parameter
+ # required_type - is a short hand for Variant[T, Undef]
+ # @api public
+ #
+ class POptionalType < PAnyType
+ contains_one_uni 'optional_type', PAnyType
+ end
+
+end
diff --git a/lib/puppet/pops/utils.rb b/lib/puppet/pops/utils.rb
index 45e166984..7fd98c58f 100644
--- a/lib/puppet/pops/utils.rb
+++ b/lib/puppet/pops/utils.rb
@@ -7,8 +7,8 @@ module Puppet::Pops::Utils
# and check if value is nil.
def self.is_numeric?(o)
case o
- when Numeric, Integer, Fixnum, Float
- !!o
+ when Numeric
+ true
else
!!Puppet::Pops::Patterns::NUMERIC.match(relativize_name(o.to_s))
end
@@ -17,6 +17,7 @@ module Puppet::Pops::Utils
# To Numeric with radix, or nil if not a number.
# If the value is already Numeric it is returned verbatim with a radix of 10.
# @param o [String, Number] a string containing a number in octal, hex, integer (decimal) or floating point form
+ # with optional sign +/-
# @return [Array<Number, Integer>, nil] array with converted number and radix, or nil if not possible to convert
# @api public
#
@@ -27,23 +28,23 @@ module Puppet::Pops::Utils
match = Puppet::Pops::Patterns::NUMERIC.match(relativize_name(o))
if !match
nil
- elsif match[3].to_s.length > 0
+ elsif match[5].to_s.length > 0
# Use default radix (default is decimal == 10) for floats
- [Float(match[0]), 10]
+ match[1] == '-' ? [-Float(match[2]), 10] : [Float(match[2]), 10]
else
# Set radix (default is decimal == 10)
radix = 10
- if match[1].to_s.length > 0
+ if match[3].to_s.length > 0
radix = 16
- elsif match[2].to_s.length > 1 && match[2][0,1] == '0'
+ elsif match[4].to_s.length > 1 && match[4][0,1] == '0'
radix = 8
end
# Ruby 1.8.7 does not have a second argument to Kernel method that creates an
# integer from a string, it relies on the prefix 0x, 0X, 0 (and unsupported in puppet binary 'b')
- # We have the correct string here, match[0] is safe to parse without passing on radix
- [Integer(match[0]), radix]
+ # We have the correct string here, match[2] is safe to parse without passing on radix
+ match[1] == '-' ? [-Integer(match[2]), radix] : [Integer(match[2]), radix]
end
- when Numeric, Fixnum, Integer, Float
+ when Numeric
# Impossible to calculate radix, assume decimal
[o, 10]
else
@@ -55,7 +56,8 @@ module Puppet::Pops::Utils
end
# To Numeric (or already numeric)
- # Returns nil if value is not numeric, else an Integer or Float
+ # Returns nil if value is not numeric, else an Integer or Float. A String may have an optional sign.
+ #
# A leading '::' is accepted (and ignored)
#
def self.to_n o
@@ -65,12 +67,12 @@ module Puppet::Pops::Utils
match = Puppet::Pops::Patterns::NUMERIC.match(relativize_name(o))
if !match
nil
- elsif match[3].to_s.length > 0
- Float(match[0])
+ elsif match[5].to_s.length > 0
+ match[1] == '-' ? -Float(match[2]) : Float(match[2])
else
- Integer(match[0])
+ match[1] == '-' ? -Integer(match[2]) : Integer(match[2])
end
- when Numeric, Fixnum, Integer, Float
+ when Numeric
o
else
nil
diff --git a/lib/puppet/pops/validation/checker3_1.rb b/lib/puppet/pops/validation/checker3_1.rb
deleted file mode 100644
index 77f643fb1..000000000
--- a/lib/puppet/pops/validation/checker3_1.rb
+++ /dev/null
@@ -1,558 +0,0 @@
-# A Validator validates a model.
-#
-# Validation is performed on each model element in isolation. Each method should validate the model element's state
-# but not validate its referenced/contained elements except to check their validity in their respective role.
-# The intent is to drive the validation with a tree iterator that visits all elements in a model.
-#
-#
-# TODO: Add validation of multiplicities - this is a general validation that can be checked for all
-# Model objects via their metamodel. (I.e an extra call to multiplicity check in polymorph check).
-# This is however mostly valuable when validating model to model transformations, and is therefore T.B.D
-#
-class Puppet::Pops::Validation::Checker3_1
- Issues = Puppet::Pops::Issues
- Model = Puppet::Pops::Model
-
- attr_reader :acceptor
- # Initializes the validator with a diagnostics producer. This object must respond to
- # `:will_accept?` and `:accept`.
- #
- def initialize(diagnostics_producer)
- @@check_visitor ||= Puppet::Pops::Visitor.new(nil, "check", 0, 0)
- @@rvalue_visitor ||= Puppet::Pops::Visitor.new(nil, "rvalue", 0, 0)
- @@hostname_visitor ||= Puppet::Pops::Visitor.new(nil, "hostname", 1, 2)
- @@assignment_visitor ||= Puppet::Pops::Visitor.new(nil, "assign", 0, 1)
- @@query_visitor ||= Puppet::Pops::Visitor.new(nil, "query", 0, 0)
- @@top_visitor ||= Puppet::Pops::Visitor.new(nil, "top", 1, 1)
- @@relation_visitor ||= Puppet::Pops::Visitor.new(nil, "relation", 1, 1)
-
- @acceptor = diagnostics_producer
- end
-
- # Validates the entire model by visiting each model element and calling `check`.
- # The result is collected (or acted on immediately) by the configured diagnostic provider/acceptor
- # given when creating this Checker.
- #
- def validate(model)
- # tree iterate the model, and call check for each element
- check(model)
- model.eAllContents.each {|m| check(m) }
- end
-
- # Performs regular validity check
- def check(o)
- @@check_visitor.visit_this_0(self, o)
- end
-
- # Performs check if this is a vaid hostname expression
- # @param single_feature_name [String, nil] the name of a single valued hostname feature of the value's container. e.g. 'parent'
- def hostname(o, semantic, single_feature_name = nil)
- @@hostname_visitor.visit_this_2(self, o, semantic, single_feature_name)
- end
-
- # Performs check if this is valid as a query
- def query(o)
- @@query_visitor.visit_this_0(self, o)
- end
-
- # Performs check if this is valid as a relationship side
- def relation(o, container)
- @@relation_visitor.visit_this_1(self, o, container)
- end
-
- # Performs check if this is valid as a rvalue
- def rvalue(o)
- @@rvalue_visitor.visit_this_0(self, o)
- end
-
- # Performs check if this is valid as a container of a definition (class, define, node)
- def top(o, definition)
- @@top_visitor.visit_this_1(self, o, definition)
- end
-
- # Checks the LHS of an assignment (is it assignable?).
- # If args[0] is true, assignment via index is checked.
- #
- def assign(o, via_index = false)
- @@assignment_visitor.visit_this_1(self, o, via_index)
- end
-
- #---ASSIGNMENT CHECKS
-
- def assign_VariableExpression(o, via_index)
- varname_string = varname_to_s(o.expr)
- if varname_string =~ /^[0-9]+$/
- acceptor.accept(Issues::ILLEGAL_NUMERIC_ASSIGNMENT, o, :varname => varname_string)
- end
- # Can not assign to something in another namespace (i.e. a '::' in the name is not legal)
- if acceptor.will_accept? Issues::CROSS_SCOPE_ASSIGNMENT
- if varname_string =~ /::/
- acceptor.accept(Issues::CROSS_SCOPE_ASSIGNMENT, o, :name => varname_string)
- end
- end
- # TODO: Could scan for reassignment of the same variable if done earlier in the same container
- # Or if assigning to a parameter (more work).
- # TODO: Investigate if there are invalid cases for += assignment
- end
-
- def assign_AccessExpression(o, via_index)
- # Are indexed assignments allowed at all ? $x[x] = '...'
- if acceptor.will_accept? Issues::ILLEGAL_INDEXED_ASSIGNMENT
- acceptor.accept(Issues::ILLEGAL_INDEXED_ASSIGNMENT, o)
- else
- # Then the left expression must be assignable-via-index
- assign(o.left_expr, true)
- end
- end
-
- def assign_Object(o, via_index)
- # Can not assign to anything else (differentiate if this is via index or not)
- # i.e. 10 = 'hello' vs. 10['x'] = 'hello' (the root is reported as being in error in both cases)
- #
- acceptor.accept(via_index ? Issues::ILLEGAL_ASSIGNMENT_VIA_INDEX : Issues::ILLEGAL_ASSIGNMENT, o)
- end
-
- #---CHECKS
-
- def check_Object(o)
- end
-
- def check_Factory(o)
- check(o.current)
- end
-
- def check_AccessExpression(o)
- # Check multiplicity of keys
- case o.left_expr
- when Model::QualifiedName
- # allows many keys, but the name should really be a QualifiedReference
- acceptor.accept(Issues::DEPRECATED_NAME_AS_TYPE, o, :name => o.left_expr.value)
- when Model::QualifiedReference
- # ok, allows many - this is a resource reference
-
- else
- # i.e. for any other expression that may produce an array or hash
- if o.keys.size > 1
- acceptor.accept(Issues::UNSUPPORTED_RANGE, o, :count => o.keys.size)
- end
- if o.keys.size < 1
- acceptor.accept(Issues::MISSING_INDEX, o)
- end
- end
- end
-
- def check_AssignmentExpression(o)
- acceptor.accept(Issues::UNSUPPORTED_OPERATOR, o, {:operator => o.operator}) unless [:'=', :'+='].include? o.operator
- assign(o.left_expr)
- rvalue(o.right_expr)
- end
-
- # Checks that operation with :+> is contained in a ResourceOverride or Collector.
- #
- # Parent of an AttributeOperation can be one of:
- # * CollectExpression
- # * ResourceOverride
- # * ResourceBody (ILLEGAL this is a regular resource expression)
- # * ResourceDefaults (ILLEGAL)
- #
- def check_AttributeOperation(o)
- if o.operator == :'+>'
- # Append operator use is constrained
- parent = o.eContainer
- unless parent.is_a?(Model::CollectExpression) || parent.is_a?(Model::ResourceOverrideExpression)
- acceptor.accept(Issues::ILLEGAL_ATTRIBUTE_APPEND, o, {:name=>o.attribute_name, :parent=>parent})
- end
- end
- rvalue(o.value_expr)
- end
-
- def check_BinaryExpression(o)
- rvalue(o.left_expr)
- rvalue(o.right_expr)
- end
-
- def check_CallNamedFunctionExpression(o)
- unless o.functor_expr.is_a? Model::QualifiedName
- acceptor.accept(Issues::ILLEGAL_EXPRESSION, o.functor_expr, :feature => 'function name', :container => o)
- end
- end
-
- def check_MethodCallExpression(o)
- unless o.functor_expr.is_a? Model::QualifiedName
- acceptor.accept(Issues::ILLEGAL_EXPRESSION, o.functor_expr, :feature => 'function name', :container => o)
- end
- end
-
- def check_CaseExpression(o)
- # There should only be one LiteralDefault case option value
- # TODO: Implement this check
- end
-
- def check_CollectExpression(o)
- unless o.type_expr.is_a? Model::QualifiedReference
- acceptor.accept(Issues::ILLEGAL_EXPRESSION, o.type_expr, :feature=> 'type name', :container => o)
- end
-
- # If a collect expression tries to collect exported resources and storeconfigs is not on
- # then it will not work... This was checked in the parser previously. This is a runtime checking
- # thing as opposed to a language thing.
- if acceptor.will_accept?(Issues::RT_NO_STORECONFIGS) && o.query.is_a?(Model::ExportedQuery)
- acceptor.accept(Issues::RT_NO_STORECONFIGS, o)
- end
- end
-
- # Only used for function names, grammar should not be able to produce something faulty, but
- # check anyway if model is created programatically (it will fail in transformation to AST for sure).
- def check_NamedAccessExpression(o)
- name = o.right_expr
- unless name.is_a? Model::QualifiedName
- acceptor.accept(Issues::ILLEGAL_EXPRESSION, name, :feature=> 'function name', :container => o.eContainer)
- end
- end
-
- # for 'class' and 'define'
- def check_NamedDefinition(o)
- top(o.eContainer, o)
- if (acceptor.will_accept? Issues::NAME_WITH_HYPHEN) && o.name.include?('-')
- acceptor.accept(Issues::NAME_WITH_HYPHEN, o, {:name => o.name})
- end
- end
-
- def check_ImportExpression(o)
- o.files.each do |f|
- unless f.is_a? Model::LiteralString
- acceptor.accept(Issues::ILLEGAL_EXPRESSION, f, :feature => 'file name', :container => o)
- end
- end
- end
-
- def check_InstanceReference(o)
- # TODO: Original warning is :
- # Puppet.warning addcontext("Deprecation notice: Resource references should now be capitalized")
- # This model element is not used in the egrammar.
- # Either implement checks or deprecate the use of InstanceReference (the same is acheived by
- # transformation of AccessExpression when used where an Instance/Resource reference is allowed.
- #
- end
-
- # Restrictions on hash key are because of the strange key comparisons/and merge rules in the AST evaluation
- # (Even the allowed ones are handled in a strange way).
- #
- def transform_KeyedEntry(o)
- case o.key
- when Model::QualifiedName
- when Model::LiteralString
- when Model::LiteralNumber
- when Model::ConcatenatedString
- else
- acceptor.accept(Issues::ILLEGAL_EXPRESSION, o.key, :feature => 'hash key', :container => o.eContainer)
- end
- end
-
- # A Lambda is a Definition, but it may appear in other scopes that top scope (Which check_Definition asserts).
- #
- def check_LambdaExpression(o)
- end
-
- def check_NodeDefinition(o)
- # Check that hostnames are valid hostnames (or regular expressons)
- hostname(o.host_matches, o)
- hostname(o.parent, o, 'parent') unless o.parent.nil?
- top(o.eContainer, o)
- end
-
- # No checking takes place - all expressions using a QualifiedName need to check. This because the
- # rules are slightly different depending on the container (A variable allows a numeric start, but not
- # other names). This means that (if the lexer/parser so chooses) a QualifiedName
- # can be anything when it represents a Bare Word and evaluates to a String.
- #
- def check_QualifiedName(o)
- end
-
- # Checks that the value is a valid UpperCaseWord (a CLASSREF), and optionally if it contains a hypen.
- # DOH: QualifiedReferences are created with LOWER CASE NAMES at parse time
- def check_QualifiedReference(o)
- # Is this a valid qualified name?
- if o.value !~ Puppet::Pops::Patterns::CLASSREF
- acceptor.accept(Issues::ILLEGAL_CLASSREF, o, {:name=>o.value})
- elsif (acceptor.will_accept? Issues::NAME_WITH_HYPHEN) && o.value.include?('-')
- acceptor.accept(Issues::NAME_WITH_HYPHEN, o, {:name => o.value})
- end
- end
-
- def check_QueryExpression(o)
- query(o.expr) if o.expr # is optional
- end
-
- def relation_Object(o, rel_expr)
- acceptor.accept(Issues::ILLEGAL_EXPRESSION, o, {:feature => o.eContainingFeature, :container => rel_expr})
- end
-
- def relation_AccessExpression(o, rel_expr); end
-
- def relation_CollectExpression(o, rel_expr); end
-
- def relation_VariableExpression(o, rel_expr); end
-
- def relation_LiteralString(o, rel_expr); end
-
- def relation_ConcatenatedStringExpression(o, rel_expr); end
-
- def relation_SelectorExpression(o, rel_expr); end
-
- def relation_CaseExpression(o, rel_expr); end
-
- def relation_ResourceExpression(o, rel_expr); end
-
- def relation_RelationshipExpression(o, rel_expr); end
-
- def check_Parameter(o)
- if o.name =~ /^[0-9]+$/
- acceptor.accept(Issues::ILLEGAL_NUMERIC_PARAMETER, o, :name => o.name)
- end
- end
-
- #relationship_side: resource
- # | resourceref
- # | collection
- # | variable
- # | quotedtext
- # | selector
- # | casestatement
- # | hasharrayaccesses
-
- def check_RelationshipExpression(o)
- relation(o.left_expr, o)
- relation(o.right_expr, o)
- end
-
- def check_ResourceExpression(o)
- # A resource expression must have a lower case NAME as its type e.g. 'file { ... }'
- unless o.type_name.is_a? Model::QualifiedName
- acceptor.accept(Issues::ILLEGAL_EXPRESSION, o.type_name, :feature => 'resource type', :container => o)
- end
-
- # This is a runtime check - the model is valid, but will have runtime issues when evaluated
- # and storeconfigs is not set.
- if acceptor.will_accept?(Issues::RT_NO_STORECONFIGS) && o.exported
- acceptor.accept(Issues::RT_NO_STORECONFIGS_EXPORT, o)
- end
- end
-
- def check_ResourceDefaultsExpression(o)
- if o.form && o.form != :regular
- acceptor.accept(Issues::NOT_VIRTUALIZEABLE, o)
- end
- end
-
- # Transformation of SelectorExpression is limited to certain types of expressions.
- # This is probably due to constraints in the old grammar rather than any real concerns.
- def select_SelectorExpression(o)
- case o.left_expr
- when Model::CallNamedFunctionExpression
- when Model::AccessExpression
- when Model::VariableExpression
- when Model::ConcatenatedString
- else
- acceptor.accept(Issues::ILLEGAL_EXPRESSION, o.left_expr, :feature => 'left operand', :container => o)
- end
- end
-
- def check_UnaryExpression(o)
- rvalue(o.expr)
- end
-
- def check_UnlessExpression(o)
- # TODO: Unless may not have an elsif
- # TODO: 3.x unless may not have an else
- end
-
- def check_VariableExpression(o)
- # The expression must be a qualified name
- if !o.expr.is_a? Model::QualifiedName
- acceptor.accept(Issues::ILLEGAL_EXPRESSION, o, :feature => 'name', :container => o)
- else
- # Note, that if it later becomes illegal with hyphen in any name, this special check
- # can be skipped in favor of the check in QualifiedName, which is now not done if contained in
- # a VariableExpression
- name = o.expr.value
- if (acceptor.will_accept? Issues::VAR_WITH_HYPHEN) && name.include?('-')
- acceptor.accept(Issues::VAR_WITH_HYPHEN, o, {:name => name})
- end
- end
- end
-
- #--- HOSTNAME CHECKS
-
- # Transforms Array of host matching expressions into a (Ruby) array of AST::HostName
- def hostname_Array(o, semantic, single_feature_name)
- if single_feature_name
- acceptor.accept(Issues::ILLEGAL_EXPRESSION, o, {:feature=>single_feature_name, :container=>semantic})
- end
- o.each {|x| hostname(x, semantic, false) }
- end
-
- def hostname_String(o, semantic, single_feature_name)
- # The 3.x checker only checks for illegal characters - if matching /[^-\w.]/ the name is invalid,
- # but this allows pathological names like "a..b......c", "----"
- # TODO: Investigate if more illegal hostnames should be flagged.
- #
- if o =~ Puppet::Pops::Patterns::ILLEGAL_HOSTNAME_CHARS
- acceptor.accept(Issues::ILLEGAL_HOSTNAME_CHARS, semantic, :hostname => o)
- end
- end
-
- def hostname_LiteralValue(o, semantic, single_feature_name)
- hostname_String(o.value.to_s, o, single_feature_name)
- end
-
- def hostname_ConcatenatedString(o, semantic, single_feature_name)
- # Puppet 3.1. only accepts a concatenated string without interpolated expressions
- if the_expr = o.segments.index {|s| s.is_a?(Model::TextExpression) }
- acceptor.accept(Issues::ILLEGAL_HOSTNAME_INTERPOLATION, o.segments[the_expr].expr)
- elsif o.segments.size() != 1
- # corner case, bad model, concatenation of several plain strings
- acceptor.accept(Issues::ILLEGAL_HOSTNAME_INTERPOLATION, o)
- else
- # corner case, may be ok, but lexer may have replaced with plain string, this is
- # here if it does not
- hostname_String(o.segments[0], o.segments[0], false)
- end
- end
-
- def hostname_QualifiedName(o, semantic, single_feature_name)
- hostname_String(o.value.to_s, o, single_feature_name)
- end
-
- def hostname_QualifiedReference(o, semantic, single_feature_name)
- hostname_String(o.value.to_s, o, single_feature_name)
- end
-
- def hostname_LiteralNumber(o, semantic, single_feature_name)
- # always ok
- end
-
- def hostname_LiteralDefault(o, semantic, single_feature_name)
- # always ok
- end
-
- def hostname_LiteralRegularExpression(o, semantic, single_feature_name)
- # always ok
- end
-
- def hostname_Object(o, semantic, single_feature_name)
- acceptor.accept(Issues::ILLEGAL_EXPRESSION, o, {:feature=> single_feature_name || 'hostname', :container=>semantic})
- end
-
- #---QUERY CHECKS
-
- # Anything not explicitly allowed is flagged as error.
- def query_Object(o)
- acceptor.accept(Issues::ILLEGAL_QUERY_EXPRESSION, o)
- end
-
- # Puppet AST only allows == and !=
- #
- def query_ComparisonExpression(o)
- acceptor.accept(Issues::ILLEGAL_QUERY_EXPRESSION, o) unless [:'==', :'!='].include? o.operator
- end
-
- # Allows AND, OR, and checks if left/right are allowed in query.
- def query_BooleanExpression(o)
- query o.left_expr
- query o.right_expr
- end
-
- def query_ParenthesizedExpression(o)
- query(o.expr)
- end
-
- def query_VariableExpression(o); end
-
- def query_QualifiedName(o); end
-
- def query_LiteralNumber(o); end
-
- def query_LiteralString(o); end
-
- def query_LiteralBoolean(o); end
-
- #---RVALUE CHECKS
-
- # By default, all expressions are reported as being rvalues
- # Implement specific rvalue checks for those that are not.
- #
- def rvalue_Expression(o); end
-
- def rvalue_ImportExpression(o) ; acceptor.accept(Issues::NOT_RVALUE, o) ; end
-
- def rvalue_BlockExpression(o) ; acceptor.accept(Issues::NOT_RVALUE, o) ; end
-
- def rvalue_CaseExpression(o) ; acceptor.accept(Issues::NOT_RVALUE, o) ; end
-
- def rvalue_IfExpression(o) ; acceptor.accept(Issues::NOT_RVALUE, o) ; end
-
- def rvalue_UnlessExpression(o) ; acceptor.accept(Issues::NOT_RVALUE, o) ; end
-
- def rvalue_ResourceExpression(o) ; acceptor.accept(Issues::NOT_RVALUE, o) ; end
-
- def rvalue_ResourceDefaultsExpression(o); acceptor.accept(Issues::NOT_RVALUE, o) ; end
-
- def rvalue_ResourceOverrideExpression(o); acceptor.accept(Issues::NOT_RVALUE, o) ; end
-
- def rvalue_CollectExpression(o) ; acceptor.accept(Issues::NOT_RVALUE, o) ; end
-
- def rvalue_Definition(o) ; acceptor.accept(Issues::NOT_RVALUE, o) ; end
-
- def rvalue_NodeDefinition(o) ; acceptor.accept(Issues::NOT_RVALUE, o) ; end
-
- def rvalue_UnaryExpression(o) ; rvalue o.expr ; end
-
- #---TOP CHECK
-
- def top_NilClass(o, definition)
- # ok, reached the top, no more parents
- end
-
- def top_Object(o, definition)
- # fail, reached a container that is not top level
- acceptor.accept(Issues::NOT_TOP_LEVEL, definition)
- end
-
- def top_BlockExpression(o, definition)
- # ok, if this is a block representing the body of a class, or is top level
- top o.eContainer, definition
- end
-
- def top_HostClassDefinition(o, definition)
- # ok, stop scanning parents
- end
-
- def top_Program(o, definition)
- # ok
- end
-
- # A LambdaExpression is a BlockExpression, and this method is needed to prevent the polymorph method for BlockExpression
- # to accept a lambda.
- # A lambda can not iteratively create classes, nodes or defines as the lambda does not have a closure.
- #
- def top_LambdaExpression(o, definition)
- # fail, stop scanning parents
- acceptor.accept(Issues::NOT_TOP_LEVEL, definition)
- end
-
- #--- NON POLYMORPH, NON CHECKING CODE
-
- # Produces string part of something named, or nil if not a QualifiedName or QualifiedReference
- #
- def varname_to_s(o)
- case o
- when Model::QualifiedName
- o.value
- when Model::QualifiedReference
- o.value
- else
- nil
- end
- end
-end
diff --git a/lib/puppet/pops/validation/checker4_0.rb b/lib/puppet/pops/validation/checker4_0.rb
index fa2587620..079710d59 100644
--- a/lib/puppet/pops/validation/checker4_0.rb
+++ b/lib/puppet/pops/validation/checker4_0.rb
@@ -25,6 +25,7 @@ class Puppet::Pops::Validation::Checker4_0
@@query_visitor ||= Puppet::Pops::Visitor.new(nil, "query", 0, 0)
@@top_visitor ||= Puppet::Pops::Visitor.new(nil, "top", 1, 1)
@@relation_visitor ||= Puppet::Pops::Visitor.new(nil, "relation", 0, 0)
+ @@idem_visitor ||= Puppet::Pops::Visitor.new(self, "idem", 0, 0)
@acceptor = diagnostics_producer
end
@@ -77,6 +78,25 @@ class Puppet::Pops::Validation::Checker4_0
@@assignment_visitor.visit_this_1(self, o, via_index)
end
+ # Checks if the expression has side effect ('idem' is latin for 'the same', here meaning that the evaluation state
+ # is known to be unchanged after the expression has been evaluated). The result is not 100% authoritative for
+ # negative answers since analysis of function behavior is not possible.
+ # @return [Boolean] true if expression is known to have no effect on evaluation state
+ #
+ def idem(o)
+ @@idem_visitor.visit_this_0(self, o)
+ end
+
+ # Returns the last expression in a block, or the expression, if that expression is idem
+ def ends_with_idem(o)
+ if o.is_a?(Puppet::Pops::Model::BlockExpression)
+ last = o.statements[-1]
+ idem(last) ? last : nil
+ else
+ idem(o) ? o : nil
+ end
+ end
+
#---ASSIGNMENT CHECKS
def assign_VariableExpression(o, via_index)
@@ -130,9 +150,15 @@ class Puppet::Pops::Validation::Checker4_0
end
def check_AssignmentExpression(o)
- acceptor.accept(Issues::UNSUPPORTED_OPERATOR, o, {:operator => o.operator}) unless [:'=', :'+=', :'-='].include? o.operator
- assign(o.left_expr)
- rvalue(o.right_expr)
+ case o.operator
+ when :'='
+ assign(o.left_expr)
+ rvalue(o.right_expr)
+ when :'+=', :'-='
+ acceptor.accept(Issues::APPENDS_DELETES_NO_LONGER_SUPPORTED, o, {:operator => o.operator})
+ else
+ acceptor.accept(Issues::UNSUPPORTED_OPERATOR, o, {:operator => o.operator})
+ end
end
# Checks that operation with :+> is contained in a ResourceOverride or Collector.
@@ -154,11 +180,31 @@ class Puppet::Pops::Validation::Checker4_0
rvalue(o.value_expr)
end
+ def check_AttributesOperation(o)
+ # Append operator use is constrained
+ parent = o.eContainer
+ parent = parent.eContainer unless parent.nil?
+ unless parent.is_a?(Model::ResourceExpression)
+ acceptor.accept(Issues::UNSUPPORTED_OPERATOR_IN_CONTEXT, o, :operator=>'* =>')
+ end
+
+ rvalue(o.expr)
+ end
+
def check_BinaryExpression(o)
rvalue(o.left_expr)
rvalue(o.right_expr)
end
+ def check_BlockExpression(o)
+ o.statements[0..-2].each do |statement|
+ if idem(statement)
+ acceptor.accept(Issues::IDEM_EXPRESSION_NOT_LAST, statement)
+ break # only flag the first
+ end
+ end
+ end
+
def check_CallNamedFunctionExpression(o)
case o.functor_expr
when Puppet::Pops::Model::QualifiedName
@@ -172,6 +218,12 @@ class Puppet::Pops::Validation::Checker4_0
end
end
+ def check_EppExpression(o)
+ if o.eContainer.is_a?(Puppet::Pops::Model::LambdaExpression)
+ internal_check_no_capture(o.eContainer, o)
+ end
+ end
+
def check_MethodCallExpression(o)
unless o.functor_expr.is_a? Model::QualifiedName
acceptor.accept(Issues::ILLEGAL_EXPRESSION, o.functor_expr, :feature => 'function name', :container => o)
@@ -210,12 +262,84 @@ class Puppet::Pops::Validation::Checker4_0
end
end
+ RESERVED_TYPE_NAMES = {
+ 'type' => true,
+ 'any' => true,
+ 'unit' => true,
+ 'scalar' => true,
+ 'boolean' => true,
+ 'numeric' => true,
+ 'integer' => true,
+ 'float' => true,
+ 'collection' => true,
+ 'array' => true,
+ 'hash' => true,
+ 'tuple' => true,
+ 'struct' => true,
+ 'variant' => true,
+ 'optional' => true,
+ 'enum' => true,
+ 'regexp' => true,
+ 'pattern' => true,
+ 'runtime' => true,
+ }
+
# for 'class', 'define', and function
def check_NamedDefinition(o)
top(o.eContainer, o)
if o.name !~ Puppet::Pops::Patterns::CLASSREF
acceptor.accept(Issues::ILLEGAL_DEFINITION_NAME, o, {:name=>o.name})
end
+
+ if RESERVED_TYPE_NAMES[o.name()]
+ acceptor.accept(Issues::RESERVED_TYPE_NAME, o, {:name => o.name})
+ end
+
+ if violator = ends_with_idem(o.body)
+ acceptor.accept(Issues::IDEM_NOT_ALLOWED_LAST, violator, {:container => o})
+ end
+ end
+
+ def check_HostClassDefinition(o)
+ check_NamedDefinition(o)
+ internal_check_no_capture(o)
+ internal_check_reserved_params(o)
+ end
+
+ def check_ResourceTypeDefinition(o)
+ check_NamedDefinition(o)
+ internal_check_no_capture(o)
+ internal_check_reserved_params(o)
+ end
+
+ def internal_check_capture_last(o)
+ accepted_index = o.parameters.size() -1
+ o.parameters.each_with_index do |p, index|
+ if p.captures_rest && index != accepted_index
+ acceptor.accept(Issues::CAPTURES_REST_NOT_LAST, p, {:param_name => p.name})
+ end
+ end
+ end
+
+ def internal_check_no_capture(o, container = o)
+ o.parameters.each do |p|
+ if p.captures_rest
+ acceptor.accept(Issues::CAPTURES_REST_NOT_SUPPORTED, p, {:container => container, :param_name => p.name})
+ end
+ end
+ end
+
+ RESERVED_PARAMETERS = {
+ 'name' => true,
+ 'title' => true,
+ }
+
+ def internal_check_reserved_params(o)
+ o.parameters.each do |p|
+ if RESERVED_PARAMETERS[p.name]
+ acceptor.accept(Issues::RESERVED_PARAMETER, p, {:container => o, :param_name => p.name})
+ end
+ end
end
def check_IfExpression(o)
@@ -229,9 +353,8 @@ class Puppet::Pops::Validation::Checker4_0
# acceptor.accept(Issues::ILLEGAL_EXPRESSION, o.key, :feature => 'hash key', :container => o.eContainer)
end
- # A Lambda is a Definition, but it may appear in other scopes than top scope (Which check_Definition asserts).
- #
def check_LambdaExpression(o)
+ internal_check_capture_last(o)
end
def check_LiteralList(o)
@@ -243,6 +366,12 @@ class Puppet::Pops::Validation::Checker4_0
hostname(o.host_matches, o)
hostname(o.parent, o, 'parent') unless o.parent.nil?
top(o.eContainer, o)
+ if violator = ends_with_idem(o.body)
+ acceptor.accept(Issues::IDEM_NOT_ALLOWED_LAST, violator, {:container => o})
+ end
+ unless o.parent.nil?
+ acceptor.accept(Issues::ILLEGAL_NODE_INHERITANCE, o.parent)
+ end
end
# No checking takes place - all expressions using a QualifiedName need to check. This because the
@@ -275,7 +404,7 @@ class Puppet::Pops::Validation::Checker4_0
def relation_RelationshipExpression(o); end
def check_Parameter(o)
- if o.name =~ /^[0-9]+$/
+ if o.name =~ /^(?:0x)?[0-9]+$/
acceptor.accept(Issues::ILLEGAL_NUMERIC_PARAMETER, o, :name => o.name)
end
end
@@ -295,24 +424,41 @@ class Puppet::Pops::Validation::Checker4_0
end
def check_ResourceExpression(o)
- # A resource expression must have a lower case NAME as its type e.g. 'file { ... }'
- unless o.type_name.is_a? Model::QualifiedName
- acceptor.accept(Issues::ILLEGAL_EXPRESSION, o.type_name, :feature => 'resource type', :container => o)
+ # The expression for type name cannot be statically checked - this is instead done at runtime
+ # to enable better error message of the result of the expression rather than the static instruction.
+ # (This can be revised as there are static constructs that are illegal, but require updating many
+ # tests that expect the detailed reporting).
+ end
+
+ def check_ResourceBody(o)
+ seenUnfolding = false
+ o.operations.each do |ao|
+ if ao.is_a?(Puppet::Pops::Model::AttributesOperation)
+ if seenUnfolding
+ acceptor.accept(Issues::MULTIPLE_ATTRIBUTES_UNFOLD, ao)
+ else
+ seenUnfolding = true
+ end
+ end
end
+ end
- # This is a runtime check - the model is valid, but will have runtime issues when evaluated
- # and storeconfigs is not set.
- if acceptor.will_accept?(Issues::RT_NO_STORECONFIGS) && o.exported
- acceptor.accept(Issues::RT_NO_STORECONFIGS_EXPORT, o)
+ def check_ResourceDefaultsExpression(o)
+ if o.form && o.form != :regular
+ acceptor.accept(Issues::NOT_VIRTUALIZEABLE, o)
end
end
- def check_ResourceDefaultsExpression(o)
+ def check_ResourceOverrideExpression(o)
if o.form && o.form != :regular
acceptor.accept(Issues::NOT_VIRTUALIZEABLE, o)
end
end
+ def check_ReservedWord(o)
+ acceptor.accept(Issues::RESERVED_WORD, o, :word => o.word)
+ end
+
def check_SelectorExpression(o)
rvalue(o.left_expr)
end
@@ -452,10 +598,6 @@ class Puppet::Pops::Validation::Checker4_0
#
def rvalue_Expression(o); end
- def rvalue_ResourceDefaultsExpression(o); acceptor.accept(Issues::NOT_RVALUE, o) ; end
-
- def rvalue_ResourceOverrideExpression(o); acceptor.accept(Issues::NOT_RVALUE, o) ; end
-
def rvalue_CollectExpression(o) ; acceptor.accept(Issues::NOT_RVALUE, o) ; end
def rvalue_Definition(o) ; acceptor.accept(Issues::NOT_RVALUE, o) ; end
@@ -497,6 +639,110 @@ class Puppet::Pops::Validation::Checker4_0
acceptor.accept(Issues::NOT_TOP_LEVEL, definition)
end
+ #--IDEM CHECK
+ def idem_Object(o)
+ false
+ end
+
+ def idem_Nop(o)
+ true
+ end
+
+ def idem_NilClass(o)
+ true
+ end
+
+ def idem_Literal(o)
+ true
+ end
+
+ def idem_LiteralList(o)
+ true
+ end
+
+ def idem_LiteralHash(o)
+ true
+ end
+
+ def idem_Factory(o)
+ idem(o.current)
+ end
+
+ def idem_AccessExpression(o)
+ true
+ end
+
+ def idem_BinaryExpression(o)
+ true
+ end
+
+ def idem_RelationshipExpression(o)
+ # Always side effect
+ false
+ end
+
+ def idem_AssignmentExpression(o)
+ # Always side effect
+ false
+ end
+
+ # Handles UnaryMinusExpression, NotExpression, VariableExpression
+ def idem_UnaryExpression(o)
+ true
+ end
+
+ # Allow (no-effect parentheses) to be used around a productive expression
+ def idem_ParenthesizedExpression(o)
+ idem(o.expr)
+ end
+
+ def idem_RenderExpression(o)
+ false
+ end
+
+ def idem_RenderStringExpression(o)
+ false
+ end
+
+ def idem_BlockExpression(o)
+ # productive if there is at least one productive expression
+ ! o.statements.any? {|expr| !idem(expr) }
+ end
+
+ # Returns true even though there may be interpolated expressions that have side effect.
+ # Report as idem anyway, as it is very bad design to evaluate an interpolated string for its
+ # side effect only.
+ def idem_ConcatenatedString(o)
+ true
+ end
+
+ # Heredoc is just a string, but may contain interpolated string (which may have side effects).
+ # This is still bad design and should be reported as idem.
+ def idem_HeredocExpression(o)
+ true
+ end
+
+ # May technically have side effects inside the Selector, but this is bad design - treat as idem
+ def idem_SelectorExpression(o)
+ true
+ end
+
+ def idem_IfExpression(o)
+ [o.test, o.then_expr, o.else_expr].all? {|e| idem(e) }
+ end
+
+ # Case expression is idem, if test, and all options are idem
+ def idem_CaseExpression(o)
+ return false if !idem(o.test)
+ ! o.options.any? {|opt| !idem(opt) }
+ end
+
+ # An option is idem if values and the then_expression are idem
+ def idem_CaseOption(o)
+ return false if o.values.any? { |value| !idem(value) }
+ idem(o.then_expr)
+ end
+
#--- NON POLYMORPH, NON CHECKING CODE
# Produces string part of something named, or nil if not a QualifiedName or QualifiedReference
diff --git a/lib/puppet/pops/validation/validator_factory_3_1.rb b/lib/puppet/pops/validation/validator_factory_3_1.rb
deleted file mode 100644
index 738eac404..000000000
--- a/lib/puppet/pops/validation/validator_factory_3_1.rb
+++ /dev/null
@@ -1,31 +0,0 @@
-# Configures validation suitable for 3.1 + iteration
-#
-class Puppet::Pops::Validation::ValidatorFactory_3_1 < Puppet::Pops::Validation::Factory
- Issues = Puppet::Pops::Issues
-
- # Produces the checker to use
- def checker diagnostic_producer
- Puppet::Pops::Validation::Checker3_1.new(diagnostic_producer)
- end
-
- # Produces the label provider to use
- def label_provider
- Puppet::Pops::Model::ModelLabelProvider.new()
- end
-
- # Produces the severity producer to use
- def severity_producer
- p = super
-
- # Configure each issue that should **not** be an error
- #
- # Validate as per the current runtime configuration
- p[Issues::RT_NO_STORECONFIGS_EXPORT] = Puppet[:storeconfigs] ? :ignore : :warning
- p[Issues::RT_NO_STORECONFIGS] = Puppet[:storeconfigs] ? :ignore : :warning
-
- p[Issues::NAME_WITH_HYPHEN] = :deprecation
- p[Issues::DEPRECATED_NAME_AS_TYPE] = :deprecation
-
- p
- end
-end
diff --git a/lib/puppet/pops/validation/validator_factory_4_0.rb b/lib/puppet/pops/validation/validator_factory_4_0.rb
index 7eae59351..783164016 100644
--- a/lib/puppet/pops/validation/validator_factory_4_0.rb
+++ b/lib/puppet/pops/validation/validator_factory_4_0.rb
@@ -24,7 +24,6 @@ class Puppet::Pops::Validation::ValidatorFactory_4_0 < Puppet::Pops::Validation:
p[Issues::RT_NO_STORECONFIGS] = Puppet[:storeconfigs] ? :ignore : :warning
p[Issues::NAME_WITH_HYPHEN] = :error
- p[Issues::DEPRECATED_NAME_AS_TYPE] = :error
p[Issues::EMPTY_RESOURCE_SPECIALIZATION] = :ignore
p
end
diff --git a/lib/puppet/pops/visitor.rb b/lib/puppet/pops/visitor.rb
index c35fa0b93..baae887f7 100644
--- a/lib/puppet/pops/visitor.rb
+++ b/lib/puppet/pops/visitor.rb
@@ -86,107 +86,4 @@ class Puppet::Pops::Visitor
visit_this(receiver, thing, arg1, arg2, arg3)
end
- # This is an alternative implementation that separates the finding of method names
- # (Cached in the Visitor2 class), and bound methods (in an inner Delegator class) that
- # are cached for this receiver instance. This is based on micro benchmarks measuring that a send is slower
- # that directly calling a bound method.
- # Larger benchmark however show that the overhead is fractional. Additional (larger) tests may
- # show otherwise.
- # To use this class instead of the regular Visitor.
- # @@the_visitor_c = Visitor2.new(...)
- # @@the_visitor = @@the_visitor_c.instance(self)
- # then visit with one of the Delegator's visit methods.
- #
- # Performance Note: there are still issues with this implementation (although cleaner) since it requires
- # holding on to the first instance in order to compute respond_do?. This is required if the class
- # is using method_missing? which cannot be computed by introspection of the class (which would be
- # ideal). Another approach is to pre-scan all the available methods starting with the pattern for
- # the visitor, scan the class, and just check if the class has this method. (This will not work
- # for dispatch to methods that requires method missing. (Maybe that does not matter)
- # Further experiments could try looking up unbound methods via the class, cloning and binding them
- # instead of again looking them up with #method(name)
- # Also note that this implementation does not check min/max args on each call - there was not much gain
- # from skipping this. It is safe to skip, but produces less friendly errors if there is an error in the
- # implementation.
- #
- class Visitor2
- attr_reader :receiver, :message, :min_args, :max_args, :cache
-
- def initialize(receiver, message, min_args=0, max_args=nil)
- raise ArgumentError.new("receiver can not be nil") if receiver.nil?
- raise ArgumentError.new("min_args must be >= 0") if min_args < 0
- raise ArgumentError.new("max_args must be >= min_args or nil") if max_args && max_args < min_args
-
- @receiver = receiver
- @message = message
- @min_args = min_args
- @max_args = max_args
- @cache = Hash.new
- end
-
- def instance(receiver)
- # Create a visitable instance for the receiver
- Delegator.new(receiver, self)
- end
-
- # Produce the name of the method to use
- # @return [Symbol, nil] the method name symbol, or nil if there is no method to call for thing
- #
- def method_name_for(thing)
- if method_name = @cache[thing.class]
- return method_name
- else
- thing.class.ancestors().each do |ancestor|
- method_name = :"#{@message}_#{ancestor.name.split(/::/).last}"
- next unless receiver.respond_to?(method_name, true)
- @cache[thing.class] = method_name
- return method_name
- end
- end
- end
-
- class Delegator
- attr_reader :receiver, :visitor, :cache
- def initialize(receiver, visitor)
- @receiver = receiver
- @visitor = visitor
- @cache = Hash.new
- end
-
- # Visit
- def visit(thing, *args)
- if method = @cache[thing.class]
- return method.call(thing, *args)
- else
- method_name = visitor.method_name_for(thing)
- method = receiver.method(method_name)
- unless method
- raise "Visitor Error: the configured receiver (#{receiver.class}) can't handle instance of: #{thing.class}"
- end
- @cache[thing.class] = method
- method.call(thing, *args)
- end
- end
-
- # Visit an explicit receiver with 0 args
- # (This is ~30% faster than calling the general method)
- #
- def visit_0(thing)
- (method = @cache[thing.class]) ? method.call(thing) : visit(thing)
- end
-
- def visit_1(thing, arg)
- (method = @cache[thing.class]) ? method.call(thing, arg) : visit(thing, arg)
- end
-
- def visit_2(thing, arg1, arg2)
- (method = @cache[thing.class]) ? method.call(thing, arg1, arg2) : visit(thing, arg1, arg2)
- end
-
- def visit_3(thing, arg1, arg2, arg3)
- (method = @cache[thing.class]) ? method.call(thing, arg1, arg2, arg3) : visit(thing, arg1, arg2, arg3)
- end
-
- end
- end
end
diff --git a/lib/puppet/provider/exec.rb b/lib/puppet/provider/exec.rb
index e5b90b6e2..70bcda8c0 100644
--- a/lib/puppet/provider/exec.rb
+++ b/lib/puppet/provider/exec.rb
@@ -47,12 +47,21 @@ class Puppet::Provider::Exec < Puppet::Provider
end
end
+ if Puppet.features.microsoft_windows?
+ exec_user = resource[:user]
+ # Etc.getpwuid() returns nil on Windows
+ elsif resource.current_username == resource[:user]
+ exec_user = nil
+ else
+ exec_user = resource[:user]
+ end
+
Timeout::timeout(resource[:timeout]) do
# note that we are passing "false" for the "override_locale" parameter, which ensures that the user's
# default/system locale will be respected. Callers may override this behavior by setting locale-related
# environment variables (LANG, LC_ALL, etc.) in their 'environment' configuration.
output = Puppet::Util::Execution.execute(command, :failonfail => false, :combine => true,
- :uid => resource[:user], :gid => resource[:group],
+ :uid => exec_user, :gid => resource[:group],
:override_locale => false,
:custom_environment => environment)
end
diff --git a/lib/puppet/provider/file/windows.rb b/lib/puppet/provider/file/windows.rb
index 5c8038db1..c0ef68cd9 100644
--- a/lib/puppet/provider/file/windows.rb
+++ b/lib/puppet/provider/file/windows.rb
@@ -8,20 +8,19 @@ Puppet::Type.type(:file).provide :windows do
if Puppet.features.microsoft_windows?
require 'puppet/util/windows'
- require 'puppet/util/adsi'
include Puppet::Util::Windows::Security
end
# Determine if the account is valid, and if so, return the UID
def name2id(value)
- Puppet::Util::Windows::Security.name_to_sid(value)
+ Puppet::Util::Windows::SID.name_to_sid(value)
end
# If it's a valid SID, get the name. Otherwise, it's already a name,
# so just return it.
def id2name(id)
- if Puppet::Util::Windows::Security.valid_sid?(id)
- Puppet::Util::Windows::Security.sid_to_name(id)
+ if Puppet::Util::Windows::SID.valid_sid?(id)
+ Puppet::Util::Windows::SID.sid_to_name(id)
else
id
end
diff --git a/lib/puppet/provider/group/windows_adsi.rb b/lib/puppet/provider/group/windows_adsi.rb
index 56c0c175b..c6db2af92 100644
--- a/lib/puppet/provider/group/windows_adsi.rb
+++ b/lib/puppet/provider/group/windows_adsi.rb
@@ -1,4 +1,4 @@
-require 'puppet/util/adsi'
+require 'puppet/util/windows'
Puppet::Type.type(:group).provide :windows_adsi do
desc "Local group management for Windows. Group members can be both users and groups.
@@ -22,15 +22,15 @@ Puppet::Type.type(:group).provide :windows_adsi do
return false if current.empty? != should_empty
# dupes automatically weeded out when hashes built
- Puppet::Util::ADSI::Group.name_sid_hash(current) == Puppet::Util::ADSI::Group.name_sid_hash(should)
+ Puppet::Util::Windows::ADSI::Group.name_sid_hash(current) == Puppet::Util::Windows::ADSI::Group.name_sid_hash(should)
end
def members_to_s(users)
return '' if users.nil? or !users.kind_of?(Array)
users = users.map do |user_name|
- sid = Puppet::Util::Windows::Security.name_to_sid_object(user_name)
+ sid = Puppet::Util::Windows::SID.name_to_sid_object(user_name)
if sid.account =~ /\\/
- account, _ = Puppet::Util::ADSI::User.parse_name(sid.account)
+ account, _ = Puppet::Util::Windows::ADSI::User.parse_name(sid.account)
else
account = sid.account
end
@@ -41,7 +41,7 @@ Puppet::Type.type(:group).provide :windows_adsi do
end
def group
- @group ||= Puppet::Util::ADSI::Group.new(@resource[:name])
+ @group ||= Puppet::Util::Windows::ADSI::Group.new(@resource[:name])
end
def members
@@ -53,18 +53,18 @@ Puppet::Type.type(:group).provide :windows_adsi do
end
def create
- @group = Puppet::Util::ADSI::Group.create(@resource[:name])
+ @group = Puppet::Util::Windows::ADSI::Group.create(@resource[:name])
@group.commit
self.members = @resource[:members]
end
def exists?
- Puppet::Util::ADSI::Group.exists?(@resource[:name])
+ Puppet::Util::Windows::ADSI::Group.exists?(@resource[:name])
end
def delete
- Puppet::Util::ADSI::Group.delete(@resource[:name])
+ Puppet::Util::Windows::ADSI::Group.delete(@resource[:name])
end
# Only flush if we created or modified a group, not deleted
@@ -73,7 +73,7 @@ Puppet::Type.type(:group).provide :windows_adsi do
end
def gid
- Puppet::Util::Windows::Security.name_to_sid(@resource[:name])
+ Puppet::Util::Windows::SID.name_to_sid(@resource[:name])
end
def gid=(value)
@@ -81,6 +81,6 @@ Puppet::Type.type(:group).provide :windows_adsi do
end
def self.instances
- Puppet::Util::ADSI::Group.map { |g| new(:ensure => :present, :name => g.name) }
+ Puppet::Util::Windows::ADSI::Group.map { |g| new(:ensure => :present, :name => g.name) }
end
end
diff --git a/lib/puppet/provider/nameservice/directoryservice.rb b/lib/puppet/provider/nameservice/directoryservice.rb
index 0dd4113a0..9f3108911 100644
--- a/lib/puppet/provider/nameservice/directoryservice.rb
+++ b/lib/puppet/provider/nameservice/directoryservice.rb
@@ -90,9 +90,6 @@ class Puppet::Provider::NameService::DirectoryService < Puppet::Provider::NameSe
def self.get_macosx_version_major
return @macosx_version_major if defined?(@macosx_version_major)
begin
- # Make sure we've loaded all of the facts
- Facter.loadfacts
-
product_version_major = Facter.value(:macosx_productversion_major)
fail("#{product_version_major} is not supported by the directoryservice provider") if %w{10.0 10.1 10.2 10.3 10.4}.include?(product_version_major)
@@ -178,7 +175,9 @@ class Puppet::Provider::NameService::DirectoryService < Puppet::Provider::NameSe
end
def self.fail_if_wrong_version
- fail("Puppet does not support OS X versions < 10.5") unless self.get_macosx_version_major >= "10.5"
+ if (Puppet::Util::Package.versioncmp(self.get_macosx_version_major, '10.5') == -1)
+ fail("Puppet does not support OS X versions < 10.5")
+ end
end
def self.get_exec_preamble(ds_action, resource_name = nil)
diff --git a/lib/puppet/provider/package/apt.rb b/lib/puppet/provider/package/apt.rb
index 63187cdda..f43f51446 100644
--- a/lib/puppet/provider/package/apt.rb
+++ b/lib/puppet/provider/package/apt.rb
@@ -2,7 +2,11 @@ Puppet::Type.type(:package).provide :apt, :parent => :dpkg, :source => :dpkg do
# Provide sorting functionality
include Puppet::Util::Package
- desc "Package management via `apt-get`."
+ desc "Package management via `apt-get`.
+
+ This provider supports the `install_options` attribute, which allows command-line flags to be passed to apt-get.
+ These options should be specified as a string (e.g. '--flag'), a hash (e.g. {'--flag' => 'value'}),
+ or an array where each element is either a string or a hash."
has_feature :versionable, :install_options
diff --git a/lib/puppet/provider/package/gem.rb b/lib/puppet/provider/package/gem.rb
index 380e81ef7..7cd61f4f4 100644
--- a/lib/puppet/provider/package/gem.rb
+++ b/lib/puppet/provider/package/gem.rb
@@ -6,7 +6,11 @@ Puppet::Type.type(:package).provide :gem, :parent => Puppet::Provider::Package d
desc "Ruby Gem support. If a URL is passed via `source`, then that URL is used as the
remote gem repository; if a source is present but is not a valid URL, it will be
interpreted as the path to a local gem file. If source is not present at all,
- the gem will be installed from the default gem repositories."
+ the gem will be installed from the default gem repositories.
+
+ This provider supports the `install_options` attribute, which allows command-line flags to be passed to the gem command.
+ These options should be specified as a string (e.g. '--flag'), a hash (e.g. {'--flag' => 'value'}),
+ or an array where each element is either a string or a hash."
has_feature :versionable, :install_options
@@ -24,7 +28,7 @@ Puppet::Type.type(:package).provide :gem, :parent => Puppet::Provider::Package d
gem_list_command << "--source" << options[:source]
end
if name = options[:justme]
- gem_list_command << name + "$"
+ gem_list_command << "^" + name + "$"
end
begin
@@ -128,4 +132,4 @@ Puppet::Type.type(:package).provide :gem, :parent => Puppet::Provider::Package d
def install_options
join_options(resource[:install_options])
end
-end \ No newline at end of file
+end
diff --git a/lib/puppet/provider/package/openbsd.rb b/lib/puppet/provider/package/openbsd.rb
index 4437537c8..acf9b42f0 100644
--- a/lib/puppet/provider/package/openbsd.rb
+++ b/lib/puppet/provider/package/openbsd.rb
@@ -2,9 +2,16 @@ require 'puppet/provider/package'
# Packaging on OpenBSD. Doesn't work anywhere else that I know of.
Puppet::Type.type(:package).provide :openbsd, :parent => Puppet::Provider::Package do
- desc "OpenBSD's form of `pkg_add` support."
+ desc "OpenBSD's form of `pkg_add` support.
- commands :pkginfo => "pkg_info", :pkgadd => "pkg_add", :pkgdelete => "pkg_delete"
+ This provider supports the `install_options` and `uninstall_options`
+ attributes, which allow command-line flags to be passed to pkg_add and pkg_delete.
+ These options should be specified as a string (e.g. '--flag'), a hash (e.g. {'--flag' => 'value'}),
+ or an array where each element is either a string or a hash."
+
+ commands :pkginfo => "pkg_info",
+ :pkgadd => "pkg_add",
+ :pkgdelete => "pkg_delete"
defaultfor :operatingsystem => :openbsd
confine :operatingsystem => :openbsd
@@ -12,6 +19,7 @@ Puppet::Type.type(:package).provide :openbsd, :parent => Puppet::Provider::Packa
has_feature :versionable
has_feature :install_options
has_feature :uninstall_options
+ has_feature :upgradeable
def self.instances
packages = []
@@ -54,6 +62,60 @@ Puppet::Type.type(:package).provide :openbsd, :parent => Puppet::Provider::Packa
[command(:pkginfo), "-a"]
end
+ def latest
+ parse_pkgconf
+
+ if @resource[:source][-1,1] == ::File::SEPARATOR
+ e_vars = { 'PKG_PATH' => @resource[:source] }
+ else
+ e_vars = {}
+ end
+
+ if @resource[:flavor]
+ query = "#{@resource[:name]}--#{@resource[:flavor]}"
+ else
+ query = @resource[:name]
+ end
+
+ output = Puppet::Util.withenv(e_vars) {pkginfo "-Q", query}
+
+ if output.nil? or output.size == 0 or output =~ /Error from /
+ debug "Failed to query for #{resource[:name]}"
+ return properties[:ensure]
+ else
+ # Remove all fuzzy matches first.
+ output = output.split.select {|p| p =~ /^#{resource[:name]}-(\d[^-]*)[-]?(\w*)/ }.join
+ debug "pkg_info -Q for #{resource[:name]}: #{output}"
+ end
+
+ if output =~ /^#{resource[:name]}-(\d[^-]*)[-]?(\w*) \(installed\)$/
+ debug "Package is already the latest available"
+ return properties[:ensure]
+ else
+ match = /^(.*)-(\d[^-]*)[-]?(\w*)$/.match(output)
+ debug "Latest available for #{resource[:name]}: #{match[2]}"
+
+ if properties[:ensure].to_sym == :absent
+ return match[2]
+ end
+
+ vcmp = properties[:ensure].split('.').map{|s|s.to_i} <=> match[2].split('.').map{|s|s.to_i}
+ if vcmp > 0
+ debug "ensure: #{properties[:ensure]}"
+ # The locally installed package may actually be newer than what a mirror
+ # has. Log it at debug, but ignore it otherwise.
+ debug "Package #{resource[:name]} #{properties[:ensure]} newer then available #{match[2]}"
+ return properties[:ensure]
+ else
+ return match[2]
+ end
+ end
+ end
+
+ def update
+ self.install(true)
+ end
+
def parse_pkgconf
unless @resource[:source]
if Puppet::FileSystem.exist?("/etc/pkg.conf")
@@ -80,14 +142,25 @@ Puppet::Type.type(:package).provide :openbsd, :parent => Puppet::Provider::Packa
end
end
- def install
+ def install(latest = false)
cmd = []
parse_pkgconf
if @resource[:source][-1,1] == ::File::SEPARATOR
e_vars = { 'PKG_PATH' => @resource[:source] }
- full_name = [ @resource[:name], get_version || @resource[:ensure], @resource[:flavor] ].join('-').chomp('-').chomp('-')
+ # In case of a real update (i.e., the package already exists) then
+ # pkg_add(8) can handle the flavors. However, if we're actually
+ # installing with 'latest', we do need to handle the flavors.
+ # So we always need to handle flavors ourselves as to not break installs.
+ if latest and resource[:flavor]
+ full_name = "#{resource[:name]}--#{resource[:flavor]}"
+ elsif latest
+ # Don't depend on get_version for updates.
+ full_name = @resource[:name]
+ else
+ full_name = [ @resource[:name], get_version || @resource[:ensure], @resource[:flavor] ].join('-').chomp('-').chomp('-')
+ end
else
e_vars = {}
full_name = @resource[:source]
@@ -96,13 +169,17 @@ Puppet::Type.type(:package).provide :openbsd, :parent => Puppet::Provider::Packa
cmd << install_options
cmd << full_name
- Puppet::Util.withenv(e_vars) { pkgadd cmd.flatten.compact.join(' ') }
+ if latest
+ cmd.unshift('-rz')
+ end
+
+ Puppet::Util.withenv(e_vars) { pkgadd cmd.flatten.compact }
end
def get_version
execpipe([command(:pkginfo), "-I", @resource[:name]]) do |process|
# our regex for matching pkg_info output
- regex = /^(.*)-(\d[^-]*)[-]?(\D*)(.*)$/
+ regex = /^(.*)-(\d[^-]*)[-]?(\w*)(.*)$/
master_version = 0
version = -1
@@ -142,7 +219,7 @@ Puppet::Type.type(:package).provide :openbsd, :parent => Puppet::Provider::Packa
end
def uninstall
- pkgdelete uninstall_options.flatten.compact.join(' '), @resource[:name]
+ pkgdelete uninstall_options.flatten.compact, @resource[:name]
end
def purge
diff --git a/lib/puppet/provider/package/pacman.rb b/lib/puppet/provider/package/pacman.rb
index 0c721c9d4..4ca356b16 100644
--- a/lib/puppet/provider/package/pacman.rb
+++ b/lib/puppet/provider/package/pacman.rb
@@ -3,7 +3,11 @@ require 'set'
require 'uri'
Puppet::Type.type(:package).provide :pacman, :parent => Puppet::Provider::Package do
- desc "Support for the Package Manager Utility (pacman) used in Archlinux."
+ desc "Support for the Package Manager Utility (pacman) used in Archlinux.
+
+ This provider supports the `install_options` attribute, which allows command-line flags to be passed to pacman.
+ These options should be specified as a string (e.g. '--flag'), a hash (e.g. {'--flag' => 'value'}),
+ or an array where each element is either a string or a hash."
commands :pacman => "/usr/bin/pacman"
# Yaourt is a common AUR helper which, if installed, we can use to query the AUR
@@ -11,6 +15,8 @@ Puppet::Type.type(:package).provide :pacman, :parent => Puppet::Provider::Packag
confine :operatingsystem => :archlinux
defaultfor :operatingsystem => :archlinux
+ has_feature :install_options
+ has_feature :uninstall_options
has_feature :upgradeable
# If yaourt is installed, we can make use of it
@@ -35,9 +41,15 @@ Puppet::Type.type(:package).provide :pacman, :parent => Puppet::Provider::Packag
def install_from_repo
if yaourt?
- yaourt "--noconfirm", "-S", @resource[:name]
+ cmd = %w{--noconfirm}
+ cmd += install_options if @resource[:install_options]
+ cmd << "-S" << @resource[:name]
+ yaourt *cmd
else
- pacman "--noconfirm", "--noprogressbar", "-Sy", @resource[:name]
+ cmd = %w{--noconfirm --noprogressbar}
+ cmd += install_options if @resource[:install_options]
+ cmd << "-Sy" << @resource[:name]
+ pacman *cmd
end
end
private :install_from_repo
@@ -204,6 +216,19 @@ Puppet::Type.type(:package).provide :pacman, :parent => Puppet::Provider::Packag
# Removes a package from the system.
def uninstall
- pacman "--noconfirm", "--noprogressbar", "-R", @resource[:name]
+ cmd = %w{--noconfirm --noprogressbar}
+ cmd += uninstall_options if @resource[:uninstall_options]
+ cmd << "-R" << @resource[:name]
+ pacman *cmd
+ end
+
+ private
+
+ def install_options
+ join_options(@resource[:install_options])
+ end
+
+ def uninstall_options
+ join_options(@resource[:uninstall_options])
end
end
diff --git a/lib/puppet/provider/package/rpm.rb b/lib/puppet/provider/package/rpm.rb
index fad6b4845..3b4b6388a 100644
--- a/lib/puppet/provider/package/rpm.rb
+++ b/lib/puppet/provider/package/rpm.rb
@@ -6,11 +6,9 @@ Puppet::Type.type(:package).provide :rpm, :source => :rpm, :parent => Puppet::Pr
binary.
This provider supports the `install_options` and `uninstall_options`
- attributes, which allow command-line flags to be passed to the RPM binary.
- These options should be specified as an array, where each element is either
- a string or a `{'--flag' => 'value'}` hash. (That hash example would be
- equivalent to a `'--flag=value'` string; the hash syntax is available as a
- convenience.)"
+ attributes, which allow command-line flags to be passed to rpm.
+ These options should be specified as a string (e.g. '--flag'), a hash (e.g. {'--flag' => 'value'}),
+ or an array where each element is either a string or a hash."
has_feature :versionable
has_feature :install_options
@@ -42,12 +40,12 @@ Puppet::Type.type(:package).provide :rpm, :source => :rpm, :parent => Puppet::Pr
@current_version = output.gsub('RPM version ', '').strip
end
- # rpm < 4.1 don't support --nosignature
+ # rpm < 4.1 does not support --nosignature
def self.nosignature
'--nosignature' unless Puppet::Util::Package.versioncmp(current_version, '4.1') < 0
end
- # rpm < 4.0.2 don't support --nodigest
+ # rpm < 4.0.2 does not support --nodigest
def self.nodigest
'--nodigest' unless Puppet::Util::Package.versioncmp(current_version, '4.0.2') < 0
end
diff --git a/lib/puppet/provider/package/sun.rb b/lib/puppet/provider/package/sun.rb
index 87a13a2a5..15b78392d 100644
--- a/lib/puppet/provider/package/sun.rb
+++ b/lib/puppet/provider/package/sun.rb
@@ -4,7 +4,11 @@ require 'puppet/provider/package'
Puppet::Type.type(:package).provide :sun, :parent => Puppet::Provider::Package do
desc "Sun's packaging system. Requires that you specify the source for
- the packages you're managing."
+ the packages you're managing.
+
+ This provider supports the `install_options` attribute, which allows command-line flags to be passed to pkgadd.
+ These options should be specified as a string (e.g. '--flag'), a hash (e.g. {'--flag' => 'value'}),
+ or an array where each element is either a string or a hash."
commands :pkginfo => "/usr/bin/pkginfo",
:pkgadd => "/usr/sbin/pkgadd",
diff --git a/lib/puppet/provider/package/windows.rb b/lib/puppet/provider/package/windows.rb
index c91460f45..143d1c1e6 100644
--- a/lib/puppet/provider/package/windows.rb
+++ b/lib/puppet/provider/package/windows.rb
@@ -10,6 +10,11 @@ Puppet::Type.type(:package).provide(:windows, :parent => Puppet::Provider::Packa
This provider requires a `source` attribute when installing the package.
It accepts paths to local files, mapped drives, or UNC paths.
+ This provider supports the `install_options` and `uninstall_options`
+ attributes, which allow command-line flags to be passed to the installer.
+ These options should be specified as a string (e.g. '--flag'), a hash (e.g. {'--flag' => 'value'}),
+ or an array where each element is either a string or a hash.
+
If the executable requires special arguments to perform a silent install or
uninstall, then the appropriate arguments should be specified using the
`install_options` or `uninstall_options` attributes, respectively. Puppet
@@ -93,7 +98,7 @@ Puppet::Type.type(:package).provide(:windows, :parent => Puppet::Provider::Packa
end
end
- # This only get's called if there is a value to validate, but not if it's absent
+ # This only gets called if there is a value to validate, but not if it's absent
def validate_source(value)
fail("The source parameter cannot be empty when using the Windows provider.") if value.empty?
end
diff --git a/lib/puppet/provider/package/windows/exe_package.rb b/lib/puppet/provider/package/windows/exe_package.rb
index 5525ca0c6..9c9e46fbc 100644
--- a/lib/puppet/provider/package/windows/exe_package.rb
+++ b/lib/puppet/provider/package/windows/exe_package.rb
@@ -41,7 +41,7 @@ class Puppet::Provider::Package::Windows
end
def self.install_command(resource)
- ['cmd.exe', '/c', 'start', '"puppet-install"', '/w', quote(resource[:source])]
+ ['cmd.exe', '/c', 'start', '"puppet-install"', '/w', munge(resource[:source])]
end
def uninstall_command
diff --git a/lib/puppet/provider/package/windows/msi_package.rb b/lib/puppet/provider/package/windows/msi_package.rb
index 9245d847d..1b8b04dfb 100644
--- a/lib/puppet/provider/package/windows/msi_package.rb
+++ b/lib/puppet/provider/package/windows/msi_package.rb
@@ -52,7 +52,7 @@ class Puppet::Provider::Package::Windows
end
def self.install_command(resource)
- ['msiexec.exe', '/qn', '/norestart', '/i', quote(resource[:source])]
+ ['msiexec.exe', '/qn', '/norestart', '/i', munge(resource[:source])]
end
def uninstall_command
diff --git a/lib/puppet/provider/package/windows/package.rb b/lib/puppet/provider/package/windows/package.rb
index 07fb64d25..a1526a886 100644
--- a/lib/puppet/provider/package/windows/package.rb
+++ b/lib/puppet/provider/package/windows/package.rb
@@ -42,7 +42,7 @@ class Puppet::Provider::Package::Windows
end
end
rescue Puppet::Util::Windows::Error => e
- raise e unless e.code == Windows::Error::ERROR_FILE_NOT_FOUND
+ raise e unless e.code == Puppet::Util::Windows::Error::ERROR_FILE_NOT_FOUND
end
end
end
@@ -65,6 +65,18 @@ class Puppet::Provider::Package::Windows
end
end
+ def self.munge(value)
+ quote(replace_forward_slashes(value))
+ end
+
+ def self.replace_forward_slashes(value)
+ if value.include?('/')
+ value.gsub!('/', "\\")
+ Puppet.debug('Package source parameter contained /s - replaced with \\s')
+ end
+ value
+ end
+
def self.quote(value)
value.include?(' ') ? %Q["#{value.gsub(/"/, '\"')}"] : value
end
diff --git a/lib/puppet/provider/package/yum.rb b/lib/puppet/provider/package/yum.rb
index 50ff2f28a..bfbe9df16 100644
--- a/lib/puppet/provider/package/yum.rb
+++ b/lib/puppet/provider/package/yum.rb
@@ -5,7 +5,11 @@ Puppet::Type.type(:package).provide :yum, :parent => :rpm, :source => :rpm do
Using this provider's `uninstallable` feature will not remove dependent packages. To
remove dependent packages with this provider use the `purgeable` feature, but note this
- feature is destructive and should be used with the utmost care."
+ feature is destructive and should be used with the utmost care.
+
+ This provider supports the `install_options` attribute, which allows command-line flags to be passed to yum.
+ These options should be specified as a string (e.g. '--flag'), a hash (e.g. {'--flag' => 'value'}),
+ or an array where each element is either a string or a hash."
has_feature :install_options, :versionable, :virtual_packages
@@ -23,7 +27,7 @@ Puppet::Type.type(:package).provide :yum, :parent => :rpm, :source => :rpm do
end
end
- defaultfor :operatingsystem => [:fedora, :centos, :redhat]
+ defaultfor :osfamily => :redhat
def self.prefetch(packages)
raise Puppet::Error, "The yum provider can only be used as root" if Process.euid != 0
@@ -93,7 +97,7 @@ Puppet::Type.type(:package).provide :yum, :parent => :rpm, :source => :rpm do
wanted = @resource[:name]
# If not allowing virtual packages, do a query to ensure a real package exists
unless @resource.allow_virtual?
- yum '-d', '0', '-e', '0', '-y', :list, wanted
+ yum *['-d', '0', '-e', '0', '-y', install_options, :list, wanted].compact
end
should = @resource.should(:ensure)
diff --git a/lib/puppet/provider/package/zypper.rb b/lib/puppet/provider/package/zypper.rb
index c0af6a59e..ddfd34084 100644
--- a/lib/puppet/provider/package/zypper.rb
+++ b/lib/puppet/provider/package/zypper.rb
@@ -1,5 +1,9 @@
Puppet::Type.type(:package).provide :zypper, :parent => :rpm do
- desc "Support for SuSE `zypper` package manager. Found in SLES10sp2+ and SLES11"
+ desc "Support for SuSE `zypper` package manager. Found in SLES10sp2+ and SLES11.
+
+ This provider supports the `install_options` attribute, which allows command-line flags to be passed to zypper.
+ These options should be specified as a string (e.g. '--flag'), a hash (e.g. {'--flag' => 'value'}),
+ or an array where each element is either a string or a hash."
has_feature :versionable, :install_options, :virtual_packages
diff --git a/lib/puppet/provider/parsedfile.rb b/lib/puppet/provider/parsedfile.rb
index 03ad1e194..37d0ec483 100644
--- a/lib/puppet/provider/parsedfile.rb
+++ b/lib/puppet/provider/parsedfile.rb
@@ -317,9 +317,25 @@ class Puppet::Provider::ParsedFile < Puppet::Provider
record_type(record[:record_type]).text?
end
+ # The mode for generated files if they are newly created.
+ # No mode will be set on existing files.
+ #
+ # @abstract Providers inheriting parsedfile can override this method
+ # to provide a mode. The value should be suitable for File.chmod
+ def self.default_mode
+ nil
+ end
+
# Initialize the object if necessary.
def self.target_object(target)
- @target_objects[target] ||= filetype.new(target)
+ # only send the default mode if the actual provider defined it,
+ # because certain filetypes (e.g. the crontab variants) do not
+ # expect it in their initialize method
+ if default_mode
+ @target_objects[target] ||= filetype.new(target, default_mode)
+ else
+ @target_objects[target] ||= filetype.new(target)
+ end
@target_objects[target]
end
diff --git a/lib/puppet/provider/scheduled_task/win32_taskscheduler.rb b/lib/puppet/provider/scheduled_task/win32_taskscheduler.rb
index 5a8741122..91526d7fa 100644
--- a/lib/puppet/provider/scheduled_task/win32_taskscheduler.rb
+++ b/lib/puppet/provider/scheduled_task/win32_taskscheduler.rb
@@ -1,17 +1,11 @@
require 'puppet/parameter'
if Puppet.features.microsoft_windows?
- require 'win32/taskscheduler'
- require 'puppet/util/adsi'
+ require 'puppet/util/windows/taskscheduler'
end
Puppet::Type.type(:scheduled_task).provide(:win32_taskscheduler) do
- desc %q{This provider uses the win32-taskscheduler gem to manage scheduled
- tasks on Windows.
-
- Puppet requires version 0.2.1 or later of the win32-taskscheduler gem;
- previous versions can cause "Could not evaluate: The operation completed
- successfully" errors.}
+ desc %q{This provider manages scheduled tasks on Windows.}
defaultfor :operatingsystem => :windows
confine :operatingsystem => :windows
@@ -125,7 +119,7 @@ Puppet::Type.type(:scheduled_task).provide(:win32_taskscheduler) do
# By comparing account SIDs we don't have to worry about case
# sensitivity, or canonicalization of the account name.
- Puppet::Util::Windows::Security.name_to_sid(current) == Puppet::Util::Windows::Security.name_to_sid(should[0])
+ Puppet::Util::Windows::SID.name_to_sid(current) == Puppet::Util::Windows::SID.name_to_sid(should[0])
end
def trigger_insync?(current, should)
@@ -202,7 +196,7 @@ Puppet::Type.type(:scheduled_task).provide(:win32_taskscheduler) do
end
def user=(value)
- self.fail("Invalid user: #{value}") unless Puppet::Util::Windows::Security.name_to_sid(value)
+ self.fail("Invalid user: #{value}") unless Puppet::Util::Windows::SID.name_to_sid(value)
if value.to_s.downcase != 'system'
task.set_account_information(value, resource[:password])
@@ -284,8 +278,8 @@ Puppet::Type.type(:scheduled_task).provide(:win32_taskscheduler) do
trigger = dummy_time_trigger
if user_provided_input
- self.fail "'enabled' is read-only on triggers" if puppet_trigger.has_key?('enabled')
- self.fail "'index' is read-only on triggers" if puppet_trigger.has_key?('index')
+ self.fail "'enabled' is read-only on scheduled_task triggers and should be removed ('enabled' is usually provided in puppet resource scheduled_task)." if puppet_trigger.has_key?('enabled')
+ self.fail "'index' is read-only on scheduled_task triggers and should be removed ('index' is usually provided in puppet resource scheduled_task)." if puppet_trigger.has_key?('index')
end
puppet_trigger.delete('index')
diff --git a/lib/puppet/provider/service/freebsd.rb b/lib/puppet/provider/service/freebsd.rb
index 1e59fc47d..91a3e0244 100644
--- a/lib/puppet/provider/service/freebsd.rb
+++ b/lib/puppet/provider/service/freebsd.rb
@@ -27,24 +27,24 @@ Puppet::Type.type(:service).provide :freebsd, :parent => :init do
rcvar
end
+ # Extract value name from service or rcvar
+ def extract_value_name(name, rc_index, regex, regex_index)
+ value_name = self.rcvar[rc_index]
+ self.error("No #{name} name found in rcvar") if value_name.nil?
+ value_name = value_name.gsub!(regex, regex_index)
+ self.error("#{name} name is empty") if value_name.nil?
+ self.debug("#{name} name is #{value_name}")
+ value_name
+ end
+
# Extract service name
def service_name
- name = self.rcvar[0]
- self.error("No service name found in rcvar") if name.nil?
- name = name.gsub!(/# (.*)/, '\1')
- self.error("Service name is empty") if name.nil?
- self.debug("Service name is #{name}")
- name
+ extract_value_name('service', 0, /# (.*)/, '\1')
end
# Extract rcvar name
def rcvar_name
- name = self.rcvar[1]
- self.error("No rcvar name found in rcvar") if name.nil?
- name = name.gsub!(/(.*?)(_enable)?=(.*)/, '\1')
- self.error("rcvar name is empty") if name.nil?
- self.debug("rcvar name is #{name}")
- name
+ extract_value_name('rcvar', 1, /(.*?)(_enable)?=(.*)/, '\1')
end
# Extract rcvar value
diff --git a/lib/puppet/provider/service/init.rb b/lib/puppet/provider/service/init.rb
index 19e171571..387212f11 100644
--- a/lib/puppet/provider/service/init.rb
+++ b/lib/puppet/provider/service/init.rb
@@ -50,6 +50,11 @@ Puppet::Type.type(:service).provide :init, :parent => :base do
# Prevent puppet failing to get status of these services, which need parameters
# passed in (see https://bugs.launchpad.net/ubuntu/+source/puppet/+bug/1276766).
excludes += %w{idmapd-mounting startpar-bridge}
+ # Prevent puppet failing to get status of these services, additional upstart
+ # service with instances
+ excludes += %w{cryptdisks-udev}
+ excludes += %w{statd-mounting}
+ excludes += %w{gssd-mounting}
end
# List all services of this type.
diff --git a/lib/puppet/provider/service/launchd.rb b/lib/puppet/provider/service/launchd.rb
index 49f14d619..b65c79261 100644
--- a/lib/puppet/provider/service/launchd.rb
+++ b/lib/puppet/provider/service/launchd.rb
@@ -213,9 +213,6 @@ Puppet::Type.type(:service).provide :launchd, :parent => :base do
def self.get_macosx_version_major
return @macosx_version_major if @macosx_version_major
begin
- # Make sure we've loaded all of the facts
- Facter.loadfacts
-
product_version_major = Facter.value(:macosx_productversion_major)
fail("#{product_version_major} is not supported by the launchd provider") if %w{10.0 10.1 10.2 10.3 10.4}.include?(product_version_major)
diff --git a/lib/puppet/provider/service/openbsd.rb b/lib/puppet/provider/service/openbsd.rb
index 55b521785..521462b78 100644
--- a/lib/puppet/provider/service/openbsd.rb
+++ b/lib/puppet/provider/service/openbsd.rb
@@ -171,8 +171,10 @@ Puppet::Type.type(:service).provide :openbsd, :parent => :init do
content.reject! {|l| l.nil? }
end
- if flags.nil?
- append = resource[:name] + '_flags=""'
+ if flags.nil? or flags.size == 0
+ if in_base?
+ append = resource[:name] + '_flags=""'
+ end
else
append = resource[:name] + '_flags="' + flags + '"'
end
@@ -208,7 +210,7 @@ Puppet::Type.type(:service).provide :openbsd, :parent => :init do
# return the array with the current resource added
# @api private
def pkg_scripts_append
- [pkg_scripts(), resource[:name]].flatten.sort.uniq
+ [pkg_scripts(), resource[:name]].flatten.uniq
end
# return the array without the current resource
@@ -243,12 +245,11 @@ Puppet::Type.type(:service).provide :openbsd, :parent => :init do
content
end
- # Determine if the rc script is included in base, or if it exists as a result
- # of a package installation.
+ # Determine if the rc script is included in base
# @api private
def in_base?
- system("/usr/sbin/pkg_info -qE /etc/rc.d/#{self.name}> /dev/null")
- $?.exitstatus == 1
+ script = File.readlines(self.class.rcconf).find {|s| s =~ /^#{rcvar_name}/ }
+ !script.nil?
end
# @api private
diff --git a/lib/puppet/provider/ssh_authorized_key/parsed.rb b/lib/puppet/provider/ssh_authorized_key/parsed.rb
index af0e082ea..67403c9d6 100644
--- a/lib/puppet/provider/ssh_authorized_key/parsed.rb
+++ b/lib/puppet/provider/ssh_authorized_key/parsed.rb
@@ -15,7 +15,7 @@ Puppet::Type.type(:ssh_authorized_key).provide(
:fields => %w{options type key name},
:optional => %w{options},
:rts => /^\s+/,
- :match => /^(?:(.+) )?(ssh-dss|ssh-ed25519|ssh-rsa|ecdsa-sha2-nistp256|ecdsa-sha2-nistp384|ecdsa-sha2-nistp521) ([^ ]+) ?(.*)$/,
+ :match => Puppet::Type.type(:ssh_authorized_key).keyline_regex,
:post_parse => proc { |h|
h[:name] = "" if h[:name] == :absent
h[:options] ||= [:absent]
@@ -74,7 +74,7 @@ Puppet::Type.type(:ssh_authorized_key).provide(
while !scanner.eos?
scanner.skip(/[ \t]*/)
# scan a long option
- if out = scanner.scan(/[-a-z0-9A-Z_]+=\".*?\"/) or out = scanner.scan(/[-a-z0-9A-Z_]+/)
+ if out = scanner.scan(/[-a-z0-9A-Z_]+=\".*?[^\\]\"/) or out = scanner.scan(/[-a-z0-9A-Z_]+/)
result << out
else
# found an unscannable token, let's abort
diff --git a/lib/puppet/provider/sshkey/parsed.rb b/lib/puppet/provider/sshkey/parsed.rb
index f874683b7..29f345916 100644
--- a/lib/puppet/provider/sshkey/parsed.rb
+++ b/lib/puppet/provider/sshkey/parsed.rb
@@ -31,5 +31,10 @@ Puppet::Type.type(:sshkey).provide(
hash.delete(:host_aliases)
end
}
+
+ # Make sure to use mode 644 if ssh_known_hosts is newly created
+ def self.default_mode
+ 0644
+ end
end
diff --git a/lib/puppet/provider/user/user_role_add.rb b/lib/puppet/provider/user/user_role_add.rb
index 96b4928fd..f3100e04f 100644
--- a/lib/puppet/provider/user/user_role_add.rb
+++ b/lib/puppet/provider/user/user_role_add.rb
@@ -26,7 +26,7 @@ Puppet::Type.type(:user).provide :user_role_add, :parent => :useradd, :source =>
value !~ /\s/
end
- has_features :manages_homedir, :allows_duplicates, :manages_solaris_rbac, :manages_passwords, :manages_password_age
+ has_features :manages_homedir, :allows_duplicates, :manages_solaris_rbac, :manages_passwords, :manages_password_age, :manages_shell
#must override this to hand the keyvalue pairs
def add_properties
@@ -161,7 +161,8 @@ Puppet::Type.type(:user).provide :user_role_add, :parent => :useradd, :source =>
return @shadow_entry if defined? @shadow_entry
@shadow_entry = File.readlines(target_file_path).
reject { |r| r =~ /^[^\w]/ }.
- collect { |l| l.chomp.split(':') }.
+ # PUP-229 dont suppress the empty fields
+ collect { |l| l.chomp.split(':', -1) }.
find { |user, _| user == @resource[:name] }
end
@@ -170,12 +171,12 @@ Puppet::Type.type(:user).provide :user_role_add, :parent => :useradd, :source =>
end
def password_min_age
- shadow_entry ? shadow_entry[3] : :absent
+ shadow_entry[3].empty? ? -1 : shadow_entry[3]
end
def password_max_age
return :absent unless shadow_entry
- shadow_entry[4] || -1
+ shadow_entry[4].empty? ? -1 : shadow_entry[4]
end
# Read in /etc/shadow, find the line for our used and rewrite it with the
diff --git a/lib/puppet/provider/user/windows_adsi.rb b/lib/puppet/provider/user/windows_adsi.rb
index 14b905d3b..fbca8744b 100644
--- a/lib/puppet/provider/user/windows_adsi.rb
+++ b/lib/puppet/provider/user/windows_adsi.rb
@@ -1,4 +1,4 @@
-require 'puppet/util/adsi'
+require 'puppet/util/windows'
Puppet::Type.type(:user).provide :windows_adsi do
desc "Local user management for Windows."
@@ -9,7 +9,7 @@ Puppet::Type.type(:user).provide :windows_adsi do
has_features :manages_homedir, :manages_passwords
def user
- @user ||= Puppet::Util::ADSI::User.new(@resource[:name])
+ @user ||= Puppet::Util::Windows::ADSI::User.new(@resource[:name])
end
def groups
@@ -21,7 +21,7 @@ Puppet::Type.type(:user).provide :windows_adsi do
end
def create
- @user = Puppet::Util::ADSI::User.create(@resource[:name])
+ @user = Puppet::Util::Windows::ADSI::User.create(@resource[:name])
@user.password = @resource[:password]
@user.commit
@@ -35,17 +35,17 @@ Puppet::Type.type(:user).provide :windows_adsi do
end
def exists?
- Puppet::Util::ADSI::User.exists?(@resource[:name])
+ Puppet::Util::Windows::ADSI::User.exists?(@resource[:name])
end
def delete
# lookup sid before we delete account
sid = uid if @resource.managehome?
- Puppet::Util::ADSI::User.delete(@resource[:name])
+ Puppet::Util::Windows::ADSI::User.delete(@resource[:name])
if sid
- Puppet::Util::ADSI::UserProfile.delete(sid)
+ Puppet::Util::Windows::ADSI::UserProfile.delete(sid)
end
end
@@ -79,7 +79,7 @@ Puppet::Type.type(:user).provide :windows_adsi do
end
def uid
- Puppet::Util::Windows::Security.name_to_sid(@resource[:name])
+ Puppet::Util::Windows::SID.name_to_sid(@resource[:name])
end
def uid=(value)
@@ -94,6 +94,6 @@ Puppet::Type.type(:user).provide :windows_adsi do
end
def self.instances
- Puppet::Util::ADSI::User.map { |u| new(:ensure => :present, :name => u.name) }
+ Puppet::Util::Windows::ADSI::User.map { |u| new(:ensure => :present, :name => u.name) }
end
end
diff --git a/lib/puppet/provider/zone/solaris.rb b/lib/puppet/provider/zone/solaris.rb
index e681aca75..cbf07eec8 100644
--- a/lib/puppet/provider/zone/solaris.rb
+++ b/lib/puppet/provider/zone/solaris.rb
@@ -30,7 +30,7 @@ Puppet::Type.type(:zone).provide(:solaris) do
def multi_conf(name, should, &action)
has = properties[name]
- has = [] if has == :absent
+ has = [] if !has || has == :absent
rms = has - should
adds = should - has
(rms.map{|o| action.call(:rm,o)} + adds.map{|o| action.call(:add,o)}).join("\n")
diff --git a/lib/puppet/reference/metaparameter.rb b/lib/puppet/reference/metaparameter.rb
index 7c701a110..cd8d6d44f 100644
--- a/lib/puppet/reference/metaparameter.rb
+++ b/lib/puppet/reference/metaparameter.rb
@@ -10,12 +10,14 @@ Puppet::Util::Reference.newreference :metaparameter, :doc => "All Puppet metapar
str = %{
-# Metaparameters
-
-Metaparameters are parameters that work with any resource type; they are part of the
-Puppet framework itself rather than being part of the implementation of any
-given instance. Thus, any defined metaparameter can be used with any instance
-in your manifest, including defined components.
+Metaparameters are attributes that work with any resource type, including custom
+types and defined types.
+
+In general, they affect _Puppet's_ behavior rather than the desired state of the
+resource. Metaparameters do things like add metadata to a resource (`alias`,
+`tag`), set limits on when the resource should be synced (`require`, `schedule`,
+etc.), prevent Puppet from making changes (`noop`), and change logging verbosity
+(`loglevel`).
## Available Metaparameters
diff --git a/lib/puppet/reports/store.rb b/lib/puppet/reports/store.rb
index 5b82f6d0f..528f9dba8 100644
--- a/lib/puppet/reports/store.rb
+++ b/lib/puppet/reports/store.rb
@@ -1,6 +1,6 @@
require 'puppet'
require 'fileutils'
-require 'tempfile'
+require 'puppet/util'
SEPARATOR = [Regexp.escape(File::SEPARATOR.to_s), Regexp.escape(File::ALT_SEPARATOR.to_s)].join
@@ -31,17 +31,12 @@ Puppet::Reports.register_report(:store) do
file = File.join(dir, name)
- f = Tempfile.new(name, dir)
begin
- begin
- f.chmod(0640)
- f.print to_yaml
- ensure
- f.close
+ Puppet::Util.replace_file(file, 0640) do |fh|
+ fh.print to_yaml
end
- FileUtils.mv(f.path, file)
rescue => detail
- Puppet.log_exception(detail, "Could not write report for #{host} at #{file}: #{detail}")
+ Puppet.log_exception(detail, "Could not write report for #{host} at #{file}: #{detail}")
end
# Only testing cares about the return value
diff --git a/lib/puppet/resource.rb b/lib/puppet/resource.rb
index 40f12b5d2..7e7a6ab2c 100644
--- a/lib/puppet/resource.rb
+++ b/lib/puppet/resource.rb
@@ -186,9 +186,42 @@ class Puppet::Resource
@is_stage ||= @type.to_s.downcase == "stage"
end
- # Create our resource.
+ # Cache to reduce respond_to? lookups
+ @@nondeprecating_type = {}
+
+ # Construct a resource from data.
+ #
+ # Constructs a resource instance with the given `type` and `title`. Multiple
+ # type signatures are possible for these arguments and most will result in an
+ # expensive call to {Puppet::Node::Environment#known_resource_types} in order
+ # to resolve `String` and `Symbol` Types to actual Ruby classes.
+ #
+ # @param type [Symbol, String] The name of the Puppet Type, as a string or
+ # symbol. The actual Type will be looked up using
+ # {Puppet::Node::Environment#known_resource_types}. This lookup is expensive.
+ # @param type [String] The full resource name in the form of
+ # `"Type[Title]"`. This method of calling should only be used when
+ # `title` is `nil`.
+ # @param type [nil] If a `nil` is passed, the title argument must be a string
+ # of the form `"Type[Title]"`.
+ # @param type [Class] A class that inherits from `Puppet::Type`. This method
+ # of construction is much more efficient as it skips calls to
+ # {Puppet::Node::Environment#known_resource_types}.
+ #
+ # @param title [String, :main, nil] The title of the resource. If type is `nil`, may also
+ # be the full resource name in the form of `"Type[Title]"`.
+ #
+ # @api public
def initialize(type, title = nil, attributes = {})
@parameters = {}
+ if type.is_a?(Class) && type < Puppet::Type
+ # Set the resource type to avoid an expensive `known_resource_types`
+ # lookup.
+ self.resource_type = type
+ # From this point on, the constructor behaves the same as if `type` had
+ # been passed as a symbol.
+ type = type.name
+ end
# Set things like strictness first.
attributes.each do |attr, value|
@@ -209,6 +242,14 @@ class Puppet::Resource
extract_parameters(params)
end
+ if resource_type and ! @@nondeprecating_type[resource_type]
+ if resource_type.respond_to?(:deprecate_params)
+ resource_type.deprecate_params(title, attributes[:parameters])
+ else
+ @@nondeprecating_type[resource_type] = true
+ end
+ end
+
tag(self.type)
tag(self.title) if valid_tag?(self.title)
@@ -254,7 +295,7 @@ class Puppet::Resource
@environment ||= if catalog
catalog.environment_instance
else
- Puppet::Node::Environment::NONE
+ Puppet.lookup(:current_environment) { Puppet::Node::Environment::NONE }
end
end
@@ -407,15 +448,23 @@ class Puppet::Resource
end
end
- # If the value is an array with only one value, then
- # convert it to a single value. This is largely so that
- # the database interaction doesn't have to worry about
- # whether it returns an array or a string.
- result[p] = if v.is_a?(Array) and v.length == 1
- v[0]
- else
- v
- end
+ if Puppet[:parser] == 'current'
+ # If the value is an array with only one value, then
+ # convert it to a single value. This is largely so that
+ # the database interaction doesn't have to worry about
+ # whether it returns an array or a string.
+ #
+ # This behavior is not done in the future parser, but we can't issue a
+ # deprecation warning either since there isn't anything that a user can
+ # do about it.
+ result[p] = if v.is_a?(Array) and v.length == 1
+ v[0]
+ else
+ v
+ end
+ else
+ result[p] = v
+ end
end
result
@@ -436,6 +485,21 @@ class Puppet::Resource
param = param.to_sym
fail Puppet::ParseError, "Must pass #{param} to #{self}" unless parameters.include?(param)
end
+
+ # Perform optional type checking
+ if Puppet[:parser] == 'future'
+ # Perform type checking
+ arg_types = resource_type.argument_types
+ # Parameters is a map from name, to parameter, and the parameter again has name and value
+ parameters.each do |name, value|
+ next unless t = arg_types[name.to_s] # untyped, and parameters are symbols here (aargh, strings in the type)
+ unless Puppet::Pops::Types::TypeCalculator.instance?(t, value.value)
+ inferred_type = Puppet::Pops::Types::TypeCalculator.infer(value.value)
+ actual = Puppet::Pops::Types::TypeCalculator.generalize!(inferred_type)
+ fail Puppet::ParseError, "Expected parameter '#{name}' of '#{self}' to have type #{t.to_s}, got #{actual.to_s}"
+ end
+ end
+ end
end
def validate_parameter(name)
diff --git a/lib/puppet/resource/catalog.rb b/lib/puppet/resource/catalog.rb
index 7de7df8dc..832d88eab 100644
--- a/lib/puppet/resource/catalog.rb
+++ b/lib/puppet/resource/catalog.rb
@@ -81,9 +81,12 @@ class Puppet::Resource::Catalog < Puppet::Graph::SimpleGraph
end
def add_one_resource(resource)
- fail_on_duplicate_type_and_title(resource)
+ title_key = title_key_for_ref(resource.ref)
+ if @resource_table[title_key]
+ fail_on_duplicate_type_and_title(resource, title_key)
+ end
- add_resource_to_table(resource)
+ add_resource_to_table(resource, title_key)
create_resource_aliases(resource)
resource.catalog = self if resource.respond_to?(:catalog=)
@@ -91,8 +94,7 @@ class Puppet::Resource::Catalog < Puppet::Graph::SimpleGraph
end
private :add_one_resource
- def add_resource_to_table(resource)
- title_key = title_key_for_ref(resource.ref)
+ def add_resource_to_table(resource, title_key)
@resource_table[title_key] = resource
@resources << title_key
end
@@ -184,11 +186,14 @@ class Puppet::Resource::Catalog < Puppet::Graph::SimpleGraph
# The relationship_graph form of the catalog. This contains all of the
# dependency edges that are used for determining order.
#
+ # @param given_prioritizer [Puppet::Graph::Prioritizer] The prioritization
+ # strategy to use when constructing the relationship graph. Defaults the
+ # being determined by the `ordering` setting.
# @return [Puppet::Graph::RelationshipGraph]
# @api public
- def relationship_graph
+ def relationship_graph(given_prioritizer = nil)
if @relationship_graph.nil?
- @relationship_graph = Puppet::Graph::RelationshipGraph.new(prioritizer)
+ @relationship_graph = Puppet::Graph::RelationshipGraph.new(given_prioritizer || prioritizer)
@relationship_graph.populate_from(self)
end
@relationship_graph
@@ -473,9 +478,9 @@ class Puppet::Resource::Catalog < Puppet::Graph::SimpleGraph
end
# Verify that the given resource isn't declared elsewhere.
- def fail_on_duplicate_type_and_title(resource)
+ def fail_on_duplicate_type_and_title(resource, title_key)
# Short-circuit the common case,
- return unless existing_resource = @resource_table[title_key_for_ref(resource.ref)]
+ return unless existing_resource = @resource_table[title_key]
# If we've gotten this far, it's a real conflict
msg = "Duplicate declaration: #{resource.ref} is already declared"
@@ -544,6 +549,6 @@ class Puppet::Resource::Catalog < Puppet::Graph::SimpleGraph
end
def virtual_not_exported?(resource)
- resource.respond_to?(:virtual?) and resource.virtual? and (resource.respond_to?(:exported?) and not resource.exported?)
+ resource.virtual && !resource.exported
end
end
diff --git a/lib/puppet/resource/type.rb b/lib/puppet/resource/type.rb
index 27884df9f..04d1f1f03 100644
--- a/lib/puppet/resource/type.rb
+++ b/lib/puppet/resource/type.rb
@@ -33,6 +33,11 @@ class Puppet::Resource::Type
attr_accessor :file, :line, :doc, :code, :ruby_code, :parent, :resource_type_collection
attr_reader :namespace, :arguments, :behaves_like, :module_name
+ # Map from argument (aka parameter) names to Puppet Type
+ # @return [Hash<Symbol, Puppet::Pops::Types::PAnyType] map from name to type
+ #
+ attr_reader :argument_types
+
# This should probably be renamed to 'kind' eventually, in accordance with the changes
# made for serialization and API usability (#14137). At the moment that seems like
# it would touch a whole lot of places in the code, though. --cprice 2012-04-23
@@ -140,6 +145,7 @@ class Puppet::Resource::Type
end
set_arguments(options[:arguments])
+ set_argument_types(options[:argument_types])
@match = nil
@@ -327,6 +333,27 @@ class Puppet::Resource::Type
end
end
+ # Sets the argument name to Puppet Type hash used for type checking.
+ # Names must correspond to available arguments (they must be defined first).
+ # Arguments not mentioned will not be type-checked. Only supported when parser == "future"
+ #
+ def set_argument_types(name_to_type_hash)
+ @argument_types = {}
+ # Stop here if not running under future parser, the rest requires pops to be initialized
+ # and that the type system is available
+ return unless Puppet[:parser] == 'future' && name_to_type_hash
+ name_to_type_hash.each do |name, t|
+ # catch internal errors
+ unless @arguments.include?(name)
+ raise Puppet::DevError, "Parameter '#{name}' is given a type, but is not a valid parameter."
+ end
+ unless t.is_a? Puppet::Pops::Types::PAnyType
+ raise Puppet::DevError, "Parameter '#{name}' is given a type that is not a Puppet Type, got #{t.class}"
+ end
+ @argument_types[name] = t
+ end
+ end
+
private
def convert_from_ast(name)
diff --git a/lib/puppet/settings.rb b/lib/puppet/settings.rb
index fbafa5ef8..3201b125f 100644
--- a/lib/puppet/settings.rb
+++ b/lib/puppet/settings.rb
@@ -2,15 +2,18 @@ require 'puppet'
require 'getoptlong'
require 'puppet/util/watched_file'
require 'puppet/util/command_line/puppet_option_parser'
+require 'forwardable'
# The class for handling configuration files.
class Puppet::Settings
+ extend Forwardable
include Enumerable
require 'puppet/settings/errors'
require 'puppet/settings/base_setting'
require 'puppet/settings/string_setting'
require 'puppet/settings/enum_setting'
+ require 'puppet/settings/array_setting'
require 'puppet/settings/file_setting'
require 'puppet/settings/directory_setting'
require 'puppet/settings/file_or_directory_setting'
@@ -88,11 +91,14 @@ class Puppet::Settings
# And keep a per-environment cache
@cache = Hash.new { |hash, key| hash[key] = {} }
+ @values = Hash.new { |hash, key| hash[key] = {} }
# The list of sections we've used.
@used = []
@hooks_to_call_on_application_initialization = []
+ @deprecated_setting_names = []
+ @deprecated_settings_that_have_been_configured = []
@translate = Puppet::Settings::ValueTranslator.new
@config_file_parser = Puppet::Settings::ConfigFile.new(@translate)
@@ -109,7 +115,9 @@ class Puppet::Settings
# @return [Object] the value of the setting
# @api private
def [](param)
- Puppet.deprecation_warning("Accessing '#{param}' as a setting is deprecated. See http://links.puppetlabs.com/env-settings-deprecations") if DEPRECATED_SETTINGS.include?(param)
+ if @deprecated_setting_names.include?(param)
+ issue_deprecation_warning(setting(param), "Accessing '#{param}' as a setting is deprecated.")
+ end
value(param)
end
@@ -118,7 +126,9 @@ class Puppet::Settings
# @param value [Object] the new value of the setting
# @api private
def []=(param, value)
- Puppet.deprecation_warning("Modifying '#{param}' as a setting is deprecated. See http://links.puppetlabs.com/env-settings-deprecations") if DEPRECATED_SETTINGS.include?(param)
+ if @deprecated_setting_names.include?(param)
+ issue_deprecation_warning(setting(param), "Modifying '#{param}' as a setting is deprecated.")
+ end
@value_sets[:memory].set(param, value)
unsafe_flush_cache
end
@@ -189,6 +199,8 @@ class Puppet::Settings
@value_sets[:memory] = Values.new(:memory, @config)
@value_sets[:overridden_defaults] = Values.new(:overridden_defaults, @config)
+ @deprecated_settings_that_have_been_configured.clear
+ @values.clear
@cache.clear
end
private :unsafe_clear
@@ -315,6 +327,7 @@ class Puppet::Settings
end
apply_metadata
call_hooks_deferred_to_application_initialization
+ issue_deprecations
@app_defaults_initialized = true
end
@@ -341,11 +354,7 @@ class Puppet::Settings
end
end
- def each
- @config.each { |name, object|
- yield name, object
- }
- end
+ def_delegator :@config, :each
# Iterate over each section name.
def eachsection
@@ -392,8 +401,8 @@ class Puppet::Settings
end
end
- if FULLY_DEPRECATED_SETTINGS.include?(str)
- Puppet.deprecation_warning("Setting #{str} is deprecated. See http://links.puppetlabs.com/env-settings-deprecations", "setting-#{str}")
+ if s = @config[str]
+ @deprecated_settings_that_have_been_configured << s if s.completely_deprecated?
end
@value_sets[:cli].set(str, value)
@@ -533,12 +542,12 @@ class Puppet::Settings
# If we get here and don't have any data, we just return and don't muck with the current state of the world.
return if data.nil?
- issue_deprecations(data)
-
- # If we get here then we have some data, so we need to clear out any previous settings that may have come from
- # config files.
+ # If we get here then we have some data, so we need to clear out any
+ # previous settings that may have come from config files.
unsafe_clear(false, false)
+ record_deprecations_from_puppet_conf(data)
+
# And now we can repopulate with the values from our last parsing of the config files.
@configuration_file = data
@@ -656,6 +665,7 @@ class Puppet::Settings
:terminus => TerminusSetting,
:duration => DurationSetting,
:ttl => TTLSetting,
+ :array => ArraySetting,
:enum => EnumSetting,
:priority => PrioritySetting,
:autosign => AutosignSetting,
@@ -816,9 +826,9 @@ class Puppet::Settings
# @param [Hash[Hash]] defs the settings to be defined. This argument is a hash of hashes; each key should be a symbol,
# which is basically the name of the setting that you are defining. The value should be another hash that specifies
# the parameters for the particular setting. Legal values include:
- # [:default] => required; this is a string value that will be used as a default value for a setting if no other
- # value is specified (via cli, config file, etc.) This string may include "variables", demarcated with $ or ${},
- # which will be interpolated with values of other settings.
+ # [:default] => not required; this is the value for the setting if no other value is specified (via cli, config file, etc.)
+ # For string settings this may include "variables", demarcated with $ or ${} which will be interpolated with values of other settings.
+ # The default value may also be a Proc that will be called only once to evaluate the default when the setting's value is retrieved.
# [:desc] => required; a description of the setting, used in documentation / help generation
# [:type] => not required, but highly encouraged! This specifies the data type that the setting represents. If
# you do not specify it, it will default to "string". Legal values include:
@@ -863,6 +873,8 @@ class Puppet::Settings
@hooks_to_call_on_application_initialization << tryconfig
end
end
+
+ @deprecated_setting_names << name if tryconfig.deprecated?
end
call.each do |setting|
@@ -888,6 +900,7 @@ class Puppet::Settings
end
add_user_resources(catalog, sections)
+ add_environment_resources(catalog, sections)
catalog
end
@@ -982,7 +995,7 @@ Generated on #{Time.now}.
# @return [Puppet::Settings::ChainedValues] An object to perform lookups
# @api public
def values(environment, section)
- ChainedValues.new(
+ @values[environment][section] ||= ChainedValues.new(
section,
environment,
value_sets_for(environment, section),
@@ -1053,24 +1066,50 @@ Generated on #{Time.now}.
private
- DEPRECATED_ENVIRONMENT_SETTINGS = [:manifest, :modulepath, :config_version].freeze
- FULLY_DEPRECATED_SETTINGS = [:templatedir, :manifestdir].freeze
- DEPRECATED_SETTINGS = (DEPRECATED_ENVIRONMENT_SETTINGS + FULLY_DEPRECATED_SETTINGS).freeze
+ DEPRECATION_REFS = {
+ [:manifest, :modulepath, :config_version, :templatedir, :manifestdir] =>
+ "See http://links.puppetlabs.com/env-settings-deprecations"
+ }.freeze
- def issue_deprecations(data)
- sections = data.sections.inject([]) do |accum,entry|
+ # Record that we want to issue a deprecation warning later in the application
+ # initialization cycle when we have settings bootstrapped to the point where
+ # we can read the Puppet[:disable_warnings] setting.
+ #
+ # We are only recording warnings applicable to settings set in puppet.conf
+ # itself.
+ def record_deprecations_from_puppet_conf(puppet_conf)
+ conf_sections = puppet_conf.sections.inject([]) do |accum,entry|
accum << entry[1] if [:main, :master, :agent, :user].include?(entry[0])
accum
end
- sections.each do |section|
- DEPRECATED_ENVIRONMENT_SETTINGS.each do |s|
- Puppet.deprecation_warning("Setting #{s} is deprecated in puppet.conf. See http://links.puppetlabs.com/env-settings-deprecations", "puppet-conf-setting-#{s}") if !section.setting(s).nil?
+ conf_sections.each do |section|
+ section.settings.each do |conf_setting|
+ if setting = self.setting(conf_setting.name)
+ @deprecated_settings_that_have_been_configured << setting if setting.deprecated?
+ end
end
+ end
+ end
- FULLY_DEPRECATED_SETTINGS.each do |s|
- Puppet.deprecation_warning("Setting #{s} is deprecated. See http://links.puppetlabs.com/env-settings-deprecations", "setting-#{s}") if !section.setting(s).nil?
- end
+ def issue_deprecations
+ @deprecated_settings_that_have_been_configured.each do |setting|
+ issue_deprecation_warning(setting)
+ end
+ end
+
+ def issue_deprecation_warning(setting, msg = nil)
+ name = setting.name
+ ref = DEPRECATION_REFS.find { |params,reference| params.include?(name) }
+ ref = ref[1] if ref
+ case
+ when msg
+ msg << " #{ref}" if ref
+ Puppet.deprecation_warning(msg)
+ when setting.completely_deprecated?
+ Puppet.deprecation_warning("Setting #{name} is deprecated. #{ref}", "setting-#{name}")
+ when setting.allowed_on_commandline?
+ Puppet.deprecation_warning("Setting #{name} is deprecated in puppet.conf. #{ref}", "puppet-conf-setting-#{name}")
end
end
@@ -1085,6 +1124,20 @@ Generated on #{Time.now}.
obj
end
+ def add_environment_resources(catalog, sections)
+ path = self[:environmentpath]
+ envdir = path.split(File::PATH_SEPARATOR).first if path
+ configured_environment = self[:environment]
+ if configured_environment == "production" && envdir && Puppet::FileSystem.exist?(envdir)
+ configured_environment_path = File.join(envdir, configured_environment)
+ catalog.add_resource(
+ Puppet::Resource.new(:file,
+ configured_environment_path,
+ :parameters => { :ensure => 'directory' })
+ )
+ end
+ end
+
def add_user_resources(catalog, sections)
return unless Puppet.features.root?
return if Puppet.features.microsoft_windows?
@@ -1144,6 +1197,7 @@ Generated on #{Time.now}.
# @return nil
def clear_everything_for_tests()
unsafe_clear(true, true)
+ @configuration_file = nil
@global_defaults_initialized = false
@app_defaults_initialized = false
end
@@ -1177,6 +1231,8 @@ Generated on #{Time.now}.
#
# @api public
class ChainedValues
+ ENVIRONMENT_SETTING = "environment".freeze
+
# @see Puppet::Settings.values
# @api private
def initialize(mode, environment, value_sets, defaults)
@@ -1241,50 +1297,54 @@ Generated on #{Time.now}.
private
def convert(value)
- return nil if value.nil?
- return value unless value.is_a? String
- value.gsub(/\$(\w+)|\$\{(\w+)\}/) do |value|
- varname = $2 || $1
- if varname == "environment" && @environment
- @environment
- elsif varname == "run_mode"
- @mode
- elsif !(pval = interpolate(varname.to_sym)).nil?
- pval
- else
- raise InterpolationError, "Could not find value for #{value}"
+ case value
+ when nil
+ nil
+ when String
+ value.gsub(/\$(\w+)|\$\{(\w+)\}/) do |value|
+ varname = $2 || $1
+ if varname == ENVIRONMENT_SETTING && @environment
+ @environment
+ elsif varname == "run_mode"
+ @mode
+ elsif !(pval = interpolate(varname.to_sym)).nil?
+ pval
+ else
+ raise InterpolationError, "Could not find value for #{value}"
+ end
end
+ else
+ value
end
end
end
class Values
+ extend Forwardable
+
def initialize(name, defaults)
@name = name
@values = {}
@defaults = defaults
end
- def include?(name)
- @values.include?(name)
- end
+ def_delegator :@values, :include?
+ def_delegator :@values, :[], :lookup
def set(name, value)
- if !@defaults[name]
+ default = @defaults[name]
+
+ if !default
raise ArgumentError,
"Attempt to assign a value to unknown setting #{name.inspect}"
end
- if @defaults[name].has_hook?
- @defaults[name].handle(value)
+ if default.has_hook?
+ default.handle(value)
end
@values[name] = value
end
-
- def lookup(name)
- @values[name]
- end
end
class ValuesFromSection
@@ -1324,12 +1384,9 @@ Generated on #{Time.now}.
end
def conf
- unless @conf
- if environments = Puppet.lookup(:environments)
- @conf = environments.get_conf(@environment_name)
- end
- end
- return @conf
+ @conf ||= if environments = Puppet.lookup(:environments)
+ environments.get_conf(@environment_name)
+ end
end
end
end
diff --git a/lib/puppet/settings/array_setting.rb b/lib/puppet/settings/array_setting.rb
new file mode 100644
index 000000000..82558c3bb
--- /dev/null
+++ b/lib/puppet/settings/array_setting.rb
@@ -0,0 +1,17 @@
+class Puppet::Settings::ArraySetting < Puppet::Settings::BaseSetting
+
+ def type
+ :array
+ end
+
+ def munge(value)
+ case value
+ when String
+ value.split(/\s*,\s*/)
+ when Array
+ value
+ else
+ raise ArgumentError, "Expected an Array or String, got a #{value.class}"
+ end
+ end
+end
diff --git a/lib/puppet/settings/base_setting.rb b/lib/puppet/settings/base_setting.rb
index e6c12de42..e7c8cf17f 100644
--- a/lib/puppet/settings/base_setting.rb
+++ b/lib/puppet/settings/base_setting.rb
@@ -3,7 +3,7 @@ require 'puppet/settings/errors'
# The base setting type
class Puppet::Settings::BaseSetting
attr_accessor :name, :desc, :section, :default, :call_on_define, :call_hook
- attr_reader :short
+ attr_reader :short, :deprecated
def self.available_call_hook_values
[:on_define_and_write, :on_initialize_and_write, :on_write_only]
@@ -122,6 +122,9 @@ class Puppet::Settings::BaseSetting
end
def default(check_application_defaults_first = false)
+ if @default.is_a? Proc
+ @default = @default.call
+ end
return @default unless check_application_defaults_first
return @settings.value(name, :application_defaults, true) || @default
end
@@ -152,9 +155,12 @@ class Puppet::Settings::BaseSetting
str.gsub(/^/, " ")
end
- # Retrieves the value, or if it's not set, retrieves the default.
- def value
- @settings.value(self.name)
+ # @param bypass_interpolation [Boolean] Set this true to skip the
+ # interpolation step, returning the raw setting value. Defaults to false.
+ # @return [String] Retrieves the value, or if it's not set, retrieves the default.
+ # @api public
+ def value(bypass_interpolation = false)
+ @settings.value(self.name, nil, bypass_interpolation)
end
# Modify the value when it is first evaluated
@@ -165,4 +171,25 @@ class Puppet::Settings::BaseSetting
def set_meta(meta)
Puppet.notice("#{name} does not support meta data. Ignoring.")
end
+
+ def deprecated=(deprecation)
+ raise(ArgumentError, "'#{deprecation}' is an unknown setting deprecation state. Must be either :completely or :allowed_on_commandline") unless [:completely, :allowed_on_commandline].include?(deprecation)
+ @deprecated = deprecation
+ end
+
+ def deprecated?
+ !!@deprecated
+ end
+
+ # True if we should raise a deprecation_warning if the setting is submitted
+ # on the commandline or is set in puppet.conf.
+ def completely_deprecated?
+ @deprecated == :completely
+ end
+
+ # True if we should raise a deprecation_warning if the setting is found in
+ # puppet.conf, but not if the user sets it on the commandline
+ def allowed_on_commandline?
+ @deprecated == :allowed_on_commandline
+ end
end
diff --git a/lib/puppet/settings/environment_conf.rb b/lib/puppet/settings/environment_conf.rb
index 187f963d7..c6fe42c66 100644
--- a/lib/puppet/settings/environment_conf.rb
+++ b/lib/puppet/settings/environment_conf.rb
@@ -51,8 +51,32 @@ class Puppet::Settings::EnvironmentConf
end
def manifest
- get_setting(:manifest, File.join(@path_to_env, "manifests")) do |manifest|
- absolute(manifest)
+ puppet_conf_manifest = Pathname.new(Puppet.settings.value(:default_manifest))
+ disable_per_environment_manifest = Puppet.settings.value(:disable_per_environment_manifest)
+
+ fallback_manifest_directory =
+ if puppet_conf_manifest.absolute?
+ puppet_conf_manifest.to_s
+ else
+ File.join(@path_to_env, puppet_conf_manifest.to_s)
+ end
+
+ if disable_per_environment_manifest
+ environment_conf_manifest = absolute(raw_setting(:manifest))
+ if environment_conf_manifest && fallback_manifest_directory != environment_conf_manifest
+ errmsg = ["The 'disable_per_environment_manifest' setting is true, but the",
+ "environment located at #{@path_to_env} has a manifest setting in its",
+ "environment.conf of '#{environment_conf_manifest}' which does not match",
+ "the default_manifest setting '#{puppet_conf_manifest}'. If this",
+ "environment is expecting to find modules in",
+ "'#{environment_conf_manifest}', they will not be available!"]
+ Puppet.err(errmsg.join(' '))
+ end
+ fallback_manifest_directory.to_s
+ else
+ get_setting(:manifest, fallback_manifest_directory) do |manifest|
+ absolute(manifest)
+ end
end
end
@@ -82,6 +106,11 @@ class Puppet::Settings::EnvironmentConf
end
end
+ def raw_setting(setting_name)
+ setting = section.setting(setting_name) if section
+ setting.value if setting
+ end
+
private
def self.validate(path_to_conf_file, config)
@@ -103,8 +132,7 @@ class Puppet::Settings::EnvironmentConf
end
def get_setting(setting_name, default = nil)
- setting = section.setting(setting_name) if section
- value = setting.value if setting
+ value = raw_setting(setting_name)
value ||= default
yield value
end
diff --git a/lib/puppet/settings/file_setting.rb b/lib/puppet/settings/file_setting.rb
index e20767374..800a9b7c4 100644
--- a/lib/puppet/settings/file_setting.rb
+++ b/lib/puppet/settings/file_setting.rb
@@ -219,7 +219,15 @@ class Puppet::Settings::FileSetting < Puppet::Settings::StringSetting
Puppet::Util::SUIDManager.asuser(*chown) do
# Update the umask to make non-executable files
Puppet::Util.withumask(File.umask ^ 0111) do
- yield mode ? mode.to_i : 0640
+ mode = case mode.class
+ when String
+ mode.to_i(8)
+ when NilClass
+ 0640
+ else
+ mode
+ end
+ yield mode
end
end
end
diff --git a/lib/puppet/settings/priority_setting.rb b/lib/puppet/settings/priority_setting.rb
index 707b8ab82..66443398f 100644
--- a/lib/puppet/settings/priority_setting.rb
+++ b/lib/puppet/settings/priority_setting.rb
@@ -5,12 +5,12 @@ require 'puppet/settings/base_setting'
class Puppet::Settings::PrioritySetting < Puppet::Settings::BaseSetting
PRIORITY_MAP =
if Puppet::Util::Platform.windows?
- require 'win32/process'
+ require 'puppet/util/windows/process'
{
- :high => Process::HIGH_PRIORITY_CLASS,
- :normal => Process::NORMAL_PRIORITY_CLASS,
- :low => Process::BELOW_NORMAL_PRIORITY_CLASS,
- :idle => Process::IDLE_PRIORITY_CLASS
+ :high => Puppet::Util::Windows::Process::HIGH_PRIORITY_CLASS,
+ :normal => Puppet::Util::Windows::Process::NORMAL_PRIORITY_CLASS,
+ :low => Puppet::Util::Windows::Process::BELOW_NORMAL_PRIORITY_CLASS,
+ :idle => Puppet::Util::Windows::Process::IDLE_PRIORITY_CLASS
}
else
{
diff --git a/lib/puppet/ssl.rb b/lib/puppet/ssl.rb
index 596feb933..f22c82d0e 100644
--- a/lib/puppet/ssl.rb
+++ b/lib/puppet/ssl.rb
@@ -4,6 +4,7 @@ require 'openssl'
module Puppet::SSL # :nodoc:
CA_NAME = "ca"
+ require 'puppet/ssl/configuration'
require 'puppet/ssl/host'
require 'puppet/ssl/oids'
require 'puppet/ssl/validator'
diff --git a/lib/puppet/ssl/certificate_authority.rb b/lib/puppet/ssl/certificate_authority.rb
index 7caf44978..3ffc58463 100644
--- a/lib/puppet/ssl/certificate_authority.rb
+++ b/lib/puppet/ssl/certificate_authority.rb
@@ -243,14 +243,23 @@ class Puppet::SSL::CertificateAuthority
def revoke(name)
raise ArgumentError, "Cannot revoke certificates when the CRL is disabled" unless crl
- if cert = Puppet::SSL::Certificate.indirection.find(name)
- serial = cert.content.serial
- elsif name =~ /^0x[0-9A-Fa-f]+$/
- serial = name.hex
- elsif ! serial = inventory.serial(name)
+ cert = Puppet::SSL::Certificate.indirection.find(name)
+
+ serials = if cert
+ [cert.content.serial]
+ elsif name =~ /^0x[0-9A-Fa-f]+$/
+ [name.hex]
+ else
+ inventory.serials(name)
+ end
+
+ if serials.empty?
raise ArgumentError, "Could not find a serial number for #{name}"
end
- crl.revoke(serial, host.key.content)
+
+ serials.each do |s|
+ crl.revoke(s, host.key.content)
+ end
end
# This initializes our CA so it actually works. This should be a private
diff --git a/lib/puppet/ssl/certificate_authority/autosign_command.rb b/lib/puppet/ssl/certificate_authority/autosign_command.rb
index d4533bab9..822b374ff 100644
--- a/lib/puppet/ssl/certificate_authority/autosign_command.rb
+++ b/lib/puppet/ssl/certificate_authority/autosign_command.rb
@@ -1,4 +1,5 @@
require 'puppet/ssl/certificate_authority'
+require 'puppet/file_system/uniquefile'
# This class wraps a given command and invokes it with a CSR name and body to
# determine if the given CSR should be autosigned
@@ -21,7 +22,7 @@ class Puppet::SSL::CertificateAuthority::AutosignCommand
name = csr.name
cmd = [@path, name]
- output = Puppet::FileSystem::Tempfile.open('puppet-csr') do |csr_file|
+ output = Puppet::FileSystem::Uniquefile.open_tmp('puppet-csr') do |csr_file|
csr_file.write(csr.to_s)
csr_file.flush
diff --git a/lib/puppet/ssl/host.rb b/lib/puppet/ssl/host.rb
index 566ec18f5..442c648bf 100644
--- a/lib/puppet/ssl/host.rb
+++ b/lib/puppet/ssl/host.rb
@@ -222,8 +222,9 @@ To fix this, remove the certificate from both the master and the agent and then
On the master:
puppet cert clean #{Puppet[:certname]}
On the agent:
- rm -f #{Puppet[:hostcert]}
- puppet agent -t
+ 1a. On most platforms: find #{Puppet[:ssldir]} -name #{Puppet[:certname]}.pem -delete
+ 1b. On Windows: del "#{Puppet[:ssldir]}/#{Puppet[:certname]}.pem" /f
+ 2. puppet agent -t
ERROR_STRING
end
end
diff --git a/lib/puppet/ssl/inventory.rb b/lib/puppet/ssl/inventory.rb
index e3ad3121f..83b53889a 100644
--- a/lib/puppet/ssl/inventory.rb
+++ b/lib/puppet/ssl/inventory.rb
@@ -37,14 +37,19 @@ class Puppet::SSL::Inventory
# Find the serial number for a given certificate.
def serial(name)
+ Puppet.deprecation_warning 'Inventory#serial is deprecated, use Inventory#serials instead.'
return nil unless Puppet::FileSystem.exist?(@path)
+ serials(name).first
+ end
- File.readlines(@path).each do |line|
- next unless line =~ /^(\S+).+\/CN=#{name}$/
-
- return Integer($1)
- end
+ # Find all serial numbers for a given certificate. If none can be found, returns
+ # an empty array.
+ def serials(name)
+ return [] unless Puppet::FileSystem.exist?(@path)
- return nil
+ File.readlines(@path).collect do |line|
+ /^(\S+).+\/CN=#{name}$/.match(line)
+ end.compact.map { |m| Integer(m[1]) }
end
+
end
diff --git a/lib/puppet/ssl/validator/default_validator.rb b/lib/puppet/ssl/validator/default_validator.rb
index e8e1d16e1..1f31499e2 100644
--- a/lib/puppet/ssl/validator/default_validator.rb
+++ b/lib/puppet/ssl/validator/default_validator.rb
@@ -1,4 +1,5 @@
require 'openssl'
+require 'puppet/ssl'
# Perform peer certificate verification against the known CA.
# If there is no CA information known, then no verification is performed
diff --git a/lib/puppet/ssl/validator/no_validator.rb b/lib/puppet/ssl/validator/no_validator.rb
index 1141b6952..b019369cc 100644
--- a/lib/puppet/ssl/validator/no_validator.rb
+++ b/lib/puppet/ssl/validator/no_validator.rb
@@ -1,3 +1,6 @@
+require 'openssl'
+require 'puppet/ssl'
+
# Performs no SSL verification
# @api private
#
diff --git a/lib/puppet/transaction.rb b/lib/puppet/transaction.rb
index ab74ed385..53118755e 100644
--- a/lib/puppet/transaction.rb
+++ b/lib/puppet/transaction.rb
@@ -47,6 +47,33 @@ class Puppet::Transaction
@prefetched_providers = Hash.new { |h,k| h[k] = {} }
end
+ # Invoke the pre_run_check hook in every resource in the catalog.
+ # This should (only) be called by Transaction#evaluate before applying
+ # the catalog.
+ #
+ # @see Puppet::Transaction#evaluate
+ # @see Puppet::Type#pre_run_check
+ # @raise [Puppet::Error] If any pre-run checks failed.
+ # @return [void]
+ def perform_pre_run_checks
+ prerun_errors = {}
+
+ @catalog.vertices.each do |res|
+ begin
+ res.pre_run_check
+ rescue Puppet::Error => detail
+ prerun_errors[res] = detail
+ end
+ end
+
+ unless prerun_errors.empty?
+ prerun_errors.each do |res, detail|
+ res.log_exception(detail)
+ end
+ raise Puppet::Error, "Some pre-run checks failed"
+ end
+ end
+
# This method does all the actual work of running a transaction. It
# collects all of the changes, executes them, and responds to any
# necessary events.
@@ -55,6 +82,8 @@ class Puppet::Transaction
generator = AdditionalResourceGenerator.new(@catalog, relationship_graph, @prioritizer)
@catalog.vertices.each { |resource| generator.generate_additional_resources(resource) }
+ perform_pre_run_checks
+
Puppet.info "Applying configuration version '#{catalog.version}'" if catalog.version
continue_while = lambda { !stop_processing? }
@@ -138,7 +167,7 @@ class Puppet::Transaction
end
def relationship_graph
- catalog.relationship_graph
+ catalog.relationship_graph(@prioritizer)
end
def resource_status(resource)
diff --git a/lib/puppet/transaction/resource_harness.rb b/lib/puppet/transaction/resource_harness.rb
index 3f02c1436..f57677c26 100644
--- a/lib/puppet/transaction/resource_harness.rb
+++ b/lib/puppet/transaction/resource_harness.rb
@@ -164,10 +164,23 @@ class Puppet::Transaction::ResourceHarness
event
end
+ # This method is an ugly hack because, given a Time object with nanosecond
+ # resolution, roundtripped through YAML serialization, the Time object will
+ # be truncated to microseconds.
+ # For audit purposes, this code special cases this comparison, and compares
+ # the two objects by their second and microsecond components. tv_sec is the
+ # number of seconds since the epoch, and tv_usec is only the microsecond
+ # portion of time. This compare satisfies compatibility requirements for
+ # Ruby 1.8.7, where to_r does not exist on the Time class.
+ def are_audited_values_equal(a, b)
+ a == b || (a.is_a?(Time) && b.is_a?(Time) && a.tv_sec == b.tv_sec && a.tv_usec == b.tv_usec)
+ end
+ private :are_audited_values_equal
+
def audit_event(event, property)
event.audited = true
event.status = "audit"
- if event.historical_value != event.previous_value
+ if !are_audited_values_equal(event.historical_value, event.previous_value)
event.message = "audit change: previously recorded value #{property.is_to_s(event.historical_value)} has been changed to #{property.is_to_s(event.previous_value)}"
end
@@ -175,7 +188,7 @@ class Puppet::Transaction::ResourceHarness
end
def audit_message(param, do_audit, historical_value, current_value)
- if do_audit && historical_value && historical_value != current_value
+ if do_audit && historical_value && !are_audited_values_equal(historical_value, current_value)
" (previously recorded value was #{param.is_to_s(historical_value)})"
else
""
@@ -196,7 +209,7 @@ class Puppet::Transaction::ResourceHarness
def capture_audit_events(resource, context)
context.audited_params.each do |param_name|
if context.historical_values.include?(param_name)
- if context.historical_values[param_name] != context.current_values[param_name] && !context.synced_params.include?(param_name)
+ if !are_audited_values_equal(context.historical_values[param_name], context.current_values[param_name]) && !context.synced_params.include?(param_name)
parameter = resource.parameter(param_name)
event = audit_event(create_change_event(parameter,
context.current_values[param_name],
diff --git a/lib/puppet/type.rb b/lib/puppet/type.rb
index f382f30ca..208d860f0 100644
--- a/lib/puppet/type.rb
+++ b/lib/puppet/type.rb
@@ -297,9 +297,8 @@ class Type
end
# Returns the documentation for a given meta-parameter of this type.
- # @todo the type for the param metaparam
- # @param metaparam [??? Puppet::Parameter] the meta-parameter to get documentation for.
- # @return [String] the documentation associated with the given meta-parameter, or nil of not such documentation
+ # @param metaparam [Puppet::Parameter] the meta-parameter to get documentation for.
+ # @return [String] the documentation associated with the given meta-parameter, or nil of no such documentation
# exists.
# @raise if the given metaparam is not a meta-parameter in this type
#
@@ -308,7 +307,7 @@ class Type
end
# Creates a new meta-parameter.
- # This creates a new meta-parameter that is added to all types.
+ # This creates a new meta-parameter that is added to this and all inheriting types.
# @param name [Symbol] the name of the parameter
# @param options [Hash] a hash with options.
# @option options [Class<inherits Puppet::Parameter>] :parent (Puppet::Parameter) the super class of this parameter
@@ -349,9 +348,9 @@ class Type
param
end
- # Returns parameters that act as a key.
+ # Returns the list of parameters that comprise the composite key / "uniqueness key".
# All parameters that return true from #isnamevar? or is named `:name` are included in the returned result.
- # @todo would like a better explanation
+ # @see uniqueness_key
# @return [Array<Puppet::Parameter>] WARNING: this return type is uncertain
def self.key_attribute_parameters
@key_attribute_parameters ||= (
@@ -361,8 +360,9 @@ class Type
)
end
- # Returns cached {key_attribute_parameters} names
- # @todo what is a 'key_attribute' ?
+ # Returns cached {key_attribute_parameters} names.
+ # Key attributes are properties and parameters that comprise a composite key
+ # or "uniqueness key".
# @return [Array<String>] cached key_attribute names
#
def self.key_attributes
@@ -408,8 +408,9 @@ class Type
end
end
- # Produces a _uniqueness_key_
- # @todo Explain what a uniqueness_key is
+ # Produces a resource's _uniqueness_key_ (or composite key).
+ # This key is an array of all key attributes' values. Each distinct tuple must be unique for each resource type.
+ # @see key_attributes
# @return [Object] an object that is a _uniqueness_key_ for this object
#
def uniqueness_key
@@ -661,10 +662,8 @@ class Type
nil
end
- # Removes a property from the object; useful in testing or in cleanup
+ # Removes an attribute from the object; useful in testing or in cleanup
# when an error has been encountered
- # @todo Incomprehensible - the comment says "Remove a property", the code refers to @parameters, and
- # the method parameter is called "attr" - What is it, property, parameter, both (i.e an attribute) or what?
# @todo Don't know what the attr is (name or Property/Parameter?). Guessing it is a String name...
# @todo Is it possible to delete a meta-parameter?
# @todo What does delete mean? Is it deleted from the type or is its value state 'is'/'should' deleted?
@@ -680,9 +679,7 @@ class Type
end
end
- # Iterates over the existing properties.
- # @todo what does this mean? As opposed to iterating over the "non existing properties" ??? Is it an
- # iteration over those properties that have state? CONFUSING.
+ # Iterates over the properties that were set on this resource.
# @yieldparam property [Puppet::Property] each property
# @return [void]
def eachproperty
@@ -725,18 +722,18 @@ class Type
(prop = @parameters[name] and prop.is_a?(Puppet::Property)) ? prop.should : nil
end
- # Creates an instance to represent/manage the given attribute.
- # Requires either the attribute name or class as the first argument, then an optional hash of
- # attributes to set during initialization.
- # @todo The original comment is just wrong - the method does not accept a hash of options
- # @todo Detective work required; this method interacts with provider to ask if it supports a parameter of
- # the given class. it then returns the parameter if it exists, otherwise creates a parameter
- # with its :resource => self.
+ # Registers an attribute to this resource type insance.
+ # Requires either the attribute name or class as its argument.
+ # This is a noop if the named property/parameter is not supported
+ # by this resource. Otherwise, an attribute instance is created
+ # and kept in this resource's parameters hash.
# @overload newattr(name)
- # @param name [String] Unclear what name is (probably a symbol) - Needs investigation.
+ # @param name [Symbol] symbolic name of the attribute
# @overload newattr(klass)
- # @param klass [Class] a class supported as an attribute class - Needs clarification what that means.
- # @return [???] Probably returns a new instance of the class - Needs investigation.
+ # @param klass [Class] a class supported as an attribute class, i.e. a subclass of
+ # Parameter or Property
+ # @return [Object] An instance of the named Parameter or Property class associated
+ # to this resource type instance, or nil if the attribute is not supported
#
def newattr(name)
if name.is_a?(Class)
@@ -773,17 +770,17 @@ class Type
@parameters[name.to_sym]
end
- # Returns a shallow copy of this object's hash of parameters.
- # @todo Add that this is not only "parameters", but also "properties" and "meta-parameters" ?
+ # Returns a shallow copy of this object's hash of attributes by name.
+ # Note that his not only comprises parameters, but also properties and metaparameters.
# Changes to the contained parameters will have an effect on the parameters of this type, but changes to
# the returned hash does not.
- # @return [Hash{String => Puppet:???Parameter}] a new hash being a shallow copy of the parameters map name to parameter
+ # @return [Hash{String => Object}] a new hash being a shallow copy of the parameters map name to parameter
def parameters
@parameters.dup
end
- # @return [Boolean] Returns whether the property given by name is defined or not.
- # @todo what does it mean to be defined?
+ # @return [Boolean] Returns whether the attribute given by name has been added
+ # to this resource or not.
def propertydefined?(name)
name = name.intern unless name.is_a? Symbol
@parameters.include?(name)
@@ -967,6 +964,22 @@ class Type
[]
end
+ # Lifecycle method for a resource. This is called during graph creation.
+ # It should perform any consistency checking of the catalog and raise a
+ # Puppet::Error if the transaction should be aborted.
+ #
+ # It differs from the validate method, since it is called later during
+ # initialization and can rely on self.catalog to have references to all
+ # resources that comprise the catalog.
+ #
+ # @see Puppet::Transaction#add_vertex
+ # @raise [Puppet::Error] If the pre-run check failed.
+ # @return [void]
+ # @abstract a resource type may implement this method to perform
+ # validation checks that can query the complete catalog
+ def pre_run_check
+ end
+
# Flushes the provider if supported by the provider, else no action.
# This is called by the transaction.
# @todo What does Flushing the provider mean? Why is it interesting to know that this is
@@ -1026,7 +1039,7 @@ class Type
def retrieve
fail "Provider #{provider.class.name} is not functional on this host" if self.provider.is_a?(Puppet::Provider) and ! provider.class.suitable?
- result = Puppet::Resource.new(type, title)
+ result = Puppet::Resource.new(self.class, title)
# Provide the name, so we know we'll always refer to a real thing
result[:name] = self[:name] unless self[:name] == title
@@ -1061,7 +1074,7 @@ class Type
# @api private
def retrieve_resource
resource = retrieve
- resource = Resource.new(type, title, :parameters => resource) if resource.is_a? Hash
+ resource = Resource.new(self.class, title, :parameters => resource) if resource.is_a? Hash
resource
end
@@ -1180,9 +1193,8 @@ class Type
raise Puppet::Error, "Title or name must be provided" unless title
# Now create our resource.
- resource = Puppet::Resource.new(self.name, title)
+ resource = Puppet::Resource.new(self, title)
resource.catalog = hash.delete(:catalog)
- resource.resource_type = self
hash.each do |param, value|
resource[param] = value
@@ -1314,7 +1326,19 @@ class Type
newmetaparam(:loglevel) do
desc "Sets the level that information will be logged.
The log levels have the biggest impact when logs are sent to
- syslog (which is currently the default)."
+ syslog (which is currently the default).
+
+ The order of the log levels, in decreasing priority, is:
+
+ * `crit`
+ * `emerg`
+ * `alert`
+ * `err`
+ * `warning`
+ * `notice`
+ * `info` / `verbose`
+ * `debug`
+ "
defaultto :notice
newvalues(*Puppet::Util::Log.levels)
@@ -1810,8 +1834,8 @@ class Type
}.join
end
- # @todo this does what? where and how?
- # @return [String] the name of the provider
+ # For each resource, the provider param defaults to
+ # the type's default provider
defaultto {
prov = @resource.class.defaultprovider
prov.name if prov
@@ -1912,11 +1936,12 @@ class Type
# All of the relationship code.
# Adds a block producing a single name (or list of names) of the given resource type name to autorequire.
+ # Resources in the catalog that have the named type and a title that is included in the result will be linked
+ # to the calling resource as a requirement.
+ #
# @example Autorequire the files File['foo', 'bar']
# autorequire( 'file', {|| ['foo', 'bar'] })
#
- # @todo original = _"Specify a block for generating a list of objects to autorequire.
- # This makes it so that you don't have to manually specify things that you clearly require."_
# @param name [String] the name of a type of which one or several resources should be autorequired e.g. "file"
# @yield [ ] a block returning list of names of given type to auto require
# @yieldreturn [String, Array<String>] one or several resource names for the named type
@@ -1979,12 +2004,9 @@ class Type
reqs
end
- # Builds the dependencies associated with an individual object.
- # @todo Which object is the "individual object", as opposed to "object as a group?" or should it simply
- # be "this object" as in "this resource" ?
- # @todo Does this method "build dependencies" or "build what it depends on" ... CONFUSING
+ # Builds the dependencies associated with this resource.
#
- # @return [Array<???>] list of WHAT? resources? edges?
+ # @return [Array<Puppet::Relationship>] list of relationships to other resources
def builddepends
# Handle the requires
self.class.relationship_params.collect do |klass|
@@ -1994,8 +2016,8 @@ class Type
end.flatten.reject { |r| r.nil? }
end
- # Sets the initial list of tags...
- # @todo The initial list of tags, that ... that what?
+ # Sets the initial list of tags to associate to this resource.
+ #
# @return [void] ???
def tags=(list)
tag(self.class.name)
@@ -2386,8 +2408,8 @@ class Type
self.ref
end
- # @todo What to resource? Which one of the resource forms is prroduced? returned here?
- # @return [??? Resource] a resource that WHAT???
+ # Convert this resource type instance to a Puppet::Resource.
+ # @return [Puppet::Resource] Returns a serializable representation of this resource
#
def to_resource
resource = self.retrieve_resource
diff --git a/lib/puppet/type/exec.rb b/lib/puppet/type/exec.rb
index 732a0c1c5..325219bf4 100644
--- a/lib/puppet/type/exec.rb
+++ b/lib/puppet/type/exec.rb
@@ -77,9 +77,27 @@ module Puppet
defaultto "0"
attr_reader :output
- desc "The expected return code(s). An error will be returned if the
- executed command returns something else. Defaults to 0. Can be
- specified as an array of acceptable return codes or a single value."
+ desc "The expected exit code(s). An error will be returned if the
+ executed command has some other exit code. Defaults to 0. Can be
+ specified as an array of acceptable exit codes or a single value.
+
+ On POSIX systems, exit codes are always integers between 0 and 255.
+
+ On Windows, **most** exit codes should be integers between 0
+ and 2147483647.
+
+ Larger exit codes on Windows can behave inconsistently across different
+ tools. The Win32 APIs define exit codes as 32-bit unsigned integers, but
+ both the cmd.exe shell and the .NET runtime cast them to signed
+ integers. This means some tools will report negative numbers for exit
+ codes above 2147483647. (For example, cmd.exe reports 4294967295 as -1.)
+ Since Puppet uses the plain Win32 APIs, it will report the very large
+ number instead of the negative number, which might not be what you
+ expect if you got the exit code from a cmd.exe session.
+
+ Microsoft recommends against using negative/very large exit codes, and
+ you should avoid them when possible. To convert a negative exit code to
+ the positive one Puppet will use, add it to 4294967296."
# Make output a bit prettier
def change_to_s(currentvalue, newvalue)
@@ -183,10 +201,12 @@ module Puppet
Please note that the $HOME environment variable is not automatically set
when using this attribute."
- # Most validation is handled by the SUIDManager class.
validate do |user|
- self.fail "Only root can execute commands as other users" unless Puppet.features.root?
- self.fail "Unable to execute commands as other users on Windows" if Puppet.features.microsoft_windows?
+ if Puppet.features.microsoft_windows?
+ self.fail "Unable to execute commands as other users on Windows"
+ elsif !Puppet.features.root? && resource.current_username() != user
+ self.fail "Only root can execute commands as other users"
+ end
end
end
@@ -382,7 +402,7 @@ module Puppet
newcheck(:unless) do
desc <<-'EOT'
If this parameter is set, then this `exec` will run unless
- the command returns 0. For example:
+ the command has an exit code of 0. For example:
exec { "/bin/echo root >> /usr/lib/cron/cron.allow":
path => "/usr/bin:/usr/sbin:/bin",
@@ -394,6 +414,8 @@ module Puppet
Note that this command follows the same rules as the main command,
which is to say that it must be fully qualified if the path is not set.
+ It also uses the same provider as the main command, so any behavior
+ that differs by provider will match.
EOT
validate do |cmds|
@@ -424,7 +446,7 @@ module Puppet
newcheck(:onlyif) do
desc <<-'EOT'
If this parameter is set, then this `exec` will only run if
- the command returns 0. For example:
+ the command has an exit code of 0. For example:
exec { "logrotate":
path => "/usr/bin:/usr/sbin:/bin",
@@ -435,6 +457,8 @@ module Puppet
Note that this command follows the same rules as the main command,
which is to say that it must be fully qualified if the path is not set.
+ It also uses the same provider as the main command, so any behavior
+ that differs by provider will match.
Also note that onlyif can take an array as its value, e.g.:
@@ -560,5 +584,9 @@ module Puppet
end
end
end
+
+ def current_username
+ Etc.getpwuid(Process.uid).name
+ end
end
end
diff --git a/lib/puppet/type/file.rb b/lib/puppet/type/file.rb
index 9f2d46b8d..851b97312 100644
--- a/lib/puppet/type/file.rb
+++ b/lib/puppet/type/file.rb
@@ -20,8 +20,7 @@ Puppet::Type.newtype(:file) do
@doc = "Manages files, including their content, ownership, and permissions.
The `file` type can manage normal files, directories, and symlinks; the
- type should be specified in the `ensure` attribute. Note that symlinks cannot
- be managed on Windows systems.
+ type should be specified in the `ensure` attribute.
File contents can be managed directly with the `content` attribute, or
downloaded from a remote source using the `source` attribute; the latter
@@ -126,17 +125,34 @@ Puppet::Type.newtype(:file) do
end
newparam(:recurse) do
- desc "Whether and how to do recursive file management. Options are:
-
- * `inf,true` --- Regular style recursion on both remote and local
- directory structure. See `recurselimit` to specify a limit to the
- recursion depth.
- * `remote` --- Descends recursively into the remote (source) directory
- but not the local (destination) directory. Allows copying of
- a few files into a directory containing many
- unmanaged files without scanning all the local files.
- This can only be used when a source parameter is specified.
- * `false` --- Default of no recursion.
+ desc "Whether to recursively manage the _contents_ of a directory. This attribute
+ is only used when `ensure => directory` is set. The allowed values are:
+
+ * `false` --- The default behavior. The contents of the directory will not be
+ automatically managed.
+ * `remote` --- If the `source` attribute is set, Puppet will automatically
+ manage the contents of the source directory (or directories), ensuring
+ that equivalent files and directories exist on the target system and
+ that their contents match.
+
+ Using `remote` will disable the `purge` attribute, but results in faster
+ catalog application than `recurse => true`.
+
+ The `source` attribute is mandatory when `recurse => remote`.
+ * `true` --- If the `source` attribute is set, this behaves similarly to
+ `recurse => remote`, automatically managing files from the source directory.
+
+ This also enables the `purge` attribute, which can delete unmanaged
+ files from a directory. See the description of `purge` for more details.
+
+ The `source` attribute is not mandatory when using `recurse => true`, so you
+ can enable purging in directories where all files are managed individually.
+
+ (Note: `inf` is a deprecated synonym for `true`.)
+
+ By default, setting recurse to `remote` or `true` will manage _all_
+ subdirectories. You can use the `recurselimit` attribute to limit the
+ recursion depth.
"
newvalues(:true, :false, :inf, :remote)
@@ -155,7 +171,24 @@ Puppet::Type.newtype(:file) do
end
newparam(:recurselimit) do
- desc "How deeply to do recursive management."
+ desc "How far Puppet should descend into subdirectories, when using
+ `ensure => directory` and either `recurse => true` or `recurse => remote`.
+ The recursion limit affects which files will be copied from the `source`
+ directory, as well as which files can be purged when `purge => true`.
+
+ Setting `recurselimit => 0` is the same as setting `recurse => false` ---
+ Puppet will manage the directory, but all of its contents will be treated
+ as unmanaged.
+
+ Setting `recurselimit => 1` will manage files and directories that are
+ directly inside the directory, but will not manage the contents of any
+ subdirectories.
+
+ Setting `recurselimit => 2` will manage the direct contents of the
+ directory, as well as the contents of the _first_ level of subdirectories.
+
+ And so on --- 3 will manage the contents of the second level of
+ subdirectories, etc."
newvalues(/^[0-9]+$/)
@@ -218,7 +251,7 @@ Puppet::Type.newtype(:file) do
newparam(:purge, :boolean => true, :parent => Puppet::Parameter::Boolean) do
desc "Whether unmanaged files should be purged. This option only makes
- sense when managing directories with `recurse => true`.
+ sense when `ensure => directory` and `recurse => true`.
* When recursively duplicating an entire directory with the `source`
attribute, `purge => true` will automatically purge any files
@@ -228,7 +261,14 @@ Puppet::Type.newtype(:file) do
specifically managed.
If you have a filebucket configured, the purged files will be uploaded,
- but if you do not, this will destroy data."
+ but if you do not, this will destroy data.
+
+ Unless `force => true` is set, purging will **not** delete directories,
+ although it will delete the files they contain.
+
+ If `recurselimit` is set and you aren't using `force => true`, purging
+ will obey the recursion limit; files in any subdirectories deeper than the
+ limit will be treated as unmanaged and left alone."
defaultto :false
end
diff --git a/lib/puppet/type/file/content.rb b/lib/puppet/type/file/content.rb
index c3681f8dc..2ac13becb 100644
--- a/lib/puppet/type/file/content.rb
+++ b/lib/puppet/type/file/content.rb
@@ -38,6 +38,7 @@ module Puppet
...but for larger files, this attribute is more useful when combined with the
[template](http://docs.puppetlabs.com/references/latest/function.html#template)
+ or [file](http://docs.puppetlabs.com/references/latest/function.html#file)
function.
EOT
@@ -206,9 +207,8 @@ module Puppet
end
def get_from_source(source_or_content, &block)
- source = source_or_content.uri
-
- request = Puppet::Indirector::Request.new(:file_content, :find, source.to_s, nil, :environment => resource.catalog.environment)
+ source = source_or_content.metadata.source
+ request = Puppet::Indirector::Request.new(:file_content, :find, source, nil, :environment => resource.catalog.environment)
request.do_request(:fileserver) do |req|
connection = Puppet::Network::HttpPool.http_instance(req.server, req.port)
diff --git a/lib/puppet/type/file/mode.rb b/lib/puppet/type/file/mode.rb
index 682e744cd..6f104947b 100644
--- a/lib/puppet/type/file/mode.rb
+++ b/lib/puppet/type/file/mode.rb
@@ -10,9 +10,14 @@ module Puppet
desc <<-'EOT'
The desired permissions mode for the file, in symbolic or numeric
- notation. Puppet uses traditional Unix permission schemes and translates
+ notation. This value should be specified as a quoted string; do not use
+ un-quoted numbers to represent file modes.
+
+ The `file` type uses traditional Unix permission schemes and translates
them to equivalent permissions for systems which represent permissions
- differently, including Windows.
+ differently, including Windows. For detailed ACL controls on Windows,
+ you can leave `mode` unmanaged and use
+ [the puppetlabs/acl module.](https://forge.puppetlabs.com/puppetlabs/acl)
Numeric modes should use the standard four-digit octal notation of
`<setuid/setgid/sticky><owner><group><other>` (e.g. 0644). Each of the
@@ -60,6 +65,10 @@ module Puppet
EOT
validate do |value|
+ if !value.is_a?(String)
+ Puppet.deprecation_warning("Non-string values for the file mode property are deprecated. It must be a string, " \
+ "either a symbolic mode like 'o+w,a+r' or an octal representation like '0644' or '755'.")
+ end
unless value.nil? or valid_symbolic_mode?(value)
raise Puppet::Error, "The file mode specification is invalid: #{value.inspect}"
end
@@ -77,7 +86,7 @@ module Puppet
def desired_mode_from_current(desired, current)
current = current.to_i(8) if current.is_a? String
- is_a_directory = @resource.stat and @resource.stat.directory?
+ is_a_directory = @resource.stat && @resource.stat.directory?
symbolic_mode_to_int(desired, current, is_a_directory)
end
diff --git a/lib/puppet/type/file/source.rb b/lib/puppet/type/file/source.rb
index ce704c5be..a7ebfc6a2 100644
--- a/lib/puppet/type/file/source.rb
+++ b/lib/puppet/type/file/source.rb
@@ -225,7 +225,10 @@ module Puppet
def copy_source_value(metadata_method)
param_name = (metadata_method == :checksum) ? :content : metadata_method
if resource[param_name].nil? or resource[param_name] == :absent
- resource[param_name] = metadata.send(metadata_method)
+ value = metadata.send(metadata_method)
+ # Force the mode value in file resources to be a string containing octal.
+ value = value.to_s(8) if param_name == :mode && value.is_a?(Numeric)
+ resource[param_name] = value
end
end
end
diff --git a/lib/puppet/type/group.rb b/lib/puppet/type/group.rb
index cf9eb2382..d5adaf455 100644
--- a/lib/puppet/type/group.rb
+++ b/lib/puppet/type/group.rb
@@ -168,7 +168,7 @@ module Puppet
newparam(:forcelocal, :boolean => true,
:required_features => :libuser,
:parent => Puppet::Parameter::Boolean) do
- desc "Forces the mangement of local accounts when accounts are also
+ desc "Forces the management of local accounts when accounts are also
being managed by some other NSS"
defaultto false
end
diff --git a/lib/puppet/type/mount.rb b/lib/puppet/type/mount.rb
index 8111a0d4a..620ea8dfc 100644
--- a/lib/puppet/type/mount.rb
+++ b/lib/puppet/type/mount.rb
@@ -206,7 +206,7 @@ module Puppet
newproperty(:dump) do
desc "Whether to dump the mount. Not all platform support this.
- Valid values are `1` or `0`. or `2` on FreeBSD, Default is `0`."
+ Valid values are `1` or `0` (or `2` on FreeBSD). Default is `0`."
if Facter.value(:operatingsystem) == "FreeBSD"
newvalue(%r{(0|1|2)})
@@ -214,8 +214,6 @@ module Puppet
newvalue(%r{(0|1)})
end
- newvalue(%r{(0|1)})
-
defaultto {
0 if @resource.managed?
}
diff --git a/lib/puppet/type/resources.rb b/lib/puppet/type/resources.rb
index e8dd08a92..7a4ffd8f3 100644
--- a/lib/puppet/type/resources.rb
+++ b/lib/puppet/type/resources.rb
@@ -19,11 +19,11 @@ Puppet::Type.newtype(:resources) do
end
newparam(:purge, :boolean => true, :parent => Puppet::Parameter::Boolean) do
- desc "Purge unmanaged resources. This will delete any resource
- that is not specified in your configuration
- and is not required by any specified resources.
- Purging ssh_authorized_keys this way is deprecated; see the
- purge_ssh_keys parameter of the user type for a better alternative."
+ desc "Whether to purge unmanaged resources. When set to `true`, this will
+ delete any resource that is not specified in your configuration and is not
+ autorequired by any managed resources. **Note:** The `ssh_authorized_key`
+ resource type can't be purged this way; instead, see the `purge_ssh_keys`
+ attribute of the `user` type."
defaultto :false
@@ -39,7 +39,7 @@ Puppet::Type.newtype(:resources) do
newparam(:unless_system_user) do
desc "This keeps system users from being purged. By default, it
- does not purge users whose UIDs are less than or equal to 500, but you can specify
+ does not purge users whose UIDs are less than the minimum UID for the system (typically 500 or 1000), but you can specify
a different UID as the inclusive limit."
newvalues(:true, :false, /^\d+$/)
@@ -49,7 +49,7 @@ Puppet::Type.newtype(:resources) do
when /^\d+/
Integer(value)
when :true, true
- 500
+ @resource.class.system_users_max_uid
when :false, false
false
when Integer; value
@@ -60,7 +60,7 @@ Puppet::Type.newtype(:resources) do
defaultto {
if @resource[:name] == "user"
- 500
+ @resource.class.system_users_max_uid
else
nil
end
@@ -68,26 +68,24 @@ Puppet::Type.newtype(:resources) do
end
newparam(:unless_uid) do
- desc "This keeps specific uids or ranges of uids from being purged when purge is true.
- Accepts ranges, integers and (mixed) arrays of both."
-
- munge do |value|
- case value
- when /^\d+/
- [Integer(value)]
- when Integer
- [value]
- when Range
- [value]
- when Array
- value
- when /^\[\d+/
- value.split(',').collect{|x| x.include?('..') ? Integer(x.split('..')[0])..Integer(x.split('..')[1]) : Integer(x) }
- else
- raise ArgumentError, "Invalid value #{value.inspect}"
- end
- end
- end
+ desc 'This keeps specific uids or ranges of uids from being purged when purge is true.
+ Accepts integers, integer strings, and arrays of integers or integer strings.
+ To specify a range of uids, consider using the range() function from stdlib.'
+
+ munge do |value|
+ value = [value] unless value.is_a? Array
+ value.flatten.collect do |v|
+ case v
+ when Integer
+ v
+ when String
+ Integer(v)
+ else
+ raise ArgumentError, "Invalid value #{v.inspect}."
+ end
+ end
+ end
+ end
def check(resource)
@checkmethod ||= "#{self[:name]}_check"
@@ -136,6 +134,13 @@ Puppet::Type.newtype(:resources) do
@resource_type
end
+ def self.deprecate_params(title,params)
+ return unless params
+ if title == 'cron' and ! params.select { |param| param.name.intern == :purge and param.value == true }.empty?
+ Puppet.deprecation_warning("Change notice: purging cron entries will be more aggressive in future versions, take care when updating your agents. See http://links.puppetlabs.com/puppet-aggressive-cron-purge")
+ end
+ end
+
# Make sure we don't purge users with specific uids
def user_check(resource)
return true unless self[:name] == "user"
@@ -146,13 +151,7 @@ Puppet::Type.newtype(:resources) do
unless_uids = self[:unless_uid]
return false if system_users.include?(resource[:name])
-
- if unless_uids && unless_uids.length > 0
- unless_uids.each do |unless_uid|
- return false if unless_uid == current_uid
- return false if unless_uid.respond_to?('include?') && unless_uid.include?(current_uid)
- end
- end
+ return false if unless_uids && unless_uids.include?(current_uid)
current_uid > self[:unless_system_user]
end
@@ -160,4 +159,29 @@ Puppet::Type.newtype(:resources) do
def system_users
%w{root nobody bin noaccess daemon sys}
end
+
+ def self.system_users_max_uid
+ return @system_users_max_uid if @system_users_max_uid
+
+ # First try to read the minimum user id from login.defs
+ if Puppet::FileSystem.exist?('/etc/login.defs')
+ @system_users_max_uid = Puppet::FileSystem.each_line '/etc/login.defs' do |line|
+ break $1.to_i - 1 if line =~ /^\s*UID_MIN\s+(\d+)(\s*#.*)?$/
+ end
+ end
+
+ # Otherwise, use a sensible default based on the OS family
+ @system_users_max_uid ||= case Facter.value(:osfamily)
+ when 'OpenBSD', 'FreeBSD'
+ 999
+ else
+ 499
+ end
+
+ @system_users_max_uid
+ end
+
+ def self.reset_system_users_max_uid!
+ @system_users_max_uid = nil
+ end
end
diff --git a/lib/puppet/type/ssh_authorized_key.rb b/lib/puppet/type/ssh_authorized_key.rb
index 12c8294b1..8b40907f3 100644
--- a/lib/puppet/type/ssh_authorized_key.rb
+++ b/lib/puppet/type/ssh_authorized_key.rb
@@ -1,24 +1,50 @@
module Puppet
newtype(:ssh_authorized_key) do
- @doc = "Manages SSH authorized keys. Currently only type 2 keys are
- supported.
+ @doc = "Manages SSH authorized keys. Currently only type 2 keys are supported.
- **Autorequires:** If Puppet is managing the user account in which this
- SSH key should be installed, the `ssh_authorized_key` resource will autorequire
- that user."
+ In their native habitat, SSH keys usually appear as a single long line. This
+ resource type requires you to split that line into several attributes. Thus, a
+ key that appears in your `~/.ssh/id_rsa.pub` file like this...
+
+ ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAy5mtOAMHwA2ZAIfW6Ap70r+I4EclYHEec5xIN59ROUjss23Skb1OtjzYpVPaPH8mSdSmsN0JHaBLiRcu7stl4O8D8zA4mz/vw32yyQ/Kqaxw8l0K76k6t2hKOGqLTY4aFbFISV6GDh7MYLn8KU7cGp96J+caO5R5TqtsStytsUhSyqH+iIDh4e4+BrwTc6V4Y0hgFxaZV5d18mLA4EPYKeG5+zyBCVu+jueYwFqM55E0tHbfiaIN9IzdLV+7NEEfdLkp6w2baLKPqWUBmuvPF1Mn3FwaFLjVsMT3GQeMue6b3FtUdTDeyAYoTxrsRo/WnDkS6Pa3YhrFwjtUqXfdaQ== nick@magpie.puppetlabs.lan
+
+ ...would translate to the following resource:
+
+ ssh_authorized_key { 'nick@magpie.puppetlabs.lan':
+ user => 'nick',
+ type => 'ssh-rsa',
+ key => 'AAAAB3NzaC1yc2EAAAABIwAAAQEAy5mtOAMHwA2ZAIfW6Ap70r+I4EclYHEec5xIN59ROUjss23Skb1OtjzYpVPaPH8mSdSmsN0JHaBLiRcu7stl4O8D8zA4mz/vw32yyQ/Kqaxw8l0K76k6t2hKOGqLTY4aFbFISV6GDh7MYLn8KU7cGp96J+caO5R5TqtsStytsUhSyqH+iIDh4e4+BrwTc6V4Y0hgFxaZV5d18mLA4EPYKeG5+zyBCVu+jueYwFqM55E0tHbfiaIN9IzdLV+7NEEfdLkp6w2baLKPqWUBmuvPF1Mn3FwaFLjVsMT3GQeMue6b3FtUdTDeyAYoTxrsRo/WnDkS6Pa3YhrFwjtUqXfdaQ==',
+ }
+
+ To ensure that only the currently approved keys are present, you can purge
+ unmanaged SSH keys on a per-user basis. Do this with the `user` resource
+ type's `purge_ssh_keys` attribute:
+
+ user { 'nick':
+ ensure => present,
+ purge_ssh_keys => true,
+ }
+
+ This will remove any keys in `~/.ssh/authorized_keys` that aren't being
+ managed with `ssh_authorized_key` resources. See the documentation of the
+ `user` type for more details.
+
+ **Autorequires:** If Puppet is managing the user account in which this
+ SSH key should be installed, the `ssh_authorized_key` resource will autorequire
+ that user."
ensurable
newparam(:name) do
desc "The SSH key comment. This attribute is currently used as a
- system-wide primary key and therefore has to be unique."
+ system-wide primary key and therefore has to be unique."
isnamevar
end
newproperty(:type) do
- desc "The encryption type used: ssh-dss or ssh-rsa."
+ desc "The encryption type used."
newvalues :'ssh-dss', :'ssh-rsa', :'ecdsa-sha2-nistp256', :'ecdsa-sha2-nistp384', :'ecdsa-sha2-nistp521', :'ssh-ed25519'
@@ -28,9 +54,15 @@ module Puppet
end
newproperty(:key) do
- desc "The public key itself; generally a long string of hex characters. The key attribute
- may not contain whitespace: Omit key headers (e.g. 'ssh-rsa') and key identifiers
- (e.g. 'joe@joescomputer.local') found in the public key file."
+ desc "The public key itself; generally a long string of hex characters. The `key`
+ attribute may not contain whitespace.
+
+ Make sure to omit the following in this attribute (and specify them in
+ other attributes):
+
+ * Key headers (e.g. 'ssh-rsa') --- put these in the `type` attribute.
+ * Key identifiers / comments (e.g. 'joe@joescomputer.local') --- put these in
+ the `name` attribute/resource title."
validate do |value|
raise Puppet::Error, "Key must not contain whitespace: #{value}" if value =~ /\s/
@@ -38,15 +70,15 @@ module Puppet
end
newproperty(:user) do
- desc "The user account in which the SSH key should be installed.
- The resource will automatically depend on this user."
+ desc "The user account in which the SSH key should be installed. The resource
+ will autorequire this user if it is being managed as a `user` resource."
end
newproperty(:target) do
desc "The absolute filename in which to store the SSH key. This
- property is optional and should only be used in cases where keys
- are stored in a non-standard location (i.e.` not in
- `~user/.ssh/authorized_keys`)."
+ property is optional and should only be used in cases where keys
+ are stored in a non-standard location (i.e.` not in
+ `~user/.ssh/authorized_keys`)."
defaultto :absent
@@ -69,7 +101,7 @@ module Puppet
end
newproperty(:options, :array_matching => :all) do
- desc "Key options, see sshd(8) for possible values. Multiple values
+ desc "Key options; see sshd(8) for possible values. Multiple values
should be specified as an array."
defaultto do :absent end
@@ -111,5 +143,11 @@ module Puppet
# If neither target nor user is defined, this is an error
raise Puppet::Error, "Attribute 'user' or 'target' is mandatory"
end
+
+ # regular expression suitable for use by a ParsedFile based provider
+ REGEX = /^(?:(.+) )?(ssh-dss|ssh-ed25519|ssh-rsa|ecdsa-sha2-nistp256|ecdsa-sha2-nistp384|ecdsa-sha2-nistp521) ([^ ]+) ?(.*)$/
+ def self.keyline_regex
+ REGEX
+ end
end
end
diff --git a/lib/puppet/type/sshkey.rb b/lib/puppet/type/sshkey.rb
index db3c52c85..2a3d5fedf 100644
--- a/lib/puppet/type/sshkey.rb
+++ b/lib/puppet/type/sshkey.rb
@@ -17,7 +17,7 @@ module Puppet
end
newproperty(:key) do
- desc "The key itself; generally a long string of hex digits."
+ desc "The key itself; generally a long string of uuencoded characters."
end
# FIXME This should automagically check for aliases to the hosts, just
diff --git a/lib/puppet/type/user.rb b/lib/puppet/type/user.rb
index ad6df8848..b9b26295f 100644
--- a/lib/puppet/type/user.rb
+++ b/lib/puppet/type/user.rb
@@ -297,7 +297,8 @@ module Puppet
newparam(:system, :boolean => true, :parent => Puppet::Parameter::Boolean) do
desc "Whether the user is a system user, according to the OS's criteria;
on most platforms, a UID less than or equal to 500 indicates a system
- user. Defaults to `false`."
+ user. This parameter is only used when the resource is created and will
+ not affect the UID when the user is present. Defaults to `false`."
defaultto false
end
@@ -567,23 +568,28 @@ module Puppet
newparam(:forcelocal, :boolean => true,
:required_features => :libuser,
:parent => Puppet::Parameter::Boolean) do
- desc "Forces the mangement of local accounts when accounts are also
+ desc "Forces the management of local accounts when accounts are also
being managed by some other NSS"
defaultto false
end
- def eval_generate
+ def generate
return [] if self[:purge_ssh_keys].empty?
find_unmanaged_keys
end
newparam(:purge_ssh_keys) do
- desc "Purge ssh keys authorized for the user
- if they are not managed via ssh_authorized_keys. When true,
- looks for keys in .ssh/authorized_keys in the user's home
- directory. Possible values are true, false, or an array of
- paths to file to search for authorized keys. If a path starts
- with ~ or %h, this token is replaced with the user's home directory."
+ desc "Whether to purge authorized SSH keys for this user if they are not managed
+ with the `ssh_authorized_key` resource type. Allowed values are:
+
+ * `false` (default) --- don't purge SSH keys for this user.
+ * `true` --- look for keys in the `.ssh/authorized_keys` file in the user's
+ home directory. Purge any keys that aren't managed as `ssh_authorized_key`
+ resources.
+ * An array of file paths --- look for keys in all of the files listed. Purge
+ any keys that aren't managed as `ssh_authorized_key` resources. If any of
+ these paths starts with `~` or `%h`, that token will be replaced with
+ the user's home directory."
defaultto :false
@@ -639,7 +645,7 @@ module Puppet
#
# @return [Array<Puppet::Type::Ssh_authorized_key] a list of resources
# representing the found keys
- # @see eval_generate
+ # @see generate
# @api private
def find_unmanaged_keys
self[:purge_ssh_keys].
@@ -647,6 +653,7 @@ module Puppet
map { |f| unknown_keys_in_file(f) }.
flatten.each do |res|
res[:ensure] = :absent
+ res[:user] = self[:name]
@parameters.each do |name, param|
res[name] = param.value if param.metaparam?
end
@@ -657,16 +664,16 @@ module Puppet
# on the keys. These are considered names of possible ssh_authorized_keys
# resources. Keys that are managed by the present catalog are ignored.
#
- # @see eval_generate
+ # @see generate
# @api private
# @return [Array<Puppet::Type::Ssh_authorized_key] a list of resources
# representing the found keys
def unknown_keys_in_file(keyfile)
names = []
File.new(keyfile).each do |line|
- next if line.strip.empty?
- next if line =~ /^\s*#/
- names << line.strip.split.last
+ next unless line =~ Puppet::Type.type(:ssh_authorized_key).keyline_regex
+ # the name is stored in the 4th capture of the regex
+ names << $4
end
names.map { |keyname|
diff --git a/lib/puppet/type/yumrepo.rb b/lib/puppet/type/yumrepo.rb
index d6de1b369..028717071 100644
--- a/lib/puppet/type/yumrepo.rb
+++ b/lib/puppet/type/yumrepo.rb
@@ -18,7 +18,7 @@ Puppet::Type.newtype(:yumrepo) do
# Doc string for properties that can be made 'absent'
ABSENT_DOC="Set this to `absent` to remove it from the file completely."
# False can be false/0/no and True can be true/1/yes in yum.
- YUM_BOOLEAN=/(True|False|0|1|No|Yes)/i
+ YUM_BOOLEAN=/^(True|False|0|1|No|Yes)$/i
YUM_BOOLEAN_DOC="Valid values are: False/0/No or True/1/Yes."
VALID_SCHEMES = %w[file http https ftp]
@@ -119,6 +119,13 @@ Puppet::Type.newtype(:yumrepo) do
end
end
+ newproperty(:mirrorlist_expire) do
+ desc "Time (in seconds) after which the mirrorlist locally cached
+ will expire.\n#{ABSENT_DOC}"
+
+ newvalues(/^[0-9]+$/, :absent)
+ end
+
newproperty(:include) do
desc "The URL of a remote file containing additional yum configuration
settings. Puppet does not check for this file's existence or validity.
@@ -143,6 +150,20 @@ Puppet::Type.newtype(:yumrepo) do
newvalues(/.*/, :absent)
end
+ newproperty(:gpgcakey) do
+ desc "The URL for the GPG CA key for this repository. #{ABSENT_DOC}"
+
+ newvalues(/.*/, :absent)
+ validate do |value|
+ next if value.to_s == 'absent'
+ parsed = URI.parse(value)
+
+ unless VALID_SCHEMES.include?(parsed.scheme)
+ raise "Must be a valid URL"
+ end
+ end
+ end
+
newproperty(:includepkgs) do
desc "List of shell globs. If this is set, only packages
matching one of the globs will be considered for
@@ -164,7 +185,7 @@ Puppet::Type.newtype(:yumrepo) do
desc "The failover method for this repository; should be either
`roundrobin` or `priority`. #{ABSENT_DOC}"
- newvalues(/roundrobin|priority/, :absent)
+ newvalues(/^roundrobin|priority$/, :absent)
end
newproperty(:keepalive) do
@@ -175,24 +196,32 @@ Puppet::Type.newtype(:yumrepo) do
newvalues(YUM_BOOLEAN, :absent)
end
+ newproperty(:retries) do
+ desc "Set the number of times any attempt to retrieve a file should
+ retry before returning an error. Setting this to `0` makes yum
+ try forever.\n#{ABSENT_DOC}"
+
+ newvalues(/^[0-9]+$/, :absent)
+ end
+
newproperty(:http_caching) do
desc "What to cache from this repository. #{ABSENT_DOC}"
- newvalues(/(packages|all|none)/, :absent)
+ newvalues(/^(packages|all|none)$/, :absent)
end
newproperty(:timeout) do
desc "Number of seconds to wait for a connection before timing
out. #{ABSENT_DOC}"
- newvalues(/[0-9]+/, :absent)
+ newvalues(/^\d+$/, :absent)
end
newproperty(:metadata_expire) do
desc "Number of seconds after which the metadata will expire.
#{ABSENT_DOC}"
- newvalues(/[0-9]+/, :absent)
+ newvalues(/^([0-9]+[dhm]?|never)$/, :absent)
end
newproperty(:protect) do
@@ -218,18 +247,40 @@ Puppet::Type.newtype(:yumrepo) do
end
end
+ newproperty(:throttle) do
+ desc "Enable bandwidth throttling for downloads. This option
+ can be expressed as a absolute data rate in bytes/sec or a
+ percentage `60%`. An SI prefix (k, M or G) may be appended
+ to the data rate values.\n#{ABSENT_DOC}"
+
+ newvalues(/^\d+[kMG%]?$/, :absent)
+ end
+
+ newproperty(:bandwidth) do
+ desc "Use to specify the maximum available network bandwidth
+ in bytes/second. Used with the `throttle` option. If `throttle`
+ is a percentage and `bandwidth` is `0` then bandwidth throttling
+ will be disabled. If `throttle` is expressed as a data rate then
+ this option is ignored.\n#{ABSENT_DOC}"
+
+ newvalues(/^\d+[kMG]?$/, :absent)
+ end
+
newproperty(:cost) do
desc "Cost of this repository. #{ABSENT_DOC}"
- newvalues(/\d+/, :absent)
+ newvalues(/^\d+$/, :absent)
end
newproperty(:proxy) do
- desc "URL to the proxy server for this repository. #{ABSENT_DOC}"
+ desc "URL of a proxy server that Yum should use when accessing this repository.
+ This attribute can also be set to `'_none_'`, which will make Yum bypass any
+ global proxy settings when accessing this repository.
+ #{ABSENT_DOC}"
newvalues(/.*/, :absent)
validate do |value|
- next if value.to_s == 'absent'
+ next if value.to_s =~ /^(absent|_none_)$/
parsed = URI.parse(value)
unless VALID_SCHEMES.include?(parsed.scheme)
diff --git a/lib/puppet/type/zone.rb b/lib/puppet/type/zone.rb
index 09572bdb6..9094aaf6f 100644
--- a/lib/puppet/type/zone.rb
+++ b/lib/puppet/type/zone.rb
@@ -171,8 +171,7 @@ end
# overridden so that we match with self.should
def insync?(is)
- return true unless is
- is = [] if is == :absent
+ is = [] if !is || is == :absent
is.sort == self.should.sort
end
end
@@ -228,8 +227,7 @@ end
# overridden so that we match with self.should
def insync?(is)
- return true unless is
- is = [] if is == :absent
+ is = [] if !is || is == :absent
is.sort == self.should.sort
end
@@ -250,8 +248,7 @@ end
# overridden so that we match with self.should
def insync?(is)
- return true unless is
- is = [] if is == :absent
+ is = [] if !is || is == :absent
is.sort == self.should.sort
end
diff --git a/lib/puppet/util.rb b/lib/puppet/util.rb
index a8b78da7c..e05339517 100644
--- a/lib/puppet/util.rb
+++ b/lib/puppet/util.rb
@@ -4,11 +4,11 @@ require 'English'
require 'puppet/error'
require 'puppet/util/execution_stub'
require 'uri'
-require 'tempfile'
require 'pathname'
require 'ostruct'
require 'puppet/util/platform'
require 'puppet/util/symbolic_file_mode'
+require 'puppet/file_system/uniquefile'
require 'securerandom'
module Puppet
@@ -396,70 +396,79 @@ module Util
end
end
- file = Puppet::FileSystem.pathname(file)
- tempfile = Tempfile.new(Puppet::FileSystem.basename_string(file), Puppet::FileSystem.dir_string(file))
-
- # Set properties of the temporary file before we write the content, because
- # Tempfile doesn't promise to be safe from reading by other people, just
- # that it avoids races around creating the file.
- #
- # Our Windows emulation is pretty limited, and so we have to carefully
- # and specifically handle the platform, which has all sorts of magic.
- # So, unlike Unix, we don't pre-prep security; we use the default "quite
- # secure" tempfile permissions instead. Magic happens later.
- if !Puppet.features.microsoft_windows?
- # Grab the current file mode, and fall back to the defaults.
- effective_mode =
- if Puppet::FileSystem.exist?(file)
- stat = Puppet::FileSystem.lstat(file)
- tempfile.chown(stat.uid, stat.gid)
- stat.mode
- else
- mode
- end
+ begin
+ file = Puppet::FileSystem.pathname(file)
+ tempfile = Puppet::FileSystem::Uniquefile.new(Puppet::FileSystem.basename_string(file), Puppet::FileSystem.dir_string(file))
+
+ # Set properties of the temporary file before we write the content, because
+ # Tempfile doesn't promise to be safe from reading by other people, just
+ # that it avoids races around creating the file.
+ #
+ # Our Windows emulation is pretty limited, and so we have to carefully
+ # and specifically handle the platform, which has all sorts of magic.
+ # So, unlike Unix, we don't pre-prep security; we use the default "quite
+ # secure" tempfile permissions instead. Magic happens later.
+ if !Puppet.features.microsoft_windows?
+ # Grab the current file mode, and fall back to the defaults.
+ effective_mode =
+ if Puppet::FileSystem.exist?(file)
+ stat = Puppet::FileSystem.lstat(file)
+ tempfile.chown(stat.uid, stat.gid)
+ stat.mode
+ else
+ mode
+ end
- if effective_mode
- # We only care about the bottom four slots, which make the real mode,
- # and not the rest of the platform stat call fluff and stuff.
- tempfile.chmod(effective_mode & 07777)
+ if effective_mode
+ # We only care about the bottom four slots, which make the real mode,
+ # and not the rest of the platform stat call fluff and stuff.
+ tempfile.chmod(effective_mode & 07777)
+ end
end
- end
- # OK, now allow the caller to write the content of the file.
- yield tempfile
+ # OK, now allow the caller to write the content of the file.
+ yield tempfile
- # Now, make sure the data (which includes the mode) is safe on disk.
- tempfile.flush
- begin
- tempfile.fsync
- rescue NotImplementedError
- # fsync may not be implemented by Ruby on all platforms, but
- # there is absolutely no recovery path if we detect that. So, we just
- # ignore the return code.
- #
- # However, don't be fooled: that is accepting that we are running in
- # an unsafe fashion. If you are porting to a new platform don't stub
- # that out.
- end
+ # Now, make sure the data (which includes the mode) is safe on disk.
+ tempfile.flush
+ begin
+ tempfile.fsync
+ rescue NotImplementedError
+ # fsync may not be implemented by Ruby on all platforms, but
+ # there is absolutely no recovery path if we detect that. So, we just
+ # ignore the return code.
+ #
+ # However, don't be fooled: that is accepting that we are running in
+ # an unsafe fashion. If you are porting to a new platform don't stub
+ # that out.
+ end
- tempfile.close
+ tempfile.close
- if Puppet.features.microsoft_windows?
- # Windows ReplaceFile needs a file to exist, so touch handles this
- if !Puppet::FileSystem.exist?(file)
- Puppet::FileSystem.touch(file)
- if mode
- Puppet::Util::Windows::Security.set_mode(mode, Puppet::FileSystem.path_string(file))
+ if Puppet.features.microsoft_windows?
+ # Windows ReplaceFile needs a file to exist, so touch handles this
+ if !Puppet::FileSystem.exist?(file)
+ Puppet::FileSystem.touch(file)
+ if mode
+ Puppet::Util::Windows::Security.set_mode(mode, Puppet::FileSystem.path_string(file))
+ end
end
- end
- # Yes, the arguments are reversed compared to the rename in the rest
- # of the world.
- Puppet::Util::Windows::File.replace_file(FileSystem.path_string(file), tempfile.path)
+ # Yes, the arguments are reversed compared to the rename in the rest
+ # of the world.
+ Puppet::Util::Windows::File.replace_file(FileSystem.path_string(file), tempfile.path)
- else
- File.rename(tempfile.path, Puppet::FileSystem.path_string(file))
+ else
+ File.rename(tempfile.path, Puppet::FileSystem.path_string(file))
+ end
+ ensure
+ # in case an error occurred before we renamed the temp file, make sure it
+ # gets deleted
+ if tempfile
+ tempfile.close!
+ end
end
+
# Ideally, we would now fsync the directory as well, but Ruby doesn't
# have support for that, and it doesn't matter /that/ much...
diff --git a/lib/puppet/util/autoload.rb b/lib/puppet/util/autoload.rb
index f8f7c260c..51ff94e1c 100644
--- a/lib/puppet/util/autoload.rb
+++ b/lib/puppet/util/autoload.rb
@@ -43,7 +43,7 @@ class Puppet::Util::Autoload
name = cleanpath(name).chomp('.rb')
return true unless loaded.include?(name)
file, old_mtime = loaded[name]
- environment = Puppet.lookup(:environments).get(Puppet[:environment])
+ environment = Puppet.lookup(:current_environment)
return true unless file == get_file(name, environment)
begin
old_mtime.to_i != File.mtime(file).to_i
@@ -127,7 +127,7 @@ class Puppet::Util::Autoload
# now we are accomplishing that by calling the
# "app_defaults_initialized?" method on the main puppet Settings object.
# --cprice 2012-03-16
- if Puppet.settings.app_defaults_initialized?
+ if Puppet.settings.app_defaults_initialized? &&
env ||= Puppet.lookup(:environments).get(Puppet[:environment])
# if the app defaults have been initialized then it should be safe to access the module path setting.
diff --git a/lib/puppet/util/colors.rb b/lib/puppet/util/colors.rb
index 622c3b583..dee607a9f 100644
--- a/lib/puppet/util/colors.rb
+++ b/lib/puppet/util/colors.rb
@@ -79,42 +79,84 @@ module Puppet::Util::Colors
# We define console_has_color? at load time since it's checking the
# underlying platform which will not change, and we don't want to perform
# the check every time we use logging
- if Puppet::Util::Platform.windows?
- # We're on windows, need win32console for color to work
+ if Puppet::Util::Platform.windows? && RUBY_VERSION =~ /^1\./
+ # We're on windows and using ruby less than v2
+ # so we need win32console for color to work
begin
- require 'Win32API'
+ require 'ffi'
require 'win32console'
- require 'puppet/util/windows/string'
# The win32console gem uses ANSI functions for writing to the console
# which doesn't work for unicode strings, e.g. module tool. Ruby 1.9
# does the same thing, but doesn't account for ANSI escape sequences
class WideConsole < Win32::Console
- WriteConsole = Win32API.new( "kernel32", "WriteConsoleW", ['l', 'p', 'l', 'p', 'p'], 'l' )
- WriteConsoleOutputCharacter = Win32API.new( "kernel32", "WriteConsoleOutputCharacterW", ['l', 'p', 'l', 'l', 'p'], 'l' )
+ extend FFI::Library
+
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/ms687401(v=vs.85).aspx
+ # BOOL WINAPI WriteConsole(
+ # _In_ HANDLE hConsoleOutput,
+ # _In_ const VOID *lpBuffer,
+ # _In_ DWORD nNumberOfCharsToWrite,
+ # _Out_ LPDWORD lpNumberOfCharsWritten,
+ # _Reserved_ LPVOID lpReserved
+ # );
+ ffi_lib :kernel32
+ attach_function_private :WriteConsoleW,
+ [:handle, :lpcwstr, :dword, :lpdword, :lpvoid], :win32_bool
+
+ # typedef struct _COORD {
+ # SHORT X;
+ # SHORT Y;
+ # } COORD, *PCOORD;
+ class COORD < FFI::Struct
+ layout :X, :short,
+ :Y, :short
+ end
+
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/ms687410(v=vs.85).aspx
+ # BOOL WINAPI WriteConsoleOutputCharacter(
+ # _In_ HANDLE hConsoleOutput,
+ # _In_ LPCTSTR lpCharacter,
+ # _In_ DWORD nLength,
+ # _In_ COORD dwWriteCoord,
+ # _Out_ LPDWORD lpNumberOfCharsWritten
+ # );
+ ffi_lib :kernel32
+ attach_function_private :WriteConsoleOutputCharacterW,
+ [:handle, :lpcwstr, :dword, COORD, :lpdword], :win32_bool
def initialize(t = nil)
super(t)
end
def WriteChar(str, col, row)
- dwWriteCoord = (row << 16) + col
- lpNumberOfCharsWritten = ' ' * 4
- utf16, nChars = string_encode(str)
- WriteConsoleOutputCharacter.call(@handle, utf16, nChars, dwWriteCoord, lpNumberOfCharsWritten)
- lpNumberOfCharsWritten.unpack('L')
+ writeCoord = COORD.new()
+ writeCoord[:X] = row
+ writeCoord[:Y] = col
+
+ chars_written = 0
+ FFI::MemoryPointer.from_string_to_wide_string(str) do |msg_ptr|
+ FFI::MemoryPointer.new(:dword, 1) do |numberOfCharsWritten_ptr|
+ WriteConsoleOutputCharacterW(@handle, msg_ptr,
+ str.length, writeCoord, numberOfCharsWritten_ptr)
+ chars_written = numberOfCharsWritten_ptr.read_dword
+ end
+ end
+
+ chars_written
end
def Write(str)
- written = 0.chr * 4
- reserved = 0.chr * 4
- utf16, nChars = string_encode(str)
- WriteConsole.call(@handle, utf16, nChars, written, reserved)
- end
+ result = false
+ FFI::MemoryPointer.from_string_to_wide_string(str) do |msg_ptr|
+ FFI::MemoryPointer.new(:dword, 1) do |numberOfCharsWritten_ptr|
+ result = WriteConsoleW(@handle, msg_ptr,
+ str.length, FFI::MemoryPointer.new(:dword, 1),
+ FFI::MemoryPointer::NULL) != FFI::WIN32_FALSE
+ end
+ end
- def string_encode(str)
- wstr = Puppet::Util::Windows::String.wide_string(str)
- [wstr, wstr.length - 1]
+ result
end
end
diff --git a/lib/puppet/util/command_line.rb b/lib/puppet/util/command_line.rb
index 35a38f5c3..22fd3532a 100644
--- a/lib/puppet/util/command_line.rb
+++ b/lib/puppet/util/command_line.rb
@@ -14,6 +14,7 @@ require 'puppet/util'
require "puppet/util/plugins"
require "puppet/util/rubygems"
require "puppet/util/limits"
+require 'puppet/util/colors'
module Puppet
module Util
@@ -125,9 +126,17 @@ module Puppet
# ruby process, but that requires fixing (#17210, #12173, #8750). So for now
# we try to restrict to only code that can be autoloaded from the node's
# environment.
+
+ # PUP-2114 - at this point in the bootstrapping process we do not
+ # have an appropriate application-wide current_environment set.
+ # If we cannot find the configured environment, which may not exist,
+ # we do not attempt to add plugin directories to the load path.
+ #
if @subcommand_name != 'master' and @subcommand_name != 'agent'
- Puppet.lookup(:environments).get(Puppet[:environment]).each_plugin_directory do |dir|
- $LOAD_PATH << dir unless $LOAD_PATH.include?(dir)
+ if configured_environment = Puppet.lookup(:environments).get(Puppet[:environment])
+ configured_environment.each_plugin_directory do |dir|
+ $LOAD_PATH << dir unless $LOAD_PATH.include?(dir)
+ end
end
end
@@ -152,13 +161,20 @@ module Puppet
# @api private
class NilSubcommand
+ include Puppet::Util::Colors
+
def initialize(command_line)
@command_line = command_line
end
def run
- if @command_line.args.include? "--version" or @command_line.args.include? "-V"
+ args = @command_line.args
+ if args.include? "--version" or args.include? "-V"
puts Puppet.version
+ elsif @command_line.subcommand_name.nil? && args.count > 0
+ # If the subcommand is truly nil and there is an arg, it's an option; print out the invalid option message
+ puts colorize(:hred, "Error: Could not parse application options: invalid option: #{args[0]}")
+ exit 1
else
puts "See 'puppet help' for help on available puppet subcommands"
end
@@ -173,8 +189,9 @@ module Puppet
end
def run
- puts "Error: Unknown Puppet subcommand '#{@subcommand_name}'"
+ puts colorize(:hred, "Error: Unknown Puppet subcommand '#{@subcommand_name}'")
super
+ exit 1
end
end
end
diff --git a/lib/puppet/util/execution.rb b/lib/puppet/util/execution.rb
index fb03510a9..556aba50e 100644
--- a/lib/puppet/util/execution.rb
+++ b/lib/puppet/util/execution.rb
@@ -1,3 +1,5 @@
+require 'puppet/file_system/uniquefile'
+
module Puppet
require 'rbconfig'
@@ -79,15 +81,18 @@ module Puppet::Util::Execution
end
end
- if failonfail
- unless $CHILD_STATUS == 0
- raise Puppet::ExecutionFailure, output
- end
+ if failonfail && exitstatus != 0
+ raise Puppet::ExecutionFailure, output
end
output
end
+ def self.exitstatus
+ $CHILD_STATUS.exitstatus
+ end
+ private_class_method :exitstatus
+
# Wraps execution of {execute} with mapping of exception to given exception (and output as argument).
# @raise [exception] under same conditions as {execute}, but raises the given `exception` with the output as argument
# @return (see execute)
@@ -164,37 +169,44 @@ module Puppet::Util::Execution
null_file = Puppet.features.microsoft_windows? ? 'NUL' : '/dev/null'
- stdin = File.open(options[:stdinfile] || null_file, 'r')
- stdout = options[:squelch] ? File.open(null_file, 'w') : Tempfile.new('puppet')
- stderr = options[:combine] ? stdout : File.open(null_file, 'w')
+ begin
+ stdin = File.open(options[:stdinfile] || null_file, 'r')
+ stdout = options[:squelch] ? File.open(null_file, 'w') : Puppet::FileSystem::Uniquefile.new('puppet')
+ stderr = options[:combine] ? stdout : File.open(null_file, 'w')
- exec_args = [command, options, stdin, stdout, stderr]
+ exec_args = [command, options, stdin, stdout, stderr]
- if execution_stub = Puppet::Util::ExecutionStub.current_value
- return execution_stub.call(*exec_args)
- elsif Puppet.features.posix?
- child_pid = execute_posix(*exec_args)
- exit_status = Process.waitpid2(child_pid).last.exitstatus
- elsif Puppet.features.microsoft_windows?
- process_info = execute_windows(*exec_args)
- begin
- exit_status = Puppet::Util::Windows::Process.wait_process(process_info.process_handle)
- ensure
- Puppet::Util::Windows::Process.CloseHandle(process_info.process_handle)
- Puppet::Util::Windows::Process.CloseHandle(process_info.thread_handle)
+ if execution_stub = Puppet::Util::ExecutionStub.current_value
+ return execution_stub.call(*exec_args)
+ elsif Puppet.features.posix?
+ child_pid = execute_posix(*exec_args)
+ exit_status = Process.waitpid2(child_pid).last.exitstatus
+ elsif Puppet.features.microsoft_windows?
+ process_info = execute_windows(*exec_args)
+ begin
+ exit_status = Puppet::Util::Windows::Process.wait_process(process_info.process_handle)
+ ensure
+ FFI::WIN32.CloseHandle(process_info.process_handle)
+ FFI::WIN32.CloseHandle(process_info.thread_handle)
+ end
end
- end
- [stdin, stdout, stderr].each {|io| io.close rescue nil}
+ [stdin, stdout, stderr].each {|io| io.close rescue nil}
- # read output in if required
- unless options[:squelch]
- output = wait_for_output(stdout)
- Puppet.warning "Could not get output" unless output
- end
+ # read output in if required
+ unless options[:squelch]
+ output = wait_for_output(stdout)
+ Puppet.warning "Could not get output" unless output
+ end
- if options[:failonfail] and exit_status != 0
- raise Puppet::ExecutionFailure, "Execution of '#{str}' returned #{exit_status}: #{output.strip}"
+ if options[:failonfail] and exit_status != 0
+ raise Puppet::ExecutionFailure, "Execution of '#{str}' returned #{exit_status}: #{output.strip}"
+ end
+ ensure
+ if !options[:squelch] && stdout
+ # if we opened a temp file for stdout, we need to clean it up.
+ stdout.close!
+ end
end
Puppet::Util::Execution::ProcessOutput.new(output || '', exit_status)
diff --git a/lib/puppet/util/feature.rb b/lib/puppet/util/feature.rb
index 19c544070..23d00edde 100644
--- a/lib/puppet/util/feature.rb
+++ b/lib/puppet/util/feature.rb
@@ -1,3 +1,5 @@
+require 'puppet'
+
class Puppet::Util::Feature
attr_reader :path
@@ -23,10 +25,19 @@ class Puppet::Util::Feature
end
meta_def(method) do
- # Positive cache only, except blocks which are executed just once above
- final = @results[name] || block_given?
- @results[name] = test(name, options) unless final
- @results[name]
+ # we return a cached result if:
+ # * if a block is given (and we just evaluated it above)
+ # * if we already have a positive result
+ # * if we've tested this feature before and it failed, but we're
+ # configured to always cache
+ if block_given? ||
+ @results[name] ||
+ (@results.has_key?(name) and Puppet[:always_cache_features])
+ @results[name]
+ else
+ @results[name] = test(name, options)
+ @results[name]
+ end
end
end
diff --git a/lib/puppet/util/filetype.rb b/lib/puppet/util/filetype.rb
index 9fc3b289a..08f763ee5 100644
--- a/lib/puppet/util/filetype.rb
+++ b/lib/puppet/util/filetype.rb
@@ -78,9 +78,10 @@ class Puppet::Util::FileType
@bucket ||= Puppet::Type.type(:filebucket).mkdefaultbucket.bucket
end
- def initialize(path)
+ def initialize(path, default_mode = nil)
raise ArgumentError.new("Path is nil") if path.nil?
@path = path
+ @default_mode = default_mode
end
# Arguments that will be passed to the execute method. Will set the uid
@@ -119,6 +120,7 @@ class Puppet::Util::FileType
def write(text)
tf = Tempfile.new("puppet")
tf.print text; tf.flush
+ File.chmod(@default_mode, tf.path) if @default_mode
FileUtils.cp(tf.path, @path)
tf.close
# If SELinux is present, we need to ensure the file has its expected context
@@ -134,7 +136,9 @@ class Puppet::Util::FileType
@@tabs.clear
end
- def initialize(path)
+ def initialize(path, default_mode = nil)
+ # default_mode is meaningless for this filetype,
+ # supported only for compatibility with :flat
super
@@tabs[@path] ||= ""
end
diff --git a/lib/puppet/util/http_proxy.rb b/lib/puppet/util/http_proxy.rb
index 8c979c400..3785d4911 100644
--- a/lib/puppet/util/http_proxy.rb
+++ b/lib/puppet/util/http_proxy.rb
@@ -14,7 +14,7 @@ module Puppet::Util::HttpProxy
def self.http_proxy_host
env = self.http_proxy_env
- if env and env.host then
+ if env and env.host
return env.host
end
@@ -28,11 +28,38 @@ module Puppet::Util::HttpProxy
def self.http_proxy_port
env = self.http_proxy_env
- if env and env.port then
+ if env and env.port
return env.port
end
return Puppet.settings[:http_proxy_port]
end
+ def self.http_proxy_user
+ env = self.http_proxy_env
+
+ if env and env.user
+ return env.user
+ end
+
+ if Puppet.settings[:http_proxy_user] == 'none'
+ return nil
+ end
+
+ return Puppet.settings[:http_proxy_user]
+ end
+
+ def self.http_proxy_password
+ env = self.http_proxy_env
+
+ if env and env.password
+ return env.password
+ end
+
+ if Puppet.settings[:http_proxy_user] == 'none' or Puppet.settings[:http_proxy_password] == 'none'
+ return nil
+ end
+
+ return Puppet.settings[:http_proxy_password]
+ end
end
diff --git a/lib/puppet/util/lockfile.rb b/lib/puppet/util/lockfile.rb
index 9771f389e..21a1c2d9b 100644
--- a/lib/puppet/util/lockfile.rb
+++ b/lib/puppet/util/lockfile.rb
@@ -59,7 +59,7 @@ class Puppet::Util::Lockfile
# by other methods in this class without as much risk of accidentally
# being overridden by child classes.
# @return [boolean] true if the file is locked, false if it is not.
- def file_locked?()
+ def file_locked?
Puppet::FileSystem.exist? @file_path
end
private :file_locked?
diff --git a/lib/puppet/util/log/destinations.rb b/lib/puppet/util/log/destinations.rb
index 932007099..6c5cc7f23 100644
--- a/lib/puppet/util/log/destinations.rb
+++ b/lib/puppet/util/log/destinations.rb
@@ -189,6 +189,10 @@ Puppet::Util::Log.newdesttype :array do
end
Puppet::Util::Log.newdesttype :eventlog do
+ Puppet::Util::Log::DestEventlog::EVENTLOG_ERROR_TYPE = 0x0001
+ Puppet::Util::Log::DestEventlog::EVENTLOG_WARNING_TYPE = 0x0002
+ Puppet::Util::Log::DestEventlog::EVENTLOG_INFORMATION_TYPE = 0x0004
+
def self.suitable?(obj)
Puppet.features.eventlog?
end
@@ -200,11 +204,11 @@ Puppet::Util::Log.newdesttype :eventlog do
def to_native(level)
case level
when :debug,:info,:notice
- [Win32::EventLog::INFO, 0x01]
+ [self.class::EVENTLOG_INFORMATION_TYPE, 0x01]
when :warning
- [Win32::EventLog::WARN, 0x02]
+ [self.class::EVENTLOG_WARNING_TYPE, 0x02]
when :err,:alert,:emerg,:crit
- [Win32::EventLog::ERROR, 0x03]
+ [self.class::EVENTLOG_ERROR_TYPE, 0x03]
end
end
diff --git a/lib/puppet/util/logging.rb b/lib/puppet/util/logging.rb
index 67b986ddb..84a35ddc2 100644
--- a/lib/puppet/util/logging.rb
+++ b/lib/puppet/util/logging.rb
@@ -55,24 +55,40 @@ module Puppet::Util::Logging
class DeprecationWarning < Exception; end
- # Logs a warning indicating that the code path is deprecated. Note that this
- # method keeps track of the offending lines of code that triggered the
+ # Logs a warning indicating that the Ruby code path is deprecated. Note that
+ # this method keeps track of the offending lines of code that triggered the
# deprecation warning, and will only log a warning once per offending line of
# code. It will also stop logging deprecation warnings altogether after 100
- # unique deprecation warnings have been logged.
+ # unique deprecation warnings have been logged. Finally, if
+ # Puppet[:disable_warnings] includes 'deprecations', it will squelch all
+ # warning calls made via this method.
#
- # @param [String] message The message to log (logs via )
- # @param [String] key Optional key to mark the message as unique. If not
+ # @param message [String] The message to log (logs via warning)
+ # @param key [String] Optional key to mark the message as unique. If not
# passed in, the originating call line will be used instead.
def deprecation_warning(message, key = nil)
- $deprecation_warnings ||= {}
- if $deprecation_warnings.length < 100 then
- key ||= (offender = get_deprecation_offender)
- if (! $deprecation_warnings.has_key?(key)) then
- $deprecation_warnings[key] = message
- warning("#{message}\n (at #{(offender || get_deprecation_offender).join('; ')})")
- end
- end
+ issue_deprecation_warning(message, key, nil, nil, true)
+ end
+
+ # Logs a warning whose origin comes from Puppet source rather than somewhere
+ # internal within Puppet. Otherwise the same as deprecation_warning()
+ #
+ # @param message [String] The message to log (logs via warning)
+ # @param options [Hash]
+ # @option options [String] :file File we are warning from
+ # @option options [Integer] :line Line number we are warning from
+ # @option options [String] :key (:file + :line) Alternative key used to mark
+ # warning as unique
+ #
+ # Either :file and :line and/or :key must be passed.
+ def puppet_deprecation_warning(message, options = {})
+ key = options[:key]
+ file = options[:file]
+ line = options[:line]
+ raise(Puppet::DevError, "Need either :file and :line, or :key") if (key.nil?) && (file.nil? || line.nil?)
+
+ key ||= "#{file}:#{line}"
+ issue_deprecation_warning(message, key, file, line, false)
end
def get_deprecation_offender()
@@ -127,6 +143,21 @@ module Puppet::Util::Logging
private
+ def issue_deprecation_warning(message, key, file, line, use_caller)
+ return if Puppet[:disable_warnings].include?('deprecations')
+ $deprecation_warnings ||= {}
+ if $deprecation_warnings.length < 100 then
+ key ||= (offender = get_deprecation_offender)
+ if (! $deprecation_warnings.has_key?(key)) then
+ $deprecation_warnings[key] = message
+ call_trace = use_caller ?
+ (offender || get_deprecation_offender).join('; ') :
+ "#{file || 'unknown'}:#{line || 'unknown'}"
+ warning("#{message}\n (at #{call_trace})")
+ end
+ end
+ end
+
def is_resource?
defined?(Puppet::Type) && is_a?(Puppet::Type)
end
diff --git a/lib/puppet/util/pidlock.rb b/lib/puppet/util/pidlock.rb
index 35e4ad431..3ebb4e0c9 100644
--- a/lib/puppet/util/pidlock.rb
+++ b/lib/puppet/util/pidlock.rb
@@ -22,7 +22,7 @@ class Puppet::Util::Pidlock
@lockfile.lock(Process.pid)
end
- def unlock()
+ def unlock
if mine?
return @lockfile.unlock
else
@@ -31,7 +31,12 @@ class Puppet::Util::Pidlock
end
def lock_pid
- @lockfile.lock_data.to_i
+ pid = @lockfile.lock_data
+ begin
+ Integer(pid)
+ rescue ArgumentError, TypeError
+ nil
+ end
end
def file_path
@@ -39,11 +44,12 @@ class Puppet::Util::Pidlock
end
def clear_if_stale
- return if lock_pid.nil?
+ return @lockfile.unlock if lock_pid.nil?
errors = [Errno::ESRCH]
- # Process::Error can only happen, and is only defined, on Windows
- errors << Process::Error if defined? Process::Error
+ # Win32::Process now throws SystemCallError. Since this could be
+ # defined anywhere, only add when on Windows.
+ errors << SystemCallError if Puppet::Util::Platform.windows?
begin
Process.kill(0, lock_pid)
diff --git a/lib/puppet/util/posix.rb b/lib/puppet/util/posix.rb
index 2af0e4b91..fa2c609a7 100644
--- a/lib/puppet/util/posix.rb
+++ b/lib/puppet/util/posix.rb
@@ -98,49 +98,39 @@ module Puppet::Util::POSIX
end
end
- # Get the GID of a given group, provided either a GID or a name
+ # Get the GID
def gid(group)
- begin
- group = Integer(group)
- rescue ArgumentError
- # pass
- end
- if group.is_a?(Integer)
- return nil unless name = get_posix_field(:group, :name, group)
- gid = get_posix_field(:group, :gid, name)
- check_value = gid
- else
- return nil unless gid = get_posix_field(:group, :gid, group)
- name = get_posix_field(:group, :name, gid)
- check_value = name
- end
- if check_value != group
- return search_posix_field(:group, :gid, group)
- else
- return gid
- end
+ get_posix_value(:group, :gid, group)
end
- # Get the UID of a given user, whether a UID or name is provided
+ # Get the UID
def uid(user)
+ get_posix_value(:passwd, :uid, user)
+ end
+
+ private
+
+ # Get the specified id_field of a given field (user or group),
+ # whether an ID name is provided
+ def get_posix_value(location, id_field, field)
begin
- user = Integer(user)
+ field = Integer(field)
rescue ArgumentError
# pass
end
- if user.is_a?(Integer)
- return nil unless name = get_posix_field(:passwd, :name, user)
- uid = get_posix_field(:passwd, :uid, name)
- check_value = uid
+ if field.is_a?(Integer)
+ return nil unless name = get_posix_field(location, :name, field)
+ id = get_posix_field(location, id_field, name)
+ check_value = id
else
- return nil unless uid = get_posix_field(:passwd, :uid, user)
- name = get_posix_field(:passwd, :name, uid)
+ return nil unless id = get_posix_field(location, id_field, field)
+ name = get_posix_field(location, :name, id)
check_value = name
end
- if check_value != user
- return search_posix_field(:passwd, :uid, user)
+ if check_value != field
+ return search_posix_field(location, id_field, field)
else
- return uid
+ return id
end
end
end
diff --git a/lib/puppet/util/profiler.rb b/lib/puppet/util/profiler.rb
index 4246181b4..820231f27 100644
--- a/lib/puppet/util/profiler.rb
+++ b/lib/puppet/util/profiler.rb
@@ -6,27 +6,34 @@ require 'benchmark'
module Puppet::Util::Profiler
require 'puppet/util/profiler/wall_clock'
require 'puppet/util/profiler/object_counts'
- require 'puppet/util/profiler/none'
+ require 'puppet/util/profiler/around_profiler'
- NONE = Puppet::Util::Profiler::None.new
+ @profiler = Puppet::Util::Profiler::AroundProfiler.new
# Reset the profiling system to the original state
#
# @api private
def self.clear
- @profiler = nil
+ @profiler.clear
end
- # @return This thread's configured profiler
+ # Retrieve the current list of profilers
+ #
# @api private
def self.current
- @profiler || NONE
+ @profiler.current
end
# @param profiler [#profile] A profiler for the current thread
# @api private
- def self.current=(profiler)
- @profiler = profiler
+ def self.add_profiler(profiler)
+ @profiler.add_profiler(profiler)
+ end
+
+ # @param profiler [#profile] A profiler to remove from the current thread
+ # @api private
+ def self.remove_profiler(profiler)
+ @profiler.remove_profiler(profiler)
end
# Profile a block of code and log the time it took to execute.
@@ -37,9 +44,10 @@ module Puppet::Util::Profiler
# in the profiled hierachy.
#
# @param message [String] A description of the profiled event
+ # @param metric_id [Array] A list of strings making up the ID of a metric to profile
# @param block [Block] The segment of code to profile
# @api public
- def self.profile(message, &block)
- current.profile(message, &block)
+ def self.profile(message, metric_id = nil, &block)
+ @profiler.profile(message, metric_id, &block)
end
end
diff --git a/lib/puppet/util/profiler/aggregate.rb b/lib/puppet/util/profiler/aggregate.rb
new file mode 100644
index 000000000..e8a4ca595
--- /dev/null
+++ b/lib/puppet/util/profiler/aggregate.rb
@@ -0,0 +1,85 @@
+require 'puppet/util/profiler'
+require 'puppet/util/profiler/wall_clock'
+
+class Puppet::Util::Profiler::Aggregate < Puppet::Util::Profiler::WallClock
+ def initialize(logger, identifier)
+ super(logger, identifier)
+ @metrics_hash = Metric.new
+ end
+
+ def shutdown()
+ super
+ @logger.call("AGGREGATE PROFILING RESULTS:")
+ @logger.call("----------------------------")
+ print_metrics(@metrics_hash, "")
+ @logger.call("----------------------------")
+ end
+
+ def do_start(description, metric_id)
+ super(description, metric_id)
+ end
+
+ def do_finish(context, description, metric_id)
+ result = super(context, description, metric_id)
+ update_metric(@metrics_hash, metric_id, result[:time])
+ result
+ end
+
+ def update_metric(metrics_hash, metric_id, time)
+ first, *rest = *metric_id
+ if first
+ m = metrics_hash[first]
+ m.increment
+ m.add_time(time)
+ if rest.count > 0
+ update_metric(m, rest, time)
+ end
+ end
+ end
+
+ def values
+ @metrics_hash
+ end
+
+ def print_metrics(metrics_hash, prefix)
+ metrics_hash.sort_by {|k,v| v.time }.reverse.each do |k,v|
+ @logger.call("#{prefix}#{k}: #{v.time} ms (#{v.count} calls)")
+ print_metrics(metrics_hash[k], "#{prefix}#{k} -> ")
+ end
+ end
+
+ class Metric < Hash
+ def initialize
+ super
+ @count = 0
+ @time = 0
+ end
+ attr_reader :count, :time
+
+ def [](key)
+ if !has_key?(key)
+ self[key] = Metric.new
+ end
+ super(key)
+ end
+
+ def increment
+ @count += 1
+ end
+
+ def add_time(time)
+ @time += time
+ end
+ end
+
+ class Timer
+ def initialize
+ @start = Time.now
+ end
+
+ def stop
+ Time.now - @start
+ end
+ end
+end
+
diff --git a/lib/puppet/util/profiler/around_profiler.rb b/lib/puppet/util/profiler/around_profiler.rb
new file mode 100644
index 000000000..0b408c6d8
--- /dev/null
+++ b/lib/puppet/util/profiler/around_profiler.rb
@@ -0,0 +1,67 @@
+# A Profiler that can be used to wrap around blocks of code. It is configured
+# with other profilers and controls them to start before the block is executed
+# and finish after the block is executed.
+#
+# @api private
+class Puppet::Util::Profiler::AroundProfiler
+
+ def initialize
+ @profilers = []
+ end
+
+ # Reset the profiling system to the original state
+ #
+ # @api private
+ def clear
+ @profilers = []
+ end
+
+ # Retrieve the current list of profilers
+ #
+ # @api private
+ def current
+ @profilers
+ end
+
+ # @param profiler [#profile] A profiler for the current thread
+ # @api private
+ def add_profiler(profiler)
+ @profilers << profiler
+ profiler
+ end
+
+ # @param profiler [#profile] A profiler to remove from the current thread
+ # @api private
+ def remove_profiler(profiler)
+ @profilers.delete(profiler)
+ end
+
+ # Profile a block of code and log the time it took to execute.
+ #
+ # This outputs logs entries to the Puppet masters logging destination
+ # providing the time it took, a message describing the profiled code
+ # and a leaf location marking where the profile method was called
+ # in the profiled hierachy.
+ #
+ # @param message [String] A description of the profiled event
+ # @param metric_id [Array] A list of strings making up the ID of a metric to profile
+ # @param block [Block] The segment of code to profile
+ # @api private
+ def profile(message, metric_id = nil)
+ retval = nil
+ contexts = {}
+ @profilers.each do |profiler|
+ contexts[profiler] = profiler.start(message, metric_id)
+ end
+
+ begin
+ retval = yield
+ ensure
+ @profilers.each do |profiler|
+ profiler.finish(contexts[profiler], message, metric_id)
+ end
+ end
+
+ retval
+ end
+end
diff --git a/lib/puppet/util/profiler/logging.rb b/lib/puppet/util/profiler/logging.rb
index c0de09e25..5d1fd101c 100644
--- a/lib/puppet/util/profiler/logging.rb
+++ b/lib/puppet/util/profiler/logging.rb
@@ -5,19 +5,20 @@ class Puppet::Util::Profiler::Logging
@sequence = Sequence.new
end
- def profile(description, &block)
- retval = nil
+ def start(description, metric_id)
@sequence.next
@sequence.down
- context = start
- begin
- retval = yield
- ensure
- profile_explanation = finish(context)
- @sequence.up
- @logger.call("PROFILE [#{@identifier}] #{@sequence} #{description}: #{profile_explanation}")
- end
- retval
+ do_start(description, metric_id)
+ end
+
+ def finish(context, description, metric_id)
+ profile_explanation = do_finish(context, description, metric_id)[:msg]
+ @sequence.up
+ @logger.call("PROFILE [#{@identifier}] #{@sequence} #{description}: #{profile_explanation}")
+ end
+
+ def shutdown()
+ # nothing to do
end
class Sequence
diff --git a/lib/puppet/util/profiler/none.rb b/lib/puppet/util/profiler/none.rb
deleted file mode 100644
index 7d4ad716d..000000000
--- a/lib/puppet/util/profiler/none.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-# A no-op profiler. Used when there is no profiling wanted.
-#
-# @api private
-class Puppet::Util::Profiler::None
- def profile(description, &block)
- yield
- end
-end
diff --git a/lib/puppet/util/profiler/wall_clock.rb b/lib/puppet/util/profiler/wall_clock.rb
index 2ba47ca3e..46ac095ec 100644
--- a/lib/puppet/util/profiler/wall_clock.rb
+++ b/lib/puppet/util/profiler/wall_clock.rb
@@ -6,13 +6,13 @@ require 'puppet/util/profiler/logging'
#
# @api private
class Puppet::Util::Profiler::WallClock < Puppet::Util::Profiler::Logging
- def start
+ def do_start(description, metric_id)
Timer.new
end
- def finish(context)
- context.stop
- "took #{context} seconds"
+ def do_finish(context, description, metric_id)
+ {:time => context.stop,
+ :msg => "took #{context} seconds"}
end
class Timer
@@ -23,11 +23,12 @@ class Puppet::Util::Profiler::WallClock < Puppet::Util::Profiler::Logging
end
def stop
- @finish = Time.now
+ @time = Time.now - @start
+ @time
end
def to_s
- format(FOUR_DECIMAL_DIGITS, @finish - @start)
+ format(FOUR_DECIMAL_DIGITS, @time)
end
end
end
diff --git a/lib/puppet/util/rdoc.rb b/lib/puppet/util/rdoc.rb
index 9119784e7..c2b9c15f4 100644
--- a/lib/puppet/util/rdoc.rb
+++ b/lib/puppet/util/rdoc.rb
@@ -37,6 +37,13 @@ module Puppet::Util::RDoc
options << "--force-update"
end
options += [ "--charset", charset] if charset
+ # Rdoc root default is Dir.pwd, but the win32-dir gem monkey patchs Dir.pwd
+ # replacing Ruby's normal / with \. When RDoc generates relative paths it
+ # uses relative_path_from that will generate errors when the slashes don't
+ # properly match. This is a workaround for that issue.
+ if Puppet.features.microsoft_windows? && RDoc::VERSION !~ /^[0-3]\./
+ options += [ "--root", Dir.pwd.gsub(/\\/, '/')]
+ end
options += files
# launch the documentation process
@@ -47,7 +54,7 @@ module Puppet::Util::RDoc
def manifestdoc(files)
Puppet[:ignoreimport] = true
files.select { |f| FileTest.file?(f) }.each do |f|
- parser = Puppet::Parser::Parser.new(Puppet.lookup(:environments).get(Puppet[:environment]))
+ parser = Puppet::Parser::Parser.new(Puppet.lookup(:current_environment))
parser.file = f
ast = parser.parse
output(f, ast)
diff --git a/lib/puppet/util/rdoc/parser/puppet_parser_core.rb b/lib/puppet/util/rdoc/parser/puppet_parser_core.rb
index baff91e98..4d29cc127 100644
--- a/lib/puppet/util/rdoc/parser/puppet_parser_core.rb
+++ b/lib/puppet/util/rdoc/parser/puppet_parser_core.rb
@@ -24,7 +24,7 @@ module RDoc::PuppetParserCore
# main entry point
def scan
- environment = Puppet.lookup(:environments).get(Puppet[:environment])
+ environment = Puppet.lookup(:current_environment)
known_resource_types = environment.known_resource_types
unless known_resource_types.watching_file?(@input_file_name)
Puppet.info "rdoc: scanning #{@input_file_name}"
diff --git a/lib/puppet/util/suidmanager.rb b/lib/puppet/util/suidmanager.rb
index 481e3864c..b4d77c7cd 100644
--- a/lib/puppet/util/suidmanager.rb
+++ b/lib/puppet/util/suidmanager.rb
@@ -18,14 +18,7 @@ module Puppet::Util::SUIDManager
def osx_maj_ver
return @osx_maj_ver unless @osx_maj_ver.nil?
- # 'kernel' is available without explicitly loading all facts
- if Facter.value('kernel') != 'Darwin'
- @osx_maj_ver = false
- return @osx_maj_ver
- end
- # But 'macosx_productversion_major' requires it.
- Facter.loadfacts
- @osx_maj_ver = Facter.value('macosx_productversion_major')
+ @osx_maj_ver = Facter.value('macosx_productversion_major') || false
end
module_function :osx_maj_ver
diff --git a/lib/puppet/util/tagging.rb b/lib/puppet/util/tagging.rb
index 031b27f93..266029a1f 100644
--- a/lib/puppet/util/tagging.rb
+++ b/lib/puppet/util/tagging.rb
@@ -3,12 +3,14 @@ require 'puppet/util/tag_set'
module Puppet::Util::Tagging
ValidTagRegex = /^\w[-\w:.]*$/
- # Add a tag to our current list. These tags will be added to all
- # of the objects contained in this scope.
+ # Add a tag to the current tag set.
+ # When a tag set is used for a scope, these tags will be added to all of
+ # the objects contained in this scope when the objects are finished.
+ #
def tag(*ary)
@tags ||= new_tags
- ary.each do |tag|
+ ary.flatten.each do |tag|
name = tag.to_s.downcase
if name =~ ValidTagRegex
@tags << name
@@ -16,12 +18,12 @@ module Puppet::Util::Tagging
@tags << section
end
else
- fail(Puppet::ParseError, "Invalid tag #{name}")
+ fail(Puppet::ParseError, "Invalid tag '#{name}'")
end
end
end
- # Are we tagged with the provided tag?
+ # Is the receiver tagged with the given tags?
def tagged?(*tags)
not ( self.tags & tags.flatten.collect { |t| t.to_s } ).empty?
end
@@ -39,7 +41,6 @@ module Puppet::Util::Tagging
return if tags.nil? or tags == ""
tags = tags.strip.split(/\s*,\s*/) if tags.is_a?(String)
-
tags.each {|t| tag(t) }
end
diff --git a/lib/puppet/util/windows.rb b/lib/puppet/util/windows.rb
index af8a36995..24aec4934 100644
--- a/lib/puppet/util/windows.rb
+++ b/lib/puppet/util/windows.rb
@@ -1,17 +1,28 @@
module Puppet::Util::Windows
+ module ADSI
+ class User; end
+ class UserProfile; end
+ class Group; end
+ end
+ module Registry
+ end
+
if Puppet::Util::Platform.windows?
# these reference platform specific gems
+ require 'puppet/util/windows/api_types'
+ require 'puppet/util/windows/string'
require 'puppet/util/windows/error'
+ require 'puppet/util/windows/com'
require 'puppet/util/windows/sid'
+ require 'puppet/util/windows/file'
require 'puppet/util/windows/security'
require 'puppet/util/windows/user'
require 'puppet/util/windows/process'
- require 'puppet/util/windows/string'
- require 'puppet/util/windows/file'
require 'puppet/util/windows/root_certs'
require 'puppet/util/windows/access_control_entry'
require 'puppet/util/windows/access_control_list'
require 'puppet/util/windows/security_descriptor'
+ require 'puppet/util/windows/adsi'
+ require 'puppet/util/windows/registry'
end
- require 'puppet/util/windows/registry'
end
diff --git a/lib/puppet/util/windows/access_control_list.rb b/lib/puppet/util/windows/access_control_list.rb
index 88e635ea2..ce0e8e6bd 100644
--- a/lib/puppet/util/windows/access_control_list.rb
+++ b/lib/puppet/util/windows/access_control_list.rb
@@ -68,9 +68,9 @@ class Puppet::Util::Windows::AccessControlList
# granted or denied the old_sid. We mask off all
# flags except those affecting inheritance of the
# ACE we're creating.
- inherit_mask = Windows::Security::CONTAINER_INHERIT_ACE |
- Windows::Security::OBJECT_INHERIT_ACE |
- Windows::Security::INHERIT_ONLY_ACE
+ inherit_mask = Puppet::Util::Windows::AccessControlEntry::CONTAINER_INHERIT_ACE |
+ Puppet::Util::Windows::AccessControlEntry::OBJECT_INHERIT_ACE |
+ Puppet::Util::Windows::AccessControlEntry::INHERIT_ONLY_ACE
explicit_ace = Puppet::Util::Windows::AccessControlEntry.new(new_sid, ace.mask, ace.flags & inherit_mask, ace.type)
aces_to_prepend << explicit_ace
else
@@ -85,7 +85,7 @@ class Puppet::Util::Windows::AccessControlList
@aces = []
if prepend_needed
- mask = Windows::Security::STANDARD_RIGHTS_ALL | Windows::Security::SPECIFIC_RIGHTS_ALL
+ mask = Puppet::Util::Windows::File::STANDARD_RIGHTS_ALL | Puppet::Util::Windows::File::SPECIFIC_RIGHTS_ALL
ace = Puppet::Util::Windows::AccessControlEntry.new(
Win32::Security::SID::LocalSystem,
mask)
diff --git a/lib/puppet/util/adsi.rb b/lib/puppet/util/windows/adsi.rb
index 4c30e18c2..041be7765 100644
--- a/lib/puppet/util/adsi.rb
+++ b/lib/puppet/util/windows/adsi.rb
@@ -1,5 +1,9 @@
-module Puppet::Util::ADSI
+module Puppet::Util::Windows::ADSI
+ require 'ffi'
+
class << self
+ extend FFI::Library
+
def connectable?(uri)
begin
!! connect(uri)
@@ -17,18 +21,29 @@ module Puppet::Util::ADSI
end
def create(name, resource_type)
- Puppet::Util::ADSI.connect(computer_uri).Create(resource_type, name)
+ Puppet::Util::Windows::ADSI.connect(computer_uri).Create(resource_type, name)
end
def delete(name, resource_type)
- Puppet::Util::ADSI.connect(computer_uri).Delete(resource_type, name)
+ Puppet::Util::Windows::ADSI.connect(computer_uri).Delete(resource_type, name)
end
+ # taken from winbase.h
+ MAX_COMPUTERNAME_LENGTH = 31
+
def computer_name
unless @computer_name
- buf = " " * 128
- Win32API.new('kernel32', 'GetComputerName', ['P','P'], 'I').call(buf, buf.length.to_s)
- @computer_name = buf.unpack("A*")[0]
+ max_length = MAX_COMPUTERNAME_LENGTH + 1 # NULL terminated
+ FFI::MemoryPointer.new(max_length * 2) do |buffer| # wide string
+ FFI::MemoryPointer.new(:dword, 1) do |buffer_size|
+ buffer_size.write_dword(max_length) # length in TCHARs
+
+ if GetComputerNameW(buffer, buffer_size) == FFI::WIN32_FALSE
+ raise Puppet::Util::Windows::Error.new("Failed to get computer name")
+ end
+ @computer_name = buffer.read_wide_string(buffer_size.read_dword)
+ end
+ end
end
@computer_name
end
@@ -48,8 +63,8 @@ module Puppet::Util::ADSI
begin
sid = Win32::Security::SID.new(Win32::Security::SID.string_to_sid(sid))
sid_uri(sid)
- rescue Win32::Security::SID::Error
- return nil
+ rescue SystemCallError
+ nil
end
end
@@ -71,14 +86,26 @@ module Puppet::Util::ADSI
end
def sid_for_account(name)
- Puppet.deprecation_warning "Puppet::Util::ADSI.sid_for_account is deprecated and will be removed in 3.0, use Puppet::Util::Windows::SID.name_to_sid instead."
+ Puppet.deprecation_warning "Puppet::Util::Windows::ADSI.sid_for_account is deprecated and will be removed in 3.0, use Puppet::Util::Windows::SID.name_to_sid instead."
- Puppet::Util::Windows::Security.name_to_sid(name)
+ Puppet::Util::Windows::SID.name_to_sid(name)
end
+
+ ffi_convention :stdcall
+
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/ms724295(v=vs.85).aspx
+ # BOOL WINAPI GetComputerName(
+ # _Out_ LPTSTR lpBuffer,
+ # _Inout_ LPDWORD lpnSize
+ # );
+ ffi_lib :kernel32
+ attach_function_private :GetComputerNameW,
+ [:lpwstr, :lpdword], :win32_bool
end
class User
extend Enumerable
+ extend FFI::Library
attr_accessor :native_user
attr_reader :name, :sid
@@ -100,19 +127,19 @@ module Puppet::Util::ADSI
end
def native_user
- @native_user ||= Puppet::Util::ADSI.connect(self.class.uri(*self.class.parse_name(@name)))
+ @native_user ||= Puppet::Util::Windows::ADSI.connect(self.class.uri(*self.class.parse_name(@name)))
end
def sid
- @sid ||= Puppet::Util::Windows::Security.octet_string_to_sid_object(native_user.objectSID)
+ @sid ||= Puppet::Util::Windows::SID.octet_string_to_sid_object(native_user.objectSID)
end
def self.uri(name, host = '.')
- if sid_uri = Puppet::Util::ADSI.sid_uri_safe(name) then return sid_uri end
+ if sid_uri = Puppet::Util::Windows::ADSI.sid_uri_safe(name) then return sid_uri end
host = '.' if ['NT AUTHORITY', 'BUILTIN', Socket.gethostname].include?(host)
- Puppet::Util::ADSI.uri(name, 'user', host)
+ Puppet::Util::Windows::ADSI.uri(name, 'user', host)
end
def uri
@@ -168,14 +195,14 @@ module Puppet::Util::ADSI
def add_to_groups(*group_names)
group_names.each do |group_name|
- Puppet::Util::ADSI::Group.new(group_name).add_member_sids(sid)
+ Puppet::Util::Windows::ADSI::Group.new(group_name).add_member_sids(sid)
end
end
alias add_to_group add_to_groups
def remove_from_groups(*group_names)
group_names.each do |group_name|
- Puppet::Util::ADSI::Group.new(group_name).remove_member_sids(sid)
+ Puppet::Util::Windows::ADSI::Group.new(group_name).remove_member_sids(sid)
end
end
alias remove_from_group remove_from_groups
@@ -199,20 +226,40 @@ module Puppet::Util::ADSI
def self.create(name)
# Windows error 1379: The specified local group already exists.
- raise Puppet::Error.new( "Cannot create user if group '#{name}' exists." ) if Puppet::Util::ADSI::Group.exists? name
- new(name, Puppet::Util::ADSI.create(name, 'user'))
+ raise Puppet::Error.new( "Cannot create user if group '#{name}' exists." ) if Puppet::Util::Windows::ADSI::Group.exists? name
+ new(name, Puppet::Util::Windows::ADSI.create(name, 'user'))
+ end
+
+ # UNLEN from lmcons.h - http://stackoverflow.com/a/2155176
+ MAX_USERNAME_LENGTH = 256
+ def self.current_user_name
+ user_name = ''
+ max_length = MAX_USERNAME_LENGTH + 1 # NULL terminated
+ FFI::MemoryPointer.new(max_length * 2) do |buffer| # wide string
+ FFI::MemoryPointer.new(:dword, 1) do |buffer_size|
+ buffer_size.write_dword(max_length) # length in TCHARs
+
+ if GetUserNameW(buffer, buffer_size) == FFI::WIN32_FALSE
+ raise Puppet::Util::Windows::Error.new("Failed to get user name")
+ end
+ # buffer_size includes trailing NULL
+ user_name = buffer.read_wide_string(buffer_size.read_dword - 1)
+ end
+ end
+
+ user_name
end
def self.exists?(name)
- Puppet::Util::ADSI::connectable?(User.uri(*User.parse_name(name)))
+ Puppet::Util::Windows::ADSI::connectable?(User.uri(*User.parse_name(name)))
end
def self.delete(name)
- Puppet::Util::ADSI.delete(name, 'user')
+ Puppet::Util::Windows::ADSI.delete(name, 'user')
end
def self.each(&block)
- wql = Puppet::Util::ADSI.execquery('select name from win32_useraccount where localaccount = "TRUE"')
+ wql = Puppet::Util::Windows::ADSI.execquery('select name from win32_useraccount where localaccount = "TRUE"')
users = []
wql.each do |u|
@@ -221,12 +268,23 @@ module Puppet::Util::ADSI
users.each(&block)
end
+
+ ffi_convention :stdcall
+
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/ms724432(v=vs.85).aspx
+ # BOOL WINAPI GetUserName(
+ # _Out_ LPTSTR lpBuffer,
+ # _Inout_ LPDWORD lpnSize
+ # );
+ ffi_lib :advapi32
+ attach_function_private :GetUserNameW,
+ [:lpwstr, :lpdword], :win32_bool
end
class UserProfile
def self.delete(sid)
begin
- Puppet::Util::ADSI.wmi_connection.Delete("Win32_UserProfile.SID='#{sid}'")
+ Puppet::Util::Windows::ADSI.wmi_connection.Delete("Win32_UserProfile.SID='#{sid}'")
rescue => e
# http://social.technet.microsoft.com/Forums/en/ITCG/thread/0f190051-ac96-4bf1-a47f-6b864bfacee5
# Prior to Vista SP1, there's no builtin way to programmatically
@@ -243,7 +301,7 @@ module Puppet::Util::ADSI
extend Enumerable
attr_accessor :native_group
- attr_reader :name
+ attr_reader :name, :sid
def initialize(name, native_group = nil)
@name = name
@native_group = native_group
@@ -254,13 +312,17 @@ module Puppet::Util::ADSI
end
def self.uri(name, host = '.')
- if sid_uri = Puppet::Util::ADSI.sid_uri_safe(name) then return sid_uri end
+ if sid_uri = Puppet::Util::Windows::ADSI.sid_uri_safe(name) then return sid_uri end
- Puppet::Util::ADSI.uri(name, 'group', host)
+ Puppet::Util::Windows::ADSI.uri(name, 'group', host)
end
def native_group
- @native_group ||= Puppet::Util::ADSI.connect(uri)
+ @native_group ||= Puppet::Util::Windows::ADSI.connect(uri)
+ end
+
+ def sid
+ @sid ||= Puppet::Util::Windows::SID.octet_string_to_sid_object(native_group.objectSID)
end
def commit
@@ -276,7 +338,7 @@ module Puppet::Util::ADSI
return [] if names.nil? or names.empty?
sids = names.map do |name|
- sid = Puppet::Util::Windows::Security.name_to_sid_object(name)
+ sid = Puppet::Util::Windows::SID.name_to_sid_object(name)
raise Puppet::Error.new( "Could not resolve username: #{name}" ) if !sid
[sid.to_s, sid]
end
@@ -285,14 +347,14 @@ module Puppet::Util::ADSI
end
def add_members(*names)
- Puppet.deprecation_warning('Puppet::Util::ADSI::Group#add_members is deprecated; please use Puppet::Util::ADSI::Group#add_member_sids')
+ Puppet.deprecation_warning('Puppet::Util::Windows::ADSI::Group#add_members is deprecated; please use Puppet::Util::Windows::ADSI::Group#add_member_sids')
sids = self.class.name_sid_hash(names)
add_member_sids(*sids.values)
end
alias add_member add_members
def remove_members(*names)
- Puppet.deprecation_warning('Puppet::Util::ADSI::Group#remove_members is deprecated; please use Puppet::Util::ADSI::Group#remove_member_sids')
+ Puppet.deprecation_warning('Puppet::Util::Windows::ADSI::Group#remove_members is deprecated; please use Puppet::Util::Windows::ADSI::Group#remove_member_sids')
sids = self.class.name_sid_hash(names)
remove_member_sids(*sids.values)
end
@@ -300,13 +362,13 @@ module Puppet::Util::ADSI
def add_member_sids(*sids)
sids.each do |sid|
- native_group.Add(Puppet::Util::ADSI.sid_uri(sid))
+ native_group.Add(Puppet::Util::Windows::ADSI.sid_uri(sid))
end
end
def remove_member_sids(*sids)
sids.each do |sid|
- native_group.Remove(Puppet::Util::ADSI.sid_uri(sid))
+ native_group.Remove(Puppet::Util::Windows::ADSI.sid_uri(sid))
end
end
@@ -320,7 +382,7 @@ module Puppet::Util::ADSI
def member_sids
sids = []
native_group.Members.each do |m|
- sids << Puppet::Util::Windows::Security.octet_string_to_sid_object(m.objectSID)
+ sids << Puppet::Util::Windows::SID.octet_string_to_sid_object(m.objectSID)
end
sids
end
@@ -342,20 +404,20 @@ module Puppet::Util::ADSI
def self.create(name)
# Windows error 2224: The account already exists.
- raise Puppet::Error.new( "Cannot create group if user '#{name}' exists." ) if Puppet::Util::ADSI::User.exists? name
- new(name, Puppet::Util::ADSI.create(name, 'group'))
+ raise Puppet::Error.new( "Cannot create group if user '#{name}' exists." ) if Puppet::Util::Windows::ADSI::User.exists? name
+ new(name, Puppet::Util::Windows::ADSI.create(name, 'group'))
end
def self.exists?(name)
- Puppet::Util::ADSI.connectable?(Group.uri(name))
+ Puppet::Util::Windows::ADSI.connectable?(Group.uri(name))
end
def self.delete(name)
- Puppet::Util::ADSI.delete(name, 'group')
+ Puppet::Util::Windows::ADSI.delete(name, 'group')
end
def self.each(&block)
- wql = Puppet::Util::ADSI.execquery( 'select name from win32_group where localaccount = "TRUE"' )
+ wql = Puppet::Util::Windows::ADSI.execquery( 'select name from win32_group where localaccount = "TRUE"' )
groups = []
wql.each do |g|
diff --git a/lib/puppet/util/windows/api_types.rb b/lib/puppet/util/windows/api_types.rb
new file mode 100644
index 000000000..52645d879
--- /dev/null
+++ b/lib/puppet/util/windows/api_types.rb
@@ -0,0 +1,255 @@
+require 'ffi'
+require 'puppet/util/windows/string'
+
+module Puppet::Util::Windows::APITypes
+ module ::FFI
+ WIN32_FALSE = 0
+
+ # standard Win32 error codes
+ ERROR_SUCCESS = 0
+ end
+
+ module ::FFI::Library
+ # Wrapper method for attach_function + private
+ def attach_function_private(*args)
+ attach_function(*args)
+ private args[0]
+ end
+ end
+
+ class ::FFI::Pointer
+ NULL_HANDLE = 0
+ NULL_TERMINATOR_WCHAR = 0
+
+ def self.from_string_to_wide_string(str, &block)
+ str = Puppet::Util::Windows::String.wide_string(str)
+ FFI::MemoryPointer.new(:byte, str.bytesize) do |ptr|
+ # uchar here is synonymous with byte
+ ptr.put_array_of_uchar(0, str.bytes.to_a)
+
+ yield ptr
+ end
+
+ # ptr has already had free called, so nothing to return
+ nil
+ end
+
+ def read_win32_bool
+ # BOOL is always a 32-bit integer in Win32
+ # some Win32 APIs return 1 for true, while others are non-0
+ read_int32 != FFI::WIN32_FALSE
+ end
+
+ alias_method :read_dword, :read_uint32
+ alias_method :read_win32_ulong, :read_uint32
+
+ alias_method :read_hresult, :read_int32
+
+ def read_handle
+ type_size == 4 ? read_uint32 : read_uint64
+ end
+
+ alias_method :read_wchar, :read_uint16
+ alias_method :read_word, :read_uint16
+
+ def read_wide_string(char_length)
+ # char_length is number of wide chars (typically excluding NULLs), *not* bytes
+ str = get_bytes(0, char_length * 2).force_encoding('UTF-16LE')
+ str.encode(Encoding.default_external)
+ end
+
+ def read_arbitrary_wide_string_up_to(max_char_length = 512)
+ # max_char_length is number of wide chars (typically excluding NULLs), *not* bytes
+ # use a pointer to read one UTF-16LE char (2 bytes) at a time
+ wchar_ptr = FFI::Pointer.new(:wchar, address)
+
+ # now iterate 2 bytes at a time until an offset lower than max_char_length is found
+ 0.upto(max_char_length - 1) do |i|
+ if wchar_ptr[i].read_wchar == NULL_TERMINATOR_WCHAR
+ return read_wide_string(i)
+ end
+ end
+
+ read_wide_string(max_char_length)
+ end
+
+ def read_win32_local_pointer(&block)
+ ptr = nil
+ begin
+ ptr = read_pointer
+ yield ptr
+ ensure
+ if ptr && ! ptr.null?
+ if FFI::WIN32::LocalFree(ptr.address) != FFI::Pointer::NULL_HANDLE
+ Puppet.debug "LocalFree memory leak"
+ end
+ end
+ end
+
+ # ptr has already had LocalFree called, so nothing to return
+ nil
+ end
+
+ def read_com_memory_pointer(&block)
+ ptr = nil
+ begin
+ ptr = read_pointer
+ yield ptr
+ ensure
+ FFI::WIN32::CoTaskMemFree(ptr) if ptr && ! ptr.null?
+ end
+
+ # ptr has already had CoTaskMemFree called, so nothing to return
+ nil
+ end
+
+
+ alias_method :write_dword, :write_uint32
+ alias_method :write_word, :write_uint16
+ end
+
+ # FFI Types
+ # https://github.com/ffi/ffi/wiki/Types
+
+ # Windows - Common Data Types
+ # http://msdn.microsoft.com/en-us/library/cc230309.aspx
+
+ # Windows Data Types
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/aa383751(v=vs.85).aspx
+
+ FFI.typedef :uint16, :word
+ FFI.typedef :uint32, :dword
+ # uintptr_t is defined in an FFI conf as platform specific, either
+ # ulong_long on x64 or just ulong on x86
+ FFI.typedef :uintptr_t, :handle
+ FFI.typedef :uintptr_t, :hwnd
+
+ # buffer_inout is similar to pointer (platform specific), but optimized for buffers
+ FFI.typedef :buffer_inout, :lpwstr
+ # buffer_in is similar to pointer (platform specific), but optimized for CONST read only buffers
+ FFI.typedef :buffer_in, :lpcwstr
+ FFI.typedef :buffer_in, :lpcolestr
+
+ # string is also similar to pointer, but should be used for const char *
+ # NOTE that this is not wide, useful only for A suffixed functions
+ FFI.typedef :string, :lpcstr
+
+ # pointer in FFI is platform specific
+ # NOTE: for API calls with reserved lpvoid parameters, pass a FFI::Pointer::NULL
+ FFI.typedef :pointer, :lpcvoid
+ FFI.typedef :pointer, :lpvoid
+ FFI.typedef :pointer, :lpword
+ FFI.typedef :pointer, :lpdword
+ FFI.typedef :pointer, :pdword
+ FFI.typedef :pointer, :phandle
+ FFI.typedef :pointer, :ulong_ptr
+ FFI.typedef :pointer, :pbool
+ FFI.typedef :pointer, :lpunknown
+
+ # any time LONG / ULONG is in a win32 API definition DO NOT USE platform specific width
+ # which is what FFI uses by default
+ # instead create new aliases for these very special cases
+ # NOTE: not a good idea to redefine FFI :ulong since other typedefs may rely on it
+ FFI.typedef :uint32, :win32_ulong
+ FFI.typedef :int32, :win32_long
+ # FFI bool can be only 1 byte at times,
+ # Win32 BOOL is a signed int, and is always 4 bytes, even on x64
+ # http://blogs.msdn.com/b/oldnewthing/archive/2011/03/28/10146459.aspx
+ FFI.typedef :int32, :win32_bool
+
+ # Same as a LONG, a 32-bit signed integer
+ FFI.typedef :int32, :hresult
+
+ # NOTE: FFI already defines (u)short as a 16-bit (un)signed like this:
+ # FFI.typedef :uint16, :ushort
+ # FFI.typedef :int16, :short
+
+ # 8 bits per byte
+ FFI.typedef :uchar, :byte
+ FFI.typedef :uint16, :wchar
+
+ module ::FFI::WIN32
+ extend ::FFI::Library
+
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/aa373931(v=vs.85).aspx
+ # typedef struct _GUID {
+ # DWORD Data1;
+ # WORD Data2;
+ # WORD Data3;
+ # BYTE Data4[8];
+ # } GUID;
+ class GUID < FFI::Struct
+ layout :Data1, :dword,
+ :Data2, :word,
+ :Data3, :word,
+ :Data4, [:byte, 8]
+
+ def self.[](s)
+ raise 'Bad GUID format.' unless s =~ /^[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}$/i
+
+ new.tap do |guid|
+ guid[:Data1] = s[0, 8].to_i(16)
+ guid[:Data2] = s[9, 4].to_i(16)
+ guid[:Data3] = s[14, 4].to_i(16)
+ guid[:Data4][0] = s[19, 2].to_i(16)
+ guid[:Data4][1] = s[21, 2].to_i(16)
+ s[24, 12].split('').each_slice(2).with_index do |a, i|
+ guid[:Data4][i + 2] = a.join('').to_i(16)
+ end
+ end
+ end
+
+ def ==(other) Windows.memcmp(other, self, size) == 0 end
+ end
+
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/ms724950(v=vs.85).aspx
+ # typedef struct _SYSTEMTIME {
+ # WORD wYear;
+ # WORD wMonth;
+ # WORD wDayOfWeek;
+ # WORD wDay;
+ # WORD wHour;
+ # WORD wMinute;
+ # WORD wSecond;
+ # WORD wMilliseconds;
+ # } SYSTEMTIME, *PSYSTEMTIME;
+ class SYSTEMTIME < FFI::Struct
+ layout :wYear, :word,
+ :wMonth, :word,
+ :wDayOfWeek, :word,
+ :wDay, :word,
+ :wHour, :word,
+ :wMinute, :word,
+ :wSecond, :word,
+ :wMilliseconds, :word
+
+ def to_local_time
+ Time.local(self[:wYear], self[:wMonth], self[:wDay],
+ self[:wHour], self[:wMinute], self[:wSecond], self[:wMilliseconds] * 1000)
+ end
+ end
+
+ ffi_convention :stdcall
+
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/aa366730(v=vs.85).aspx
+ # HLOCAL WINAPI LocalFree(
+ # _In_ HLOCAL hMem
+ # );
+ ffi_lib :kernel32
+ attach_function :LocalFree, [:handle], :handle
+
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/ms724211(v=vs.85).aspx
+ # BOOL WINAPI CloseHandle(
+ # _In_ HANDLE hObject
+ # );
+ ffi_lib :kernel32
+ attach_function_private :CloseHandle, [:handle], :win32_bool
+
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/ms680722(v=vs.85).aspx
+ # void CoTaskMemFree(
+ # _In_opt_ LPVOID pv
+ # );
+ ffi_lib :ole32
+ attach_function :CoTaskMemFree, [:lpvoid], :void
+ end
+end
diff --git a/lib/puppet/util/windows/com.rb b/lib/puppet/util/windows/com.rb
new file mode 100644
index 000000000..0033d53c0
--- /dev/null
+++ b/lib/puppet/util/windows/com.rb
@@ -0,0 +1,224 @@
+require 'ffi'
+
+module Puppet::Util::Windows::COM
+ extend FFI::Library
+
+ ffi_convention :stdcall
+
+ S_OK = 0
+ S_FALSE = 1
+
+ def SUCCEEDED(hr) hr >= 0 end
+ def FAILED(hr) hr < 0 end
+
+ module_function :SUCCEEDED, :FAILED
+
+ def raise_if_hresult_failed(name, *args)
+ failed = FAILED(result = send(name, *args)) and raise "#{name} failed (hresult #{format('%#08x', result)})."
+
+ result
+ ensure
+ yield failed if block_given?
+ end
+
+ module_function :raise_if_hresult_failed
+
+ CLSCTX_INPROC_SERVER = 0x1
+ CLSCTX_INPROC_HANDLER = 0x2
+ CLSCTX_LOCAL_SERVER = 0x4
+ CLSCTX_INPROC_SERVER16 = 0x8
+ CLSCTX_REMOTE_SERVER = 0x10
+ CLSCTX_INPROC_HANDLER16 = 0x20
+ CLSCTX_RESERVED1 = 0x40
+ CLSCTX_RESERVED2 = 0x80
+ CLSCTX_RESERVED3 = 0x100
+ CLSCTX_RESERVED4 = 0x200
+ CLSCTX_NO_CODE_DOWNLOAD = 0x400
+ CLSCTX_RESERVED5 = 0x800
+ CLSCTX_NO_CUSTOM_MARSHAL = 0x1000
+ CLSCTX_ENABLE_CODE_DOWNLOAD = 0x2000
+ CLSCTX_NO_FAILURE_LOG = 0x4000
+ CLSCTX_DISABLE_AAA = 0x8000
+ CLSCTX_ENABLE_AAA = 0x10000
+ CLSCTX_FROM_DEFAULT_CONTEXT = 0x20000
+ CLSCTX_ACTIVATE_32_BIT_SERVER = 0x40000
+ CLSCTX_ACTIVATE_64_BIT_SERVER = 0x80000
+ CLSCTX_ENABLE_CLOAKING = 0x100000
+ CLSCTX_PS_DLL = -0x80000000
+ CLSCTX_INPROC = CLSCTX_INPROC_SERVER | CLSCTX_INPROC_HANDLER
+ CLSCTX_ALL = CLSCTX_INPROC_SERVER | CLSCTX_INPROC_HANDLER | CLSCTX_LOCAL_SERVER | CLSCTX_REMOTE_SERVER
+ CLSCTX_SERVER = CLSCTX_INPROC_SERVER | CLSCTX_LOCAL_SERVER | CLSCTX_REMOTE_SERVER
+
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/ms686615(v=vs.85).aspx
+ # HRESULT CoCreateInstance(
+ # _In_ REFCLSID rclsid,
+ # _In_ LPUNKNOWN pUnkOuter,
+ # _In_ DWORD dwClsContext,
+ # _In_ REFIID riid,
+ # _Out_ LPVOID *ppv
+ # );
+ ffi_lib :ole32
+ attach_function_private :CoCreateInstance,
+ [:pointer, :lpunknown, :dword, :pointer, :lpvoid], :hresult
+
+ # code modified from Unknownr project https://github.com/rpeev/Unknownr
+ # licensed under MIT
+ module Interface
+ def self.[](*args)
+ spec, iid, *ifaces = args.reverse
+
+ spec.each { |name, signature| signature[0].unshift(:pointer) }
+
+ Class.new(FFI::Struct) do
+ const_set(:IID, iid)
+
+ vtable = Class.new(FFI::Struct) do
+ vtable_hash = Hash[(ifaces.map { |iface| iface::VTBL::SPEC.to_a } << spec.to_a).flatten(1)]
+ const_set(:SPEC, vtable_hash)
+
+ layout \
+ *self::SPEC.map { |name, signature| [name, callback(*signature)] }.flatten
+ end
+
+ const_set(:VTBL, vtable)
+
+ layout \
+ :lpVtbl, :pointer
+ end
+ end
+ end
+
+ module Helpers
+ def QueryInstance(klass)
+ instance = nil
+
+ FFI::MemoryPointer.new(:pointer) do |ppv|
+ QueryInterface(klass::IID, ppv)
+
+ instance = klass.new(ppv.read_pointer)
+ end
+
+ begin
+ yield instance
+ return self
+ ensure
+ instance.Release
+ end if block_given?
+
+ instance
+ end
+
+ def UseInstance(klass, name, *args)
+ instance = nil
+
+ FFI::MemoryPointer.new(:pointer) do |ppv|
+ send(name, *args, ppv)
+
+ yield instance = klass.new(ppv.read_pointer)
+ end
+
+ self
+ ensure
+ instance.Release if instance && ! instance.null?
+ end
+ end
+
+ module Instance
+ def self.[](iface)
+ Class.new(iface) do
+ send(:include, Helpers)
+
+ def initialize(pointer)
+ self.pointer = pointer
+
+ @vtbl = self.class::VTBL.new(self[:lpVtbl])
+ end
+
+ attr_reader :vtbl
+
+ self::VTBL.members.each do |name|
+ define_method(name) do |*args|
+ if Puppet::Util::Windows::COM.FAILED(result = @vtbl[name].call(self, *args))
+ raise Puppet::Util::Windows::Error.new("Failed to call #{self}::#{name} with HRESULT: #{result}.", result)
+ end
+ result
+ end
+ end
+
+ layout \
+ :lpVtbl, :pointer
+ end
+ end
+ end
+
+ module Factory
+ def self.[](iface, clsid)
+ Class.new(iface) do
+ send(:include, Helpers)
+
+ const_set(:CLSID, clsid)
+
+ def initialize(opts = {})
+ @opts = opts
+
+ @opts[:clsctx] ||= CLSCTX_INPROC_SERVER
+
+ FFI::MemoryPointer.new(:pointer) do |ppv|
+ hr = Puppet::Util::Windows::COM.CoCreateInstance(self.class::CLSID, FFI::Pointer::NULL, @opts[:clsctx], self.class::IID, ppv)
+ if Puppet::Util::Windows::COM.FAILED(hr)
+ raise "CoCreateInstance failed (#{self.class})."
+ end
+
+ self.pointer = ppv.read_pointer
+ end
+
+ @vtbl = self.class::VTBL.new(self[:lpVtbl])
+ end
+
+ attr_reader :vtbl
+
+ self::VTBL.members.each do |name|
+ define_method(name) do |*args|
+ if Puppet::Util::Windows::COM.FAILED(result = @vtbl[name].call(self, *args))
+ raise Puppet::Util::Windows::Error.new("Failed to call #{self}::#{name} with HRESULT: #{result}.", result)
+ end
+ result
+ end
+ end
+
+ layout \
+ :lpVtbl, :pointer
+ end
+ end
+ end
+
+ IUnknown = Interface[
+ FFI::WIN32::GUID['00000000-0000-0000-C000-000000000046'],
+
+ QueryInterface: [[:pointer, :pointer], :hresult],
+ AddRef: [[], :win32_ulong],
+ Release: [[], :win32_ulong]
+ ]
+
+ Unknown = Instance[IUnknown]
+
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/ms678543(v=vs.85).aspx
+ # HRESULT CoInitialize(
+ # _In_opt_ LPVOID pvReserved
+ # );
+ ffi_lib :ole32
+ attach_function_private :CoInitialize, [:lpvoid], :hresult
+
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/ms688715(v=vs.85).aspx
+ # void CoUninitialize(void);
+ ffi_lib :ole32
+ attach_function_private :CoUninitialize, [], :void
+
+ def InitializeCom
+ raise_if_hresult_failed(:CoInitialize, FFI::Pointer::NULL)
+
+ at_exit { CoUninitialize() }
+ end
+
+ module_function :InitializeCom
+end
diff --git a/lib/puppet/util/windows/error.rb b/lib/puppet/util/windows/error.rb
index c6088fa7a..4f54bca45 100644
--- a/lib/puppet/util/windows/error.rb
+++ b/lib/puppet/util/windows/error.rb
@@ -2,15 +2,82 @@ require 'puppet/util/windows'
# represents an error resulting from a Win32 error code
class Puppet::Util::Windows::Error < Puppet::Error
- require 'windows/error'
- include ::Windows::Error
+ require 'ffi'
+ extend FFI::Library
attr_reader :code
- def initialize(message, code = GetLastError.call, original = nil)
- super(message + ": #{get_last_error(code)}", original)
+ # NOTE: FFI.errno only works properly when prior Win32 calls have been made
+ # through FFI bindings. Calls made through Win32API do not have their error
+ # codes captured by FFI.errno
+ def initialize(message, code = FFI.errno, original = nil)
+ super(message + ": #{self.class.format_error_code(code)}", original)
@code = code
end
-end
+ # Helper method that wraps FormatMessage that returns a human readable string.
+ def self.format_error_code(code)
+ # specifying 0 will look for LANGID in the following order
+ # 1.Language neutral
+ # 2.Thread LANGID, based on the thread's locale value
+ # 3.User default LANGID, based on the user's default locale value
+ # 4.System default LANGID, based on the system default locale value
+ # 5.US English
+ dwLanguageId = 0
+ flags = FORMAT_MESSAGE_ALLOCATE_BUFFER |
+ FORMAT_MESSAGE_FROM_SYSTEM |
+ FORMAT_MESSAGE_ARGUMENT_ARRAY |
+ FORMAT_MESSAGE_IGNORE_INSERTS |
+ FORMAT_MESSAGE_MAX_WIDTH_MASK
+ error_string = ''
+
+ # this pointer actually points to a :lpwstr (pointer) since we're letting Windows allocate for us
+ FFI::MemoryPointer.new(:pointer, 1) do |buffer_ptr|
+ length = FormatMessageW(flags, FFI::Pointer::NULL, code, dwLanguageId,
+ buffer_ptr, 0, FFI::Pointer::NULL)
+
+ if length == FFI::WIN32_FALSE
+ # can't raise same error type here or potentially recurse infinitely
+ raise Puppet::Error.new("FormatMessageW could not format code #{code}")
+ end
+
+ # returns an FFI::Pointer with autorelease set to false, which is what we want
+ buffer_ptr.read_win32_local_pointer do |wide_string_ptr|
+ if wide_string_ptr.null?
+ raise Puppet::Error.new("FormatMessageW failed to allocate buffer for code #{code}")
+ end
+
+ error_string = wide_string_ptr.read_wide_string(length)
+ end
+ end
+
+ error_string
+ end
+
+ ERROR_FILE_NOT_FOUND = 2
+ ERROR_ACCESS_DENIED = 5
+
+ FORMAT_MESSAGE_ALLOCATE_BUFFER = 0x00000100
+ FORMAT_MESSAGE_IGNORE_INSERTS = 0x00000200
+ FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000
+ FORMAT_MESSAGE_ARGUMENT_ARRAY = 0x00002000
+ FORMAT_MESSAGE_MAX_WIDTH_MASK = 0x000000FF
+
+ ffi_convention :stdcall
+
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/ms679351(v=vs.85).aspx
+ # DWORD WINAPI FormatMessage(
+ # _In_ DWORD dwFlags,
+ # _In_opt_ LPCVOID lpSource,
+ # _In_ DWORD dwMessageId,
+ # _In_ DWORD dwLanguageId,
+ # _Out_ LPTSTR lpBuffer,
+ # _In_ DWORD nSize,
+ # _In_opt_ va_list *Arguments
+ # );
+ # NOTE: since we're not preallocating the buffer, use a :pointer for lpBuffer
+ ffi_lib :kernel32
+ attach_function_private :FormatMessageW,
+ [:dword, :lpcvoid, :dword, :dword, :pointer, :dword, :pointer], :dword
+end
diff --git a/lib/puppet/util/windows/file.rb b/lib/puppet/util/windows/file.rb
index 15a6ef469..07ffc1cf4 100644
--- a/lib/puppet/util/windows/file.rb
+++ b/lib/puppet/util/windows/file.rb
@@ -2,149 +2,140 @@ require 'puppet/util/windows'
module Puppet::Util::Windows::File
require 'ffi'
- require 'windows/api'
+ extend FFI::Library
+ extend Puppet::Util::Windows::String
+
+ FILE_ATTRIBUTE_READONLY = 0x00000001
+
+ SYNCHRONIZE = 0x100000
+ STANDARD_RIGHTS_REQUIRED = 0xf0000
+ STANDARD_RIGHTS_READ = 0x20000
+ STANDARD_RIGHTS_WRITE = 0x20000
+ STANDARD_RIGHTS_EXECUTE = 0x20000
+ STANDARD_RIGHTS_ALL = 0x1F0000
+ SPECIFIC_RIGHTS_ALL = 0xFFFF
+
+ FILE_READ_DATA = 1
+ FILE_WRITE_DATA = 2
+ FILE_APPEND_DATA = 4
+ FILE_READ_EA = 8
+ FILE_WRITE_EA = 16
+ FILE_EXECUTE = 32
+ FILE_DELETE_CHILD = 64
+ FILE_READ_ATTRIBUTES = 128
+ FILE_WRITE_ATTRIBUTES = 256
+
+ FILE_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0x1FF
+
+ FILE_GENERIC_READ =
+ STANDARD_RIGHTS_READ |
+ FILE_READ_DATA |
+ FILE_READ_ATTRIBUTES |
+ FILE_READ_EA |
+ SYNCHRONIZE
+
+ FILE_GENERIC_WRITE =
+ STANDARD_RIGHTS_WRITE |
+ FILE_WRITE_DATA |
+ FILE_WRITE_ATTRIBUTES |
+ FILE_WRITE_EA |
+ FILE_APPEND_DATA |
+ SYNCHRONIZE
+
+ FILE_GENERIC_EXECUTE =
+ STANDARD_RIGHTS_EXECUTE |
+ FILE_READ_ATTRIBUTES |
+ FILE_EXECUTE |
+ SYNCHRONIZE
def replace_file(target, source)
- target_encoded = Puppet::Util::Windows::String.wide_string(target.to_s)
- source_encoded = Puppet::Util::Windows::String.wide_string(source.to_s)
+ target_encoded = wide_string(target.to_s)
+ source_encoded = wide_string(source.to_s)
flags = 0x1
backup_file = nil
- result = API.replace_file(
+ result = ReplaceFileW(
target_encoded,
source_encoded,
backup_file,
flags,
- 0,
- 0
+ FFI::Pointer::NULL,
+ FFI::Pointer::NULL
)
- return true if result
+ return true if result != FFI::WIN32_FALSE
raise Puppet::Util::Windows::Error.new("ReplaceFile(#{target}, #{source})")
end
module_function :replace_file
- MoveFileEx = Windows::API.new('MoveFileExW', 'PPL', 'B')
def move_file_ex(source, target, flags = 0)
- result = MoveFileEx.call(Puppet::Util::Windows::String.wide_string(source.to_s),
- Puppet::Util::Windows::String.wide_string(target.to_s),
- flags)
- return true unless result == 0
+ result = MoveFileExW(wide_string(source.to_s),
+ wide_string(target.to_s),
+ flags)
+
+ return true if result != FFI::WIN32_FALSE
raise Puppet::Util::Windows::Error.
new("MoveFileEx(#{source}, #{target}, #{flags.to_s(8)})")
end
module_function :move_file_ex
- module API
- extend FFI::Library
- ffi_lib 'kernel32'
- ffi_convention :stdcall
-
- # http://msdn.microsoft.com/en-us/library/windows/desktop/aa365512(v=vs.85).aspx
- # BOOL WINAPI ReplaceFile(
- # _In_ LPCTSTR lpReplacedFileName,
- # _In_ LPCTSTR lpReplacementFileName,
- # _In_opt_ LPCTSTR lpBackupFileName,
- # _In_ DWORD dwReplaceFlags - 0x1 REPLACEFILE_WRITE_THROUGH,
- # 0x2 REPLACEFILE_IGNORE_MERGE_ERRORS,
- # 0x4 REPLACEFILE_IGNORE_ACL_ERRORS
- # _Reserved_ LPVOID lpExclude,
- # _Reserved_ LPVOID lpReserved
- # );
- attach_function :replace_file, :ReplaceFileW,
- [:buffer_in, :buffer_in, :buffer_in, :uint, :uint, :uint], :bool
-
- # BOOLEAN WINAPI CreateSymbolicLink(
- # _In_ LPTSTR lpSymlinkFileName, - symbolic link to be created
- # _In_ LPTSTR lpTargetFileName, - name of target for symbolic link
- # _In_ DWORD dwFlags - 0x0 target is a file, 0x1 target is a directory
- # );
- # rescue on Windows < 6.0 so that code doesn't explode
- begin
- attach_function :create_symbolic_link, :CreateSymbolicLinkW,
- [:buffer_in, :buffer_in, :uint], :bool
- rescue LoadError
- end
-
- # DWORD WINAPI GetFileAttributes(
- # _In_ LPCTSTR lpFileName
- # );
- attach_function :get_file_attributes, :GetFileAttributesW,
- [:buffer_in], :uint
-
- # HANDLE WINAPI CreateFile(
- # _In_ LPCTSTR lpFileName,
- # _In_ DWORD dwDesiredAccess,
- # _In_ DWORD dwShareMode,
- # _In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes,
- # _In_ DWORD dwCreationDisposition,
- # _In_ DWORD dwFlagsAndAttributes,
- # _In_opt_ HANDLE hTemplateFile
- # );
- attach_function :create_file, :CreateFileW,
- [:buffer_in, :uint, :uint, :pointer, :uint, :uint, :uint], :uint
-
- # http://msdn.microsoft.com/en-us/library/windows/desktop/aa363216(v=vs.85).aspx
- # BOOL WINAPI DeviceIoControl(
- # _In_ HANDLE hDevice,
- # _In_ DWORD dwIoControlCode,
- # _In_opt_ LPVOID lpInBuffer,
- # _In_ DWORD nInBufferSize,
- # _Out_opt_ LPVOID lpOutBuffer,
- # _In_ DWORD nOutBufferSize,
- # _Out_opt_ LPDWORD lpBytesReturned,
- # _Inout_opt_ LPOVERLAPPED lpOverlapped
- # );
- attach_function :device_io_control, :DeviceIoControl,
- [:uint, :uint, :pointer, :uint, :pointer, :uint, :pointer, :pointer], :bool
-
- MAXIMUM_REPARSE_DATA_BUFFER_SIZE = 16384
-
- # REPARSE_DATA_BUFFER
- # http://msdn.microsoft.com/en-us/library/cc232006.aspx
- # http://msdn.microsoft.com/en-us/library/windows/hardware/ff552012(v=vs.85).aspx
- # struct is always MAXIMUM_REPARSE_DATA_BUFFER_SIZE bytes
- class ReparseDataBuffer < FFI::Struct
- layout :reparse_tag, :uint,
- :reparse_data_length, :ushort,
- :reserved, :ushort,
- :substitute_name_offset, :ushort,
- :substitute_name_length, :ushort,
- :print_name_offset, :ushort,
- :print_name_length, :ushort,
- :flags, :uint,
- # max less above fields dword / uint 4 bytes, ushort 2 bytes
- :path_buffer, [:uchar, MAXIMUM_REPARSE_DATA_BUFFER_SIZE - 20]
- end
-
- # BOOL WINAPI CloseHandle(
- # _In_ HANDLE hObject
- # );
- attach_function :close_handle, :CloseHandle, [:uint], :bool
- end
-
def symlink(target, symlink)
flags = File.directory?(target) ? 0x1 : 0x0
- result = API.create_symbolic_link(Puppet::Util::Windows::String.wide_string(symlink.to_s),
- Puppet::Util::Windows::String.wide_string(target.to_s), flags)
- return true if result
+ result = CreateSymbolicLinkW(wide_string(symlink.to_s),
+ wide_string(target.to_s), flags)
+ return true if result != FFI::WIN32_FALSE
raise Puppet::Util::Windows::Error.new(
"CreateSymbolicLink(#{symlink}, #{target}, #{flags.to_s(8)})")
end
module_function :symlink
INVALID_FILE_ATTRIBUTES = 0xFFFFFFFF #define INVALID_FILE_ATTRIBUTES (DWORD (-1))
- def self.get_file_attributes(file_name)
- result = API.get_file_attributes(Puppet::Util::Windows::String.wide_string(file_name.to_s))
+
+ def get_file_attributes(file_name)
+ Puppet.deprecation_warning('Puppet::Util::Windows::File.get_file_attributes is deprecated; please use Puppet::Util::Windows::File.get_attributes')
+ get_attributes(file_name)
+ end
+ module_function :get_file_attributes
+
+ def get_attributes(file_name)
+ result = GetFileAttributesW(wide_string(file_name.to_s))
return result unless result == INVALID_FILE_ATTRIBUTES
raise Puppet::Util::Windows::Error.new("GetFileAttributes(#{file_name})")
end
+ module_function :get_attributes
+
+ def add_attributes(path, flags)
+ oldattrs = get_attributes(path)
+
+ if (oldattrs | flags) != oldattrs
+ set_attributes(path, oldattrs | flags)
+ end
+ end
+ module_function :add_attributes
+
+ def remove_attributes(path, flags)
+ oldattrs = get_attributes(path)
+
+ if (oldattrs & ~flags) != oldattrs
+ set_attributes(path, oldattrs & ~flags)
+ end
+ end
+ module_function :remove_attributes
+
+ def set_attributes(path, flags)
+ success = SetFileAttributesW(wide_string(path), flags) != FFI::WIN32_FALSE
+ raise Puppet::Util::Windows::Error.new("Failed to set file attributes") if !success
- INVALID_HANDLE_VALUE = -1 #define INVALID_HANDLE_VALUE ((HANDLE)(LONG_PTR)-1)
+ success
+ end
+ module_function :set_attributes
+
+ #define INVALID_HANDLE_VALUE ((HANDLE)(LONG_PTR)-1)
+ INVALID_HANDLE_VALUE = FFI::Pointer.new(-1).address
def self.create_file(file_name, desired_access, share_mode, security_attributes,
creation_disposition, flags_and_attributes, template_file_handle)
- result = API.create_file(Puppet::Util::Windows::String.wide_string(file_name.to_s),
+ result = CreateFileW(wide_string(file_name.to_s),
desired_access, share_mode, security_attributes, creation_disposition,
flags_and_attributes, template_file_handle)
@@ -155,31 +146,47 @@ module Puppet::Util::Windows::File
"#{flags_and_attributes.to_s(8)}, #{template_file_handle})")
end
+ def self.get_reparse_point_data(handle, &block)
+ # must be multiple of 1024, min 10240
+ FFI::MemoryPointer.new(REPARSE_DATA_BUFFER.size) do |reparse_data_buffer_ptr|
+ device_io_control(handle, FSCTL_GET_REPARSE_POINT, nil, reparse_data_buffer_ptr)
+ yield REPARSE_DATA_BUFFER.new(reparse_data_buffer_ptr)
+ end
+
+ # underlying struct MemoryPointer has been cleaned up by this point, nothing to return
+ nil
+ end
+
def self.device_io_control(handle, io_control_code, in_buffer = nil, out_buffer = nil)
if out_buffer.nil?
raise Puppet::Util::Windows::Error.new("out_buffer is required")
end
- result = API.device_io_control(
- handle,
- io_control_code,
- in_buffer, in_buffer.nil? ? 0 : in_buffer.size,
- out_buffer, out_buffer.size,
- FFI::MemoryPointer.new(:uint, 1),
- nil
- )
+ FFI::MemoryPointer.new(:dword, 1) do |bytes_returned_ptr|
+ result = DeviceIoControl(
+ handle,
+ io_control_code,
+ in_buffer, in_buffer.nil? ? 0 : in_buffer.size,
+ out_buffer, out_buffer.size,
+ bytes_returned_ptr,
+ nil
+ )
+
+ if result == FFI::WIN32_FALSE
+ raise Puppet::Util::Windows::Error.new(
+ "DeviceIoControl(#{handle}, #{io_control_code}, " +
+ "#{in_buffer}, #{in_buffer ? in_buffer.size : ''}, " +
+ "#{out_buffer}, #{out_buffer ? out_buffer.size : ''}")
+ end
+ end
- return out_buffer if result
- raise Puppet::Util::Windows::Error.new(
- "DeviceIoControl(#{handle}, #{io_control_code}, " +
- "#{in_buffer}, #{in_buffer ? in_buffer.size : ''}, " +
- "#{out_buffer}, #{out_buffer ? out_buffer.size : ''}")
+ out_buffer
end
FILE_ATTRIBUTE_REPARSE_POINT = 0x400
def symlink?(file_name)
begin
- attributes = get_file_attributes(file_name)
+ attributes = get_attributes(file_name)
(attributes & FILE_ATTRIBUTE_REPARSE_POINT) == FILE_ATTRIBUTE_REPARSE_POINT
rescue
# raised INVALID_FILE_ATTRIBUTES is equivalent to file not found
@@ -189,7 +196,11 @@ module Puppet::Util::Windows::File
module_function :symlink?
GENERIC_READ = 0x80000000
+ GENERIC_WRITE = 0x40000000
+ GENERIC_EXECUTE = 0x20000000
+ GENERIC_ALL = 0x10000000
FILE_SHARE_READ = 1
+ FILE_SHARE_WRITE = 2
OPEN_EXISTING = 3
FILE_FLAG_OPEN_REPARSE_POINT = 0x00200000
FILE_FLAG_BACKUP_SEMANTICS = 0x02000000
@@ -197,7 +208,7 @@ module Puppet::Util::Windows::File
def self.open_symlink(link_name)
begin
yield handle = create_file(
- Puppet::Util::Windows::String.wide_string(link_name.to_s),
+ link_name,
GENERIC_READ,
FILE_SHARE_READ,
nil, # security_attributes
@@ -205,14 +216,20 @@ module Puppet::Util::Windows::File
FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS,
0) # template_file
ensure
- API.close_handle(handle) if handle
+ FFI::WIN32.CloseHandle(handle) if handle
end
+
+ # handle has had CloseHandle called against it, so nothing to return
+ nil
end
def readlink(link_name)
+ link = nil
open_symlink(link_name) do |handle|
- resolve_symlink(handle)
+ link = resolve_symlink(handle)
end
+
+ link
end
module_function :readlink
@@ -265,15 +282,120 @@ module Puppet::Util::Windows::File
FSCTL_GET_REPARSE_POINT = 0x900a8
def self.resolve_symlink(handle)
- # must be multiple of 1024, min 10240
- out_buffer = FFI::MemoryPointer.new(API::ReparseDataBuffer.size)
- device_io_control(handle, FSCTL_GET_REPARSE_POINT, nil, out_buffer)
+ path = nil
+ get_reparse_point_data(handle) do |reparse_data|
+ offset = reparse_data[:PrintNameOffset]
+ length = reparse_data[:PrintNameLength]
- reparse_data = API::ReparseDataBuffer.new(out_buffer)
- offset = reparse_data[:print_name_offset]
- length = reparse_data[:print_name_length]
+ ptr = reparse_data.pointer + reparse_data.offset_of(:PathBuffer) + offset
+ path = ptr.read_wide_string(length / 2) # length is bytes, need UTF-16 wchars
+ end
+
+ path
+ end
+
+ ffi_convention :stdcall
+
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/aa365512(v=vs.85).aspx
+ # BOOL WINAPI ReplaceFile(
+ # _In_ LPCTSTR lpReplacedFileName,
+ # _In_ LPCTSTR lpReplacementFileName,
+ # _In_opt_ LPCTSTR lpBackupFileName,
+ # _In_ DWORD dwReplaceFlags - 0x1 REPLACEFILE_WRITE_THROUGH,
+ # 0x2 REPLACEFILE_IGNORE_MERGE_ERRORS,
+ # 0x4 REPLACEFILE_IGNORE_ACL_ERRORS
+ # _Reserved_ LPVOID lpExclude,
+ # _Reserved_ LPVOID lpReserved
+ # );
+ ffi_lib :kernel32
+ attach_function_private :ReplaceFileW,
+ [:lpcwstr, :lpcwstr, :lpcwstr, :dword, :lpvoid, :lpvoid], :win32_bool
+
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/aa365240(v=vs.85).aspx
+ # BOOL WINAPI MoveFileEx(
+ # _In_ LPCTSTR lpExistingFileName,
+ # _In_opt_ LPCTSTR lpNewFileName,
+ # _In_ DWORD dwFlags
+ # );
+ ffi_lib :kernel32
+ attach_function_private :MoveFileExW,
+ [:lpcwstr, :lpcwstr, :dword], :win32_bool
+
+ # BOOLEAN WINAPI CreateSymbolicLink(
+ # _In_ LPTSTR lpSymlinkFileName, - symbolic link to be created
+ # _In_ LPTSTR lpTargetFileName, - name of target for symbolic link
+ # _In_ DWORD dwFlags - 0x0 target is a file, 0x1 target is a directory
+ # );
+ # rescue on Windows < 6.0 so that code doesn't explode
+ begin
+ ffi_lib :kernel32
+ attach_function_private :CreateSymbolicLinkW,
+ [:lpwstr, :lpwstr, :dword], :win32_bool
+ rescue LoadError
+ end
- result = reparse_data[:path_buffer].to_a[offset, length].pack('C*')
- result.force_encoding('UTF-16LE').encode(Encoding.default_external)
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/aa364944(v=vs.85).aspx
+ # DWORD WINAPI GetFileAttributes(
+ # _In_ LPCTSTR lpFileName
+ # );
+ ffi_lib :kernel32
+ attach_function_private :GetFileAttributesW,
+ [:lpcwstr], :dword
+
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/aa365535(v=vs.85).aspx
+ # BOOL WINAPI SetFileAttributes(
+ # _In_ LPCTSTR lpFileName,
+ # _In_ DWORD dwFileAttributes
+ # );
+ ffi_lib :kernel32
+ attach_function_private :SetFileAttributesW,
+ [:lpcwstr, :dword], :win32_bool
+
+ # HANDLE WINAPI CreateFile(
+ # _In_ LPCTSTR lpFileName,
+ # _In_ DWORD dwDesiredAccess,
+ # _In_ DWORD dwShareMode,
+ # _In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes,
+ # _In_ DWORD dwCreationDisposition,
+ # _In_ DWORD dwFlagsAndAttributes,
+ # _In_opt_ HANDLE hTemplateFile
+ # );
+ ffi_lib :kernel32
+ attach_function_private :CreateFileW,
+ [:lpcwstr, :dword, :dword, :pointer, :dword, :dword, :handle], :handle
+
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/aa363216(v=vs.85).aspx
+ # BOOL WINAPI DeviceIoControl(
+ # _In_ HANDLE hDevice,
+ # _In_ DWORD dwIoControlCode,
+ # _In_opt_ LPVOID lpInBuffer,
+ # _In_ DWORD nInBufferSize,
+ # _Out_opt_ LPVOID lpOutBuffer,
+ # _In_ DWORD nOutBufferSize,
+ # _Out_opt_ LPDWORD lpBytesReturned,
+ # _Inout_opt_ LPOVERLAPPED lpOverlapped
+ # );
+ ffi_lib :kernel32
+ attach_function_private :DeviceIoControl,
+ [:handle, :dword, :lpvoid, :dword, :lpvoid, :dword, :lpdword, :pointer], :win32_bool
+
+ MAXIMUM_REPARSE_DATA_BUFFER_SIZE = 16384
+
+ # REPARSE_DATA_BUFFER
+ # http://msdn.microsoft.com/en-us/library/cc232006.aspx
+ # http://msdn.microsoft.com/en-us/library/windows/hardware/ff552012(v=vs.85).aspx
+ # struct is always MAXIMUM_REPARSE_DATA_BUFFER_SIZE bytes
+ class REPARSE_DATA_BUFFER < FFI::Struct
+ layout :ReparseTag, :win32_ulong,
+ :ReparseDataLength, :ushort,
+ :Reserved, :ushort,
+ :SubstituteNameOffset, :ushort,
+ :SubstituteNameLength, :ushort,
+ :PrintNameOffset, :ushort,
+ :PrintNameLength, :ushort,
+ :Flags, :win32_ulong,
+ # max less above fields dword / uint 4 bytes, ushort 2 bytes
+ # technically a WCHAR buffer, but we care about size in bytes here
+ :PathBuffer, [:byte, MAXIMUM_REPARSE_DATA_BUFFER_SIZE - 20]
end
end
diff --git a/lib/puppet/util/windows/process.rb b/lib/puppet/util/windows/process.rb
index c1f0bedd4..c6a8c0db5 100644
--- a/lib/puppet/util/windows/process.rb
+++ b/lib/puppet/util/windows/process.rb
@@ -1,123 +1,12 @@
require 'puppet/util/windows'
-require 'windows/process'
-require 'windows/handle'
-require 'windows/synchronize'
+require 'win32/process'
+require 'ffi'
module Puppet::Util::Windows::Process
- extend ::Windows::Process
- extend ::Windows::Handle
- extend ::Windows::Synchronize
-
- module API
- require 'ffi'
- extend FFI::Library
- ffi_convention :stdcall
-
- ffi_lib 'kernel32'
-
- # http://msdn.microsoft.com/en-us/library/windows/desktop/ms683179(v=vs.85).aspx
- # HANDLE WINAPI GetCurrentProcess(void);
- attach_function :get_current_process, :GetCurrentProcess, [], :uint
-
- # BOOL WINAPI CloseHandle(
- # _In_ HANDLE hObject
- # );
- attach_function :close_handle, :CloseHandle, [:uint], :bool
-
- ffi_lib 'advapi32'
-
- # http://msdn.microsoft.com/en-us/library/windows/desktop/aa379295(v=vs.85).aspx
- # BOOL WINAPI OpenProcessToken(
- # _In_ HANDLE ProcessHandle,
- # _In_ DWORD DesiredAccess,
- # _Out_ PHANDLE TokenHandle
- # );
- attach_function :open_process_token, :OpenProcessToken,
- [:uint, :uint, :pointer], :bool
-
-
- # http://msdn.microsoft.com/en-us/library/windows/desktop/aa379261(v=vs.85).aspx
- # typedef struct _LUID {
- # DWORD LowPart;
- # LONG HighPart;
- # } LUID, *PLUID;
- class LUID < FFI::Struct
- layout :low_part, :uint,
- :high_part, :int
- end
-
- # http://msdn.microsoft.com/en-us/library/Windows/desktop/aa379180(v=vs.85).aspx
- # BOOL WINAPI LookupPrivilegeValue(
- # _In_opt_ LPCTSTR lpSystemName,
- # _In_ LPCTSTR lpName,
- # _Out_ PLUID lpLuid
- # );
- attach_function :lookup_privilege_value, :LookupPrivilegeValueA,
- [:string, :string, :pointer], :bool
-
- Token_Information = enum(
- :token_user, 1,
- :token_groups,
- :token_privileges,
- :token_owner,
- :token_primary_group,
- :token_default_dacl,
- :token_source,
- :token_type,
- :token_impersonation_level,
- :token_statistics,
- :token_restricted_sids,
- :token_session_id,
- :token_groups_and_privileges,
- :token_session_reference,
- :token_sandbox_inert,
- :token_audit_policy,
- :token_origin,
- :token_elevation_type,
- :token_linked_token,
- :token_elevation,
- :token_has_restrictions,
- :token_access_information,
- :token_virtualization_allowed,
- :token_virtualization_enabled,
- :token_integrity_level,
- :token_ui_access,
- :token_mandatory_policy,
- :token_logon_sid,
- :max_token_info_class
- )
-
- # http://msdn.microsoft.com/en-us/library/windows/desktop/aa379263(v=vs.85).aspx
- # typedef struct _LUID_AND_ATTRIBUTES {
- # LUID Luid;
- # DWORD Attributes;
- # } LUID_AND_ATTRIBUTES, *PLUID_AND_ATTRIBUTES;
- class LUID_And_Attributes < FFI::Struct
- layout :luid, LUID,
- :attributes, :uint
- end
+ extend Puppet::Util::Windows::String
+ extend FFI::Library
- # http://msdn.microsoft.com/en-us/library/windows/desktop/aa379630(v=vs.85).aspx
- # typedef struct _TOKEN_PRIVILEGES {
- # DWORD PrivilegeCount;
- # LUID_AND_ATTRIBUTES Privileges[ANYSIZE_ARRAY];
- # } TOKEN_PRIVILEGES, *PTOKEN_PRIVILEGES;
- class Token_Privileges < FFI::Struct
- layout :privilege_count, :uint,
- :privileges, [LUID_And_Attributes, 1] # placeholder for offset
- end
-
- # http://msdn.microsoft.com/en-us/library/windows/desktop/aa446671(v=vs.85).aspx
- # BOOL WINAPI GetTokenInformation(
- # _In_ HANDLE TokenHandle,
- # _In_ TOKEN_INFORMATION_CLASS TokenInformationClass,
- # _Out_opt_ LPVOID TokenInformation,
- # _In_ DWORD TokenInformationLength,
- # _Out_ PDWORD ReturnLength
- # );
- attach_function :get_token_information, :GetTokenInformation,
- [:uint, Token_Information, :pointer, :uint, :pointer ], :bool
- end
+ WAIT_TIMEOUT = 0x102
def execute(command, arguments, stdin, stdout, stderr)
Process.create( :command_line => command, :startup_info => {:stdin => stdin, :stdout => stdout, :stderr => stderr}, :close_handles => false )
@@ -125,21 +14,23 @@ module Puppet::Util::Windows::Process
module_function :execute
def wait_process(handle)
- while WaitForSingleObject(handle, 0) == Windows::Synchronize::WAIT_TIMEOUT
+ while WaitForSingleObject(handle, 0) == WAIT_TIMEOUT
sleep(1)
end
- exit_status = [0].pack('L')
- unless GetExitCodeProcess(handle, exit_status)
- raise Puppet::Util::Windows::Error.new("Failed to get child process exit code")
+ exit_status = -1
+ FFI::MemoryPointer.new(:dword, 1) do |exit_status_ptr|
+ if GetExitCodeProcess(handle, exit_status_ptr) == FFI::WIN32_FALSE
+ raise Puppet::Util::Windows::Error.new("Failed to get child process exit code")
+ end
+ exit_status = exit_status_ptr.read_dword
+
+ # $CHILD_STATUS is not set when calling win32/process Process.create
+ # and since it's read-only, we can't set it. But we can execute a
+ # a shell that simply returns the desired exit status, which has the
+ # desired effect.
+ %x{#{ENV['COMSPEC']} /c exit #{exit_status}}
end
- exit_status = exit_status.unpack('L').first
-
- # $CHILD_STATUS is not set when calling win32/process Process.create
- # and since it's read-only, we can't set it. But we can execute a
- # a shell that simply returns the desired exit status, which has the
- # desired effect.
- %x{#{ENV['COMSPEC']} /c exit #{exit_status}}
exit_status
end
@@ -147,86 +38,133 @@ module Puppet::Util::Windows::Process
def get_current_process
# this pseudo-handle does not require closing per MSDN docs
- API.get_current_process
+ GetCurrentProcess()
end
module_function :get_current_process
- def open_process_token(handle, desired_access)
- token_handle_ptr = FFI::MemoryPointer.new(:uint, 1)
- result = API.open_process_token(handle, desired_access, token_handle_ptr)
- if !result
- raise Puppet::Util::Windows::Error.new(
- "OpenProcessToken(#{handle}, #{desired_access.to_s(8)}, #{token_handle_ptr})")
- end
-
+ def open_process_token(handle, desired_access, &block)
+ token_handle = nil
begin
- yield token_handle = token_handle_ptr.read_uint
+ FFI::MemoryPointer.new(:handle, 1) do |token_handle_ptr|
+ result = OpenProcessToken(handle, desired_access, token_handle_ptr)
+ if result == FFI::WIN32_FALSE
+ raise Puppet::Util::Windows::Error.new(
+ "OpenProcessToken(#{handle}, #{desired_access.to_s(8)}, #{token_handle_ptr})")
+ end
+
+ yield token_handle = token_handle_ptr.read_handle
+ end
+
+ token_handle
ensure
- API.close_handle(token_handle)
+ FFI::WIN32.CloseHandle(token_handle) if token_handle
end
+
+ # token_handle has had CloseHandle called against it, so nothing to return
+ nil
end
module_function :open_process_token
- def lookup_privilege_value(name, system_name = '')
- luid = FFI::MemoryPointer.new(API::LUID.size)
- result = API.lookup_privilege_value(
- system_name,
- name.to_s,
- luid
- )
-
- return API::LUID.new(luid) if result
- raise Puppet::Util::Windows::Error.new(
- "LookupPrivilegeValue(#{system_name}, #{name}, #{luid})")
+ # Execute a block with the current process token
+ def with_process_token(access, &block)
+ handle = get_current_process
+ open_process_token(handle, access) do |token_handle|
+ yield token_handle
+ end
+
+ # all handles have been closed, so nothing to safely return
+ nil
+ end
+ module_function :with_process_token
+
+ def lookup_privilege_value(name, system_name = '', &block)
+ FFI::MemoryPointer.new(LUID.size) do |luid_ptr|
+ result = LookupPrivilegeValueW(
+ wide_string(system_name),
+ wide_string(name.to_s),
+ luid_ptr
+ )
+
+ if result == FFI::WIN32_FALSE
+ raise Puppet::Util::Windows::Error.new(
+ "LookupPrivilegeValue(#{system_name}, #{name}, #{luid_ptr})")
+ end
+
+ yield LUID.new(luid_ptr)
+ end
+
+ # the underlying MemoryPointer for LUID is cleaned up by this point
+ nil
end
module_function :lookup_privilege_value
- def get_token_information(token_handle, token_information)
+ def get_token_information(token_handle, token_information, &block)
# to determine buffer size
- return_length_ptr = FFI::MemoryPointer.new(:uint, 1)
- result = API.get_token_information(token_handle, token_information, nil, 0, return_length_ptr)
- return_length = return_length_ptr.read_uint
-
- if return_length <= 0
- raise Puppet::Util::Windows::Error.new(
- "GetTokenInformation(#{token_handle}, #{token_information}, nil, 0, #{return_length_ptr})")
+ FFI::MemoryPointer.new(:dword, 1) do |return_length_ptr|
+ result = GetTokenInformation(token_handle, token_information, nil, 0, return_length_ptr)
+ return_length = return_length_ptr.read_dword
+
+ if return_length <= 0
+ raise Puppet::Util::Windows::Error.new(
+ "GetTokenInformation(#{token_handle}, #{token_information}, nil, 0, #{return_length_ptr})")
+ end
+
+ # re-call API with properly sized buffer for all results
+ FFI::MemoryPointer.new(return_length) do |token_information_buf|
+ result = GetTokenInformation(token_handle, token_information,
+ token_information_buf, return_length, return_length_ptr)
+
+ if result == FFI::WIN32_FALSE
+ raise Puppet::Util::Windows::Error.new(
+ "GetTokenInformation(#{token_handle}, #{token_information}, #{token_information_buf}, " +
+ "#{return_length}, #{return_length_ptr})")
+ end
+
+ yield token_information_buf
+ end
end
- # re-call API with properly sized buffer for all results
- token_information_buf = FFI::MemoryPointer.new(return_length)
- result = API.get_token_information(token_handle, token_information,
- token_information_buf, return_length, return_length_ptr)
-
- if !result
- raise Puppet::Util::Windows::Error.new(
- "GetTokenInformation(#{token_handle}, #{token_information}, #{token_information_buf}, " +
- "#{return_length}, #{return_length_ptr})")
- end
+ # GetTokenInformation buffer has been cleaned up by this point, nothing to return
+ nil
+ end
+ module_function :get_token_information
- raw_privileges = API::Token_Privileges.new(token_information_buf)
- privileges = { :count => raw_privileges[:privilege_count], :privileges => [] }
+ def parse_token_information_as_token_privileges(token_information_buf)
+ raw_privileges = TOKEN_PRIVILEGES.new(token_information_buf)
+ privileges = { :count => raw_privileges[:PrivilegeCount], :privileges => [] }
- offset = token_information_buf + API::Token_Privileges.offset_of(:privileges)
- privilege_ptr = FFI::Pointer.new(API::LUID_And_Attributes, offset)
+ offset = token_information_buf + TOKEN_PRIVILEGES.offset_of(:Privileges)
+ privilege_ptr = FFI::Pointer.new(LUID_AND_ATTRIBUTES, offset)
- # extract each instance of LUID_And_Attributes
+ # extract each instance of LUID_AND_ATTRIBUTES
0.upto(privileges[:count] - 1) do |i|
- privileges[:privileges] << API::LUID_And_Attributes.new(privilege_ptr[i])
+ privileges[:privileges] << LUID_AND_ATTRIBUTES.new(privilege_ptr[i])
end
privileges
end
- module_function :get_token_information
+ module_function :parse_token_information_as_token_privileges
+
+ def parse_token_information_as_token_elevation(token_information_buf)
+ TOKEN_ELEVATION.new(token_information_buf)
+ end
+ module_function :parse_token_information_as_token_elevation
TOKEN_ALL_ACCESS = 0xF01FF
ERROR_NO_SUCH_PRIVILEGE = 1313
def process_privilege_symlink?
+ privilege_symlink = false
handle = get_current_process
open_process_token(handle, TOKEN_ALL_ACCESS) do |token_handle|
- luid = lookup_privilege_value('SeCreateSymbolicLinkPrivilege')
- token_info = get_token_information(token_handle, :token_privileges)
- token_info[:privileges].any? { |p| p[:luid].values == luid.values }
+ lookup_privilege_value('SeCreateSymbolicLinkPrivilege') do |luid|
+ get_token_information(token_handle, :TokenPrivileges) do |token_info|
+ token_privileges = parse_token_information_as_token_privileges(token_info)
+ privilege_symlink = token_privileges[:privileges].any? { |p| p[:Luid].values == luid.values }
+ end
+ end
end
+
+ privilege_symlink
rescue Puppet::Util::Windows::Error => e
if e.code == ERROR_NO_SUCH_PRIVILEGE
false # pre-Vista
@@ -235,4 +173,182 @@ module Puppet::Util::Windows::Process
end
end
module_function :process_privilege_symlink?
+
+ TOKEN_QUERY = 0x0008
+ # Returns whether or not the owner of the current process is running
+ # with elevated security privileges.
+ #
+ # Only supported on Windows Vista or later.
+ #
+ def elevated_security?
+ # default / pre-Vista
+ elevated = false
+ handle = nil
+
+ begin
+ handle = get_current_process
+ open_process_token(handle, TOKEN_QUERY) do |token_handle|
+ get_token_information(token_handle, :TokenElevation) do |token_info|
+ token_elevation = parse_token_information_as_token_elevation(token_info)
+ # TokenIsElevated member of the TOKEN_ELEVATION struct
+ elevated = token_elevation[:TokenIsElevated] != 0
+ end
+ end
+
+ elevated
+ rescue Puppet::Util::Windows::Error => e
+ raise e if e.code != ERROR_NO_SUCH_PRIVILEGE
+ ensure
+ FFI::WIN32.CloseHandle(handle) if handle
+ end
+ end
+ module_function :elevated_security?
+
+ ABOVE_NORMAL_PRIORITY_CLASS = 0x0008000
+ BELOW_NORMAL_PRIORITY_CLASS = 0x0004000
+ HIGH_PRIORITY_CLASS = 0x0000080
+ IDLE_PRIORITY_CLASS = 0x0000040
+ NORMAL_PRIORITY_CLASS = 0x0000020
+ REALTIME_PRIORITY_CLASS = 0x0000010
+
+ ffi_convention :stdcall
+
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/ms687032(v=vs.85).aspx
+ # DWORD WINAPI WaitForSingleObject(
+ # _In_ HANDLE hHandle,
+ # _In_ DWORD dwMilliseconds
+ # );
+ ffi_lib :kernel32
+ attach_function_private :WaitForSingleObject,
+ [:handle, :dword], :dword
+
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/ms683189(v=vs.85).aspx
+ # BOOL WINAPI GetExitCodeProcess(
+ # _In_ HANDLE hProcess,
+ # _Out_ LPDWORD lpExitCode
+ # );
+ ffi_lib :kernel32
+ attach_function_private :GetExitCodeProcess,
+ [:handle, :lpdword], :win32_bool
+
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/ms683179(v=vs.85).aspx
+ # HANDLE WINAPI GetCurrentProcess(void);
+ ffi_lib :kernel32
+ attach_function_private :GetCurrentProcess, [], :handle
+
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/aa379295(v=vs.85).aspx
+ # BOOL WINAPI OpenProcessToken(
+ # _In_ HANDLE ProcessHandle,
+ # _In_ DWORD DesiredAccess,
+ # _Out_ PHANDLE TokenHandle
+ # );
+ ffi_lib :advapi32
+ attach_function_private :OpenProcessToken,
+ [:handle, :dword, :phandle], :win32_bool
+
+
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/aa379261(v=vs.85).aspx
+ # typedef struct _LUID {
+ # DWORD LowPart;
+ # LONG HighPart;
+ # } LUID, *PLUID;
+ class LUID < FFI::Struct
+ layout :LowPart, :dword,
+ :HighPart, :win32_long
+ end
+
+ # http://msdn.microsoft.com/en-us/library/Windows/desktop/aa379180(v=vs.85).aspx
+ # BOOL WINAPI LookupPrivilegeValue(
+ # _In_opt_ LPCTSTR lpSystemName,
+ # _In_ LPCTSTR lpName,
+ # _Out_ PLUID lpLuid
+ # );
+ ffi_lib :advapi32
+ attach_function_private :LookupPrivilegeValueW,
+ [:lpcwstr, :lpcwstr, :pointer], :win32_bool
+
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/aa379626(v=vs.85).aspx
+ TOKEN_INFORMATION_CLASS = enum(
+ :TokenUser, 1,
+ :TokenGroups,
+ :TokenPrivileges,
+ :TokenOwner,
+ :TokenPrimaryGroup,
+ :TokenDefaultDacl,
+ :TokenSource,
+ :TokenType,
+ :TokenImpersonationLevel,
+ :TokenStatistics,
+ :TokenRestrictedSids,
+ :TokenSessionId,
+ :TokenGroupsAndPrivileges,
+ :TokenSessionReference,
+ :TokenSandBoxInert,
+ :TokenAuditPolicy,
+ :TokenOrigin,
+ :TokenElevationType,
+ :TokenLinkedToken,
+ :TokenElevation,
+ :TokenHasRestrictions,
+ :TokenAccessInformation,
+ :TokenVirtualizationAllowed,
+ :TokenVirtualizationEnabled,
+ :TokenIntegrityLevel,
+ :TokenUIAccess,
+ :TokenMandatoryPolicy,
+ :TokenLogonSid,
+ :TokenIsAppContainer,
+ :TokenCapabilities,
+ :TokenAppContainerSid,
+ :TokenAppContainerNumber,
+ :TokenUserClaimAttributes,
+ :TokenDeviceClaimAttributes,
+ :TokenRestrictedUserClaimAttributes,
+ :TokenRestrictedDeviceClaimAttributes,
+ :TokenDeviceGroups,
+ :TokenRestrictedDeviceGroups,
+ :TokenSecurityAttributes,
+ :TokenIsRestricted,
+ :MaxTokenInfoClass
+ )
+
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/aa379263(v=vs.85).aspx
+ # typedef struct _LUID_AND_ATTRIBUTES {
+ # LUID Luid;
+ # DWORD Attributes;
+ # } LUID_AND_ATTRIBUTES, *PLUID_AND_ATTRIBUTES;
+ class LUID_AND_ATTRIBUTES < FFI::Struct
+ layout :Luid, LUID,
+ :Attributes, :dword
+ end
+
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/aa379630(v=vs.85).aspx
+ # typedef struct _TOKEN_PRIVILEGES {
+ # DWORD PrivilegeCount;
+ # LUID_AND_ATTRIBUTES Privileges[ANYSIZE_ARRAY];
+ # } TOKEN_PRIVILEGES, *PTOKEN_PRIVILEGES;
+ class TOKEN_PRIVILEGES < FFI::Struct
+ layout :PrivilegeCount, :dword,
+ :Privileges, [LUID_AND_ATTRIBUTES, 1] # placeholder for offset
+ end
+
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/bb530717(v=vs.85).aspx
+ # typedef struct _TOKEN_ELEVATION {
+ # DWORD TokenIsElevated;
+ # } TOKEN_ELEVATION, *PTOKEN_ELEVATION;
+ class TOKEN_ELEVATION < FFI::Struct
+ layout :TokenIsElevated, :dword
+ end
+
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/aa446671(v=vs.85).aspx
+ # BOOL WINAPI GetTokenInformation(
+ # _In_ HANDLE TokenHandle,
+ # _In_ TOKEN_INFORMATION_CLASS TokenInformationClass,
+ # _Out_opt_ LPVOID TokenInformation,
+ # _In_ DWORD TokenInformationLength,
+ # _Out_ PDWORD ReturnLength
+ # );
+ ffi_lib :advapi32
+ attach_function_private :GetTokenInformation,
+ [:handle, TOKEN_INFORMATION_CLASS, :lpvoid, :dword, :pdword ], :win32_bool
end
diff --git a/lib/puppet/util/windows/registry.rb b/lib/puppet/util/windows/registry.rb
index 13b931ec0..84cdde793 100644
--- a/lib/puppet/util/windows/registry.rb
+++ b/lib/puppet/util/windows/registry.rb
@@ -2,6 +2,9 @@ require 'puppet/util/windows'
module Puppet::Util::Windows
module Registry
+ require 'ffi'
+ extend FFI::Library
+
# http://msdn.microsoft.com/en-us/library/windows/desktop/aa384129(v=vs.85).aspx
KEY64 = 0x100
KEY32 = 0x200
@@ -50,9 +53,8 @@ module Puppet::Util::Windows
# code page. However, ruby incorrectly sets the string
# encoding to US-ASCII. So we must force the encoding to the
# correct value.
- require 'windows/national'
begin
- cp = Windows::National::GetACP.call
+ cp = GetACP()
@encoding = Encoding.const_get("CP#{cp}")
rescue
@encoding = Encoding.default_external
@@ -66,5 +68,13 @@ module Puppet::Util::Windows
end
end
private :force_encoding
+
+
+ ffi_convention :stdcall
+
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/dd318070(v=vs.85).aspx
+ # UINT GetACP(void);
+ ffi_lib :kernel32
+ attach_function_private :GetACP, [], :uint32
end
end
diff --git a/lib/puppet/util/windows/root_certs.rb b/lib/puppet/util/windows/root_certs.rb
index 7988ab832..e15a203f0 100644
--- a/lib/puppet/util/windows/root_certs.rb
+++ b/lib/puppet/util/windows/root_certs.rb
@@ -9,9 +9,6 @@ class Puppet::Util::Windows::RootCerts
include Enumerable
extend FFI::Library
- typedef :ulong, :dword
- typedef :uintptr_t, :handle
-
def initialize(roots)
@roots = roots
end
@@ -57,11 +54,17 @@ class Puppet::Util::Windows::RootCerts
certs
end
- private
-
- # typedef ULONG_PTR HCRYPTPROV_LEGACY;
+ ffi_convention :stdcall
# typedef void *HCERTSTORE;
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/aa377189(v=vs.85).aspx
+ # typedef struct _CERT_CONTEXT {
+ # DWORD dwCertEncodingType;
+ # BYTE *pbCertEncoded;
+ # DWORD cbCertEncoded;
+ # PCERT_INFO pCertInfo;
+ # HCERTSTORE hCertStore;
+ # } CERT_CONTEXT, *PCERT_CONTEXT;typedef const CERT_CONTEXT *PCCERT_CONTEXT;
class CERT_CONTEXT < FFI::Struct
layout(
:dwCertEncodingType, :dword,
@@ -72,15 +75,18 @@ class Puppet::Util::Windows::RootCerts
)
end
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/aa376560(v=vs.85).aspx
# HCERTSTORE
# WINAPI
# CertOpenSystemStoreA(
# __in_opt HCRYPTPROV_LEGACY hProv,
# __in LPCSTR szSubsystemProtocol
# );
+ # typedef ULONG_PTR HCRYPTPROV_LEGACY;
ffi_lib :crypt32
- attach_function :CertOpenSystemStoreA, [:pointer, :string], :handle
+ attach_function_private :CertOpenSystemStoreA, [:ulong_ptr, :lpcstr], :handle
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/aa376050(v=vs.85).aspx
# PCCERT_CONTEXT
# WINAPI
# CertEnumCertificatesInStore(
@@ -88,8 +94,9 @@ class Puppet::Util::Windows::RootCerts
# __in_opt PCCERT_CONTEXT pPrevCertContext
# );
ffi_lib :crypt32
- attach_function :CertEnumCertificatesInStore, [:handle, :pointer], :pointer
+ attach_function_private :CertEnumCertificatesInStore, [:handle, :pointer], :pointer
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/aa376026(v=vs.85).aspx
# BOOL
# WINAPI
# CertCloseStore(
@@ -97,5 +104,5 @@ class Puppet::Util::Windows::RootCerts
# __in DWORD dwFlags
# );
ffi_lib :crypt32
- attach_function :CertCloseStore, [:handle, :dword], :bool
+ attach_function_private :CertCloseStore, [:handle, :dword], :win32_bool
end
diff --git a/lib/puppet/util/windows/security.rb b/lib/puppet/util/windows/security.rb
index d1c1bcd34..8dc03a9a2 100644
--- a/lib/puppet/util/windows/security.rb
+++ b/lib/puppet/util/windows/security.rb
@@ -67,26 +67,11 @@ require 'ffi'
require 'win32/security'
-require 'windows/file'
-require 'windows/handle'
-require 'windows/security'
-require 'windows/process'
-require 'windows/memory'
-require 'windows/msvcrt/buffer'
-require 'windows/volume'
-
module Puppet::Util::Windows::Security
- include ::Windows::File
- include ::Windows::Handle
- include ::Windows::Security
- include ::Windows::Process
- include ::Windows::Memory
- include ::Windows::MSVCRT::Buffer
- include ::Windows::Volume
-
- include Puppet::Util::Windows::SID
+ include Puppet::Util::Windows::String
extend Puppet::Util::Windows::Security
+ extend FFI::Library
# file modes
S_IRUSR = 0000400
@@ -111,6 +96,20 @@ module Puppet::Util::Windows::Security
NO_INHERITANCE = 0x0
SE_DACL_PROTECTED = 0x1000
+ FILE = Puppet::Util::Windows::File
+
+ SE_BACKUP_NAME = 'SeBackupPrivilege'
+ SE_RESTORE_NAME = 'SeRestorePrivilege'
+
+ DELETE = 0x00010000
+ READ_CONTROL = 0x20000
+ WRITE_DAC = 0x40000
+ WRITE_OWNER = 0x80000
+
+ OWNER_SECURITY_INFORMATION = 1
+ GROUP_SECURITY_INFORMATION = 2
+ DACL_SECURITY_INFORMATION = 4
+
# Set the owner of the object referenced by +path+ to the specified
# +owner_sid+. The owner sid should be of the form "S-1-5-32-544"
# and can either be a user or group. Only a user with the
@@ -161,51 +160,50 @@ module Puppet::Util::Windows::Security
get_security_descriptor(path).group
end
- def supports_acl?(path)
- flags = 0.chr * 4
+ FILE_PERSISTENT_ACLS = 0x00000008
+ def supports_acl?(path)
+ supported = false
root = Pathname.new(path).enum_for(:ascend).to_a.last.to_s
# 'A trailing backslash is required'
root = "#{root}\\" unless root =~ /[\/\\]$/
- unless GetVolumeInformation(root, nil, 0, nil, nil, flags, nil, 0)
- raise Puppet::Util::Windows::Error.new("Failed to get volume information")
+
+ FFI::MemoryPointer.new(:pointer, 1) do |flags_ptr|
+ if GetVolumeInformationW(wide_string(root), FFI::Pointer::NULL, 0,
+ FFI::Pointer::NULL, FFI::Pointer::NULL,
+ flags_ptr, FFI::Pointer::NULL, 0) == FFI::WIN32_FALSE
+ raise Puppet::Util::Windows::Error.new("Failed to get volume information")
+ end
+ supported = flags_ptr.read_dword & FILE_PERSISTENT_ACLS == FILE_PERSISTENT_ACLS
end
- (flags.unpack('L')[0] & Windows::File::FILE_PERSISTENT_ACLS) != 0
+ supported
end
def get_attributes(path)
- attributes = GetFileAttributes(path)
-
- raise Puppet::Util::Windows::Error.new("Failed to get file attributes") if attributes == INVALID_FILE_ATTRIBUTES
-
- attributes
+ Puppet.deprecation_warning('Puppet::Util::Windows::Security.get_attributes is deprecated; please use Puppet::Util::Windows::File.get_attributes')
+ FILE.get_attributes(file_name)
end
def add_attributes(path, flags)
- oldattrs = get_attributes(path)
-
- if (oldattrs | flags) != oldattrs
- set_attributes(path, oldattrs | flags)
- end
+ Puppet.deprecation_warning('Puppet::Util::Windows::Security.add_attributes is deprecated; please use Puppet::Util::Windows::File.add_attributes')
+ FILE.add_attributes(path, flags)
end
def remove_attributes(path, flags)
- oldattrs = get_attributes(path)
-
- if (oldattrs & ~flags) != oldattrs
- set_attributes(path, oldattrs & ~flags)
- end
+ Puppet.deprecation_warning('Puppet::Util::Windows::Security.remove_attributes is deprecated; please use Puppet::Util::Windows::File.remove_attributes')
+ FILE.remove_attributes(path, flags)
end
def set_attributes(path, flags)
- raise Puppet::Util::Windows::Error.new("Failed to set file attributes") unless SetFileAttributes(path, flags)
+ Puppet.deprecation_warning('Puppet::Util::Windows::Security.set_attributes is deprecated; please use Puppet::Util::Windows::File.set_attributes')
+ FILE.set_attributes(path, flags)
end
MASK_TO_MODE = {
- FILE_GENERIC_READ => S_IROTH,
- FILE_GENERIC_WRITE => S_IWOTH,
- (FILE_GENERIC_EXECUTE & ~FILE_READ_ATTRIBUTES) => S_IXOTH
+ FILE::FILE_GENERIC_READ => S_IROTH,
+ FILE::FILE_GENERIC_WRITE => S_IWOTH,
+ (FILE::FILE_GENERIC_EXECUTE & ~FILE::FILE_READ_ATTRIBUTES) => S_IXOTH
}
def get_aces_for_path_by_sid(path, sid)
@@ -250,11 +248,12 @@ module Puppet::Util::Windows::Security
mode |= (v << 6) | (v << 3) | v
end
end
- if File.directory?(path) && (ace.mask & (FILE_WRITE_DATA | FILE_EXECUTE | FILE_DELETE_CHILD)) == (FILE_WRITE_DATA | FILE_EXECUTE)
+ if File.directory?(path) &&
+ (ace.mask & (FILE::FILE_WRITE_DATA | FILE::FILE_EXECUTE | FILE::FILE_DELETE_CHILD)) == (FILE::FILE_WRITE_DATA | FILE::FILE_EXECUTE)
mode |= S_ISVTX;
end
when well_known_nobody_sid
- if (ace.mask & FILE_APPEND_DATA).nonzero?
+ if (ace.mask & FILE::FILE_APPEND_DATA).nonzero?
mode |= S_ISVTX
end
when well_known_system_sid
@@ -279,9 +278,9 @@ module Puppet::Util::Windows::Security
end
MODE_TO_MASK = {
- S_IROTH => FILE_GENERIC_READ,
- S_IWOTH => FILE_GENERIC_WRITE,
- S_IXOTH => (FILE_GENERIC_EXECUTE & ~FILE_READ_ATTRIBUTES),
+ S_IROTH => FILE::FILE_GENERIC_READ,
+ S_IWOTH => FILE::FILE_GENERIC_WRITE,
+ S_IXOTH => (FILE::FILE_GENERIC_EXECUTE & ~FILE::FILE_READ_ATTRIBUTES),
}
# Set the mode of the object referenced by +path+ to the specified
@@ -303,9 +302,15 @@ module Puppet::Util::Windows::Security
well_known_nobody_sid = Win32::Security::SID::Nobody
well_known_system_sid = Win32::Security::SID::LocalSystem
- owner_allow = STANDARD_RIGHTS_ALL | FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES
- group_allow = STANDARD_RIGHTS_READ | FILE_READ_ATTRIBUTES | SYNCHRONIZE
- other_allow = STANDARD_RIGHTS_READ | FILE_READ_ATTRIBUTES | SYNCHRONIZE
+ owner_allow = FILE::STANDARD_RIGHTS_ALL |
+ FILE::FILE_READ_ATTRIBUTES |
+ FILE::FILE_WRITE_ATTRIBUTES
+ group_allow = FILE::STANDARD_RIGHTS_READ |
+ FILE::FILE_READ_ATTRIBUTES |
+ FILE::SYNCHRONIZE
+ other_allow = FILE::STANDARD_RIGHTS_READ |
+ FILE::FILE_READ_ATTRIBUTES |
+ FILE::SYNCHRONIZE
nobody_allow = 0
system_allow = 0
@@ -322,27 +327,27 @@ module Puppet::Util::Windows::Security
end
if (mode & S_ISVTX).nonzero?
- nobody_allow |= FILE_APPEND_DATA;
+ nobody_allow |= FILE::FILE_APPEND_DATA;
end
# caller is NOT managing SYSTEM by using group or owner, so set to FULL
if ! [sd.owner, sd.group].include? well_known_system_sid
# we don't check S_ISYSTEM_MISSING bit, but automatically carry over existing SYSTEM perms
# by default set SYSTEM perms to full
- system_allow = FILE_ALL_ACCESS
+ system_allow = FILE::FILE_ALL_ACCESS
end
isdir = File.directory?(path)
if isdir
if (mode & (S_IWUSR | S_IXUSR)) == (S_IWUSR | S_IXUSR)
- owner_allow |= FILE_DELETE_CHILD
+ owner_allow |= FILE::FILE_DELETE_CHILD
end
if (mode & (S_IWGRP | S_IXGRP)) == (S_IWGRP | S_IXGRP) && (mode & S_ISVTX) == 0
- group_allow |= FILE_DELETE_CHILD
+ group_allow |= FILE::FILE_DELETE_CHILD
end
if (mode & (S_IWOTH | S_IXOTH)) == (S_IWOTH | S_IXOTH) && (mode & S_ISVTX) == 0
- other_allow |= FILE_DELETE_CHILD
+ other_allow |= FILE::FILE_DELETE_CHILD
end
end
@@ -354,8 +359,8 @@ module Puppet::Util::Windows::Security
# if any ACE allows write, then clear readonly bit, but do this before we overwrite
# the DACl and lose our ability to set the attribute
- if ((owner_allow | group_allow | other_allow ) & FILE_WRITE_DATA) == FILE_WRITE_DATA
- remove_attributes(path, FILE_ATTRIBUTE_READONLY)
+ if ((owner_allow | group_allow | other_allow ) & FILE::FILE_WRITE_DATA) == FILE::FILE_WRITE_DATA
+ FILE.remove_attributes(path, FILE::FILE_ATTRIBUTE_READONLY)
end
dacl = Puppet::Util::Windows::AccessControlList.new
@@ -370,14 +375,15 @@ module Puppet::Util::Windows::Security
dacl.allow(well_known_system_sid, system_allow)
# add inherit-only aces for child dirs and files that are created within the dir
+ inherit_only = Puppet::Util::Windows::AccessControlEntry::INHERIT_ONLY_ACE
if isdir
- inherit = INHERIT_ONLY_ACE | CONTAINER_INHERIT_ACE
+ inherit = inherit_only | Puppet::Util::Windows::AccessControlEntry::CONTAINER_INHERIT_ACE
dacl.allow(Win32::Security::SID::CreatorOwner, owner_allow, inherit)
dacl.allow(Win32::Security::SID::CreatorGroup, group_allow, inherit)
- inherit = INHERIT_ONLY_ACE | OBJECT_INHERIT_ACE
- dacl.allow(Win32::Security::SID::CreatorOwner, owner_allow & ~FILE_EXECUTE, inherit)
- dacl.allow(Win32::Security::SID::CreatorGroup, group_allow & ~FILE_EXECUTE, inherit)
+ inherit = inherit_only | Puppet::Util::Windows::AccessControlEntry::OBJECT_INHERIT_ACE
+ dacl.allow(Win32::Security::SID::CreatorOwner, owner_allow & ~FILE::FILE_EXECUTE, inherit)
+ dacl.allow(Win32::Security::SID::CreatorGroup, group_allow & ~FILE::FILE_EXECUTE, inherit)
end
new_sd = Puppet::Util::Windows::SecurityDescriptor.new(sd.owner, sd.group, dacl, protected)
@@ -386,45 +392,50 @@ module Puppet::Util::Windows::Security
nil
end
+ ACL_REVISION = 2
+
def add_access_allowed_ace(acl, mask, sid, inherit = nil)
inherit ||= NO_INHERITANCE
- string_to_sid_ptr(sid) do |sid_ptr|
- raise Puppet::Util::Windows::Error.new("Invalid SID") unless IsValidSid(sid_ptr)
+ Puppet::Util::Windows::SID.string_to_sid_ptr(sid) do |sid_ptr|
+ if Puppet::Util::Windows::SID.IsValidSid(sid_ptr) == FFI::WIN32_FALSE
+ raise Puppet::Util::Windows::Error.new("Invalid SID")
+ end
- unless AddAccessAllowedAceEx(acl, ACL_REVISION, inherit, mask, sid_ptr)
+ if AddAccessAllowedAceEx(acl, ACL_REVISION, inherit, mask, sid_ptr) == FFI::WIN32_FALSE
raise Puppet::Util::Windows::Error.new("Failed to add access control entry")
end
end
+
+ # ensure this method is void if it doesn't raise
+ nil
end
def add_access_denied_ace(acl, mask, sid, inherit = nil)
inherit ||= NO_INHERITANCE
- string_to_sid_ptr(sid) do |sid_ptr|
- raise Puppet::Util::Windows::Error.new("Invalid SID") unless IsValidSid(sid_ptr)
+ Puppet::Util::Windows::SID.string_to_sid_ptr(sid) do |sid_ptr|
+ if Puppet::Util::Windows::SID.IsValidSid(sid_ptr) == FFI::WIN32_FALSE
+ raise Puppet::Util::Windows::Error.new("Invalid SID")
+ end
- unless AddAccessDeniedAceEx(acl, ACL_REVISION, inherit, mask, sid_ptr)
+ if AddAccessDeniedAceEx(acl, ACL_REVISION, inherit, mask, sid_ptr) == FFI::WIN32_FALSE
raise Puppet::Util::Windows::Error.new("Failed to add access control entry")
end
end
+
+ # ensure this method is void if it doesn't raise
+ nil
end
def parse_dacl(dacl_ptr)
# REMIND: need to handle NULL DACL
- raise Puppet::Util::Windows::Error.new("Invalid DACL") unless IsValidAcl(dacl_ptr)
-
- # ACL structure, size and count are the important parts. The
- # size includes both the ACL structure and all the ACEs.
- #
- # BYTE AclRevision
- # BYTE Padding1
- # WORD AclSize
- # WORD AceCount
- # WORD Padding2
- acl_buf = 0.chr * 8
- memcpy(acl_buf, dacl_ptr, acl_buf.size)
- ace_count = acl_buf.unpack('CCSSS')[3]
+ if IsValidAcl(dacl_ptr) == FFI::WIN32_FALSE
+ raise Puppet::Util::Windows::Error.new("Invalid DACL")
+ end
+
+ dacl_struct = ACL.new(dacl_ptr)
+ ace_count = dacl_struct[:AceCount]
dacl = Puppet::Util::Windows::AccessControlList.new
@@ -432,42 +443,32 @@ module Puppet::Util::Windows::Security
return dacl if ace_count == 0
0.upto(ace_count - 1) do |i|
- ace_ptr = [0].pack('L')
-
- next unless GetAce(dacl_ptr, i, ace_ptr)
-
- # ACE structures vary depending on the type. All structures
- # begin with an ACE header, which specifies the type, flags
- # and size of what follows. We are only concerned with
- # ACCESS_ALLOWED_ACE and ACCESS_DENIED_ACEs, which have the
- # same structure:
- #
- # BYTE C AceType
- # BYTE C AceFlags
- # WORD S AceSize
- # DWORD L ACCESS_MASK
- # DWORD L Sid
- # .. ...
- # DWORD L Sid
-
- ace_buf = 0.chr * 8
- memcpy(ace_buf, ace_ptr.unpack('L')[0], ace_buf.size)
-
- ace_type, ace_flags, size, mask = ace_buf.unpack('CCSL')
-
- case ace_type
- when ACCESS_ALLOWED_ACE_TYPE
- sid_ptr = ace_ptr.unpack('L')[0] + 8 # address of ace_ptr->SidStart
- raise Puppet::Util::Windows::Error.new("Failed to read DACL, invalid SID") unless IsValidSid(sid_ptr)
- sid = sid_ptr_to_string(sid_ptr)
- dacl.allow(sid, mask, ace_flags)
- when ACCESS_DENIED_ACE_TYPE
- sid_ptr = ace_ptr.unpack('L')[0] + 8 # address of ace_ptr->SidStart
- raise Puppet::Util::Windows::Error.new("Failed to read DACL, invalid SID") unless IsValidSid(sid_ptr)
- sid = sid_ptr_to_string(sid_ptr)
- dacl.deny(sid, mask, ace_flags)
- else
- Puppet.warning "Unsupported access control entry type: 0x#{ace_type.to_s(16)}"
+ FFI::MemoryPointer.new(:pointer, 1) do |ace_ptr|
+
+ next if GetAce(dacl_ptr, i, ace_ptr) == FFI::WIN32_FALSE
+
+ # ACE structures vary depending on the type. We are only concerned with
+ # ACCESS_ALLOWED_ACE and ACCESS_DENIED_ACEs, which have the same layout
+ ace = GENERIC_ACCESS_ACE.new(ace_ptr.get_pointer(0)) #deref LPVOID *
+
+ ace_type = ace[:Header][:AceType]
+ if ace_type != Puppet::Util::Windows::AccessControlEntry::ACCESS_ALLOWED_ACE_TYPE &&
+ ace_type != Puppet::Util::Windows::AccessControlEntry::ACCESS_DENIED_ACE_TYPE
+ Puppet.warning "Unsupported access control entry type: 0x#{ace_type.to_s(16)}"
+ next
+ end
+
+ # using pointer addition gives the FFI::Pointer a size, but that's OK here
+ sid = Puppet::Util::Windows::SID.sid_ptr_to_string(ace.pointer + GENERIC_ACCESS_ACE.offset_of(:SidStart))
+ mask = ace[:Mask]
+ ace_flags = ace[:Header][:AceFlags]
+
+ case ace_type
+ when Puppet::Util::Windows::AccessControlEntry::ACCESS_ALLOWED_ACE_TYPE
+ dacl.allow(sid, mask, ace_flags)
+ when Puppet::Util::Windows::AccessControlEntry::ACCESS_DENIED_ACE_TYPE
+ dacl.deny(sid, mask, ace_flags)
+ end
end
end
@@ -476,67 +477,82 @@ module Puppet::Util::Windows::Security
# Open an existing file with the specified access mode, and execute a
# block with the opened file HANDLE.
- def open_file(path, access)
- handle = CreateFile(
- path,
+ def open_file(path, access, &block)
+ handle = CreateFileW(
+ wide_string(path),
access,
- FILE_SHARE_READ | FILE_SHARE_WRITE,
- 0, # security_attributes
- OPEN_EXISTING,
- FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS,
- 0) # template
- raise Puppet::Util::Windows::Error.new("Failed to open '#{path}'") if handle == INVALID_HANDLE_VALUE
+ FILE::FILE_SHARE_READ | FILE::FILE_SHARE_WRITE,
+ FFI::Pointer::NULL, # security_attributes
+ FILE::OPEN_EXISTING,
+ FILE::FILE_FLAG_OPEN_REPARSE_POINT | FILE::FILE_FLAG_BACKUP_SEMANTICS,
+ FFI::Pointer::NULL_HANDLE) # template
+
+ if handle == Puppet::Util::Windows::File::INVALID_HANDLE_VALUE
+ raise Puppet::Util::Windows::Error.new("Failed to open '#{path}'")
+ end
+
begin
yield handle
ensure
- CloseHandle(handle)
+ FFI::WIN32.CloseHandle(handle) if handle
end
+
+ # handle has already had CloseHandle called against it, nothing to return
+ nil
end
# Execute a block with the specified privilege enabled
- def with_privilege(privilege)
+ def with_privilege(privilege, &block)
set_privilege(privilege, true)
yield
ensure
set_privilege(privilege, false)
end
+ SE_PRIVILEGE_ENABLED = 0x00000002
+ TOKEN_ADJUST_PRIVILEGES = 0x0020
+
# Enable or disable a privilege. Note this doesn't add any privileges the
# user doesn't already has, it just enables privileges that are disabled.
def set_privilege(privilege, enable)
return unless Puppet.features.root?
- with_process_token(TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY) do |token|
- tmpLuid = 0.chr * 8
-
- # Get the LUID for specified privilege.
- unless LookupPrivilegeValue("", privilege, tmpLuid)
- raise Puppet::Util::Windows::Error.new("Failed to lookup privilege")
- end
-
- # DWORD + [LUID + DWORD]
- tkp = [1].pack('L') + tmpLuid + [enable ? SE_PRIVILEGE_ENABLED : 0].pack('L')
-
- unless AdjustTokenPrivileges(token, 0, tkp, tkp.length , nil, nil)
- raise Puppet::Util::Windows::Error.new("Failed to adjust process privileges")
+ Puppet::Util::Windows::Process.with_process_token(TOKEN_ADJUST_PRIVILEGES) do |token|
+ Puppet::Util::Windows::Process.lookup_privilege_value(privilege) do |luid|
+ FFI::MemoryPointer.new(Puppet::Util::Windows::Process::LUID_AND_ATTRIBUTES.size) do |luid_and_attributes_ptr|
+ # allocate unmanaged memory for structs that we clean up afterwards
+ luid_and_attributes = Puppet::Util::Windows::Process::LUID_AND_ATTRIBUTES.new(luid_and_attributes_ptr)
+ luid_and_attributes[:Luid] = luid
+ luid_and_attributes[:Attributes] = enable ? SE_PRIVILEGE_ENABLED : 0
+
+ FFI::MemoryPointer.new(Puppet::Util::Windows::Process::TOKEN_PRIVILEGES.size) do |token_privileges_ptr|
+ token_privileges = Puppet::Util::Windows::Process::TOKEN_PRIVILEGES.new(token_privileges_ptr)
+ token_privileges[:PrivilegeCount] = 1
+ token_privileges[:Privileges][0] = luid_and_attributes
+
+ # size is correct given we only have 1 LUID, otherwise would be:
+ # [:PrivilegeCount].size + [:PrivilegeCount] * LUID_AND_ATTRIBUTES.size
+ if AdjustTokenPrivileges(token, FFI::WIN32_FALSE,
+ token_privileges, token_privileges.size,
+ FFI::MemoryPointer::NULL, FFI::MemoryPointer::NULL) == FFI::WIN32_FALSE
+ raise Puppet::Util::Windows::Error.new("Failed to adjust process privileges")
+ end
+ end
+ end
end
end
- end
-
- # Execute a block with the current process token
- def with_process_token(access)
- token = 0.chr * 4
- unless OpenProcessToken(GetCurrentProcess(), access, token)
- raise Puppet::Util::Windows::Error.new("Failed to open process token")
- end
- begin
- token = token.unpack('L')[0]
+ # token / luid structs freed by this point, so return true as nothing raised
+ true
+ end
+ def with_process_token(access, &block)
+ Puppet.deprecation_warning('Puppet::Util::Windows::Security.with_process_token is deprecated; please use Puppet::Util::Windows::Process.with_process_token')
+ Puppet::Util::Windows::Process.with_process_token(access) do |token|
yield token
- ensure
- CloseHandle(token)
end
+
+ nil
end
def get_security_descriptor(path)
@@ -544,40 +560,43 @@ module Puppet::Util::Windows::Security
with_privilege(SE_BACKUP_NAME) do
open_file(path, READ_CONTROL) do |handle|
- owner_sid = [0].pack('L')
- group_sid = [0].pack('L')
- dacl = [0].pack('L')
- ppsd = [0].pack('L')
-
- rv = GetSecurityInfo(
- handle,
- SE_FILE_OBJECT,
- OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION,
- owner_sid,
- group_sid,
- dacl,
- nil, #sacl
- ppsd) #sec desc
- raise Puppet::Util::Windows::Error.new("Failed to get security information") unless rv == ERROR_SUCCESS
-
- begin
- owner = sid_ptr_to_string(owner_sid.unpack('L')[0])
- group = sid_ptr_to_string(group_sid.unpack('L')[0])
-
- control = FFI::MemoryPointer.new(:uint16, 1)
- revision = FFI::MemoryPointer.new(:uint32, 1)
- ffsd = FFI::Pointer.new(ppsd.unpack('L')[0])
-
- if ! API.get_security_descriptor_control(ffsd, control, revision)
- raise Puppet::Util::Windows::Error.new("Failed to get security descriptor control")
+ FFI::MemoryPointer.new(:pointer, 1) do |owner_sid_ptr_ptr|
+ FFI::MemoryPointer.new(:pointer, 1) do |group_sid_ptr_ptr|
+ FFI::MemoryPointer.new(:pointer, 1) do |dacl_ptr_ptr|
+ FFI::MemoryPointer.new(:pointer, 1) do |sd_ptr_ptr|
+
+ rv = GetSecurityInfo(
+ handle,
+ :SE_FILE_OBJECT,
+ OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION,
+ owner_sid_ptr_ptr,
+ group_sid_ptr_ptr,
+ dacl_ptr_ptr,
+ FFI::Pointer::NULL, #sacl
+ sd_ptr_ptr) #sec desc
+ raise Puppet::Util::Windows::Error.new("Failed to get security information") if rv != FFI::ERROR_SUCCESS
+
+ # these 2 convenience params are not freed since they point inside sd_ptr
+ owner = Puppet::Util::Windows::SID.sid_ptr_to_string(owner_sid_ptr_ptr.get_pointer(0))
+ group = Puppet::Util::Windows::SID.sid_ptr_to_string(group_sid_ptr_ptr.get_pointer(0))
+
+ FFI::MemoryPointer.new(:word, 1) do |control|
+ FFI::MemoryPointer.new(:dword, 1) do |revision|
+ sd_ptr_ptr.read_win32_local_pointer do |sd_ptr|
+
+ if GetSecurityDescriptorControl(sd_ptr, control, revision) == FFI::WIN32_FALSE
+ raise Puppet::Util::Windows::Error.new("Failed to get security descriptor control")
+ end
+
+ protect = (control.read_word & SE_DACL_PROTECTED) == SE_DACL_PROTECTED
+ dacl = parse_dacl(dacl_ptr_ptr.get_pointer(0))
+ sd = Puppet::Util::Windows::SecurityDescriptor.new(owner, group, dacl, protect)
+ end
+ end
+ end
+ end
+ end
end
-
- protect = (control.read_uint16 & SE_DACL_PROTECTED) == SE_DACL_PROTECTED
-
- dacl = parse_dacl(dacl.unpack('L')[0])
- sd = Puppet::Util::Windows::SecurityDescriptor.new(owner, group, dacl, protect)
- ensure
- LocalFree(ppsd.unpack('L')[0])
end
end
end
@@ -585,67 +604,317 @@ module Puppet::Util::Windows::Security
sd
end
+ def get_max_generic_acl_size(ace_count)
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/aa378853(v=vs.85).aspx
+ # To calculate the initial size of an ACL, add the following together, and then align the result to the nearest DWORD:
+ # * Size of the ACL structure.
+ # * Size of each ACE structure that the ACL is to contain minus the SidStart member (DWORD) of the ACE.
+ # * Length of the SID that each ACE is to contain.
+ ACL.size + ace_count * MAXIMUM_GENERIC_ACE_SIZE
+ end
+
# setting DACL requires both READ_CONTROL and WRITE_DACL access rights,
# and their respective privileges, SE_BACKUP_NAME and SE_RESTORE_NAME.
def set_security_descriptor(path, sd)
- # REMIND: FFI
- acl = 0.chr * 1024 # This can be increased later as neede
- unless InitializeAcl(acl, acl.size, ACL_REVISION)
- raise Puppet::Util::Windows::Error.new("Failed to initialize ACL")
- end
+ FFI::MemoryPointer.new(:byte, get_max_generic_acl_size(sd.dacl.count)) do |acl_ptr|
+ if InitializeAcl(acl_ptr, acl_ptr.size, ACL_REVISION) == FFI::WIN32_FALSE
+ raise Puppet::Util::Windows::Error.new("Failed to initialize ACL")
+ end
- raise Puppet::Util::Windows::Error.new("Invalid DACL") unless IsValidAcl(acl)
+ if IsValidAcl(acl_ptr) == FFI::WIN32_FALSE
+ raise Puppet::Util::Windows::Error.new("Invalid DACL")
+ end
- with_privilege(SE_BACKUP_NAME) do
- with_privilege(SE_RESTORE_NAME) do
- open_file(path, READ_CONTROL | WRITE_DAC | WRITE_OWNER) do |handle|
- string_to_sid_ptr(sd.owner) do |ownersid|
- string_to_sid_ptr(sd.group) do |groupsid|
- sd.dacl.each do |ace|
- case ace.type
- when ACCESS_ALLOWED_ACE_TYPE
- #puts "ace: allow, sid #{sid_to_name(ace.sid)}, mask 0x#{ace.mask.to_s(16)}"
- add_access_allowed_ace(acl, ace.mask, ace.sid, ace.flags)
- when ACCESS_DENIED_ACE_TYPE
- #puts "ace: deny, sid #{sid_to_name(ace.sid)}, mask 0x#{ace.mask.to_s(16)}"
- add_access_denied_ace(acl, ace.mask, ace.sid, ace.flags)
- else
- raise "We should never get here"
- # TODO: this should have been a warning in an earlier commit
+ with_privilege(SE_BACKUP_NAME) do
+ with_privilege(SE_RESTORE_NAME) do
+ open_file(path, READ_CONTROL | WRITE_DAC | WRITE_OWNER) do |handle|
+ Puppet::Util::Windows::SID.string_to_sid_ptr(sd.owner) do |owner_sid_ptr|
+ Puppet::Util::Windows::SID.string_to_sid_ptr(sd.group) do |group_sid_ptr|
+ sd.dacl.each do |ace|
+ case ace.type
+ when Puppet::Util::Windows::AccessControlEntry::ACCESS_ALLOWED_ACE_TYPE
+ #puts "ace: allow, sid #{Puppet::Util::Windows::SID.sid_to_name(ace.sid)}, mask 0x#{ace.mask.to_s(16)}"
+ add_access_allowed_ace(acl_ptr, ace.mask, ace.sid, ace.flags)
+ when Puppet::Util::Windows::AccessControlEntry::ACCESS_DENIED_ACE_TYPE
+ #puts "ace: deny, sid #{Puppet::Util::Windows::SID.sid_to_name(ace.sid)}, mask 0x#{ace.mask.to_s(16)}"
+ add_access_denied_ace(acl_ptr, ace.mask, ace.sid, ace.flags)
+ else
+ raise "We should never get here"
+ # TODO: this should have been a warning in an earlier commit
+ end
end
- end
- # protected means the object does not inherit aces from its parent
- flags = OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION
- flags |= sd.protect ? PROTECTED_DACL_SECURITY_INFORMATION : UNPROTECTED_DACL_SECURITY_INFORMATION
-
- rv = SetSecurityInfo(handle,
- SE_FILE_OBJECT,
- flags,
- ownersid,
- groupsid,
- acl,
- nil)
- raise Puppet::Util::Windows::Error.new("Failed to set security information") unless rv == ERROR_SUCCESS
+ # protected means the object does not inherit aces from its parent
+ flags = OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION
+ flags |= sd.protect ? PROTECTED_DACL_SECURITY_INFORMATION : UNPROTECTED_DACL_SECURITY_INFORMATION
+
+ rv = SetSecurityInfo(handle,
+ :SE_FILE_OBJECT,
+ flags,
+ owner_sid_ptr,
+ group_sid_ptr,
+ acl_ptr,
+ FFI::MemoryPointer::NULL)
+
+ if rv != FFI::ERROR_SUCCESS
+ raise Puppet::Util::Windows::Error.new("Failed to set security information")
+ end
+ end
end
end
end
end
end
+
+ def name_to_sid(name)
+ Puppet.deprecation_warning('Puppet::Util::Windows::Security.name_to_sid is deprecated; please use Puppet::Util::Windows::SID.name_to_sid')
+ Puppet::Util::Windows::SID.name_to_sid(name)
+ end
+
+ def name_to_sid_object(name)
+ Puppet.deprecation_warning('Puppet::Util::Windows::Security.name_to_sid_object is deprecated; please use Puppet::Util::Windows::SID.name_to_sid_object')
+ Puppet::Util::Windows::SID.name_to_sid_object(name)
+ end
+
+ def octet_string_to_sid_object(bytes)
+ Puppet.deprecation_warning('Puppet::Util::Windows::Security.octet_string_to_sid_object is deprecated; please use Puppet::Util::Windows::SID.octet_string_to_sid_object')
+ Puppet::Util::Windows::SID.octet_string_to_sid_object(bytes)
+ end
+
+ def sid_to_name(value)
+ Puppet.deprecation_warning('Puppet::Util::Windows::Security.sid_to_name is deprecated; please use Puppet::Util::Windows::SID.sid_to_name')
+ Puppet::Util::Windows::SID.sid_to_name(value)
+ end
+
+ def sid_ptr_to_string(psid)
+ Puppet.deprecation_warning('Puppet::Util::Windows::Security.sid_ptr_to_string is deprecated; please use Puppet::Util::Windows::SID.sid_ptr_to_string')
+ Puppet::Util::Windows::SID.sid_ptr_to_string(psid)
+ end
+
+ def string_to_sid_ptr(string_sid, &block)
+ Puppet.deprecation_warning('Puppet::Util::Windows::Security.string_to_sid_ptr is deprecated; please use Puppet::Util::Windows::SID.string_to_sid_ptr')
+ Puppet::Util::Windows::SID.string_to_sid_ptr(string_sid, &block)
+ end
+
+ def valid_sid?(string_sid)
+ Puppet.deprecation_warning('Puppet::Util::Windows::Security.valid_sid? is deprecated; please use Puppet::Util::Windows::SID.valid_sid?')
+ Puppet::Util::Windows::SID.valid_sid?(string_sid)
+ end
end
- module API
- extend FFI::Library
- ffi_lib 'kernel32'
- ffi_convention :stdcall
-
- # typedef WORD SECURITY_DESCRIPTOR_CONTROL, *PSECURITY_DESCRIPTOR_CONTROL;
- # BOOL WINAPI GetSecurityDescriptorControl(
- # _In_ PSECURITY_DESCRIPTOR pSecurityDescriptor,
- # _Out_ PSECURITY_DESCRIPTOR_CONTROL pControl,
- # _Out_ LPDWORD lpdwRevision
- # );
- ffi_lib :advapi32
- attach_function :get_security_descriptor_control, :GetSecurityDescriptorControl, [:pointer, :pointer, :pointer], :bool
+ ffi_convention :stdcall
+
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/aa363858(v=vs.85).aspx
+ # HANDLE WINAPI CreateFile(
+ # _In_ LPCTSTR lpFileName,
+ # _In_ DWORD dwDesiredAccess,
+ # _In_ DWORD dwShareMode,
+ # _In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes,
+ # _In_ DWORD dwCreationDisposition,
+ # _In_ DWORD dwFlagsAndAttributes,
+ # _In_opt_ HANDLE hTemplateFile
+ # );
+ ffi_lib :kernel32
+ attach_function_private :CreateFileW,
+ [:lpcwstr, :dword, :dword, :pointer, :dword, :dword, :handle], :handle
+
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/aa364993(v=vs.85).aspx
+ # BOOL WINAPI GetVolumeInformation(
+ # _In_opt_ LPCTSTR lpRootPathName,
+ # _Out_opt_ LPTSTR lpVolumeNameBuffer,
+ # _In_ DWORD nVolumeNameSize,
+ # _Out_opt_ LPDWORD lpVolumeSerialNumber,
+ # _Out_opt_ LPDWORD lpMaximumComponentLength,
+ # _Out_opt_ LPDWORD lpFileSystemFlags,
+ # _Out_opt_ LPTSTR lpFileSystemNameBuffer,
+ # _In_ DWORD nFileSystemNameSize
+ # );
+ ffi_lib :kernel32
+ attach_function_private :GetVolumeInformationW,
+ [:lpcwstr, :lpwstr, :dword, :lpdword, :lpdword, :lpdword, :lpwstr, :dword], :win32_bool
+
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/aa374951(v=vs.85).aspx
+ # BOOL WINAPI AddAccessAllowedAceEx(
+ # _Inout_ PACL pAcl,
+ # _In_ DWORD dwAceRevision,
+ # _In_ DWORD AceFlags,
+ # _In_ DWORD AccessMask,
+ # _In_ PSID pSid
+ # );
+ ffi_lib :advapi32
+ attach_function_private :AddAccessAllowedAceEx,
+ [:pointer, :dword, :dword, :dword, :pointer], :win32_bool
+
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/aa374964(v=vs.85).aspx
+ # BOOL WINAPI AddAccessDeniedAceEx(
+ # _Inout_ PACL pAcl,
+ # _In_ DWORD dwAceRevision,
+ # _In_ DWORD AceFlags,
+ # _In_ DWORD AccessMask,
+ # _In_ PSID pSid
+ # );
+ ffi_lib :advapi32
+ attach_function_private :AddAccessDeniedAceEx,
+ [:pointer, :dword, :dword, :dword, :pointer], :win32_bool
+
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/aa374931(v=vs.85).aspx
+ # typedef struct _ACL {
+ # BYTE AclRevision;
+ # BYTE Sbz1;
+ # WORD AclSize;
+ # WORD AceCount;
+ # WORD Sbz2;
+ # } ACL, *PACL;
+ class ACL < FFI::Struct
+ layout :AclRevision, :byte,
+ :Sbz1, :byte,
+ :AclSize, :word,
+ :AceCount, :word,
+ :Sbz2, :word
end
+
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/aa374912(v=vs.85).aspx
+ # ACE types
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/aa374919(v=vs.85).aspx
+ # typedef struct _ACE_HEADER {
+ # BYTE AceType;
+ # BYTE AceFlags;
+ # WORD AceSize;
+ # } ACE_HEADER, *PACE_HEADER;
+ class ACE_HEADER < FFI::Struct
+ layout :AceType, :byte,
+ :AceFlags, :byte,
+ :AceSize, :word
+ end
+
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/aa374892(v=vs.85).aspx
+ # ACCESS_MASK
+
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/aa374847(v=vs.85).aspx
+ # typedef struct _ACCESS_ALLOWED_ACE {
+ # ACE_HEADER Header;
+ # ACCESS_MASK Mask;
+ # DWORD SidStart;
+ # } ACCESS_ALLOWED_ACE, *PACCESS_ALLOWED_ACE;
+ #
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/aa374879(v=vs.85).aspx
+ # typedef struct _ACCESS_DENIED_ACE {
+ # ACE_HEADER Header;
+ # ACCESS_MASK Mask;
+ # DWORD SidStart;
+ # } ACCESS_DENIED_ACE, *PACCESS_DENIED_ACE;
+ class GENERIC_ACCESS_ACE < FFI::Struct
+ # ACE structures must be aligned on DWORD boundaries. All Windows
+ # memory-management functions return DWORD-aligned handles to memory
+ pack 4
+ layout :Header, ACE_HEADER,
+ :Mask, :dword,
+ :SidStart, :dword
+ end
+
+ # http://stackoverflow.com/a/1792930
+ MAXIMUM_SID_BYTES_LENGTH = 68
+ MAXIMUM_GENERIC_ACE_SIZE = GENERIC_ACCESS_ACE.offset_of(:SidStart) +
+ MAXIMUM_SID_BYTES_LENGTH
+
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/aa446634(v=vs.85).aspx
+ # BOOL WINAPI GetAce(
+ # _In_ PACL pAcl,
+ # _In_ DWORD dwAceIndex,
+ # _Out_ LPVOID *pAce
+ # );
+ ffi_lib :advapi32
+ attach_function_private :GetAce,
+ [:pointer, :dword, :pointer], :win32_bool
+
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/aa375202(v=vs.85).aspx
+ # BOOL WINAPI AdjustTokenPrivileges(
+ # _In_ HANDLE TokenHandle,
+ # _In_ BOOL DisableAllPrivileges,
+ # _In_opt_ PTOKEN_PRIVILEGES NewState,
+ # _In_ DWORD BufferLength,
+ # _Out_opt_ PTOKEN_PRIVILEGES PreviousState,
+ # _Out_opt_ PDWORD ReturnLength
+ # );
+ ffi_lib :advapi32
+ attach_function_private :AdjustTokenPrivileges,
+ [:handle, :win32_bool, :pointer, :dword, :pointer, :pdword], :win32_bool
+
+ # http://msdn.microsoft.com/en-us/library/windows/hardware/ff556610(v=vs.85).aspx
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/aa379561(v=vs.85).aspx
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/aa446647(v=vs.85).aspx
+ # typedef WORD SECURITY_DESCRIPTOR_CONTROL, *PSECURITY_DESCRIPTOR_CONTROL;
+ # BOOL WINAPI GetSecurityDescriptorControl(
+ # _In_ PSECURITY_DESCRIPTOR pSecurityDescriptor,
+ # _Out_ PSECURITY_DESCRIPTOR_CONTROL pControl,
+ # _Out_ LPDWORD lpdwRevision
+ # );
+ ffi_lib :advapi32
+ attach_function_private :GetSecurityDescriptorControl,
+ [:pointer, :lpword, :lpdword], :win32_bool
+
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/aa378853(v=vs.85).aspx
+ # BOOL WINAPI InitializeAcl(
+ # _Out_ PACL pAcl,
+ # _In_ DWORD nAclLength,
+ # _In_ DWORD dwAclRevision
+ # );
+ ffi_lib :advapi32
+ attach_function_private :InitializeAcl,
+ [:pointer, :dword, :dword], :win32_bool
+
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/aa379142(v=vs.85).aspx
+ # BOOL WINAPI IsValidAcl(
+ # _In_ PACL pAcl
+ # );
+ ffi_lib :advapi32
+ attach_function_private :IsValidAcl,
+ [:pointer], :win32_bool
+
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/aa379593(v=vs.85).aspx
+ SE_OBJECT_TYPE = enum(
+ :SE_UNKNOWN_OBJECT_TYPE, 0,
+ :SE_FILE_OBJECT,
+ :SE_SERVICE,
+ :SE_PRINTER,
+ :SE_REGISTRY_KEY,
+ :SE_LMSHARE,
+ :SE_KERNEL_OBJECT,
+ :SE_WINDOW_OBJECT,
+ :SE_DS_OBJECT,
+ :SE_DS_OBJECT_ALL,
+ :SE_PROVIDER_DEFINED_OBJECT,
+ :SE_WMIGUID_OBJECT,
+ :SE_REGISTRY_WOW64_32KEY
+ )
+
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/aa446654(v=vs.85).aspx
+ # DWORD WINAPI GetSecurityInfo(
+ # _In_ HANDLE handle,
+ # _In_ SE_OBJECT_TYPE ObjectType,
+ # _In_ SECURITY_INFORMATION SecurityInfo,
+ # _Out_opt_ PSID *ppsidOwner,
+ # _Out_opt_ PSID *ppsidGroup,
+ # _Out_opt_ PACL *ppDacl,
+ # _Out_opt_ PACL *ppSacl,
+ # _Out_opt_ PSECURITY_DESCRIPTOR *ppSecurityDescriptor
+ # );
+ ffi_lib :advapi32
+ attach_function_private :GetSecurityInfo,
+ [:handle, SE_OBJECT_TYPE, :dword, :pointer, :pointer, :pointer, :pointer, :pointer], :dword
+
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/aa379588(v=vs.85).aspx
+ # DWORD WINAPI SetSecurityInfo(
+ # _In_ HANDLE handle,
+ # _In_ SE_OBJECT_TYPE ObjectType,
+ # _In_ SECURITY_INFORMATION SecurityInfo,
+ # _In_opt_ PSID psidOwner,
+ # _In_opt_ PSID psidGroup,
+ # _In_opt_ PACL pDacl,
+ # _In_opt_ PACL pSacl
+ # );
+ ffi_lib :advapi32
+ # TODO: SECURITY_INFORMATION is actually a bitmask the size of a DWORD
+ attach_function_private :SetSecurityInfo,
+ [:handle, SE_OBJECT_TYPE, :dword, :pointer, :pointer, :pointer, :pointer], :dword
end
diff --git a/lib/puppet/util/windows/sid.rb b/lib/puppet/util/windows/sid.rb
index 90b48d933..68a780f8a 100644
--- a/lib/puppet/util/windows/sid.rb
+++ b/lib/puppet/util/windows/sid.rb
@@ -2,14 +2,8 @@ require 'puppet/util/windows'
module Puppet::Util::Windows
module SID
- require 'windows/security'
- include ::Windows::Security
-
- require 'windows/memory'
- include ::Windows::Memory
-
- require 'windows/msvcrt/string'
- include ::Windows::MSVCRT::String
+ require 'ffi'
+ extend FFI::Library
# missing from Windows::Error
ERROR_NONE_MAPPED = 1332
@@ -24,6 +18,7 @@ module Puppet::Util::Windows
sid ? sid.to_s : nil
end
+ module_function :name_to_sid
# Convert an account name, e.g. 'Administrators' into a SID object,
# e.g. 'S-1-5-32-544'. The name can be specified as 'Administrators',
@@ -40,6 +35,7 @@ module Puppet::Util::Windows
rescue
nil
end
+ module_function :name_to_sid_object
# Converts an octet string array of bytes to a SID object,
# e.g. [1, 1, 0, 0, 0, 0, 0, 5, 18, 0, 0, 0] is the representation for
@@ -52,6 +48,7 @@ module Puppet::Util::Windows
Win32::Security::SID.new(bytes.pack('C*'))
end
+ module_function :octet_string_to_sid_object
# Convert a SID string, e.g. "S-1-5-32-544" to a name,
# e.g. 'BUILTIN\Administrators'. Returns nil if an account
@@ -67,52 +64,99 @@ module Puppet::Util::Windows
rescue
nil
end
+ module_function :sid_to_name
+
+ # http://stackoverflow.com/a/1792930 - 68 bytes, 184 characters in a string
+ MAXIMUM_SID_STRING_LENGTH = 184
# Convert a SID pointer to a SID string, e.g. "S-1-5-32-544".
def sid_ptr_to_string(psid)
- sid_buf = 0.chr * 256
- str_ptr = 0.chr * 4
+ if ! psid.instance_of?(FFI::Pointer) || IsValidSid(psid) == FFI::WIN32_FALSE
+ raise Puppet::Util::Windows::Error.new("Invalid SID")
+ end
- raise Puppet::Util::Windows::Error.new("Invalid SID") unless IsValidSid(psid)
+ sid_string = nil
+ FFI::MemoryPointer.new(:pointer, 1) do |buffer_ptr|
+ if ConvertSidToStringSidW(psid, buffer_ptr) == FFI::WIN32_FALSE
+ raise Puppet::Util::Windows::Error.new("Failed to convert binary SID")
+ end
- raise Puppet::Util::Windows::Error.new("Failed to convert binary SID") unless ConvertSidToStringSid(psid, str_ptr)
+ buffer_ptr.read_win32_local_pointer do |wide_string_ptr|
+ if wide_string_ptr.null?
+ raise Puppet::Error.new("ConvertSidToStringSidW failed to allocate buffer for sid")
+ end
- begin
- strncpy(sid_buf, str_ptr.unpack('L')[0], sid_buf.size - 1)
- sid_buf[sid_buf.size - 1] = 0.chr
- return sid_buf.strip
- ensure
- LocalFree(str_ptr.unpack('L')[0])
+ sid_string = wide_string_ptr.read_arbitrary_wide_string_up_to(MAXIMUM_SID_STRING_LENGTH)
+ end
end
+
+ sid_string
end
+ module_function :sid_ptr_to_string
# Convert a SID string, e.g. "S-1-5-32-544" to a pointer (containing the
# address of the binary SID structure). The returned value can be used in
# Win32 APIs that expect a PSID, e.g. IsValidSid. The account for this
# SID may or may not exist.
- def string_to_sid_ptr(string, &block)
- sid_buf = 0.chr * 80
- string_addr = [string].pack('p*').unpack('L')[0]
-
- raise Puppet::Util::Windows::Error.new("Failed to convert string SID: #{string}") unless ConvertStringSidToSid(string_addr, sid_buf)
-
- sid_ptr = sid_buf.unpack('L')[0]
- begin
- yield sid_ptr
- ensure
- LocalFree(sid_ptr)
+ def string_to_sid_ptr(string_sid, &block)
+ FFI::MemoryPointer.from_string_to_wide_string(string_sid) do |lpcwstr|
+ FFI::MemoryPointer.new(:pointer, 1) do |sid_ptr_ptr|
+
+ if ConvertStringSidToSidW(lpcwstr, sid_ptr_ptr) == FFI::WIN32_FALSE
+ raise Puppet::Util::Windows::Error.new("Failed to convert string SID: #{string_sid}")
+ end
+
+ sid_ptr_ptr.read_win32_local_pointer do |sid_ptr|
+ yield sid_ptr
+ end
+ end
end
+
+ # yielded sid_ptr has already had LocalFree called, nothing to return
+ nil
end
+ module_function :string_to_sid_ptr
# Return true if the string is a valid SID, e.g. "S-1-5-32-544", false otherwise.
- def valid_sid?(string)
- string_to_sid_ptr(string) { |ptr| true }
- rescue Puppet::Util::Windows::Error => e
- if e.code == ERROR_INVALID_SID_STRUCTURE
- false
- else
- raise
+ def valid_sid?(string_sid)
+ valid = false
+
+ begin
+ string_to_sid_ptr(string_sid) { |ptr| valid = ! ptr.nil? && ! ptr.null? }
+ rescue Puppet::Util::Windows::Error => e
+ raise if e.code != ERROR_INVALID_SID_STRUCTURE
end
+
+ valid
end
+ module_function :valid_sid?
+
+ ffi_convention :stdcall
+
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/aa379151(v=vs.85).aspx
+ # BOOL WINAPI IsValidSid(
+ # _In_ PSID pSid
+ # );
+ ffi_lib :advapi32
+ attach_function_private :IsValidSid,
+ [:pointer], :win32_bool
+
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/aa376399(v=vs.85).aspx
+ # BOOL ConvertSidToStringSid(
+ # _In_ PSID Sid,
+ # _Out_ LPTSTR *StringSid
+ # );
+ ffi_lib :advapi32
+ attach_function_private :ConvertSidToStringSidW,
+ [:pointer, :pointer], :win32_bool
+
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/aa376402(v=vs.85).aspx
+ # BOOL WINAPI ConvertStringSidToSid(
+ # _In_ LPCTSTR StringSid,
+ # _Out_ PSID *Sid
+ # );
+ ffi_lib :advapi32
+ attach_function_private :ConvertStringSidToSidW,
+ [:lpcwstr, :pointer], :win32_bool
end
end
diff --git a/lib/puppet/util/windows/string.rb b/lib/puppet/util/windows/string.rb
index 13d9839d1..147c92915 100644
--- a/lib/puppet/util/windows/string.rb
+++ b/lib/puppet/util/windows/string.rb
@@ -2,6 +2,8 @@ require 'puppet/util/windows'
module Puppet::Util::Windows::String
def wide_string(str)
+ # if given a nil string, assume caller wants to pass a nil pointer to win32
+ return nil if str.nil?
# ruby (< 2.1) does not respect multibyte terminators, so it is possible
# for a string to contain a single trailing null byte, followed by garbage
# causing buffer overruns.
diff --git a/lib/puppet/util/windows/taskscheduler.rb b/lib/puppet/util/windows/taskscheduler.rb
new file mode 100644
index 000000000..ef598ab29
--- /dev/null
+++ b/lib/puppet/util/windows/taskscheduler.rb
@@ -0,0 +1,1241 @@
+require 'puppet/util/windows'
+
+# The Win32 module serves as a namespace only
+module Win32
+ # The TaskScheduler class encapsulates taskscheduler settings and behavior
+ class TaskScheduler
+ include Puppet::Util::Windows::String
+
+ require 'ffi'
+ extend FFI::Library
+
+ # The error class raised if any task scheduler specific calls fail.
+ class Error < Puppet::Util::Windows::Error; end
+
+ private
+
+ class << self
+ attr_accessor :com_initialized
+ end
+
+ # :stopdoc:
+ TASK_TIME_TRIGGER_ONCE = :TASK_TIME_TRIGGER_ONCE
+ TASK_TIME_TRIGGER_DAILY = :TASK_TIME_TRIGGER_DAILY
+ TASK_TIME_TRIGGER_WEEKLY = :TASK_TIME_TRIGGER_WEEKLY
+ TASK_TIME_TRIGGER_MONTHLYDATE = :TASK_TIME_TRIGGER_MONTHLYDATE
+ TASK_TIME_TRIGGER_MONTHLYDOW = :TASK_TIME_TRIGGER_MONTHLYDOW
+ TASK_EVENT_TRIGGER_ON_IDLE = :TASK_EVENT_TRIGGER_ON_IDLE
+ TASK_EVENT_TRIGGER_AT_SYSTEMSTART = :TASK_EVENT_TRIGGER_AT_SYSTEMSTART
+ TASK_EVENT_TRIGGER_AT_LOGON = :TASK_EVENT_TRIGGER_AT_LOGON
+
+ TASK_SUNDAY = 0x1
+ TASK_MONDAY = 0x2
+ TASK_TUESDAY = 0x4
+ TASK_WEDNESDAY = 0x8
+ TASK_THURSDAY = 0x10
+ TASK_FRIDAY = 0x20
+ TASK_SATURDAY = 0x40
+ TASK_FIRST_WEEK = 1
+ TASK_SECOND_WEEK = 2
+ TASK_THIRD_WEEK = 3
+ TASK_FOURTH_WEEK = 4
+ TASK_LAST_WEEK = 5
+ TASK_JANUARY = 0x1
+ TASK_FEBRUARY = 0x2
+ TASK_MARCH = 0x4
+ TASK_APRIL = 0x8
+ TASK_MAY = 0x10
+ TASK_JUNE = 0x20
+ TASK_JULY = 0x40
+ TASK_AUGUST = 0x80
+ TASK_SEPTEMBER = 0x100
+ TASK_OCTOBER = 0x200
+ TASK_NOVEMBER = 0x400
+ TASK_DECEMBER = 0x800
+
+ TASK_FLAG_INTERACTIVE = 0x1
+ TASK_FLAG_DELETE_WHEN_DONE = 0x2
+ TASK_FLAG_DISABLED = 0x4
+ TASK_FLAG_START_ONLY_IF_IDLE = 0x10
+ TASK_FLAG_KILL_ON_IDLE_END = 0x20
+ TASK_FLAG_DONT_START_IF_ON_BATTERIES = 0x40
+ TASK_FLAG_KILL_IF_GOING_ON_BATTERIES = 0x80
+ TASK_FLAG_RUN_ONLY_IF_DOCKED = 0x100
+ TASK_FLAG_HIDDEN = 0x200
+ TASK_FLAG_RUN_IF_CONNECTED_TO_INTERNET = 0x400
+ TASK_FLAG_RESTART_ON_IDLE_RESUME = 0x800
+ TASK_FLAG_SYSTEM_REQUIRED = 0x1000
+ TASK_FLAG_RUN_ONLY_IF_LOGGED_ON = 0x2000
+ TASK_TRIGGER_FLAG_HAS_END_DATE = 0x1
+ TASK_TRIGGER_FLAG_KILL_AT_DURATION_END = 0x2
+ TASK_TRIGGER_FLAG_DISABLED = 0x4
+
+ TASK_MAX_RUN_TIMES = 1440
+ TASKS_TO_RETRIEVE = 5
+
+ # COM
+
+ CLSID_CTask = FFI::WIN32::GUID['148BD520-A2AB-11CE-B11F-00AA00530503']
+ IID_ITask = FFI::WIN32::GUID['148BD524-A2AB-11CE-B11F-00AA00530503']
+ IID_IPersistFile = FFI::WIN32::GUID['0000010b-0000-0000-C000-000000000046']
+
+ SCHED_S_TASK_READY = 0x00041300
+ SCHED_S_TASK_RUNNING = 0x00041301
+ SCHED_S_TASK_HAS_NOT_RUN = 0x00041303
+ SCHED_S_TASK_NOT_SCHEDULED = 0x00041305
+ # HRESULT error codes
+ # http://blogs.msdn.com/b/eldar/archive/2007/04/03/a-lot-of-hresult-codes.aspx
+ # in Ruby, an 0x8XXXXXXX style HRESULT can be resolved to 2s complement
+ # by using "0x8XXXXXXX".to_i(16) - - 0x100000000
+ SCHED_E_ACCOUNT_INFORMATION_NOT_SET = -2147216625 # 0x8004130F
+ SCHED_E_NO_SECURITY_SERVICES = -2147216622 # 0x80041312
+ # No mapping between account names and security IDs was done.
+ ERROR_NONE_MAPPED = -2147023564 # 0x80070534 WIN32 Error CODE 1332 (0x534)
+
+ public
+
+ # :startdoc:
+
+ # Shorthand constants
+ IDLE = Puppet::Util::Windows::Process::IDLE_PRIORITY_CLASS
+ NORMAL = Puppet::Util::Windows::Process::NORMAL_PRIORITY_CLASS
+ HIGH = Puppet::Util::Windows::Process::HIGH_PRIORITY_CLASS
+ REALTIME = Puppet::Util::Windows::Process::REALTIME_PRIORITY_CLASS
+ BELOW_NORMAL = Puppet::Util::Windows::Process::BELOW_NORMAL_PRIORITY_CLASS
+ ABOVE_NORMAL = Puppet::Util::Windows::Process::ABOVE_NORMAL_PRIORITY_CLASS
+
+ ONCE = TASK_TIME_TRIGGER_ONCE
+ DAILY = TASK_TIME_TRIGGER_DAILY
+ WEEKLY = TASK_TIME_TRIGGER_WEEKLY
+ MONTHLYDATE = TASK_TIME_TRIGGER_MONTHLYDATE
+ MONTHLYDOW = TASK_TIME_TRIGGER_MONTHLYDOW
+
+ ON_IDLE = TASK_EVENT_TRIGGER_ON_IDLE
+ AT_SYSTEMSTART = TASK_EVENT_TRIGGER_AT_SYSTEMSTART
+ AT_LOGON = TASK_EVENT_TRIGGER_AT_LOGON
+ FIRST_WEEK = TASK_FIRST_WEEK
+ SECOND_WEEK = TASK_SECOND_WEEK
+ THIRD_WEEK = TASK_THIRD_WEEK
+ FOURTH_WEEK = TASK_FOURTH_WEEK
+ LAST_WEEK = TASK_LAST_WEEK
+ SUNDAY = TASK_SUNDAY
+ MONDAY = TASK_MONDAY
+ TUESDAY = TASK_TUESDAY
+ WEDNESDAY = TASK_WEDNESDAY
+ THURSDAY = TASK_THURSDAY
+ FRIDAY = TASK_FRIDAY
+ SATURDAY = TASK_SATURDAY
+ JANUARY = TASK_JANUARY
+ FEBRUARY = TASK_FEBRUARY
+ MARCH = TASK_MARCH
+ APRIL = TASK_APRIL
+ MAY = TASK_MAY
+ JUNE = TASK_JUNE
+ JULY = TASK_JULY
+ AUGUST = TASK_AUGUST
+ SEPTEMBER = TASK_SEPTEMBER
+ OCTOBER = TASK_OCTOBER
+ NOVEMBER = TASK_NOVEMBER
+ DECEMBER = TASK_DECEMBER
+
+ INTERACTIVE = TASK_FLAG_INTERACTIVE
+ DELETE_WHEN_DONE = TASK_FLAG_DELETE_WHEN_DONE
+ DISABLED = TASK_FLAG_DISABLED
+ START_ONLY_IF_IDLE = TASK_FLAG_START_ONLY_IF_IDLE
+ KILL_ON_IDLE_END = TASK_FLAG_KILL_ON_IDLE_END
+ DONT_START_IF_ON_BATTERIES = TASK_FLAG_DONT_START_IF_ON_BATTERIES
+ KILL_IF_GOING_ON_BATTERIES = TASK_FLAG_KILL_IF_GOING_ON_BATTERIES
+ RUN_ONLY_IF_DOCKED = TASK_FLAG_RUN_ONLY_IF_DOCKED
+ HIDDEN = TASK_FLAG_HIDDEN
+ RUN_IF_CONNECTED_TO_INTERNET = TASK_FLAG_RUN_IF_CONNECTED_TO_INTERNET
+ RESTART_ON_IDLE_RESUME = TASK_FLAG_RESTART_ON_IDLE_RESUME
+ SYSTEM_REQUIRED = TASK_FLAG_SYSTEM_REQUIRED
+ RUN_ONLY_IF_LOGGED_ON = TASK_FLAG_RUN_ONLY_IF_LOGGED_ON
+
+ FLAG_HAS_END_DATE = TASK_TRIGGER_FLAG_HAS_END_DATE
+ FLAG_KILL_AT_DURATION_END = TASK_TRIGGER_FLAG_KILL_AT_DURATION_END
+ FLAG_DISABLED = TASK_TRIGGER_FLAG_DISABLED
+
+ MAX_RUN_TIMES = TASK_MAX_RUN_TIMES
+
+ # Returns a new TaskScheduler object. If a work_item (and possibly the
+ # the trigger) are passed as arguments then a new work item is created and
+ # associated with that trigger, although you can still activate other tasks
+ # with the same handle.
+ #
+ # This is really just a bit of convenience. Passing arguments to the
+ # constructor is the same as calling TaskScheduler.new plus
+ # TaskScheduler#new_work_item.
+ #
+ def initialize(work_item=nil, trigger=nil)
+ @pITS = nil
+ @pITask = nil
+
+ if ! self.class.com_initialized
+ Puppet::Util::Windows::COM.InitializeCom()
+ self.class.com_initialized = true
+ end
+
+ @pITS = COM::TaskScheduler.new
+ at_exit do
+ begin
+ @pITS.Release if @pITS && !@pITS.null?
+ @pITS = nil
+ rescue
+ end
+ end
+
+ if work_item
+ if trigger
+ raise TypeError unless trigger.is_a?(Hash)
+ new_work_item(work_item, trigger)
+ end
+ end
+ end
+
+ # Returns an array of scheduled task names.
+ #
+ def enum
+ raise Error.new('No current task scheduler. ITaskScheduler is NULL.') if @pITS.nil?
+ array = []
+
+ @pITS.UseInstance(COM::EnumWorkItems, :Enum) do |pIEnum|
+ FFI::MemoryPointer.new(:pointer) do |names_array_ptr_ptr|
+ FFI::MemoryPointer.new(:win32_ulong) do |fetched_count_ptr|
+ # awkward usage, if number requested is available, returns S_OK (0), or if less were returned returns S_FALSE (1)
+ while (pIEnum.Next(TASKS_TO_RETRIEVE, names_array_ptr_ptr, fetched_count_ptr) >= Puppet::Util::Windows::COM::S_OK)
+ count = fetched_count_ptr.read_win32_ulong
+ break if count == 0
+
+ names_array_ptr_ptr.read_com_memory_pointer do |names_array_ptr|
+ # iterate over the array of pointers
+ name_ptr_ptr = FFI::Pointer.new(:pointer, names_array_ptr)
+ for i in 0 ... count
+ name_ptr_ptr[i].read_com_memory_pointer do |name_ptr|
+ array << name_ptr.read_arbitrary_wide_string_up_to(256)
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+
+ array
+ end
+
+ alias :tasks :enum
+
+ # Activate the specified task.
+ #
+ def activate(task)
+ raise Error.new('No current task scheduler. ITaskScheduler is NULL.') if @pITS.nil?
+ raise TypeError unless task.is_a?(String)
+
+ FFI::MemoryPointer.new(:pointer) do |ptr|
+ @pITS.Activate(wide_string(task), IID_ITask, ptr)
+
+ reset_current_task
+ @pITask = COM::Task.new(ptr.read_pointer)
+ end
+
+ @pITask
+ end
+
+ # Delete the specified task name.
+ #
+ def delete(task)
+ raise Error.new('No current task scheduler. ITaskScheduler is NULL.') if @pITS.nil?
+ raise TypeError unless task.is_a?(String)
+
+ @pITS.Delete(wide_string(task))
+
+ true
+ end
+
+ # Execute the current task.
+ #
+ def run
+ raise Error.new('No currently active task. ITask is NULL.') if @pITask.nil?
+
+ @pITask.Run
+ end
+
+ # Saves the current task. Tasks must be saved before they can be activated.
+ # The .job file itself is typically stored in the C:\WINDOWS\Tasks folder.
+ #
+ # If +file+ (an absolute path) is specified then the job is saved to that
+ # file instead. A '.job' extension is recommended but not enforced.
+ #
+ def save(file = nil)
+ raise Error.new('No currently active task. ITask is NULL.') if @pITask.nil?
+
+ reset = true
+
+ begin
+ @pITask.QueryInstance(COM::PersistFile) do |pIPersistFile|
+ pIPersistFile.Save(wide_string(file), 1)
+ end
+ rescue
+ reset = false
+ ensure
+ reset_current_task if reset
+ end
+ end
+
+ # Terminate the current task.
+ #
+ def terminate
+ raise Error.new('No currently active task. ITask is NULL.') if @pITask.nil?
+
+ @pITask.Terminate
+ end
+
+ # Set the host on which the various TaskScheduler methods will execute.
+ #
+ def machine=(host)
+ raise Error.new('No current task scheduler. ITaskScheduler is NULL.') if @pITS.nil?
+ raise TypeError unless host.is_a?(String)
+
+ @pITS.SetTargetComputer(wide_string(host))
+
+ host
+ end
+
+ alias :host= :machine=
+
+ # Sets the +user+ and +password+ for the given task. If the user and
+ # password are set properly then true is returned.
+ #
+ # In some cases the job may be created, but the account information was
+ # bad. In this case the task is created but a warning is generated and
+ # false is returned.
+ #
+ def set_account_information(user, password)
+ raise Error.new('No current task scheduler. ITaskScheduler is NULL.') if @pITS.nil?
+ raise Error.new('No currently active task. ITask is NULL.') if @pITask.nil?
+
+ bool = false
+
+ begin
+ if (user.nil? || user=="") && (password.nil? || password=="")
+ @pITask.SetAccountInformation(wide_string(""), FFI::Pointer::NULL)
+ else
+ user = wide_string(user)
+ password = wide_string(password)
+ @pITask.SetAccountInformation(user, password)
+ end
+
+ bool = true
+ rescue Puppet::Util::Windows::Error => e
+ raise e unless e.code == SCHED_E_ACCOUNT_INFORMATION_NOT_SET
+
+ warn 'job created, but password was invalid'
+ end
+
+ bool
+ end
+
+ # Returns the user associated with the task or nil if no user has yet
+ # been associated with the task.
+ #
+ def account_information
+ raise Error.new('No current task scheduler. ITaskScheduler is NULL.') if @pITS.nil?
+ raise Error.new('No currently active task. ITask is NULL.') if @pITask.nil?
+
+ # default under certain failures
+ user = nil
+
+ begin
+ FFI::MemoryPointer.new(:pointer) do |ptr|
+ @pITask.GetAccountInformation(ptr)
+ ptr.read_com_memory_pointer do |str_ptr|
+ user = str_ptr.read_arbitrary_wide_string_up_to(256) if ! str_ptr.null?
+ end
+ end
+ rescue Puppet::Util::Windows::Error => e
+ raise e unless e.code == SCHED_E_ACCOUNT_INFORMATION_NOT_SET ||
+ e.code == SCHED_E_NO_SECURITY_SERVICES ||
+ e.code == ERROR_NONE_MAPPED
+ end
+
+ user
+ end
+
+ # Returns the name of the application associated with the task.
+ #
+ def application_name
+ raise Error.new('No current task scheduler. ITaskScheduler is NULL.') if @pITS.nil?
+ raise Error.new('No currently active task. ITask is NULL.') if @pITask.nil?
+
+ app = nil
+
+ FFI::MemoryPointer.new(:pointer) do |ptr|
+ @pITask.GetApplicationName(ptr)
+
+ ptr.read_com_memory_pointer do |str_ptr|
+ app = str_ptr.read_arbitrary_wide_string_up_to(256) if ! str_ptr.null?
+ end
+ end
+
+ app
+ end
+
+ # Sets the application name associated with the task.
+ #
+ def application_name=(app)
+ raise Error.new('No current task scheduler. ITaskScheduler is NULL.') if @pITS.nil?
+ raise Error.new('No currently active task. ITask is NULL.') if @pITask.nil?
+ raise TypeError unless app.is_a?(String)
+
+ @pITask.SetApplicationName(wide_string(app))
+
+ app
+ end
+
+ # Returns the command line parameters for the task.
+ #
+ def parameters
+ raise Error.new('No current task scheduler. ITaskScheduler is NULL.') if @pITS.nil?
+ raise Error.new('No currently active task. ITask is NULL.') if @pITask.nil?
+
+ param = nil
+
+ FFI::MemoryPointer.new(:pointer) do |ptr|
+ @pITask.GetParameters(ptr)
+
+ ptr.read_com_memory_pointer do |str_ptr|
+ param = str_ptr.read_arbitrary_wide_string_up_to(256) if ! str_ptr.null?
+ end
+ end
+
+ param
+ end
+
+ # Sets the parameters for the task. These parameters are passed as command
+ # line arguments to the application the task will run. To clear the command
+ # line parameters set it to an empty string.
+ #
+ def parameters=(param)
+ raise Error.new('No current task scheduler. ITaskScheduler is NULL.') if @pITS.nil?
+ raise Error.new('No currently active task. ITask is NULL.') if @pITask.nil?
+ raise TypeError unless param.is_a?(String)
+
+ @pITask.SetParameters(wide_string(param))
+
+ param
+ end
+
+ # Returns the working directory for the task.
+ #
+ def working_directory
+ raise Error.new('No current task scheduler. ITaskScheduler is NULL.') if @pITS.nil?
+ raise Error.new('No currently active task. ITask is NULL.') if @pITask.nil?
+
+ dir = nil
+
+ FFI::MemoryPointer.new(:pointer) do |ptr|
+ @pITask.GetWorkingDirectory(ptr)
+
+ ptr.read_com_memory_pointer do |str_ptr|
+ dir = str_ptr.read_arbitrary_wide_string_up_to(256) if ! str_ptr.null?
+ end
+ end
+
+ dir
+ end
+
+ # Sets the working directory for the task.
+ #
+ def working_directory=(dir)
+ raise Error.new('No current task scheduler. ITaskScheduler is NULL.') if @pITS.nil?
+ raise Error.new('No currently active task. ITask is NULL.') if @pITask.nil?
+ raise TypeError unless dir.is_a?(String)
+
+ @pITask.SetWorkingDirectory(wide_string(dir))
+
+ dir
+ end
+
+ # Returns the task's priority level. Possible values are 'idle',
+ # 'normal', 'high', 'realtime', 'below_normal', 'above_normal',
+ # and 'unknown'.
+ #
+ def priority
+ raise Error.new('No current task scheduler. ITaskScheduler is NULL.') if @pITS.nil?
+ raise Error.new('No currently active task. ITask is NULL.') if @pITask.nil?
+
+ FFI::MemoryPointer.new(:dword, 1) do |ptr|
+ @pITask.GetPriority(ptr)
+
+ pri = ptr.read_dword
+ if (pri & IDLE) != 0
+ priority = 'idle'
+ elsif (pri & NORMAL) != 0
+ priority = 'normal'
+ elsif (pri & HIGH) != 0
+ priority = 'high'
+ elsif (pri & REALTIME) != 0
+ priority = 'realtime'
+ elsif (pri & BELOW_NORMAL) != 0
+ priority = 'below_normal'
+ elsif (pri & ABOVE_NORMAL) != 0
+ priority = 'above_normal'
+ else
+ priority = 'unknown'
+ end
+ end
+
+ priority
+ end
+
+ # Sets the priority of the task. The +priority+ should be a numeric
+ # priority constant value.
+ #
+ def priority=(priority)
+ raise Error.new('No current task scheduler. ITaskScheduler is NULL.') if @pITS.nil?
+ raise Error.new('No currently active task. ITask is NULL.') if @pITask.nil?
+ raise TypeError unless priority.is_a?(Numeric)
+
+ @pITask.SetPriority(priority)
+
+ priority
+ end
+
+ # Creates a new work item (scheduled job) with the given +trigger+. The
+ # trigger variable is a hash of options that define when the scheduled
+ # job should run.
+ #
+ def new_work_item(task, trigger)
+ raise TypeError unless trigger.is_a?(Hash)
+ raise Error.new('No current task scheduler. ITaskScheduler is NULL.') if @pITS.nil?
+
+ # I'm working around github issue #1 here.
+ enum.each{ |name|
+ if name.downcase == task.downcase + '.job'
+ raise Error.new("task '#{task}' already exists")
+ end
+ }
+
+ FFI::MemoryPointer.new(:pointer) do |ptr|
+ @pITS.NewWorkItem(wide_string(task), CLSID_CTask, IID_ITask, ptr)
+
+ reset_current_task
+ @pITask = COM::Task.new(ptr.read_pointer)
+
+ FFI::MemoryPointer.new(:word, 1) do |trigger_index_ptr|
+ # Without the 'enum.include?' check above the code segfaults here if the
+ # task already exists. This should probably be handled properly instead
+ # of simply avoiding the issue.
+
+ @pITask.UseInstance(COM::TaskTrigger, :CreateTrigger, trigger_index_ptr) do |pITaskTrigger|
+ populate_trigger(pITaskTrigger, trigger)
+ end
+ end
+ end
+
+ @pITask
+ end
+
+ alias :new_task :new_work_item
+
+ # Returns the number of triggers associated with the active task.
+ #
+ def trigger_count
+ raise Error.new('No current task scheduler. ITaskScheduler is NULL.') if @pITS.nil?
+ raise Error.new('No currently active task. ITask is NULL.') if @pITask.nil?
+
+ count = 0
+
+ FFI::MemoryPointer.new(:word, 1) do |ptr|
+ @pITask.GetTriggerCount(ptr)
+ count = ptr.read_word
+ end
+
+ count
+ end
+
+ # Returns a string that describes the current trigger at the specified
+ # index for the active task.
+ #
+ # Example: "At 7:14 AM every day, starting 4/11/2009"
+ #
+ def trigger_string(index)
+ raise Error.new('No current task scheduler. ITaskScheduler is NULL.') if @pITS.nil?
+ raise Error.new('No currently active task. ITask is NULL.') if @pITask.nil?
+ raise TypeError unless index.is_a?(Numeric)
+
+ FFI::MemoryPointer.new(:pointer) do |ptr|
+ @pITask.GetTriggerString(index, ptr)
+
+ ptr.read_com_memory_pointer do |str_ptr|
+ trigger = str_ptr.read_arbitrary_wide_string_up_to(256)
+ end
+ end
+
+ trigger
+ end
+
+ # Deletes the trigger at the specified index.
+ #
+ def delete_trigger(index)
+ raise Error.new('No current task scheduler. ITaskScheduler is NULL.') if @pITS.nil?
+ raise Error.new('No currently active task. ITask is NULL.') if @pITask.nil?
+
+ @pITask.DeleteTrigger(index)
+ index
+ end
+
+ # Returns a hash that describes the trigger at the given index for the
+ # current task.
+ #
+ def trigger(index)
+ raise Error.new('No current task scheduler. ITaskScheduler is NULL.') if @pITS.nil?
+ raise Error.new('No currently active task. ITask is NULL.') if @pITask.nil?
+
+ trigger = {}
+
+ @pITask.UseInstance(COM::TaskTrigger, :GetTrigger, index) do |pITaskTrigger|
+ FFI::MemoryPointer.new(COM::TASK_TRIGGER.size) do |task_trigger_ptr|
+ pITaskTrigger.GetTrigger(task_trigger_ptr)
+ trigger = populate_hash_from_trigger(COM::TASK_TRIGGER.new(task_trigger_ptr))
+ end
+ end
+
+ trigger
+ end
+
+ # Sets the trigger for the currently active task.
+ #
+ def trigger=(trigger)
+ raise Error.new('No current task scheduler. ITaskScheduler is NULL.') if @pITS.nil?
+ raise Error.new('No currently active task. ITask is NULL.') if @pITask.nil?
+ raise TypeError unless trigger.is_a?(Hash)
+
+ FFI::MemoryPointer.new(:word, 1) do |trigger_index_ptr|
+ # Without the 'enum.include?' check above the code segfaults here if the
+ # task already exists. This should probably be handled properly instead
+ # of simply avoiding the issue.
+
+ @pITask.UseInstance(COM::TaskTrigger, :CreateTrigger, trigger_index_ptr) do |pITaskTrigger|
+ populate_trigger(pITaskTrigger, trigger)
+ end
+ end
+
+ trigger
+ end
+
+ # Adds a trigger at the specified index.
+ #
+ def add_trigger(index, trigger)
+ raise Error.new('No current task scheduler. ITaskScheduler is NULL.') if @pITS.nil?
+ raise Error.new('No currently active task. ITask is NULL.') if @pITask.nil?
+ raise TypeError unless trigger.is_a?(Hash)
+
+ @pITask.UseInstance(COM::TaskTrigger, :GetTrigger, index) do |pITaskTrigger|
+ populate_trigger(pITaskTrigger, trigger)
+ end
+ end
+
+ # Returns the flags (integer) that modify the behavior of the work item. You
+ # must OR the return value to determine the flags yourself.
+ #
+ def flags
+ raise Error.new('No currently active task. ITask is NULL.') if @pITask.nil?
+
+ flags = 0
+
+ FFI::MemoryPointer.new(:dword, 1) do |ptr|
+ @pITask.GetFlags(ptr)
+ flags = ptr.read_dword
+ end
+
+ flags
+ end
+
+ # Sets an OR'd value of flags that modify the behavior of the work item.
+ #
+ def flags=(flags)
+ raise Error.new('No current task scheduler. ITaskScheduler is NULL.') if @pITS.nil?
+ raise Error.new('No currently active task. ITask is NULL.') if @pITask.nil?
+
+ @pITask.SetFlags(flags)
+ flags
+ end
+
+ # Returns the status of the currently active task. Possible values are
+ # 'ready', 'running', 'not scheduled' or 'unknown'.
+ #
+ def status
+ raise Error.new('No currently active task. ITask is NULL.') if @pITask.nil?
+
+ st = nil
+
+ FFI::MemoryPointer.new(:hresult, 1) do |ptr|
+ @pITask.GetStatus(ptr)
+ st = ptr.read_hresult
+ end
+
+ case st
+ when SCHED_S_TASK_READY
+ status = 'ready'
+ when SCHED_S_TASK_RUNNING
+ status = 'running'
+ when SCHED_S_TASK_NOT_SCHEDULED
+ status = 'not scheduled'
+ else
+ status = 'unknown'
+ end
+
+ status
+ end
+
+ # Returns the exit code from the last scheduled run.
+ #
+ def exit_code
+ raise Error.new('No currently active task. ITask is NULL.') if @pITask.nil?
+
+ status = 0
+
+ begin
+ FFI::MemoryPointer.new(:dword, 1) do |ptr|
+ @pITask.GetExitCode(ptr)
+ status = ptr.read_dword
+ end
+ rescue Puppet::Util::Windows::Error => e
+ raise e unless e.code == SCHED_S_TASK_HAS_NOT_RUN
+ end
+
+ status
+ end
+
+ # Returns the comment associated with the task, if any.
+ #
+ def comment
+ raise Error.new('No currently active task. ITask is NULL.') if @pITask.nil?
+
+ comment = nil
+
+ FFI::MemoryPointer.new(:pointer) do |ptr|
+ @pITask.GetComment(ptr)
+
+ ptr.read_com_memory_pointer do |str_ptr|
+ comment = str_ptr.read_arbitrary_wide_string_up_to(256) if ! str_ptr.null?
+ end
+ end
+
+ comment
+ end
+
+ # Sets the comment for the task.
+ #
+ def comment=(comment)
+ raise Error.new('No currently active task. ITask is NULL.') if @pITask.nil?
+ raise TypeError unless comment.is_a?(String)
+
+ @pITask.SetComment(wide_string(comment))
+ comment
+ end
+
+ # Returns the name of the user who created the task.
+ #
+ def creator
+ raise Error.new('No currently active task. ITask is NULL.') if @pITask.nil?
+
+ creator = nil
+
+ FFI::MemoryPointer.new(:pointer) do |ptr|
+ @pITask.GetCreator(ptr)
+
+ ptr.read_com_memory_pointer do |str_ptr|
+ creator = str_ptr.read_arbitrary_wide_string_up_to(256) if ! str_ptr.null?
+ end
+ end
+
+ creator
+ end
+
+ # Sets the creator for the task.
+ #
+ def creator=(creator)
+ raise Error.new('No currently active task. ITask is NULL.') if @pITask.nil?
+ raise TypeError unless creator.is_a?(String)
+
+ @pITask.SetCreator(wide_string(creator))
+ creator
+ end
+
+ # Returns a Time object that indicates the next time the task will run.
+ #
+ def next_run_time
+ raise Error.new('No currently active task. ITask is NULL.') if @pITask.nil?
+
+ time = nil
+
+ FFI::MemoryPointer.new(WIN32::SYSTEMTIME.size) do |ptr|
+ @pITask.GetNextRunTime(ptr)
+ time = WIN32::SYSTEMTIME.new(ptr).to_local_time
+ end
+
+ time
+ end
+
+ # Returns a Time object indicating the most recent time the task ran or
+ # nil if the task has never run.
+ #
+ def most_recent_run_time
+ raise Error.new('No currently active task. ITask is NULL.') if @pITask.nil?
+
+ time = nil
+
+ begin
+ FFI::MemoryPointer.new(WIN32::SYSTEMTIME.size) do |ptr|
+ @pITask.GetMostRecentRunTime(ptr)
+ time = WIN32::SYSTEMTIME.new(ptr).to_local_time
+ end
+ rescue Puppet::Util::Windows::Error => e
+ raise e unless e.code == SCHED_S_TASK_HAS_NOT_RUN
+ end
+
+ time
+ end
+
+ # Returns the maximum length of time, in milliseconds, that the task
+ # will run before terminating.
+ #
+ def max_run_time
+ raise Error.new('No currently active task. ITask is NULL.') if @pITask.nil?
+
+ max_run_time = nil
+
+ FFI::MemoryPointer.new(:dword, 1) do |ptr|
+ @pITask.GetMaxRunTime(ptr)
+ max_run_time = ptr.read_dword
+ end
+
+ max_run_time
+ end
+
+ # Sets the maximum length of time, in milliseconds, that the task can run
+ # before terminating. Returns the value you specified if successful.
+ #
+ def max_run_time=(max_run_time)
+ raise Error.new('No currently active task. ITask is NULL.') if @pITask.nil?
+ raise TypeError unless max_run_time.is_a?(Numeric)
+
+ @pITask.SetMaxRunTime(max_run_time)
+ max_run_time
+ end
+
+ # Returns whether or not the scheduled task exists.
+ def exists?(job_name)
+ bool = false
+ Dir.foreach('C:/Windows/Tasks'){ |file|
+ if File.basename(file, '.job') == job_name
+ bool = true
+ break
+ end
+ }
+ bool
+ end
+
+ private
+
+ # :stopdoc:
+
+ # Used for the new_work_item method
+ ValidTriggerKeys = [
+ 'end_day',
+ 'end_month',
+ 'end_year',
+ 'flags',
+ 'minutes_duration',
+ 'minutes_interval',
+ 'random_minutes_interval',
+ 'start_day',
+ 'start_hour',
+ 'start_minute',
+ 'start_month',
+ 'start_year',
+ 'trigger_type',
+ 'type'
+ ]
+
+ ValidTypeKeys = [
+ 'days_interval',
+ 'weeks_interval',
+ 'days_of_week',
+ 'months',
+ 'days',
+ 'weeks'
+ ]
+
+ # Private method that validates keys, and converts all keys to lowercase
+ # strings.
+ #
+ def transform_and_validate(hash)
+ new_hash = {}
+
+ hash.each{ |key, value|
+ key = key.to_s.downcase
+ if key == 'type'
+ new_type_hash = {}
+ raise ArgumentError unless value.is_a?(Hash)
+ value.each{ |subkey, subvalue|
+ subkey = subkey.to_s.downcase
+ if ValidTypeKeys.include?(subkey)
+ new_type_hash[subkey] = subvalue
+ else
+ raise ArgumentError, "Invalid type key '#{subkey}'"
+ end
+ }
+ new_hash[key] = new_type_hash
+ else
+ if ValidTriggerKeys.include?(key)
+ new_hash[key] = value
+ else
+ raise ArgumentError, "Invalid key '#{key}'"
+ end
+ end
+ }
+
+ new_hash
+ end
+
+ private
+
+ def reset_current_task
+ # Ensure that COM reference is decremented properly
+ @pITask.Release if @pITask && ! @pITask.null?
+ @pITask = nil
+ end
+
+ def populate_trigger(task_trigger, trigger)
+ raise TypeError unless task_trigger.is_a?(COM::TaskTrigger)
+ trigger = transform_and_validate(trigger)
+
+ FFI::MemoryPointer.new(COM::TASK_TRIGGER.size) do |trigger_ptr|
+ FFI::MemoryPointer.new(COM::TRIGGER_TYPE_UNION.size) do |trigger_type_union_ptr|
+ trigger_type_union = COM::TRIGGER_TYPE_UNION.new(trigger_type_union_ptr)
+
+ tmp = trigger['type'].is_a?(Hash) ? trigger['type'] : nil
+ case trigger['trigger_type']
+ when :TASK_TIME_TRIGGER_DAILY
+ if tmp && tmp['days_interval']
+ trigger_type_union[:Daily][:DaysInterval] = tmp['days_interval']
+ end
+ when :TASK_TIME_TRIGGER_WEEKLY
+ if tmp && tmp['weeks_interval'] && tmp['days_of_week']
+ trigger_type_union[:Weekly][:WeeksInterval] = tmp['weeks_interval']
+ trigger_type_union[:Weekly][:rgfDaysOfTheWeek] = tmp['days_of_week']
+ end
+ when :TASK_TIME_TRIGGER_MONTHLYDATE
+ if tmp && tmp['months'] && tmp['days']
+ trigger_type_union[:MonthlyDate][:rgfDays] = tmp['days']
+ trigger_type_union[:MonthlyDate][:rgfMonths] = tmp['months']
+ end
+ when :TASK_TIME_TRIGGER_MONTHLYDOW
+ if tmp && tmp['weeks'] && tmp['days_of_week'] && tmp['months']
+ trigger_type_union[:MonthlyDOW][:wWhichWeek] = tmp['weeks']
+ trigger_type_union[:MonthlyDOW][:rgfDaysOfTheWeek] = tmp['days_of_week']
+ trigger_type_union[:MonthlyDOW][:rgfMonths] = tmp['months']
+ end
+ when :TASK_TIME_TRIGGER_ONCE
+ # Do nothing. The Type member of the TASK_TRIGGER struct is ignored.
+ else
+ raise Error.new("Unknown trigger type #{trigger['trigger_type']}")
+ end
+
+ trigger_struct = COM::TASK_TRIGGER.new(trigger_ptr)
+ trigger_struct[:cbTriggerSize] = COM::TASK_TRIGGER.size
+ trigger_struct[:wBeginYear] = trigger['start_year'] || 0
+ trigger_struct[:wBeginMonth] = trigger['start_month'] || 0
+ trigger_struct[:wBeginDay] = trigger['start_day'] || 0
+ trigger_struct[:wEndYear] = trigger['end_year'] || 0
+ trigger_struct[:wEndMonth] = trigger['end_month'] || 0
+ trigger_struct[:wEndDay] = trigger['end_day'] || 0
+ trigger_struct[:wStartHour] = trigger['start_hour'] || 0
+ trigger_struct[:wStartMinute] = trigger['start_minute'] || 0
+ trigger_struct[:MinutesDuration] = trigger['minutes_duration'] || 0
+ trigger_struct[:MinutesInterval] = trigger['minutes_interval'] || 0
+ trigger_struct[:rgFlags] = trigger['flags'] || 0
+ trigger_struct[:TriggerType] = trigger['trigger_type'] || :TASK_TIME_TRIGGER_ONCE
+ trigger_struct[:Type] = trigger_type_union
+ trigger_struct[:wRandomMinutesInterval] = trigger['random_minutes_interval']
+
+ task_trigger.SetTrigger(trigger_struct)
+ end
+ end
+ end
+
+ def populate_hash_from_trigger(task_trigger)
+ raise TypeError unless task_trigger.is_a?(COM::TASK_TRIGGER)
+
+ trigger = {
+ 'start_year' => task_trigger[:wBeginYear],
+ 'start_month' => task_trigger[:wBeginMonth],
+ 'start_day' => task_trigger[:wBeginDay],
+ 'end_year' => task_trigger[:wEndYear],
+ 'end_month' => task_trigger[:wEndMonth],
+ 'end_day' => task_trigger[:wEndDay],
+ 'start_hour' => task_trigger[:wStartHour],
+ 'start_minute' => task_trigger[:wStartMinute],
+ 'minutes_duration' => task_trigger[:MinutesDuration],
+ 'minutes_interval' => task_trigger[:MinutesInterval],
+ 'flags' => task_trigger[:rgFlags],
+ 'trigger_type' => task_trigger[:TriggerType],
+ 'random_minutes_interval' => task_trigger[:wRandomMinutesInterval]
+ }
+
+ case task_trigger[:TriggerType]
+ when :TASK_TIME_TRIGGER_DAILY
+ trigger['type'] = { 'days_interval' => task_trigger[:Type][:Daily][:DaysInterval] }
+ when :TASK_TIME_TRIGGER_WEEKLY
+ trigger['type'] = {
+ 'weeks_interval' => task_trigger[:Type][:Weekly][:WeeksInterval],
+ 'days_of_week' => task_trigger[:Type][:Weekly][:rgfDaysOfTheWeek]
+ }
+ when :TASK_TIME_TRIGGER_MONTHLYDATE
+ trigger['type'] = {
+ 'days' => task_trigger[:Type][:MonthlyDate][:rgfDays],
+ 'months' => task_trigger[:Type][:MonthlyDate][:rgfMonths]
+ }
+ when :TASK_TIME_TRIGGER_MONTHLYDOW
+ trigger['type'] = {
+ 'weeks' => task_trigger[:Type][:MonthlyDOW][:wWhichWeek],
+ 'days_of_week' => task_trigger[:Type][:MonthlyDOW][:rgfDaysOfTheWeek],
+ 'months' => task_trigger[:Type][:MonthlyDOW][:rgfMonths]
+ }
+ when :TASK_TIME_TRIGGER_ONCE
+ trigger['type'] = { 'once' => nil }
+ else
+ raise Error.new("Unknown trigger type #{task_trigger[:TriggerType]}")
+ end
+
+ trigger
+ end
+
+ module COM
+ extend FFI::Library
+ private
+
+ com = Puppet::Util::Windows::COM
+
+ public
+
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/aa381811(v=vs.85).aspx
+ ITaskScheduler = com::Interface[com::IUnknown,
+ FFI::WIN32::GUID['148BD527-A2AB-11CE-B11F-00AA00530503'],
+
+ SetTargetComputer: [[:lpcwstr], :hresult],
+ # LPWSTR *
+ GetTargetComputer: [[:pointer], :hresult],
+ # IEnumWorkItems **
+ Enum: [[:pointer], :hresult],
+ # LPCWSTR, REFIID, IUnknown **
+ Activate: [[:lpcwstr, :pointer, :pointer], :hresult],
+ Delete: [[:lpcwstr], :hresult],
+ # LPCWSTR, REFCLSID, REFIID, IUnknown **
+ NewWorkItem: [[:lpcwstr, :pointer, :pointer, :pointer], :hresult],
+ # LPCWSTR, IScheduledWorkItem *
+ AddWorkItem: [[:lpcwstr, :pointer], :hresult],
+ # LPCWSTR, REFIID
+ IsOfType: [[:lpcwstr, :pointer], :hresult]
+ ]
+
+ TaskScheduler = com::Factory[ITaskScheduler,
+ FFI::WIN32::GUID['148BD52A-A2AB-11CE-B11F-00AA00530503']]
+
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/aa380706(v=vs.85).aspx
+ IEnumWorkItems = com::Interface[com::IUnknown,
+ FFI::WIN32::GUID['148BD528-A2AB-11CE-B11F-00AA00530503'],
+
+ # ULONG, LPWSTR **, ULONG *
+ Next: [[:win32_ulong, :pointer, :pointer], :hresult],
+ Skip: [[:win32_ulong], :hresult],
+ Reset: [[], :hresult],
+ # IEnumWorkItems ** ppEnumWorkItems
+ Clone: [[:pointer], :hresult]
+ ]
+
+ EnumWorkItems = com::Instance[IEnumWorkItems]
+
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/aa381216(v=vs.85).aspx
+ IScheduledWorkItem = com::Interface[com::IUnknown,
+ FFI::WIN32::GUID['a6b952f0-a4b1-11d0-997d-00aa006887ec'],
+
+ # WORD *, ITaskTrigger **
+ CreateTrigger: [[:pointer, :pointer], :hresult],
+ DeleteTrigger: [[:word], :hresult],
+ # WORD *
+ GetTriggerCount: [[:pointer], :hresult],
+ # WORD, ITaskTrigger **
+ GetTrigger: [[:word, :pointer], :hresult],
+ # WORD, LPWSTR *
+ GetTriggerString: [[:word, :pointer], :hresult],
+ # LPSYSTEMTIME, LPSYSTEMTIME, WORD *, LPSYSTEMTIME *
+ GetRunTimes: [[:pointer, :pointer, :pointer, :pointer], :hresult],
+ # SYSTEMTIME *
+ GetNextRunTime: [[:pointer], :hresult],
+ SetIdleWait: [[:word, :word], :hresult],
+ # WORD *, WORD *
+ GetIdleWait: [[:pointer, :pointer], :hresult],
+ Run: [[], :hresult],
+ Terminate: [[], :hresult],
+ EditWorkItem: [[:hwnd, :dword], :hresult],
+ # SYSTEMTIME *
+ GetMostRecentRunTime: [[:pointer], :hresult],
+ # HRESULT *
+ GetStatus: [[:pointer], :hresult],
+ GetExitCode: [[:pdword], :hresult],
+ SetComment: [[:lpcwstr], :hresult],
+ # LPWSTR *
+ GetComment: [[:pointer], :hresult],
+ SetCreator: [[:lpcwstr], :hresult],
+ # LPWSTR *
+ GetCreator: [[:pointer], :hresult],
+ # WORD, BYTE[]
+ SetWorkItemData: [[:word, :buffer_in], :hresult],
+ # WORD *, BYTE **
+ GetWorkItemData: [[:pointer, :pointer], :hresult],
+ SetErrorRetryCount: [[:word], :hresult],
+ # WORD *
+ GetErrorRetryCount: [[:pointer], :hresult],
+ SetErrorRetryInterval: [[:word], :hresult],
+ # WORD *
+ GetErrorRetryInterval: [[:pointer], :hresult],
+ SetFlags: [[:dword], :hresult],
+ # WORD *
+ GetFlags: [[:pointer], :hresult],
+ SetAccountInformation: [[:lpcwstr, :lpcwstr], :hresult],
+ # LPWSTR *
+ GetAccountInformation: [[:pointer], :hresult]
+ ]
+
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/aa381311(v=vs.85).aspx
+ ITask = com::Interface[IScheduledWorkItem,
+ FFI::WIN32::GUID['148BD524-A2AB-11CE-B11F-00AA00530503'],
+
+ SetApplicationName: [[:lpcwstr], :hresult],
+ # LPWSTR *
+ GetApplicationName: [[:pointer], :hresult],
+ SetParameters: [[:lpcwstr], :hresult],
+ # LPWSTR *
+ GetParameters: [[:pointer], :hresult],
+ SetWorkingDirectory: [[:lpcwstr], :hresult],
+ # LPWSTR *
+ GetWorkingDirectory: [[:pointer], :hresult],
+ SetPriority: [[:dword], :hresult],
+ # DWORD *
+ GetPriority: [[:pointer], :hresult],
+ SetTaskFlags: [[:dword], :hresult],
+ # DWORD *
+ GetTaskFlags: [[:pointer], :hresult],
+ SetMaxRunTime: [[:dword], :hresult],
+ # DWORD *
+ GetMaxRunTime: [[:pointer], :hresult]
+ ]
+
+ Task = com::Instance[ITask]
+
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/ms688695(v=vs.85).aspx
+ IPersist = com::Interface[com::IUnknown,
+ FFI::WIN32::GUID['0000010c-0000-0000-c000-000000000046'],
+ # CLSID *
+ GetClassID: [[:pointer], :hresult]
+ ]
+
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/ms687223(v=vs.85).aspx
+ IPersistFile = com::Interface[IPersist,
+ FFI::WIN32::GUID['0000010b-0000-0000-C000-000000000046'],
+
+ IsDirty: [[], :hresult],
+ Load: [[:lpcolestr, :dword], :hresult],
+ Save: [[:lpcolestr, :win32_bool], :hresult],
+ SaveCompleted: [[:lpcolestr], :hresult],
+ # LPOLESTR *
+ GetCurFile: [[:pointer], :hresult]
+ ]
+
+ PersistFile = com::Instance[IPersistFile]
+
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/aa381864(v=vs.85).aspx
+ ITaskTrigger = com::Interface[com::IUnknown,
+ FFI::WIN32::GUID['148BD52B-A2AB-11CE-B11F-00AA00530503'],
+
+ SetTrigger: [[:pointer], :hresult],
+ GetTrigger: [[:pointer], :hresult],
+ GetTriggerString: [[:pointer], :hresult]
+ ]
+
+ TaskTrigger = com::Instance[ITaskTrigger]
+
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/aa383620(v=vs.85).aspx
+ # The TASK_TRIGGER_TYPE field of the TASK_TRIGGER structure determines
+ # which member of the TRIGGER_TYPE_UNION field to use.
+ TASK_TRIGGER_TYPE = enum(
+ :TASK_TIME_TRIGGER_ONCE, 0, # Ignore the Type field
+ :TASK_TIME_TRIGGER_DAILY, 1,
+ :TASK_TIME_TRIGGER_WEEKLY, 2,
+ :TASK_TIME_TRIGGER_MONTHLYDATE, 3,
+ :TASK_TIME_TRIGGER_MONTHLYDOW, 4,
+ :TASK_EVENT_TRIGGER_ON_IDLE, 5, # Ignore the Type field
+ :TASK_EVENT_TRIGGER_AT_SYSTEMSTART, 6, # Ignore the Type field
+ :TASK_EVENT_TRIGGER_AT_LOGON, 7 # Ignore the Type field
+ )
+
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/aa446857(v=vs.85).aspx
+ class DAILY < FFI::Struct
+ layout :DaysInterval, :word
+ end
+
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/aa384014(v=vs.85).aspx
+ class WEEKLY < FFI::Struct
+ layout :WeeksInterval, :word,
+ :rgfDaysOfTheWeek, :word
+ end
+
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/aa381918(v=vs.85).aspx
+ class MONTHLYDATE < FFI::Struct
+ layout :rgfDays, :dword,
+ :rgfMonths, :word
+ end
+
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/aa381918(v=vs.85).aspx
+ class MONTHLYDOW < FFI::Struct
+ layout :wWhichWeek, :word,
+ :rgfDaysOfTheWeek, :word,
+ :rgfMonths, :word
+ end
+
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/aa384002(v=vs.85).aspx
+ class TRIGGER_TYPE_UNION < FFI::Union
+ layout :Daily, DAILY,
+ :Weekly, WEEKLY,
+ :MonthlyDate, MONTHLYDATE,
+ :MonthlyDOW, MONTHLYDOW
+ end
+
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/aa383618(v=vs.85).aspx
+ class TASK_TRIGGER < FFI::Struct
+ layout :cbTriggerSize, :word, # Structure size.
+ :Reserved1, :word, # Reserved. Must be zero.
+ :wBeginYear, :word, # Trigger beginning date year.
+ :wBeginMonth, :word, # Trigger beginning date month.
+ :wBeginDay, :word, # Trigger beginning date day.
+ :wEndYear, :word, # Optional trigger ending date year.
+ :wEndMonth, :word, # Optional trigger ending date month.
+ :wEndDay, :word, # Optional trigger ending date day.
+ :wStartHour, :word, # Run bracket start time hour.
+ :wStartMinute, :word, # Run bracket start time minute.
+ :MinutesDuration, :dword, # Duration of run bracket.
+ :MinutesInterval, :dword, # Run bracket repetition interval.
+ :rgFlags, :dword, # Trigger flags.
+ :TriggerType, TASK_TRIGGER_TYPE, # Trigger type.
+ :Type, TRIGGER_TYPE_UNION, # Trigger data.
+ :Reserved2, :word, # Reserved. Must be zero.
+ :wRandomMinutesInterval, :word # Maximum number of random minutes after start time
+ end
+ end
+ end
+end
diff --git a/lib/puppet/util/windows/user.rb b/lib/puppet/util/windows/user.rb
index 51eef779d..a2277f66c 100644
--- a/lib/puppet/util/windows/user.rb
+++ b/lib/puppet/util/windows/user.rb
@@ -1,49 +1,59 @@
require 'puppet/util/windows'
-require 'win32/security'
require 'facter'
+require 'ffi'
module Puppet::Util::Windows::User
- include ::Windows::Security
- extend ::Windows::Security
+ extend Puppet::Util::Windows::String
+ extend FFI::Library
def admin?
majversion = Facter.value(:kernelmajversion)
return false unless majversion
# if Vista or later, check for unrestricted process token
- return Win32::Security.elevated_security? unless majversion.to_f < 6.0
+ return Puppet::Util::Windows::Process.elevated_security? unless majversion.to_f < 6.0
# otherwise 2003 or less
check_token_membership
end
module_function :admin?
+
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/ee207397(v=vs.85).aspx
+ SECURITY_MAX_SID_SIZE = 68
+
def check_token_membership
- sid = 0.chr * 80
- size = [80].pack('L')
- member = 0.chr * 4
+ is_admin = false
+ FFI::MemoryPointer.new(:byte, SECURITY_MAX_SID_SIZE) do |sid_pointer|
+ FFI::MemoryPointer.new(:dword, 1) do |size_pointer|
+ size_pointer.write_uint32(SECURITY_MAX_SID_SIZE)
- unless CreateWellKnownSid(WinBuiltinAdministratorsSid, nil, sid, size)
- raise Puppet::Util::Windows::Error.new("Failed to create administrators SID")
- end
+ if CreateWellKnownSid(:WinBuiltinAdministratorsSid, FFI::Pointer::NULL, sid_pointer, size_pointer) == FFI::WIN32_FALSE
+ raise Puppet::Util::Windows::Error.new("Failed to create administrators SID")
+ end
+ end
- unless IsValidSid(sid)
- raise Puppet::Util::Windows::Error.new("Invalid SID")
- end
+ if IsValidSid(sid_pointer) == FFI::WIN32_FALSE
+ raise Puppet::Util::Windows::Error.new("Invalid SID")
+ end
+
+ FFI::MemoryPointer.new(:win32_bool, 1) do |ismember_pointer|
+ if CheckTokenMembership(FFI::Pointer::NULL_HANDLE, sid_pointer, ismember_pointer) == FFI::WIN32_FALSE
+ raise Puppet::Util::Windows::Error.new("Failed to check membership")
+ end
- unless CheckTokenMembership(nil, sid, member)
- raise Puppet::Util::Windows::Error.new("Failed to check membership")
+ # Is administrators SID enabled in calling thread's access token?
+ is_admin = ismember_pointer.read_win32_bool != FFI::WIN32_FALSE
+ end
end
- # Is administrators SID enabled in calling thread's access token?
- member.unpack('L')[0] == 1
+ is_admin
end
module_function :check_token_membership
def password_is?(name, password)
- logon_user(name, password)
- true
+ logon_user(name, password) { |token| }
rescue Puppet::Util::Windows::Error
false
end
@@ -53,56 +63,230 @@ module Puppet::Util::Windows::User
fLOGON32_LOGON_NETWORK = 3
fLOGON32_PROVIDER_DEFAULT = 0
- logon_user = Win32API.new("advapi32", "LogonUser", ['P', 'P', 'P', 'L', 'L', 'P'], 'L')
- close_handle = Win32API.new("kernel32", "CloseHandle", ['L'], 'B')
-
- token = 0.chr * 4
- if logon_user.call(name, ".", password, fLOGON32_LOGON_NETWORK, fLOGON32_PROVIDER_DEFAULT, token) == 0
- raise Puppet::Util::Windows::Error.new("Failed to logon user #{name.inspect}")
- end
-
- token = token.unpack('L')[0]
+ token = nil
begin
- yield token if block_given?
+ FFI::MemoryPointer.new(:handle, 1) do |token_pointer|
+ if LogonUserW(wide_string(name), wide_string('.'), wide_string(password),
+ fLOGON32_LOGON_NETWORK, fLOGON32_PROVIDER_DEFAULT, token_pointer) == FFI::WIN32_FALSE
+ raise Puppet::Util::Windows::Error.new("Failed to logon user #{name.inspect}")
+ end
+
+ yield token = token_pointer.read_handle
+ end
ensure
- close_handle.call(token)
+ FFI::WIN32.CloseHandle(token) if token
end
+
+ # token has been closed by this point
+ true
end
module_function :logon_user
def load_profile(user, password)
logon_user(user, password) do |token|
- # Set up the PROFILEINFO structure that will be used to load the
- # new user's profile
- # typedef struct _PROFILEINFO {
- # DWORD dwSize;
- # DWORD dwFlags;
- # LPTSTR lpUserName;
- # LPTSTR lpProfilePath;
- # LPTSTR lpDefaultPath;
- # LPTSTR lpServerName;
- # LPTSTR lpPolicyPath;
- # HANDLE hProfile;
- # } PROFILEINFO, *LPPROFILEINFO;
- fPI_NOUI = 1
- profile = 0.chr * 4
- pi = [4 * 8, fPI_NOUI, user, nil, nil, nil, nil, profile].pack('LLPPPPPP')
-
- load_user_profile = Win32API.new('userenv', 'LoadUserProfile', ['L', 'P'], 'L')
- unload_user_profile = Win32API.new('userenv', 'UnloadUserProfile', ['L', 'L'], 'L')
-
- # Load the profile. Since it doesn't exist, it will be created
- if load_user_profile.call(token, pi) == 0
- raise Puppet::Util::Windows::Error.new("Failed to load user profile #{user.inspect}")
- end
+ FFI::MemoryPointer.from_string_to_wide_string(user) do |lpUserName|
+ pi = PROFILEINFO.new
+ pi[:dwSize] = PROFILEINFO.size
+ pi[:dwFlags] = 1 # PI_NOUI - prevents display of profile error msgs
+ pi[:lpUserName] = lpUserName
+
+ # Load the profile. Since it doesn't exist, it will be created
+ if LoadUserProfileW(token, pi.pointer) == FFI::WIN32_FALSE
+ raise Puppet::Util::Windows::Error.new("Failed to load user profile #{user.inspect}")
+ end
- Puppet.debug("Loaded profile for #{user}")
+ Puppet.debug("Loaded profile for #{user}")
- profile = pi.unpack('LLLLLLLL').last
- if unload_user_profile.call(token, profile) == 0
- raise Puppet::Util::Windows::Error.new("Failed to unload user profile #{user.inspect}")
+ if UnloadUserProfile(token, pi[:hProfile]) == FFI::WIN32_FALSE
+ raise Puppet::Util::Windows::Error.new("Failed to unload user profile #{user.inspect}")
+ end
end
end
end
module_function :load_profile
+
+ ffi_convention :stdcall
+
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/aa378184(v=vs.85).aspx
+ # BOOL LogonUser(
+ # _In_ LPTSTR lpszUsername,
+ # _In_opt_ LPTSTR lpszDomain,
+ # _In_opt_ LPTSTR lpszPassword,
+ # _In_ DWORD dwLogonType,
+ # _In_ DWORD dwLogonProvider,
+ # _Out_ PHANDLE phToken
+ # );
+ ffi_lib :advapi32
+ attach_function_private :LogonUserW,
+ [:lpwstr, :lpwstr, :lpwstr, :dword, :dword, :phandle], :win32_bool
+
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/bb773378(v=vs.85).aspx
+ # typedef struct _PROFILEINFO {
+ # DWORD dwSize;
+ # DWORD dwFlags;
+ # LPTSTR lpUserName;
+ # LPTSTR lpProfilePath;
+ # LPTSTR lpDefaultPath;
+ # LPTSTR lpServerName;
+ # LPTSTR lpPolicyPath;
+ # HANDLE hProfile;
+ # } PROFILEINFO, *LPPROFILEINFO;
+ # technically
+ # NOTE: that for structs, buffer_* (lptstr alias) cannot be used
+ class PROFILEINFO < FFI::Struct
+ layout :dwSize, :dword,
+ :dwFlags, :dword,
+ :lpUserName, :pointer,
+ :lpProfilePath, :pointer,
+ :lpDefaultPath, :pointer,
+ :lpServerName, :pointer,
+ :lpPolicyPath, :pointer,
+ :hProfile, :handle
+ end
+
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/bb762281(v=vs.85).aspx
+ # BOOL WINAPI LoadUserProfile(
+ # _In_ HANDLE hToken,
+ # _Inout_ LPPROFILEINFO lpProfileInfo
+ # );
+ ffi_lib :userenv
+ attach_function_private :LoadUserProfileW,
+ [:handle, :pointer], :win32_bool
+
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/bb762282(v=vs.85).aspx
+ # BOOL WINAPI UnloadUserProfile(
+ # _In_ HANDLE hToken,
+ # _In_ HANDLE hProfile
+ # );
+ ffi_lib :userenv
+ attach_function_private :UnloadUserProfile,
+ [:handle, :handle], :win32_bool
+
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/aa376389(v=vs.85).aspx
+ # BOOL WINAPI CheckTokenMembership(
+ # _In_opt_ HANDLE TokenHandle,
+ # _In_ PSID SidToCheck,
+ # _Out_ PBOOL IsMember
+ # );
+ ffi_lib :advapi32
+ attach_function_private :CheckTokenMembership,
+ [:handle, :pointer, :pbool], :win32_bool
+
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/aa379650(v=vs.85).aspx
+ WELL_KNOWN_SID_TYPE = enum(
+ :WinNullSid , 0,
+ :WinWorldSid , 1,
+ :WinLocalSid , 2,
+ :WinCreatorOwnerSid , 3,
+ :WinCreatorGroupSid , 4,
+ :WinCreatorOwnerServerSid , 5,
+ :WinCreatorGroupServerSid , 6,
+ :WinNtAuthoritySid , 7,
+ :WinDialupSid , 8,
+ :WinNetworkSid , 9,
+ :WinBatchSid , 10,
+ :WinInteractiveSid , 11,
+ :WinServiceSid , 12,
+ :WinAnonymousSid , 13,
+ :WinProxySid , 14,
+ :WinEnterpriseControllersSid , 15,
+ :WinSelfSid , 16,
+ :WinAuthenticatedUserSid , 17,
+ :WinRestrictedCodeSid , 18,
+ :WinTerminalServerSid , 19,
+ :WinRemoteLogonIdSid , 20,
+ :WinLogonIdsSid , 21,
+ :WinLocalSystemSid , 22,
+ :WinLocalServiceSid , 23,
+ :WinNetworkServiceSid , 24,
+ :WinBuiltinDomainSid , 25,
+ :WinBuiltinAdministratorsSid , 26,
+ :WinBuiltinUsersSid , 27,
+ :WinBuiltinGuestsSid , 28,
+ :WinBuiltinPowerUsersSid , 29,
+ :WinBuiltinAccountOperatorsSid , 30,
+ :WinBuiltinSystemOperatorsSid , 31,
+ :WinBuiltinPrintOperatorsSid , 32,
+ :WinBuiltinBackupOperatorsSid , 33,
+ :WinBuiltinReplicatorSid , 34,
+ :WinBuiltinPreWindows2000CompatibleAccessSid , 35,
+ :WinBuiltinRemoteDesktopUsersSid , 36,
+ :WinBuiltinNetworkConfigurationOperatorsSid , 37,
+ :WinAccountAdministratorSid , 38,
+ :WinAccountGuestSid , 39,
+ :WinAccountKrbtgtSid , 40,
+ :WinAccountDomainAdminsSid , 41,
+ :WinAccountDomainUsersSid , 42,
+ :WinAccountDomainGuestsSid , 43,
+ :WinAccountComputersSid , 44,
+ :WinAccountControllersSid , 45,
+ :WinAccountCertAdminsSid , 46,
+ :WinAccountSchemaAdminsSid , 47,
+ :WinAccountEnterpriseAdminsSid , 48,
+ :WinAccountPolicyAdminsSid , 49,
+ :WinAccountRasAndIasServersSid , 50,
+ :WinNTLMAuthenticationSid , 51,
+ :WinDigestAuthenticationSid , 52,
+ :WinSChannelAuthenticationSid , 53,
+ :WinThisOrganizationSid , 54,
+ :WinOtherOrganizationSid , 55,
+ :WinBuiltinIncomingForestTrustBuildersSid , 56,
+ :WinBuiltinPerfMonitoringUsersSid , 57,
+ :WinBuiltinPerfLoggingUsersSid , 58,
+ :WinBuiltinAuthorizationAccessSid , 59,
+ :WinBuiltinTerminalServerLicenseServersSid , 60,
+ :WinBuiltinDCOMUsersSid , 61,
+ :WinBuiltinIUsersSid , 62,
+ :WinIUserSid , 63,
+ :WinBuiltinCryptoOperatorsSid , 64,
+ :WinUntrustedLabelSid , 65,
+ :WinLowLabelSid , 66,
+ :WinMediumLabelSid , 67,
+ :WinHighLabelSid , 68,
+ :WinSystemLabelSid , 69,
+ :WinWriteRestrictedCodeSid , 70,
+ :WinCreatorOwnerRightsSid , 71,
+ :WinCacheablePrincipalsGroupSid , 72,
+ :WinNonCacheablePrincipalsGroupSid , 73,
+ :WinEnterpriseReadonlyControllersSid , 74,
+ :WinAccountReadonlyControllersSid , 75,
+ :WinBuiltinEventLogReadersGroup , 76,
+ :WinNewEnterpriseReadonlyControllersSid , 77,
+ :WinBuiltinCertSvcDComAccessGroup , 78,
+ :WinMediumPlusLabelSid , 79,
+ :WinLocalLogonSid , 80,
+ :WinConsoleLogonSid , 81,
+ :WinThisOrganizationCertificateSid , 82,
+ :WinApplicationPackageAuthoritySid , 83,
+ :WinBuiltinAnyPackageSid , 84,
+ :WinCapabilityInternetClientSid , 85,
+ :WinCapabilityInternetClientServerSid , 86,
+ :WinCapabilityPrivateNetworkClientServerSid , 87,
+ :WinCapabilityPicturesLibrarySid , 88,
+ :WinCapabilityVideosLibrarySid , 89,
+ :WinCapabilityMusicLibrarySid , 90,
+ :WinCapabilityDocumentsLibrarySid , 91,
+ :WinCapabilitySharedUserCertificatesSid , 92,
+ :WinCapabilityEnterpriseAuthenticationSid , 93,
+ :WinCapabilityRemovableStorageSid , 94
+ )
+
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/aa446585(v=vs.85).aspx
+ # BOOL WINAPI CreateWellKnownSid(
+ # _In_ WELL_KNOWN_SID_TYPE WellKnownSidType,
+ # _In_opt_ PSID DomainSid,
+ # _Out_opt_ PSID pSid,
+ # _Inout_ DWORD *cbSid
+ # );
+ ffi_lib :advapi32
+ attach_function_private :CreateWellKnownSid,
+ [WELL_KNOWN_SID_TYPE, :pointer, :pointer, :lpdword], :win32_bool
+
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/aa379151(v=vs.85).aspx
+ # BOOL WINAPI IsValidSid(
+ # _In_ PSID pSid
+ # );
+ ffi_lib :advapi32
+ attach_function_private :IsValidSid,
+ [:pointer], :win32_bool
end
diff --git a/lib/puppet/vendor.rb b/lib/puppet/vendor.rb
index fdaf5c053..7c6be4e80 100644
--- a/lib/puppet/vendor.rb
+++ b/lib/puppet/vendor.rb
@@ -4,8 +4,10 @@ module Puppet
# To vendor a library:
#
# * Download its whole git repo or untar into `lib/puppet/vendor/<libname>`
- # * Create a lib/puppetload_libraryname.rb file to add its libdir into the $:.
+ # * Create a vendor/puppetload_libraryname.rb file to add its libdir into the $:.
# (Look at existing load_xxx files, they should all follow the same pattern).
+ # * Add a <libname>/PUPPET_README.md file describing what the library is for
+ # and where it comes from.
# * To load the vendored lib upfront, add a `require '<vendorlib>'`line to
# `vendor/require_vendored.rb`.
# * To load the vendored lib on demand, add a comment to `vendor/require_vendored.rb`
diff --git a/lib/puppet/vendor/load_pathspec.rb b/lib/puppet/vendor/load_pathspec.rb
new file mode 100644
index 000000000..afdbc2279
--- /dev/null
+++ b/lib/puppet/vendor/load_pathspec.rb
@@ -0,0 +1 @@
+$: << File.join([File.dirname(__FILE__), "pathspec/lib"])
diff --git a/lib/puppet/vendor/load_rgen.rb b/lib/puppet/vendor/load_rgen.rb
new file mode 100644
index 000000000..ba1f1c5c7
--- /dev/null
+++ b/lib/puppet/vendor/load_rgen.rb
@@ -0,0 +1 @@
+$: << File.join([File.dirname(__FILE__), "rgen/lib"])
diff --git a/lib/puppet/vendor/pathspec/CHANGELOG.md b/lib/puppet/vendor/pathspec/CHANGELOG.md
new file mode 100644
index 000000000..e4fe66d91
--- /dev/null
+++ b/lib/puppet/vendor/pathspec/CHANGELOG.md
@@ -0,0 +1,2 @@
+0.0.2: Misc. Windows/regex fixes.
+0.0.1: Initial version.
diff --git a/lib/puppet/vendor/pathspec/LICENSE b/lib/puppet/vendor/pathspec/LICENSE
new file mode 100644
index 000000000..5c304d1a4
--- /dev/null
+++ b/lib/puppet/vendor/pathspec/LICENSE
@@ -0,0 +1,201 @@
+Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "{}"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright {yyyy} {name of copyright owner}
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/lib/puppet/vendor/pathspec/PUPPET_README.md b/lib/puppet/vendor/pathspec/PUPPET_README.md
new file mode 100644
index 000000000..e7ceddec7
--- /dev/null
+++ b/lib/puppet/vendor/pathspec/PUPPET_README.md
@@ -0,0 +1,6 @@
+Pathspec-ruby - Gitignore parsing in Ruby
+=============================================
+
+Pathspec-ruby version 0.0.2
+
+Copied from https://github.com/highb/pathspec-ruby/releases/tag/0.0.2
diff --git a/lib/puppet/vendor/pathspec/README.md b/lib/puppet/vendor/pathspec/README.md
new file mode 100644
index 000000000..ecbd64d3d
--- /dev/null
+++ b/lib/puppet/vendor/pathspec/README.md
@@ -0,0 +1,53 @@
+pathspec-ruby
+=============
+
+Match Path Specifications, such as .gitignore, in Ruby!
+
+Follows .gitignore syntax defined on [gitscm](http://git-scm.com/docs/gitignore)
+
+.gitignore functionality ported from [Python pathspec](https://pypi.python.org/pypi/pathspec/0.2.2) by [@cpburnz](https://github.com/cpburnz/python-path-specification)
+
+[Travis Status](https://travis-ci.org/highb/pathspec-ruby) ![Travis CI Status](https://travis-ci.org/highb/pathspec-ruby.svg?branch=master)
+
+## Build/Install from Rubygems
+```shell
+gem install pathspec
+```
+
+## Usage
+```ruby
+require 'pathspec'
+
+# Create a .gitignore-style Pathspec by giving it newline separated gitignore
+# lines, an array of gitignore lines, or any other enumable object that will
+# give strings matching the .gitignore-style (File, etc.)
+gitignore = Pathspec.new File.read('.gitignore', 'r')
+
+# Our .gitignore in this example contains:
+# !**/important.txt
+# abc/**
+
+# true, matches "abc/**"
+gitignore.match 'abc/def.rb'
+
+# false, because it has been negated using the line "!**/important.txt"
+gitignore.match 'abc/important.txt'
+
+# Give a path somewhere in the filesystem, and the Pathspec will return all
+# matching files underneath.
+# Returns ['/src/repo/abc/', '/src/repo/abc/123']
+gitignore.match_tree '/src/repo'
+
+# Give an enumerable of paths, and Pathspec will return the ones that match.
+# Returns ['/abc/123', '/abc/']
+gitignore.match_paths ['/abc/123', '/abc/important.txt', '/abc/']
+```
+
+## Building/Installing from Source
+```shell
+git clone git@github.com:highb/pathspec-ruby.git
+cd pathspec-ruby && bash ./build_from_source.sh
+```
+
+## Contributing
+Pull requests, bug reports, and feature requests welcome! :smile: I've tried to write exhaustive tests but who knows what cases I've missed.
diff --git a/lib/puppet/vendor/pathspec/lib/pathspec.rb b/lib/puppet/vendor/pathspec/lib/pathspec.rb
new file mode 100644
index 000000000..9d9a92837
--- /dev/null
+++ b/lib/puppet/vendor/pathspec/lib/pathspec.rb
@@ -0,0 +1,121 @@
+require 'pathspec/gitignorespec'
+require 'pathspec/regexspec'
+require 'find'
+require 'pathname'
+
+class PathSpec
+ attr_reader :specs
+
+ def initialize(lines=nil, type=:git)
+ @specs = []
+
+ if lines
+ add(lines, type)
+ end
+
+ self
+ end
+
+ # Check if a path matches the pathspecs described
+ # Returns true if there are matches and none are excluded
+ # Returns false if there aren't matches or none are included
+ def match(path)
+ matches = specs_matching(path.to_s)
+ !matches.empty? && matches.all? {|m| m.inclusive?}
+ end
+
+ def specs_matching(path)
+ @specs.select do |spec|
+ if spec.match(path)
+ spec
+ end
+ end
+ end
+
+ # Check if any files in a given directory or subdirectories match the specs
+ # Returns matched paths or nil if no paths matched
+ def match_tree(root)
+ rootpath = Pathname.new(root)
+ matching = []
+
+ Find.find(root) do |path|
+ relpath = Pathname.new(path).relative_path_from(rootpath).to_s
+ relpath += '/' if File.directory? path
+ if match(relpath)
+ matching << path
+ end
+ end
+
+ matching
+ end
+
+ def match_path(path, root='/')
+ rootpath = Pathname.new(drive_letter_to_path(root))
+ relpath = Pathname.new(drive_letter_to_path(path)).relative_path_from(rootpath).to_s
+ relpath = relpath + '/' if path[-1].chr == '/'
+
+ match(relpath)
+ end
+
+ def match_paths(paths, root='/')
+ matching = []
+
+ paths.each do |path|
+ if match_path(path, root)
+ matching << path
+ end
+ end
+
+ matching
+ end
+
+ def drive_letter_to_path(path)
+ path.gsub(/^([a-zA-z]):\//, '/\1/')
+ end
+
+ # Generate specs from a filename, such as a .gitignore
+ def self.from_filename(filename, type=:git)
+ self.from_lines(File.open(filename, 'r'))
+ end
+
+ def self.from_lines(lines, type=:git)
+ self.new lines, type
+ end
+
+ # Generate specs from lines of text
+ def add(obj, type=:git)
+ spec_class = spec_type(type)
+
+ if obj.respond_to?(:each_line)
+ obj.each_line do |l|
+ spec = spec_class.new(l.rstrip)
+
+ if !spec.regex.nil? && !spec.inclusive?.nil?
+ @specs << spec
+ end
+ end
+ elsif obj.respond_to?(:each)
+ obj.each do |l|
+ add(l, type)
+ end
+ else
+ raise 'Cannot make Pathspec from non-string/non-enumerable object.'
+ end
+
+ self
+ end
+
+ def empty?
+ @specs.empty?
+ end
+
+ def spec_type(type)
+ if type == :git
+ GitIgnoreSpec
+ elsif type == :regex
+ RegexSpec
+ else
+ raise "Unknown spec type #{type}"
+ end
+ end
+end
diff --git a/lib/puppet/vendor/pathspec/lib/pathspec/gitignorespec.rb b/lib/puppet/vendor/pathspec/lib/pathspec/gitignorespec.rb
new file mode 100644
index 000000000..f7a94b359
--- /dev/null
+++ b/lib/puppet/vendor/pathspec/lib/pathspec/gitignorespec.rb
@@ -0,0 +1,275 @@
+# encoding: utf-8
+
+require 'pathspec/regexspec'
+
+class GitIgnoreSpec < RegexSpec
+ attr_reader :regex
+
+ def initialize(pattern)
+ pattern = pattern.strip unless pattern.nil?
+
+ # A pattern starting with a hash ('#') serves as a comment
+ # (neither includes nor excludes files). Escape the hash with a
+ # back-slash to match a literal hash (i.e., '\#').
+ if pattern.start_with?('#')
+ @regex = nil
+ @inclusive = nil
+
+ # A blank pattern is a null-operation (neither includes nor
+ # excludes files).
+ elsif pattern.empty?
+ @regex = nil
+ @inclusive = nil
+
+ # Patterns containing three or more consecutive stars are invalid and
+ # will be ignored.
+ elsif pattern =~ /\*\*\*+/
+ @regex = nil
+ @inclusive = nil
+
+ # We have a valid pattern!
+ else
+ # A pattern starting with an exclamation mark ('!') negates the
+ # pattern (exclude instead of include). Escape the exclamation
+ # mark with a back-slash to match a literal exclamation mark
+ # (i.e., '\!').
+ if pattern.start_with?('!')
+ @inclusive = false
+ # Remove leading exclamation mark.
+ pattern = pattern[1..-1]
+ else
+ @inclusive = true
+ end
+
+ # Remove leading back-slash escape for escaped hash ('#') or
+ # exclamation mark ('!').
+ if pattern.start_with?('\\')
+ pattern = pattern[1..-1]
+ end
+
+ # Split pattern into segments. -1 to allow trailing slashes.
+ pattern_segs = pattern.split('/', -1)
+
+ # Normalize pattern to make processing easier.
+
+ # A pattern beginning with a slash ('/') will only match paths
+ # directly on the root directory instead of any descendant
+ # paths. So, remove empty first segment to make pattern relative
+ # to root.
+ if pattern_segs[0].empty?
+ pattern_segs.shift
+ else
+ # A pattern without a beginning slash ('/') will match any
+ # descendant path. This is equivilent to "**/{pattern}". So,
+ # prepend with double-asterisks to make pattern relative to
+ # root.
+ if pattern_segs.length == 1 && pattern_segs[0] != '**'
+ pattern_segs.insert(0, '**')
+ end
+ end
+
+ # A pattern ending with a slash ('/') will match all descendant
+ # paths of if it is a directory but not if it is a regular file.
+ # This is equivilent to "{pattern}/**". So, set last segment to
+ # double asterisks to include all descendants.
+ if pattern_segs[-1].empty?
+ pattern_segs[-1] = '**'
+ end
+
+ # Handle platforms with backslash separated paths
+ if File::SEPARATOR == '\\'
+ path_sep = '\\\\'
+ else
+ path_sep = '/'
+ end
+
+
+ # Build regular expression from pattern.
+ regex = '^'
+ need_slash = false
+ regex_end = pattern_segs.size - 1
+ pattern_segs.each_index do |i|
+ seg = pattern_segs[i]
+
+ if seg == '**'
+ # A pattern consisting solely of double-asterisks ('**')
+ # will match every path.
+ if i == 0 && i == regex_end
+ regex.concat('.+')
+
+ # A normalized pattern beginning with double-asterisks
+ # ('**') will match any leading path segments.
+ elsif i == 0
+ regex.concat("(?:.+#{path_sep})?")
+ need_slash = false
+
+ # A normalized pattern ending with double-asterisks ('**')
+ # will match any trailing path segments.
+ elsif i == regex_end
+ regex.concat("#{path_sep}.*")
+
+ # A pattern with inner double-asterisks ('**') will match
+ # multiple (or zero) inner path segments.
+ else
+ regex.concat("(?:#{path_sep}.+)?")
+ need_slash = true
+ end
+
+ # Match single path segment.
+ elsif seg == '*'
+ if need_slash
+ regex.concat(path_sep)
+ end
+
+ regex.concat("[^#{path_sep}]+")
+ need_slash = true
+
+ else
+ # Match segment glob pattern.
+ if need_slash
+ regex.concat(path_sep)
+ end
+
+ regex.concat(translate_segment_glob(seg))
+ need_slash = true
+ end
+ end
+
+ regex.concat('$')
+ super(regex)
+ end
+ end
+
+ def match(path)
+ super(path)
+ end
+
+ def translate_segment_glob(pattern)
+ """
+ Translates the glob pattern to a regular expression. This is used in
+ the constructor to translate a path segment glob pattern to its
+ corresponding regular expression.
+
+ *pattern* (``str``) is the glob pattern.
+
+ Returns the regular expression (``str``).
+ """
+ # NOTE: This is derived from `fnmatch.translate()` and is similar to
+ # the POSIX function `fnmatch()` with the `FNM_PATHNAME` flag set.
+
+ escape = false
+ regex = ''
+ i = 0
+
+ while i < pattern.size
+ # Get next character.
+ char = pattern[i].chr
+ i += 1
+
+ # Escape the character.
+ if escape
+ escape = false
+ regex += Regexp.escape(char)
+
+ # Escape character, escape next character.
+ elsif char == '\\'
+ escape = true
+
+ # Multi-character wildcard. Match any string (except slashes),
+ # including an empty string.
+ elsif char == '*'
+ regex += '[^/]*'
+
+ # Single-character wildcard. Match any single character (except
+ # a slash).
+ elsif char == '?'
+ regex += '[^/]'
+
+ # Braket expression wildcard. Except for the beginning
+ # exclamation mark, the whole braket expression can be used
+ # directly as regex but we have to find where the expression
+ # ends.
+ # - "[][!]" matchs ']', '[' and '!'.
+ # - "[]-]" matchs ']' and '-'.
+ # - "[!]a-]" matchs any character except ']', 'a' and '-'.
+ elsif char == '['
+ j = i
+ # Pass brack expression negation.
+ if j < pattern.size && pattern[j].chr == '!'
+ j += 1
+ end
+
+ # Pass first closing braket if it is at the beginning of the
+ # expression.
+ if j < pattern.size && pattern[j].chr == ']'
+ j += 1
+ end
+
+ # Find closing braket. Stop once we reach the end or find it.
+ while j < pattern.size && pattern[j].chr != ']'
+ j += 1
+ end
+
+
+ if j < pattern.size
+ expr = '['
+
+ # Braket expression needs to be negated.
+ if pattern[i].chr == '!'
+ expr += '^'
+ i += 1
+
+ # POSIX declares that the regex braket expression negation
+ # "[^...]" is undefined in a glob pattern. Python's
+ # `fnmatch.translate()` escapes the caret ('^') as a
+ # literal. To maintain consistency with undefined behavior,
+ # I am escaping the '^' as well.
+ elsif pattern[i].chr == '^'
+ expr += '\\^'
+ i += 1
+ end
+
+ # Escape brackets contained within pattern
+ if pattern[i].chr == ']' && i != j
+ expr += '\]'
+ i += 1
+ end
+
+
+ # Build regex braket expression. Escape slashes so they are
+ # treated as literal slashes by regex as defined by POSIX.
+ expr += pattern[i..j].sub('\\', '\\\\')
+
+ # Add regex braket expression to regex result.
+ regex += expr
+
+ # Found end of braket expression. Increment j to be one past
+ # the closing braket:
+ #
+ # [...]
+ # ^ ^
+ # i j
+ #
+ j += 1
+ # Set i to one past the closing braket.
+ i = j
+
+ # Failed to find closing braket, treat opening braket as a
+ # braket literal instead of as an expression.
+ else
+ regex += '\['
+ end
+
+ # Regular character, escape it for regex.
+ else
+ regex << Regexp.escape(char)
+ end
+ end
+
+ regex
+ end
+
+ def inclusive?
+ @inclusive
+ end
+end
diff --git a/lib/puppet/vendor/pathspec/lib/pathspec/regexspec.rb b/lib/puppet/vendor/pathspec/lib/pathspec/regexspec.rb
new file mode 100644
index 000000000..af043f445
--- /dev/null
+++ b/lib/puppet/vendor/pathspec/lib/pathspec/regexspec.rb
@@ -0,0 +1,17 @@
+require 'pathspec/spec'
+
+class RegexSpec < Spec
+ def initialize(regex)
+ @regex = Regexp.compile regex
+
+ super
+ end
+
+ def inclusive?
+ true
+ end
+
+ def match(path)
+ @regex.match(path) if @regex
+ end
+end
diff --git a/lib/puppet/vendor/pathspec/lib/pathspec/spec.rb b/lib/puppet/vendor/pathspec/lib/pathspec/spec.rb
new file mode 100644
index 000000000..756588b1f
--- /dev/null
+++ b/lib/puppet/vendor/pathspec/lib/pathspec/spec.rb
@@ -0,0 +1,14 @@
+class Spec
+ attr_reader :regex
+
+ def initialize(*_)
+ end
+
+ def match(files)
+ raise "Unimplemented"
+ end
+
+ def inclusive?
+ true
+ end
+end
diff --git a/lib/puppet/vendor/require_vendored.rb b/lib/puppet/vendor/require_vendored.rb
index 141c810de..d9f38fabc 100644
--- a/lib/puppet/vendor/require_vendored.rb
+++ b/lib/puppet/vendor/require_vendored.rb
@@ -5,3 +5,5 @@ require 'safe_yaml'
require 'puppet/vendor/safe_yaml_patches'
# The vendored library 'semantic' is loaded on demand.
+# The vendored library 'rgen' is loaded on demand.
+# The vendored library 'pathspec' is loaded on demand.
diff --git a/lib/puppet/vendor/rgen/CHANGELOG b/lib/puppet/vendor/rgen/CHANGELOG
new file mode 100644
index 000000000..bac8f6d8a
--- /dev/null
+++ b/lib/puppet/vendor/rgen/CHANGELOG
@@ -0,0 +1,197 @@
+=0.1.0 (August 3rd, 2006)
+
+* First public release
+
+=0.2.0 (September 3rd, 2006)
+
+* Added model transformation language (Transformer)
+* Now RGen is distributed as a gem
+* More complete documentation
+
+=0.3.0 (October 9th, 2006)
+
+* Improved XML Instantiator (Namespaces, Resolver, Customization)
+* Added many_to_one builder method
+* Added attribute reflection to MMBase (one_attributes, many_attributes)
+* Added +copy+ method to Transformer
+* Added simple model dumper module
+* Fixed mmgen/mmgen.rb
+
+=0.4.0 (Aug 8th, 2007)
+
+* Added ECore metamodel and use it as the core metametamodel
+* Revised and extended MetamodelBuilder language
+* There is an ECore instance describing each metamodel built using MetamodelBuilder now
+* Metamodel generator is now ECore based
+* Added Ruby implementation of Boolean and Enum types
+* Switched XML Instantiator to xmlscan for performance reasons
+* Cleaned up instantiator file structure
+* Renamed RGen::XMLInstantiator into RGen::Instantiator::DefaultXMLInstantiator
+* Included xmlscan as a redistributed module
+* Added support for chardata within XML tags
+* Added (Enterprise Architect) XMI to ECore instantiator
+* Some minor fixes in NameHelper
+* Some fixes to template language
+* Added UML1.3 Metamodel
+* Added tranformation from UML1.3 to ECore
+
+=0.4.1 (Nov 25th, 2007)
+
+* Template language performance improvement
+* Bugfix: use true/false instead of symbols for boolean attribute default values in metamodel classes
+* Minor fixes on metamodel generator and ecore primitive type handling
+* Made transformer implementation non-recursive to prevent "stack level too deep" exception for large models
+* Minor fixes on EAInstantiator
+* Made transformer search for matching rules for superclasses
+* Bugfix: Enums are now added to EPackages created using the "ecore" method on a module
+* Bugfix: Metamodel generator now writes enum names
+* Performance improvement: don't require ecore transformer every time someone calls "ecore"
+* Major performance improvement of template engine (no Regexps to check \n at end of line)
+* Major performance improvement: AbstractXMLInstantiator optionally controls the garbage collector
+* Major performance improvement: ERB templates are reused in metamodel_builder
+* Added delete method to Environment
+
+=0.4.2 (Mar 2nd, 2008)
+
+* Performance improvement: collection feature of array extension uses hashes now to speed up array union
+* Performance improvement: find on environment hashes elements by class
+* Extended Transformer to allow sharing of result maps between several Transformer instances
+* Bugfix: User defined upper bound values are no longer overwritten by -1 in all "many" metamodel builder methods
+
+=0.4.3 (Aug 12th, 2008)
+
+* Performance improvement: significant speed up of metamodel reverse registration
+* Bugfix: Use object identity for metamodel to-many add/remove methods
+* Bugfix: If expand's :for expression evaluates to nil an error is generated (silently used current context before)
+* Template language indentation string can be set on DirectoryTemplateContainer and with the "file" command
+
+=0.4.4 (Sep 10th, 2008)
+
+* Added "abstract" metamodel DSL command
+* Added ecore_ext.rb with convenience methods
+* Added XMI1.1 serializer, revised XMLSerializer super class
+
+=0.4.5 (Nov 17th, 2008)
+
+* Updated XMI1.1 serializer to support explicit placement of elements on content level of the XMI file
+
+=0.4.6 (Mar 1st, 2009)
+
+* Bugfix: expand :foreach silently assumed current context if :foreach evalutated to nil
+* Bugfix: fixed unit test for non-Windows plattforms (\r\n)
+* Bugfix: depending on the Ruby version and/or platform constants used in templates could not be resolved
+* Added automatic line ending detection (\n or \r\n) for template language +nl+ command
+
+=0.5.0 (Jun 8th, 2009)
+
+* Added ModelBuilder and ModelSerializer
+* Added template language "define_local" command
+* Added template language "evaluate" command
+* Fixed template language bug: indentation problem when expand continues a non-empty line
+* Fixed template language bug: template content expands several times when a template container is called recursively
+* Fixed template language bug: template resolution problem if a template file has the same name as a template directory
+* Cleaned up EA support
+* Added method to clear ecore metamodel reflection cache
+* Improved overriding of metamodel features in reopened classes
+
+=0.5.1 (Nov 10th, 2009)
+
+* Fixed metamodel builder bug: _register at one-side did not unregister from the element referenced by the old value
+* Added helper class for building simple model comparators
+
+=0.5.2 (Jun 13th, 2010)
+
+* Added has_many_attr to metamodel builder, support for "many" attributes
+* Added JSON support (json instantiator and serializer)
+* Added QualifiedNameResolver instantiation helper
+* Added reference proxy support
+* Added more generic access methods on metaclasses
+* Added ReferenceResolver resolver mixin
+* Fixed ecore xml instantiator and serializer to handle references to builtin datatypes correctly
+* Fixed bug in ecore xml serializer to not output references which are opposites of containment references
+
+=0.5.3 (Aug 13th, 2010)
+
+* Fixed string escaping in JSON instantiator and serializer
+* Fixed order of eClassifiers and eSubpackages within an EPackage created by reflection on a RGen module
+
+=0.5.4
+
+* Fixed undeterministic order of child elements in ModelSerializer
+* Fixed undeterministic order of attributes in XMI serializers
+* Fixed ModelSerializer to always serialize the to-one part of bidirectional 1:N references
+* Fixed ModelSerializer to add :as => in case of ambiguous child roles
+* Made JsonInstantiator search subpackages for unqualified class names
+
+=0.6.0
+
+* Added exception when trying to instantiate abstract class
+* Replaced xmlscan by dependency to nokogiri
+* Made RGen work with Ruby 1.9
+* Cleaned up intermediate attribute and reference description, improvement of metamodel load time
+* Added optional data property for MMProxy
+* Added ECoreToRuby which can create Ruby classes and modules from ECore models in memory (without metamodel generator)
+* Refactored out QualifiedNameProvider and OppositeReferenceFilter
+* Added model fragment/fragmented models support
+* Extended Instantiator::ReferenceResolver and changed it into a class
+* Moved utilities into util folder/module
+* Added FileCacheMap
+* Fixed template language bug: indenting not correct after callback into same template container and iinc/idec
+* Added support for fragmented models
+* Added FileChangeDetector utility
+* Added CachedGlob utility
+* Added index parameter to model element add methods
+* Added MMGeneric
+* Modified has_many_attr to allow the same value in the same attribute multiple times
+* Made Environment#delete faster on large models
+* Added type check of ecore defaultValueLiteral content in MetamodelBuilder
+* Many-feature setters can work with an Enumerable instead of an Array
+* Added pattern matcher utility
+* Fixed problem of Ruby hanging when exceptions occur
+* Fixed metamodel generator to quote illegal enum literal symbols
+* Imporved UML to ECore transformer and EA support
+
+=0.6.1
+
+* Fixed metamodel builder to not overwrite a model element's 'class' method
+* Added enum type transformation to ECoreToUML13 transformer, primitive type mapping based on instanceClassName
+* Fixed default value appearing on read after setting a feature value to nil
+* Added eIsSet and eUnset methods
+* Added eContainer and eContainingFeature methods
+* Fixed ModelFragment#elements not containing root elements
+* Added optional output of invalidation reason to FileCacheMap#load_data
+
+=0.6.2
+
+* Made qualified name provider work with unidirectional containment references
+* Fixed array_extension breaking the Hash[] method
+
+=0.6.3
+
+* Added BigDecimal support
+
+=0.6.4
+
+* Made FileChangeDetector and FileCacheMap robust against missing files
+
+=0.6.5
+
+* Fixed missing default argument of FragmentedModel#resolve
+* Added to_str to methods which aren't forwarded by array extension on empty arrays
+
+=0.6.6
+
+* Added ModelFragment#mark_resolved and ResolutionHelper
+* Added ReferenceResolver option to output failed resolutions
+* Major performance improvement of FragmentedModel#resolve
+* Fixed a Ruby 2.0 related warning
+
+=0.7.0
+
+* Enforce unique container rule by automatically disconnecting elements from other containers
+* Added support for long typed values (ELong), thanks to Thomas Hallgren;
+ Note that this is merely an EMF compatibility thing, RGen could already handle big integers before
+* Added eContents and eAllContents methods
+* Added setNilOrRemoveGeneric and setNilOrRemoveAllGeneric methods
+* Added disconnectContainer method
+
diff --git a/lib/puppet/vendor/rgen/MIT-LICENSE b/lib/puppet/vendor/rgen/MIT-LICENSE
new file mode 100644
index 000000000..e124b6ce0
--- /dev/null
+++ b/lib/puppet/vendor/rgen/MIT-LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2013 Martin Thiede
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/lib/puppet/vendor/rgen/PUPPET_README.md b/lib/puppet/vendor/rgen/PUPPET_README.md
new file mode 100644
index 000000000..2db19c522
--- /dev/null
+++ b/lib/puppet/vendor/rgen/PUPPET_README.md
@@ -0,0 +1,6 @@
+RGen - Ruby Modelling and Generator Framework
+=============================================
+
+RGen version 0.7.0
+
+Copied from https://github.com/mthiede/rgen/tree/3e3c42a470269982ef52972bed82d65f78de496c
diff --git a/lib/puppet/vendor/rgen/README.rdoc b/lib/puppet/vendor/rgen/README.rdoc
new file mode 100644
index 000000000..b6dde344e
--- /dev/null
+++ b/lib/puppet/vendor/rgen/README.rdoc
@@ -0,0 +1,78 @@
+= RGen - Ruby Modelling and Generator Framework
+
+RGen is a framework for Model Driven Software Development (MDSD)in Ruby.
+This means that it helps you build Metamodels, instantiate Models, modify
+and transform Models and finally generate arbitrary textual content from it.
+
+RGen features include:
+* Supporting Ruby 1.8.6, 1.8.7 and 1.9.x
+* Metamodel definition language (internal Ruby DSL)
+* ECore Meta-metamodel with an ECore instance available for every Metamodel
+* Generator creating the Ruby metamodel definition from an ECore instance
+* Transformer creating Ruby metamodel classes/modules from an ECore instance
+* Instantiation of Metamodels, i.e. creation of Models (e.g. from XML)
+* Model builder, internal Ruby DSL
+* Model fragmentation over several several files and per-fragment caching
+* Model Transformation language (internal Ruby DSL)
+* Powerful template based generator language (internal Ruby DSL inside of ERB)
+* UML 1.3 metamodel and XMI 1.1 instantiator included
+* ECore XML support (XMI 2.0)
+* UML-to-ECore and ECore-to-UML transformation (UML class models)
+* Enterprise Architect support (UML1.3/XMI1.1)
+
+
+== Download
+
+Get the latest release from Github: https://github.com/mthiede/rgen
+
+
+== Installation
+
+Install RGen as a Ruby gem:
+
+ gem install rgen
+
+
+== Running the Tests
+
+Change to the 'test' folder and run the test suite:
+
+ cd test
+ ruby rgen_test.rb
+
+
+== Documentation
+
+RDoc documentation is available at Github: http://mthiede.github.com/rgen/
+
+Find the main documentation parts for:
+* RGen::MetamodelBuilder
+* RGen::Transformer
+* RGen::TemplateLanguage
+* RGen::Fragment::FragmentedModel
+
+
+== Examples
+
+There are several examples of using RGen within the framework itself.
+
+Metamodel Definition:
+ lib/rgen/ecore/ecore.rb
+ lib/metamodels/uml13_metamodel.rb
+
+Instantiation:
+ lib/rgen/instantiator/xmi11_instantiator.rb
+ lib/rgen/instantiator/ecore_xml_instantiator.rb
+
+Transformations:
+ lib/rgen/ecore/ruby_to_ecore.rb
+ lib/transformers/uml13_to_ecore.rb
+
+Generators:
+ lib/mmgen/metamodel_generator.rb
+
+
+== License
+
+RGen is released under the MIT license.
+
diff --git a/lib/puppet/vendor/rgen/Rakefile b/lib/puppet/vendor/rgen/Rakefile
new file mode 100644
index 000000000..5d3e9e77b
--- /dev/null
+++ b/lib/puppet/vendor/rgen/Rakefile
@@ -0,0 +1,41 @@
+require 'rubygems/package_task'
+require 'rdoc/task'
+
+RGenGemSpec = Gem::Specification.new do |s|
+ s.name = %q{rgen}
+ s.version = "0.7.0"
+ s.date = Time.now.strftime("%Y-%m-%d")
+ s.summary = %q{Ruby Modelling and Generator Framework}
+ s.email = %q{martin dot thiede at gmx de}
+ s.homepage = %q{http://ruby-gen.org}
+ s.rubyforge_project = %q{rgen}
+ s.description = %q{RGen is a framework for Model Driven Software Development (MDSD) in Ruby. This means that it helps you build Metamodels, instantiate Models, modify and transform Models and finally generate arbitrary textual content from it.}
+ s.authors = ["Martin Thiede"]
+ gemfiles = Rake::FileList.new
+ gemfiles.include("{lib,test}/**/*")
+ gemfiles.include("README.rdoc", "CHANGELOG", "MIT-LICENSE", "Rakefile")
+ gemfiles.exclude(/\b\.bak\b/)
+ s.files = gemfiles
+ s.rdoc_options = ["--main", "README.rdoc", "-x", "test", "-x", "metamodels", "-x", "ea_support/uml13*"]
+ s.extra_rdoc_files = ["README.rdoc", "CHANGELOG", "MIT-LICENSE"]
+end
+
+RDoc::Task.new do |rd|
+ rd.main = "README.rdoc"
+ rd.rdoc_files.include("README.rdoc", "CHANGELOG", "MIT-LICENSE", "lib/**/*.rb")
+ rd.rdoc_files.exclude("lib/metamodels/*")
+ rd.rdoc_files.exclude("lib/ea_support/uml13*")
+ rd.rdoc_dir = "doc"
+end
+
+RGenPackageTask = Gem::PackageTask.new(RGenGemSpec) do |p|
+ p.need_zip = false
+end
+
+task :prepare_package_rdoc => :rdoc do
+ RGenPackageTask.package_files.include("doc/**/*")
+end
+
+task :release => [:prepare_package_rdoc, :package]
+
+task :clobber => [:clobber_rdoc, :clobber_package]
diff --git a/lib/puppet/vendor/rgen/TODO b/lib/puppet/vendor/rgen/TODO
new file mode 100644
index 000000000..f91158920
--- /dev/null
+++ b/lib/puppet/vendor/rgen/TODO
@@ -0,0 +1,41 @@
+=Known Bugs
+* <% expand ... :indent => 0 %> seems to change behaviour of active template not only expanded subtemplate
+* Ecore build in types (EString, ...) do not work in ECore instantiator, define your own EDatatype instead
+* ECore datatypes in RGen::ECore should use Java like instanceClassNames
+* overloading of transformation rules not working correctly
+* with \r\n in templates, empty lines appear in output
+* <%nl%> after <%nows%> creates no indentation (<%nl%> in another template in same file)
+
+=Major issues
+* XML instantiator documentation
+* revise builder datatypes, especially enum implementation using Enum objects as types,
+ also revise ecore metamodel at this point
+* revise documentation of BuilderExtensions
+* further cleanup EA UML import/export
+ - The differences between EA UML and uml13_metamodel.rb seem to be violations by EA, ArgoUML follows the standard much more closely
+ - Enums should be instances of Enumeration class with EnumerationLiterals (UML Standard),
+ for EA convert to Classes with stereotype "enumeration" and attributes as literals
+ (this is what EA 7 creates when clicking on the "New Enumeration" button, EA will reference these classes as type)
+ This is whats missing for Pragma MM generators.
+ - Support primitive types as instances of DataType (which basically have a name) instead of tagged values
+ (this should also be working with EA 7, the tagged values are just add on)
+ - Support more UML metamodel features in the transformers
+* Model Serializer:
+ - make "name" attribute configurable
+ - convert chars in string into something Ruby compatible (e.g newline to \n)
+
+=Minor Issues
+* allow definition of templates from within regular code
+* indexed find in environment
+* XMI Instantiator fixmap: add element names to make feature names unique
+* no error for expand '..', :forach => (foreach misspelled)
+* With JRuby (1.3.1) exceptions raised in templates have a short or no backtrace
+
+
+* extended constraint checks (feature bounds)
+* class filter in RText language
+* root classes for RText language
+* command/class aliases in RText language
+* language variants (different root classes depending on file type)
+* reference name in reference_qualifier
+
diff --git a/lib/puppet/vendor/rgen/anounce.txt b/lib/puppet/vendor/rgen/anounce.txt
new file mode 100644
index 000000000..c70de78af
--- /dev/null
+++ b/lib/puppet/vendor/rgen/anounce.txt
@@ -0,0 +1,61 @@
+=RGen - Ruby Modelling and Generator Framework
+
+RGen is a framework to support the "Model Driven Software Development (MDSD)" approach. Some people may want to call just about the same thing "Domain Specific Languages".
+
+The essence is to have a Metamodel which imposes a structure as well as certain other constraints on the Models to be instantiated from it. The Metamodel can be part of the software which is being developed.
+From a formal language point of view, the metamodel could be regarded as a grammer with the model being a sentence of the grammer.
+
+One possible application of MDSD are large domain specific software systems like banking systems. In this case one would define a metamodel which reflects the application domain (e.g. accounts, customers, their relations, ...).
+The metamodel can then serve as a common means of communication between users from the application domain (e.g. the customer) and software developers, as well as between software developers working on different subsystems. In addition, the metamodel can be used to generate recurring parts of the software instead of writing it by hand
+(e.g. database interfaces, application server glue code, etc). In a particular project a lot more usescases will typically show up.
+
+A very good framework implementing the MDSD approach is the open source Java framework OpenArchitectureWare (http://www.openarchitectureware.org/). Actually OpenArchitectureWare inspired the development of RGen.
+
+RGen implements many features also provided by OpenArchitectureWare:
+* Programmatic (textual) definition of Metamodels
+* Instantiation of models from various sources (XML, UML Models, ...)
+* Support of Model Transformations
+* Powerful template language (based on ERB) to generate arbitrary textual content
+
+In contrast to the mentioned Java framework, RGen is a more lightweight approach.
+It can be used to write powerful generator or transformation scripts quickly.
+However I believe RGen can also be useful in large software development projects.
+
+
+Here are some example usecases of RGen:
+
+Example 1: Generating C Code from a XML description
+* Directly instantiate the XML file using RGen's XMLInstantiator
+ An implicit Metamodel (Ruby classes) will be created automatically
+ The model is now available as Ruby objects in memory
+* Use the template language to navigate the instantiated model and generate C code
+
+Example 2: UML Defined Application specific Metamodel
+* Specify your metamodel as a UML Class Diagram
+* If the tool you use is Enterprise Architect, the UML Class Diagram can directly be instantiated using RGen's XMIClassInstantiator
+* If not, support for your tool should be added. The existing XMI instantiator is basically a transformation from an XMI model to an UML Class model. For a tool producing different XMI output the transformation has to be adapted.
+* Use the included MetamodelGenerator to generate Ruby classes from the UML model. These classes act as your application specific metamodel. Of course the generated classes can be extended by hand written code, either by subclassing or by just adding methods using Ruby's open classes. The generated code itself should not be touched.
+* Extend RGen with an own instantiator which reads your specific file format and instantiates your application specific metamodel
+* Then go on doing transformations on your model(s) or generate output.
+
+
+RGen could also be useful in combination with Rails.
+One application to Rails could be to generate not only the model, view and controller classes, but also the database schemas and code reflecting associations between Rails model elements (i.e. the has_many, belongs_to, .. code in the ActiveRecord subclasses)
+The base model for such a generation could be a description of the model elements and its associations as an UML class model.
+Another application could be to base new Rails generators (like the Login generator, etc) on RGen.
+
+
+=Major Performance Improvement
+
+RGen 0.4.1 features major performance improvements.
+All applications using the template language will be faster now.
+Applications using the AbstractXMLInstantiator can benefit if explicit garbage collection is enabled.
+(see documentation of AbstractXMLInstantiator)
+Another improvement makes the "ecore" class method of classes and modules much faster.
+Last but not least the loading of metamodels takes about half the time as before.
+
+For large models the metamodel generator is about 20 times faster.
+Reading a 50 000 lines ecore file now takes 9 seconds on a Centrino Duo 2GHz (85s with RGen 0.4.0)
+Generating the RGen metamodel code for the ecore model takes 5 seconds (200s with RGen 0.4.0)
+Requiring this RGen metamodel takes about 3 seconds (about 6s with RGen 0.4.0).
+
diff --git a/lib/puppet/vendor/rgen/design_rationale.txt b/lib/puppet/vendor/rgen/design_rationale.txt
new file mode 100644
index 000000000..0c6213c78
--- /dev/null
+++ b/lib/puppet/vendor/rgen/design_rationale.txt
@@ -0,0 +1,71 @@
+=ElementSet vs. Array
+
+Subject:
+Use a special array-like class "ElementSet" with the following properties:
+* can call methods of elements by . notation
+* can use all set and enumerable methods of Array
+* enforce constraints regarding type of elements
+* auto register/unregister with counterpart of an association
+
+Dependencies:
+* Without the constraint and register/unregister functionality of the ElementSet,
+ the API of model elements built by MetamodelBuilder has to be different:
+ instead of "e.myelements << newel" would be "e.addMyelements(newel)"
+ However this can also be an advantage (see Metamodel Many Assoc API)
+
+A1. ElementSet:
++ nice notation for calling methods of elements (.)
++ nice notation for adding/removing elements from a model element
+ (e.myelements << newel; e.myelements.delete newel)
+- complicated to realize
+ if ElementSet inherits from Array:
+ constraints/registration can not be garanteed for all add/remove operations
+ input and output of Array methods must be wrapped into ElementSet objects
+ if ElementSet delegates to an Array:
+ all (relevant) methods have to be delegated (methods from including
+ Enumerable do not automatically return ElementSet objects)
+- dot notation for calling methods of elements my lead to errors which are difficult
+ to find
+
+A2. Array:
++ a separate operator like >> makes calling methods of elements more explicit
++ very easy to implement
++ easy to understand by users (no "magic" going on)
+
+Decision: (2006-06-08)
+A2. Array
+Simplicity of implementation and ease of use are more important than a nice notation
+
+
+
+= Metamodel Many Assoc API
+
+Subject:
+How to implement the API to deal with to-many associations of model elements.
+One option is an array like object which is held by the model element for each to-many
+association and which is given to the user for modification (external array).
+The other option is an internal array which is only accessed via add and remove
+methods
+
+Dependencies:
+If an external array is used, this array must check the association's constraints
+and register/unregister with the other side of the association.
+(see ElementSet vs. Array)
+
+A1.External Array
++ nice API (e.myassocs << newel; e. myassocs.delete newel)
++ this is a Rails like API
+- a reference to the array might be stored somewhere else in the program and
+ accidentially be modified, this would modify the model element it belongs to
+ as well as register/unregister with other model elements leading to errors
+ which are hard to find
+- an external array is complicated to implement (see ElementSet vs. Array)
+
+A2.Internal Array
++ easy to understand for non Ruby/Rails aware users
++ simple implementation
+
+Decision: (2006-06-09)
+A2. Internal Array
+Simplicity of implementation and ease of use are more important than a nice notation
+ \ No newline at end of file
diff --git a/lib/puppet/vendor/rgen/lib/ea_support/ea_support.rb b/lib/puppet/vendor/rgen/lib/ea_support/ea_support.rb
new file mode 100644
index 000000000..a9062b9f6
--- /dev/null
+++ b/lib/puppet/vendor/rgen/lib/ea_support/ea_support.rb
@@ -0,0 +1,54 @@
+require 'ea_support/uml13_ea_metamodel'
+require 'ea_support/uml13_ea_metamodel_ext'
+require 'ea_support/uml13_to_uml13_ea'
+require 'ea_support/uml13_ea_to_uml13'
+require 'ea_support/id_store'
+require 'rgen/serializer/xmi11_serializer'
+require 'rgen/instantiator/xmi11_instantiator'
+require 'rgen/environment'
+
+module EASupport
+
+ FIXMAP = {
+ :tags => {
+ "EAStub" => proc { |tag, attr|
+ UML13EA::Class.new(:name => attr["name"]) if attr["UMLType"] == "Class"
+ }
+ }
+ }
+
+ INFO = XMI11Instantiator::INFO
+ WARN = XMI11Instantiator::WARN
+ ERROR = XMI11Instantiator::ERROR
+
+ def self.instantiateUML13FromXMI11(envUML, fileName, options={})
+ envUMLEA = RGen::Environment.new
+ xmiInst = XMI11Instantiator.new(envUMLEA, FIXMAP, options[:loglevel] || ERROR)
+ xmiInst.add_metamodel("omg.org/UML1.3", UML13EA)
+ File.open(fileName) do |f|
+ xmiInst.instantiate(f.read)
+ end
+ trans = UML13EAToUML13.new(envUMLEA, envUML)
+ trans.transform
+ trans.cleanModel if options[:clean_model]
+ end
+
+ def self.serializeUML13ToXMI11(envUML, fileName, options={})
+ envUMLEA = RGen::Environment.new
+
+ UML13EA.idStore = options[:keep_ids] ?
+ IdStore.new(File.dirname(fileName)+"/"+File.basename(fileName)+".ids") : IdStore.new
+
+ UML13ToUML13EA.new(envUML, envUMLEA).transform
+
+ File.open(fileName, "w") do |f|
+ xmiSer = RGen::Serializer::XMI11Serializer.new(f)
+ xmiSer.setNamespace("UML","omg.org/UML1.3")
+ xmiSer.serialize(envUMLEA.find(:class => UML13EA::Model).first,
+ {:documentation => {:exporter => "Enterprise Architect", :exporterVersion => "2.5"}})
+ end
+
+ UML13EA.idStore.store
+ end
+
+end \ No newline at end of file
diff --git a/lib/puppet/vendor/rgen/lib/ea_support/id_store.rb b/lib/puppet/vendor/rgen/lib/ea_support/id_store.rb
new file mode 100644
index 000000000..05af13ebf
--- /dev/null
+++ b/lib/puppet/vendor/rgen/lib/ea_support/id_store.rb
@@ -0,0 +1,32 @@
+require 'yaml'
+
+class IdStore
+ def initialize(fileName=nil)
+ if fileName
+ raise "Base directory does not exist: #{File.dirname(fileName)}" \
+ unless File.exist?(File.dirname(fileName))
+ @idsFileName = fileName
+ end
+ @idHash = nil
+ end
+
+ def idHash
+ load unless @idHash
+ @idHash
+ end
+
+ def load
+ if @idsFileName && File.exist?(@idsFileName)
+ @idHash = YAML.load_file(@idsFileName) || {}
+ else
+ @idHash = {}
+ end
+ end
+
+ def store
+ return unless @idsFileName
+ File.open(@idsFileName,"w") do |f|
+ YAML.dump(@idHash, f)
+ end
+ end
+end \ No newline at end of file
diff --git a/lib/puppet/vendor/rgen/lib/ea_support/uml13_ea_metamodel.rb b/lib/puppet/vendor/rgen/lib/ea_support/uml13_ea_metamodel.rb
new file mode 100644
index 000000000..7dc01a02c
--- /dev/null
+++ b/lib/puppet/vendor/rgen/lib/ea_support/uml13_ea_metamodel.rb
@@ -0,0 +1,562 @@
+require 'rgen/metamodel_builder'
+
+module UML13EA
+ extend RGen::MetamodelBuilder::ModuleExtension
+ include RGen::MetamodelBuilder::DataTypes
+
+ OperationDirectionKind = Enum.new(:name => 'OperationDirectionKind', :literals =>[ ])
+ MessageDirectionKind = Enum.new(:name => 'MessageDirectionKind', :literals =>[ ])
+ ChangeableKind = Enum.new(:name => 'ChangeableKind', :literals =>[ :changeable, :none, :addOnly ])
+ PseudostateKind = Enum.new(:name => 'PseudostateKind', :literals =>[ :initial, :deepHistory, :shallowHistory, :join, :fork, :branch, :junction, :final ])
+ ParameterDirectionKind = Enum.new(:name => 'ParameterDirectionKind', :literals =>[ :in, :inout, :out, :return ])
+ ScopeKind = Enum.new(:name => 'ScopeKind', :literals =>[ :instance, :classifier ])
+ OrderingKind = Enum.new(:name => 'OrderingKind', :literals =>[ :unordered, :ordered, :sorted ])
+ CallConcurrencyKind = Enum.new(:name => 'CallConcurrencyKind', :literals =>[ :sequential, :guarded, :concurrent ])
+ AggregationKind = Enum.new(:name => 'AggregationKind', :literals =>[ :none, :aggregate, :composite, :shared ])
+ VisibilityKind = Enum.new(:name => 'VisibilityKind', :literals =>[ :public, :protected, :private ])
+end
+
+class UML13EA::Expression < RGen::MetamodelBuilder::MMBase
+ has_attr 'language', String
+ has_attr 'body', String
+end
+
+class UML13EA::ActionExpression < UML13EA::Expression
+end
+
+class UML13EA::Element < RGen::MetamodelBuilder::MMBase
+end
+
+class UML13EA::ModelElement < UML13EA::Element
+ has_attr 'name', String
+ has_attr 'visibility', UML13EA::VisibilityKind, :defaultValueLiteral => "public"
+ has_attr 'isSpecification', Boolean
+end
+
+class UML13EA::Namespace < UML13EA::ModelElement
+end
+
+class UML13EA::GeneralizableElement < UML13EA::ModelElement
+ has_attr 'isRoot', Boolean
+ has_attr 'isLeaf', Boolean
+ has_attr 'isAbstract', Boolean
+end
+
+class UML13EA::Classifier < RGen::MetamodelBuilder::MMMultiple(UML13EA::GeneralizableElement, UML13EA::Namespace)
+end
+
+class UML13EA::ClassifierRole < UML13EA::Classifier
+end
+
+class UML13EA::PresentationElement < UML13EA::Element
+end
+
+class UML13EA::DiagramElement < UML13EA::PresentationElement
+ has_attr 'geometry', String
+ has_attr 'style', String
+end
+
+class UML13EA::Feature < UML13EA::ModelElement
+ has_attr 'ownerScope', UML13EA::ScopeKind, :defaultValueLiteral => "instance"
+end
+
+class UML13EA::BehavioralFeature < UML13EA::Feature
+ has_attr 'isQuery', Boolean
+end
+
+class UML13EA::Method < UML13EA::BehavioralFeature
+end
+
+class UML13EA::Actor < UML13EA::Classifier
+end
+
+class UML13EA::DataType < UML13EA::Classifier
+end
+
+class UML13EA::Primitive < UML13EA::DataType
+end
+
+class UML13EA::Action < UML13EA::ModelElement
+ has_attr 'isAsynchronous', Boolean
+end
+
+class UML13EA::SendAction < UML13EA::Action
+end
+
+class UML13EA::Interface < UML13EA::Classifier
+end
+
+class UML13EA::Event < UML13EA::ModelElement
+end
+
+class UML13EA::ChangeEvent < UML13EA::Event
+end
+
+class UML13EA::Partition < UML13EA::ModelElement
+end
+
+class UML13EA::Comment < UML13EA::ModelElement
+ has_attr 'body', String
+end
+
+class UML13EA::ProgrammingLanguageType < UML13EA::DataType
+end
+
+class UML13EA::StateMachine < UML13EA::ModelElement
+end
+
+class UML13EA::Call < RGen::MetamodelBuilder::MMBase
+end
+
+class UML13EA::Operation < UML13EA::BehavioralFeature
+ has_attr 'concurrency', UML13EA::CallConcurrencyKind, :defaultValueLiteral => "sequential"
+ has_attr 'isRoot', Boolean
+ has_attr 'isLeaf', Boolean
+ has_attr 'isAbstract', Boolean
+end
+
+class UML13EA::XmiIdProvider < RGen::MetamodelBuilder::MMBase
+end
+
+class UML13EA::StateVertex < RGen::MetamodelBuilder::MMMultiple(UML13EA::ModelElement, UML13EA::XmiIdProvider)
+end
+
+class UML13EA::SynchState < UML13EA::StateVertex
+ has_attr 'bound', Integer
+end
+
+class UML13EA::ClassifierInState < UML13EA::Classifier
+end
+
+class UML13EA::Link < UML13EA::ModelElement
+end
+
+class UML13EA::ProcedureExpression < UML13EA::Expression
+end
+
+class UML13EA::CallEvent < UML13EA::Event
+end
+
+class UML13EA::AssignmentAction < UML13EA::Action
+end
+
+class UML13EA::Relationship < UML13EA::ModelElement
+end
+
+class UML13EA::Association < RGen::MetamodelBuilder::MMMultiple(UML13EA::GeneralizableElement, UML13EA::Relationship, UML13EA::XmiIdProvider)
+end
+
+class UML13EA::AssociationRole < UML13EA::Association
+end
+
+class UML13EA::Diagram < UML13EA::PresentationElement
+ has_attr 'name', String
+ has_attr 'toolName', String
+ has_attr 'diagramType', String
+ has_attr 'style', String
+end
+
+class UML13EA::MultiplicityRange < RGen::MetamodelBuilder::MMBase
+ has_attr 'lower', String
+ has_attr 'upper', String
+end
+
+class UML13EA::ActionSequence < UML13EA::Action
+end
+
+class UML13EA::Constraint < UML13EA::ModelElement
+end
+
+class UML13EA::Instance < UML13EA::ModelElement
+end
+
+class UML13EA::UseCaseInstance < UML13EA::Instance
+end
+
+class UML13EA::State < UML13EA::StateVertex
+end
+
+class UML13EA::CompositeState < UML13EA::State
+ has_attr 'isConcurrent', Boolean
+end
+
+class UML13EA::SubmachineState < UML13EA::CompositeState
+end
+
+class UML13EA::SubactivityState < UML13EA::SubmachineState
+ has_attr 'isDynamic', Boolean
+end
+
+class UML13EA::StructuralFeature < UML13EA::Feature
+ has_attr 'changeable', UML13EA::ChangeableKind, :defaultValueLiteral => "changeable"
+ has_attr 'targetScope', UML13EA::ScopeKind, :defaultValueLiteral => "instance"
+end
+
+class UML13EA::Attribute < UML13EA::StructuralFeature
+end
+
+class UML13EA::Flow < UML13EA::Relationship
+end
+
+class UML13EA::Class < RGen::MetamodelBuilder::MMMultiple(UML13EA::Classifier, UML13EA::XmiIdProvider)
+ has_attr 'isActive', Boolean
+end
+
+class UML13EA::Guard < UML13EA::ModelElement
+end
+
+class UML13EA::CreateAction < UML13EA::Action
+end
+
+class UML13EA::IterationExpression < UML13EA::Expression
+end
+
+class UML13EA::ReturnAction < UML13EA::Action
+end
+
+class UML13EA::Parameter < UML13EA::ModelElement
+ has_attr 'kind', UML13EA::ParameterDirectionKind, :defaultValueLiteral => "inout"
+end
+
+class UML13EA::Dependency < UML13EA::Relationship
+end
+
+class UML13EA::Binding < UML13EA::Dependency
+end
+
+class UML13EA::Package < RGen::MetamodelBuilder::MMMultiple(UML13EA::Namespace, UML13EA::GeneralizableElement, UML13EA::XmiIdProvider)
+end
+
+class UML13EA::ObjectSetExpression < UML13EA::Expression
+end
+
+class UML13EA::StubState < UML13EA::StateVertex
+ has_attr 'referenceState', String
+end
+
+class UML13EA::Stereotype < UML13EA::GeneralizableElement
+ has_attr 'icon', String
+ has_attr 'baseClass', String
+end
+
+class UML13EA::Object < UML13EA::Instance
+end
+
+class UML13EA::LinkObject < RGen::MetamodelBuilder::MMMultiple(UML13EA::Link, UML13EA::Object)
+end
+
+class UML13EA::ComponentInstance < UML13EA::Instance
+end
+
+class UML13EA::Usage < UML13EA::Dependency
+end
+
+class UML13EA::SignalEvent < UML13EA::Event
+end
+
+class UML13EA::Structure < UML13EA::DataType
+end
+
+class UML13EA::AssociationEnd < RGen::MetamodelBuilder::MMMultiple(UML13EA::ModelElement, UML13EA::XmiIdProvider)
+ has_attr 'isNavigable', Boolean, :defaultValueLiteral => "false"
+ has_attr 'isOrdered', Boolean, :defaultValueLiteral => "false"
+ has_attr 'aggregation', UML13EA::AggregationKind, :defaultValueLiteral => "none"
+ has_attr 'targetScope', UML13EA::ScopeKind, :defaultValueLiteral => "instance"
+ has_attr 'changeable', UML13EA::ChangeableKind, :defaultValueLiteral => "changeable"
+ has_attr 'multiplicity', String
+end
+
+class UML13EA::AssociationEndRole < UML13EA::AssociationEnd
+end
+
+class UML13EA::Signal < UML13EA::Classifier
+end
+
+class UML13EA::Exception < UML13EA::Signal
+end
+
+class UML13EA::Extend < UML13EA::Relationship
+end
+
+class UML13EA::Argument < UML13EA::ModelElement
+end
+
+class UML13EA::TemplateParameter < RGen::MetamodelBuilder::MMBase
+end
+
+class UML13EA::PseudoState < UML13EA::StateVertex
+ has_attr 'kind', UML13EA::PseudostateKind, :defaultValueLiteral => "initial"
+end
+
+class UML13EA::SimpleState < UML13EA::State
+end
+
+class UML13EA::ActionState < UML13EA::SimpleState
+ has_attr 'isDynamic', Boolean
+end
+
+class UML13EA::TypeExpression < UML13EA::Expression
+end
+
+class UML13EA::DestroyAction < UML13EA::Action
+end
+
+class UML13EA::TerminateAction < UML13EA::Action
+end
+
+class UML13EA::Generalization < RGen::MetamodelBuilder::MMMultiple(UML13EA::Relationship, UML13EA::XmiIdProvider)
+ has_attr 'discriminator', String
+end
+
+class UML13EA::FinalState < UML13EA::State
+end
+
+class UML13EA::Subsystem < RGen::MetamodelBuilder::MMMultiple(UML13EA::Package, UML13EA::Classifier)
+ has_attr 'isInstantiable', Boolean
+end
+
+class UML13EA::TimeExpression < UML13EA::Expression
+end
+
+class UML13EA::TaggedValue < UML13EA::Element
+ has_attr 'tag', String
+ has_attr 'value', String
+end
+
+class UML13EA::DataValue < UML13EA::Instance
+end
+
+class UML13EA::Transition < UML13EA::ModelElement
+end
+
+class UML13EA::NodeInstance < UML13EA::Instance
+end
+
+class UML13EA::Component < UML13EA::Classifier
+end
+
+class UML13EA::Message < UML13EA::ModelElement
+end
+
+class UML13EA::Enumeration < UML13EA::DataType
+end
+
+class UML13EA::Reception < UML13EA::BehavioralFeature
+ has_attr 'isPolymorphic', Boolean
+ has_attr 'specification', String
+end
+
+class UML13EA::Include < UML13EA::Relationship
+end
+
+class UML13EA::CallState < UML13EA::ActionState
+end
+
+class UML13EA::ElementResidence < RGen::MetamodelBuilder::MMBase
+ has_attr 'visibility', UML13EA::VisibilityKind, :defaultValueLiteral => "public"
+end
+
+class UML13EA::UninterpretedAction < UML13EA::Action
+end
+
+class UML13EA::ArgListsExpression < UML13EA::Expression
+end
+
+class UML13EA::Stimulus < UML13EA::ModelElement
+end
+
+class UML13EA::AssociationClass < RGen::MetamodelBuilder::MMMultiple(UML13EA::Class, UML13EA::Association)
+end
+
+class UML13EA::Node < UML13EA::Classifier
+end
+
+class UML13EA::ElementImport < RGen::MetamodelBuilder::MMBase
+ has_attr 'visibility', UML13EA::VisibilityKind, :defaultValueLiteral => "public"
+ has_attr 'alias', String
+end
+
+class UML13EA::BooleanExpression < UML13EA::Expression
+end
+
+class UML13EA::Collaboration < RGen::MetamodelBuilder::MMMultiple(UML13EA::GeneralizableElement, UML13EA::Namespace)
+end
+
+class UML13EA::CallAction < UML13EA::Action
+end
+
+class UML13EA::UseCase < UML13EA::Classifier
+end
+
+class UML13EA::ActivityModel < UML13EA::StateMachine
+end
+
+class UML13EA::Permission < UML13EA::Dependency
+end
+
+class UML13EA::Interaction < UML13EA::ModelElement
+end
+
+class UML13EA::EnumerationLiteral < RGen::MetamodelBuilder::MMBase
+ has_attr 'name', String
+end
+
+class UML13EA::Model < UML13EA::Package
+end
+
+class UML13EA::LinkEnd < UML13EA::ModelElement
+end
+
+class UML13EA::ExtensionPoint < UML13EA::ModelElement
+ has_attr 'location', String
+end
+
+class UML13EA::Multiplicity < RGen::MetamodelBuilder::MMBase
+end
+
+class UML13EA::ObjectFlowState < UML13EA::SimpleState
+ has_attr 'isSynch', Boolean
+end
+
+class UML13EA::AttributeLink < UML13EA::ModelElement
+end
+
+class UML13EA::MappingExpression < UML13EA::Expression
+end
+
+class UML13EA::TimeEvent < UML13EA::Event
+end
+
+class UML13EA::Abstraction < UML13EA::Dependency
+end
+
+class UML13EA::ActionInstance < RGen::MetamodelBuilder::MMBase
+end
+
+
+UML13EA::ClassifierRole.contains_one_uni 'multiplicity', UML13EA::Multiplicity
+UML13EA::ClassifierRole.has_many 'availableContents', UML13EA::ModelElement
+UML13EA::ClassifierRole.has_many 'availableFeature', UML13EA::Feature
+UML13EA::ClassifierRole.has_one 'base', UML13EA::Classifier, :lowerBound => 1
+UML13EA::Diagram.contains_many 'element', UML13EA::DiagramElement, 'diagram'
+UML13EA::Method.many_to_one 'specification', UML13EA::Operation, 'method'
+UML13EA::Method.contains_one_uni 'body', UML13EA::ProcedureExpression
+UML13EA::SendAction.has_one 'signal', UML13EA::Signal, :lowerBound => 1
+UML13EA::ChangeEvent.contains_one_uni 'changeExpression', UML13EA::BooleanExpression
+UML13EA::Partition.has_many 'contents', UML13EA::ModelElement
+UML13EA::Comment.many_to_many 'annotatedElement', UML13EA::ModelElement, 'comment'
+UML13EA::ProgrammingLanguageType.contains_one_uni 'type', UML13EA::TypeExpression
+UML13EA::Action.contains_one_uni 'recurrence', UML13EA::IterationExpression
+UML13EA::Action.contains_one_uni 'target', UML13EA::ObjectSetExpression
+UML13EA::Action.contains_one_uni 'script', UML13EA::ActionExpression
+UML13EA::Action.contains_many_uni 'actualArgument', UML13EA::Argument
+UML13EA::StateMachine.many_to_one 'context', UML13EA::ModelElement, 'behavior'
+UML13EA::StateMachine.contains_many_uni 'transitions', UML13EA::Transition
+UML13EA::StateMachine.contains_one_uni 'top', UML13EA::State, :lowerBound => 1
+UML13EA::Operation.one_to_many 'occurrence', UML13EA::CallEvent, 'operation'
+UML13EA::ClassifierInState.has_one 'type', UML13EA::Classifier, :lowerBound => 1
+UML13EA::ClassifierInState.has_many 'inState', UML13EA::State
+UML13EA::Link.contains_many_uni 'connection', UML13EA::LinkEnd, :lowerBound => 2
+UML13EA::Link.has_one 'association', UML13EA::Association, :lowerBound => 1
+UML13EA::PresentationElement.many_to_many 'subject', UML13EA::ModelElement, 'presentation'
+UML13EA::AssociationRole.contains_one_uni 'multiplicity', UML13EA::Multiplicity
+UML13EA::AssociationRole.has_one 'base', UML13EA::Association
+UML13EA::Diagram.has_one 'owner', UML13EA::ModelElement, :lowerBound => 1
+UML13EA::ActionSequence.contains_many_uni 'action', UML13EA::Action
+UML13EA::Constraint.contains_one_uni 'body', UML13EA::BooleanExpression
+UML13EA::Constraint.many_to_many 'constrainedElement', UML13EA::ModelElement, 'constraint', :lowerBound => 1
+UML13EA::SubactivityState.contains_one_uni 'dynamicArguments', UML13EA::ArgListsExpression
+UML13EA::AssociationEnd.contains_many 'qualifier', UML13EA::Attribute, 'associationEnd'
+UML13EA::Attribute.contains_one_uni 'initialValue', UML13EA::Expression
+UML13EA::Flow.many_to_many 'source', UML13EA::ModelElement, 'sourceFlow'
+UML13EA::Flow.many_to_many 'target', UML13EA::ModelElement, 'targetFlow'
+UML13EA::Guard.contains_one_uni 'expression', UML13EA::BooleanExpression
+UML13EA::CreateAction.has_one 'instantiation', UML13EA::Classifier, :lowerBound => 1
+UML13EA::Namespace.contains_many 'ownedElement', UML13EA::ModelElement, 'namespace'
+UML13EA::Parameter.contains_one_uni 'defaultValue', UML13EA::Expression
+UML13EA::Parameter.many_to_many 'state', UML13EA::ObjectFlowState, 'parameter'
+UML13EA::Parameter.has_one 'type', UML13EA::Classifier, :lowerBound => 1
+UML13EA::Binding.has_many 'argument', UML13EA::ModelElement, :lowerBound => 1
+UML13EA::Event.contains_many_uni 'parameters', UML13EA::Parameter
+UML13EA::Dependency.many_to_many 'supplier', UML13EA::ModelElement, 'supplierDependency', :opposite_lowerBound => 1
+UML13EA::Dependency.many_to_many 'client', UML13EA::ModelElement, 'clientDependency', :opposite_lowerBound => 1
+UML13EA::Package.contains_many 'importedElement', UML13EA::ElementImport, 'package'
+UML13EA::Classifier.contains_many 'feature', UML13EA::Feature, 'owner'
+UML13EA::Stereotype.one_to_many 'extendedElement', UML13EA::ModelElement, 'stereotype'
+UML13EA::Stereotype.has_many 'requiredTag', UML13EA::TaggedValue
+UML13EA::ComponentInstance.has_many 'resident', UML13EA::Instance
+UML13EA::SignalEvent.many_to_one 'signal', UML13EA::Signal, 'occurrence', :lowerBound => 1
+UML13EA::Instance.contains_many_uni 'slot', UML13EA::AttributeLink
+UML13EA::Instance.one_to_many 'linkEnd', UML13EA::LinkEnd, 'instance'
+UML13EA::Instance.has_many 'classifier', UML13EA::Classifier, :lowerBound => 1
+UML13EA::AssociationEndRole.has_many 'availableQualifier', UML13EA::Attribute
+UML13EA::AssociationEndRole.has_one 'base', UML13EA::AssociationEnd
+UML13EA::Extend.many_to_one 'extension', UML13EA::UseCase, 'extend'
+UML13EA::Extend.contains_one_uni 'condition', UML13EA::BooleanExpression
+UML13EA::Extend.has_many 'extensionPoint', UML13EA::ExtensionPoint, :lowerBound => 1
+UML13EA::Extend.has_one 'base', UML13EA::UseCase, :lowerBound => 1
+UML13EA::Argument.contains_one_uni 'value', UML13EA::Expression
+UML13EA::TemplateParameter.has_one 'modelElement', UML13EA::ModelElement
+UML13EA::TemplateParameter.has_one 'defaultElement', UML13EA::ModelElement
+UML13EA::ActionState.contains_one_uni 'dynamicArguments', UML13EA::ArgListsExpression
+UML13EA::GeneralizableElement.one_to_many 'specialization', UML13EA::Generalization, 'supertype'
+UML13EA::GeneralizableElement.one_to_many 'generalization', UML13EA::Generalization, 'subtype'
+UML13EA::StateVertex.one_to_many 'incoming', UML13EA::Transition, 'target', :opposite_lowerBound => 1
+UML13EA::StateVertex.one_to_many 'outgoing', UML13EA::Transition, 'source', :opposite_lowerBound => 1
+UML13EA::CompositeState.contains_many 'substate', UML13EA::StateVertex, 'container', :lowerBound => 1
+UML13EA::ModelElement.contains_many 'taggedValue', UML13EA::TaggedValue, 'modelElement'
+UML13EA::StructuralFeature.contains_one_uni 'multiplicity', UML13EA::Multiplicity
+UML13EA::StructuralFeature.has_one 'type', UML13EA::Classifier, :lowerBound => 1
+UML13EA::Transition.has_one 'trigger', UML13EA::Event
+UML13EA::Transition.contains_one_uni 'effect', UML13EA::Action
+UML13EA::Transition.contains_one_uni 'guard', UML13EA::Guard
+UML13EA::NodeInstance.has_many 'resident', UML13EA::ComponentInstance
+UML13EA::Component.contains_many 'residentElement', UML13EA::ElementResidence, 'implementationLocation'
+UML13EA::Component.many_to_many 'deploymentLocation', UML13EA::Node, 'resident'
+UML13EA::Message.has_one 'action', UML13EA::Action, :lowerBound => 1
+UML13EA::Message.has_one 'communicationConnection', UML13EA::AssociationRole
+UML13EA::Message.has_many 'predecessor', UML13EA::Message
+UML13EA::Message.has_one 'receiver', UML13EA::ClassifierRole, :lowerBound => 1
+UML13EA::Message.has_one 'sender', UML13EA::ClassifierRole, :lowerBound => 1
+UML13EA::Message.has_one 'activator', UML13EA::Message
+UML13EA::Interaction.contains_many 'message', UML13EA::Message, 'interaction', :lowerBound => 1
+UML13EA::ModelElement.one_to_many 'elementResidence', UML13EA::ElementResidence, 'resident'
+UML13EA::ModelElement.contains_many_uni 'templateParameter', UML13EA::TemplateParameter
+UML13EA::ModelElement.one_to_many 'elementImport', UML13EA::ElementImport, 'modelElement'
+UML13EA::Enumeration.contains_many_uni 'literal', UML13EA::EnumerationLiteral, :lowerBound => 1
+UML13EA::Reception.many_to_one 'signal', UML13EA::Signal, 'reception'
+UML13EA::Association.contains_many 'connection', UML13EA::AssociationEnd, 'association', :lowerBound => 2
+UML13EA::Include.many_to_one 'base', UML13EA::UseCase, 'include'
+UML13EA::Include.has_one 'addition', UML13EA::UseCase, :lowerBound => 1
+UML13EA::Classifier.many_to_many 'participant', UML13EA::AssociationEnd, 'specification'
+UML13EA::Classifier.one_to_many 'associationEnd', UML13EA::AssociationEnd, 'type'
+UML13EA::Stimulus.has_one 'dispatchAction', UML13EA::Action, :lowerBound => 1
+UML13EA::Stimulus.has_one 'communicationLink', UML13EA::Link
+UML13EA::Stimulus.has_one 'receiver', UML13EA::Instance, :lowerBound => 1
+UML13EA::Stimulus.has_one 'sender', UML13EA::Instance, :lowerBound => 1
+UML13EA::Stimulus.has_many 'argument', UML13EA::Instance
+UML13EA::State.contains_one_uni 'doActivity', UML13EA::Action
+UML13EA::State.contains_many_uni 'internalTransition', UML13EA::Transition
+UML13EA::State.has_many 'deferrableEvent', UML13EA::Event
+UML13EA::State.contains_one_uni 'exit', UML13EA::Action
+UML13EA::State.contains_one_uni 'entry', UML13EA::Action
+UML13EA::Collaboration.has_one 'representedOperation', UML13EA::Operation
+UML13EA::Collaboration.has_one 'representedClassifier', UML13EA::Classifier
+UML13EA::Collaboration.has_many 'constrainingElement', UML13EA::ModelElement
+UML13EA::Collaboration.contains_many 'interaction', UML13EA::Interaction, 'context'
+UML13EA::CallAction.has_one 'operation', UML13EA::Operation, :lowerBound => 1
+UML13EA::UseCase.has_many 'extensionPoint', UML13EA::ExtensionPoint
+UML13EA::ActivityModel.contains_many_uni 'partition', UML13EA::Partition
+UML13EA::Interaction.contains_many_uni 'link', UML13EA::Link
+UML13EA::LinkEnd.has_one 'associationEnd', UML13EA::AssociationEnd, :lowerBound => 1
+UML13EA::LinkEnd.has_one 'participant', UML13EA::Instance, :lowerBound => 1
+UML13EA::BehavioralFeature.many_to_many 'raisedSignal', UML13EA::Signal, 'context'
+UML13EA::BehavioralFeature.contains_many_uni 'parameter', UML13EA::Parameter
+UML13EA::SubmachineState.has_one 'submachine', UML13EA::StateMachine, :lowerBound => 1
+UML13EA::Multiplicity.contains_many_uni 'range', UML13EA::MultiplicityRange, :lowerBound => 1
+UML13EA::ObjectFlowState.has_one 'type', UML13EA::Classifier, :lowerBound => 1
+UML13EA::ObjectFlowState.has_one 'available', UML13EA::Parameter, :lowerBound => 1
+UML13EA::AttributeLink.has_one 'value', UML13EA::Instance, :lowerBound => 1
+UML13EA::AttributeLink.has_one 'attribute', UML13EA::Attribute, :lowerBound => 1
+UML13EA::TimeEvent.contains_one_uni 'when', UML13EA::TimeExpression
+UML13EA::Abstraction.contains_one_uni 'mapping', UML13EA::MappingExpression
diff --git a/lib/puppet/vendor/rgen/lib/ea_support/uml13_ea_metamodel_ext.rb b/lib/puppet/vendor/rgen/lib/ea_support/uml13_ea_metamodel_ext.rb
new file mode 100644
index 000000000..6c338c6aa
--- /dev/null
+++ b/lib/puppet/vendor/rgen/lib/ea_support/uml13_ea_metamodel_ext.rb
@@ -0,0 +1,45 @@
+module UML13EA
+ class << self
+ attr_accessor :idStore
+ end
+ module ModelElement::ClassModule
+ def qualifiedName
+ _name = (respond_to?(:_name) ? self._name : name) || "unnamed"
+ _namespace = respond_to?(:_namespace) ? self._namespace : namespace
+ _namespace && _namespace.qualifiedName ? _namespace.qualifiedName+"::"+_name : _name
+ end
+ end
+ module XmiIdProvider::ClassModule
+ def _xmi_id
+ UML13EA.idStore.idHash[qualifiedName] ||= "EAID_"+object_id.to_s
+ end
+ end
+ module Package::ClassModule
+ def _xmi_id
+ UML13EA.idStore.idHash[qualifiedName] ||= "EAPK_"+object_id.to_s
+ end
+ end
+ module Generalization::ClassModule
+ def _name
+ "#{subtype.name}_#{supertype.name}"
+ end
+ end
+ module Association::ClassModule
+ def _name
+ connection.collect{|c| "#{c.getType.name}_#{c.name}"}.sort.join("_")
+ end
+ end
+ module AssociationEnd::ClassModule
+ def _name
+ "#{getType.name}_#{name}"
+ end
+ def _namespace
+ association
+ end
+ end
+ module StateVertex::ClassModule
+ def _namespace
+ container
+ end
+ end
+end
diff --git a/lib/puppet/vendor/rgen/lib/ea_support/uml13_ea_metamodel_generator.rb b/lib/puppet/vendor/rgen/lib/ea_support/uml13_ea_metamodel_generator.rb
new file mode 100644
index 000000000..725d601a3
--- /dev/null
+++ b/lib/puppet/vendor/rgen/lib/ea_support/uml13_ea_metamodel_generator.rb
@@ -0,0 +1,43 @@
+require 'metamodels/uml13_metamodel'
+require 'mmgen/metamodel_generator'
+require 'rgen/transformer'
+require 'rgen/environment'
+require 'rgen/ecore/ecore'
+
+include MMGen::MetamodelGenerator
+
+class ECoreCopyTransformer < RGen::Transformer
+ copy_all RGen::ECore
+end
+
+eaMMRoot = ECoreCopyTransformer.new.trans(UML13.ecore)
+
+eaMMRoot.name = "UML13EA"
+eaMMRoot.eClassifiers.find{|c| c.name == "ActivityGraph"}.name = "ActivityModel"
+eaMMRoot.eClassifiers.find{|c| c.name == "Pseudostate"}.name = "PseudoState"
+
+compositeState = eaMMRoot.eClassifiers.find{|c| c.name == "CompositeState"}
+compositeState.eReferences.find{|r| r.name == "subvertex"}.name = "substate"
+
+generalization = eaMMRoot.eClassifiers.find{|c| c.name == "Generalization"}
+generalization.eReferences.find{|r| r.name == "parent"}.name = "supertype"
+generalization.eReferences.find{|r| r.name == "child"}.name = "subtype"
+
+assocEnd = eaMMRoot.eClassifiers.find{|c| c.name == "AssociationEnd"}
+assocEnd.eAttributes.find{|r| r.name == "ordering"}.name = "isOrdered"
+assocEnd.eAttributes.find{|r| r.name == "changeability"}.name = "changeable"
+assocEnd.eAttributes.find{|r| r.name == "isOrdered"}.eType = RGen::ECore::EBoolean
+assocEnd.eAttributes.find{|r| r.name == "changeable"}.eType.eLiterals.find{|l| l.name == "frozen"}.name = "none"
+multRef = assocEnd.eStructuralFeatures.find{|f| f.name == "multiplicity"}
+multRef.eType = nil
+assocEnd.removeEStructuralFeatures(multRef)
+assocEnd.addEStructuralFeatures(RGen::ECore::EAttribute.new(:name => "multiplicity", :eType => RGen::ECore::EString))
+
+xmiIdProvider = RGen::ECore::EClass.new(:name => "XmiIdProvider", :ePackage => eaMMRoot)
+eaMMRoot.eClassifiers.each do |c|
+ if %w(Package Class Generalization Association AssociationEnd StateVertex).include?(c.name)
+ c.addESuperTypes(xmiIdProvider)
+ end
+end
+
+generateMetamodel(eaMMRoot, File.dirname(__FILE__)+"/uml13_ea_metamodel.rb")
diff --git a/lib/puppet/vendor/rgen/lib/ea_support/uml13_ea_to_uml13.rb b/lib/puppet/vendor/rgen/lib/ea_support/uml13_ea_to_uml13.rb
new file mode 100644
index 000000000..d857bc293
--- /dev/null
+++ b/lib/puppet/vendor/rgen/lib/ea_support/uml13_ea_to_uml13.rb
@@ -0,0 +1,103 @@
+require 'rgen/transformer'
+require 'metamodels/uml13_metamodel'
+require 'ea_support/uml13_ea_metamodel'
+
+class UML13EAToUML13 < RGen::Transformer
+ include UML13EA
+
+ def transform
+ trans(:class => Package)
+ trans(:class => Class)
+ @env_out.find(:class => UML13::Attribute).each do |me|
+ # remove tagged vales internally used by EA which have been converted to UML
+ me.taggedValue = me.taggedValue.reject{|tv| ["lowerBound", "upperBound"].include?(tv.tag)}
+ end
+ end
+
+ def cleanModel
+ @env_out.find(:class => UML13::ModelElement).each do |me|
+ me.taggedValue = []
+ end
+ end
+
+ copy_all UML13EA, :to => UML13, :except => %w(
+ XmiIdProvider
+ AssociationEnd AssociationEndRole
+ StructuralFeature
+ Attribute
+ Generalization
+ ActivityModel
+ CompositeState
+ PseudoState
+ Dependency
+ )
+
+ transform AssociationEndRole, :to => UML13::AssociationEndRole do
+ copyAssociationEnd
+ end
+
+ transform AssociationEnd, :to => UML13::AssociationEnd do
+ copyAssociationEnd
+ end
+
+ def copyAssociationEnd
+ copy_features :except => [:isOrdered, :changeable] do
+ {:ordering => isOrdered ? :ordered : :unordered,
+ :changeability => {:none => :frozen}[changeable] || changeable,
+ :aggregation => {:shared => :aggregate}[aggregation] || aggregation,
+ :multiplicity => UML13::Multiplicity.new(
+ :range => [UML13::MultiplicityRange.new(
+ :lower => multiplicity && multiplicity.split("..").first,
+ :upper => multiplicity && multiplicity.split("..").last)])}
+ end
+ end
+
+ transform StructuralFeature, :to => UML13::StructuralFeature,
+ :if => lambda{|c| !@current_object.is_a?(UML13EA::Attribute)} do
+ copy_features :except => [:changeable] do
+ {:changeability => {:none => :frozen}[changeable] }
+ end
+ end
+
+ transform StructuralFeature, :to => UML13::Attribute,
+ :if => lambda{|c| @current_object.is_a?(UML13EA::Attribute)} do
+ _lowerBound = taggedValue.find{|tv| tv.tag == "lowerBound"}
+ _upperBound = taggedValue.find{|tv| tv.tag == "upperBound"}
+ if _lowerBound || _upperBound
+ _multiplicity = UML13::Multiplicity.new(
+ :range => [UML13::MultiplicityRange.new(
+ :lower => (_lowerBound && _lowerBound.value) || "0",
+ :upper => (_upperBound && _upperBound.value) || "1"
+ )])
+ end
+ copy_features :except => [:changeable] do
+ {:changeability => {:none => :frozen}[changeable],
+ :multiplicity => _multiplicity }
+ end
+ end
+
+ transform Generalization, :to => UML13::Generalization do
+ copy_features :except => [:subtype, :supertype] do
+ { :child => trans(subtype),
+ :parent => trans(supertype) }
+ end
+ end
+
+ copy ActivityModel, :to => UML13::ActivityGraph
+
+ transform CompositeState, :to => UML13::CompositeState do
+ copy_features :except => [:substate] do
+ { :subvertex => trans(substate) }
+ end
+ end
+
+ copy PseudoState, :to => UML13::Pseudostate
+
+ transform Dependency, :to => UML13::Dependency do
+ _name_tag = taggedValue.find{|tv| tv.tag == "dst_name"}
+ copy_features do
+ { :name => (_name_tag && _name_tag.value) || "Anonymous" }
+ end
+ end
+
+end
diff --git a/lib/puppet/vendor/rgen/lib/ea_support/uml13_to_uml13_ea.rb b/lib/puppet/vendor/rgen/lib/ea_support/uml13_to_uml13_ea.rb
new file mode 100644
index 000000000..2d8dc7cf6
--- /dev/null
+++ b/lib/puppet/vendor/rgen/lib/ea_support/uml13_to_uml13_ea.rb
@@ -0,0 +1,89 @@
+require 'rgen/transformer'
+require 'metamodels/uml13_metamodel'
+require 'ea_support/uml13_ea_metamodel'
+require 'ea_support/uml13_ea_metamodel_ext'
+
+class UML13ToUML13EA < RGen::Transformer
+ include UML13
+
+ def transform
+ trans(:class => Package)
+ trans(:class => Class)
+ end
+
+ copy_all UML13, :to => UML13EA, :except => %w(
+ ActivityGraph
+ CompositeState SimpleState
+ Class
+ Association AssociationEnd AssociationEndRole
+ Generalization
+ Pseudostate
+ Attribute
+ )
+
+ copy ActivityGraph, :to => UML13EA::ActivityModel
+
+ copy Pseudostate, :to => UML13EA::PseudoState
+
+ transform CompositeState, :to => UML13EA::CompositeState do
+ copy_features :except => [:subvertex] do
+ { :substate => trans(subvertex) }
+ end
+ end
+
+ transform SimpleState, :to => UML13EA::SimpleState do
+ copy_features :except => [:container] do
+ { :taggedValue => trans(taggedValue) +
+ [@env_out.new(UML13EA::TaggedValue, :tag => "ea_stype", :value => "State")] +
+ (container ? [ @env_out.new(UML13EA::TaggedValue, :tag => "owner", :value => trans(container)._xmi_id)] : []) }
+ end
+ end
+
+ transform Class, :to => UML13EA::Class do
+ copy_features do
+ { :taggedValue => trans(taggedValue) + [@env_out.new(UML13EA::TaggedValue, :tag => "ea_stype", :value => "Class")]}
+ end
+ end
+
+ transform Association, :to => UML13EA::Association do
+ copy_features do
+ { :connection => trans(connection[1].isNavigable ? [connection[0], connection[1]] : [connection[1], connection[0]]),
+ :taggedValue => trans(taggedValue) + [
+ @env_out.new(UML13EA::TaggedValue, :tag => "ea_type", :value => "Association"),
+ @env_out.new(UML13EA::TaggedValue, :tag => "direction", :value =>
+ connection.all?{|c| c.isNavigable} ? "Bi-Directional" : "Source -&gt; Destination")] }
+ end
+ end
+
+ transform AssociationEnd, :to => UML13EA::AssociationEnd do
+ copyAssociationEnd
+ end
+
+ transform AssociationEndRole, :to => UML13EA::AssociationEndRole do
+ copyAssociationEnd
+ end
+
+ def copyAssociationEnd
+ _lower = multiplicity && multiplicity.range.first.lower
+ _upper = multiplicity && multiplicity.range.first.upper
+ copy_features :except => [:multiplicity, :ordering, :changeability] do
+ { :multiplicity => _lower == _upper ? _lower : "#{_lower}..#{_upper}",
+ :isOrdered => ordering == :ordered,
+ :changeable => :none } #{:frozen => :none}[changeability] || changeability}
+ end
+ end
+
+ transform Attribute, :to => UML13EA::Attribute do
+ copy_features :except => [:changeability] do
+ { :changeable => {:frozen => :none}[changeability] }
+ end
+ end
+
+ transform Generalization, :to => UML13EA::Generalization do
+ copy_features :except => [:child, :parent] do
+ { :taggedValue => trans(taggedValue) + [@env_out.new(UML13EA::TaggedValue, :tag => "ea_type", :value => "Generalization")],
+ :subtype => trans(child),
+ :supertype => trans(parent)}
+ end
+ end
+end
diff --git a/lib/puppet/vendor/rgen/lib/metamodels/uml13_metamodel.rb b/lib/puppet/vendor/rgen/lib/metamodels/uml13_metamodel.rb
new file mode 100644
index 000000000..01157df96
--- /dev/null
+++ b/lib/puppet/vendor/rgen/lib/metamodels/uml13_metamodel.rb
@@ -0,0 +1,559 @@
+require 'rgen/metamodel_builder'
+
+module UML13
+ extend RGen::MetamodelBuilder::ModuleExtension
+ include RGen::MetamodelBuilder::DataTypes
+
+ AggregationKind = Enum.new(:name => "AggregationKind", :literals =>[ :none, :aggregate, :composite ])
+ ChangeableKind = Enum.new(:name => "ChangeableKind", :literals =>[ :changeable, :frozen, :addOnly ])
+ OperationDirectionKind = Enum.new(:name => "OperationDirectionKind", :literals =>[ ])
+ ParameterDirectionKind = Enum.new(:name => "ParameterDirectionKind", :literals =>[ :in, :inout, :out, :return ])
+ MessageDirectionKind = Enum.new(:name => "MessageDirectionKind", :literals =>[ ])
+ ScopeKind = Enum.new(:name => "ScopeKind", :literals =>[ :instance, :classifier ])
+ VisibilityKind = Enum.new(:name => "VisibilityKind", :literals =>[ :public, :protected, :private ])
+ PseudostateKind = Enum.new(:name => "PseudostateKind", :literals =>[ :initial, :deepHistory, :shallowHistory, :join, :fork, :branch, :junction, :final ])
+ CallConcurrencyKind = Enum.new(:name => "CallConcurrencyKind", :literals =>[ :sequential, :guarded, :concurrent ])
+ OrderingKind = Enum.new(:name => "OrderingKind", :literals =>[ :unordered, :ordered, :sorted ])
+
+ class Element < RGen::MetamodelBuilder::MMBase
+ end
+
+ class ModelElement < Element
+ has_attr 'name', String
+ has_attr 'visibility', UML13::VisibilityKind, :defaultValueLiteral => "public"
+ has_attr 'isSpecification', Boolean
+ end
+
+ class Namespace < ModelElement
+ end
+
+ class GeneralizableElement < ModelElement
+ has_attr 'isRoot', Boolean
+ has_attr 'isLeaf', Boolean
+ has_attr 'isAbstract', Boolean
+ end
+
+ class Classifier < RGen::MetamodelBuilder::MMMultiple(GeneralizableElement, Namespace)
+ end
+
+ class Class < Classifier
+ has_attr 'isActive', Boolean
+ end
+
+ class DataType < Classifier
+ end
+
+ class Feature < ModelElement
+ has_attr 'ownerScope', UML13::ScopeKind, :defaultValueLiteral => "instance"
+ end
+
+ class StructuralFeature < Feature
+ has_attr 'changeability', UML13::ChangeableKind, :defaultValueLiteral => "changeable"
+ has_attr 'targetScope', UML13::ScopeKind, :defaultValueLiteral => "instance"
+ end
+
+ class AssociationEnd < ModelElement
+ has_attr 'isNavigable', Boolean, :defaultValueLiteral => "false"
+ has_attr 'ordering', UML13::OrderingKind, :defaultValueLiteral => "unordered"
+ has_attr 'aggregation', UML13::AggregationKind, :defaultValueLiteral => "none"
+ has_attr 'targetScope', UML13::ScopeKind, :defaultValueLiteral => "instance"
+ has_attr 'changeability', UML13::ChangeableKind, :defaultValueLiteral => "changeable"
+ end
+
+ class Interface < Classifier
+ end
+
+ class Constraint < ModelElement
+ end
+
+ class Relationship < ModelElement
+ end
+
+ class Association < RGen::MetamodelBuilder::MMMultiple(GeneralizableElement, Relationship)
+ end
+
+ class Attribute < StructuralFeature
+ end
+
+ class BehavioralFeature < Feature
+ has_attr 'isQuery', Boolean
+ end
+
+ class Operation < BehavioralFeature
+ has_attr 'concurrency', UML13::CallConcurrencyKind, :defaultValueLiteral => "sequential"
+ has_attr 'isRoot', Boolean
+ has_attr 'isLeaf', Boolean
+ has_attr 'isAbstract', Boolean
+ end
+
+ class Parameter < ModelElement
+ has_attr 'kind', UML13::ParameterDirectionKind, :defaultValueLiteral => "inout"
+ end
+
+ class Method < BehavioralFeature
+ end
+
+ class Generalization < Relationship
+ has_attr 'discriminator', String
+ end
+
+ class AssociationClass < RGen::MetamodelBuilder::MMMultiple(Class, Association)
+ end
+
+ class Dependency < Relationship
+ end
+
+ class Abstraction < Dependency
+ end
+
+ class PresentationElement < Element
+ end
+
+ class Usage < Dependency
+ end
+
+ class Binding < Dependency
+ end
+
+ class Component < Classifier
+ end
+
+ class Node < Classifier
+ end
+
+ class Permission < Dependency
+ end
+
+ class Comment < ModelElement
+ has_attr 'body', String
+ end
+
+ class Flow < Relationship
+ end
+
+ class TemplateParameter < RGen::MetamodelBuilder::MMBase
+ end
+
+ class ElementResidence < RGen::MetamodelBuilder::MMBase
+ has_attr 'visibility', UML13::VisibilityKind, :defaultValueLiteral => "public"
+ end
+
+ class Multiplicity < RGen::MetamodelBuilder::MMBase
+ end
+
+ class Expression < RGen::MetamodelBuilder::MMBase
+ has_attr 'language', String
+ has_attr 'body', String
+ end
+
+ class ObjectSetExpression < Expression
+ end
+
+ class TimeExpression < Expression
+ end
+
+ class BooleanExpression < Expression
+ end
+
+ class ActionExpression < Expression
+ end
+
+ class MultiplicityRange < RGen::MetamodelBuilder::MMBase
+ has_attr 'lower', String
+ has_attr 'upper', String
+ end
+
+ class Structure < DataType
+ end
+
+ class Primitive < DataType
+ end
+
+ class Enumeration < DataType
+ end
+
+ class EnumerationLiteral < RGen::MetamodelBuilder::MMBase
+ has_attr 'name', String
+ end
+
+ class ProgrammingLanguageType < DataType
+ end
+
+ class IterationExpression < Expression
+ end
+
+ class TypeExpression < Expression
+ end
+
+ class ArgListsExpression < Expression
+ end
+
+ class MappingExpression < Expression
+ end
+
+ class ProcedureExpression < Expression
+ end
+
+ class Stereotype < GeneralizableElement
+ has_attr 'icon', String
+ has_attr 'baseClass', String
+ end
+
+ class TaggedValue < Element
+ has_attr 'tag', String
+ has_attr 'value', String
+ end
+
+ class UseCase < Classifier
+ end
+
+ class Actor < Classifier
+ end
+
+ class Instance < ModelElement
+ end
+
+ class UseCaseInstance < Instance
+ end
+
+ class Extend < Relationship
+ end
+
+ class Include < Relationship
+ end
+
+ class ExtensionPoint < ModelElement
+ has_attr 'location', String
+ end
+
+ class StateMachine < ModelElement
+ end
+
+ class Event < ModelElement
+ end
+
+ class StateVertex < ModelElement
+ end
+
+ class State < StateVertex
+ end
+
+ class TimeEvent < Event
+ end
+
+ class CallEvent < Event
+ end
+
+ class SignalEvent < Event
+ end
+
+ class Transition < ModelElement
+ end
+
+ class CompositeState < State
+ has_attr 'isConcurrent', Boolean
+ end
+
+ class ChangeEvent < Event
+ end
+
+ class Guard < ModelElement
+ end
+
+ class Pseudostate < StateVertex
+ has_attr 'kind', UML13::PseudostateKind, :defaultValueLiteral => "initial"
+ end
+
+ class SimpleState < State
+ end
+
+ class SubmachineState < CompositeState
+ end
+
+ class SynchState < StateVertex
+ has_attr 'bound', Integer
+ end
+
+ class StubState < StateVertex
+ has_attr 'referenceState', String
+ end
+
+ class FinalState < State
+ end
+
+ class Collaboration < RGen::MetamodelBuilder::MMMultiple(GeneralizableElement, Namespace)
+ end
+
+ class ClassifierRole < Classifier
+ end
+
+ class AssociationRole < Association
+ end
+
+ class AssociationEndRole < AssociationEnd
+ end
+
+ class Message < ModelElement
+ end
+
+ class Interaction < ModelElement
+ end
+
+ class Signal < Classifier
+ end
+
+ class Action < ModelElement
+ has_attr 'isAsynchronous', Boolean
+ end
+
+ class CreateAction < Action
+ end
+
+ class DestroyAction < Action
+ end
+
+ class UninterpretedAction < Action
+ end
+
+ class AttributeLink < ModelElement
+ end
+
+ class Object < Instance
+ end
+
+ class Link < ModelElement
+ end
+
+ class LinkObject < RGen::MetamodelBuilder::MMMultiple(Object, Link)
+ end
+
+ class DataValue < Instance
+ end
+
+ class CallAction < Action
+ end
+
+ class SendAction < Action
+ end
+
+ class ActionSequence < Action
+ end
+
+ class Argument < ModelElement
+ end
+
+ class Reception < BehavioralFeature
+ has_attr 'isPolymorphic', Boolean
+ has_attr 'specification', String
+ end
+
+ class LinkEnd < ModelElement
+ end
+
+ class Call < RGen::MetamodelBuilder::MMBase
+ end
+
+ class ReturnAction < Action
+ end
+
+ class TerminateAction < Action
+ end
+
+ class Stimulus < ModelElement
+ end
+
+ class ActionInstance < RGen::MetamodelBuilder::MMBase
+ end
+
+ class Exception < Signal
+ end
+
+ class AssignmentAction < Action
+ end
+
+ class ComponentInstance < Instance
+ end
+
+ class NodeInstance < Instance
+ end
+
+ class ActivityGraph < StateMachine
+ end
+
+ class Partition < ModelElement
+ end
+
+ class SubactivityState < SubmachineState
+ has_attr 'isDynamic', Boolean
+ end
+
+ class ActionState < SimpleState
+ has_attr 'isDynamic', Boolean
+ end
+
+ class CallState < ActionState
+ end
+
+ class ObjectFlowState < SimpleState
+ has_attr 'isSynch', Boolean
+ end
+
+ class ClassifierInState < Classifier
+ end
+
+ class Package < RGen::MetamodelBuilder::MMMultiple(GeneralizableElement, Namespace)
+ end
+
+ class Model < Package
+ end
+
+ class Subsystem < RGen::MetamodelBuilder::MMMultiple(Classifier, Package)
+ has_attr 'isInstantiable', Boolean
+ end
+
+ class ElementImport < RGen::MetamodelBuilder::MMBase
+ has_attr 'visibility', UML13::VisibilityKind, :defaultValueLiteral => "public"
+ has_attr 'alias', String
+ end
+
+ class DiagramElement < PresentationElement
+ has_attr 'geometry', String
+ has_attr 'style', String
+ end
+
+ class Diagram < PresentationElement
+ has_attr 'name', String
+ has_attr 'toolName', String
+ has_attr 'diagramType', String
+ has_attr 'style', String
+ end
+
+end
+
+UML13::Classifier.many_to_many 'participant', UML13::AssociationEnd, 'specification'
+UML13::Classifier.one_to_many 'associationEnd', UML13::AssociationEnd, 'type'
+UML13::Classifier.contains_many 'feature', UML13::Feature, 'owner'
+UML13::StructuralFeature.contains_one_uni 'multiplicity', UML13::Multiplicity
+UML13::StructuralFeature.has_one 'type', UML13::Classifier, :lowerBound => 1
+UML13::Namespace.contains_many 'ownedElement', UML13::ModelElement, 'namespace'
+UML13::AssociationEnd.contains_one_uni 'multiplicity', UML13::Multiplicity
+UML13::AssociationEnd.contains_many 'qualifier', UML13::Attribute, 'associationEnd'
+UML13::Association.contains_many 'connection', UML13::AssociationEnd, 'association', :lowerBound => 2
+UML13::Constraint.contains_one_uni 'body', UML13::BooleanExpression
+UML13::Constraint.many_to_many 'constrainedElement', UML13::ModelElement, 'constraint', :lowerBound => 1
+UML13::GeneralizableElement.one_to_many 'specialization', UML13::Generalization, 'parent'
+UML13::GeneralizableElement.one_to_many 'generalization', UML13::Generalization, 'child'
+UML13::Attribute.contains_one_uni 'initialValue', UML13::Expression
+UML13::Operation.one_to_many 'occurrence', UML13::CallEvent, 'operation'
+UML13::Operation.one_to_many 'method', UML13::Method, 'specification'
+UML13::Parameter.contains_one_uni 'defaultValue', UML13::Expression
+UML13::Parameter.many_to_many 'state', UML13::ObjectFlowState, 'parameter'
+UML13::Parameter.has_one 'type', UML13::Classifier, :lowerBound => 1
+UML13::Method.contains_one_uni 'body', UML13::ProcedureExpression
+UML13::BehavioralFeature.many_to_many 'raisedSignal', UML13::Signal, 'context'
+UML13::BehavioralFeature.contains_many_uni 'parameter', UML13::Parameter
+UML13::ModelElement.one_to_many 'behavior', UML13::StateMachine, 'context'
+UML13::ModelElement.many_to_one 'stereotype', UML13::Stereotype, 'extendedElement'
+UML13::ModelElement.one_to_many 'elementResidence', UML13::ElementResidence, 'resident'
+UML13::ModelElement.many_to_many 'sourceFlow', UML13::Flow, 'source'
+UML13::ModelElement.many_to_many 'targetFlow', UML13::Flow, 'target'
+UML13::ModelElement.many_to_many 'presentation', UML13::PresentationElement, 'subject'
+UML13::ModelElement.many_to_many 'supplierDependency', UML13::Dependency, 'supplier', :lowerBound => 1
+UML13::ModelElement.contains_many 'taggedValue', UML13::TaggedValue, 'modelElement'
+UML13::ModelElement.contains_many_uni 'templateParameter', UML13::TemplateParameter
+UML13::ModelElement.many_to_many 'clientDependency', UML13::Dependency, 'client', :lowerBound => 1
+UML13::ModelElement.many_to_many 'comment', UML13::Comment, 'annotatedElement'
+UML13::ModelElement.one_to_many 'elementImport', UML13::ElementImport, 'modelElement'
+UML13::Abstraction.contains_one_uni 'mapping', UML13::MappingExpression
+UML13::Binding.has_many 'argument', UML13::ModelElement, :lowerBound => 1
+UML13::Component.contains_many 'residentElement', UML13::ElementResidence, 'implementationLocation'
+UML13::Component.many_to_many 'deploymentLocation', UML13::Node, 'resident'
+UML13::TemplateParameter.has_one 'modelElement', UML13::ModelElement
+UML13::TemplateParameter.has_one 'defaultElement', UML13::ModelElement
+UML13::Multiplicity.contains_many_uni 'range', UML13::MultiplicityRange, :lowerBound => 1
+UML13::Enumeration.contains_many_uni 'literal', UML13::EnumerationLiteral, :lowerBound => 1
+UML13::ProgrammingLanguageType.contains_one_uni 'type', UML13::TypeExpression
+UML13::Stereotype.has_many 'requiredTag', UML13::TaggedValue
+UML13::UseCase.has_many 'extensionPoint', UML13::ExtensionPoint
+UML13::UseCase.one_to_many 'include', UML13::Include, 'base'
+UML13::UseCase.one_to_many 'extend', UML13::Extend, 'extension'
+UML13::Extend.contains_one_uni 'condition', UML13::BooleanExpression
+UML13::Extend.has_many 'extensionPoint', UML13::ExtensionPoint, :lowerBound => 1
+UML13::Extend.has_one 'base', UML13::UseCase, :lowerBound => 1
+UML13::Include.has_one 'addition', UML13::UseCase, :lowerBound => 1
+UML13::StateMachine.contains_many_uni 'transitions', UML13::Transition
+UML13::StateMachine.contains_one_uni 'top', UML13::State, :lowerBound => 1
+UML13::Event.contains_many_uni 'parameters', UML13::Parameter
+UML13::State.contains_one_uni 'doActivity', UML13::Action
+UML13::State.contains_many_uni 'internalTransition', UML13::Transition
+UML13::State.has_many 'deferrableEvent', UML13::Event
+UML13::State.contains_one_uni 'exit', UML13::Action
+UML13::State.contains_one_uni 'entry', UML13::Action
+UML13::TimeEvent.contains_one_uni 'when', UML13::TimeExpression
+UML13::SignalEvent.many_to_one 'signal', UML13::Signal, 'occurrence', :lowerBound => 1
+UML13::Transition.many_to_one 'target', UML13::StateVertex, 'incoming', :lowerBound => 1
+UML13::Transition.many_to_one 'source', UML13::StateVertex, 'outgoing', :lowerBound => 1
+UML13::Transition.has_one 'trigger', UML13::Event
+UML13::Transition.contains_one_uni 'effect', UML13::Action
+UML13::Transition.contains_one_uni 'guard', UML13::Guard
+UML13::CompositeState.contains_many 'subvertex', UML13::StateVertex, 'container', :lowerBound => 1
+UML13::ChangeEvent.contains_one_uni 'changeExpression', UML13::BooleanExpression
+UML13::Guard.contains_one_uni 'expression', UML13::BooleanExpression
+UML13::SubmachineState.has_one 'submachine', UML13::StateMachine, :lowerBound => 1
+UML13::Collaboration.has_one 'representedOperation', UML13::Operation
+UML13::Collaboration.has_one 'representedClassifier', UML13::Classifier
+UML13::Collaboration.has_many 'constrainingElement', UML13::ModelElement
+UML13::Collaboration.contains_many 'interaction', UML13::Interaction, 'context'
+UML13::ClassifierRole.contains_one_uni 'multiplicity', UML13::Multiplicity
+UML13::ClassifierRole.has_many 'availableContents', UML13::ModelElement
+UML13::ClassifierRole.has_many 'availableFeature', UML13::Feature
+UML13::ClassifierRole.has_one 'base', UML13::Classifier, :lowerBound => 1
+UML13::AssociationRole.contains_one_uni 'multiplicity', UML13::Multiplicity
+UML13::AssociationRole.has_one 'base', UML13::Association
+UML13::AssociationEndRole.has_many 'availableQualifier', UML13::Attribute
+UML13::AssociationEndRole.has_one 'base', UML13::AssociationEnd
+UML13::Message.has_one 'action', UML13::Action, :lowerBound => 1
+UML13::Message.has_one 'communicationConnection', UML13::AssociationRole
+UML13::Message.has_many 'predecessor', UML13::Message
+UML13::Message.has_one 'receiver', UML13::ClassifierRole, :lowerBound => 1
+UML13::Message.has_one 'sender', UML13::ClassifierRole, :lowerBound => 1
+UML13::Message.has_one 'activator', UML13::Message
+UML13::Interaction.contains_many 'message', UML13::Message, 'interaction', :lowerBound => 1
+UML13::Interaction.contains_many_uni 'link', UML13::Link
+UML13::Instance.contains_many_uni 'slot', UML13::AttributeLink
+UML13::Instance.one_to_many 'linkEnd', UML13::LinkEnd, 'instance'
+UML13::Instance.has_many 'classifier', UML13::Classifier, :lowerBound => 1
+UML13::Signal.one_to_many 'reception', UML13::Reception, 'signal'
+UML13::CreateAction.has_one 'instantiation', UML13::Classifier, :lowerBound => 1
+UML13::Action.contains_one_uni 'recurrence', UML13::IterationExpression
+UML13::Action.contains_one_uni 'target', UML13::ObjectSetExpression
+UML13::Action.contains_one_uni 'script', UML13::ActionExpression
+UML13::Action.contains_many_uni 'actualArgument', UML13::Argument
+UML13::AttributeLink.has_one 'value', UML13::Instance, :lowerBound => 1
+UML13::AttributeLink.has_one 'attribute', UML13::Attribute, :lowerBound => 1
+UML13::CallAction.has_one 'operation', UML13::Operation, :lowerBound => 1
+UML13::SendAction.has_one 'signal', UML13::Signal, :lowerBound => 1
+UML13::ActionSequence.contains_many_uni 'action', UML13::Action
+UML13::Argument.contains_one_uni 'value', UML13::Expression
+UML13::Link.contains_many_uni 'connection', UML13::LinkEnd, :lowerBound => 2
+UML13::Link.has_one 'association', UML13::Association, :lowerBound => 1
+UML13::LinkEnd.has_one 'associationEnd', UML13::AssociationEnd, :lowerBound => 1
+UML13::LinkEnd.has_one 'participant', UML13::Instance, :lowerBound => 1
+UML13::Stimulus.has_one 'dispatchAction', UML13::Action, :lowerBound => 1
+UML13::Stimulus.has_one 'communicationLink', UML13::Link
+UML13::Stimulus.has_one 'receiver', UML13::Instance, :lowerBound => 1
+UML13::Stimulus.has_one 'sender', UML13::Instance, :lowerBound => 1
+UML13::Stimulus.has_many 'argument', UML13::Instance
+UML13::ComponentInstance.has_many 'resident', UML13::Instance
+UML13::NodeInstance.has_many 'resident', UML13::ComponentInstance
+UML13::ActivityGraph.contains_many_uni 'partition', UML13::Partition
+UML13::Partition.has_many 'contents', UML13::ModelElement
+UML13::SubactivityState.contains_one_uni 'dynamicArguments', UML13::ArgListsExpression
+UML13::ObjectFlowState.has_one 'type', UML13::Classifier, :lowerBound => 1
+UML13::ObjectFlowState.has_one 'available', UML13::Parameter, :lowerBound => 1
+UML13::ClassifierInState.has_one 'type', UML13::Classifier, :lowerBound => 1
+UML13::ClassifierInState.has_many 'inState', UML13::State
+UML13::ActionState.contains_one_uni 'dynamicArguments', UML13::ArgListsExpression
+UML13::Package.contains_many 'importedElement', UML13::ElementImport, 'package'
+UML13::Diagram.contains_many 'element', UML13::DiagramElement, 'diagram'
+UML13::Diagram.has_one 'owner', UML13::ModelElement, :lowerBound => 1
diff --git a/lib/puppet/vendor/rgen/lib/metamodels/uml13_metamodel_ext.rb b/lib/puppet/vendor/rgen/lib/metamodels/uml13_metamodel_ext.rb
new file mode 100644
index 000000000..bc7eb7178
--- /dev/null
+++ b/lib/puppet/vendor/rgen/lib/metamodels/uml13_metamodel_ext.rb
@@ -0,0 +1,26 @@
+# Some handy extensions to the UML13 metamodel
+#
+module UML13
+
+ module AssociationEnd::ClassModule
+ def otherEnd
+ association.connection.find{|c| c != self}
+ end
+ end
+
+ module Classifier::ClassModule
+ def localCompositeEnd
+ associationEnd.select{|e| e.aggregation == :composite}
+ end
+ def remoteCompositeEnd
+ associationEnd.otherEnd.select{|e| e.aggregation == :composite}
+ end
+ def localNavigableEnd
+ associationEnd.select{|e| e.isNavigable}
+ end
+ def remoteNavigableEnd
+ associationEnd.otherEnd.select{|e| e.isNavigable}
+ end
+ end
+
+end
diff --git a/lib/puppet/vendor/rgen/lib/mmgen/metamodel_generator.rb b/lib/puppet/vendor/rgen/lib/mmgen/metamodel_generator.rb
new file mode 100644
index 000000000..9ea9fe608
--- /dev/null
+++ b/lib/puppet/vendor/rgen/lib/mmgen/metamodel_generator.rb
@@ -0,0 +1,20 @@
+require 'rgen/environment'
+require 'rgen/template_language'
+require 'rgen/ecore/ecore'
+require 'rgen/ecore/ecore_ext'
+require 'mmgen/mm_ext/ecore_mmgen_ext'
+
+module MMGen
+
+module MetamodelGenerator
+
+ def generateMetamodel(rootPackage, out_file)
+ tc = RGen::TemplateLanguage::DirectoryTemplateContainer.new(RGen::ECore, File.dirname(out_file))
+ tpl_path = File.dirname(__FILE__) + '/templates'
+ tc.load(tpl_path)
+ tc.expand('metamodel_generator::GenerateMetamodel', File.basename(out_file), :for => rootPackage)
+ end
+
+end
+
+end \ No newline at end of file
diff --git a/lib/puppet/vendor/rgen/lib/mmgen/mm_ext/ecore_mmgen_ext.rb b/lib/puppet/vendor/rgen/lib/mmgen/mm_ext/ecore_mmgen_ext.rb
new file mode 100644
index 000000000..17dd81c69
--- /dev/null
+++ b/lib/puppet/vendor/rgen/lib/mmgen/mm_ext/ecore_mmgen_ext.rb
@@ -0,0 +1,91 @@
+require 'rgen/util/name_helper'
+
+module RGen
+
+ module ECore
+
+ module EPackage::ClassModule
+ include RGen::Util::NameHelper
+
+ def moduleName
+ firstToUpper(name)
+ end
+
+ def qualifiedModuleName(rootPackage)
+ return moduleName unless eSuperPackage and self != rootPackage
+ eSuperPackage.qualifiedModuleName(rootPackage) + "::" + moduleName
+ end
+
+ def ancestorPackages
+ return [] unless eSuperPackage
+ [eSuperPackage] + eSuperPackage.ancestorPackages
+ end
+
+ def ownClasses
+ eClassifiers.select{|c| c.is_a?(EClass)}
+ end
+
+ def classesInGenerationOrdering
+ ownClasses + eSubpackages.collect{|s| s.classesInGenerationOrdering}.flatten
+ end
+
+ def needClassReorder?
+ classesInGenerationOrdering != inheritanceOrderClasses(classesInGenerationOrdering)
+ end
+
+ def allClassesSorted
+ inheritanceOrderClasses(classesInGenerationOrdering)
+ end
+
+ def inheritanceOrderClasses(cls)
+ sortArray = cls.dup
+ i1 = 0
+ while i1 < sortArray.size-1
+ again = false
+ for i2 in i1+1..sortArray.size-1
+ e2 = sortArray[i2]
+ if sortArray[i1].eSuperTypes.include?(e2)
+ sortArray.delete(e2)
+ sortArray.insert(i1,e2)
+ again = true
+ break
+ end
+ end
+ i1 += 1 unless again
+ end
+ sortArray
+ end
+ end
+
+ module EClassifier::ClassModule
+ include RGen::Util::NameHelper
+ def classifierName
+ firstToUpper(name)
+ end
+ def qualifiedClassifierName(rootPackage)
+ (ePackage ? ePackage.qualifiedModuleName(rootPackage) + "::" : "") + classifierName
+ end
+ def ancestorPackages
+ return [] unless ePackage
+ [ePackage] + ePackage.ancestorPackages
+ end
+ def qualifiedClassifierNameIfRequired(package)
+ if ePackage != package
+ commonSuper = (package.ancestorPackages & ancestorPackages).first
+ qualifiedClassifierName(commonSuper)
+ else
+ classifierName
+ end
+ end
+ end
+
+ module EAttribute::ClassModule
+ def RubyType
+ typeMap = {'float' => 'Float', 'int' => 'Integer'}
+ (self.getType && typeMap[self.getType.downcase]) || 'String'
+ end
+ end
+
+ end
+
+end
diff --git a/lib/puppet/vendor/rgen/lib/mmgen/mmgen.rb b/lib/puppet/vendor/rgen/lib/mmgen/mmgen.rb
new file mode 100644
index 000000000..41a696b06
--- /dev/null
+++ b/lib/puppet/vendor/rgen/lib/mmgen/mmgen.rb
@@ -0,0 +1,28 @@
+$:.unshift File.join(File.dirname(__FILE__),"..")
+
+require 'ea/xmi_ecore_instantiator'
+require 'mmgen/metamodel_generator'
+
+include MMGen::MetamodelGenerator
+
+unless ARGV.length >= 2
+ puts "Usage: mmgen.rb <xmi_class_model_file> <root package> (<module>)*"
+ exit
+else
+ file_name = ARGV.shift
+ root_package_name = ARGV.shift
+ modules = ARGV
+ out_file = file_name.gsub(/\.\w+$/,'.rb')
+ puts out_file
+end
+
+env = RGen::Environment.new
+File.open(file_name) { |f|
+ puts "instantiating ..."
+ XMIECoreInstantiator.new.instantiateECoreModel(env, f.read)
+}
+
+rootPackage = env.find(:class => RGen::ECore::EPackage, :name => root_package_name).first
+
+puts "generating ..."
+generateMetamodel(rootPackage, out_file, modules)
diff --git a/lib/puppet/vendor/rgen/lib/mmgen/templates/annotations.tpl b/lib/puppet/vendor/rgen/lib/mmgen/templates/annotations.tpl
new file mode 100644
index 000000000..b9f5d1bfd
--- /dev/null
+++ b/lib/puppet/vendor/rgen/lib/mmgen/templates/annotations.tpl
@@ -0,0 +1,37 @@
+<% define 'Annotations', :for => EPackage do %>
+ <% for a in eAnnotations %>
+ annotation <% expand 'AnnotationArgs', :for => a %>
+ <% end %>
+<% end %>
+
+<% define 'Annotations', :for => EClass do %>
+ <% for a in eAnnotations %>
+ annotation <% expand 'AnnotationArgs', :for => a %>
+ <% end %>
+<% end %>
+
+<% define 'Annotations', :for => EStructuralFeature do %>
+ <% oppositeAnnotations = (this.respond_to?(:eOpposite) && eOpposite && eOpposite.eAnnotations) || [] %>
+ <% if eAnnotations.size > 0 || oppositeAnnotations.size > 0 %>
+ do<%iinc%>
+ <% for a in eAnnotations %>
+ annotation <% expand 'AnnotationArgs', :for => a %>
+ <% end %>
+ <% for a in oppositeAnnotations %>
+ opposite_annotation <% expand 'AnnotationArgs', :for => a %>
+ <% end %><%idec%>
+ end<%nows%>
+ <% end %>
+<% end %>
+
+<% define 'AnnotationArgs', :for => EAnnotation do %>
+ <% if source.nil? %>
+ <% expand 'Details' %>
+ <% else %>
+ :source => "<%= source.to_s %>", :details => {<% expand 'Details' %>}<%nows%>
+ <% end %>
+<% end %>
+
+<% define 'Details', :for => EAnnotation do %>
+ <%= details.sort{|a,b| a.key<=>b.key}.collect{ |d| "\'" + d.key + "\' => \'"+ (d.value || "").gsub('\'','\\\'').to_s + "\'"}.join(', ') %><%nows%>
+<% end %> \ No newline at end of file
diff --git a/lib/puppet/vendor/rgen/lib/mmgen/templates/metamodel_generator.tpl b/lib/puppet/vendor/rgen/lib/mmgen/templates/metamodel_generator.tpl
new file mode 100644
index 000000000..5e6877a74
--- /dev/null
+++ b/lib/puppet/vendor/rgen/lib/mmgen/templates/metamodel_generator.tpl
@@ -0,0 +1,172 @@
+
+<% define 'GenerateMetamodel', :for => EPackage do |filename| %>
+ <% file filename do %>
+ require 'rgen/metamodel_builder'
+ <%nl%>
+ <% if needClassReorder? %>
+ <% expand 'GeneratePackagesOnly' %>
+ <% expand 'GenerateClassesReordered' %>
+ <% else %>
+ <% expand 'GeneratePackage' %>
+ <% end %>
+ <%nl%>
+ <% expand 'GenerateAssocs' %>
+ <% end %>
+<% end %>
+
+<% define 'GeneratePackage', :for => EPackage do %>
+ module <%= moduleName %><% iinc %>
+ extend RGen::MetamodelBuilder::ModuleExtension
+ include RGen::MetamodelBuilder::DataTypes
+ <% expand 'annotations::Annotations' %>
+ <%nl%>
+ <% expand 'EnumTypes' %>
+ <% for c in ownClasses %><%nl%>
+ <% expand 'ClassHeader', this, :for => c %><%iinc%>
+ <% if c.abstract %>abstract<% end %>
+ <% expand 'annotations::Annotations', :for => c %>
+ <% expand 'Attribute', this, :foreach => c.eAttributes %>
+ <%idec%>
+ end
+ <% end %><%nl%>
+ <% for p in eSubpackages %>
+ <%nl%><% expand 'GeneratePackage', :for => p %>
+ <% end %><%idec%>
+ end
+<% end %>
+
+<% define 'GenerateClassesReordered', :for => EPackage do %>
+ <% for c in allClassesSorted %><%nl%>
+ <% expand 'ClassHeaderFullyQualified', this, :for => c %><%iinc%>
+ <% if c.abstract %>abstract<% end %>
+ <% expand 'annotations::Annotations', :for => c %>
+ <% expand 'Attribute', this, :foreach => c.eAttributes %>
+ <%idec%>
+ end
+ <% end %><%nl%>
+<% end %>
+
+<% define 'GeneratePackagesOnly', :for => EPackage do %>
+ module <%= moduleName %><% iinc %>
+ extend RGen::MetamodelBuilder::ModuleExtension
+ include RGen::MetamodelBuilder::DataTypes
+ <% expand 'annotations::Annotations' %>
+ <%nl%>
+ <% expand 'EnumTypes' %>
+ <% for p in eSubpackages %>
+ <%nl%><% expand 'GeneratePackagesOnly', :for => p %>
+ <% end %><%idec%>
+ end
+<% end %>
+
+<% define 'Attribute', :for => EAttribute do |rootp| %>
+ <% if upperBound == 1%>has_attr<% else %>has_many_attr<% end %> '<%= name %>', <%nows%>
+ <% if eType.is_a?(EEnum) %><%nows%>
+ <%= eType.qualifiedClassifierName(rootp) %><%nows%>
+ <% else %><%nows%>
+ <%= eType && eType.instanceClass.to_s %><%nows%>
+ <% end %><%nows%>
+ <% for p in RGen::MetamodelBuilder::Intermediate::Attribute.properties %>
+ <% unless p == :name || (p == :upperBound && (upperBound == 1 || upperBound == -1)) ||
+ RGen::MetamodelBuilder::Intermediate::Attribute.default_value(p) == getGeneric(p) %>
+ , :<%=p%> => <%nows%>
+ <% if getGeneric(p).is_a?(String) %>
+ "<%= getGeneric(p) %>"<%nows%>
+ <% elsif getGeneric(p).is_a?(Symbol) %>
+ :<%= getGeneric(p) %><%nows%>
+ <% else %>
+ <%= getGeneric(p) %><%nows%>
+ <% end %>
+ <% end %>
+ <% end %>
+ <%ws%><% expand 'annotations::Annotations' %><%nl%>
+<% end %>
+
+<% define 'EnumTypes', :for => EPackage do %>
+ <% for enum in eClassifiers.select{|c| c.is_a?(EEnum)} %>
+ <%= enum.name %> = Enum.new(:name => '<%= enum.name %>', :literals =>[ <%nows%>
+ <%= enum.eLiterals.collect { |lit| ":"+(lit.name =~ /^\d|\W/ ? "'"+lit.name+"'" : lit.name) }.join(', ') %> ])
+ <% end %>
+<% end %>
+
+<% define 'GenerateAssocs', :for => EPackage do %>
+ <% refDone = {} %>
+ <% for ref in eAllClassifiers.select{|c| c.is_a?(EClass)}.eReferences %>
+ <% if !refDone[ref] && ref.eOpposite %>
+ <% ref = ref.eOpposite if ref.eOpposite.containment %>
+ <% refDone[ref] = refDone[ref.eOpposite] = true %>
+ <% if !ref.many && !ref.eOpposite.many %>
+ <% if ref.containment %>
+ <% expand 'Reference', "contains_one", this, :for => ref %>
+ <% else %>
+ <% expand 'Reference', "one_to_one", this, :for => ref %>
+ <% end %>
+ <% elsif !ref.many && ref.eOpposite.many %>
+ <% expand 'Reference', "many_to_one", this, :for => ref %>
+ <% elsif ref.many && !ref.eOpposite.many %>
+ <% if ref.containment %>
+ <% expand 'Reference', "contains_many", this, :for => ref %>
+ <% else %>
+ <% expand 'Reference', "one_to_many", this, :for => ref %>
+ <% end %>
+ <% elsif ref.many && ref.eOpposite.many %>
+ <% expand 'Reference', "many_to_many", this, :for => ref %>
+ <% end %>
+ <% expand 'annotations::Annotations', :for => ref %><%nl%>
+ <% elsif !refDone[ref] %>
+ <% refDone[ref] = true %>
+ <% if !ref.many %>
+ <% if ref.containment %>
+ <% expand 'Reference', "contains_one_uni", this, :for => ref %>
+ <% else %>
+ <% expand 'Reference', "has_one", this, :for => ref %>
+ <% end %>
+ <% elsif ref.many %>
+ <% if ref.containment %>
+ <% expand 'Reference', "contains_many_uni", this, :for => ref %>
+ <% else %>
+ <% expand 'Reference', "has_many", this, :for => ref %>
+ <% end %>
+ <% end %>
+ <% expand 'annotations::Annotations', :for => ref %><%nl%>
+ <% end %>
+ <% end %>
+<% end %>
+
+<% define 'Reference', :for => EReference do |cmd, rootpackage| %>
+ <%= eContainingClass.qualifiedClassifierName(rootpackage) %>.<%= cmd %> '<%= name %>', <%= eType && eType.qualifiedClassifierName(rootpackage) %><%nows%>
+ <% if eOpposite %><%nows%>
+ , '<%= eOpposite.name%>'<%nows%>
+ <% end %><%nows%>
+ <% pset = RGen::MetamodelBuilder::Intermediate::Reference.properties.reject{|p| p == :name || p == :upperBound || p == :containment} %>
+ <% for p in pset.reject{|p| RGen::MetamodelBuilder::Intermediate::Reference.default_value(p) == getGeneric(p)} %>
+ , :<%=p%> => <%=getGeneric(p)%><%nows%>
+ <% end %>
+ <% if eOpposite %>
+ <% for p in pset.reject{|p| RGen::MetamodelBuilder::Intermediate::Reference.default_value(p) == eOpposite.getGeneric(p)} %>
+ , :opposite_<%=p%> => <%=eOpposite.getGeneric(p)%><%nows%>
+ <% end %>
+ <% end %><%ws%>
+<% end %>
+
+<% define 'ClassHeader', :for => EClass do |rootp| %>
+ class <%= classifierName %> < <% nows %>
+ <% if eSuperTypes.size > 1 %><% nows %>
+ RGen::MetamodelBuilder::MMMultiple(<%= eSuperTypes.collect{|t| t.qualifiedClassifierNameIfRequired(rootp)}.join(', ') %>)
+ <% elsif eSuperTypes.size > 0 %><% nows %>
+ <%= eSuperTypes.first.qualifiedClassifierNameIfRequired(rootp) %>
+ <% else %><% nows %>
+ RGen::MetamodelBuilder::MMBase
+ <% end %>
+<% end %>
+
+<% define 'ClassHeaderFullyQualified', :for => EClass do |rootp| %>
+ class <%= qualifiedClassifierName(rootp) %> < <% nows %>
+ <% if eSuperTypes.size > 1 %><% nows %>
+ RGen::MetamodelBuilder::MMMultiple(<%= eSuperTypes.collect{|t| t.qualifiedClassifierName(rootp)}.join(', ') %>)
+ <% elsif eSuperTypes.size > 0 %><% nows %>
+ <%= eSuperTypes.first.qualifiedClassifierName(rootp) %>
+ <% else %><% nows %>
+ RGen::MetamodelBuilder::MMBase
+ <% end %>
+<% end %>
diff --git a/lib/puppet/vendor/rgen/lib/rgen/array_extensions.rb b/lib/puppet/vendor/rgen/lib/rgen/array_extensions.rb
new file mode 100644
index 000000000..d1d7beda0
--- /dev/null
+++ b/lib/puppet/vendor/rgen/lib/rgen/array_extensions.rb
@@ -0,0 +1,45 @@
+# RGen Framework
+# (c) Martin Thiede, 2006
+
+require 'rgen/metamodel_builder'
+
+class Array
+
+ def >>(method)
+ compact.inject([]) { |r,e| r | ( (o=e.send(method)).is_a?(Array) ? o : [o] ) }
+ end
+
+ def method_missing(m, *args)
+
+ # This extensions has the side effect that it allows to call any method on any
+ # empty array with an empty array as the result. This behavior is required for
+ # navigating models.
+ #
+ # This is a problem for Hash[] called with an (empty) array of tupels.
+ # It will call to_hash expecting a Hash as the result. When it gets an array instead,
+ # it fails with an exception. Make sure it gets a NoMethodException as without this
+ # extension and it will catch that and return an empty hash as expected.
+ #
+ # Similar problems exist for other Ruby built-in methods which are expected to fail.
+ #
+ return super unless (size == 0 &&
+ m != :to_hash && m != :to_str) ||
+ compact.any?{|e| e.is_a? RGen::MetamodelBuilder::MMBase}
+ # use an array to build the result to achiev similar ordering
+ result = []
+ inResult = {}
+ compact.each do |e|
+ if e.is_a? RGen::MetamodelBuilder::MMBase
+ ((o=e.send(m)).is_a?(Array) ? o : [o] ).each do |v|
+ next if inResult[v.object_id]
+ inResult[v.object_id] = true
+ result << v
+ end
+ else
+ raise StandardError.new("Trying to call a method on an array element not a RGen MMBase")
+ end
+ end
+ result.compact
+ end
+
+end
diff --git a/lib/puppet/vendor/rgen/lib/rgen/ecore/ecore.rb b/lib/puppet/vendor/rgen/lib/rgen/ecore/ecore.rb
new file mode 100644
index 000000000..251f60733
--- /dev/null
+++ b/lib/puppet/vendor/rgen/lib/rgen/ecore/ecore.rb
@@ -0,0 +1,218 @@
+require 'rgen/metamodel_builder'
+
+module RGen
+ extend RGen::MetamodelBuilder::ModuleExtension
+
+ # This is the ECore metamodel described using the RGen::MetamodelBuilder language.
+ #
+ # Known differences to the Java/EMF implementation are:
+ # * Attributes can not be "many"
+ #
+ module ECore
+ extend RGen::MetamodelBuilder::ModuleExtension
+
+ class EObject < RGen::MetamodelBuilder::MMBase
+ end
+
+ class EModelElement < RGen::MetamodelBuilder::MMBase
+ end
+
+ class EAnnotation < RGen::MetamodelBuilder::MMMultiple(EModelElement, EObject)
+ has_attr 'source', String
+ end
+
+ class ENamedElement < EModelElement
+ has_attr 'name', String
+ end
+
+ class ETypedElement < ENamedElement
+ has_attr 'lowerBound', Integer, :defaultValueLiteral => "0"
+ has_attr 'ordered', Boolean, :defaultValueLiteral => "true"
+ has_attr 'unique', Boolean, :defaultValueLiteral => "true"
+ has_attr 'upperBound', Integer, :defaultValueLiteral => "1"
+ has_attr 'many', Boolean, :derived=>true
+ has_attr 'required', Boolean, :derived=>true
+ module ClassModule
+ def many_derived
+ upperBound > 1 || upperBound == -1
+ end
+ def required_derived
+ lowerBound > 0
+ end
+ end
+ end
+
+ class EStructuralFeature < ETypedElement
+ has_attr 'changeable', Boolean, :defaultValueLiteral => "true"
+ has_attr 'defaultValue', Object, :derived=>true
+ has_attr 'defaultValueLiteral', String
+ has_attr 'derived', Boolean, :defaultValueLiteral => "false"
+ has_attr 'transient', Boolean, :defaultValueLiteral => "false"
+ has_attr 'unsettable', Boolean, :defaultValueLiteral => "false"
+ has_attr 'volatile', Boolean, :defaultValueLiteral => "false"
+ module ClassModule
+ def defaultValue_derived
+ return nil if defaultValueLiteral.nil?
+ case eType
+ when EInt, ELong
+ defaultValueLiteral.to_i
+ when EFloat
+ defaultValueLiteral.to_f
+ when EEnum
+ defaultValueLiteral.to_sym
+ when EBoolean
+ defaultValueLiteral == "true"
+ when EString
+ defaultValueLiteral
+ else
+ raise "Unhandled type"
+ end
+ end
+ end
+ end
+
+ class EAttribute < EStructuralFeature
+ has_attr 'iD', Boolean, :defaultValueLiteral => "false"
+ end
+
+ class EClassifier < ENamedElement
+ has_attr 'defaultValue', Object, :derived=>true
+ has_attr 'instanceClass', Object, :derived=>true
+ has_attr 'instanceClassName', String
+ module ClassModule
+ def instanceClass_derived
+ map = {"java.lang.string" => "String",
+ "boolean" => "RGen::MetamodelBuilder::DataTypes::Boolean",
+ "int" => "Integer",
+ "long" => "RGen::MetamodelBuilder::DataTypes::Long"}
+ icn = instanceClassName
+ icn = "NilClass" if icn.nil?
+ icn = map[icn.downcase] if map[icn.downcase]
+ eval(icn)
+ end
+ end
+ end
+
+ class EDataType < EClassifier
+ has_attr 'serializable', Boolean
+ end
+
+ class EGenericType < EDataType
+ has_one 'eClassifier', EDataType
+ end
+
+ class ETypeArgument < EModelElement
+ has_one 'eClassifier', EDataType
+ end
+
+ class EEnum < EDataType
+ end
+
+ class EEnumLiteral < ENamedElement
+ # instance may point to a "singleton object" (e.g. a Symbol) representing the literal
+ # has_attr 'instance', Object, :eType=>:EEnumerator, :transient=>true
+ has_attr 'literal', String
+ has_attr 'value', Integer
+ end
+
+ # TODO: check if required
+ class EFactory < EModelElement
+ end
+
+ class EOperation < ETypedElement
+ end
+
+ class EPackage < ENamedElement
+ has_attr 'nsPrefix', String
+ has_attr 'nsURI', String
+ end
+
+ class EParameter < ETypedElement
+ end
+
+ class EReference < EStructuralFeature
+ has_attr 'container', Boolean, :derived=>true
+ has_attr 'containment', Boolean, :defaultValueLiteral => "false"
+ has_attr 'resolveProxies', Boolean, :defaultValueLiteral => "true"
+ end
+
+ class EStringToStringMapEntry < RGen::MetamodelBuilder::MMBase
+ has_attr 'key', String
+ has_attr 'value', String
+ end
+
+ class EClass < EClassifier
+ has_attr 'abstract', Boolean
+ has_attr 'interface', Boolean
+ has_one 'eIDAttribute', ECore::EAttribute, :derived=>true, :resolveProxies=>false
+
+ has_many 'eAllAttributes', ECore::EAttribute, :derived=>true
+ has_many 'eAllContainments', ECore::EReference, :derived=>true
+ has_many 'eAllOperations', ECore::EOperation, :derived=>true
+ has_many 'eAllReferences', ECore::EReference, :derived=>true
+ has_many 'eAllStructuralFeatures', ECore::EStructuralFeature, :derived=>true
+ has_many 'eAllSuperTypes', ECore::EClass, :derived=>true
+ has_many 'eAttributes', ECore::EAttribute, :derived=>true
+ has_many 'eReferences', ECore::EReference, :derived=>true
+
+ module ClassModule
+ def eAllAttributes_derived
+ eAttributes + eSuperTypes.eAllAttributes
+ end
+ def eAllContainments_derived
+ eReferences.select{|r| r.containment} + eSuperTypes.eAllContainments
+ end
+ def eAllReferences_derived
+ eReferences + eSuperTypes.eAllReferences
+ end
+ def eAllStructuralFeatures_derived
+ eStructuralFeatures + eSuperTypes.eAllStructuralFeatures
+ end
+ def eAllSuperTypes_derived
+ eSuperTypes + eSuperTypes.eAllSuperTypes
+ end
+ def eAttributes_derived
+ eStructuralFeatures.select{|f| f.is_a?(EAttribute)}
+ end
+ def eReferences_derived
+ eStructuralFeatures.select{|f| f.is_a?(EReference)}
+ end
+ end
+ end
+
+ # predefined datatypes
+
+ EString = EDataType.new(:name => "EString", :instanceClassName => "String")
+ EInt = EDataType.new(:name => "EInt", :instanceClassName => "Integer")
+ ELong = EDataType.new(:name => "ELong", :instanceClassName => "Long")
+ EBoolean = EDataType.new(:name => "EBoolean", :instanceClassName => "Boolean")
+ EFloat = EDataType.new(:name => "EFloat", :instanceClassName => "Float")
+ ERubyObject = EDataType.new(:name => "ERubyObject", :instanceClassName => "Object")
+ EJavaObject = EDataType.new(:name => "EJavaObject")
+ ERubyClass = EDataType.new(:name => "ERubyClass", :instanceClassName => "Class")
+ EJavaClass = EDataType.new(:name => "EJavaClass")
+
+ end
+
+ ECore::EModelElement.contains_many 'eAnnotations', ECore::EAnnotation, 'eModelElement', :resolveProxies=>false
+ ECore::EAnnotation.contains_many_uni 'details', ECore::EStringToStringMapEntry, :resolveProxies=>false
+ ECore::EAnnotation.contains_many_uni 'contents', ECore::EObject, :resolveProxies=>false
+ ECore::EAnnotation.has_many 'references', ECore::EObject
+ ECore::EPackage.contains_many 'eClassifiers', ECore::EClassifier, 'ePackage'
+ ECore::EPackage.contains_many 'eSubpackages', ECore::EPackage, 'eSuperPackage'
+ ECore::ETypedElement.has_one 'eType', ECore::EClassifier
+ ECore::EClass.contains_many 'eOperations', ECore::EOperation, 'eContainingClass', :resolveProxies=>false
+ ECore::EClass.contains_many 'eStructuralFeatures', ECore::EStructuralFeature, 'eContainingClass', :resolveProxies=>false
+ ECore::EClass.has_many 'eSuperTypes', ECore::EClass
+ ECore::EEnum.contains_many 'eLiterals', ECore::EEnumLiteral, 'eEnum', :resolveProxies=>false
+ ECore::EFactory.one_to_one 'ePackage', ECore::EPackage, 'eFactoryInstance', :lowerBound=>1, :transient=>true, :resolveProxies=>false
+ ECore::EOperation.contains_many 'eParameters', ECore::EParameter, 'eOperation', :resolveProxies=>false
+ ECore::EOperation.has_many 'eExceptions', ECore::EClassifier
+ ECore::EReference.has_one 'eOpposite', ECore::EReference
+
+ ECore::EAttribute.has_one 'eAttributeType', ECore::EDataType, :lowerBound=>1, :derived=>true
+ ECore::EReference.has_one 'eReferenceType', ECore::EClass, :lowerBound=>1, :derived=>true
+ ECore::EParameter.contains_one 'eGenericType', ECore::EGenericType, 'eParameter'
+ ECore::EGenericType.contains_many 'eTypeArguments', ECore::ETypeArgument, 'eGenericType'
+
+end
diff --git a/lib/puppet/vendor/rgen/lib/rgen/ecore/ecore_builder_methods.rb b/lib/puppet/vendor/rgen/lib/rgen/ecore/ecore_builder_methods.rb
new file mode 100644
index 000000000..8d78415b0
--- /dev/null
+++ b/lib/puppet/vendor/rgen/lib/rgen/ecore/ecore_builder_methods.rb
@@ -0,0 +1,81 @@
+module RGen
+
+module ECore
+
+module ECoreBuilderMethods
+ def eAttr(name, type, argHash={}, &block)
+ eAttribute(name, {:eType => type}.merge(argHash), &block)
+ end
+
+ def eRef(name, type, argHash={}, &block)
+ eReference(name, {:eType => type}.merge(argHash), &block)
+ end
+
+ # create bidirectional reference at once
+ def eBiRef(name, type, oppositeName, argHash={})
+ raise BuilderError.new("eOpposite attribute not allowed for bidirectional references") \
+ if argHash[:eOpposite] || argHash[:opposite_eOpposite]
+ eReference(name, {:eType => type}.merge(argHash.reject{|k,v| k.to_s =~ /^opposite_/})) do
+ eReference oppositeName, {:eContainingClass => type, :eType => _context(2),
+ :as => :eOpposite, :eOpposite => _context(1)}.
+ merge(Hash[*(argHash.select{|k,v| k.to_s =~ /^opposite_/}.
+ collect{|p| [p[0].to_s.sub(/^opposite_/,"").to_sym, p[1]]}.flatten)])
+ end
+ end
+
+ # reference shortcuts
+
+ alias references_1 eRef
+ alias references_one eRef
+
+ def references_N(name, type, argHash={})
+ eRef(name, type, {:upperBound => -1}.merge(argHash))
+ end
+ alias references_many references_N
+
+ def references_1to1(name, type, oppositeName, argHash={})
+ eBiRef(name, type, oppositeName, {:upperBound => 1, :opposite_upperBound => 1}.merge(argHash))
+ end
+ alias references_one_to_one references_1to1
+
+ def references_1toN(name, type, oppositeName, argHash={})
+ eBiRef(name, type, oppositeName, {:upperBound => -1, :opposite_upperBound => 1}.merge(argHash))
+ end
+ alias references_one_to_many references_1toN
+
+ def references_Nto1(name, type, oppositeName, argHash={})
+ eBiRef(name, type, oppositeName, {:upperBound => 1, :opposite_upperBound => -1}.merge(argHash))
+ end
+ alias references_many_to_one references_Nto1
+
+ def references_MtoN(name, type, oppositeName, argHash={})
+ eBiRef(name, type, oppositeName, {:upperBound => -1, :opposite_upperBound => -1}.merge(argHash))
+ end
+ alias references_many_to_many references_MtoN
+
+ # containment reference shortcuts
+
+ def contains_1(name, type, argHash={})
+ references_1(name, type, {:containment => true}.merge(argHash))
+ end
+ alias contains_one contains_1
+
+ def contains_N(name, type, argHash={})
+ references_N(name, type, {:containment => true}.merge(argHash))
+ end
+ alias contains_many contains_N
+
+ def contains_1to1(name, type, oppositeName, argHash={})
+ references_1to1(name, type, oppositeName, {:containment => true}.merge(argHash))
+ end
+ alias contains_one_to_one contains_1to1
+
+ def contains_1toN(name, type, oppositeName, argHash={})
+ references_1toN(name, type, oppositeName, {:containment => true}.merge(argHash))
+ end
+ alias contains_one_to_many contains_1toN
+end
+
+end
+
+end \ No newline at end of file
diff --git a/lib/puppet/vendor/rgen/lib/rgen/ecore/ecore_ext.rb b/lib/puppet/vendor/rgen/lib/rgen/ecore/ecore_ext.rb
new file mode 100644
index 000000000..8c3441389
--- /dev/null
+++ b/lib/puppet/vendor/rgen/lib/rgen/ecore/ecore_ext.rb
@@ -0,0 +1,69 @@
+require 'rgen/array_extensions'
+require 'rgen/ecore/ecore'
+
+module RGen
+ module ECore
+
+ # make super type reference bidirectional
+ EClass.many_to_many 'eSuperTypes', ECore::EClass, 'eSubTypes'
+
+ module EModelElement::ClassModule
+
+ def annotationValue(source, tag)
+ detail = eAnnotations.select{ |a| a.source == source }.details.find{ |d| d.key == tag }
+ detail && detail.value
+ end
+
+ end
+
+ module EPackage::ClassModule
+
+ def qualifiedName
+ if eSuperPackage
+ eSuperPackage.qualifiedName+"::"+name
+ else
+ name
+ end
+ end
+
+ def eAllClassifiers
+ eClassifiers + eSubpackages.eAllClassifiers
+ end
+ def eAllSubpackages
+ eSubpackages + eSubpackages.eAllSubpackages
+ end
+
+ def eClasses
+ eClassifiers.select{|c| c.is_a?(ECore::EClass)}
+ end
+
+ def eAllClasses
+ eClasses + eSubpackages.eAllClasses
+ end
+
+ def eDataTypes
+ eClassifiers.select{|c| c.is_a?(ECore::EDataType)}
+ end
+
+ def eAllDataTypes
+ eDataTypes + eSubpackages.eAllDataTypes
+ end
+ end
+
+ module EClass::ClassModule
+
+ def qualifiedName
+ if ePackage
+ ePackage.qualifiedName+"::"+name
+ else
+ name
+ end
+ end
+
+ def eAllSubTypes
+ eSubTypes + eSubTypes.eAllSubTypes
+ end
+
+ end
+ end
+end
diff --git a/lib/puppet/vendor/rgen/lib/rgen/ecore/ecore_interface.rb b/lib/puppet/vendor/rgen/lib/rgen/ecore/ecore_interface.rb
new file mode 100644
index 000000000..89ec32547
--- /dev/null
+++ b/lib/puppet/vendor/rgen/lib/rgen/ecore/ecore_interface.rb
@@ -0,0 +1,47 @@
+module RGen
+
+module ECore
+
+# Mixin to provide access to the ECore model describing a Ruby class or module
+# built using MetamodelBuilder.
+# The module should be used to +extend+ a class or module, i.e. to make its
+# methods class methods.
+#
+module ECoreInterface
+
+ # This method will lazily build to ECore model element belonging to the calling
+ # class or module using RubyToECore.
+ # Alternatively, the ECore model element can be provided up front. This is used
+ # when the Ruby metamodel classes and modules are created from ECore.
+ #
+ def ecore
+ if defined?(@ecore)
+ @ecore
+ else
+ unless defined?(@@transformer)
+ require 'rgen/ecore/ruby_to_ecore'
+ @@transformer = RubyToECore.new
+ end
+ @@transformer.trans(self)
+ end
+ end
+
+ # This method can be used to clear the ecore cache after the metamodel classes
+ # or modules have been changed; the ecore model will be recreated on next access
+ # to the +ecore+ method
+ # Beware, the ecore cache is global, i.e. for all metamodels.
+ #
+ def self.clear_ecore_cache
+ require 'rgen/ecore/ruby_to_ecore'
+ @@transformer = RubyToECore.new
+ end
+
+ def _set_ecore_internal(ecore) # :nodoc:
+ @ecore = ecore
+ end
+
+end
+
+end
+
+end
diff --git a/lib/puppet/vendor/rgen/lib/rgen/ecore/ecore_to_ruby.rb b/lib/puppet/vendor/rgen/lib/rgen/ecore/ecore_to_ruby.rb
new file mode 100644
index 000000000..c69bd38ed
--- /dev/null
+++ b/lib/puppet/vendor/rgen/lib/rgen/ecore/ecore_to_ruby.rb
@@ -0,0 +1,167 @@
+require 'rgen/ecore/ecore'
+
+module RGen
+
+module ECore
+
+class ECoreToRuby
+
+ def initialize
+ @modules = {}
+ @classifiers = {}
+ @features_added = {}
+ @in_create_module = false
+ end
+
+ def create_module(epackage)
+ return @modules[epackage] if @modules[epackage]
+
+ top = (@in_create_module == false)
+ @in_create_module = true
+
+ m = Module.new do
+ extend RGen::MetamodelBuilder::ModuleExtension
+ end
+ @modules[epackage] = m
+
+ epackage.eSubpackages.each{|p| create_module(p)}
+ m._set_ecore_internal(epackage)
+
+ create_module(epackage.eSuperPackage).const_set(epackage.name, m) if epackage.eSuperPackage
+
+ # create classes only after all modules have been created
+ # otherwise classes may be created multiple times
+ if top
+ epackage.eAllClassifiers.each do |c|
+ if c.is_a?(RGen::ECore::EClass)
+ create_class(c)
+ elsif c.is_a?(RGen::ECore::EEnum)
+ create_enum(c)
+ end
+ end
+ @in_create_module = false
+ end
+ m
+ end
+
+ def create_class(eclass)
+ return @classifiers[eclass] if @classifiers[eclass]
+
+ c = Class.new(super_class(eclass)) do
+ abstract if eclass.abstract
+ class << self
+ attr_accessor :_ecore_to_ruby
+ end
+ end
+ class << eclass
+ attr_accessor :instanceClass
+ def instanceClassName
+ instanceClass.to_s
+ end
+ end
+ eclass.instanceClass = c
+ c::ClassModule.module_eval do
+ alias _method_missing method_missing
+ def method_missing(m, *args)
+ if self.class._ecore_to_ruby.add_features(self.class.ecore)
+ send(m, *args)
+ else
+ _method_missing(m, *args)
+ end
+ end
+ alias _respond_to respond_to?
+ def respond_to?(m, include_all=false)
+ self.class._ecore_to_ruby.add_features(self.class.ecore)
+ _respond_to(m)
+ end
+ end
+ @classifiers[eclass] = c
+ c._set_ecore_internal(eclass)
+ c._ecore_to_ruby = self
+
+ create_module(eclass.ePackage).const_set(eclass.name, c)
+ c
+ end
+
+ def create_enum(eenum)
+ return @classifiers[eenum] if @classifiers[eenum]
+
+ e = RGen::MetamodelBuilder::DataTypes::Enum.new(eenum.eLiterals.collect{|l| l.name.to_sym})
+ @classifiers[eenum] = e
+
+ create_module(eenum.ePackage).const_set(eenum.name, e)
+ e
+ end
+
+ class FeatureWrapper
+ def initialize(efeature, classifiers)
+ @efeature = efeature
+ @classifiers = classifiers
+ end
+ def value(prop)
+ return false if prop == :containment && @efeature.is_a?(RGen::ECore::EAttribute)
+ @efeature.send(prop)
+ end
+ def many?
+ @efeature.many
+ end
+ def reference?
+ @efeature.is_a?(RGen::ECore::EReference)
+ end
+ def opposite
+ @efeature.eOpposite
+ end
+ def impl_type
+ etype = @efeature.eType
+ if etype.is_a?(RGen::ECore::EClass) || etype.is_a?(RGen::ECore::EEnum)
+ @classifiers[etype]
+ else
+ ic = etype.instanceClass
+ if ic
+ ic
+ else
+ raise "unknown type: #{etype.name}"
+ end
+ end
+ end
+ end
+
+ def add_features(eclass)
+ return false if @features_added[eclass]
+ c = @classifiers[eclass]
+ eclass.eStructuralFeatures.each do |f|
+ w1 = FeatureWrapper.new(f, @classifiers)
+ w2 = FeatureWrapper.new(f.eOpposite, @classifiers) if f.is_a?(RGen::ECore::EReference) && f.eOpposite
+ c.module_eval do
+ if w1.many?
+ _build_many_methods(w1, w2)
+ else
+ _build_one_methods(w1, w2)
+ end
+ end
+ end
+ @features_added[eclass] = true
+ eclass.eSuperTypes.each do |t|
+ add_features(t)
+ end
+ true
+ end
+
+ def super_class(eclass)
+ super_types = eclass.eSuperTypes
+ case super_types.size
+ when 0
+ RGen::MetamodelBuilder::MMBase
+ when 1
+ create_class(super_types.first)
+ else
+ RGen::MetamodelBuilder::MMMultiple(*super_types.collect{|t| create_class(t)})
+ end
+ end
+
+end
+
+end
+
+end
+
diff --git a/lib/puppet/vendor/rgen/lib/rgen/ecore/ruby_to_ecore.rb b/lib/puppet/vendor/rgen/lib/rgen/ecore/ruby_to_ecore.rb
new file mode 100644
index 000000000..841b72f89
--- /dev/null
+++ b/lib/puppet/vendor/rgen/lib/rgen/ecore/ruby_to_ecore.rb
@@ -0,0 +1,91 @@
+require 'rgen/transformer'
+require 'rgen/ecore/ecore'
+
+module RGen
+
+module ECore
+
+# This transformer creates an ECore model from Ruby classes built
+# by RGen::MetamodelBuilder.
+#
+class RubyToECore < Transformer
+
+ transform Class, :to => EClass, :if => :convert? do
+ { :name => name.gsub(/.*::(\w+)$/,'\1'),
+ :abstract => _abstract_class,
+ :interface => false,
+ :eStructuralFeatures => trans(_metamodel_description),
+ :ePackage => trans(name =~ /(.*)::\w+$/ ? eval($1) : nil),
+ :eSuperTypes => trans(superclasses),
+ :instanceClassName => name,
+ :eAnnotations => trans(_annotations)
+ }
+ end
+
+ method :superclasses do
+ if superclass.respond_to?(:multiple_superclasses) && superclass.multiple_superclasses
+ superclass.multiple_superclasses
+ else
+ [ superclass ]
+ end
+ end
+
+ transform Module, :to => EPackage, :if => :convert? do
+ @enumParentModule ||= {}
+ _constants = _constantOrder + (constants - _constantOrder)
+ _constants.select {|c| const_get(c).is_a?(MetamodelBuilder::DataTypes::Enum)}.
+ each {|c| @enumParentModule[const_get(c)] = @current_object}
+ { :name => name.gsub(/.*::(\w+)$/,'\1'),
+ :eClassifiers => trans(_constants.collect{|c| const_get(c)}.select{|c| c.is_a?(Class) ||
+ (c.is_a?(MetamodelBuilder::DataTypes::Enum) && c != MetamodelBuilder::DataTypes::Boolean) }),
+ :eSuperPackage => trans(name =~ /(.*)::\w+$/ ? eval($1) : nil),
+ :eSubpackages => trans(_constants.collect{|c| const_get(c)}.select{|c| c.is_a?(Module) && !c.is_a?(Class)}),
+ :eAnnotations => trans(_annotations)
+ }
+ end
+
+ method :convert? do
+ @current_object.respond_to?(:ecore) && @current_object != RGen::MetamodelBuilder::MMBase
+ end
+
+ transform MetamodelBuilder::Intermediate::Attribute, :to => EAttribute do
+ Hash[*MetamodelBuilder::Intermediate::Attribute.properties.collect{|p| [p, value(p)]}.flatten].merge({
+ :eType => (etype == :EEnumerable ? trans(impl_type) : RGen::ECore.const_get(etype)),
+ :eAnnotations => trans(annotations)
+ })
+ end
+
+ transform MetamodelBuilder::Intermediate::Reference, :to => EReference do
+ Hash[*MetamodelBuilder::Intermediate::Reference.properties.collect{|p| [p, value(p)]}.flatten].merge({
+ :eType => trans(impl_type),
+ :eOpposite => trans(opposite),
+ :eAnnotations => trans(annotations)
+ })
+ end
+
+ transform MetamodelBuilder::Intermediate::Annotation, :to => EAnnotation do
+ { :source => source,
+ :details => details.keys.collect do |k|
+ e = RGen::ECore::EStringToStringMapEntry.new
+ e.key = k
+ e.value = details[k]
+ e
+ end
+ }
+ end
+
+ transform MetamodelBuilder::DataTypes::Enum, :to => EEnum do
+ { :name => name,
+ :instanceClassName => @enumParentModule && @enumParentModule[@current_object] && @enumParentModule[@current_object].name+"::"+name,
+ :eLiterals => literals.collect do |l|
+ lit = RGen::ECore::EEnumLiteral.new
+ lit.name = l.to_s
+ lit
+ end }
+ end
+
+end
+
+end
+
+end
diff --git a/lib/puppet/vendor/rgen/lib/rgen/environment.rb b/lib/puppet/vendor/rgen/lib/rgen/environment.rb
new file mode 100644
index 000000000..cf88bd4ea
--- /dev/null
+++ b/lib/puppet/vendor/rgen/lib/rgen/environment.rb
@@ -0,0 +1,129 @@
+module RGen
+
+# An Environment is used to hold model elements.
+#
+class Environment
+
+ def initialize
+ @elements = {}
+ @subClasses = {}
+ @subClassesUpdated = {}
+ @deleted = {}
+ @deletedClasses = {}
+ end
+
+ # Add a model element. Returns the environment so <code><<</code> can be chained.
+ #
+ def <<(el)
+ clazz = el.class
+ @elements[clazz] ||= []
+ @elements[clazz] << el
+ updateSubClasses(clazz)
+ self
+ end
+
+ # Removes model element from environment.
+ def delete(el)
+ @deleted[el] = true
+ @deletedClasses[el.class] = true
+ end
+
+ # Iterates each element
+ #
+ def each(&b)
+ removeDeleted
+ @elements.values.flatten.each(&b)
+ end
+
+ # Return the elements of the environment as an array
+ #
+ def elements
+ removeDeleted
+ @elements.values.flatten
+ end
+
+ # This method can be used to instantiate a class and automatically put it into
+ # the environment. The new instance is returned.
+ #
+ def new(clazz, *args)
+ obj = clazz.new(*args)
+ self << obj
+ obj
+ end
+
+ # Finds and returns model elements in the environment.
+ #
+ # The search description argument must be a hash specifying attribute/value pairs.
+ # Only model elements are returned which respond to the specified attribute methods
+ # and return the specified values as result of these attribute methods.
+ #
+ # As a special hash key :class can be used to look for model elements of a specific
+ # class. In this case an array of possible classes can optionally be given.
+ #
+ def find(desc)
+ removeDeleted
+ result = []
+ classes = desc[:class] if desc[:class] and desc[:class].is_a?(Array)
+ classes = [ desc[:class] ] if !classes and desc[:class]
+ if classes
+ hashKeys = classesWithSubClasses(classes)
+ else
+ hashKeys = @elements.keys
+ end
+ hashKeys.each do |clazz|
+ next unless @elements[clazz]
+ @elements[clazz].each do |e|
+ failed = false
+ desc.each_pair { |k,v|
+ failed = true if k != :class and ( !e.respond_to?(k) or e.send(k) != v )
+ }
+ result << e unless failed
+ end
+ end
+ result
+ end
+
+ private
+
+ def removeDeleted
+ @deletedClasses.keys.each do |c|
+ @elements[c].reject!{|e| @deleted[e]}
+ end
+ @deletedClasses.clear
+ @deleted.clear
+ end
+
+ def updateSubClasses(clazz)
+ return if @subClassesUpdated[clazz]
+ if clazz.respond_to?( :ecore )
+ superClasses = clazz.ecore.eAllSuperTypes.collect{|c| c.instanceClass}
+ else
+ superClasses = superclasses(clazz)
+ end
+ superClasses.each do |c|
+ next if c == Object
+ @subClasses[c] ||= []
+ @subClasses[c] << clazz
+ end
+ @subClassesUpdated[clazz] = true
+ end
+
+ def classesWithSubClasses(classes)
+ result = classes
+ classes.each do |c|
+ result += @subClasses[c] if @subClasses[c]
+ end
+ result.uniq
+ end
+
+ def superclasses(clazz)
+ if clazz == Object
+ []
+ else
+ superclasses(clazz.superclass) << clazz.superclass
+ end
+ end
+
+end
+
+end \ No newline at end of file
diff --git a/lib/puppet/vendor/rgen/lib/rgen/fragment/dump_file_cache.rb b/lib/puppet/vendor/rgen/lib/rgen/fragment/dump_file_cache.rb
new file mode 100644
index 000000000..3963aeb2b
--- /dev/null
+++ b/lib/puppet/vendor/rgen/lib/rgen/fragment/dump_file_cache.rb
@@ -0,0 +1,63 @@
+module RGen
+
+module Fragment
+
+# Caches model fragments in Ruby dump files.
+#
+# Dump files are created per each fragment file.
+#
+# The main goal is to support fast loading and joining of fragments. Therefore the cache
+# stores additional information which makes the joining process faster (adding to
+# environment, resolving references)
+#
+class DumpFileCache
+
+ # +cache_map+ must be an object responding to +load_data+ and +store_data+
+ # for loading or storing data associated with a file;
+ # this can be an instance of Util::FileCacheMap
+ def initialize(cache_map)
+ @cache_map = cache_map
+ end
+
+ # Note that the fragment must not be connected to other fragments by resolved references
+ # unresolve the fragment if necessary
+ def store(fragment)
+ fref = fragment.fragment_ref
+ # temporarily remove the reference to the fragment to avoid dumping the fragment
+ fref.fragment = nil
+ @cache_map.store_data(fragment.location,
+ Marshal.dump({
+ :root_elements => fragment.root_elements,
+ :elements => fragment.elements,
+ :index => fragment.index,
+ :unresolved_refs => fragment.unresolved_refs,
+ :fragment_ref => fref,
+ :data => fragment.data
+ }))
+ fref.fragment = fragment
+ end
+
+ def load(fragment)
+ dump = @cache_map.load_data(fragment.location)
+ return :invalid if dump == :invalid
+ header = Marshal.load(dump)
+ fragment.set_root_elements(header[:root_elements],
+ :elements => header[:elements],
+ :index => header[:index],
+ :unresolved_refs => header[:unresolved_refs])
+ fragment.data = header[:data]
+ if header[:fragment_ref]
+ fragment.fragment_ref = header[:fragment_ref]
+ fragment.fragment_ref.fragment = fragment
+ else
+ raise "no fragment_ref in fragment loaded from cache"
+ end
+ end
+
+end
+
+end
+
+end
+
+
diff --git a/lib/puppet/vendor/rgen/lib/rgen/fragment/fragmented_model.rb b/lib/puppet/vendor/rgen/lib/rgen/fragment/fragmented_model.rb
new file mode 100644
index 000000000..155c767ff
--- /dev/null
+++ b/lib/puppet/vendor/rgen/lib/rgen/fragment/fragmented_model.rb
@@ -0,0 +1,140 @@
+require 'rgen/instantiator/reference_resolver'
+
+module RGen
+
+module Fragment
+
+# A FragmentedModel represents a model which consists of fragments (ModelFragment).
+#
+# The main purpose of this class is to resolve references across fragments and
+# to keep the references consistent while fragments are added or removed.
+# This way it also plays an important role in keeping the model fragments consistent
+# and thus ModelFragment objects should only be accessed via this interface.
+# Overall unresolved references after the resolution step are also maintained.
+#
+# A FragmentedModel can also keep an RGen::Environment object up to date while fragments
+# are added or removed. The environment must be registered with the constructor.
+#
+# Reference resolution is based on arbitrary identifiers. The identifiers must be
+# provided in the fragments' indices. The FragmentedModel takes care to maintain
+# the overall index.
+#
+class FragmentedModel
+ attr_reader :fragments
+ attr_reader :environment
+
+ # Creates a fragmented model. Options:
+ #
+ # :env
+ # environment which will be updated as model elements are added and removed
+ #
+ def initialize(options={})
+ @environment = options[:env]
+ @fragments = []
+ @index = nil
+ @fragment_change_listeners = []
+ @fragment_index = {}
+ end
+
+ # Adds a proc which is called when a fragment is added or removed
+ # The proc receives the fragment and one of :added, :removed
+ #
+ def add_fragment_change_listener(listener)
+ @fragment_change_listeners << listener
+ end
+
+ def remove_fragment_change_listener(listener)
+ @fragment_change_listeners.delete(listener)
+ end
+
+ # Add a fragment.
+ #
+ def add_fragment(fragment)
+ invalidate_cache
+ @fragments << fragment
+ fragment.elements.each{|e| @environment << e} if @environment
+ @fragment_change_listeners.each{|l| l.call(fragment, :added)}
+ end
+
+ # Removes the fragment. The fragment will be unresolved using unresolve_fragment.
+ #
+ def remove_fragment(fragment)
+ raise "fragment not part of model" unless @fragments.include?(fragment)
+ invalidate_cache
+ @fragments.delete(fragment)
+ @fragment_index.delete(fragment)
+ unresolve_fragment(fragment)
+ fragment.elements.each{|e| @environment.delete(e)} if @environment
+ @fragment_change_listeners.each{|l| l.call(fragment, :removed)}
+ end
+
+ # Resolve references between fragments.
+ # It is assumed that references within fragments have already been resolved.
+ # This method can be called several times. It will update the overall unresolved references.
+ #
+ # Options:
+ #
+ # :fragment_provider:
+ # Only if a +fragment_provider+ is given, the resolve step can be reverted later on
+ # by a call to unresolve_fragment. The fragment provider is a proc which receives a model
+ # element and must return the fragment in which the element is contained.
+ #
+ # :use_target_type:
+ # reference resolver uses the expected target type to narrow the set of possible targets
+ #
+ def resolve(options={})
+ local_index = index
+ @fragments.each do |f|
+ f.resolve_external(local_index, options)
+ end
+ end
+
+ # Remove all references between this fragment and all other fragments.
+ # The references will be replaced with unresolved references (MMProxy objects).
+ #
+ def unresolve_fragment(fragment)
+ fragment.unresolve_external
+ @fragments.each do |f|
+ if f != fragment
+ f.unresolve_external_fragment(fragment)
+ end
+ end
+ end
+
+ # Returns the overall unresolved references.
+ #
+ def unresolved_refs
+ @fragments.collect{|f| f.unresolved_refs}.flatten
+ end
+
+ # Returns the overall index.
+ # This is a Hash mapping identifiers to model elements accessible via the identifier.
+ #
+ def index
+ fragments.each do |f|
+ if !@fragment_index[f] || (@fragment_index[f].object_id != f.index.object_id)
+ @fragment_index[f] = f.index
+ invalidate_cache
+ end
+ end
+ return @index if @index
+ @index = {}
+ fragments.each do |f|
+ f.index.each do |i|
+ (@index[i[0]] ||= []) << i[1]
+ end
+ end
+ @index
+ end
+
+ private
+
+ def invalidate_cache
+ @index = nil
+ end
+
+end
+
+end
+
+end
diff --git a/lib/puppet/vendor/rgen/lib/rgen/fragment/model_fragment.rb b/lib/puppet/vendor/rgen/lib/rgen/fragment/model_fragment.rb
new file mode 100644
index 000000000..4e79f6126
--- /dev/null
+++ b/lib/puppet/vendor/rgen/lib/rgen/fragment/model_fragment.rb
@@ -0,0 +1,289 @@
+require 'rgen/instantiator/reference_resolver'
+
+module RGen
+
+module Fragment
+
+# A model fragment is a list of root model elements associated with a location (e.g. a file).
+# It also stores a list of unresolved references as well as a list of unresolved references
+# which have been resolved. Using the latter, a fragment can undo reference resolution.
+#
+# Optionally, an arbitrary data object may be associated with the fragment. The data object
+# will also be stored in the cache.
+#
+# If an element within the fragment changes this must be indicated to the fragment by calling
+# +mark_changed+.
+#
+# Note: the fragment knows how to resolve references (+resolve_local+, +resolve_external+).
+# However considering a fragment a data structure, this functionality might be removed in the
+# future. Instead the fragment should be told about each resolution taking place. Use
+# method +mark_resolved+ for this purpose.
+#
+class ModelFragment
+ attr_reader :root_elements
+ attr_accessor :location, :fragment_ref, :data
+
+ # A FragmentRef serves as a single target object for elements which need to reference the
+ # fragment they are contained in. The FragmentRef references the fragment it is contained in.
+ # The FragmentRef is separate from the fragment itself to allow storing it in a marshal dump
+ # independently of the fragment.
+ #
+ class FragmentRef
+ attr_accessor :fragment
+ end
+
+ # A ResolvedReference wraps an unresolved reference after it has been resolved.
+ # It also holds the target element to which it has been resolved, i.e. with which the proxy
+ # object has been replaced.
+ #
+ class ResolvedReference
+ attr_reader :uref, :target
+ def initialize(uref, target)
+ @uref, @target = uref, target
+ end
+ end
+
+ # Create a model fragment
+ #
+ # :data
+ # data object associated with this fragment
+ #
+ # :identifier_provider
+ # identifier provider to be used when resolving references
+ # it must be a proc which receives a model element and must return
+ # that element's identifier or nil if the element has no identifier
+ #
+ def initialize(location, options={})
+ @location = location
+ @fragment_ref = FragmentRef.new
+ @fragment_ref.fragment = self
+ @data = options[:data]
+ @resolved_refs = nil
+ @changed = false
+ @identifier_provider = options[:identifier_provider]
+ end
+
+ # Set the root elements, normally done by an instantiator.
+ #
+ # For optimization reasons the instantiator of the fragment may provide data explicitly which
+ # is normally derived by the fragment itself. In this case it is essential that this
+ # data is consistent with the fragment.
+ #
+ def set_root_elements(root_elements, options={})
+ @root_elements = root_elements
+ @elements = options[:elements]
+ @index = options[:index]
+ @unresolved_refs = options[:unresolved_refs]
+ @resolved_refs = nil
+ # new unresolved refs, reset removed_urefs
+ @removed_urefs = nil
+ @changed = false
+ end
+
+ # Must be called when any of the elements in this fragment has been changed
+ #
+ def mark_changed
+ @changed = true
+ @elements = nil
+ @index = nil
+ @unresolved_refs = nil
+ # unresolved refs will be recalculated, no need to keep removed_urefs
+ @removed_urefs = nil
+ @resolved_refs = :dirty
+ end
+
+ # Can be used to reset the change status to unchanged.
+ #
+ def mark_unchanged
+ @changed = false
+ end
+
+ # Indicates whether the fragment has been changed or not
+ #
+ def changed?
+ @changed
+ end
+
+ # Returns all elements within this fragment
+ #
+ def elements
+ return @elements if @elements
+ @elements = []
+ @root_elements.each do |e|
+ @elements << e
+ @elements.concat(e.eAllContents)
+ end
+ @elements
+ end
+
+ # Returns the index of the element contained in this fragment.
+ #
+ def index
+ build_index unless @index
+ @index
+ end
+
+ # Returns all unresolved references within this fragment, i.e. references to MMProxy objects
+ #
+ def unresolved_refs
+ @unresolved_refs ||= collect_unresolved_refs
+ if @removed_urefs
+ @unresolved_refs -= @removed_urefs
+ @removed_urefs = nil
+ end
+ @unresolved_refs
+ end
+
+ # Builds the index of all elements within this fragment having an identifier
+ # the index is an array of 2-element arrays holding the identifier and the element
+ #
+ def build_index
+ raise "cannot build index without an identifier provider" unless @identifier_provider
+ @index = elements.collect { |e|
+ ident = @identifier_provider.call(e, nil)
+ ident && !ident.empty? ? [ident, e] : nil
+ }.compact
+ end
+
+ # Resolves local references (within this fragment) as far as possible
+ #
+ # Options:
+ #
+ # :use_target_type:
+ # reference resolver uses the expected target type to narrow the set of possible targets
+ #
+ def resolve_local(options={})
+ resolver = RGen::Instantiator::ReferenceResolver.new
+ index.each do |i|
+ resolver.add_identifier(i[0], i[1])
+ end
+ @unresolved_refs = resolver.resolve(unresolved_refs, :use_target_type => options[:use_target_type])
+ end
+
+ # Resolves references to external fragments using the external_index provided.
+ # The external index must be a Hash mapping identifiers uniquely to model elements.
+ #
+ # Options:
+ #
+ # :fragment_provider:
+ # If a +fragment_provider+ is given, the resolve step can be reverted later on
+ # by a call to unresolve_external or unresolve_external_fragment. The fragment provider
+ # is a proc which receives a model element and must return the fragment in which it is
+ # contained.
+ #
+ # :use_target_type:
+ # reference resolver uses the expected target type to narrow the set of possible targets
+ #
+ #
+ def resolve_external(external_index, options)
+ fragment_provider = options[:fragment_provider]
+ resolver = RGen::Instantiator::ReferenceResolver.new(
+ :identifier_resolver => proc {|ident| external_index[ident] })
+ if fragment_provider
+ @resolved_refs = {} if @resolved_refs.nil? || @resolved_refs == :dirty
+ on_resolve = proc { |ur, target|
+ target_fragment = fragment_provider.call(target)
+ target_fragment ||= :unknown
+ raise "can not resolve local reference in resolve_external, call resolve_local first" \
+ if target_fragment == self
+ @resolved_refs[target_fragment] ||= []
+ @resolved_refs[target_fragment] << ResolvedReference.new(ur, target)
+ }
+ @unresolved_refs = resolver.resolve(unresolved_refs, :on_resolve => on_resolve, :use_target_type => options[:use_target_type])
+ else
+ @unresolved_refs = resolver.resolve(unresolved_refs, :use_target_type => options[:use_target_type])
+ end
+ end
+
+ # Marks a particular unresolved reference +uref+ as resolved to +target+ in +target_fragment+.
+ #
+ def mark_resolved(uref, target_fragment, target)
+ @resolved_refs = {} if @resolved_refs.nil? || @resolved_refs == :dirty
+ target_fragment ||= :unknown
+ if target_fragment != self
+ @resolved_refs[target_fragment] ||= []
+ @resolved_refs[target_fragment] << ResolvedReference.new(uref, target)
+ end
+ @removed_urefs ||= []
+ @removed_urefs << uref
+ end
+
+ # Unresolve outgoing references to all external fragments, i.e. references which used to
+ # be represented by an unresolved reference from within this fragment.
+ # Note, that there may be more references to external fragments due to references which
+ # were represented by unresolved references from within other fragments.
+ #
+ def unresolve_external
+ return if @resolved_refs.nil?
+ raise "can not unresolve, missing fragment information" if @resolved_refs == :dirty || @resolved_refs[:unknown]
+ rrefs = @resolved_refs.values.flatten
+ @resolved_refs = {}
+ unresolve_refs(rrefs)
+ end
+
+ # Like unresolve_external but only unresolve references to external fragment +fragment+
+ #
+ def unresolve_external_fragment(fragment)
+ return if @resolved_refs.nil?
+ raise "can not unresolve, missing fragment information" if @resolved_refs == :dirty || @resolved_refs[:unknown]
+ rrefs = @resolved_refs[fragment]
+ @resolved_refs.delete(fragment)
+ unresolve_refs(rrefs) if rrefs
+ end
+
+ private
+
+ # Turns resolved references +rrefs+ back into unresolved references
+ #
+ def unresolve_refs(rrefs)
+ # make sure any removed_urefs have been removed,
+ # otherwise they will be removed later even if this method actually re-added them
+ unresolved_refs
+ rrefs.each do |rr|
+ ur = rr.uref
+ refs = ur.element.getGeneric(ur.feature_name)
+ if refs.is_a?(Array)
+ index = refs.index(rr.target)
+ ur.element.removeGeneric(ur.feature_name, rr.target)
+ ur.element.addGeneric(ur.feature_name, ur.proxy, index)
+ else
+ ur.element.setGeneric(ur.feature_name, ur.proxy)
+ end
+ @unresolved_refs << ur
+ end
+ end
+
+ def collect_unresolved_refs
+ unresolved_refs = []
+ elements.each do |e|
+ each_reference_target(e) do |r, t|
+ if t.is_a?(RGen::MetamodelBuilder::MMProxy)
+ unresolved_refs <<
+ RGen::Instantiator::ReferenceResolver::UnresolvedReference.new(e, r.name, t)
+ end
+ end
+ end
+ unresolved_refs
+ end
+
+ def each_reference_target(element)
+ non_containment_references(element.class).each do |r|
+ element.getGenericAsArray(r.name).each do |t|
+ yield(r, t)
+ end
+ end
+ end
+
+ def non_containment_references(clazz)
+ @@non_containment_references_cache ||= {}
+ @@non_containment_references_cache[clazz] ||=
+ clazz.ecore.eAllReferences.select{|r| !r.containment}
+ end
+
+end
+
+end
+
+end
+
+
diff --git a/lib/puppet/vendor/rgen/lib/rgen/instantiator/abstract_instantiator.rb b/lib/puppet/vendor/rgen/lib/rgen/instantiator/abstract_instantiator.rb
new file mode 100644
index 000000000..8329a3851
--- /dev/null
+++ b/lib/puppet/vendor/rgen/lib/rgen/instantiator/abstract_instantiator.rb
@@ -0,0 +1,66 @@
+module RGen
+
+module Instantiator
+
+class AbstractInstantiator
+
+ ResolverDescription = Struct.new(:from, :attribute, :block) # :nodoc:
+
+ class << self
+ attr_accessor :resolver_descs
+ end
+
+ def initialize(env)
+ @env = env
+ end
+
+ # Specifies that +attribute+ should be resolved. If +:class+ is specified,
+ # resolve +attribute+ only for objects of type class.
+ # The block must return the value to which the attribute should be assigned.
+ # The object for which the attribute is to be resolved will be accessible
+ # in the current context within the block.
+ #
+ def self.resolve(attribute, desc=nil, &block)
+ from = (desc.is_a?(Hash) && desc[:class])
+ self.resolver_descs ||= []
+ self.resolver_descs << ResolverDescription.new(from, attribute, block)
+ end
+
+ # Resolves +attribute+ to a model element which has attribute +:id+ set to the
+ # value currently in attribute +:src+
+ #
+ def self.resolve_by_id(attribute, desc)
+ id_attr = (desc.is_a?(Hash) && desc[:id])
+ src_attr = (desc.is_a?(Hash) && desc[:src])
+ raise StandardError.new("No id attribute given.") unless id_attr
+ resolve(attribute) do
+ @env.find(id_attr => @current_object.send(src_attr)).first
+ end
+ end
+
+ private
+
+ def method_missing(m, *args) #:nodoc:
+ if @current_object
+ @current_object.send(m)
+ else
+ super
+ end
+ end
+
+ def resolve
+ self.class.resolver_descs ||= []
+ self.class.resolver_descs.each { |desc|
+ @env.find(:class => desc.from).each { |e|
+ old_object, @current_object = @current_object, e
+ e.send("#{desc.attribute}=", instance_eval(&desc.block)) if e.respond_to?("#{desc.attribute}=")
+ @current_object = old_object
+ }
+ }
+ end
+
+end
+
+end
+
+end \ No newline at end of file
diff --git a/lib/puppet/vendor/rgen/lib/rgen/instantiator/abstract_xml_instantiator.rb b/lib/puppet/vendor/rgen/lib/rgen/instantiator/abstract_xml_instantiator.rb
new file mode 100644
index 000000000..441950e73
--- /dev/null
+++ b/lib/puppet/vendor/rgen/lib/rgen/instantiator/abstract_xml_instantiator.rb
@@ -0,0 +1,66 @@
+require 'nokogiri'
+
+class AbstractXMLInstantiator
+
+ class Visitor < Nokogiri::XML::SAX::Document
+
+ def initialize(inst, gcSuspendCount)
+ @instantiator = inst
+ @gcSuspendCount = gcSuspendCount
+ @namespaces = {}
+ end
+
+ def start_element_namespace(tag, attributes, prefix, uri, ns)
+ controlGC
+ ns.each{|n| @namespaces[n[0]] = n[1]}
+ attrs = attributes.collect{|a| [a.prefix ? a.prefix+":"+a.localname : a.localname, a.value]}
+ @instantiator.start_tag(prefix, tag, @namespaces, Hash[*(attrs.flatten)])
+ attrs.each { |pair| @instantiator.set_attribute(pair[0], pair[1]) }
+ end
+
+ def end_element_namespace(tag, prefix, uri)
+ @instantiator.end_tag(prefix, tag)
+ end
+
+ def characters(str)
+ @instantiator.text(str)
+ end
+
+ def controlGC
+ return unless @gcSuspendCount > 0
+ @gcCounter ||= 0
+ @gcCounter += 1
+ if @gcCounter == @gcSuspendCount
+ @gcCounter = 0
+ GC.enable
+ ObjectSpace.garbage_collect
+ GC.disable
+ end
+ end
+ end
+
+ # Parses str and calls start_tag, end_tag, set_attribute and text methods of a subclass.
+ #
+ # If gcSuspendCount is specified, the garbage collector will be disabled for that
+ # number of start or end tags. After that period it will clean up and then be disabled again.
+ # A value of about 1000 can significantly improve overall performance.
+ # The memory usage normally does not increase.
+ # Depending on the work done for every xml tag the value might have to be adjusted.
+ #
+ def instantiate(str, gcSuspendCount=0)
+ gcDisabledBefore = GC.disable
+ gcSuspendCount = 0 if gcDisabledBefore
+ begin
+ visitor = Visitor.new(self, gcSuspendCount)
+ parser = Nokogiri::XML::SAX::Parser.new(visitor)
+ parser.parse(str) do |ctx|
+ @parserContext = ctx
+ end
+ ensure
+ GC.enable unless gcDisabledBefore
+ end
+ end
+
+ def text(str)
+ end
+end
diff --git a/lib/puppet/vendor/rgen/lib/rgen/instantiator/default_xml_instantiator.rb b/lib/puppet/vendor/rgen/lib/rgen/instantiator/default_xml_instantiator.rb
new file mode 100644
index 000000000..e8d2571f8
--- /dev/null
+++ b/lib/puppet/vendor/rgen/lib/rgen/instantiator/default_xml_instantiator.rb
@@ -0,0 +1,117 @@
+require 'rgen/instantiator/nodebased_xml_instantiator'
+
+module RGen
+
+module Instantiator
+
+# A default XML instantiator.
+# Derive your own instantiator from this class or use it as is.
+#
+class DefaultXMLInstantiator < NodebasedXMLInstantiator
+ include Util::NameHelper
+
+ NamespaceDescriptor = Struct.new(:prefix, :target)
+
+ class << self
+
+ def map_tag_ns(from, to, prefix="")
+ tag_ns_map[from] = NamespaceDescriptor.new(prefix, to)
+ end
+
+ def tag_ns_map # :nodoc:
+ @tag_ns_map ||={}
+ @tag_ns_map
+ end
+
+ end
+
+ def initialize(env, default_module, create_mm=false)
+ super(env)
+ @default_module = default_module
+ @create_mm = create_mm
+ end
+
+ def on_descent(node)
+ obj = new_object(node)
+ @env << obj unless obj.nil?
+ node.object = obj
+ node.attributes.each_pair { |k,v| set_attribute(node, k, v) }
+ end
+
+ def on_ascent(node)
+ node.children.each { |c| assoc_p2c(node, c) }
+ node.object.class.has_attr 'chardata', Object unless node.object.respond_to?(:chardata)
+ set_attribute(node, "chardata", node.chardata)
+ end
+
+ def class_name(str)
+ saneClassName(str)
+ end
+
+ def new_object(node)
+ ns_desc = self.class.tag_ns_map[node.namespace]
+ class_name = class_name(ns_desc.nil? ? node.qtag : ns_desc.prefix+node.tag)
+ mod = (ns_desc && ns_desc.target) || @default_module
+ build_on_error(NameError, :build_class, class_name, mod) do
+ mod.const_get(class_name).new
+ end
+ end
+
+ def build_class(name, mod)
+ mod.const_set(name, Class.new(RGen::MetamodelBuilder::MMBase))
+ end
+
+ def method_name(str)
+ saneMethodName(str)
+ end
+
+ def assoc_p2c(parent, child)
+ return unless parent.object && child.object
+ method_name = method_name(className(child.object))
+ build_on_error(NoMethodError, :build_p2c_assoc, parent, child, method_name) do
+ parent.object.addGeneric(method_name, child.object)
+ child.object.setGeneric("parent", parent.object)
+ end
+ end
+
+ def build_p2c_assoc(parent, child, method_name)
+ parent.object.class.has_many(method_name, child.object.class)
+ child.object.class.has_one("parent", RGen::MetamodelBuilder::MMBase)
+ end
+
+ def set_attribute(node, attr, value)
+ return unless node.object
+ build_on_error(NoMethodError, :build_attribute, node, attr, value) do
+ node.object.setGeneric(method_name(attr), value)
+ end
+ end
+
+ def build_attribute(node, attr, value)
+ node.object.class.has_attr(method_name(attr))
+ end
+
+ protected
+
+ # Helper method for implementing classes.
+ # This method yields the given block.
+ # If the metamodel should be create automatically (see constructor)
+ # rescues +error+ and calls +builder_method+ with +args+, then
+ # yields the block again.
+ def build_on_error(error, builder_method, *args)
+ begin
+ yield
+ rescue error
+ if @create_mm
+ send(builder_method, *args)
+ yield
+ else
+ raise
+ end
+ end
+ end
+
+end
+
+end
+
+end
diff --git a/lib/puppet/vendor/rgen/lib/rgen/instantiator/ecore_xml_instantiator.rb b/lib/puppet/vendor/rgen/lib/rgen/instantiator/ecore_xml_instantiator.rb
new file mode 100644
index 000000000..bc911afd4
--- /dev/null
+++ b/lib/puppet/vendor/rgen/lib/rgen/instantiator/ecore_xml_instantiator.rb
@@ -0,0 +1,169 @@
+require 'rgen/ecore/ecore'
+require 'rgen/instantiator/abstract_xml_instantiator'
+require 'rgen/array_extensions'
+
+class ECoreXMLInstantiator < AbstractXMLInstantiator
+
+ include RGen::ECore
+
+ INFO = 0
+ WARN = 1
+ ERROR = 2
+
+ def initialize(env, loglevel=ERROR)
+ @env = env
+ @rolestack = []
+ @elementstack = []
+ @element_by_id = {}
+ @loglevel = loglevel
+ end
+
+ def start_tag(prefix, tag, namespaces, attributes)
+ eRef = nil
+ if @elementstack.last
+ eRef = eAllReferences(@elementstack.last).find{|r|r.name == tag}
+ if eRef
+ if attributes["xsi:type"] && attributes["xsi:type"] =~ /ecore:(\w+)/
+ class_name = $1
+ attributes.delete("xsi:type")
+ else
+ class_name = eRef.eType.name
+ end
+ else
+ raise "Reference not found: #{tag} on #{@elementstack.last}"
+ end
+ else
+ class_name = tag
+ end
+
+ eClass = RGen::ECore.ecore.eClassifiers.find{|c| c.name == class_name}
+ if eClass
+ obj = RGen::ECore.const_get(class_name).new
+ if attributes["xmi:id"]
+ @element_by_id[attributes["xmi:id"]] = obj
+ attributes.delete("xmi:id")
+ end
+ if eRef
+ if eRef.many
+ @elementstack.last.addGeneric(eRef.name, obj)
+ else
+ @elementstack.last.setGeneric(eRef.name, obj)
+ end
+ end
+ @env << obj
+ @elementstack.push obj
+ else
+ log WARN, "Class not found: #{class_name}"
+ @elementstack.push nil
+ end
+
+ attributes.each_pair do |attr, value|
+ set_attribute_internal(attr, value)
+ end
+ end
+
+ def end_tag(prefix, tag)
+ @elementstack.pop
+ end
+
+ ResolverDescription = Struct.new(:object, :attribute, :value)
+
+ def set_attribute(attr, value)
+ # do nothing, already handled by start_tag/set_attribute_internal
+ end
+
+ def set_attribute_internal(attr, value)
+ return unless @elementstack.last
+ eFeat = eAllStructuralFeatures(@elementstack.last).find{|a| a.name == attr}
+ if eFeat.is_a?(EReference)
+ rd = ResolverDescription.new
+ rd.object = @elementstack.last
+ rd.attribute = attr
+ rd.value = value
+ @resolver_descs << rd
+ elsif eFeat
+ value = true if value == "true" && eFeat.eType == EBoolean
+ value = false if value == "false" && eFeat.eType == EBoolean
+ value = value.to_i if eFeat.eType == EInt || eFeat.eType == ELong
+ @elementstack.last.setGeneric(attr, value)
+ else
+ log WARN, "Feature not found: #{attr} on #{@elementstack.last}"
+ end
+ end
+
+ def instantiate(str)
+ @resolver_descs = []
+# puts "Instantiating ..."
+ super(str, 1000)
+ rootpackage = @env.find(:class => EPackage).first
+# puts "Resolving ..."
+ @resolver_descs.each do |rd|
+ refed = find_referenced(rootpackage, rd.value)
+ feature = eAllStructuralFeatures(rd.object).find{|f| f.name == rd.attribute}
+ raise StandardError.new("StructuralFeature not found: #{rd.attribute}") unless feature
+ if feature.many
+ rd.object.setGeneric(feature.name, refed)
+ else
+ rd.object.setGeneric(feature.name, refed.first)
+ end
+ end
+ end
+
+ def eAllReferences(element)
+ @eAllReferences ||= {}
+ @eAllReferences[element.class] ||= element.class.ecore.eAllReferences
+ end
+
+ def eAllAttributes(element)
+ @eAllAttributes ||= {}
+ @eAllAttributes[element.class] ||= element.class.ecore.eAllAttributes
+ end
+
+ def eAllStructuralFeatures(element)
+ @eAllStructuralFeatures ||= {}
+ @eAllStructuralFeatures[element.class] ||= element.class.ecore.eAllStructuralFeatures
+ end
+
+ def find_referenced(context, desc)
+ desc.split(/\s+/).collect do |r|
+ if r =~ /^#([^\/]+)$/
+ @element_by_id[$1]
+ elsif r =~ /^#\/\d*\/([\w\/]+)/
+ find_in_context(context, $1.split('/'))
+ elsif r =~ /#\/\/(\w+)$/
+ case $1
+ when "EString"; RGen::ECore::EString
+ when "EInt"; RGen::ECore::EInt
+ when "ELong"; RGen::ECore::ELong
+ when "EBoolean"; RGen::ECore::EBoolean
+ when "EFloat"; RGen::ECore::EFloat
+ when "EJavaObject"; RGen::ECore::EJavaObject
+ when "EJavaClass"; RGen::ECore::EJavaClass
+ end
+ end
+ end.compact
+ end
+
+ def find_in_context(context, desc_elements)
+ if context.is_a?(EPackage)
+ r = (context.eClassifiers + context.eSubpackages).find{|c| c.name == desc_elements.first}
+ elsif context.is_a?(EClass)
+ r = context.eStructuralFeatures.find{|s| s.name == desc_elements.first}
+ else
+ raise StandardError.new("Don't know how to find #{desc_elements.join('/')} in context #{context}")
+ end
+ if r
+ if desc_elements.size > 1
+ find_in_context(r, desc_elements[1..-1])
+ else
+ r
+ end
+ else
+ log WARN, "Can not follow path, element #{desc_elements.first} not found within #{context}(#{context.name})"
+ end
+ end
+
+ def log(level, msg)
+ puts %w(INFO WARN ERROR)[level] + ": " + msg if level >= @loglevel
+ end
+end
diff --git a/lib/puppet/vendor/rgen/lib/rgen/instantiator/json_instantiator.rb b/lib/puppet/vendor/rgen/lib/rgen/instantiator/json_instantiator.rb
new file mode 100644
index 000000000..1aa4d951e
--- /dev/null
+++ b/lib/puppet/vendor/rgen/lib/rgen/instantiator/json_instantiator.rb
@@ -0,0 +1,126 @@
+require 'rgen/instantiator/qualified_name_resolver'
+require 'rgen/instantiator/json_parser'
+
+module RGen
+
+module Instantiator
+
+# JsonInstantiator is used to create RGen models from JSON.
+#
+# Each JSON object needs to have an attribute "_class" which is used to find
+# the metamodel class to instantiate. The value of "_class" should be the
+# the relative qualified class name within the root package as a string.
+#
+# If the option "short_class_names" is set to true, unqualified class names can be used.
+# In this case, metamodel classes are searched in the metamodel root package first.
+# If this search is not successful, all subpackages will be searched for the class name.
+#
+class JsonInstantiator
+
+ # Model elements will be created in evironment +env+,
+ # classes are looked for in metamodel package module +mm+,
+ # +options+ include:
+ # short_class_names: if true subpackages will be searched for unqualifed class names (default: true)
+ # ignore_keys: an array of json object key names which are to be ignored (default: none)
+ #
+ # The options are also passed to the underlying QualifiedNameResolver.
+ #
+ def initialize(env, mm, options={})
+ @env = env
+ @mm = mm
+ @options = options
+ @short_class_names = !@options.has_key?(:short_class_names) || @options[:short_class_names]
+ @ignore_keys = @options[:ignore_keys] || []
+ @unresolvedReferences = []
+ @classes = {}
+ @classes_flat = {}
+ mm.ecore.eAllClasses.each do |c|
+ @classes[c.instanceClass.name.sub(mm.name+"::","")] = c
+ @classes_flat[c.name] = c
+ end
+ @parser = JsonParser.new(self)
+ end
+
+ # Creates the elements described by the json string +str+.
+ # Returns an array of ReferenceResolver::UnresolvedReference
+ # describing the references which could not be resolved
+ #
+ # Options:
+ # :root_elements: if an array is provided, it will be filled with the root elements
+ #
+ def instantiate(str, options={})
+ root = @parser.parse(str)
+ if options[:root_elements].is_a?(Array)
+ options[:root_elements].clear
+ root.each{|r| options[:root_elements] << r}
+ end
+ resolver = QualifiedNameResolver.new(root, @options)
+ resolver.resolveReferences(@unresolvedReferences)
+ end
+
+ def createObject(hash)
+ className = hash["_class"]
+ # hashes without a _class key are returned as is
+ return hash unless className
+ if @classes[className]
+ clazz = @classes[className].instanceClass
+ elsif @short_class_names && @classes_flat[className]
+ clazz = @classes_flat[className].instanceClass
+ else
+ raise "class not found: #{className}"
+ end
+ hash.delete("_class")
+ @ignore_keys.each do |k|
+ hash.delete(k)
+ end
+ urefs = []
+ hash.keys.each do |k|
+ f = eFeature(k, clazz)
+ hash[k] = [hash[k]] if f.many && !hash[k].is_a?(Array)
+ if f.is_a?(RGen::ECore::EReference) && !f.containment
+ if f.many
+ idents = hash[k]
+ hash[k] = idents.collect do |i|
+ proxy = RGen::MetamodelBuilder::MMProxy.new(i)
+ urefs << ReferenceResolver::UnresolvedReference.new(nil, k, proxy)
+ proxy
+ end
+ else
+ ident = hash[k]
+ ident = ident.first if ident.is_a?(Array)
+ proxy = RGen::MetamodelBuilder::MMProxy.new(ident)
+ hash[k] = proxy
+ urefs << ReferenceResolver::UnresolvedReference.new(nil, k, proxy)
+ end
+ elsif f.eType.is_a?(RGen::ECore::EEnum)
+ hash[k] = hash[k].to_sym
+ elsif f.eType.instanceClassName == "Float"
+ hash[k] = hash[k].to_f
+ end
+ end
+ obj = @env.new(clazz, hash)
+ urefs.each do |r|
+ r.element = obj
+ @unresolvedReferences << r
+ end
+ obj
+ end
+
+ private
+
+ def eFeature(name, clazz)
+ @eFeature ||= {}
+ @eFeature[clazz] ||= {}
+ unless @eFeature[clazz][name]
+ feature = clazz.ecore.eAllStructuralFeatures.find{|f| f.name == name}
+ raise "feature '#{name}' not found in class '#{clazz}'" unless feature
+ end
+ @eFeature[clazz][name] ||= feature
+ end
+
+end
+
+end
+
+end
+
diff --git a/lib/puppet/vendor/rgen/lib/rgen/instantiator/json_parser.rb b/lib/puppet/vendor/rgen/lib/rgen/instantiator/json_parser.rb
new file mode 100644
index 000000000..02c04a0e8
--- /dev/null
+++ b/lib/puppet/vendor/rgen/lib/rgen/instantiator/json_parser.rb
@@ -0,0 +1,331 @@
+#
+# DO NOT MODIFY!!!!
+# This file is automatically generated by racc 1.4.5
+# from racc grammer file "json_parser.y".
+#
+
+require 'racc/parser'
+
+
+
+module RGen
+
+module Instantiator
+
+
+class JsonParser < Racc::Parser
+
+module_eval <<'..end json_parser.y modeval..id3d5fb611e2', 'json_parser.y', 38
+
+ ParserToken = Struct.new(:line, :file, :value)
+
+ def initialize(instantiator)
+ @instantiator = instantiator
+ end
+
+ def parse(str, file=nil)
+ @q = []
+ line = 1
+
+ until str.empty?
+ case str
+ when /\A\n/
+ str = $'
+ line +=1
+ when /\A\s+/
+ str = $'
+ when /\A([-+]?\d+\.\d+)/
+ str = $'
+ @q << [:FLOAT, ParserToken.new(line, file, $1)]
+ when /\A([-+]?\d+)/
+ str = $'
+ @q << [:INTEGER, ParserToken.new(line, file, $1)]
+ when /\A"((?:[^"\\]|\\"|\\\\|\\[^"\\])*)"/
+ str = $'
+ sval = $1
+ sval.gsub!('\\\\','\\')
+ sval.gsub!('\\"','"')
+ sval.gsub!('\\n',"\n")
+ sval.gsub!('\\r',"\r")
+ sval.gsub!('\\t',"\t")
+ sval.gsub!('\\f',"\f")
+ sval.gsub!('\\b',"\b")
+ @q << [:STRING, ParserToken.new(line, file, sval)]
+ when /\A(\{|\}|\[|\]|,|:|true|false)/
+ str = $'
+ @q << [$1, ParserToken.new(line, file, $1)]
+ else
+ raise "parse error in line #{line} on "+str[0..20].inspect+"..."
+ end
+ end
+ @q.push [false, ParserToken.new(line, file, '$end')]
+ do_parse
+ end
+
+ def next_token
+ r = @q.shift
+ r
+ end
+
+..end json_parser.y modeval..id3d5fb611e2
+
+##### racc 1.4.5 generates ###
+
+racc_reduce_table = [
+ 0, 0, :racc_error,
+ 1, 14, :_reduce_1,
+ 3, 16, :_reduce_2,
+ 2, 16, :_reduce_3,
+ 1, 17, :_reduce_4,
+ 3, 17, :_reduce_5,
+ 3, 18, :_reduce_6,
+ 2, 18, :_reduce_7,
+ 1, 19, :_reduce_8,
+ 3, 19, :_reduce_9,
+ 3, 20, :_reduce_10,
+ 1, 15, :_reduce_11,
+ 1, 15, :_reduce_12,
+ 1, 15, :_reduce_13,
+ 1, 15, :_reduce_14,
+ 1, 15, :_reduce_15,
+ 1, 15, :_reduce_16,
+ 1, 15, :_reduce_17 ]
+
+racc_reduce_n = 18
+
+racc_shift_n = 29
+
+racc_action_table = [
+ 3, 16, 17, 7, 22, 8, 21, 10, 11, 1,
+ 2, 3, 12, 23, 7, 24, 8, 25, 10, 11,
+ 1, 2, 3, 20, 15, 7, 17, 8, nil, 10,
+ 11, 1, 2, 3, nil, nil, 7, nil, 8, nil,
+ 10, 11, 1, 2 ]
+
+racc_action_check = [
+ 0, 7, 7, 0, 15, 0, 14, 0, 0, 0,
+ 0, 3, 3, 17, 3, 18, 3, 19, 3, 3,
+ 3, 3, 20, 13, 4, 20, 25, 20, nil, 20,
+ 20, 20, 20, 23, nil, nil, 23, nil, 23, nil,
+ 23, 23, 23, 23 ]
+
+racc_action_pointer = [
+ -2, nil, nil, 9, 24, nil, nil, -5, nil, nil,
+ nil, nil, nil, 19, 3, 4, nil, 5, 9, 13,
+ 20, nil, nil, 31, nil, 19, nil, nil, nil ]
+
+racc_action_default = [
+ -18, -16, -17, -18, -18, -1, -11, -18, -13, -12,
+ -14, -15, -3, -4, -18, -18, -7, -18, -18, -8,
+ -18, -2, 29, -18, -6, -18, -5, -10, -9 ]
+
+racc_goto_table = [
+ 5, 18, 4, 14, nil, nil, nil, nil, nil, nil,
+ nil, nil, nil, nil, nil, nil, nil, nil, nil, 28,
+ 26, nil, nil, 27 ]
+
+racc_goto_check = [
+ 2, 6, 1, 4, nil, nil, nil, nil, nil, nil,
+ nil, nil, nil, nil, nil, nil, nil, nil, nil, 6,
+ 4, nil, nil, 2 ]
+
+racc_goto_pointer = [
+ nil, 2, 0, nil, 0, nil, -6, nil ]
+
+racc_goto_default = [
+ nil, nil, 13, 6, nil, 9, nil, 19 ]
+
+racc_token_table = {
+ false => 0,
+ Object.new => 1,
+ "[" => 2,
+ "]" => 3,
+ "," => 4,
+ "{" => 5,
+ "}" => 6,
+ :STRING => 7,
+ ":" => 8,
+ :INTEGER => 9,
+ :FLOAT => 10,
+ "true" => 11,
+ "false" => 12 }
+
+racc_use_result_var = true
+
+racc_nt_base = 13
+
+Racc_arg = [
+ racc_action_table,
+ racc_action_check,
+ racc_action_default,
+ racc_action_pointer,
+ racc_goto_table,
+ racc_goto_check,
+ racc_goto_default,
+ racc_goto_pointer,
+ racc_nt_base,
+ racc_reduce_table,
+ racc_token_table,
+ racc_shift_n,
+ racc_reduce_n,
+ racc_use_result_var ]
+
+Racc_token_to_s_table = [
+'$end',
+'error',
+'"["',
+'"]"',
+'","',
+'"{"',
+'"}"',
+'STRING',
+'":"',
+'INTEGER',
+'FLOAT',
+'"true"',
+'"false"',
+'$start',
+'json',
+'value',
+'array',
+'valueList',
+'object',
+'memberList',
+'member']
+
+Racc_debug_parser = false
+
+##### racc system variables end #####
+
+ # reduce 0 omitted
+
+module_eval <<'.,.,', 'json_parser.y', 4
+ def _reduce_1( val, _values, result )
+ result = val[0]
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'json_parser.y', 6
+ def _reduce_2( val, _values, result )
+ result = val[1]
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'json_parser.y', 7
+ def _reduce_3( val, _values, result )
+ result = []
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'json_parser.y', 9
+ def _reduce_4( val, _values, result )
+ result = [ val[0] ]
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'json_parser.y', 10
+ def _reduce_5( val, _values, result )
+ result = [ val[0] ] + val[2]
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'json_parser.y', 12
+ def _reduce_6( val, _values, result )
+ result = @instantiator.createObject(val[1])
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'json_parser.y', 13
+ def _reduce_7( val, _values, result )
+ result = nil
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'json_parser.y', 15
+ def _reduce_8( val, _values, result )
+ result = val[0]
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'json_parser.y', 16
+ def _reduce_9( val, _values, result )
+ result = val[0].merge(val[2])
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'json_parser.y', 18
+ def _reduce_10( val, _values, result )
+ result = {val[0].value => val[2]}
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'json_parser.y', 20
+ def _reduce_11( val, _values, result )
+ result = val[0]
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'json_parser.y', 21
+ def _reduce_12( val, _values, result )
+ result = val[0]
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'json_parser.y', 22
+ def _reduce_13( val, _values, result )
+ result = val[0].value
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'json_parser.y', 23
+ def _reduce_14( val, _values, result )
+ result = val[0].value.to_i
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'json_parser.y', 24
+ def _reduce_15( val, _values, result )
+ result = val[0].value.to_f
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'json_parser.y', 25
+ def _reduce_16( val, _values, result )
+ result = true
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'json_parser.y', 26
+ def _reduce_17( val, _values, result )
+ result = false
+ result
+ end
+.,.,
+
+ def _reduce_none( val, _values, result )
+ result
+ end
+
+end # class JsonParser
+
+
+end
+
+end
+
diff --git a/lib/puppet/vendor/rgen/lib/rgen/instantiator/json_parser.y b/lib/puppet/vendor/rgen/lib/rgen/instantiator/json_parser.y
new file mode 100644
index 000000000..7bdc46464
--- /dev/null
+++ b/lib/puppet/vendor/rgen/lib/rgen/instantiator/json_parser.y
@@ -0,0 +1,94 @@
+class JsonParser
+
+rule
+
+ json: value { result = val[0] }
+
+ array: "[" valueList "]" { result = val[1] }
+ | "[" "]" { result = [] }
+
+ valueList: value { result = [ val[0] ] }
+ | value "," valueList { result = [ val[0] ] + val[2] }
+
+ object: "{" memberList "}" { result = @instantiator.createObject(val[1]) }
+ | "{" "}" { result = nil }
+
+ memberList: member { result = val[0] }
+ | member "," memberList { result = val[0].merge(val[2]) }
+
+ member: STRING ":" value { result = {val[0].value => val[2]} }
+
+ value: array { result = val[0] }
+ | object { result = val[0] }
+ | STRING { result = val[0].value }
+ | INTEGER { result = val[0].value.to_i }
+ | FLOAT { result = val[0].value.to_f }
+ | "true" { result = true }
+ | "false" { result = false }
+
+end
+
+---- header
+
+module RGen
+
+module Instantiator
+
+---- inner
+
+ ParserToken = Struct.new(:line, :file, :value)
+
+ def initialize(instantiator)
+ @instantiator = instantiator
+ end
+
+ def parse(str, file=nil)
+ @q = []
+ line = 1
+
+ until str.empty?
+ case str
+ when /\A\n/
+ str = $'
+ line +=1
+ when /\A\s+/
+ str = $'
+ when /\A([-+]?\d+\.\d+)/
+ str = $'
+ @q << [:FLOAT, ParserToken.new(line, file, $1)]
+ when /\A([-+]?\d+)/
+ str = $'
+ @q << [:INTEGER, ParserToken.new(line, file, $1)]
+ when /\A"((?:[^"\\]|\\"|\\\\|\\[^"\\])*)"/
+ str = $'
+ sval = $1
+ sval.gsub!('\\\\','\\')
+ sval.gsub!('\\"','"')
+ sval.gsub!('\\n',"\n")
+ sval.gsub!('\\r',"\r")
+ sval.gsub!('\\t',"\t")
+ sval.gsub!('\\f',"\f")
+ sval.gsub!('\\b',"\b")
+ @q << [:STRING, ParserToken.new(line, file, sval)]
+ when /\A(\{|\}|\[|\]|,|:|true|false)/
+ str = $'
+ @q << [$1, ParserToken.new(line, file, $1)]
+ else
+ raise "parse error in line #{line} on "+str[0..20].inspect+"..."
+ end
+ end
+ @q.push [false, ParserToken.new(line, file, '$end')]
+ do_parse
+ end
+
+ def next_token
+ r = @q.shift
+ r
+ end
+
+---- footer
+
+end
+
+end
+
diff --git a/lib/puppet/vendor/rgen/lib/rgen/instantiator/nodebased_xml_instantiator.rb b/lib/puppet/vendor/rgen/lib/rgen/instantiator/nodebased_xml_instantiator.rb
new file mode 100644
index 000000000..5abc05523
--- /dev/null
+++ b/lib/puppet/vendor/rgen/lib/rgen/instantiator/nodebased_xml_instantiator.rb
@@ -0,0 +1,137 @@
+require 'rgen/metamodel_builder'
+require 'rgen/instantiator/abstract_instantiator'
+require 'nokogiri'
+
+module RGen
+
+module Instantiator
+
+class NodebasedXMLInstantiator < AbstractInstantiator
+
+ class << self
+
+ # The prune level is the number of parent/children associations which
+ # is kept when the instantiator ascents the XML tree.
+ # If the level is 2, information for the node's children and the childrens'
+ # children will be available as an XMLNodeDescriptor object.
+ # If the level is 0 no pruning will take place, i.e. the whole information
+ # is kept until the end of the instantiation process. 0 is default.
+ def set_prune_level(level)
+ @prune_level = level
+ end
+
+ def prune_level # :nodoc:
+ @prune_level ||= 0
+ end
+
+ end
+
+ class XMLNodeDescriptor
+ attr_reader :namespace, :qtag, :prefix, :tag, :parent, :attributes, :chardata
+ attr_accessor :object, :children
+
+ def initialize(ns, qtag, prefix, tag, parent, children, attributes)
+ @namespace, @qtag, @prefix, @tag, @parent, @children, @attributes =
+ ns, qtag, prefix, tag, parent, children, attributes
+ @parent.children << self if @parent
+ @chardata = []
+ end
+ end
+
+ class Visitor < Nokogiri::XML::SAX::Document
+ attr_reader :namespaces
+
+ def initialize(inst)
+ @instantiator = inst
+ @namespaces = {}
+ end
+
+ def start_element_namespace(tag, attributes, prefix, uri, ns)
+ ns.each{|n| @namespaces[n[0]] = n[1]}
+ attrs = {}
+ attributes.each{|a| attrs[a.prefix ? a.prefix+":"+a.localname : a.localname] = a.value}
+ qname = prefix ? prefix+":"+tag : tag
+ @instantiator.start_element(uri, qname, prefix, tag, attrs)
+ end
+
+ def end_element(name)
+ @instantiator.end_element
+ end
+
+ def characters(str)
+ @instantiator.on_chardata(str)
+ end
+ end
+
+ def initialize(env)
+ super
+ @env = env
+ @stack = []
+ end
+
+ def instantiate_file(file)
+ File.open(file) { |f| parse(f.read)}
+ resolve
+ end
+
+ def instantiate(text)
+ parse(text)
+ resolve
+ end
+
+ def parse(src)
+ @visitor = Visitor.new(self)
+ parser = Nokogiri::XML::SAX::Parser.new(@visitor)
+ parser.parse(src)
+ @visitor = nil
+ end
+
+ def start_element(ns, qtag, prefix, tag, attributes)
+ node = XMLNodeDescriptor.new(ns, qtag, prefix, tag, @stack[-1], [], attributes)
+ @stack.push node
+ on_descent(node)
+ end
+
+ def end_element
+ node = @stack.pop
+ on_ascent(node)
+ prune_children(node, self.class.prune_level - 1) if self.class.prune_level > 0
+ end
+
+ def on_chardata(str)
+ node = @stack.last
+ node.chardata << str
+ end
+
+ # This method is called when the XML parser goes down the tree.
+ # An XMLNodeDescriptor +node+ describes the current node.
+ # Implementing classes must overwrite this method.
+ def on_descent(node)
+ raise "Overwrite this method !"
+ end
+
+ # This method is called when the XML parser goes up the tree.
+ # An XMLNodeDescriptor +node+ describes the current node.
+ # Implementing classes must overwrite this method.
+ def on_ascent(node)
+ raise "Overwrite this method !"
+ end
+
+ def namespaces
+ @visitor.namespaces if @visitor
+ end
+
+ private
+
+ def prune_children(node, level)
+ if level == 0
+ node.children = nil
+ else
+ node.children.each { |c| prune_children(c, level-1) }
+ end
+ end
+end
+
+end
+
+end
diff --git a/lib/puppet/vendor/rgen/lib/rgen/instantiator/qualified_name_resolver.rb b/lib/puppet/vendor/rgen/lib/rgen/instantiator/qualified_name_resolver.rb
new file mode 100644
index 000000000..7fe1c7cc2
--- /dev/null
+++ b/lib/puppet/vendor/rgen/lib/rgen/instantiator/qualified_name_resolver.rb
@@ -0,0 +1,97 @@
+require 'rgen/instantiator/reference_resolver'
+
+module RGen
+
+module Instantiator
+
+# This is a resolver resolving element identifiers which are qualified names.
+class QualifiedNameResolver
+
+ attr_reader :nameAttribute
+ attr_reader :separator
+ attr_reader :leadingSeparator
+
+ def initialize(rootElements, options={})
+ @rootElements = rootElements
+ @nameAttribute = options[:nameAttribute] || "name"
+ @separator = options[:separator] || "/"
+ @leadingSeparator = options.has_key?(:leadingSeparator) ? options[:leadingSeparator] : true
+ @elementByQName = {}
+ @visitedQName = {}
+ @childReferences = {}
+ @resolverDelegate = ReferenceResolver.new(:identifier_resolver => method(:resolveIdentifier))
+ end
+
+ def resolveIdentifier(qualifiedName)
+ return @elementByQName[qualifiedName] if @elementByQName.has_key?(qualifiedName)
+ path = qualifiedName.split(separator).reject{|s| s == ""}
+ if path.size > 1
+ parentQName = (leadingSeparator ? separator : "") + path[0..-2].join(separator)
+ parents = resolveIdentifier(parentQName)
+ parents = [parents].compact unless parents.is_a?(Array)
+ children = parents.collect{|p| allNamedChildren(p)}.flatten
+ elsif path.size == 1
+ parentQName = ""
+ children = allRootNamedChildren
+ else
+ return @elementByQName[qualifiedName] = nil
+ end
+ # if the parent was already visited all matching elements are the hash
+ if !@visitedQName[parentQName]
+ children.each do |c|
+ name = c.send(nameAttribute)
+ if name
+ qname = parentQName + ((parentQName != "" || leadingSeparator) ? separator : "") + name
+ existing = @elementByQName[qname]
+ if existing
+ @elementByQName[qname] = [existing] unless existing.is_a?(Array)
+ @elementByQName[qname] << c
+ else
+ @elementByQName[qname] = c
+ end
+ end
+ end
+ # all named children of praent have been checked and hashed
+ @visitedQName[parentQName] = true
+ end
+ @elementByQName[qualifiedName] ||= nil
+ end
+
+ def resolveReferences(unresolvedReferences, problems=[])
+ @resolverDelegate.resolve(unresolvedReferences, :problems => problems)
+ end
+
+ private
+
+ def allNamedChildren(element)
+ childReferences(element.class).collect do |r|
+ element.getGenericAsArray(r.name).collect do |c|
+ if c.respond_to?(nameAttribute)
+ c
+ else
+ allNamedChildren(c)
+ end
+ end
+ end.flatten
+ end
+
+ def allRootNamedChildren
+ @rootElements.collect do |e|
+ if e.respond_to?(nameAttribute)
+ e
+ else
+ allNamedChildren(e)
+ end
+ end.flatten
+ end
+
+ def childReferences(clazz)
+ @childReferences[clazz] ||= clazz.ecore.eAllReferences.select{|r| r.containment}
+ end
+
+end
+
+end
+
+end
+
diff --git a/lib/puppet/vendor/rgen/lib/rgen/instantiator/reference_resolver.rb b/lib/puppet/vendor/rgen/lib/rgen/instantiator/reference_resolver.rb
new file mode 100644
index 000000000..87a4a834a
--- /dev/null
+++ b/lib/puppet/vendor/rgen/lib/rgen/instantiator/reference_resolver.rb
@@ -0,0 +1,128 @@
+require 'rgen/instantiator/resolution_helper'
+
+module RGen
+
+module Instantiator
+
+# The ReferenceResolver can be used to resolve unresolved references, i.e. instances
+# of class UnresolvedReference
+#
+# There are two ways how this can be used:
+# 1. the identifiers and associated model elements are added upfront using +add_identifier+
+# 2. register an :identifier_resolver with the constructor, which will be invoked
+# for every unresolved identifier
+#
+class ReferenceResolver
+
+ # Instances of this class represent information about not yet resolved references.
+ # This consists of the +element+ and metamodel +feature_name+ which hold/is to hold the
+ # reference and the +proxy+ object which is the placeholder for the reference.
+ # If the reference could not be resolved because the target type does not match the
+ # feature type, the flag +target_type_error+ will be set.
+ #
+ class UnresolvedReference
+ attr_reader :feature_name, :proxy
+ attr_accessor :element, :target_type_error
+ def initialize(element, feature_name, proxy)
+ @element = element
+ @feature_name = feature_name
+ @proxy = proxy
+ end
+ end
+
+ # Create a reference resolver, options:
+ #
+ # :identifier_resolver:
+ # a proc which is called with an identifier and which should return the associated element
+ # in case the identifier is not uniq, the proc may return multiple values
+ # default: lookup element in internal map
+ #
+ def initialize(options={})
+ @identifier_resolver = options[:identifier_resolver]
+ @identifier_map = {}
+ end
+
+ # Add an +identifer+ / +element+ pair which will be used for looking up unresolved identifers
+ def add_identifier(ident, element)
+ map_entry = @identifier_map[ident]
+ if map_entry
+ if map_entry.is_a?(Array)
+ map_entry << element
+ else
+ @identifier_map[ident] = [map_entry, element]
+ end
+ else
+ @identifier_map[ident] = element
+ end
+ end
+
+ # Tries to resolve the given +unresolved_refs+. If resolution is successful, the proxy object
+ # will be removed, otherwise there will be an error description in the problems array.
+ # In case the resolved target element's type is not valid for the given feature, the
+ # +target_type_error+ flag will be set on the unresolved reference.
+ # Returns an array of the references which are still unresolved. Options:
+ #
+ # :problems
+ # an array to which problems will be appended
+ #
+ # :on_resolve
+ # a proc which will be called for every sucessful resolution, receives the unresolved
+ # reference as well as to new target element
+ #
+ # :use_target_type
+ # use the expected target type to narrow the set of possible targets
+ # (i.e. ignore targets with wrong type)
+ #
+ # :failed_resolutions
+ # a Hash which will receive an entry for each failed resolution for which at least one
+ # target element was found (wrong target type, or target not unique).
+ # hash key is the uref, hash value is the target element or the Array of target elements
+ #
+ def resolve(unresolved_refs, options={})
+ problems = options[:problems] || []
+ still_unresolved_refs = []
+ failed_resolutions = options[:failed_resolutions] || {}
+ unresolved_refs.each do |ur|
+ if @identifier_resolver
+ target = @identifier_resolver.call(ur.proxy.targetIdentifier)
+ else
+ target = @identifier_map[ur.proxy.targetIdentifier]
+ end
+ target = [target].compact unless target.is_a?(Array)
+ if options[:use_target_type]
+ feature = ur.element.class.ecore.eAllReferences.find{|r| r.name == ur.feature_name}
+ target = target.select{|e| e.is_a?(feature.eType.instanceClass)}
+ end
+ if target.size == 1
+ status = ResolutionHelper.set_uref_target(ur, target[0])
+ if status == :success
+ options[:on_resolve] && options[:on_resolve].call(ur, target[0])
+ elsif status == :type_error
+ ur.target_type_error = true
+ problems << type_error_message(target[0])
+ still_unresolved_refs << ur
+ failed_resolutions[ur] = target[0]
+ end
+ elsif target.size > 1
+ problems << "identifier #{ur.proxy.targetIdentifier} not uniq"
+ still_unresolved_refs << ur
+ failed_resolutions[ur] = target
+ else
+ problems << "identifier #{ur.proxy.targetIdentifier} not found"
+ still_unresolved_refs << ur
+ end
+ end
+ still_unresolved_refs
+ end
+
+ private
+
+ def type_error_message(target)
+ "invalid target type #{target.class}"
+ end
+
+end
+
+end
+
+end
diff --git a/lib/puppet/vendor/rgen/lib/rgen/instantiator/resolution_helper.rb b/lib/puppet/vendor/rgen/lib/rgen/instantiator/resolution_helper.rb
new file mode 100644
index 000000000..5452fec4c
--- /dev/null
+++ b/lib/puppet/vendor/rgen/lib/rgen/instantiator/resolution_helper.rb
@@ -0,0 +1,47 @@
+module RGen
+module Instantiator
+
+module ResolutionHelper
+
+# sets the target of an unresolved reference in the model
+# returns :type_error if the target is of wrong type, otherwise :success
+#
+def self.set_uref_target(uref, target)
+ refs = uref.element.getGeneric(uref.feature_name)
+ if refs.is_a?(Array)
+ index = refs.index(uref.proxy)
+ uref.element.removeGeneric(uref.feature_name, uref.proxy)
+ begin
+ uref.element.addGeneric(uref.feature_name, target, index)
+ rescue StandardError => e
+ if is_type_error?(e)
+ uref.element.addGeneric(uref.feature_name, uref.proxy, index)
+ return :type_error
+ else
+ raise
+ end
+ end
+ else
+ begin
+ # this will replace the proxy
+ uref.element.setGeneric(uref.feature_name, target)
+ rescue StandardError => e
+ if is_type_error?(e)
+ return :type_error
+ else
+ raise
+ end
+ end
+ end
+ :success
+end
+
+def self.is_type_error?(e)
+ e.message =~ /Can not use a .* where a .* is expected/
+end
+
+end
+
+end
+end
+
diff --git a/lib/puppet/vendor/rgen/lib/rgen/instantiator/xmi11_instantiator.rb b/lib/puppet/vendor/rgen/lib/rgen/instantiator/xmi11_instantiator.rb
new file mode 100644
index 000000000..af9657b16
--- /dev/null
+++ b/lib/puppet/vendor/rgen/lib/rgen/instantiator/xmi11_instantiator.rb
@@ -0,0 +1,168 @@
+require 'rgen/ecore/ecore'
+require 'rgen/instantiator/abstract_xml_instantiator'
+require 'rgen/array_extensions'
+
+class XMI11Instantiator < AbstractXMLInstantiator
+
+ include RGen::ECore
+
+ ResolverDescription = Struct.new(:object, :attribute, :value, :many)
+
+ INFO = 0
+ WARN = 1
+ ERROR = 2
+
+ def initialize(env, fix_map={}, loglevel=ERROR)
+ @env = env
+ @fix_map = fix_map
+ @loglevel = loglevel
+ @rolestack = []
+ @elementstack = []
+ end
+
+ def add_metamodel(ns, mod)
+ @ns_module_map ||={}
+ @ns_module_map[ns] = mod
+ end
+
+ def instantiate(str)
+ @resolver_descs = []
+ @element_by_id = {}
+ super(str, 1000)
+ @resolver_descs.each do |rd|
+ if rd.many
+ newval = rd.value.split(" ").collect{|v| @element_by_id[v]}
+ else
+ newval = @element_by_id[rd.value]
+ end
+ log WARN, "Could not resolve reference #{rd.attribute} on #{rd.object}" unless newval
+ begin
+ rd.object.setGeneric(rd.attribute,newval)
+ rescue Exception
+ log WARN, "Could not set reference #{rd.attribute} on #{rd.object}"
+ end
+ end
+ end
+
+ def start_tag(prefix, tag, namespaces, attributes)
+ if tag =~ /\w+\.(\w+)/
+ # XMI role
+ role_name = map_feature_name($1) || $1
+ eRef = @elementstack.last && eAllReferences(@elementstack.last).find{|r|r.name == role_name}
+ log WARN, "No reference found for #{role_name} on #{@elementstack.last}" unless eRef
+ @rolestack.push eRef
+ elsif attributes["xmi.idref"]
+ # reference
+ rd = ResolverDescription.new
+ rd.object = @elementstack.last
+ rd.attribute = @rolestack.last.name
+ rd.value = attributes["xmi.idref"]
+ rd.many = @rolestack.last.many
+ @resolver_descs << rd
+ @elementstack.push nil
+ else
+ # model element
+ value = map_tag(tag, attributes) || tag
+ if value.is_a?(String)
+ mod = @ns_module_map[namespaces[prefix]]
+ unless mod
+ log WARN, "Ignoring tag #{tag}"
+ return
+ end
+ value = mod.const_get(value).new
+ end
+ @env << value
+ eRef = @rolestack.last
+ if eRef && eRef.many
+ @elementstack.last.addGeneric(eRef.name, value)
+ elsif eRef
+ @elementstack.last.setGeneric(eRef.name, value)
+ end
+ @elementstack.push value
+ end
+ end
+
+ def end_tag(prefix, tag)
+ if tag =~ /\w+\.(\w+)/
+ @rolestack.pop
+ else
+ @elementstack.pop
+ end
+ end
+
+ def set_attribute(attr, value)
+ return unless @elementstack.last
+ if attr == "xmi.id"
+ @element_by_id[value] = @elementstack.last
+ else
+ attr_name = map_feature_name(attr) || attr
+ eFeat = eAllStructuralFeatures(@elementstack.last).find{|a| a.name == attr_name}
+ unless eFeat
+ log WARN, "No structural feature found for #{attr_name} on #{@elementstack.last}"
+ return
+ end
+ if eFeat.is_a?(RGen::ECore::EReference)
+ if map_feature_value(attr_name, value).is_a?(eFeat.eType.instanceClass)
+ @elementstack.last.setGeneric(attr_name, map_feature_value(attr_name, value))
+ else
+ rd = ResolverDescription.new
+ rd.object = @elementstack.last
+ rd.attribute = attr_name
+ rd.value = value
+ rd.many = eFeat.many
+ @resolver_descs << rd
+ end
+ else
+ value = map_feature_value(attr_name, value) || value
+ value = true if value == "true" && eFeat.eType == EBoolean
+ value = false if value == "false" && eFeat.eType == EBoolean
+ value = value.to_i if eFeat.eType == EInt || eFeat.eType == ELong
+ value = value.to_f if eFeat.eType == EFloat
+ value = value.to_sym if eFeat.eType.is_a?(EEnum)
+ @elementstack.last.setGeneric(attr_name, value)
+ end
+ end
+ end
+
+ private
+
+ def map_tag(tag, attributes)
+ tag_map = @fix_map[:tags] || {}
+ value = tag_map[tag]
+ if value.is_a?(Proc)
+ value.call(tag, attributes)
+ else
+ value
+ end
+ end
+
+ def map_feature_name(name)
+ name_map = @fix_map[:feature_names] || {}
+ name_map[name]
+ end
+
+ def map_feature_value(attr_name, value)
+ value_map = @fix_map[:feature_values] || {}
+ map = value_map[attr_name]
+ if map.is_a?(Hash)
+ map[value]
+ elsif map.is_a?(Proc)
+ map.call(value)
+ end
+ end
+
+ def log(level, msg)
+ puts %w(INFO WARN ERROR)[level] + ": " + msg if level >= @loglevel
+ end
+
+ def eAllReferences(element)
+ @eAllReferences ||= {}
+ @eAllReferences[element.class] ||= element.class.ecore.eAllReferences
+ end
+
+ def eAllStructuralFeatures(element)
+ @eAllStructuralFeatures ||= {}
+ @eAllStructuralFeatures[element.class] ||= element.class.ecore.eAllStructuralFeatures
+ end
+
+end
diff --git a/lib/puppet/vendor/rgen/lib/rgen/metamodel_builder.rb b/lib/puppet/vendor/rgen/lib/rgen/metamodel_builder.rb
new file mode 100644
index 000000000..6fd680f7a
--- /dev/null
+++ b/lib/puppet/vendor/rgen/lib/rgen/metamodel_builder.rb
@@ -0,0 +1,224 @@
+# RGen Framework
+# (c) Martin Thiede, 2006
+
+require 'rgen/metamodel_builder/constant_order_helper'
+require 'rgen/metamodel_builder/builder_runtime'
+require 'rgen/metamodel_builder/builder_extensions'
+require 'rgen/metamodel_builder/module_extension'
+require 'rgen/metamodel_builder/data_types'
+require 'rgen/metamodel_builder/mm_multiple'
+require 'rgen/ecore/ecore_interface'
+
+module RGen
+
+# MetamodelBuilder can be used to create a metamodel, i.e. Ruby classes which
+# act as metamodel elements.
+#
+# To create a new metamodel element, create a Ruby class which inherits from
+# MetamodelBuilder::MMBase
+#
+# class Person < RGen::MetamodelBuilder::MMBase
+# end
+#
+# This way a couple of class methods are made available to the new class.
+# These methods can be used to:
+# * add attributes to the class
+# * add associations with other classes
+#
+# Here is an example:
+#
+# class Person < RGen::MetamodelBuilder::MMBase
+# has_attr 'name', String
+# has_attr 'age', Integer
+# end
+#
+# class House < RGen::MetamodelBuilder::MMBase
+# has_attr 'address' # String is default
+# end
+#
+# Person.many_to_many 'homes', House, 'inhabitants'
+#
+# See BuilderExtensions for details about the available class methods.
+#
+# =Attributes
+#
+# The example above creates two classes 'Person' and 'House'. Person has the attributes
+# 'name' and 'age', House has the attribute 'address'. The attributes can be
+# accessed on instances of the classes in the following way:
+#
+# p = Person.new
+# p.name = "MyName"
+# p.age = 22
+# p.name # => "MyName"
+# p.age # => 22
+#
+# Note that the class Person takes care of the type of its attributes. As
+# declared above, a 'name' can only be a String, an 'age' must be an Integer.
+# So the following would return an exception:
+#
+# p.name = :myName # => exception: can not put a Symbol where a String is expected
+#
+# If the type of an attribute should be left undefined, use Object as type.
+#
+# =Associations
+#
+# As well as attributes show up as instance methods, associations bring their own
+# accessor methods. For the Person-to-House association this would be:
+#
+# h1 = House.new
+# h1.address = "Street1"
+# h2 = House.new
+# h2.address = "Street2"
+# p.addHomes(h1)
+# p.addHomes(h2)
+# p.removeHomes(h1)
+# p.homes # => [ h2 ]
+#
+# The Person-to-House association is _bidirectional_. This means that with the
+# addition of a House to a Person, the Person is also added to the House. Thus:
+#
+# h1.inhabitants # => []
+# h2.inhabitants # => [ p ]
+#
+# Note that the association is defined between two specific classes, instances of
+# different classes can not be added. Thus, the following would result in an
+# exception:
+#
+# p.addHomes(:justASymbol) # => exception: can not put a Symbol where a House is expected
+#
+# =ECore Metamodel description
+#
+# The class methods described above are used to create a Ruby representation of the metamodel
+# we have in mind in a very simple and easy way. We don't have to care about all the details
+# of a metamodel at this point (e.g. multiplicities, changeability, etc).
+#
+# At the same time however, an instance of the ECore metametamodel (i.e. a ECore based
+# description of our metamodel) is provided for all the Ruby classes and modules we create.
+# Since we did not provide the nitty-gritty details of the metamodel, defaults are used to
+# fully complete the ECore metamodel description.
+#
+# In order to access the ECore metamodel description, just call the +ecore+ method on a
+# Ruby class or module object belonging to your metamodel.
+#
+# Here is the example continued from above:
+#
+# Person.ecore.eAttributes.name # => ["name", "age"]
+# h2pRef = House.ecore.eReferences.first
+# h2pRef.eType # => Person
+# h2pRef.eOpposite.eType # => House
+# h2pRef.lowerBound # => 0
+# h2pRef.upperBound # => -1
+# h2pRef.many # => true
+# h2pRef.containment # => false
+#
+# Note that the use of array_extensions.rb is assumed here to make model navigation convenient.
+#
+# The following metamodel builder methods are supported, see individual method description
+# for details:
+#
+# Attributes:
+# * BuilderExtensions#has_attr
+#
+# Unidirectional references:
+# * BuilderExtensions#has_one
+# * BuilderExtensions#has_many
+# * BuilderExtensions#contains_one_uni
+# * BuilderExtensions#contains_many_uni
+#
+# Bidirectional references:
+# * BuilderExtensions#one_to_one
+# * BuilderExtensions#one_to_many
+# * BuilderExtensions#many_to_one
+# * BuilderExtensions#many_to_many
+# * BuilderExtensions#contains_one
+# * BuilderExtensions#contains_many
+#
+# Every builder command can optionally take a specification of further ECore properties.
+# Additional properties for Attributes and References are (with defaults in brackets):
+# * :ordered (true),
+# * :unique (true),
+# * :changeable (true),
+# * :volatile (false),
+# * :transient (false),
+# * :unsettable (false),
+# * :derived (false),
+# * :lowerBound (0),
+# * :resolveProxies (true) <i>references only</i>,
+#
+# Using these additional properties, the above example can be refined as follows:
+#
+# class Person < RGen::MetamodelBuilder::MMBase
+# has_attr 'name', String, :lowerBound => 1
+# has_attr 'yearOfBirth', Integer,
+# has_attr 'age', Integer, :derived => true
+# def age_derived
+# Time.now.year - yearOfBirth
+# end
+# end
+#
+# Person.many_to_many 'homes', House, 'inhabitants', :upperBound => 5
+#
+# Person.ecore.eReferences.find{|r| r.name == 'homes'}.upperBound # => 5
+#
+# This way we state that there must be a name for each person, we introduce a new attribute
+# 'yearOfBirth' and make 'age' a derived attribute. We also say that a person can
+# have at most 5 houses in our metamodel.
+#
+# ==Derived attributes and references
+#
+# If the attribute 'derived' of an attribute or reference is set to true, a method +attributeName_derived+
+# has to be provided. This method is called whenever the original attribute is accessed. The
+# original attribute can not be written if it is derived.
+#
+#
+module MetamodelBuilder
+
+ # Use this class as a start for new metamodel elements (i.e. Ruby classes)
+ # by inheriting for it.
+ #
+ # See MetamodelBuilder for an example.
+ class MMBase
+ include BuilderRuntime
+ include DataTypes
+ extend BuilderExtensions
+ extend ModuleExtension
+ extend RGen::ECore::ECoreInterface
+
+ def initialize(arg=nil)
+ raise StandardError.new("Class #{self.class} is abstract") if self.class._abstract_class
+ arg.each_pair { |k,v| setGeneric(k, v) } if arg.is_a?(Hash)
+ end
+
+ # Object#inspect causes problems on most models
+ def inspect
+ self.class.name
+ end
+
+ def self.method_added(m)
+ raise "Do not add methods to model classes directly, add them to the ClassModule instead"
+ end
+ end
+
+ # Instances of MMGeneric can be used as values of any attribute are reference
+ class MMGeneric
+ # empty implementation so we don't have to check if a value is a MMGeneriv before setting the container
+ def _set_container(container, containing_feature_name)
+ end
+ end
+
+ # MMProxy objects can be used instead of real target elements in case references should be resolved later on
+ class MMProxy < MMGeneric
+ # The +targetIdentifer+ is an object identifying the element the proxy represents
+ attr_accessor :targetIdentifier
+ # +data+ is optional additional information to be associated with the proxy
+ attr_accessor :data
+
+ def initialize(ident=nil, data=nil)
+ @targetIdentifier = ident
+ @data = data
+ end
+ end
+
+end
+
+end
diff --git a/lib/puppet/vendor/rgen/lib/rgen/metamodel_builder/builder_extensions.rb b/lib/puppet/vendor/rgen/lib/rgen/metamodel_builder/builder_extensions.rb
new file mode 100644
index 000000000..dfa4a517e
--- /dev/null
+++ b/lib/puppet/vendor/rgen/lib/rgen/metamodel_builder/builder_extensions.rb
@@ -0,0 +1,556 @@
+# RGen Framework
+# (c) Martin Thiede, 2006
+
+require 'erb'
+require 'rgen/metamodel_builder/intermediate/feature'
+
+module RGen
+
+module MetamodelBuilder
+
+# This module provides methods which can be used to setup a metamodel element.
+# The module is used to +extend+ MetamodelBuilder::MMBase, i.e. add the module's
+# methods as class methods.
+#
+# MetamodelBuilder::MMBase should be used as a start for new metamodel elements.
+# See MetamodelBuilder for an example.
+#
+module BuilderExtensions
+ include Util::NameHelper
+
+ class FeatureBlockEvaluator
+ def self.eval(block, props1, props2=nil)
+ return unless block
+ e = self.new(props1, props2)
+ e.instance_eval(&block)
+ end
+ def initialize(props1, props2)
+ @props1, @props2 = props1, props2
+ end
+ def annotation(hash)
+ @props1.annotations << Intermediate::Annotation.new(hash)
+ end
+ def opposite_annotation(hash)
+ raise "No opposite available" unless @props2
+ @props2.annotations << Intermediate::Annotation.new(hash)
+ end
+ end
+
+ # Add an attribute which can hold a single value.
+ # 'role' specifies the name which is used to access the attribute.
+ # 'target_class' specifies the type of objects which can be held by this attribute.
+ # If no target class is given, String will be default.
+ #
+ # This class method adds the following instance methods, where 'role' is to be
+ # replaced by the given role name:
+ # class#role # getter
+ # class#role=(value) # setter
+ def has_attr(role, target_class=nil, raw_props={}, &block)
+ props = Intermediate::Attribute.new(target_class, _ownProps(raw_props).merge({
+ :name=>role, :upperBound=>1}))
+ raise "No opposite available" unless _oppositeProps(raw_props).empty?
+ FeatureBlockEvaluator.eval(block, props)
+ _build_internal(props)
+ end
+
+ # Add an attribute which can hold multiple values.
+ # 'role' specifies the name which is used to access the attribute.
+ # 'target_class' specifies the type of objects which can be held by this attribute.
+ # If no target class is given, String will be default.
+ #
+ # This class method adds the following instance methods, where 'role' is to be
+ # replaced by the given role name:
+ # class#addRole(value, index=-1)
+ # class#removeRole(value)
+ # class#role # getter, returns an array
+ # class#role= # setter, sets multiple values at once
+ # Note that the first letter of the role name is turned into an uppercase
+ # for the add and remove methods.
+ def has_many_attr(role, target_class=nil, raw_props={}, &block)
+ props = Intermediate::Attribute.new(target_class, _setManyUpperBound(_ownProps(raw_props).merge({
+ :name=>role})))
+ raise "No opposite available" unless _oppositeProps(raw_props).empty?
+ FeatureBlockEvaluator.eval(block, props)
+ _build_internal(props)
+ end
+
+ # Add a single unidirectional association.
+ # 'role' specifies the name which is used to access the association.
+ # 'target_class' specifies the type of objects which can be held by this association.
+ #
+ # This class method adds the following instance methods, where 'role' is to be
+ # replaced by the given role name:
+ # class#role # getter
+ # class#role=(value) # setter
+ #
+ def has_one(role, target_class=nil, raw_props={}, &block)
+ props = Intermediate::Reference.new(target_class, _ownProps(raw_props).merge({
+ :name=>role, :upperBound=>1, :containment=>false}))
+ raise "No opposite available" unless _oppositeProps(raw_props).empty?
+ FeatureBlockEvaluator.eval(block, props)
+ _build_internal(props)
+ end
+
+ # Add an unidirectional _many_ association.
+ # 'role' specifies the name which is used to access the attribute.
+ # 'target_class' is optional and can be used to fix the type of objects which
+ # can be referenced by this association.
+ #
+ # This class method adds the following instance methods, where 'role' is to be
+ # replaced by the given role name:
+ # class#addRole(value, index=-1)
+ # class#removeRole(value)
+ # class#role # getter, returns an array
+ # Note that the first letter of the role name is turned into an uppercase
+ # for the add and remove methods.
+ #
+ def has_many(role, target_class=nil, raw_props={}, &block)
+ props = Intermediate::Reference.new(target_class, _setManyUpperBound(_ownProps(raw_props).merge({
+ :name=>role, :containment=>false})))
+ raise "No opposite available" unless _oppositeProps(raw_props).empty?
+ FeatureBlockEvaluator.eval(block, props)
+ _build_internal(props)
+ end
+
+ def contains_one_uni(role, target_class=nil, raw_props={}, &block)
+ props = Intermediate::Reference.new(target_class, _ownProps(raw_props).merge({
+ :name=>role, :upperBound=>1, :containment=>true}))
+ raise "No opposite available" unless _oppositeProps(raw_props).empty?
+ FeatureBlockEvaluator.eval(block, props)
+ _build_internal(props)
+ end
+
+ def contains_many_uni(role, target_class=nil, raw_props={}, &block)
+ props = Intermediate::Reference.new(target_class, _setManyUpperBound(_ownProps(raw_props).merge({
+ :name=>role, :containment=>true})))
+ raise "No opposite available" unless _oppositeProps(raw_props).empty?
+ FeatureBlockEvaluator.eval(block, props)
+ _build_internal(props)
+ end
+
+ # Add a bidirectional one-to-many association between two classes.
+ # The class this method is called on is refered to as _own_class_ in
+ # the following.
+ #
+ # Instances of own_class can use 'own_role' to access _many_ associated instances
+ # of type 'target_class'. Instances of 'target_class' can use 'target_role' to
+ # access _one_ associated instance of own_class.
+ #
+ # This class method adds the following instance methods where 'ownRole' and
+ # 'targetRole' are to be replaced by the given role names:
+ # own_class#addOwnRole(value, index=-1)
+ # own_class#removeOwnRole(value)
+ # own_class#ownRole
+ # target_class#targetRole
+ # target_class#targetRole=(value)
+ # Note that the first letter of the role name is turned into an uppercase
+ # for the add and remove methods.
+ #
+ # When an element is added/set on either side, this element also receives the element
+ # is is added to as a new element.
+ #
+ def one_to_many(target_role, target_class, own_role, raw_props={}, &block)
+ props1 = Intermediate::Reference.new(target_class, _setManyUpperBound(_ownProps(raw_props).merge({
+ :name=>target_role, :containment=>false})))
+ props2 = Intermediate::Reference.new(self, _oppositeProps(raw_props).merge({
+ :name=>own_role, :upperBound=>1, :containment=>false}))
+ FeatureBlockEvaluator.eval(block, props1, props2)
+ _build_internal(props1, props2)
+ end
+
+ def contains_many(target_role, target_class, own_role, raw_props={}, &block)
+ props1 = Intermediate::Reference.new(target_class, _setManyUpperBound(_ownProps(raw_props).merge({
+ :name=>target_role, :containment=>true})))
+ props2 = Intermediate::Reference.new(self, _oppositeProps(raw_props).merge({
+ :name=>own_role, :upperBound=>1, :containment=>false}))
+ FeatureBlockEvaluator.eval(block, props1, props2)
+ _build_internal(props1, props2)
+ end
+
+ # This is the inverse of one_to_many provided for convenience.
+ def many_to_one(target_role, target_class, own_role, raw_props={}, &block)
+ props1 = Intermediate::Reference.new(target_class, _ownProps(raw_props).merge({
+ :name=>target_role, :upperBound=>1, :containment=>false}))
+ props2 = Intermediate::Reference.new(self, _setManyUpperBound(_oppositeProps(raw_props).merge({
+ :name=>own_role, :containment=>false})))
+ FeatureBlockEvaluator.eval(block, props1, props2)
+ _build_internal(props1, props2)
+ end
+
+ # Add a bidirectional many-to-many association between two classes.
+ # The class this method is called on is refered to as _own_class_ in
+ # the following.
+ #
+ # Instances of own_class can use 'own_role' to access _many_ associated instances
+ # of type 'target_class'. Instances of 'target_class' can use 'target_role' to
+ # access _many_ associated instances of own_class.
+ #
+ # This class method adds the following instance methods where 'ownRole' and
+ # 'targetRole' are to be replaced by the given role names:
+ # own_class#addOwnRole(value, index=-1)
+ # own_class#removeOwnRole(value)
+ # own_class#ownRole
+ # target_class#addTargetRole
+ # target_class#removeTargetRole=(value)
+ # target_class#targetRole
+ # Note that the first letter of the role name is turned into an uppercase
+ # for the add and remove methods.
+ #
+ # When an element is added on either side, this element also receives the element
+ # is is added to as a new element.
+ #
+ def many_to_many(target_role, target_class, own_role, raw_props={}, &block)
+ props1 = Intermediate::Reference.new(target_class, _setManyUpperBound(_ownProps(raw_props).merge({
+ :name=>target_role, :containment=>false})))
+ props2 = Intermediate::Reference.new(self, _setManyUpperBound(_oppositeProps(raw_props).merge({
+ :name=>own_role, :containment=>false})))
+ FeatureBlockEvaluator.eval(block, props1, props2)
+ _build_internal(props1, props2)
+ end
+
+ # Add a bidirectional one-to-one association between two classes.
+ # The class this method is called on is refered to as _own_class_ in
+ # the following.
+ #
+ # Instances of own_class can use 'own_role' to access _one_ associated instance
+ # of type 'target_class'. Instances of 'target_class' can use 'target_role' to
+ # access _one_ associated instance of own_class.
+ #
+ # This class method adds the following instance methods where 'ownRole' and
+ # 'targetRole' are to be replaced by the given role names:
+ # own_class#ownRole
+ # own_class#ownRole=(value)
+ # target_class#targetRole
+ # target_class#targetRole=(value)
+ #
+ # When an element is set on either side, this element also receives the element
+ # is is added to as the new element.
+ #
+ def one_to_one(target_role, target_class, own_role, raw_props={}, &block)
+ props1 = Intermediate::Reference.new(target_class, _ownProps(raw_props).merge({
+ :name=>target_role, :upperBound=>1, :containment=>false}))
+ props2 = Intermediate::Reference.new(self, _oppositeProps(raw_props).merge({
+ :name=>own_role, :upperBound=>1, :containment=>false}))
+ FeatureBlockEvaluator.eval(block, props1, props2)
+ _build_internal(props1, props2)
+ end
+
+ def contains_one(target_role, target_class, own_role, raw_props={}, &block)
+ props1 = Intermediate::Reference.new(target_class, _ownProps(raw_props).merge({
+ :name=>target_role, :upperBound=>1, :containment=>true}))
+ props2 = Intermediate::Reference.new(self, _oppositeProps(raw_props).merge({
+ :name=>own_role, :upperBound=>1, :containment=>false}))
+ FeatureBlockEvaluator.eval(block, props1, props2)
+ _build_internal(props1, props2)
+ end
+
+ def _metamodel_description # :nodoc:
+ @metamodel_description ||= []
+ end
+
+ def _add_metamodel_description(desc) # :nodoc
+ @metamodel_description ||= []
+ @metamodelDescriptionByName ||= {}
+ @metamodel_description.delete(@metamodelDescriptionByName[desc.value(:name)])
+ @metamodel_description << desc
+ @metamodelDescriptionByName[desc.value(:name)] = desc
+ end
+
+ def abstract
+ @abstract = true
+ end
+
+ def _abstract_class
+ @abstract || false
+ end
+
+ def inherited(c)
+ c.send(:include, c.const_set(:ClassModule, Module.new))
+ MetamodelBuilder::ConstantOrderHelper.classCreated(c)
+ end
+
+ protected
+
+ # Central builder method
+ #
+ def _build_internal(props1, props2=nil)
+ _add_metamodel_description(props1)
+ if props1.many?
+ _build_many_methods(props1, props2)
+ else
+ _build_one_methods(props1, props2)
+ end
+ if props2
+ # this is a bidirectional reference
+ props1.opposite, props2.opposite = props2, props1
+ other_class = props1.impl_type
+ other_class._add_metamodel_description(props2)
+ raise "Internal error: second description must be a reference description" \
+ unless props2.reference?
+ if props2.many?
+ other_class._build_many_methods(props2, props1)
+ else
+ other_class._build_one_methods(props2, props1)
+ end
+ end
+ end
+
+ # To-One association methods
+ #
+ def _build_one_methods(props, other_props=nil)
+ name = props.value(:name)
+ other_role = other_props && other_props.value(:name)
+
+ if props.value(:derived)
+ build_derived_method(name, props, :one)
+ else
+ @@one_read_builder ||= ERB.new <<-CODE
+
+ def get<%= firstToUpper(name) %>
+ <% if !props.reference? && props.value(:defaultValueLiteral) %>
+ <% defVal = props.value(:defaultValueLiteral) %>
+ <% check_default_value_literal(defVal, props) %>
+ <% defVal = '"'+defVal+'"' if props.impl_type == String %>
+ <% defVal = ':'+defVal if props.impl_type.is_a?(DataTypes::Enum) && props.impl_type != DataTypes::Boolean %>
+ (defined? @<%= name %>) ? @<%= name %> : <%= defVal %>
+ <% else %>
+ @<%= name %>
+ <% end %>
+ end
+ <% if name != "class" %>
+ alias <%= name %> get<%= firstToUpper(name) %>
+ <% end %>
+
+ CODE
+ self::ClassModule.module_eval(@@one_read_builder.result(binding))
+ end
+
+ if props.value(:changeable)
+ @@one_write_builder ||= ERB.new <<-CODE
+
+ def set<%= firstToUpper(name) %>(val)
+ return if (defined? @<%= name %>) && val == @<%= name %>
+ <%= type_check_code("val", props) %>
+ oldval = @<%= name %>
+ @<%= name %> = val
+ <% if other_role %>
+ oldval._unregister<%= firstToUpper(other_role) %>(self) unless oldval.nil? || oldval.is_a?(MMGeneric)
+ val._register<%= firstToUpper(other_role) %>(self) unless val.nil? || val.is_a?(MMGeneric)
+ <% end %>
+ <% if props.reference? && props.value(:containment) %>
+ val._set_container(self, :<%= name %>) unless val.nil?
+ oldval._set_container(nil, nil) unless oldval.nil?
+ <% end %>
+ end
+ alias <%= name %>= set<%= firstToUpper(name) %>
+
+ def _register<%= firstToUpper(name) %>(val)
+ <% if other_role %>
+ @<%= name %>._unregister<%= firstToUpper(other_role) %>(self) unless @<%= name %>.nil? || @<%= name %>.is_a?(MMGeneric)
+ <% end %>
+ <% if props.reference? && props.value(:containment) %>
+ @<%= name %>._set_container(nil, nil) unless @<%= name %>.nil?
+ val._set_container(self, :<%= name %>) unless val.nil?
+ <% end %>
+ @<%= name %> = val
+ end
+
+ def _unregister<%= firstToUpper(name) %>(val)
+ <% if props.reference? && props.value(:containment) %>
+ @<%= name %>._set_container(nil, nil) unless @<%= name %>.nil?
+ <% end %>
+ @<%= name %> = nil
+ end
+
+ CODE
+ self::ClassModule.module_eval(@@one_write_builder.result(binding))
+
+ end
+ end
+
+ # To-Many association methods
+ #
+ def _build_many_methods(props, other_props=nil)
+ name = props.value(:name)
+ other_role = other_props && other_props.value(:name)
+
+ if props.value(:derived)
+ build_derived_method(name, props, :many)
+ else
+ @@many_read_builder ||= ERB.new <<-CODE
+
+ def get<%= firstToUpper(name) %>
+ ( @<%= name %> ? @<%= name %>.dup : [] )
+ end
+ <% if name != "class" %>
+ alias <%= name %> get<%= firstToUpper(name) %>
+ <% end %>
+
+ CODE
+ self::ClassModule.module_eval(@@many_read_builder.result(binding))
+ end
+
+ if props.value(:changeable)
+ @@many_write_builder ||= ERB.new <<-CODE
+
+ def add<%= firstToUpper(name) %>(val, index=-1)
+ @<%= name %> = [] unless @<%= name %>
+ return if val.nil? || (@<%= name %>.any?{|e| e.object_id == val.object_id} && (val.is_a?(MMBase) || val.is_a?(MMGeneric)))
+ <%= type_check_code("val", props) %>
+ @<%= name %>.insert(index, val)
+ <% if other_role %>
+ val._register<%= firstToUpper(other_role) %>(self) unless val.is_a?(MMGeneric)
+ <% end %>
+ <% if props.reference? && props.value(:containment) %>
+ val._set_container(self, :<%= name %>)
+ <% end %>
+ end
+
+ def remove<%= firstToUpper(name) %>(val)
+ @<%= name %> = [] unless @<%= name %>
+ @<%= name %>.each_with_index do |e,i|
+ if e.object_id == val.object_id
+ @<%= name %>.delete_at(i)
+ <% if props.reference? && props.value(:containment) %>
+ val._set_container(nil, nil)
+ <% end %>
+ <% if other_role %>
+ val._unregister<%= firstToUpper(other_role) %>(self) unless val.is_a?(MMGeneric)
+ <% end %>
+ return
+ end
+ end
+ end
+
+ def set<%= firstToUpper(name) %>(val)
+ return if val.nil?
+ raise _assignmentTypeError(self, val, Enumerable) unless val.is_a? Enumerable
+ get<%= firstToUpper(name) %>.each {|e|
+ remove<%= firstToUpper(name) %>(e)
+ }
+ val.each {|v|
+ add<%= firstToUpper(name) %>(v)
+ }
+ end
+ alias <%= name %>= set<%= firstToUpper(name) %>
+
+ def _register<%= firstToUpper(name) %>(val)
+ @<%= name %> = [] unless @<%= name %>
+ @<%= name %>.push val
+ <% if props.reference? && props.value(:containment) %>
+ val._set_container(self, :<%= name %>)
+ <% end %>
+ end
+
+ def _unregister<%= firstToUpper(name) %>(val)
+ @<%= name %>.delete val
+ <% if props.reference? && props.value(:containment) %>
+ val._set_container(nil, nil)
+ <% end %>
+ end
+
+ CODE
+ self::ClassModule.module_eval(@@many_write_builder.result(binding))
+ end
+
+ end
+
+ private
+
+ def build_derived_method(name, props, kind)
+ raise "Implement method #{name}_derived instead of method #{name}" \
+ if (public_instance_methods+protected_instance_methods+private_instance_methods).include?(name)
+ @@derived_builder ||= ERB.new <<-CODE
+
+ def get<%= firstToUpper(name) %>
+ raise "Derived feature requires public implementation of method <%= name %>_derived" \
+ unless respond_to?(:<%= name+"_derived" %>)
+ val = <%= name %>_derived
+ <% if kind == :many %>
+ raise _assignmentTypeError(self,val,Enumerable) unless val && val.is_a?(Enumerable)
+ val.each do |v|
+ <%= type_check_code("v", props) %>
+ end
+ <% else %>
+ <%= type_check_code("val", props) %>
+ <% end %>
+ val
+ end
+ <% if name != "class" %>
+ alias <%= name %> get<%= firstToUpper(name) %>
+ <% end %>
+ #TODO final_method :<%= name %>
+
+ CODE
+ self::ClassModule.module_eval(@@derived_builder.result(binding))
+ end
+
+ def check_default_value_literal(literal, props)
+ return if literal.nil? || props.impl_type == String
+ if props.impl_type == Integer || props.impl_type == RGen::MetamodelBuilder::DataTypes::Long
+ unless literal =~ /^\d+$/
+ raise StandardError.new("Property #{props.value(:name)} can not take value #{literal}, expected an Integer")
+ end
+ elsif props.impl_type == Float
+ unless literal =~ /^\d+\.\d+$/
+ raise StandardError.new("Property #{props.value(:name)} can not take value #{literal}, expected a Float")
+ end
+ elsif props.impl_type == RGen::MetamodelBuilder::DataTypes::Boolean
+ unless ["true", "false"].include?(literal)
+ raise StandardError.new("Property #{props.value(:name)} can not take value #{literal}, expected true or false")
+ end
+ elsif props.impl_type.is_a?(RGen::MetamodelBuilder::DataTypes::Enum)
+ unless props.impl_type.literals.include?(literal.to_sym)
+ raise StandardError.new("Property #{props.value(:name)} can not take value #{literal}, expected one of #{props.impl_type.literals_as_strings.join(', ')}")
+ end
+ else
+ raise StandardError.new("Unkown type "+props.impl_type.to_s)
+ end
+ end
+
+ def type_check_code(varname, props)
+ code = ""
+ if props.impl_type == RGen::MetamodelBuilder::DataTypes::Long
+ code << "unless #{varname}.nil? || #{varname}.is_a?(Integer) || #{varname}.is_a?(MMGeneric)"
+ code << "\n"
+ expected = "Integer"
+ elsif props.impl_type.is_a?(Class)
+ code << "unless #{varname}.nil? || #{varname}.is_a?(#{props.impl_type}) || #{varname}.is_a?(MMGeneric)"
+ code << " || #{varname}.is_a?(BigDecimal)" if props.impl_type == Float && defined?(BigDecimal)
+ code << "\n"
+ expected = props.impl_type.to_s
+ elsif props.impl_type.is_a?(RGen::MetamodelBuilder::DataTypes::Enum)
+ code << "unless #{varname}.nil? || [#{props.impl_type.literals_as_strings.join(',')}].include?(#{varname}) || #{varname}.is_a?(MMGeneric)\n"
+ expected = "["+props.impl_type.literals_as_strings.join(',')+"]"
+ else
+ raise StandardError.new("Unkown type "+props.impl_type.to_s)
+ end
+ code << "raise _assignmentTypeError(self,#{varname},\"#{expected}\")\n"
+ code << "end"
+ code
+ end
+
+ def _ownProps(props)
+ Hash[*(props.select{|k,v| !(k.to_s =~ /^opposite_/)}.flatten)]
+ end
+
+ def _oppositeProps(props)
+ r = {}
+ props.each_pair do |k,v|
+ if k.to_s =~ /^opposite_(.*)$/
+ r[$1.to_sym] = v
+ end
+ end
+ r
+ end
+
+ def _setManyUpperBound(props)
+ props[:upperBound] = -1 unless props[:upperBound].is_a?(Integer) && props[:upperBound] > 1
+ props
+ end
+
+end
+
+end
+
+end
diff --git a/lib/puppet/vendor/rgen/lib/rgen/metamodel_builder/builder_runtime.rb b/lib/puppet/vendor/rgen/lib/rgen/metamodel_builder/builder_runtime.rb
new file mode 100644
index 000000000..3ddbe3b5a
--- /dev/null
+++ b/lib/puppet/vendor/rgen/lib/rgen/metamodel_builder/builder_runtime.rb
@@ -0,0 +1,174 @@
+# RGen Framework
+# (c) Martin Thiede, 2006
+
+require 'rgen/util/name_helper'
+
+module RGen
+
+module MetamodelBuilder
+
+# This module is mixed into MetamodelBuilder::MMBase.
+# The methods provided by this module are used by the methods generated
+# by the class methods of MetamodelBuilder::BuilderExtensions
+module BuilderRuntime
+ include Util::NameHelper
+
+ def is_a?(c)
+ return super unless c.const_defined?(:ClassModule)
+ kind_of?(c::ClassModule)
+ end
+
+ def addGeneric(role, value, index=-1)
+ send("add#{firstToUpper(role.to_s)}",value, index)
+ end
+
+ def removeGeneric(role, value)
+ send("remove#{firstToUpper(role.to_s)}",value)
+ end
+
+ def setGeneric(role, value)
+ send("set#{firstToUpper(role.to_s)}",value)
+ end
+
+ def hasManyMethods(role)
+ respond_to?("add#{firstToUpper(role.to_s)}")
+ end
+
+ def setOrAddGeneric(role, value)
+ if hasManyMethods(role)
+ addGeneric(role, value)
+ else
+ setGeneric(role, value)
+ end
+ end
+
+ def setNilOrRemoveGeneric(role, value)
+ if hasManyMethods(role)
+ removeGeneric(role, value)
+ else
+ setGeneric(role, nil)
+ end
+ end
+
+ def setNilOrRemoveAllGeneric(role)
+ if hasManyMethods(role)
+ setGeneric(role, [])
+ else
+ setGeneric(role, nil)
+ end
+ end
+
+ def getGeneric(role)
+ send("get#{firstToUpper(role.to_s)}")
+ end
+
+ def getGenericAsArray(role)
+ result = getGeneric(role)
+ result = [result].compact unless result.is_a?(Array)
+ result
+ end
+
+ def eIsSet(role)
+ eval("defined? @#{role}") != nil
+ end
+
+ def eUnset(role)
+ if respond_to?("add#{firstToUpper(role.to_s)}")
+ setGeneric(role, [])
+ else
+ setGeneric(role, nil)
+ end
+ remove_instance_variable("@#{role}")
+ end
+
+ def eContainer
+ @_container
+ end
+
+ def eContainingFeature
+ @_containing_feature_name
+ end
+
+ # returns the contained elements in no particular order
+ def eContents
+ if @_contained_elements
+ @_contained_elements.dup
+ else
+ []
+ end
+ end
+
+ # if a block is given, calls the block on every contained element in depth first order.
+ # if the block returns :prune, recursion will stop at this point.
+ #
+ # if no block is given builds and returns a list of all contained elements.
+ #
+ def eAllContents(&block)
+ if block
+ if @_contained_elements
+ @_contained_elements.each do |e|
+ res = block.call(e)
+ e.eAllContents(&block) if res != :prune
+ end
+ end
+ nil
+ else
+ result = []
+ if @_contained_elements
+ @_contained_elements.each do |e|
+ result << e
+ result.concat(e.eAllContents)
+ end
+ end
+ result
+ end
+ end
+
+ def disconnectContainer
+ eContainer.setNilOrRemoveGeneric(eContainingFeature, self) if eContainer
+ end
+
+ def _set_container(container, containing_feature_name)
+ # if a new container is set, make sure to disconnect from the old one.
+ # note that _set_container will never be called for the container and the role
+ # which are currently set because the accessor methods in BuilderExtensions
+ # block setting/adding a value which is already present.
+ # (it may be called for the same container with a different role, a different container
+ # with the same role and a different container with a different role, though)
+ # this ensures, that disconnecting for the current container doesn't break
+ # a new connection which has just been set up in the accessor methods.
+ disconnectContainer if container
+ @_container._remove_contained_element(self) if @_container
+ container._add_contained_element(self) if container
+ @_container = container
+ @_containing_feature_name = containing_feature_name
+ end
+
+ def _add_contained_element(element)
+ @_contained_elements ||= []
+ @_contained_elements << element
+ end
+
+ def _remove_contained_element(element)
+ @_contained_elements.delete(element) if @_contained_elements
+ end
+
+ def _assignmentTypeError(target, value, expected)
+ text = ""
+ if target
+ targetId = target.class.name
+ targetId += "(" + target.name + ")" if target.respond_to?(:name) and target.name
+ text += "In #{targetId} : "
+ end
+ valueId = value.class.name
+ valueId += "(" + value.name + ")" if value.respond_to?(:name) and value.name
+ valueId += "(:" + value.to_s + ")" if value.is_a?(Symbol)
+ text += "Can not use a #{valueId} where a #{expected} is expected"
+ StandardError.new(text)
+ end
+
+end
+
+end
+
+end
diff --git a/lib/puppet/vendor/rgen/lib/rgen/metamodel_builder/constant_order_helper.rb b/lib/puppet/vendor/rgen/lib/rgen/metamodel_builder/constant_order_helper.rb
new file mode 100644
index 000000000..51c8034a0
--- /dev/null
+++ b/lib/puppet/vendor/rgen/lib/rgen/metamodel_builder/constant_order_helper.rb
@@ -0,0 +1,89 @@
+module RGen
+
+module MetamodelBuilder
+
+# The purpose of the ConstantOrderHelper is to capture the definition order of RGen metamodel builder
+# classes, modules and enums. The problem is that Ruby doesn't seem to track the order of
+# constants being created in a module. However the order is important because it defines the order
+# of eClassifiers and eSubpackages in a EPackage.
+#
+# It would be helpful here if Ruby provided a +const_added+ callback, but this is not the case up to now.
+#
+# The idea for capturing is that all events of creating a RGen class, module or enum are reported to the
+# ConstantOrderHelper singleton.
+# For classes and modules it tries to add their names to the parent's +_constantOrder+ array.
+# The parent module is derived from the class's or module's name. However, the new name is only added
+# if the respective parent module has a new constant (which is not yet in +_constantOrder+) which
+# points to the new class or module.
+# For enums it is a bit more complicated, because at the time the enum is created, the parent
+# module does not yet contain the constant to which the enum is assigned. Therefor, the enum is remembered
+# and it is tried to be stored on the next event (class, module or enum) within the module which was
+# created last (which was last extended with ModuleExtension). If it can not be found in that module,
+# all parent modules of the last module are searched. This way it should also be correctly entered in
+# case it was defined outside of the last created module.
+# Note that an enum is not stored to the constant order array unless another event occurs. That's why
+# it is possible that one enum is missing at the enum. This needs to be taken care of by the ECore transformer.
+#
+# This way of capturing should be sufficient for the regular use cases of the RGen metamodel builder language.
+# However, it is possible to write code which messes this up, see unit tests for details.
+# In the worst case, the new classes, modules or enums will just not be found in a parent module and thus be ignored.
+#
+ConstantOrderHelper = Class.new do
+
+ def initialize
+ @currentModule = nil
+ @pendingEnum = nil
+ end
+
+ def classCreated(c)
+ handlePendingEnum
+ cont = containerModule(c)
+ name = (c.name || "").split("::").last
+ return unless cont.respond_to?(:_constantOrder) && !cont._constantOrder.include?(name)
+ cont._constantOrder << name
+ end
+
+ def moduleCreated(m)
+ handlePendingEnum
+ cont = containerModule(m)
+ name = (m.name || "").split("::").last
+ return unless cont.respond_to?(:_constantOrder) && !cont._constantOrder.include?(name)
+ cont._constantOrder << name
+ @currentModule = m
+ end
+
+ def enumCreated(e)
+ handlePendingEnum
+ @pendingEnum = e
+ end
+
+ private
+
+ def containerModule(m)
+ containerName = (m.name || "").split("::")[0..-2].join("::")
+ containerName.empty? ? nil : eval(containerName, TOPLEVEL_BINDING)
+ end
+
+ def handlePendingEnum
+ return unless @pendingEnum
+ m = @currentModule
+ while m
+ if m.respond_to?(:_constantOrder)
+ newConstants = m.constants - m._constantOrder
+ const = newConstants.find{|c| m.const_get(c).object_id == @pendingEnum.object_id}
+ if const
+ m._constantOrder << const.to_s
+ break
+ end
+ end
+ m = containerModule(m)
+ end
+ @pendingEnum = nil
+ end
+
+end.new
+
+end
+
+end
+
diff --git a/lib/puppet/vendor/rgen/lib/rgen/metamodel_builder/data_types.rb b/lib/puppet/vendor/rgen/lib/rgen/metamodel_builder/data_types.rb
new file mode 100644
index 000000000..17268f4e3
--- /dev/null
+++ b/lib/puppet/vendor/rgen/lib/rgen/metamodel_builder/data_types.rb
@@ -0,0 +1,77 @@
+module RGen
+
+module MetamodelBuilder
+
+module DataTypes
+
+ # An enum object is used to describe possible attribute values within a
+ # MetamodelBuilder attribute definition. An attribute defined this way can only
+ # take the values specified when creating the Enum object.
+ # Literal values can only be symbols or true or false.
+ # Optionally a name may be specified for the enum object.
+ #
+ # Examples:
+ #
+ # Enum.new(:name => "AnimalEnum", :literals => [:cat, :dog])
+ # Enum.new(:literals => [:cat, :dog])
+ # Enum.new([:cat, :dog])
+ #
+ class Enum
+ attr_reader :name, :literals
+
+ # Creates a new named enum type object consisting of the elements passed as arguments.
+ def initialize(params)
+ MetamodelBuilder::ConstantOrderHelper.enumCreated(self)
+ if params.is_a?(Array)
+ @literals = params
+ @name = "anonymous"
+ elsif params.is_a?(Hash)
+ raise StandardError.new("Hash entry :literals is missing") unless params[:literals]
+ @literals = params[:literals]
+ @name = params[:name] || "anonymous"
+ else
+ raise StandardError.new("Pass an Array or a Hash")
+ end
+ end
+
+ # This method can be used to check if an object can be used as value for
+ # variables having this enum object as type.
+ def validLiteral?(l)
+ literals.include?(l)
+ end
+
+ def literals_as_strings
+ literals.collect do |l|
+ if l.is_a?(Symbol)
+ if l.to_s =~ /^\d|\W/
+ ":'"+l.to_s+"'"
+ else
+ ":"+l.to_s
+ end
+ elsif l.is_a?(TrueClass) || l.is_a?(FalseClass)
+ l.to_s
+ else
+ raise StandardError.new("Literal values can only be symbols or true/false")
+ end
+ end
+ end
+
+ def to_s # :nodoc:
+ name
+ end
+ end
+
+ # Boolean is a predefined enum object having Ruby's true and false singletons
+ # as possible values.
+ Boolean = Enum.new(:name => "Boolean", :literals => [true, false])
+
+ # Long represents a 64-bit Integer
+ # This constant is merely a marker for keeping this information in the Ruby version of the metamodel,
+ # values of this type will always be instances of Integer or Bignum;
+ # Setting it to a string value ensures that it responds to "to_s" which is used in the metamodel generator
+ Long = "Long"
+end
+
+end
+
+end
diff --git a/lib/puppet/vendor/rgen/lib/rgen/metamodel_builder/intermediate/annotation.rb b/lib/puppet/vendor/rgen/lib/rgen/metamodel_builder/intermediate/annotation.rb
new file mode 100644
index 000000000..eaa1eee6e
--- /dev/null
+++ b/lib/puppet/vendor/rgen/lib/rgen/metamodel_builder/intermediate/annotation.rb
@@ -0,0 +1,30 @@
+module RGen
+
+module MetamodelBuilder
+
+module Intermediate
+
+class Annotation
+ attr_reader :details, :source
+
+ def initialize(hash)
+ if hash[:source] || hash[:details]
+ restKeys = hash.keys - [:source, :details]
+ raise "Hash key #{restKeys.first} not allowed." unless restKeys.empty?
+ raise "Details not provided, key :details is missing" unless hash[:details]
+ raise "Details must be provided as a hash" unless hash[:details].is_a?(Hash)
+ @details = hash[:details]
+ @source = hash[:source]
+ else
+ raise "Details must be provided as a hash" unless hash.is_a?(Hash)
+ @details = hash
+ end
+ end
+
+end
+
+end
+
+end
+
+end
diff --git a/lib/puppet/vendor/rgen/lib/rgen/metamodel_builder/intermediate/feature.rb b/lib/puppet/vendor/rgen/lib/rgen/metamodel_builder/intermediate/feature.rb
new file mode 100644
index 000000000..ed319ea1d
--- /dev/null
+++ b/lib/puppet/vendor/rgen/lib/rgen/metamodel_builder/intermediate/feature.rb
@@ -0,0 +1,168 @@
+require 'rgen/metamodel_builder/data_types'
+
+module RGen
+
+module MetamodelBuilder
+
+module Intermediate
+
+class Feature
+ attr_reader :etype, :impl_type
+
+ def value(prop)
+ @props[prop]
+ end
+
+ def annotations
+ @annotations ||= []
+ end
+
+ def many?
+ value(:upperBound) > 1 || value(:upperBound) == -1
+ end
+
+ def reference?
+ is_a?(Reference)
+ end
+
+ protected
+
+ def check(props)
+ @props.keys.each do |p|
+ kind = props[p]
+ raise StandardError.new("invalid property #{p}") unless kind
+ raise StandardError.new("property '#{p}' not set") if value(p).nil? && kind == :required
+ end
+ end
+
+end
+
+class Attribute < Feature
+
+ Properties = {
+ :name => :required,
+ :ordered => :required,
+ :unique => :required,
+ :changeable => :required,
+ :volatile => :required,
+ :transient => :required,
+ :unsettable => :required,
+ :derived => :required,
+ :lowerBound => :required,
+ :upperBound => :required,
+ :defaultValueLiteral => :optional
+ }
+
+ Defaults = {
+ :ordered => true,
+ :unique => true,
+ :changeable => true,
+ :volatile => false,
+ :transient => false,
+ :unsettable => false,
+ :derived => false,
+ :lowerBound => 0
+ }
+
+ Types = {
+ String => :EString,
+ Integer => :EInt,
+ RGen::MetamodelBuilder::DataTypes::Long => :ELong,
+ Float => :EFloat,
+ RGen::MetamodelBuilder::DataTypes::Boolean => :EBoolean,
+ Object => :ERubyObject,
+ Class => :ERubyClass
+ }
+
+ def self.default_value(prop)
+ Defaults[prop]
+ end
+
+ def self.properties
+ Properties.keys.sort{|a,b| a.to_s <=> b.to_s}
+ end
+
+ def initialize(type, props)
+ @props = Defaults.merge(props)
+ type ||= String
+ @etype = Types[type]
+ if @etype
+ @impl_type = type
+ elsif type.is_a?(RGen::MetamodelBuilder::DataTypes::Enum)
+ @etype = :EEnumerable
+ @impl_type = type
+ else
+ raise ArgumentError.new("invalid type '#{type}'")
+ end
+ if @props[:derived]
+ @props[:changeable] = false
+ @props[:volatile] = true
+ @props[:transient] = true
+ end
+ check(Properties)
+ end
+
+end
+
+class Reference < Feature
+ attr_accessor :opposite
+
+ Properties = {
+ :name => :required,
+ :ordered => :required,
+ :unique => :required,
+ :changeable => :required,
+ :volatile => :required,
+ :transient => :required,
+ :unsettable => :required,
+ :derived => :required,
+ :lowerBound => :required,
+ :upperBound => :required,
+ :resolveProxies => :required,
+ :containment => :required
+ }
+
+ Defaults = {
+ :ordered => true,
+ :unique => true,
+ :changeable => true,
+ :volatile => false,
+ :transient => false,
+ :unsettable => false,
+ :derived => false,
+ :lowerBound => 0,
+ :resolveProxies => true
+ }
+
+ def self.default_value(prop)
+ Defaults[prop]
+ end
+
+ def self.properties
+ Properties.keys.sort{|a,b| a.to_s <=> b.to_s}
+ end
+
+ def initialize(type, props)
+ @props = Defaults.merge(props)
+ if type.respond_to?(:_metamodel_description)
+ @etype = nil
+ @impl_type = type
+ else
+ raise ArgumentError.new("'#{type}' (#{type.class}) is not a MMBase in reference #{props[:name]}")
+ end
+ if @props[:derived]
+ @props[:changeable] = false
+ @props[:volatile] = true
+ @props[:transient] = true
+ end
+ check(Properties)
+ end
+
+end
+
+end
+
+end
+
+end
+
diff --git a/lib/puppet/vendor/rgen/lib/rgen/metamodel_builder/mm_multiple.rb b/lib/puppet/vendor/rgen/lib/rgen/metamodel_builder/mm_multiple.rb
new file mode 100644
index 000000000..8c4b402bf
--- /dev/null
+++ b/lib/puppet/vendor/rgen/lib/rgen/metamodel_builder/mm_multiple.rb
@@ -0,0 +1,23 @@
+
+module RGen
+
+module MetamodelBuilder
+
+def self.MMMultiple(*superclasses)
+ c = Class.new(MMBase)
+ class << c
+ attr_reader :multiple_superclasses
+ end
+ c.instance_variable_set(:@multiple_superclasses, superclasses)
+ superclasses.collect{|sc| sc.ancestors}.flatten.
+ reject{|m| m.is_a?(Class)}.each do |arg|
+ c.instance_eval do
+ include arg
+ end
+ end
+ return c
+end
+
+end
+
+end \ No newline at end of file
diff --git a/lib/puppet/vendor/rgen/lib/rgen/metamodel_builder/module_extension.rb b/lib/puppet/vendor/rgen/lib/rgen/metamodel_builder/module_extension.rb
new file mode 100644
index 000000000..16d61b0e0
--- /dev/null
+++ b/lib/puppet/vendor/rgen/lib/rgen/metamodel_builder/module_extension.rb
@@ -0,0 +1,42 @@
+require 'rgen/ecore/ecore_interface'
+require 'rgen/metamodel_builder/intermediate/annotation'
+
+module RGen
+
+module MetamodelBuilder
+
+# This module is used to extend modules which should be
+# part of RGen metamodels
+module ModuleExtension
+ include RGen::ECore::ECoreInterface
+
+ def annotation(hash)
+ _annotations << Intermediate::Annotation.new(hash)
+ end
+
+ def _annotations
+ @_annotations ||= []
+ end
+
+ def _constantOrder
+ @_constantOrder ||= []
+ end
+
+ def final_method(m)
+ @final_methods ||= []
+ @final_methods << m
+ end
+
+ def method_added(m)
+ raise "Method #{m} can not be redefined" if @final_methods && @final_methods.include?(m)
+ end
+
+ def self.extended(m)
+ MetamodelBuilder::ConstantOrderHelper.moduleCreated(m)
+ end
+
+end
+
+end
+
+end
diff --git a/lib/puppet/vendor/rgen/lib/rgen/model_builder.rb b/lib/puppet/vendor/rgen/lib/rgen/model_builder.rb
new file mode 100644
index 000000000..f4643c8d7
--- /dev/null
+++ b/lib/puppet/vendor/rgen/lib/rgen/model_builder.rb
@@ -0,0 +1,32 @@
+require 'rgen/model_builder/builder_context'
+require 'rgen/util/method_delegation'
+#require 'ruby-prof'
+
+module RGen
+
+module ModelBuilder
+
+ def self.build(package, env=nil, builderMethodsModule=nil, &block)
+ resolver = ReferenceResolver.new
+ bc = BuilderContext.new(package, builderMethodsModule, resolver, env)
+ contextModule = eval("Module.nesting", block.binding).first
+ Util::MethodDelegation.registerDelegate(bc, contextModule, "const_missing")
+ BuilderContext.currentBuilderContext = bc
+ begin
+ #RubyProf.start
+ bc.instance_eval(&block)
+ #prof = RubyProf.stop
+ #File.open("profile_flat.txt","w+") do |f|
+ # RubyProf::FlatPrinter.new(prof).print(f, 0)
+ # end
+ ensure
+ BuilderContext.currentBuilderContext = nil
+ end
+ Util::MethodDelegation.unregisterDelegate(bc, contextModule, "const_missing")
+ #puts "Resolving..."
+ resolver.resolve(bc.toplevelElements)
+ bc.toplevelElements
+ end
+end
+
+end
diff --git a/lib/puppet/vendor/rgen/lib/rgen/model_builder/builder_context.rb b/lib/puppet/vendor/rgen/lib/rgen/model_builder/builder_context.rb
new file mode 100644
index 000000000..09de32a22
--- /dev/null
+++ b/lib/puppet/vendor/rgen/lib/rgen/model_builder/builder_context.rb
@@ -0,0 +1,334 @@
+require 'rgen/ecore/ecore_ext'
+require 'rgen/model_builder/reference_resolver'
+
+module RGen
+
+module ModelBuilder
+
+class BuilderContext
+ attr_reader :toplevelElements
+
+ def initialize(package, extensionsModule, resolver, env=nil)
+ package = package.ecore unless package.is_a?(RGen::ECore::EPackage)
+ raise "First argument must be a metamodel package" \
+ unless package.is_a?(RGen::ECore::EPackage)
+ @rootPackage, @env = package, env
+ @commandResolver = CommandResolver.new(package, extensionsModule, self)
+ @package = @rootPackage
+ @resolver = resolver
+ @contextStack = []
+ @toplevelElements = []
+ @helperNames = {}
+ end
+
+ def const_missing_delegated(delegator, const)
+ ConstPathElement.new(const, self)
+ end
+
+ # in Ruby 1.9.0 and 1.9.1 #instance_eval looks up constants in the calling scope
+ # that's why const_missing needs to be prepared in BuilderContext, too
+ class << self
+ def currentBuilderContext=(bc)
+ @@currentBuilderContext = bc
+ end
+
+ def const_missing(name)
+ if @@currentBuilderContext
+ ConstPathElement.new(name, @@currentBuilderContext)
+ else
+ super
+ end
+ end
+ end
+
+ class CommandResolver
+ def initialize(rootPackage, extensionsModule, builderContext)
+ @extensionFactory = ExtensionContainerFactory.new(rootPackage, extensionsModule, builderContext)
+ @packageResolver = PackageResolver.new(rootPackage, @extensionFactory)
+ @resolveCommand = {}
+ end
+
+ def resolveCommand(cmd, parentPackage)
+ return @resolveCommand[[parentPackage, cmd]] if @resolveCommand.has_key?([parentPackage, cmd])
+ package = @packageResolver.packageByCommand(parentPackage, cmd)
+ result = nil
+ if package
+ extensionContainer = @extensionFactory.extensionContainer(package)
+ if extensionContainer.respond_to?(cmd)
+ result = extensionContainer
+ else
+ className = cmd.to_s[0..0].upcase + cmd.to_s[1..-1]
+ result = package.eClasses.find{|c| c.name == className}
+ end
+ end
+ @resolveCommand[[parentPackage, cmd]] = [package, result]
+ end
+ end
+
+ def method_missing(m, *args, &block)
+ package, classOrContainer = @commandResolver.resolveCommand(m, @package)
+ return super if package.nil?
+ return classOrContainer.send(m, *args, &block) if classOrContainer.is_a?(ExtensionContainerFactory::ExtensionContainer)
+ eClass = classOrContainer
+ nameArg, argHash = self.class.processArguments(args)
+ internalName = nameArg || argHash[:name]
+ argHash[:name] ||= nameArg if nameArg && self.class.hasNameAttribute(eClass)
+ resolverJobs, asRole, helperName = self.class.filterArgHash(argHash, eClass)
+ element = eClass.instanceClass.new(argHash)
+ @resolver.setElementName(element, internalName)
+ @env << element if @env
+ contextElement = @contextStack.last
+ if contextElement
+ self.class.associateWithContextElement(element, contextElement, asRole)
+ else
+ @toplevelElements << element
+ end
+ resolverJobs.each do |job|
+ job.receiver = element
+ job.namespace = contextElement
+ @resolver.addJob(job)
+ end
+ # process block
+ if block
+ @contextStack.push(element)
+ @package, oldPackage = package, @package
+ instance_eval(&block)
+ @package = oldPackage
+ @contextStack.pop
+ end
+ element
+ end
+
+ def _using(constPathElement, &block)
+ @package, oldPackage =
+ self.class.resolvePackage(@package, @rootPackage, constPathElement.constPath), @package
+ instance_eval(&block)
+ @package = oldPackage
+ end
+
+ def _context(depth=1)
+ @contextStack[-depth]
+ end
+
+ class ExtensionContainerFactory
+
+ class ExtensionContainer
+ def initialize(builderContext)
+ @builderContext = builderContext
+ end
+ def method_missing(m, *args, &block)
+ @builderContext.send(m, *args, &block)
+ end
+ end
+
+ def initialize(rootPackage, extensionsModule, builderContext)
+ @rootPackage, @extensionsModule, @builderContext = rootPackage, extensionsModule, builderContext
+ @extensionContainer = {}
+ end
+
+ def moduleForPackage(package)
+ qName = package.qualifiedName
+ rqName = @rootPackage.qualifiedName
+ raise "Package #{qName} is not contained within #{rqName}" unless qName.index(rqName) == 0
+ path = qName.sub(rqName,'').split('::')
+ path.shift if path.first == ""
+ mod = @extensionsModule
+ path.each do |p|
+ if mod && mod.const_defined?(p)
+ mod = mod.const_get(p)
+ else
+ mod = nil
+ break
+ end
+ end
+ mod
+ end
+
+ def extensionContainer(package)
+ return @extensionContainer[package] if @extensionContainer[package]
+ container = ExtensionContainer.new(@builderContext)
+ extensionModule = moduleForPackage(package)
+ container.extend(extensionModule) if extensionModule
+ @extensionContainer[package] = container
+ end
+ end
+
+ class PackageResolver
+ def initialize(rootPackage, extensionFactory)
+ @rootPackage = rootPackage
+ @extensionFactory = extensionFactory
+ @packageByCommand = {}
+ end
+
+ def packageByCommand(contextPackage, name)
+ return @packageByCommand[[contextPackage, name]] if @packageByCommand.has_key?([contextPackage, name])
+ if @extensionFactory.extensionContainer(contextPackage).respond_to?(name)
+ result = contextPackage
+ else
+ className = name.to_s[0..0].upcase + name.to_s[1..-1]
+ eClass = contextPackage.eClasses.find{|c| c.name == className}
+ if eClass
+ result = contextPackage
+ elsif contextPackage != @rootPackage
+ result = packageByCommand(contextPackage.eSuperPackage, name)
+ else
+ result = nil
+ end
+ end
+ @packageByCommand[[contextPackage, name]] = result
+ end
+ end
+
+ class ConstPathElement < Module
+ def initialize(name, builderContext, parent=nil)
+ @name = name.to_s
+ @builderContext = builderContext
+ @parent = parent
+ end
+
+ def const_missing(const)
+ ConstPathElement.new(const, @builderContext, self)
+ end
+
+ def method_missing(m, *args, &block)
+ @builderContext._using(self) do
+ send(m, *args, &block)
+ end
+ end
+
+ def constPath
+ if @parent
+ @parent.constPath << @name
+ else
+ [@name]
+ end
+ end
+ end
+
+ # helper methods put in the class object to be out of the way of
+ # method evaluation in the builder context
+ class << self
+ class PackageNotFoundException < Exception
+ end
+
+ def resolvePackage(contextPackage, rootPackage, path)
+ begin
+ return resolvePackageDownwards(contextPackage, path)
+ rescue PackageNotFoundException
+ if contextPackage.eSuperPackage && contextPackage != rootPackage
+ return resolvePackage(contextPackage.eSuperPackage, rootPackage, path)
+ else
+ raise
+ end
+ end
+ end
+
+ def resolvePackageDownwards(contextPackage, path)
+ first, *rest = path
+ package = contextPackage.eSubpackages.find{|p| p.name == first}
+ raise PackageNotFoundException.new("Could not resolve package: #{first} is not a subpackage of #{contextPackage.name}") unless package
+ if rest.empty?
+ package
+ else
+ resolvePackageDownwards(package, rest)
+ end
+ end
+
+ def processArguments(args)
+ unless (args.size == 2 && args.first.is_a?(String) && args.last.is_a?(Hash)) ||
+ (args.size == 1 && (args.first.is_a?(String) || args.first.is_a?(Hash))) ||
+ args.size == 0
+ raise "Provide a Hash to set feature values, " +
+ "optionally the first argument may be a String specifying " +
+ "the value of the \"name\" attribute."
+ end
+ if args.last.is_a?(Hash)
+ argHash = args.last
+ else
+ argHash = {}
+ end
+ nameArg = args.first if args.first.is_a?(String)
+ [nameArg, argHash]
+ end
+
+ def filterArgHash(argHash, eClass)
+ resolverJobs = []
+ asRole, helperName = nil, nil
+ refByName = {}
+ eAllReferences(eClass).each {|r| refByName[r.name] = r}
+ argHash.each_pair do |k,v|
+ if k == :as
+ asRole = v
+ argHash.delete(k)
+ elsif k == :name && !hasNameAttribute(eClass)
+ helperName = v
+ argHash.delete(k)
+ elsif v.is_a?(String)
+ ref = refByName[k.to_s]#eAllReferences(eClass).find{|r| r.name == k.to_s}
+ if ref
+ argHash.delete(k)
+ resolverJobs << ReferenceResolver::ResolverJob.new(nil, ref, nil, v)
+ end
+ elsif v.is_a?(Array)
+ ref = refByName[k.to_s] #eAllReferences(eClass).find{|r| r.name == k.to_s}
+ ref && v.dup.each do |e|
+ if e.is_a?(String)
+ v.delete(e)
+ resolverJobs << ReferenceResolver::ResolverJob.new(nil, ref, nil, e)
+ end
+ end
+ end
+ end
+ [ resolverJobs, asRole, helperName ]
+ end
+
+ def hasNameAttribute(eClass)
+ @hasNameAttribute ||= {}
+ @hasNameAttribute[eClass] ||= eClass.eAllAttributes.any?{|a| a.name == "name"}
+ end
+
+ def eAllReferences(eClass)
+ @eAllReferences ||= {}
+ @eAllReferences[eClass] ||= eClass.eAllReferences
+ end
+
+ def containmentRefs(contextClass, eClass)
+ @containmentRefs ||= {}
+ @containmentRefs[[contextClass, eClass]] ||=
+ eAllReferences(contextClass).select do |r|
+ r.containment && (eClass.eAllSuperTypes << eClass).include?(r.eType)
+ end
+ end
+
+ def associateWithContextElement(element, contextElement, asRole)
+ return unless contextElement
+ contextClass = contextElement.class.ecore
+ if asRole
+ asRoleRef = eAllReferences(contextClass).find{|r| r.name == asRole.to_s}
+ raise "Context class #{contextClass.name} has no reference named #{asRole}" unless asRoleRef
+ ref = asRoleRef
+ else
+ possibleContainmentRefs = containmentRefs(contextClass, element.class.ecore)
+ if possibleContainmentRefs.size == 1
+ ref = possibleContainmentRefs.first
+ elsif possibleContainmentRefs.size == 0
+ raise "Context class #{contextClass.name} can not contain a #{element.class.ecore.name}"
+ else
+ raise "Context class #{contextClass.name} has several containment references to a #{element.class.ecore.name}." +
+ " Clearify using \":as => <role>\""
+ end
+ end
+ if ref.many
+ contextElement.addGeneric(ref.name, element)
+ else
+ contextElement.setGeneric(ref.name, element)
+ end
+ end
+
+ end
+
+end
+
+end
+
+end
diff --git a/lib/puppet/vendor/rgen/lib/rgen/model_builder/model_serializer.rb b/lib/puppet/vendor/rgen/lib/rgen/model_builder/model_serializer.rb
new file mode 100644
index 000000000..7799c4dc0
--- /dev/null
+++ b/lib/puppet/vendor/rgen/lib/rgen/model_builder/model_serializer.rb
@@ -0,0 +1,225 @@
+require 'rgen/array_extensions'
+require 'rgen/ecore/ecore_ext'
+
+module RGen
+
+module ModelBuilder
+
+class ModelSerializer
+
+ def initialize(writable, rootPackage)
+ @writable = writable
+ @currentPackage = rootPackage
+ @qualifiedElementName = {}
+ @internalElementName = {}
+ @relativeQualifiedElementName = {}
+ end
+
+ def serialize(elements)
+ calcQualifiedElementNames(elements)
+ unifyQualifiedElementNames
+ elements = [elements] unless elements.is_a?(Enumerable)
+ elements.each do |e|
+ serializeElement(e)
+ end
+ end
+
+ private
+
+ def serializeElement(element, viaRef=nil, namePath=[], indent=0)
+ className = element.class.ecore.name
+ cmd = className[0..0].downcase+className[1..-1]
+ args = ["\"#{@internalElementName[element]}\""]
+ namePath = namePath + [@internalElementName[element]]
+ childs = []
+ eAllStructuralFeatures(element).each do |f|
+ next if f.derived
+ if f.is_a?(RGen::ECore::EAttribute)
+ next if f.name == "name" && element.name == @internalElementName[element]
+ val = element.getGeneric(f.name)
+ #puts f.defaultValue.inspect if f.name == "isRoot"
+ args << ":#{f.name} => #{serializeAttribute(val)}" unless val == f.defaultValue || val.nil?
+ elsif !f.containment
+ next if f.eOpposite && f.eOpposite == viaRef
+ val = element.getGeneric(f.name)
+ refString = serializeReference(element, f, val)
+ args << ":#{f.name} => #{refString}" if refString
+ else
+ cs = element.getGeneric(f.name)
+ refString = nil
+ if cs.is_a?(Array)
+ cs.compact!
+ rcs = cs.select{|c| serializeChild?(c, namePath)}
+ childs << [f, rcs] unless rcs.empty?
+ refString = serializeReference(element, f, cs-rcs)
+ else
+ if cs && serializeChild?(cs, namePath)
+ childs << [f, [cs]]
+ else
+ refString = serializeReference(element, f, cs)
+ end
+ end
+ args << ":#{f.name} => #{refString}" if refString
+ end
+ end
+
+ args << ":as => :#{viaRef.name}" if viaRef && containmentRefs(viaRef.eContainingClass, element.class.ecore).size > 1
+ cmd = elementPackage(element)+"."+cmd if elementPackage(element).size > 0
+ @writable.write " " * indent + cmd + " " + args.join(", ")
+ if childs.size > 0
+ @writable.write " do\n"
+ oldPackage, @currentPackage = @currentPackage, element.class.ecore.ePackage
+ childs.each do |pair|
+ f, cs = pair
+ cs.each {|c| serializeElement(c, f, namePath, indent+1) }
+ end
+ @currentPackage = oldPackage
+ @writable.write " " * indent + "end\n"
+ else
+ @writable.write "\n"
+ end
+ end
+
+ def serializeChild?(child, namePath)
+ @qualifiedElementName[child][0..-2] == namePath
+ end
+
+ def serializeAttribute(value)
+ if value.is_a?(String)
+ "\"#{value.gsub("\"","\\\"")}\""
+ elsif value.is_a?(Symbol)
+ ":#{value}"
+ elsif value.nil?
+ "nil"
+ else
+ value.to_s
+ end
+ end
+
+ def serializeReference(element, ref, value)
+ if value.is_a?(Array)
+ value = value.compact
+ value = value.select{|v| compareWithOppositeReference(ref, element, v) > 0} if ref.eOpposite
+ qualNames = value.collect do |v|
+ relativeQualifiedElementName(v, element).join(".")
+ end
+ !qualNames.empty? && ("[" + qualNames.collect { |v| "\"#{v}\"" }.join(", ") + "]")
+ elsif value && (!ref.eOpposite || compareWithOppositeReference(ref, element, value) > 0)
+ qualName = relativeQualifiedElementName(value, element).join(".")
+ ("\"#{qualName}\"")
+ end
+ end
+
+ # descide which part of a bidirectional reference get serialized
+ def compareWithOppositeReference(ref, element, target)
+ result = 0
+ # first try to make the reference from the many side to the one side
+ result = -1 if ref.many && !ref.eOpposite.many
+ result = 1 if !ref.many && ref.eOpposite.many
+ return result if result != 0
+ # for 1:1 or many:many perfer, shorter references
+ result = relativeQualifiedElementName(element, target).size <=>
+ relativeQualifiedElementName(target, element).size
+ return result if result != 0
+ # there just needs to be a descision, use class name or object_id
+ result = element.class.name <=> target.class.name
+ return result if result != 0
+ element.object_id <=> target.object_id
+ end
+
+ def elementPackage(element)
+ @elementPackage ||= {}
+ return @elementPackage[element] if @elementPackage[element]
+ eNames = element.class.ecore.ePackage.qualifiedName.split("::")
+ rNames = @currentPackage.qualifiedName.split("::")
+ while eNames.first == rNames.first && !eNames.first.nil?
+ eNames.shift
+ rNames.shift
+ end
+ @elementPackage[element] = eNames.join("::")
+ end
+
+ def relativeQualifiedElementName(element, context)
+ return @relativeQualifiedElementName[[element, context]] if @relativeQualifiedElementName[[element, context]]
+ # elements which are not in the @qualifiedElementName Hash are not in the scope
+ # of this serialization and will be ignored
+ return [] if element.nil? || @qualifiedElementName[element].nil?
+ return [] if context.nil? || @qualifiedElementName[context].nil?
+ eNames = @qualifiedElementName[element].dup
+ cNames = @qualifiedElementName[context].dup
+ while eNames.first == cNames.first && eNames.size > 1
+ eNames.shift
+ cNames.shift
+ end
+ @relativeQualifiedElementName[[element, context]] = eNames
+ end
+
+ def calcQualifiedElementNames(elements, prefix=[], takenNames=[])
+ elements = [elements] unless elements.is_a?(Array)
+ elements.compact!
+ elements.each do |element|
+ qualifiedNamePath = prefix + [calcInternalElementName(element, takenNames)]
+ @qualifiedElementName[element] ||= []
+ @qualifiedElementName[element] << qualifiedNamePath
+ takenChildNames = []
+ eAllStructuralFeatures(element).each do |f|
+ if f.is_a?(RGen::ECore::EReference) && f.containment
+ childs = element.getGeneric(f.name)
+ calcQualifiedElementNames(childs, qualifiedNamePath, takenChildNames)
+ end
+ end
+ end
+ end
+
+ def unifyQualifiedElementNames
+ @qualifiedElementName.keys.each do |k|
+ @qualifiedElementName[k] = @qualifiedElementName[k].sort{|a,b| a.size <=> b.size}.first
+ end
+ end
+
+ def calcInternalElementName(element, takenNames)
+ return @internalElementName[element] if @internalElementName[element]
+ name = if element.respond_to?(:name) && element.name && !element.name.empty?
+ element.name
+ else
+ nextElementHelperName(element)
+ end
+ while takenNames.include?(name)
+ name = nextElementHelperName(element)
+ end
+ takenNames << name
+ @internalElementName[element] = name
+ end
+
+ def nextElementHelperName(element)
+ eClass = element.class.ecore
+ @nextElementNameId ||= {}
+ @nextElementNameId[eClass] ||= 1
+ result = "_#{eClass.name}#{@nextElementNameId[eClass]}"
+ @nextElementNameId[eClass] += 1
+ result
+ end
+
+ def eAllStructuralFeatures(element)
+ @eAllStructuralFeatures ||= {}
+ @eAllStructuralFeatures[element.class] ||= element.class.ecore.eAllStructuralFeatures
+ end
+
+ def eAllReferences(eClass)
+ @eAllReferences ||= {}
+ @eAllReferences[eClass] ||= eClass.eAllReferences
+ end
+
+ def containmentRefs(contextClass, eClass)
+ @containmentRefs ||= {}
+ @containmentRefs[[contextClass, eClass]] ||=
+ eAllReferences(contextClass).select do |r|
+ r.containment && (eClass.eAllSuperTypes << eClass).include?(r.eType)
+ end
+ end
+
+end
+
+end
+
+end
diff --git a/lib/puppet/vendor/rgen/lib/rgen/model_builder/reference_resolver.rb b/lib/puppet/vendor/rgen/lib/rgen/model_builder/reference_resolver.rb
new file mode 100644
index 000000000..cf870b0c2
--- /dev/null
+++ b/lib/puppet/vendor/rgen/lib/rgen/model_builder/reference_resolver.rb
@@ -0,0 +1,156 @@
+require 'rgen/array_extensions'
+
+module RGen
+
+module ModelBuilder
+
+class ReferenceResolver
+ ResolverJob = Struct.new(:receiver, :reference, :namespace, :string)
+
+ class ResolverException < Exception
+ end
+
+ class ToplevelNamespace
+ def initialize(ns)
+ raise "Namespace must be an Enumerable" unless ns.is_a?(Enumerable)
+ @ns = ns
+ end
+ def elements
+ @ns
+ end
+ end
+
+ def initialize
+ @jobs = []
+ @elementName = {}
+ end
+
+ def addJob(job)
+ @jobs << job
+ end
+
+ def setElementName(element, name)
+ @elementName[element] = name
+ end
+
+ def resolve(ns=[])
+ @toplevelNamespace = ToplevelNamespace.new(ns)
+ (@jobs || []).each_with_index do |job, i|
+ target = resolveReference(job.namespace || @toplevelNamespace, job.string.split("."))
+ raise ResolverException.new("Can not resolve reference #{job.string}") unless target
+ if job.reference.many
+ job.receiver.addGeneric(job.reference.name, target)
+ else
+ job.receiver.setGeneric(job.reference.name, target)
+ end
+ end
+ end
+
+ private
+
+ # TODO: if a reference can not be fully resolved, but a prefix can be found,
+ # the exception reported is that its first path element can not be found on
+ # toplevel
+ def resolveReference(namespace, nameParts)
+ element = resolveReferenceDownwards(namespace, nameParts)
+ if element.nil? && parentNamespace(namespace)
+ element = resolveReference(parentNamespace(namespace), nameParts)
+ end
+ element
+ end
+
+ def resolveReferenceDownwards(namespace, nameParts)
+ firstPart, *restParts = nameParts
+ element = namespaceElementByName(namespace, firstPart)
+ return nil unless element
+ if restParts.size > 0
+ resolveReferenceDownwards(element, restParts)
+ else
+ element
+ end
+ end
+
+ def namespaceElementByName(namespace, name)
+ @namespaceElementsByName ||= {}
+ return @namespaceElementsByName[namespace][name] if @namespaceElementsByName[namespace]
+ hash = {}
+ namespaceElements(namespace).each do |e|
+ raise ResolverException.new("Multiple elements named #{elementName(e)} found in #{nsToS(namespace)}") if hash[elementName(e)]
+ hash[elementName(e)] = e if elementName(e)
+ end
+ @namespaceElementsByName[namespace] = hash
+ hash[name]
+ end
+
+ def parentNamespace(namespace)
+ if namespace.class.respond_to?(:ecore)
+ parents = elementParents(namespace)
+ raise ResolverException.new("Element #{nsToS(namespace)} has multiple parents") \
+ if parents.size > 1
+ parents.first || @toplevelNamespace
+ else
+ nil
+ end
+ end
+
+ def namespaceElements(namespace)
+ if namespace.is_a?(ToplevelNamespace)
+ namespace.elements
+ elsif namespace.class.respond_to?(:ecore)
+ elementChildren(namespace)
+ else
+ raise ResolverException.new("Element #{nsToS(namespace)} can not be used as a namespace")
+ end
+ end
+
+ def nsToS(namespace)
+ if namespace.is_a?(ToplevelNamespace)
+ "toplevel namespace"
+ else
+ result = namespace.class.name
+ result += ":\"#{elementName(namespace)}\"" if elementName(namespace)
+ result
+ end
+ end
+
+ def elementName(element)
+ @elementName[element]
+ end
+
+ def elementChildren(element)
+ @elementChildren ||= {}
+ return @elementChildren[element] if @elementChildren[element]
+ children = containmentRefs(element).collect do |r|
+ element.getGeneric(r.name)
+ end.flatten.compact
+ @elementChildren[element] = children
+ end
+
+ def elementParents(element)
+ @elementParents ||= {}
+ return @elementParents[element] if @elementParents[element]
+ parents = parentRefs(element).collect do |r|
+ element.getGeneric(r.name)
+ end.flatten.compact
+ @elementParents[element] = parents
+ end
+
+ def containmentRefs(element)
+ @containmentRefs ||= {}
+ @containmentRefs[element.class] ||= eAllReferences(element).select{|r| r.containment}
+ end
+
+ def parentRefs(element)
+ @parentRefs ||= {}
+ @parentRefs[element.class] ||= eAllReferences(element).select{|r| r.eOpposite && r.eOpposite.containment}
+ end
+
+ def eAllReferences(element)
+ @eAllReferences ||= {}
+ @eAllReferences[element.class] ||= element.class.ecore.eAllReferences
+ end
+end
+
+end
+
+end \ No newline at end of file
diff --git a/lib/puppet/vendor/rgen/lib/rgen/serializer/json_serializer.rb b/lib/puppet/vendor/rgen/lib/rgen/serializer/json_serializer.rb
new file mode 100644
index 000000000..46e7bcf68
--- /dev/null
+++ b/lib/puppet/vendor/rgen/lib/rgen/serializer/json_serializer.rb
@@ -0,0 +1,121 @@
+module RGen
+
+module Serializer
+
+class JsonSerializer
+
+ def initialize(writer, opts={})
+ @writer = writer
+ @elementIdentifiers = {}
+ @identAttrName = opts[:identAttrName] || "name"
+ @separator = opts[:separator] || "/"
+ @leadingSeparator = opts.has_key?(:leadingSeparator) ? opts[:leadingSeparator] : true
+ @featureFilter = opts[:featureFilter]
+ @identifierProvider = opts[:identifierProvider]
+ end
+
+ def elementIdentifier(element)
+ ident = @identifierProvider && @identifierProvider.call(element)
+ ident || (element.is_a?(RGen::MetamodelBuilder::MMProxy) && element.targetIdentifier) || qualifiedElementName(element)
+ end
+
+ # simple identifier calculation based on qualified names
+ # prerequisits:
+ # * containment relations must be bidirectionsl
+ # * local name stored in single attribute +@identAttrName+ for all classes
+ #
+ def qualifiedElementName(element)
+ return @elementIdentifiers[element] if @elementIdentifiers[element]
+ localIdent = ((element.respond_to?(@identAttrName) && element.getGeneric(@identAttrName)) || "").strip
+ parentRef = element.class.ecore.eAllReferences.select{|r| r.eOpposite && r.eOpposite.containment}.first
+ parent = parentRef && element.getGeneric(parentRef.name)
+ if parent
+ if localIdent.size > 0
+ parentIdent = qualifiedElementName(parent)
+ result = parentIdent + @separator + localIdent
+ else
+ result = qualifiedElementName(parent)
+ end
+ else
+ result = (@leadingSeparator ? @separator : "") + localIdent
+ end
+ @elementIdentifiers[element] = result
+ end
+
+ def serialize(elements)
+ if elements.is_a?(Array)
+ write("[ ")
+ elements.each_with_index do |e, i|
+ serializeElement(e)
+ write(",\n") unless i == elements.size-1
+ end
+ write("]")
+ else
+ serializeElement(elements)
+ end
+ end
+
+ def serializeElement(element, indent="")
+ write(indent + "{ \"_class\": \""+element.class.ecore.name+"\"")
+ element.class.ecore.eAllStructuralFeatures.each do |f|
+ next if f.derived
+ value = element.getGeneric(f.name)
+ unless value == [] || value.nil? ||
+ (f.is_a?(RGen::ECore::EReference) && f.eOpposite && f.eOpposite.containment) ||
+ (@featureFilter && !@featureFilter.call(f))
+ write(", ")
+ writeFeature(f, value, indent)
+ end
+ end
+ write(" }")
+ end
+
+ def writeFeature(feat, value, indent)
+ write("\""+feat.name+"\": ")
+ if feat.is_a?(RGen::ECore::EAttribute)
+ if value.is_a?(Array)
+ write("[ "+value.collect{|v| attributeValue(v, feat)}.join(", ")+" ]")
+ else
+ write(attributeValue(value, feat))
+ end
+ elsif !feat.containment
+ if value.is_a?(Array)
+ write("[ "+value.collect{|v| "\""+elementIdentifier(v)+"\""}.join(", ")+" ]")
+ else
+ write("\""+elementIdentifier(value)+"\"")
+ end
+ else
+ if value.is_a?(Array)
+ write("[ \n")
+ value.each_with_index do |v, i|
+ serializeElement(v, indent+" ")
+ write(",\n") unless i == value.size-1
+ end
+ write("]")
+ else
+ write("\n")
+ serializeElement(value, indent+" ")
+ end
+ end
+ end
+
+ def attributeValue(value, a)
+ if a.eType == RGen::ECore::EString || a.eType.is_a?(RGen::ECore::EEnum)
+ "\""+value.to_s.gsub('\\','\\\\\\\\').gsub('"','\\"').gsub("\n","\\n").gsub("\r","\\r").
+ gsub("\t","\\t").gsub("\f","\\f").gsub("\b","\\b")+"\""
+ else
+ value.to_s
+ end
+ end
+
+ private
+
+ def write(s)
+ @writer.write(s)
+ end
+end
+
+end
+
+end
+
diff --git a/lib/puppet/vendor/rgen/lib/rgen/serializer/opposite_reference_filter.rb b/lib/puppet/vendor/rgen/lib/rgen/serializer/opposite_reference_filter.rb
new file mode 100644
index 000000000..7a6f7235c
--- /dev/null
+++ b/lib/puppet/vendor/rgen/lib/rgen/serializer/opposite_reference_filter.rb
@@ -0,0 +1,18 @@
+module RGen
+
+module Serializer
+
+# Filters refereences with an eOpposite:
+# 1. containment references are always preferred
+# 2. at a 1-to-n reference the 1-reference is always preferred
+# 3. otherwise the reference with the name in string sort order before the opposite's name is prefereed
+#
+OppositeReferenceFilter = proc do |features|
+ features.reject{|f| f.is_a?(RGen::ECore::EReference) && !f.containment && f.eOpposite &&
+ (f.eOpposite.containment || (f.many && !f.eOpposite.many) || (!(!f.many && f.eOpposite.many) && (f.name < f.eOpposite.name)))}
+end
+
+end
+
+end
+
diff --git a/lib/puppet/vendor/rgen/lib/rgen/serializer/qualified_name_provider.rb b/lib/puppet/vendor/rgen/lib/rgen/serializer/qualified_name_provider.rb
new file mode 100644
index 000000000..1fcce1bc5
--- /dev/null
+++ b/lib/puppet/vendor/rgen/lib/rgen/serializer/qualified_name_provider.rb
@@ -0,0 +1,47 @@
+module RGen
+
+module Serializer
+
+# simple identifier calculation based on qualified names.
+# as a prerequisit, elements must have a local name stored in single attribute +attribute_name+.
+# there may be classes without the name attribute though and there may be elements without a
+# local name. in both cases the element will have the same qualified name as its container.
+#
+class QualifiedNameProvider
+
+ def initialize(options={})
+ @qualified_name_cache = {}
+ @attribute_name = options[:attribute_name] || "name"
+ @separator = options[:separator] || "/"
+ @leading_separator = options.has_key?(:leading_separator) ? options[:leading_separator] : true
+ end
+
+ def identifier(element)
+ if element.is_a?(RGen::MetamodelBuilder::MMProxy)
+ element.targetIdentifier
+ else
+ qualified_name(element)
+ end
+ end
+
+ def qualified_name(element)
+ return @qualified_name_cache[element] if @qualified_name_cache[element]
+ local_ident = ((element.respond_to?(@attribute_name) && element.getGeneric(@attribute_name)) || "").strip
+ parent = element.eContainer
+ if parent
+ if local_ident.size > 0
+ result = qualified_name(parent) + @separator + local_ident
+ else
+ result = qualified_name(parent)
+ end
+ else
+ result = (@leading_separator ? @separator : "") + local_ident
+ end
+ @qualified_name_cache[element] = result
+ end
+end
+
+end
+
+end
+
diff --git a/lib/puppet/vendor/rgen/lib/rgen/serializer/xmi11_serializer.rb b/lib/puppet/vendor/rgen/lib/rgen/serializer/xmi11_serializer.rb
new file mode 100644
index 000000000..f76f6c3c7
--- /dev/null
+++ b/lib/puppet/vendor/rgen/lib/rgen/serializer/xmi11_serializer.rb
@@ -0,0 +1,116 @@
+require 'rgen/serializer/xml_serializer'
+
+module RGen
+
+module Serializer
+
+class XMI11Serializer < XMLSerializer
+
+ def initialize(file)
+ super
+ @namespacePrefix = ""
+ @contentLevelElements = []
+ end
+
+ def setNamespace(shortcut, url)
+ @namespaceShortcut = shortcut
+ @namespaceUrl = url
+ @namespacePrefix = shortcut+":"
+ end
+
+ def serialize(rootElement, headerInfo=nil)
+ attrs = []
+ attrs << ['xmi.version', "1.1"]
+ attrs << ['xmlns:'+@namespaceShortcut, @namespaceUrl] if @namespaceUrl
+ attrs << ['timestamp', Time.now.to_s]
+ startTag("XMI", attrs)
+ if headerInfo
+ startTag("XMI.header")
+ writeHeaderInfo(headerInfo)
+ endTag("XMI.header")
+ end
+ startTag("XMI.content")
+ @contentLevelElements = []
+ writeElement(rootElement)
+ # write remaining toplevel elements, each of which could have
+ # more toplevel elements as childs
+ while @contentLevelElements.size > 0
+ writeElement(@contentLevelElements.shift)
+ end
+ endTag("XMI.content")
+ endTag("XMI")
+ end
+
+ def writeHeaderInfo(hash)
+ hash.each_pair do |k,v|
+ tag = "XMI." + k.to_s
+ startTag(tag)
+ if v.is_a?(Hash)
+ writeHeaderInfo(v)
+ else
+ writeText(v.to_s)
+ end
+ endTag(tag)
+ end
+ end
+
+ def writeElement(element)
+ tag = @namespacePrefix + element.class.ecore.name
+ attrs = attributeValues(element)
+ startTag(tag, attrs)
+ containmentReferences(element).each do |r|
+ roletag = @namespacePrefix + r.eContainingClass.name + "." + r.name
+ targets = element.getGeneric(r.name)
+ targets = [ targets ] unless targets.is_a?(Array)
+ targets.compact!
+ next if targets.empty?
+ startTag(roletag)
+ targets.each do |t|
+ if xmiLevel(t) == :content
+ @contentLevelElements << t
+ else
+ writeElement(t)
+ end
+ end
+ endTag(roletag)
+ end
+ endTag(tag)
+ end
+
+ def attributeValues(element)
+ result = [["xmi.id", xmiId(element)]]
+ eAllAttributes(element).select{|a| !a.derived}.each do |a|
+ val = element.getGeneric(a.name)
+ result << [a.name, val] unless val.nil? || val == ""
+ end
+ eAllReferences(element).each do |r|
+ next if r.derived
+ next if r.containment
+ next if r.eOpposite && r.eOpposite.containment && xmiLevel(element).nil?
+ next if r.eOpposite && r.many && !r.eOpposite.many
+ targetElements = element.getGenericAsArray(r.name)
+ targetElements.compact!
+ val = targetElements.collect{|te| xmiId(te)}.compact.join(' ')
+ result << [r.name, val] unless val == ""
+ end
+ result
+ end
+
+ def xmiId(element)
+ if element.respond_to?(:_xmi_id) && element._xmi_id
+ element._xmi_id.to_s
+ else
+ element.object_id.to_s
+ end
+ end
+
+ def xmiLevel(element)
+ return nil unless element.respond_to?(:_xmi_level)
+ element._xmi_level
+ end
+
+end
+
+end
+
+end
diff --git a/lib/puppet/vendor/rgen/lib/rgen/serializer/xmi20_serializer.rb b/lib/puppet/vendor/rgen/lib/rgen/serializer/xmi20_serializer.rb
new file mode 100644
index 000000000..bfe7d8a3d
--- /dev/null
+++ b/lib/puppet/vendor/rgen/lib/rgen/serializer/xmi20_serializer.rb
@@ -0,0 +1,71 @@
+require 'rgen/serializer/xml_serializer'
+
+module RGen
+
+module Serializer
+
+class XMI20Serializer < XMLSerializer
+
+ def serialize(rootElement)
+ @referenceStrings = {}
+ buildReferenceStrings(rootElement, "#/")
+ addBuiltinReferenceStrings
+ attrs = attributeValues(rootElement)
+ attrs << ['xmi:version', "2.0"]
+ attrs << ['xmlns:xmi', "http://www.omg.org/XMI"]
+ attrs << ['xmlns:xsi', "http://www.w3.org/2001/XMLSchema-instance"]
+ attrs << ['xmlns:ecore', "http://www.eclipse.org/emf/2002/Ecore" ]
+ tag = "ecore:"+rootElement.class.ecore.name
+ startTag(tag, attrs)
+ writeComposites(rootElement)
+ endTag(tag)
+ end
+
+ def writeComposites(element)
+ eachReferencedElement(element, containmentReferences(element)) do |r,te|
+ attrs = attributeValues(te)
+ attrs << ['xsi:type', "ecore:"+te.class.ecore.name]
+ tag = r.name
+ startTag(tag, attrs)
+ writeComposites(te)
+ endTag(tag)
+ end
+ end
+
+ def attributeValues(element)
+ result = []
+ eAllAttributes(element).select{|a| !a.derived}.each do |a|
+ val = element.getGeneric(a.name)
+ result << [a.name, val] unless val.nil? || val == ""
+ end
+ eAllReferences(element).select{|r| !r.containment && !(r.eOpposite && r.eOpposite.containment) && !r.derived}.each do |r|
+ targetElements = element.getGenericAsArray(r.name)
+ val = targetElements.collect{|te| @referenceStrings[te]}.compact.join(' ')
+ result << [r.name, val] unless val.nil? || val == ""
+ end
+ result
+ end
+
+ def buildReferenceStrings(element, string)
+ @referenceStrings[element] = string
+ eachReferencedElement(element, containmentReferences(element)) do |r,te|
+ buildReferenceStrings(te, string+"/"+te.name) if te.respond_to?(:name)
+ end
+ end
+
+ def addBuiltinReferenceStrings
+ pre = "ecore:EDataType http://www.eclipse.org/emf/2002/Ecore"
+ @referenceStrings[RGen::ECore::EString] = pre+"#//EString"
+ @referenceStrings[RGen::ECore::EInt] = pre+"#//EInt"
+ @referenceStrings[RGen::ECore::ELong] = pre+"#//ELong"
+ @referenceStrings[RGen::ECore::EFloat] = pre+"#//EFloat"
+ @referenceStrings[RGen::ECore::EBoolean] = pre+"#//EBoolean"
+ @referenceStrings[RGen::ECore::EJavaObject] = pre+"#//EJavaObject"
+ @referenceStrings[RGen::ECore::EJavaClass] = pre+"#//EJavaClass"
+ end
+
+end
+
+end
+
+end
diff --git a/lib/puppet/vendor/rgen/lib/rgen/serializer/xml_serializer.rb b/lib/puppet/vendor/rgen/lib/rgen/serializer/xml_serializer.rb
new file mode 100644
index 000000000..c89da8ec1
--- /dev/null
+++ b/lib/puppet/vendor/rgen/lib/rgen/serializer/xml_serializer.rb
@@ -0,0 +1,98 @@
+module RGen
+
+module Serializer
+
+class XMLSerializer
+
+ INDENT_SPACE = 2
+
+ def initialize(file)
+ @indent = 0
+ @lastStartTag = nil
+ @textContent = false
+ @file = file
+ end
+
+ def serialize(rootElement)
+ raise "Abstract class, overwrite method in subclass!"
+ end
+
+ def startTag(tag, attributes={})
+ @textContent = false
+ handleLastStartTag(false, true)
+ if attributes.is_a?(Hash)
+ attrString = attributes.keys.collect{|k| "#{k}=\"#{attributes[k]}\""}.join(" ")
+ else
+ attrString = attributes.collect{|pair| "#{pair[0]}=\"#{pair[1]}\""}.join(" ")
+ end
+ @lastStartTag = " "*@indent*INDENT_SPACE + "<#{tag} "+attrString
+ @indent += 1
+ end
+
+ def endTag(tag)
+ @indent -= 1
+ unless handleLastStartTag(true, true)
+ output " "*@indent*INDENT_SPACE unless @textContent
+ output "</#{tag}>\n"
+ end
+ @textContent = false
+ end
+
+ def writeText(text)
+ handleLastStartTag(false, false)
+ output "#{text}"
+ @textContent = true
+ end
+
+ protected
+
+ def eAllReferences(element)
+ @eAllReferences ||= {}
+ @eAllReferences[element.class] ||= element.class.ecore.eAllReferences
+ end
+
+ def eAllAttributes(element)
+ @eAllAttributes ||= {}
+ @eAllAttributes[element.class] ||= element.class.ecore.eAllAttributes
+ end
+
+ def eAllStructuralFeatures(element)
+ @eAllStructuralFeatures ||= {}
+ @eAllStructuralFeatures[element.class] ||= element.class.ecore.eAllStructuralFeatures
+ end
+
+ def eachReferencedElement(element, refs, &block)
+ refs.each do |r|
+ targetElements = element.getGeneric(r.name)
+ targetElements = [targetElements] unless targetElements.is_a?(Array)
+ targetElements.each do |te|
+ yield(r,te)
+ end
+ end
+ end
+
+ def containmentReferences(element)
+ @containmentReferences ||= {}
+ @containmentReferences[element.class] ||= eAllReferences(element).select{|r| r.containment}
+ end
+
+ private
+
+ def handleLastStartTag(close, newline)
+ return false unless @lastStartTag
+ output @lastStartTag
+ output close ? "/>" : ">"
+ output "\n" if newline
+ @lastStartTag = nil
+ true
+ end
+
+ def output(text)
+ @file.write(text)
+ end
+
+end
+
+end
+
+end
diff --git a/lib/puppet/vendor/rgen/lib/rgen/template_language.rb b/lib/puppet/vendor/rgen/lib/rgen/template_language.rb
new file mode 100644
index 000000000..05fe5cc47
--- /dev/null
+++ b/lib/puppet/vendor/rgen/lib/rgen/template_language.rb
@@ -0,0 +1,297 @@
+# RGen Framework
+# (c) Martin Thiede, 2006
+
+require 'rgen/template_language/directory_template_container'
+require 'rgen/template_language/template_container'
+
+module RGen
+
+# The RGen template language has been designed to build complex generators.
+# It is very similar to the EXPAND language of the Java based
+# OpenArchitectureWare framework.
+#
+# =Templates
+#
+# The basic idea is to allow "templates" not only being template files
+# but smaller parts. Those parts can be expanded from other parts very
+# much like Ruby methods are called from other methods.
+# Thus the term "template" refers to such a part within a "template file".
+#
+# Template files used by the RGen template language should have a
+# filename with the postfix ".tpl". Those files can reside within (nested)
+# template file directories.
+#
+# As an example a template directory could look like the following:
+#
+# templates/root.tpl
+# templates/dbaccess/dbaccess.tpl
+# templates/dbaccess/schema.tpl
+# templates/headers/generic_headers.tpl
+# templates/headers/specific/component.tpl
+#
+# A template is always called for a <i>context object</i>. The context object
+# serves as the receiver of methods called within the template. Details are given
+# below.
+#
+#
+# =Defining Templates
+#
+# One or more templates can be defined in a template file using the +define+
+# keyword as in the following example:
+#
+# <% define 'GenerateDBAdapter', :for => DBDescription do |dbtype| %>
+# Content to be generated; use ERB syntax here
+# <% end %>
+#
+# The template definition takes three kinds of parameters:
+# 1. The name of the template within the template file as a String or Symbol
+# 2. An optional class object describing the class of context objects for which
+# this template is valid.
+# 3. An arbitrary number of template parameters
+# See RGen::TemplateLanguage::TemplateContainer for details about the syntax of +define+.
+#
+# Within a template, regular ERB syntax can be used. This is
+# * <code><%</code> and <code>%></code> are used to embed Ruby code
+# * <code><%=</code> and <code>%></code> are used to embed Ruby expressions with
+# the expression result being written to the template output
+# * <code><%#</code> and <code>%></code> are used for comments
+# All content not within these tags is written to the template output verbatim.
+# See below for details about output files and output formatting.
+#
+# All methods which are called from within the template are sent to the context
+# object.
+#
+# Experience shows that one easily forgets the +do+ at the end of the first
+# line of a template definition. This will result in an ERB parse error.
+#
+#
+# =Expanding Templates
+#
+# Templates are normally expanded from within other templates. The only
+# exception is the root template, which is expanded from the surrounding code.
+#
+# Template names can be specified in the following ways:
+# * Non qualified name: use the template with the given name in the current template file
+# * Relative qualified name: use the template within the template file specified by the relative path
+# * Absolute qualified name: use the template within the template file specified by the absolute path
+#
+# The +expand+ keyword is used to expand templates.
+#
+# Here are some examples:
+#
+# <% expand 'GenerateDBAdapter', dbtype, :for => dbDesc %>
+#
+# <i>Non qualified</i>. Must be called within the file where 'GenerateDBAdapter' is defined.
+# There is one template parameter passed in via variable +dbtype+.
+# The context object is provided in variable +dbDesc+.
+#
+# <% expand 'dbaccess::ExampleSQL' %>
+#
+# <i>Qualified with filename</i>. Must be called from a file in the same directory as 'dbaccess.tpl'
+# There are no parameters. The current context object will be used as the context
+# object for this template expansion.
+#
+# <% expand '../headers/generic_headers::CHeader', :foreach => modules %>
+#
+# <i>Relatively qualified</i>. Must be called from a location from which the file
+# 'generic_headers.tpl' is accessible via the relative path '../headers'.
+# The template is expanded for each module in +modules+ (which has to be an Array).
+# Each element of +modules+ will be the context object in turn.
+#
+# <% expand '/headers/generic_headers::CHeader', :foreach => modules %>
+#
+# Absolutely qualified: The same behaviour as before but with an absolute path from
+# the template directory root (which in this example is 'templates', see above)
+#
+# Sometimes it is neccessary to generate some text (e.g. a ',') in between the single
+# template expansion results from a <code>:foreach</code> expansion. This can be achieved by
+# using the <code>:separator</code> keyword:
+#
+# <% expand 'ColumnName', :foreach => column, :separator => ', ' %>
+#
+# Note that the separator may also contain newline characters (\n). See below for
+# details about formatting.
+#
+#
+# =Formatting
+#
+# For many generator tools a formatting postprocess (e.g. using a pretty printer) is
+# required in order to make the output readable. However, depending on the kind of
+# generated output, such a tool might not be available.
+#
+# The RGen template language has been design for generators which do not need a
+# postprocessing step. The basic idea is to eliminate all whitespace at the beginning
+# of template lines (the indentation that makes the _template_ readable) and output
+# newlines only after at least on character has been generated in the corresponding
+# line. This way there are no empty lines in the output and each line will start with
+# a non-whitspace character.
+#
+# Starting from this point one can add indentation and newlines as required by using
+# explicit formatting commands:
+# * <code><%nl%></code> (newline) starts a new line
+# * <code><%iinc%></code> (indentation increment) increases the current indentation
+# * <code><%idec%></code> (indentation decrement) decreases the current indentation
+# * <code><%nonl%></code> (no newline) ignore next newline
+# * <code><%nows%></code> (no whitespace) ignore next whitespace
+#
+# Indentation takes place for every new line in the output unless it is 0.
+# The initial indentation can be specified with a root +expand+ command by using
+# the <code>:indent</code> keyword.
+#
+# Here is an example:
+#
+# expand 'GenerateDBAdapter', dbtype, :for => dbDesc, :indent => 1
+#
+# Initial indentation defaults to 0. Normally <code><%iinc%></code> and
+# <code><%idec%></code> are used to change the indentation.
+# The current indentation is kept for expansion of subtemplates.
+#
+# The string which is used to realize one indentation step can be set using
+# DirectoryTemplateContainer#indentString or with the template language +file+ command.
+# The default is " " (3 spaces), the indentation string given at a +file+ command
+# overwrites the container's default which in turn overwrites the overall default.
+#
+# Note that commands to ignore whitespace and newlines are still useful if output
+# generated from multiple template lines should show up in one single output line.
+#
+# Here is an example of a template generating a C program:
+#
+# #include <stdio.h>
+# <%nl%>
+# int main() {<%iinc%>
+# printf("Hello World\n");
+# return 0;<%idec>
+# }
+#
+# The result is:
+#
+# #include <stdio.h>
+#
+# int main() {
+# printf("Hello World\n");
+# return 0;
+# }
+#
+# Note that without the explicit formatting commands, the output generated from the
+# example above would not have any empty lines or whitespace in the beginning of lines.
+# This may seem like unneccessary extra work for the example above which could also
+# have been generated by passing the template to the output verbatimly.
+# However in most cases templates will contain more template specific indentation and
+# newlines which should be eliminated than formatting that should be visible in the
+# output.
+#
+# Here is a more realistic example for generating C function prototypes:
+#
+# <% define 'Prototype', :for => CFunction do %>
+# <%= getType.name %> <%= name %>(<%nows%>
+# <% expand 'Signature', :foreach => argument, :separator => ', ' %>);
+# <% end %>
+#
+# <% define 'Signature', :for => CFunctionArgument do %>
+# <%= getType.name %> <%= name%><%nows%>
+# <% end %>
+#
+# The result could look something like:
+#
+# void somefunc(int a, float b, int c);
+# int otherfunc(short x);
+#
+# In this example a separator is used to join the single arguments of the C functions.
+# Note that the template generating the argument type and name needs to contain
+# a <code><%nows%></code> if the result should consist of a single line.
+#
+# Here is one more example for generating C array initializations:
+#
+# <% define 'Array', :for => CArray do %>
+# <%= getType.name %> <%= name %>[<%= size %>] = {<%iinc%>
+# <% expand 'InitValue', :foreach => initvalue, :separator => ",\n" %><%nl%><%idec%>
+# };
+# <% end %>
+#
+# <% define 'InitValue', :for => PrimitiveInitValue do %>
+# <%= value %><%nows%>
+# <% end %>
+#
+# The result could look something like:
+#
+# int myArray[3] = {
+# 1,
+# 2,
+# 3
+# };
+#
+# Note that in this example, the separator contains a newline. The current increment
+# will be applied to each single expansion result since it starts in a new line.
+#
+#
+# =Output Files
+#
+# Normally the generated content is to be written into one or more output files.
+# The RGen template language facilitates this by means of the +file+ keyword.
+#
+# When the +file+ keyword is used to define a block, all output generated
+# from template code within this block will be written to the specified file.
+# This includes output generated from template expansions.
+# Thus all output from templates expanded within this block is written to
+# the same file as long as those templates do not use the +file+ keyword to
+# define a new file context.
+#
+# Here is an example:
+#
+# <% file 'dbadapter/'+adapter.name+'.c' do %>
+# all content within this block will be written to the specified file
+# <% end %>
+#
+# Note that the filename itself can be calculated dynamically by an arbitrary
+# Ruby expression.
+#
+# The absolute position where the output file is created depends on the output
+# root directory passed to DirectoryTemplateContainer as described below.
+#
+# As a second argument, the +file+ command can take the indentation string which is
+# used to indent output lines (see Formatting).
+#
+# =Setting up the Generator
+#
+# Setting up the generator consists of 3 steps:
+# * Instantiate DirectoryTemplateContainer passing one or more metamodel(s) and the output
+# directory to the constructor.
+# * Load the templates into the template container
+# * Expand the root template to start generation
+#
+# Here is an example:
+#
+# module MyMM
+# # metaclasses are defined here, e.g. using RGen::MetamodelBuilder
+# end
+#
+# OUTPUT_DIR = File.dirname(__FILE__)+"/output"
+# TEMPLATES_DIR = File.dirname(__FILE__)+"/templates"
+#
+# tc = RGen::TemplateLanguage::DirectoryTemplateContainer.new(MyMM, OUTPUT_DIR)
+# tc.load(TEMPLATES_DIR)
+# # testModel should hold an instance of the metamodel class expected by the root template
+# # the following line starts generation
+# tc.expand('root::Root', :for => testModel, :indent => 1)
+#
+# The metamodel is the Ruby module which contains the metaclasses.
+# This information is required for the template container in order to resolve the
+# metamodel classes used within the template file.
+# If several metamodels shall be used, an array of modules can be passed instead
+# of a single module.
+#
+# The output path is prepended to the relative paths provided to the +file+
+# definitions in the template files.
+#
+# The template directory should contain template files as described above.
+#
+# Finally the generation process is started by calling +expand+ in the same way as it
+# is used from within templates.
+#
+# Also see the unit tests for more examples.
+#
+module TemplateLanguage
+
+end
+
+end \ No newline at end of file
diff --git a/lib/puppet/vendor/rgen/lib/rgen/template_language/directory_template_container.rb b/lib/puppet/vendor/rgen/lib/rgen/template_language/directory_template_container.rb
new file mode 100644
index 000000000..5f355b6c4
--- /dev/null
+++ b/lib/puppet/vendor/rgen/lib/rgen/template_language/directory_template_container.rb
@@ -0,0 +1,83 @@
+# RGen Framework
+# (c) Martin Thiede, 2006
+
+require 'rgen/template_language/template_container'
+require 'rgen/template_language/template_helper'
+
+module RGen
+
+module TemplateLanguage
+
+class DirectoryTemplateContainer
+ include TemplateHelper
+
+ def initialize(metamodel=nil, output_path=nil, parent=nil)
+ @containers = {}
+ @directoryContainers = {}
+ @parent = parent
+ @metamodel = metamodel
+ @output_path = output_path
+ end
+
+ def load(dir)
+ Dir.foreach(dir) { |f|
+ qf = dir+"/"+f
+ if !File.directory?(qf) && f =~ /^(.*)\.tpl$/
+ (@containers[$1] = TemplateContainer.new(@metamodel, @output_path, self,qf)).load
+ elsif File.directory?(qf) && f != "." && f != ".."
+ (@directoryContainers[f] = DirectoryTemplateContainer.new(@metamodel, @output_path, self)).load(qf)
+ end
+ }
+ end
+
+ def expand(template, *all_args)
+ if template =~ /^\//
+ if @parent
+ # pass to parent
+ @parent.expand(template, *all_args)
+ else
+ # this is root
+ _expand(template, *all_args)
+ end
+ elsif template =~ /^\.\.\/(.*)/
+ if @parent
+ # pass to parent
+ @parent.expand($1, *all_args)
+ else
+ raise "No parent directory for root"
+ end
+ else
+ _expand(template, *all_args)
+ end
+ end
+
+ # Set indentation string.
+ # Every generated line will be prependend with n times this string at an indentation level of n.
+ # Defaults to " " (3 spaces)
+ def indentString=(str)
+ @indentString = str
+ end
+
+ def indentString
+ @indentString || (@parent && @parent.indentString) || " "
+ end
+
+ private
+
+ def _expand(template, *all_args)
+ if template =~ /^\/?(\w+)::(\w.*)/
+ raise "Template not found: #{$1}" unless @containers[$1]
+ @containers[$1].expand($2, *all_args)
+ elsif template =~ /^\/?(\w+)\/(\w.*)/
+ raise "Template not found: #{$1}" unless @directoryContainers[$1]
+ @directoryContainers[$1].expand($2, *all_args)
+ else
+ raise "Invalid template name: #{template}"
+ end
+ end
+
+end
+
+end
+
+end \ No newline at end of file
diff --git a/lib/puppet/vendor/rgen/lib/rgen/template_language/output_handler.rb b/lib/puppet/vendor/rgen/lib/rgen/template_language/output_handler.rb
new file mode 100644
index 000000000..e6cd692a1
--- /dev/null
+++ b/lib/puppet/vendor/rgen/lib/rgen/template_language/output_handler.rb
@@ -0,0 +1,87 @@
+# RGen Framework
+# (c) Martin Thiede, 2006
+
+module RGen
+
+module TemplateLanguage
+
+ class OutputHandler
+ attr_writer :indent
+ attr_accessor :noIndentNextLine
+
+ def initialize(indent=0, indentString=" ", mode=:explicit)
+ self.mode = mode
+ @indent = indent
+ @indentString = indentString
+ @state = :wait_for_nonws
+ @output = ""
+ end
+
+ # ERB will call this method for every string s which is part of the
+ # template file in between %> and <%. If s contains a newline, it will
+ # call this method for every part of s which is terminated by a \n
+ #
+ def concat(s)
+ return @output.concat(s) if s.is_a? OutputHandler
+ #puts [object_id, noIndentNextLine, @state, @output.to_s, s].inspect
+ s = s.to_str.gsub(/^[\t ]*\r?\n/,'') if @ignoreNextNL
+ s = s.to_str.gsub(/^\s+/,'') if @ignoreNextWS
+ @ignoreNextNL = @ignoreNextWS = false if s =~ /\S/
+ if @mode == :direct
+ @output.concat(s)
+ elsif @mode == :explicit
+ while s.size > 0
+ if @state == :wait_for_nl
+ if s =~ /\A([^\r\n]*\r?\n)(.*)/m
+ rest = $2
+ @output.concat($1.gsub(/[\t ]+(?=\r|\n)/,''))
+ s = rest || ""
+ @state = :wait_for_nonws
+ else
+ @output.concat(s)
+ s = ""
+ end
+ elsif @state == :wait_for_nonws
+ if s =~ /\A\s*(\S+.*)/m
+ s = $1 || ""
+ if !@noIndentNextLine && !(@output.to_s.size > 0 && @output.to_s[-1] != "\n"[0])
+ @output.concat(@indentString * @indent)
+ else
+ @noIndentNextLine = false
+ end
+ @state = :wait_for_nl
+ else
+ s = ""
+ end
+ end
+ end
+ end
+ end
+ alias << concat
+
+ def to_str
+ @output
+ end
+ alias to_s to_str
+
+ def direct_concat(s)
+ @output.concat(s)
+ end
+
+ def ignoreNextNL
+ @ignoreNextNL = true
+ end
+
+ def ignoreNextWS
+ @ignoreNextWS = true
+ end
+
+ def mode=(m)
+ raise StandardError.new("Unknown mode: #{m}") unless [:direct, :explicit].include?(m)
+ @mode = m
+ end
+ end
+
+end
+
+end \ No newline at end of file
diff --git a/lib/puppet/vendor/rgen/lib/rgen/template_language/template_container.rb b/lib/puppet/vendor/rgen/lib/rgen/template_language/template_container.rb
new file mode 100644
index 000000000..0a8331448
--- /dev/null
+++ b/lib/puppet/vendor/rgen/lib/rgen/template_language/template_container.rb
@@ -0,0 +1,234 @@
+# RGen Framework
+# (c) Martin Thiede, 2006
+
+require 'erb'
+require 'fileutils'
+require 'rgen/template_language/output_handler'
+require 'rgen/template_language/template_helper'
+
+module RGen
+
+ module TemplateLanguage
+
+ class TemplateContainer
+ include TemplateHelper
+
+ def initialize(metamodels, output_path, parent, filename)
+ @templates = {}
+ @parent = parent
+ @filename = filename
+ @indent = 0
+ @output_path = output_path
+ @metamodels = metamodels
+ @metamodels = [ @metamodels ] unless @metamodels.is_a?(Array)
+ end
+
+ def load
+ File.open(@filename,"rb") do |f|
+ begin
+ @@metamodels = @metamodels
+ fileContent = f.read
+ _detectNewLinePattern(fileContent)
+ ERB.new(fileContent,nil,nil,'@output').result(binding)
+ rescue Exception => e
+ processAndRaise(e)
+ end
+ end
+ end
+
+ def expand(template, *all_args)
+ args, params = _splitArgsAndOptions(all_args)
+ if params.has_key?(:foreach)
+ raise StandardError.new("expand :foreach argument is not enumerable") \
+ unless params[:foreach].is_a?(Enumerable)
+ _expand_foreach(template, args, params)
+ else
+ _expand(template, args, params)
+ end
+ end
+
+ def evaluate(template, *all_args)
+ args, params = _splitArgsAndOptions(all_args)
+ raise StandardError.new(":foreach can not be used with evaluate") if params[:foreach]
+ _expand(template, args, params.merge({:_evalOnly => true}))
+ end
+
+ def this
+ @context
+ end
+
+ def method_missing(name, *args)
+ @context.send(name, *args)
+ end
+
+ def self.const_missing(name)
+ super unless @@metamodels
+ @@metamodels.each do |mm|
+ return mm.const_get(name) rescue NameError
+ end
+ super
+ end
+
+ private
+
+ def nonl
+ @output.ignoreNextNL
+ end
+
+ def nows
+ @output.ignoreNextWS
+ end
+
+ def nl
+ _direct_concat(@newLinePattern)
+ end
+
+ def ws
+ _direct_concat(" ")
+ end
+
+ def iinc
+ @indent += 1
+ @output.indent = @indent
+ end
+
+ def idec
+ @indent -= 1 if @indent > 0
+ @output.indent = @indent
+ end
+
+ TemplateDesc = Struct.new(:block, :local)
+
+ def define(template, params={}, &block)
+ _define(template, params, &block)
+ end
+
+ def define_local(template, params={}, &block)
+ _define(template, params.merge({:local => true}), &block)
+ end
+
+ def file(name, indentString=nil)
+ old_output, @output = @output, OutputHandler.new(@indent, indentString || @parent.indentString)
+ begin
+ yield
+ rescue Exception => e
+ processAndRaise(e)
+ end
+ path = ""
+ path += @output_path+"/" if @output_path
+ dirname = File.dirname(path+name)
+ FileUtils.makedirs(dirname) unless File.exist?(dirname)
+ File.open(path+name,"wb") { |f| f.write(@output) }
+ @output = old_output
+ end
+
+ # private private
+
+ def _define(template, params={}, &block)
+ @templates[template] ||= {}
+ cls = params[:for] || Object
+ @templates[template][cls] = TemplateDesc.new(block, params[:local])
+ end
+
+ def _expand_foreach(template, args, params)
+ sep = params[:separator]
+ params[:foreach].each_with_index {|e,i|
+ _direct_concat(sep.to_s) if sep && i > 0
+ output = _expand(template, args, params.merge({:for => e}))
+ }
+ end
+
+ LOCAL_TEMPLATE_REGEX = /^:*(\w+)$/
+
+ def _expand(template, args, params)
+ raise StandardError.new("expand :for argument evaluates to nil") if params.has_key?(:for) && params[:for].nil?
+ context = params[:for]
+ old_indent = @indent
+ @indent = params[:indent] || @indent
+ noIndentNextLine = params[:_noIndentNextLine] ||
+ (@output.is_a?(OutputHandler) && @output.noIndentNextLine) ||
+ (@output.to_s.size > 0 && @output.to_s[-1] != "\n"[0])
+ caller = params[:_caller] || self
+ old_context, @context = @context, context if context
+ local_output = nil
+ if template =~ LOCAL_TEMPLATE_REGEX
+ tplname = $1
+ raise StandardError.new("Template not found: #{$1}") unless @templates[tplname]
+ old_output, @output = @output, OutputHandler.new(@indent, @parent.indentString)
+ @output.noIndentNextLine = noIndentNextLine
+ _call_template(tplname, @context, args, caller == self)
+ old_output.noIndentNextLine = false if old_output.is_a?(OutputHandler) && !old_output.noIndentNextLine
+ local_output, @output = @output, old_output
+ else
+ local_output = @parent.expand(template, *(args.dup << {:for => @context, :indent => @indent, :_noIndentNextLine => noIndentNextLine, :_evalOnly => true, :_caller => caller}))
+ end
+ _direct_concat(local_output) unless params[:_evalOnly]
+ @context = old_context if old_context
+ @indent = old_indent
+ local_output.to_s
+ end
+
+ def processAndRaise(e, tpl=nil)
+ bt = e.backtrace.dup
+ e.backtrace.each_with_index do |t,i|
+ if t =~ /\(erb\):(\d+):/
+ bt[i] = "#{@filename}:#{$1}"
+ bt[i] += ":in '#{tpl}'" if tpl
+ break
+ end
+ end
+ raise e, e.to_s, bt
+ end
+
+ def _call_template(tpl, context, args, localCall)
+ found = false
+ @templates[tpl].each_pair do |key, value|
+ if context.is_a?(key)
+ templateDesc = @templates[tpl][key]
+ proc = templateDesc.block
+ arity = proc.arity
+ arity = 0 if arity == -1 # if no args are given
+ raise StandardError.new("Wrong number of arguments calling template #{tpl}: #{args.size} for #{arity} "+
+ "(Beware: Hashes as last arguments are taken as options and are ignored)") \
+ if arity != args.size
+ raise StandardError.new("Template can only be called locally: #{tpl}") \
+ if templateDesc.local && !localCall
+ begin
+ @@metamodels = @metamodels
+ proc.call(*args)
+ rescue Exception => e
+ processAndRaise(e, tpl)
+ end
+ found = true
+ end
+ end
+ raise StandardError.new("Template class not matching: #{tpl} for #{context.class.name}") unless found
+ end
+
+ def _direct_concat(s)
+ if @output.is_a? OutputHandler
+ @output.direct_concat(s)
+ else
+ @output << s
+ end
+ end
+ def _detectNewLinePattern(text)
+ tests = 0
+ rnOccurances = 0
+ text.scan(/(\r?)\n/) do |groups|
+ tests += 1
+ rnOccurances += 1 if groups[0] == "\r"
+ break if tests >= 10
+ end
+ if rnOccurances > (tests / 2)
+ @newLinePattern = "\r\n"
+ else
+ @newLinePattern = "\n"
+ end
+ end
+
+ end
+
+ end
+
+end
diff --git a/lib/puppet/vendor/rgen/lib/rgen/template_language/template_helper.rb b/lib/puppet/vendor/rgen/lib/rgen/template_language/template_helper.rb
new file mode 100644
index 000000000..5eb3a98a2
--- /dev/null
+++ b/lib/puppet/vendor/rgen/lib/rgen/template_language/template_helper.rb
@@ -0,0 +1,26 @@
+# RGen Framework
+# (c) Martin Thiede, 2006
+
+module RGen
+
+module TemplateLanguage
+
+module TemplateHelper
+
+ private
+
+ def _splitArgsAndOptions(all)
+ if all[-1] and all[-1].is_a? Hash
+ args = all[0..-2] || []
+ options = all[-1]
+ else
+ args = all
+ options = {}
+ end
+ return args, options
+ end
+end
+
+end
+
+end \ No newline at end of file
diff --git a/lib/puppet/vendor/rgen/lib/rgen/transformer.rb b/lib/puppet/vendor/rgen/lib/rgen/transformer.rb
new file mode 100644
index 000000000..097c089c5
--- /dev/null
+++ b/lib/puppet/vendor/rgen/lib/rgen/transformer.rb
@@ -0,0 +1,475 @@
+require 'rgen/ecore/ecore'
+require 'rgen/ecore/ecore_ext'
+
+module RGen
+
+# The Transformer class can be used to specify model transformations.
+#
+# Model transformations take place between a <i>source model</i> (located in the <i>source
+# environment</i> being an instance of the <i>source metamodel</i>) and a <i>target model</i> (located
+# in the <i>target environment</i> being an instance of the <i>target metamodel</i>).
+# Normally a "model" consists of several model elements associated with each other.
+#
+# =Transformation Rules
+#
+# The transformation is specified within a subclass of Transformer.
+# Within the subclass, the Transformer.transform class method can be used to specify transformation
+# blocks for specific metamodel classes of the source metamodel.
+#
+# If there is no transformation rule for the current object's class, a rule for the superclass
+# is used instead. If there's no rule for the superclass, the class hierarchy is searched
+# this way until Object.
+#
+# Here is an example:
+#
+# class MyTransformer < RGen::Transformer
+#
+# transform InputClass, :to => OutputClass do
+# { :name => name, :otherClass => trans(otherClass) }
+# end
+#
+# transform OtherInputClass, :to => OtherOutputClass do
+# { :name => name }
+# end
+# end
+#
+# In this example a transformation rule is specified for model elements of class InputClass
+# as well as for elements of class OtherInputClass. The former is to be transformed into
+# an instance of OutputClass, the latter into an instance of OtherOutputClass.
+# Note that the Ruby class objects are used to specifiy the classes.
+#
+# =Transforming Attributes
+#
+# Besides the target class of a transformation, the attributes of the result object are
+# specified in the above example. This is done by providing a Ruby block with the call of
+# +transform+. Within this block arbitrary Ruby code may be placed, however the block
+# must return a hash. This hash object specifies the attribute assignment of the
+# result object using key/value pairs: The key must be a Symbol specifying the attribute
+# which is to be assigned by name, the value is the value that will be assigned.
+#
+# For convenience, the transformation block will be evaluated in the context of the
+# source model element which is currently being converted. This way it is possible to just
+# write <code>:name => name</code> in the example in order to assign the name of the source
+# object to the name attribute of the target object.
+#
+# =Transforming References
+#
+# When attributes of elements are references to other elements, those referenced
+# elements have to be transformed as well. As shown above, this can be done by calling
+# the Transformer#trans method. This method initiates a transformation of the element
+# or array of elements passed as parameter according to transformation rules specified
+# using +transform+. In fact the +trans+ method is the only way to start the transformation
+# at all.
+#
+# For convenience and performance reasons, the result of +trans+ is cached with respect
+# to the parameter object. I.e. calling trans on the same source object a second time will
+# return the same result object _without_ a second evaluation of the corresponding
+# transformation rules.
+#
+# This way the +trans+ method can be used to lookup the target element for some source
+# element without the need to locally store a reference to the target element. In addition
+# this can be useful if it is not clear if certain element has already been transformed
+# when it is required within some other transformation block. See example below.
+#
+# Special care has been taken to allow the transformation of elements which reference
+# each other cyclically. The key issue here is that the target element of some transformation
+# is created _before_ the transformation's block is evaluated, i.e before the elements
+# attributes are set. Otherwise a call to +trans+ within the transformation's block
+# could lead to a +trans+ of the element itself.
+#
+# Here is an example:
+#
+# transform ModelAIn, :to => ModelAOut do
+# { :name => name, :modelB => trans(modelB) }
+# end
+#
+# transform ModelBIn, :to => ModelBOut do
+# { :name => name, :modelA => trans(modelA) }
+# end
+#
+# Note that in this case it does not matter if the transformation is initiated by calling
+# +trans+ with a ModelAIn element or ModelBIn element due to the caching feature described
+# above.
+#
+# =Transformer Methods
+#
+# When code in transformer blocks becomes more complex it might be useful to refactor
+# it into smaller methods. If regular Ruby methods within the Transformer subclass are
+# used for this purpose, it is necessary to know the source element being transformed.
+# This could be achieved by explicitly passing the +@current_object+ as parameter of the
+# method (see Transformer#trans).
+#
+# A more convenient way however is to define a special kind of method using the
+# Transformer.method class method. Those methods are evaluated within the context of the
+# current source element being transformed just the same as transformer blocks are.
+#
+# Here is an example:
+#
+# transform ModelIn, :to => ModelOut do
+# { :number => doubleNumber }
+# end
+#
+# method :doubleNumber do
+# number * 2;
+# end
+#
+# In this example the transformation assigns the 'number' attribute of the source element
+# multiplied by 2 to the target element. The multiplication is done in a dedicated method
+# called 'doubleNumber'. Note that the 'number' attribute of the source element is
+# accessed without an explicit reference to the source element as the method's body
+# evaluates in the source element's context.
+#
+# =Conditional Transformations
+#
+# Using the transformations as described above, all elements of the same class are
+# transformed the same way. Conditional transformations allow to transform elements of
+# the same class into elements of different target classes as well as applying different
+# transformations on the attributes.
+#
+# Conditional transformations are defined by specifying multiple transformer blocks for
+# the same source class and providing a condition with each block. Since it is important
+# to create the target object before evaluation of the transformation block (see above),
+# the conditions must also be evaluated separately _before_ the transformer block.
+#
+# Conditions are specified using transformer methods as described above. If the return
+# value is true, the corresponding block is used for transformation. If more than one
+# conditions are true, only the first transformer block will be evaluated.
+#
+# If there is no rule with a condition evaluating to true, rules for superclasses will
+# be checked as described above.
+#
+# Here is an example:
+#
+# transform ModelIn, :to => ModelOut, :if => :largeNumber do
+# { :number => number * 2}
+# end
+#
+# transform ModelIn, :to => ModelOut, :if => :smallNumber do
+# { :number => number / 2 }
+# end
+#
+# method :largeNumber do
+# number > 1000
+# end
+#
+# method :smallNumber do
+# number < 500
+# end
+#
+# In this case the transformation of an element of class ModelIn depends on the value
+# of the element's 'number' attribute. If the value is greater than 1000, the first rule
+# as taken and the number is doubled. If the value is smaller than 500, the second rule
+# is taken and the number is divided by two.
+#
+# Note that it is up to the user to avoid cycles within the conditions. A cycle could
+# occure if the condition are based on transformation target elements, i.e. if +trans+
+# is used within the condition to lookup or transform other elements.
+#
+# = Copy Transformations
+#
+# In some cases, transformations should just copy a model, either in the same metamodel
+# or in another metamodel with the same package/class structure. Sometimes, a transformation
+# is not exactly a copy, but a copy with slight modifications. Also in this case most
+# classes need to be copied verbatim.
+#
+# The class method Transformer.copy can be used to specify a copy rule for a single
+# metamodel class. If no target class is specified using the :to named parameter, the
+# target class will be the same as the source class (i.e. in the same metamodel).
+#
+# copy MM1::ClassA # copy within the same metamodel
+# copy MM1::ClassA, :to => MM2::ClassA
+#
+# The class method Transfomer.copy_all can be used to specify copy rules for all classes
+# of a particular metamodel package. Again with :to, the target metamodel package may
+# be specified which must have the same package/class structure. If :to is omitted, the
+# target metamodel is the same as the source metamodel. In case that for some classes
+# specific transformation rules should be used instead of copy rules, exceptions may be
+# specified using the :except named parameter. +copy_all+ also provides an easy way to
+# copy (clone) a model within the same metamodel.
+#
+# copy_all MM1 # copy rules for the whole metamodel MM1,
+# # used to clone models of MM1
+#
+# copy_all MM1, :to => MM2, :except => %w( # copy rules for all classes of MM1 to
+# ClassA # equally named classes in MM2, except
+# Sub1::ClassB # "ClassA" and "Sub1::ClassB"
+# )
+#
+# If a specific class transformation is not an exact copy, the Transformer.transform method
+# should be used to actually specify the transformation. If this transformation is also
+# mostly a copy, the helper method Transformer#copy_features can be used to create the
+# transformation Hash required by the transform method. Any changes to this hash may be done
+# in a hash returned by a block given to +copy_features+. This hash will extend or overwrite
+# the default copy hash. In case a particular feature should not be part of the copy hash
+# (e.g. because it does not exist in the target metamodel), exceptions can be specified using
+# the :except named parameter. Here is an example:
+#
+# transform ClassA, :to => ClassAx do
+# copy_features :except => [:featA] do
+# { :featB => featA }
+# end
+# end
+#
+# In this example, ClassAx is a copy of ClassA except, that feature "featA" in ClassA is renamed
+# into "featB" in ClassAx. Using +copy_features+ all features are copied except "featA". Then
+# "featB" of the target class is assigned the value of "featA" of the source class.
+#
+class Transformer
+
+ TransformationDescription = Struct.new(:block, :target) # :nodoc:
+
+ @@methods = {}
+ @@transformer_blocks = {}
+
+ def self._transformer_blocks # :nodoc:
+ @@transformer_blocks[self] ||= {}
+ end
+
+ def self._methods # :nodoc:
+ @@methods[self] ||= {}
+ end
+
+ # This class method is used to specify a transformation rule.
+ #
+ # The first argument specifies the class of elements for which this rule applies.
+ # The second argument must be a hash including the target class
+ # (as value of key ':to') and an optional condition (as value of key ':if').
+ #
+ # The target class is specified by passing the actual Ruby class object.
+ # The condition is either the name of a transformer method (see Transfomer.method) as
+ # a symbol or a proc object. In either case the block is evaluated at transformation
+ # time and its result value determines if the rule applies.
+ #
+ def self.transform(from, desc=nil, &block)
+ to = (desc && desc.is_a?(Hash) && desc[:to])
+ condition = (desc && desc.is_a?(Hash) && desc[:if])
+ raise StandardError.new("No transformation target specified.") unless to
+ block_desc = TransformationDescription.new(block, to)
+ if condition
+ _transformer_blocks[from] ||= {}
+ raise StandardError.new("Multiple (non-conditional) transformations for class #{from.name}.") unless _transformer_blocks[from].is_a?(Hash)
+ _transformer_blocks[from][condition] = block_desc
+ else
+ raise StandardError.new("Multiple (non-conditional) transformations for class #{from.name}.") unless _transformer_blocks[from].nil?
+ _transformer_blocks[from] = block_desc
+ end
+ end
+
+ # This class method specifies that all objects of class +from+ are to be copied
+ # into an object of class +to+. If +to+ is omitted, +from+ is used as target class.
+ # The target class may also be specified using the :to => <class> hash notation.
+ # During copy, all attributes and references of the target object
+ # are set to their transformed counterparts of the source object.
+ #
+ def self.copy(from, to=nil)
+ raise StandardError.new("Specify target class either directly as second parameter or using :to => <class>") \
+ unless to.nil? || to.is_a?(Class) || (to.is_a?(Hash) && to[:to].is_a?(Class))
+ to = to[:to] if to.is_a?(Hash)
+ transform(from, :to => to || from) do
+ copy_features
+ end
+ end
+
+ # Create copy rules for all classes of metamodel package (module) +from+ and its subpackages.
+ # The target classes are the classes with the same name in the metamodel package
+ # specified using named parameter :to. If no target metamodel is specified, source
+ # and target classes will be the same.
+ # The named parameter :except can be used to specify classes by qualified name for which
+ # no copy rules should be created. Qualified names are relative to the metamodel package
+ # specified.
+ #
+ def self.copy_all(from, hash={})
+ to = hash[:to] || from
+ except = hash[:except]
+ fromDepth = from.ecore.qualifiedName.split("::").size
+ from.ecore.eAllClasses.each do |c|
+ path = c.qualifiedName.split("::")[fromDepth..-1]
+ next if except && except.include?(path.join("::"))
+ copy c.instanceClass, :to => path.inject(to){|m,c| m.const_get(c)}
+ end
+ end
+
+ # Define a transformer method for the current transformer class.
+ # In contrast to regular Ruby methods, a method defined this way executes in the
+ # context of the object currently being transformed.
+ #
+ def self.method(name, &block)
+ _methods[name.to_s] = block
+ end
+
+
+ # Creates a new transformer
+ # Optionally an input and output Environment can be specified.
+ # If an elementMap is provided (normally a Hash) this map will be used to lookup
+ # and store transformation results. This way results can be predefined
+ # and it is possible to have several transformers work on the same result map.
+ #
+ def initialize(env_in=nil, env_out=nil, elementMap=nil)
+ @env_in = env_in
+ @env_out = env_out
+ @transformer_results = elementMap || {}
+ @transformer_jobs = []
+ end
+
+
+ # Transforms a given model element according to the rules specified by means of
+ # the Transformer.transform class method.
+ #
+ # The transformation result element is created in the output environment and returned.
+ # In addition, the result is cached, i.e. a second invocation with the same parameter
+ # object will return the same result object without any further evaluation of the
+ # transformation rules. Nil will be transformed into nil. Ruby "singleton" objects
+ # +true+, +false+, numerics and symbols will be returned without any change. Ruby strings
+ # will be duplicated with the result being cached.
+ #
+ # The transformation input can be given as:
+ # * a single object
+ # * an array each element of which is transformed in turn
+ # * a hash used as input to Environment#find with the result being transformed
+ #
+ def trans(obj)
+ if obj.is_a?(Hash)
+ raise StandardError.new("No input environment available to find model element.") unless @env_in
+ obj = @env_in.find(obj)
+ end
+ return nil if obj.nil?
+ return obj if obj.is_a?(TrueClass) or obj.is_a?(FalseClass) or obj.is_a?(Numeric) or obj.is_a?(Symbol)
+ return @transformer_results[obj] if @transformer_results[obj]
+ return @transformer_results[obj] = obj.dup if obj.is_a?(String)
+ return obj.collect{|o| trans(o)}.compact if obj.is_a? Array
+ raise StandardError.new("No transformer for class #{obj.class.name}") unless _transformerBlock(obj.class)
+ block_desc = _evaluateCondition(obj)
+ return nil unless block_desc
+ @transformer_results[obj] = _instantiateTargetClass(obj, block_desc.target)
+ # we will transform the properties later
+ @transformer_jobs << TransformerJob.new(self, obj, block_desc)
+ # if there have been jobs in the queue before, don't process them in this call
+ # this way calls to trans are not nested; a recursive implementation
+ # may cause a "Stack level too deep" exception for large models
+ return @transformer_results[obj] if @transformer_jobs.size > 1
+ # otherwise this is the first call of trans, process all jobs here
+ # more jobs will be added during job execution
+ while @transformer_jobs.size > 0
+ @transformer_jobs.first.execute
+ @transformer_jobs.shift
+ end
+ @transformer_results[obj]
+ end
+
+ # Create the hash required as return value of the block given to the Transformer.transform method.
+ # The hash will assign feature values of the source class to the features of the target class,
+ # assuming the features of both classes are the same. If the :except named parameter specifies
+ # an Array of symbols, the listed features are not copied by the hash. In order to easily manipulate
+ # the resulting hash, a block may be given which should also return a feature assignmet hash. This
+ # hash should be created manually and will extend/overwrite the automatically created hash.
+ #
+ def copy_features(options={})
+ hash = {}
+ @current_object.class.ecore.eAllStructuralFeatures.each do |f|
+ next if f.derived
+ next if options[:except] && options[:except].include?(f.name.to_sym)
+ hash[f.name.to_sym] = trans(@current_object.send(f.name))
+ end
+ hash.merge!(yield) if block_given?
+ hash
+ end
+
+ def _transformProperties(obj, block_desc) #:nodoc:
+ old_object, @current_object = @current_object, obj
+ block_result = instance_eval(&block_desc.block)
+ raise StandardError.new("Transformer must return a hash") unless block_result.is_a? Hash
+ @current_object = old_object
+ _attributesFromHash(@transformer_results[obj], block_result)
+ end
+
+ class TransformerJob #:nodoc:
+ def initialize(transformer, obj, block_desc)
+ @transformer, @obj, @block_desc = transformer, obj, block_desc
+ end
+ def execute
+ @transformer._transformProperties(@obj, @block_desc)
+ end
+ end
+
+ # Each call which is not handled by the transformer object is passed to the object
+ # currently being transformed.
+ # If that object also does not respond to the call, it is treated as a transformer
+ # method call (see Transformer.method).
+ #
+ def method_missing(m, *args) #:nodoc:
+ if @current_object.respond_to?(m)
+ @current_object.send(m, *args)
+ else
+ _invokeMethod(m, *args)
+ end
+ end
+
+ private
+
+ # returns _transformer_blocks content for clazz or one of its superclasses
+ def _transformerBlock(clazz) # :nodoc:
+ block = self.class._transformer_blocks[clazz]
+ block = _transformerBlock(clazz.superclass) if block.nil? && clazz != Object
+ block
+ end
+
+ # returns the first TransformationDescription for clazz or one of its superclasses
+ # for which condition is true
+ def _evaluateCondition(obj, clazz=obj.class) # :nodoc:
+ tb = self.class._transformer_blocks[clazz]
+ block_description = nil
+ if tb.is_a?(TransformationDescription)
+ # non-conditional
+ block_description = tb
+ elsif tb
+ old_object, @current_object = @current_object, obj
+ tb.each_pair {|condition, block|
+ if condition.is_a?(Proc)
+ result = instance_eval(&condition)
+ elsif condition.is_a?(Symbol)
+ result = _invokeMethod(condition)
+ else
+ result = condition
+ end
+ if result
+ block_description = block
+ break
+ end
+ }
+ @current_object = old_object
+ end
+ block_description = _evaluateCondition(obj, clazz.superclass) if block_description.nil? && clazz != Object
+ block_description
+ end
+
+ def _instantiateTargetClass(obj, target_desc) # :nodoc:
+ old_object, @current_object = @current_object, obj
+ if target_desc.is_a?(Proc)
+ target_class = instance_eval(&target_desc)
+ elsif target_desc.is_a?(Symbol)
+ target_class = _invokeMethod(target_desc)
+ else
+ target_class = target_desc
+ end
+ @current_object = old_object
+ result = target_class.new
+ @env_out << result if @env_out
+ result
+ end
+
+ def _invokeMethod(m) # :nodoc:
+ raise StandardError.new("Method not found: #{m}") unless self.class._methods[m.to_s]
+ instance_eval(&self.class._methods[m.to_s])
+ end
+
+ def _attributesFromHash(obj, hash) # :nodoc:
+ hash.delete(:class)
+ hash.each_pair{|k,v|
+ obj.send("#{k}=", v)
+ }
+ obj
+ end
+
+end
+
+end \ No newline at end of file
diff --git a/lib/puppet/vendor/rgen/lib/rgen/util/auto_class_creator.rb b/lib/puppet/vendor/rgen/lib/rgen/util/auto_class_creator.rb
new file mode 100644
index 000000000..1bdaa44f0
--- /dev/null
+++ b/lib/puppet/vendor/rgen/lib/rgen/util/auto_class_creator.rb
@@ -0,0 +1,61 @@
+# RGen Framework
+# (c) Martin Thiede, 2006
+
+require 'rgen/metamodel_builder'
+
+module RGen
+
+module Util
+
+class Base
+ extend MetamodelBuilder
+ def initialize(env=nil)
+ env << self if env
+ end
+end
+
+class AutoCreatedClass < Base
+ def method_missing(m,*args)
+ return super unless self.class.parent.accEnabled
+ if m.to_s =~ /(.*)=$/
+ self.class.has_one($1)
+ send(m,args[0])
+ elsif args.size == 0
+ self.class.has_many(m)
+ send(m)
+ end
+ end
+end
+
+# will be "extended" to the auto created class
+module ParentAccess
+ def parent=(p)
+ @parent = p
+ end
+ def parent
+ @parent
+ end
+end
+
+module AutoClassCreator
+ attr_reader :accEnabled
+ def const_missing(className)
+ return super unless @accEnabled
+ module_eval("class "+className.to_s+" < RGen::AutoCreatedClass; end")
+ c = const_get(className)
+ c.extend(ParentAccess)
+ c.parent = self
+ c
+ end
+ def enableACC
+ @accEnabled = true
+ end
+ def disableACC
+ @accEnabled = false
+ end
+end
+
+end
+
+end
+
diff --git a/lib/puppet/vendor/rgen/lib/rgen/util/cached_glob.rb b/lib/puppet/vendor/rgen/lib/rgen/util/cached_glob.rb
new file mode 100644
index 000000000..c5718c8b6
--- /dev/null
+++ b/lib/puppet/vendor/rgen/lib/rgen/util/cached_glob.rb
@@ -0,0 +1,67 @@
+module RGen
+
+module Util
+
+# WARNING: the mechanism of taking timestamps of directories in order to find out if the
+# content has changed doesn't work reliably across all kinds of filesystems
+#
+class CachedGlob
+
+ def initialize(dir_glob, file_glob)
+ @dir_glob = dir_glob
+ @file_glob = file_glob
+ @root_dirs = []
+ @dirs = {}
+ @files = {}
+ @timestamps = {}
+ end
+
+ # returns all files contained in directories matched by +dir_glob+ which match +file_glob+.
+ # +file_glob+ must be relative to +dir_glob+.
+ # dir_glob "*/a" with file_glob "**/*.txt" is basically equivalent with Dir.glob("*/a/**/*.txt")
+ # the idea is that the file glob will only be re-eavluated when the content of one of the
+ # directories matched by dir_glob has changed.
+ # this will only be faster than a normal Dir.glob if the number of dirs matched by dir_glob is
+ # relatively large and changes in files affect only a few of them at a time.
+ def glob
+ root_dirs = Dir.glob(@dir_glob)
+ (@root_dirs - root_dirs).each do |d|
+ remove_root_dir(d)
+ end
+ (@root_dirs & root_dirs).each do |d|
+ update_root_dir(d) if dir_changed?(d)
+ end
+ (root_dirs - @root_dirs).each do |d|
+ update_root_dir(d)
+ end
+ @root_dirs = root_dirs
+ @root_dirs.sort.collect{|d| @files[d]}.flatten
+ end
+
+ private
+
+ def dir_changed?(dir)
+ @dirs[dir].any?{|d| File.mtime(d) != @timestamps[dir][d]}
+ end
+
+ def update_root_dir(dir)
+ @dirs[dir] = Dir.glob(dir+"/**/")
+ @files[dir] = Dir.glob(dir+"/"+@file_glob)
+ @timestamps[dir] = {}
+ @dirs[dir].each do |d|
+ @timestamps[dir][d] = File.mtime(d)
+ end
+ end
+
+ def remove_root_dir(dir)
+ @dirs.delete(dir)
+ @files.delete(dir)
+ @timestamps.delete(dir)
+ end
+
+end
+
+end
+
+end
+
diff --git a/lib/puppet/vendor/rgen/lib/rgen/util/file_cache_map.rb b/lib/puppet/vendor/rgen/lib/rgen/util/file_cache_map.rb
new file mode 100644
index 000000000..a8464d43b
--- /dev/null
+++ b/lib/puppet/vendor/rgen/lib/rgen/util/file_cache_map.rb
@@ -0,0 +1,124 @@
+require 'digest'
+require 'fileutils'
+
+module RGen
+
+module Util
+
+# Implements a cache for storing and loading data associated with arbitrary files.
+# The data is stored in cache files within a subfolder of the folder where
+# the associated files exists.
+# The cache files are protected with a checksum and loaded data will be
+# invalid in case either the associated file are the cache file has changed.
+#
+class FileCacheMap
+ # optional program version info to be associated with the cache files
+ # if the program version changes, cached data will also be invalid
+ attr_accessor :version_info
+
+ # +cache_dir+ is the name of the subfolder where cache files are created
+ # +postfix+ is an extension appended to the original file name for
+ # creating the name of the cache file
+ def initialize(cache_dir, postfix)
+ @postfix = postfix
+ @cache_dir = cache_dir
+ end
+
+ # load data associated with file +key_path+
+ # returns :invalid in case either the associated file or the cache file has changed
+ #
+ # options:
+ # :invalidation_reasons:
+ # an array which will receive symbols indicating why the cache is invalid:
+ # :no_cachefile, :cachefile_corrupted, :keyfile_changed
+ #
+ def load_data(key_path, options={})
+ reasons = options[:invalidation_reasons] || []
+ cf = cache_file(key_path)
+ result = nil
+ begin
+ File.open(cf, "rb") do |f|
+ header = f.read(41)
+ if !header
+ reasons << :cachefile_corrupted
+ return :invalid
+ end
+ checksum = header[0..39]
+ data = f.read
+ if calc_sha1(data) == checksum
+ if calc_sha1_keydata(key_path) == data[0..39]
+ result = data[41..-1]
+ else
+ reasons << :keyfile_changed
+ result = :invalid
+ end
+ else
+ reasons << :cachefile_corrupted
+ result = :invalid
+ end
+ end
+ rescue Errno::ENOENT
+ reasons << :no_cachefile
+ result = :invalid
+ end
+ result
+ end
+
+ # store data +value_data+ associated with file +key_path+
+ def store_data(key_path, value_data)
+ data = calc_sha1_keydata(key_path) + "\n" + value_data
+ data = calc_sha1(data) + "\n" + data
+ cf = cache_file(key_path)
+ FileUtils.mkdir(File.dirname(cf)) rescue Errno::EEXIST
+ File.open(cf, "wb") do |f|
+ f.write(data)
+ end
+ end
+
+ # remove cache files which are not associated with any file in +key_paths+
+ # will only remove files within +root_path+
+ def clean_unused(root_path, key_paths)
+ raise "key paths must be within root path" unless key_paths.all?{|p| p.index(root_path) == 0}
+ used_files = key_paths.collect{|p| cache_file(p)}
+ files = Dir[root_path+"/**/"+@cache_dir+"/*"+@postfix]
+ files.each do |f|
+ FileUtils.rm(f) unless used_files.include?(f)
+ end
+ end
+
+private
+
+ def cache_file(path)
+ File.dirname(path) + "/"+@cache_dir+"/" + File.basename(path) + @postfix
+ end
+
+ def calc_sha1(data)
+ sha1 = Digest::SHA1.new
+ sha1.update(data)
+ sha1.hexdigest
+ end
+
+ def keyData(path)
+ File.read(path)+@version_info.to_s
+ end
+
+ # this method is much faster than calling +keyData+ and putting the result in +calc_sha1+
+ # reason is probably that there are not so many big strings being created
+ def calc_sha1_keydata(path)
+ begin
+ sha1 = Digest::SHA1.new
+ sha1.file(path)
+ sha1.update(@version_info.to_s)
+ sha1.hexdigest
+ rescue Errno::ENOENT
+ "<missing_key_file>"
+ end
+ end
+
+end
+
+end
+
+end
+
+
diff --git a/lib/puppet/vendor/rgen/lib/rgen/util/file_change_detector.rb b/lib/puppet/vendor/rgen/lib/rgen/util/file_change_detector.rb
new file mode 100644
index 000000000..061802efc
--- /dev/null
+++ b/lib/puppet/vendor/rgen/lib/rgen/util/file_change_detector.rb
@@ -0,0 +1,84 @@
+require 'digest'
+
+module RGen
+
+module Util
+
+# The FileChangeDetector detects changes in a set of files.
+# Changes are detected between successive calls to check_files with a give set of files.
+# Changes include files being added, removed or having changed their content.
+#
+class FileChangeDetector
+
+ FileInfo = Struct.new(:timestamp, :digest)
+
+ # Create a FileChangeDetector, options include:
+ #
+ # :file_added
+ # a proc which is called when a file is added, receives the filename
+ #
+ # :file_removed
+ # a proc which is called when a file is removed, receives the filename
+ #
+ # :file_changed
+ # a proc which is called when a file is changed, receives the filename
+ #
+ def initialize(options={})
+ @file_added = options[:file_added]
+ @file_removed = options[:file_removed]
+ @file_changed = options[:file_changed]
+ @file_info = {}
+ end
+
+ # Checks if any of the files has changed compared to the last call of check_files.
+ # When called for the first time on a new object, all files will be reported as being added.
+ #
+ def check_files(files)
+ files_before = @file_info.keys
+ used_files = {}
+ files.each do |file|
+ begin
+ if @file_info[file]
+ if @file_info[file].timestamp != File.mtime(file)
+ @file_info[file].timestamp = File.mtime(file)
+ digest = calc_digest(file)
+ if @file_info[file].digest != digest
+ @file_info[file].digest = digest
+ @file_changed && @file_changed.call(file)
+ end
+ end
+ else
+ @file_info[file] = FileInfo.new
+ @file_info[file].timestamp = File.mtime(file)
+ @file_info[file].digest = calc_digest(file)
+ @file_added && @file_added.call(file)
+ end
+ used_files[file] = true
+ # protect against missing files
+ rescue Errno::ENOENT
+ # used_files is not set and @file_info will be removed below
+ # notification hook hasn't been called yet since it comes after file accesses
+ end
+ end
+ files_before.each do |file|
+ if !used_files[file]
+ @file_info.delete(file)
+ @file_removed && @file_removed.call(file)
+ end
+ end
+ end
+
+ private
+
+ def calc_digest(file)
+ sha1 = Digest::SHA1.new
+ sha1.file(file)
+ sha1.hexdigest
+ end
+
+end
+
+end
+
+end
+
diff --git a/lib/puppet/vendor/rgen/lib/rgen/util/method_delegation.rb b/lib/puppet/vendor/rgen/lib/rgen/util/method_delegation.rb
new file mode 100644
index 000000000..7d540e944
--- /dev/null
+++ b/lib/puppet/vendor/rgen/lib/rgen/util/method_delegation.rb
@@ -0,0 +1,114 @@
+module RGen
+
+module Util
+
+module MethodDelegation
+
+class << self
+
+ def registerDelegate(delegate, object, method)
+ method = method.to_sym
+ createDelegateStore(object)
+ if object._methodDelegates[method]
+ object._methodDelegates[method] << delegate
+ else
+ object._methodDelegates[method] = [delegate]
+ createDelegatingMethod(object, method)
+ end
+ end
+
+ def unregisterDelegate(delegate, object, method)
+ method = method.to_sym
+ return unless object.respond_to?(:_methodDelegates)
+ return unless object._methodDelegates[method]
+ object._methodDelegates[method].delete(delegate)
+ if object._methodDelegates[method].empty?
+ object._methodDelegates[method] = nil
+ removeDelegatingMethod(object, method)
+ removeDelegateStore(object)
+ end
+ end
+
+ private
+
+ def createDelegateStore(object)
+ return if object.respond_to?(:_methodDelegates)
+ class << object
+ def _methodDelegates
+ @_methodDelegates ||= {}
+ end
+ end
+ end
+
+ def removeDelegateStore(object)
+ return unless object.respond_to?(:_methodDelegates)
+ class << object
+ remove_method(:_methodDelegates)
+ end
+ end
+
+ def createDelegatingMethod(object, method)
+ if hasMethod(object, method)
+ object.instance_eval <<-END
+ class << self
+ alias #{aliasMethodName(method)} #{method}
+ end
+ END
+ end
+
+ # define the delegating method
+ object.instance_eval <<-END
+ class << self
+ def #{method}(*args, &block)
+ @_methodDelegates[:#{method}].each do |d|
+ catch(:continue) do
+ return d.#{method}_delegated(self, *args, &block)
+ end
+ end
+ # if aliased method does not exist, we want an exception
+ #{aliasMethodName(method)}(*args, &block)
+ end
+ end
+ END
+ end
+
+ def removeDelegatingMethod(object, method)
+ if hasMethod(object, aliasMethodName(method))
+ # there is an aliased original, restore it
+ object.instance_eval <<-END
+ class << self
+ alias #{method} #{aliasMethodName(method)}
+ remove_method(:#{aliasMethodName(method)})
+ end
+ END
+ else
+ # just delete the delegating method
+ object.instance_eval <<-END
+ class << self
+ remove_method(:#{method})
+ end
+ END
+ end
+ end
+
+ def hasMethod(object, method)
+ # in Ruby 1.9, #methods returns symbols
+ if object.methods.first.is_a?(Symbol)
+ method = method.to_sym
+ else
+ method = method.to_s
+ end
+ object.methods.include?(method)
+ end
+
+ def aliasMethodName(method)
+ "#{method}_delegate_original"
+ end
+end
+
+end
+
+end
+
+end
+
diff --git a/lib/puppet/vendor/rgen/lib/rgen/util/model_comparator.rb b/lib/puppet/vendor/rgen/lib/rgen/util/model_comparator.rb
new file mode 100644
index 000000000..f17ebc722
--- /dev/null
+++ b/lib/puppet/vendor/rgen/lib/rgen/util/model_comparator.rb
@@ -0,0 +1,68 @@
+require 'rgen/ecore/ecore'
+
+module RGen
+
+module Util
+
+module ModelComparator
+
+# This method compares to models regarding equality
+# For this the identity of a model element is defined based on identity
+# of all attributes and referenced elements.
+# Arrays are sorted before comparison if possible (if <=> is provided).
+#
+def modelEqual?(a, b, featureIgnoreList=[])
+ @modelEqual_visited = {}
+ _modelEqual_internal(a, b, featureIgnoreList, [])
+end
+
+def _modelEqual_internal(a, b, featureIgnoreList, path)
+ return true if @modelEqual_visited[[a,b]]
+ @modelEqual_visited[[a,b]] = true
+ return true if a.nil? && b.nil?
+ unless a.class == b.class
+ puts "#{path.inspect}\n Classes differ: #{a} vs. #{b}"
+ return false
+ end
+ if a.is_a?(Array)
+ unless a.size == b.size
+ puts "#{path.inspect}\n Array size differs: #{a.size} vs. #{b.size}"
+ return false
+ end
+ begin
+ # in Ruby 1.9 every object has the <=> operator but the default one returns
+ # nil and thus sorting won't work (ArgumentError)
+ as = a.sort
+ rescue ArgumentError, NoMethodError
+ as = a
+ end
+ begin
+ bs = b.sort
+ rescue ArgumentError, NoMethodError
+ bs = b
+ end
+ a.each_index do |i|
+ return false unless _modelEqual_internal(as[i], bs[i], featureIgnoreList, path+[i])
+ end
+ else
+ a.class.ecore.eAllStructuralFeatures.reject{|f| f.derived}.each do |feat|
+ next if featureIgnoreList.include?(feat.name)
+ if feat.eType.is_a?(RGen::ECore::EDataType)
+ unless a.getGeneric(feat.name) == b.getGeneric(feat.name)
+ puts "#{path.inspect}\n Value '#{feat.name}' differs: #{a.getGeneric(feat.name)} vs. #{b.getGeneric(feat.name)}"
+ return false
+ end
+ else
+ return false unless _modelEqual_internal(a.getGeneric(feat.name), b.getGeneric(feat.name), featureIgnoreList, path+["#{a.respond_to?(:name) && a.name}:#{feat.name}"])
+ end
+ end
+ end
+ return true
+end
+
+end
+
+end
+
+end
+
diff --git a/lib/puppet/vendor/rgen/lib/rgen/util/model_comparator_base.rb b/lib/puppet/vendor/rgen/lib/rgen/util/model_comparator_base.rb
new file mode 100644
index 000000000..bf8137f8c
--- /dev/null
+++ b/lib/puppet/vendor/rgen/lib/rgen/util/model_comparator_base.rb
@@ -0,0 +1,142 @@
+require 'andand'
+
+module RGen
+
+module Util
+
+class ModelComparatorBase
+
+ CompareSpec = Struct.new(:identifier, :recurse, :filter, :ignore_features, :display_name, :sort)
+ INDENT = " "
+
+ class << self
+ attr_reader :compareSpecs
+
+ def compare_spec(clazz, hash)
+ @compareSpecs ||= {}
+ raise "Compare spec already defined for #{clazz}" if @compareSpecs[clazz]
+ spec = CompareSpec.new
+ hash.each_pair do |k,v|
+ spec.send("#{k}=",v)
+ end
+ @compareSpecs[clazz] = spec
+ end
+ end
+
+ # compares two sets of elements
+ def compare(as, bs, recursive=true)
+ result = []
+ aById = as.select{|e| useElement?(e)}.inject({}){|r, e| r[elementIdentifier(e)] = e; r}
+ bById = bs.select{|e| useElement?(e)}.inject({}){|r, e| r[elementIdentifier(e)] = e; r}
+ onlyA = sortElements((aById.keys - bById.keys).collect{|id| aById[id]})
+ onlyB = sortElements((bById.keys - aById.keys).collect{|id| bById[id]})
+ aAndB = sortElementPairs((aById.keys & bById.keys).collect{|id| [aById[id], bById[id]]})
+ onlyA.each do |e|
+ result << "- #{elementDisplayName(e)}"
+ end
+ onlyB.each do |e|
+ result << "+ #{elementDisplayName(e)}"
+ end
+ if recursive
+ aAndB.each do |ab|
+ a, b = *ab
+ r = compareElements(a, b)
+ if r.size > 0
+ result << "#{elementDisplayName(a)}"
+ result += r.collect{|l| INDENT+l}
+ end
+ end
+ end
+ result
+ end
+
+ def sortElementPairs(pairs)
+ pairs.sort do |x,y|
+ a, b = x[0], y[0]
+ r = a.class.name <=> b.class.name
+ r = compareSpec(a).sort.call(a,b) if r == 0 && compareSpec(a) && compareSpec(a).sort
+ r
+ end
+ end
+
+ def sortElements(elements)
+ elements.sort do |a,b|
+ r = a.class.name <=> b.class.name
+ r = compareSpec(a).sort.call(a,b) if r == 0 && compareSpec(a) && compareSpec(a).sort
+ r
+ end
+ end
+
+ def elementDisplayName(e)
+ if compareSpec(e) && compareSpec(e).display_name
+ compareSpec(e).display_name.call(e)
+ else
+ elementIdentifier(e)
+ end
+ end
+
+ def compareElements(a, b)
+ result = []
+ if a.class != b.class
+ result << "Class: #{a.class} -> #{b.class}"
+ else
+ a.class.ecore.eAllStructuralFeatures.reject{|f| f.derived || compareSpec(a).andand.ignore_features.andand.include?(f.name.to_sym)}.each do |f|
+ va, vb = a.getGeneric(f.name), b.getGeneric(f.name)
+ if f.is_a?(RGen::ECore::EAttribute)
+ r = compareValues(f.name, va, vb)
+ result << r if r
+ else
+ va, vb = [va].compact, [vb].compact unless f.many
+ r = compare(va, vb, f.containment || compareSpec(a).andand.recurse.andand.include?(f.name.to_sym))
+ if r.size > 0
+ result << "[#{f.name}]"
+ result += r.collect{|l| INDENT+l}
+ end
+ end
+ end
+ end
+ result
+ end
+
+ def compareValues(name, val1, val2)
+ result = nil
+ result = "[#{name}] #{val1} -> #{val2}" if val1 != val2
+ result
+ end
+
+ def elementIdentifier(element)
+ cs = compareSpec(element)
+ if cs && cs.identifier
+ if cs.identifier.is_a?(Proc)
+ cs.identifier.call(element)
+ else
+ cs.identifier
+ end
+ else
+ if element.respond_to?(:name)
+ element.name
+ else
+ element.object_id
+ end
+ end
+ end
+
+ def useElement?(element)
+ cs = compareSpec(element)
+ !(cs && cs.filter) || cs.filter.call(element)
+ end
+
+ def compareSpec(element)
+ @compareSpec ||= {}
+ return @compareSpec[element.class] if @compareSpec[element.class]
+ return nil unless self.class.compareSpecs
+ key = self.class.compareSpecs.keys.find{|k| element.is_a?(k)}
+ @compareSpec[element.class] = self.class.compareSpecs[key]
+ end
+
+end
+
+end
+
+end
+
diff --git a/lib/puppet/vendor/rgen/lib/rgen/util/model_dumper.rb b/lib/puppet/vendor/rgen/lib/rgen/util/model_dumper.rb
new file mode 100644
index 000000000..20fc2d886
--- /dev/null
+++ b/lib/puppet/vendor/rgen/lib/rgen/util/model_dumper.rb
@@ -0,0 +1,29 @@
+module RGen
+
+module Util
+
+module ModelDumper
+
+ def dump(obj=nil)
+ obj ||= self
+ if obj.is_a?(Array)
+ obj.collect {|o| dump(o)}.join("\n\n")
+ elsif obj.class.respond_to?(:ecore)
+ ([obj.to_s] +
+ obj.class.ecore.eAllStructuralFeatures.select{|f| !f.many}.collect { |a|
+ " #{a} => #{obj.getGeneric(a.name)}"
+ } +
+ obj.class.ecore.eAllStructuralFeatures.select{|f| f.many}.collect { |a|
+ " #{a} => [ #{obj.getGeneric(a.name).join(', ')} ]"
+ }).join("\n")
+ else
+ obj.to_s
+ end
+ end
+
+end
+
+end
+
+end
+
diff --git a/lib/puppet/vendor/rgen/lib/rgen/util/name_helper.rb b/lib/puppet/vendor/rgen/lib/rgen/util/name_helper.rb
new file mode 100644
index 000000000..2f827de0c
--- /dev/null
+++ b/lib/puppet/vendor/rgen/lib/rgen/util/name_helper.rb
@@ -0,0 +1,42 @@
+# RGen Framework
+# (c) Martin Thiede, 2006
+
+module RGen
+
+module Util
+
+module NameHelper
+
+ def normalize(name)
+ name.gsub(/\W/,'_')
+ end
+
+ def className(object)
+ object.class.name =~ /::(\w+)$/; $1
+ end
+
+ def firstToUpper(str)
+ str[0..0].upcase + ( str[1..-1] || "" )
+ end
+
+ def firstToLower(str)
+ str[0..0].downcase + ( str[1..-1] || "" )
+ end
+
+ def saneClassName(str)
+ firstToUpper(normalize(str)).sub(/^Class$/, 'Clazz')
+ end
+
+ def saneMethodName(str)
+ firstToLower(normalize(str)).sub(/^class$/, 'clazz')
+ end
+
+ def camelize(str)
+ str.split(/[\W_]/).collect{|s| firstToUpper(s.downcase)}.join
+ end
+end
+
+end
+
+end
+
diff --git a/lib/puppet/vendor/rgen/lib/rgen/util/pattern_matcher.rb b/lib/puppet/vendor/rgen/lib/rgen/util/pattern_matcher.rb
new file mode 100644
index 000000000..0429cd154
--- /dev/null
+++ b/lib/puppet/vendor/rgen/lib/rgen/util/pattern_matcher.rb
@@ -0,0 +1,329 @@
+module RGen
+
+module Util
+
+# A PatternMatcher can be used to find, insert and remove patterns on a given model.
+#
+# A pattern is specified by means of a block passed to the add_pattern method.
+# The block must take an Environment as first parameter and at least one model element
+# as connection point as further parameter. The pattern matches if it can be found
+# in a given environment and connected to the given connection point elements.
+#
+class PatternMatcher
+
+ Match = Struct.new(:root, :elements, :bound_values)
+ attr_accessor :debug
+
+ def initialize
+ @patterns = {}
+ @insert_mode = false
+ @debug = false
+ end
+
+ def add_pattern(name, &block)
+ raise "a pattern needs at least 2 block parameters: " +
+ "an RGen environment and a model element as connection point" \
+ unless block.arity >= 2
+ @patterns[name] = block
+ end
+
+ def find_pattern(env, name, *connection_points)
+ match = find_pattern_internal(env, name, *connection_points)
+ end
+
+ def insert_pattern(env, name, *connection_points)
+ @insert_mode = true
+ root = evaluate_pattern(name, env, connection_points)
+ @insert_mode = false
+ root
+ end
+
+ def remove_pattern(env, name, *connection_points)
+ match = find_pattern_internal(env, name, *connection_points)
+ if match
+ match.elements.each do |e|
+ disconnect_element(e)
+ env.delete(e)
+ end
+ match
+ else
+ nil
+ end
+ end
+
+ def lazy(&block)
+ if @insert_mode
+ block.call
+ else
+ Lazy.new(&block)
+ end
+ end
+
+ class Lazy < RGen::MetamodelBuilder::MMGeneric
+ def initialize(&block)
+ @block = block
+ end
+ def _eval
+ @block.call
+ end
+ end
+
+ private
+
+ class Proxy < RGen::MetamodelBuilder::MMProxy
+ attr_reader :_target
+ def initialize(target)
+ @_target = target
+ end
+ def method_missing(m, *args)
+ result = @_target.send(m, *args)
+ if result.is_a?(Array)
+ result.collect do |e|
+ if e.is_a?(RGen::MetamodelBuilder::MMBase)
+ Proxy.new(e)
+ else
+ e
+ end
+ end
+ else
+ if result.is_a?(RGen::MetamodelBuilder::MMBase)
+ Proxy.new(result)
+ else
+ result
+ end
+ end
+ end
+ end
+
+ class Bindable < RGen::MetamodelBuilder::MMGeneric
+ # by being an Enumerable, Bindables can be used for many-features as well
+ include Enumerable
+ def initialize
+ @bound = false
+ @value = nil
+ @many = false
+ end
+ def _bound?
+ @bound
+ end
+ def _many?
+ @many
+ end
+ def _bind(value)
+ @value = value
+ @bound = true
+ end
+ def _value
+ @value
+ end
+ def to_s
+ @value.to_s
+ end
+ # pretend this is an enumerable which contains itself, so the bindable can be
+ # inserted into many-features, when this is done the bindable is marked as a many-bindable
+ def each
+ @many = true
+ yield(self)
+ end
+ def method_missing(m, *args)
+ raise "bindable not bound" unless _bound?
+ @value.send(m, *args)
+ end
+ end
+
+ TempEnv = RGen::Environment.new
+ class << TempEnv
+ def <<(el); end
+ end
+
+ def find_pattern_internal(env, name, *connection_points)
+ proxied_args = connection_points.collect{|a|
+ a.is_a?(RGen::MetamodelBuilder::MMBase) ? Proxy.new(a) : a }
+ bindables = create_bindables(name, connection_points)
+ pattern_root = evaluate_pattern(name, TempEnv, proxied_args+bindables)
+ candidates = candidates_via_connection_points(pattern_root, connection_points)
+ candidates ||= env.find(:class => pattern_root.class)
+ candidates.each do |e|
+ # create new bindables for every try, otherwise they can could be bound to old values
+ bindables = create_bindables(name, connection_points)
+ pattern_root = evaluate_pattern(name, TempEnv, proxied_args+bindables)
+ matched = match(pattern_root, e)
+ return Match.new(e, matched, bindables.collect{|b| b._value}) if matched
+ end
+ nil
+ end
+
+ def create_bindables(pattern_name, connection_points)
+ (1..(num_pattern_variables(pattern_name) - connection_points.size)).collect{|i| Bindable.new}
+ end
+
+ def candidates_via_connection_points(pattern_root, connection_points)
+ @candidates_via_connection_points_refs ||= {}
+ refs = (@candidates_via_connection_points_refs[pattern_root.class] ||=
+ pattern_root.class.ecore.eAllReferences.reject{|r| r.derived || r.many || !r.eOpposite})
+ candidates = nil
+ refs.each do |r|
+ t = pattern_root.getGeneric(r.name)
+ cp = t.is_a?(Proxy) && connection_points.find{|cp| cp.object_id == t._target.object_id}
+ if cp
+ elements = cp.getGenericAsArray(r.eOpposite.name)
+ candidates = elements if candidates.nil? || elements.size < candidates.size
+ end
+ end
+ candidates
+ end
+
+ def match(pat_element, test_element)
+ visited = {}
+ check_later = []
+ return false unless match_internal(pat_element, test_element, visited, check_later)
+ while cl = check_later.shift
+ pv, tv = cl.lazy._eval, cl.value
+ if cl.feature.is_a?(RGen::ECore::EAttribute)
+ unless pv == tv
+ match_failed(cl.feature, "wrong attribute value (lazy): #{pv} vs. #{tv}")
+ return false
+ end
+ else
+ if pv.is_a?(Proxy)
+ unless pv._target.object_id == tv.object_id
+ match_failed(f, "wrong target object")
+ return false
+ end
+ else
+ unless (pv.nil? && tv.nil?) || (!pv.nil? && !tv.nil? && match_internal(pv, tv, visited, check_later))
+ return false
+ end
+ end
+ end
+ end
+ visited.keys
+ end
+
+ CheckLater = Struct.new(:feature, :lazy, :value)
+ def match_internal(pat_element, test_element, visited, check_later)
+ return true if visited[test_element]
+ visited[test_element] = true
+ unless pat_element.class == test_element.class
+ match_failed(nil, "wrong class: #{pat_element.class} vs #{test_element.class}")
+ return false
+ end
+ all_structural_features(pat_element).each do |f|
+ pat_values = pat_element.getGeneric(f.name)
+ # nil values must be kept to support size check with Bindables
+ pat_values = [ pat_values ] unless pat_values.is_a?(Array)
+ test_values = test_element.getGeneric(f.name)
+ test_values = [ test_values] unless test_values.is_a?(Array)
+ if pat_values.size == 1 && pat_values.first.is_a?(Bindable) && pat_values.first._many?
+ unless match_many_bindable(pat_values.first, test_values)
+ return false
+ end
+ else
+ unless pat_values.size == test_values.size
+ match_failed(f, "wrong size #{pat_values.size} vs. #{test_values.size}")
+ return false
+ end
+ pat_values.each_with_index do |pv,i|
+ tv = test_values[i]
+ if pv.is_a?(Lazy)
+ check_later << CheckLater.new(f, pv, tv)
+ elsif pv.is_a?(Bindable)
+ if pv._bound?
+ unless pv._value == tv
+ match_failed(f, "value does not match bound value #{pv._value.class}:#{pv._value.object_id} vs. #{tv.class}:#{tv.object_id}")
+ return false
+ end
+ else
+ pv._bind(tv)
+ end
+ else
+ if f.is_a?(RGen::ECore::EAttribute)
+ unless pv == tv
+ match_failed(f, "wrong attribute value")
+ return false
+ end
+ else
+ if pv.is_a?(Proxy)
+ unless pv._target.object_id == tv.object_id
+ match_failed(f, "wrong target object")
+ return false
+ end
+ else
+ unless both_nil_or_match(pv, tv, visited, check_later)
+ return false
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ true
+ end
+
+ def match_many_bindable(bindable, test_values)
+ if bindable._bound?
+ bindable._value.each_with_index do |pv,i|
+ tv = test_values[i]
+ if f.is_a?(RGen::ECore::EAttribute)
+ unless pv == tv
+ match_failed(f, "wrong attribute value")
+ return false
+ end
+ else
+ unless both_nil_or_match(pv, tv, visited, check_later)
+ return false
+ end
+ end
+ end
+ else
+ bindable._bind(test_values.dup)
+ end
+ true
+ end
+
+ def both_nil_or_match(pv, tv, visited, check_later)
+ (pv.nil? && tv.nil?) || (!pv.nil? && !tv.nil? && match_internal(pv, tv, visited, check_later))
+ end
+
+ def match_failed(f, msg)
+ puts "match failed #{f&&f.eContainingClass.name}##{f&&f.name}: #{msg}" if @debug
+ end
+
+ def num_pattern_variables(name)
+ prok = @patterns[name]
+ prok.arity - 1
+ end
+
+ def evaluate_pattern(name, env, connection_points)
+ prok = @patterns[name]
+ raise "unknown pattern #{name}" unless prok
+ raise "wrong number of arguments, expected #{prok.arity-1} connection points)" \
+ unless connection_points.size == prok.arity-1
+ prok.call(env, *connection_points)
+ end
+
+ def disconnect_element(element)
+ return if element.nil?
+ all_structural_features(element).each do |f|
+ if f.many
+ element.setGeneric(f.name, [])
+ else
+ element.setGeneric(f.name, nil)
+ end
+ end
+ end
+
+ def all_structural_features(element)
+ @all_structural_features ||= {}
+ return @all_structural_features[element.class] if @all_structural_features[element.class]
+ @all_structural_features[element.class] =
+ element.class.ecore.eAllStructuralFeatures.reject{|f| f.derived}
+ end
+
+end
+
+end
+
+end
+
diff --git a/lib/puppet/vendor/rgen/lib/transformers/ecore_to_uml13.rb b/lib/puppet/vendor/rgen/lib/transformers/ecore_to_uml13.rb
new file mode 100644
index 000000000..cbd832a35
--- /dev/null
+++ b/lib/puppet/vendor/rgen/lib/transformers/ecore_to_uml13.rb
@@ -0,0 +1,79 @@
+require 'rgen/transformer'
+require 'rgen/ecore/ecore'
+require 'metamodels/uml13_metamodel'
+
+class ECoreToUML13 < RGen::Transformer
+ include RGen::ECore
+
+ def transform
+ trans(:class => EPackage)
+ trans(:class => EClass)
+ trans(:class => EEnum)
+ end
+
+ transform EPackage, :to => UML13::Package do
+ {:name => name,
+ :namespace => trans(eSuperPackage) || model,
+ :ownedElement => trans(eClassifiers.select{|c| c.is_a?(EClass)} + eSubpackages)
+ }
+ end
+
+ transform EClass, :to => UML13::Class do
+ {:name => name,
+ :namespace => trans(ePackage),
+ :feature => trans(eStructuralFeatures.select{|f| f.is_a?(EAttribute)} + eOperations),
+ :associationEnd => trans(eStructuralFeatures.select{|f| f.is_a?(EReference)}),
+ :generalization => eSuperTypes.collect { |st|
+ @env_out.new(UML13::Generalization, :parent => trans(st), :namespace => trans(ePackage) || model)
+ }
+ }
+ end
+
+ transform EEnum, :to => UML13::Class do
+ {:name => name,
+ :namespace => trans(ePackage),
+ :feature => trans(eLiterals)
+ }
+ end
+
+ transform EEnumLiteral, :to => UML13::Attribute do
+ {:name => name }
+ end
+
+ transform EAttribute, :to => UML13::Attribute do
+ _typemap = {"String" => "string", "Boolean" => "boolean", "Integer" => "int", "Float" => "float"}
+ {:name => name,
+ :taggedValue => [@env_out.new(UML13::TaggedValue, :tag => "type",
+ :value => _typemap[eType.instanceClassName] || eType.name)]
+ }
+ end
+
+ transform EReference, :to => UML13::AssociationEnd do
+ _otherAssocEnd = eOpposite ? trans(eOpposite) :
+ @env_out.new(UML13::AssociationEnd,
+ :type => trans(eType), :name => name, :multiplicity => createMultiplicity(@current_object),
+ :aggregation => :none, :isNavigable => true)
+ { :association => trans(@current_object).association || @env_out.new(UML13::Association,
+ :connection => [_otherAssocEnd], :namespace => trans(eContainingClass.ePackage) || model),
+ :name => eOpposite && eOpposite.name,
+ :multiplicity => eOpposite && createMultiplicity(eOpposite),
+ :aggregation => containment ? :composite : :none,
+ :isNavigable => !eOpposite.nil?
+ }
+ end
+
+ transform EOperation, :to => UML13::Operation do
+ {:name => name}
+ end
+
+ def createMultiplicity(ref)
+ @env_out.new(UML13::Multiplicity, :range => [
+ @env_out.new(UML13::MultiplicityRange,
+ :lower => ref.lowerBound.to_s.sub("-1","*"), :upper => ref.upperBound.to_s.sub("-1","*"))])
+ end
+
+ def model
+ @model ||= @env_out.new(UML13::Model, :name => "Model")
+ end
+
+end
diff --git a/lib/puppet/vendor/rgen/lib/transformers/uml13_to_ecore.rb b/lib/puppet/vendor/rgen/lib/transformers/uml13_to_ecore.rb
new file mode 100644
index 000000000..1bc57ba62
--- /dev/null
+++ b/lib/puppet/vendor/rgen/lib/transformers/uml13_to_ecore.rb
@@ -0,0 +1,127 @@
+require 'metamodels/uml13_metamodel'
+require 'rgen/transformer'
+require 'rgen/ecore/ecore'
+require 'rgen/array_extensions'
+
+class UML13ToECore < RGen::Transformer
+ include RGen::ECore
+
+ # Options:
+ #
+ # :reference_filter:
+ # a proc which receives an AssociationEnd or a Dependency and should return
+ # true or false, depending on if a referece should be created for it or not
+ #
+ def initialize(*args)
+ options = {}
+ if args.last.is_a?(Hash)
+ options = args.pop
+ end
+ @reference_filter = options[:reference_filter] || proc do |e|
+ if e.is_a?(UML13::AssociationEnd)
+ otherEnd = e.association.connection.find{|ae| ae != e}
+ otherEnd.name && otherEnd.name.size > 0
+ else
+ false
+ end
+ end
+ super(*args)
+ end
+
+ def transform
+ trans(:class => UML13::Class)
+ end
+
+ transform UML13::Model, :to => EPackage do
+ trans(ownedClassOrPackage)
+ { :name => name && name.strip }
+ end
+
+ transform UML13::Package, :to => EPackage do
+ trans(ownedClassOrPackage)
+ { :name => name && name.strip,
+ :eSuperPackage => trans(namespace.is_a?(UML13::Package) ? namespace : nil) }
+ end
+
+ method :ownedClassOrPackage do
+ ownedElement.select{|e| e.is_a?(UML13::Package) || e.is_a?(UML13::Class)}
+ end
+
+ transform UML13::Class, :to => EClass do
+ { :name => name && name.strip,
+ :abstract => isAbstract,
+ :ePackage => trans(namespace.is_a?(UML13::Package) ? namespace : nil),
+ :eStructuralFeatures => trans(feature.select{|f| f.is_a?(UML13::Attribute)} +
+ associationEnd + clientDependency),
+ :eOperations => trans(feature.select{|f| f.is_a?(UML13::Operation)}),
+ :eSuperTypes => trans(generalization.parent + clientDependency.select{|d| d.stereotype && d.stereotype.name == "implements"}.supplier),
+ :eAnnotations => createAnnotations(taggedValue) }
+ end
+
+ transform UML13::Interface, :to => EClass do
+ { :name => name && name.strip,
+ :abstract => isAbstract,
+ :ePackage => trans(namespace.is_a?(UML13::Package) ? namespace : nil),
+ :eStructuralFeatures => trans(feature.select{|f| f.is_a?(UML13::Attribute)} + associationEnd),
+ :eOperations => trans(feature.select{|f| f.is_a?(UML13::Operation)}),
+ :eSuperTypes => trans(generalization.parent)}
+ end
+
+ transform UML13::Attribute, :to => EAttribute do
+ { :name => name && name.strip, :eType => trans(getType),
+ :lowerBound => (multiplicity && multiplicity.range.first.lower &&
+ multiplicity.range.first.lower.to_i) || 0,
+ :upperBound => (multiplicity && multiplicity.range.first.upper &&
+ multiplicity.range.first.upper.gsub('*','-1').to_i) || 1,
+ :eAnnotations => createAnnotations(taggedValue) }
+ end
+
+ transform UML13::DataType, :to => EDataType do
+ { :name => name && name.strip,
+ :ePackage => trans(namespace.is_a?(UML13::Package) ? namespace : nil),
+ :eAnnotations => createAnnotations(taggedValue) }
+ end
+
+ transform UML13::Operation, :to => EOperation do
+ { :name => name && name.strip }
+ end
+
+ transform UML13::AssociationEnd, :to => EReference, :if => :isReference do
+ otherEnd = association.connection.find{|ae| ae != @current_object}
+ { :eType => trans(otherEnd.type),
+ :name => otherEnd.name && otherEnd.name.strip,
+ :eOpposite => trans(otherEnd),
+ :lowerBound => (otherEnd.multiplicity && otherEnd.multiplicity.range.first.lower &&
+ otherEnd.multiplicity.range.first.lower.to_i) || 0,
+ :upperBound => (otherEnd.multiplicity && otherEnd.multiplicity.range.first.upper &&
+ otherEnd.multiplicity.range.first.upper.gsub('*','-1').to_i) || 1,
+ :containment => (aggregation == :composite),
+ :eAnnotations => createAnnotations(association.taggedValue) }
+ end
+
+ transform UML13::Dependency, :to => EReference, :if => :isReference do
+ { :eType => trans(supplier.first),
+ :name => name,
+ :lowerBound => 0,
+ :upperBound => 1,
+ :containment => false,
+ :eAnnotations => createAnnotations(taggedValue)
+ }
+ end
+
+ method :isReference do
+ @reference_filter.call(@current_object)
+ end
+
+ def createAnnotations(taggedValues)
+ if taggedValues.size > 0
+ [ EAnnotation.new(:details => trans(taggedValues)) ]
+ else
+ []
+ end
+ end
+
+ transform UML13::TaggedValue, :to => EStringToStringMapEntry do
+ { :key => tag, :value => value}
+ end
+end
diff --git a/lib/puppet/vendor/rgen/test/array_extensions_test.rb b/lib/puppet/vendor/rgen/test/array_extensions_test.rb
new file mode 100644
index 000000000..e6caa9037
--- /dev/null
+++ b/lib/puppet/vendor/rgen/test/array_extensions_test.rb
@@ -0,0 +1,64 @@
+$:.unshift File.join(File.dirname(__FILE__),"..","lib")
+
+require 'test/unit'
+require 'rgen/array_extensions'
+
+class ArrayExtensionsTest < Test::Unit::TestCase
+
+ def test_element_methods
+ c = Struct.new("SomeClass",:name,:age)
+ a = []
+ a << c.new('MyName',33)
+ a << c.new('YourName',22)
+ assert_equal ["MyName", "YourName"], a >> :name
+ assert_raise NoMethodError do
+ a.name
+ end
+ assert_equal [33, 22], a>>:age
+ assert_raise NoMethodError do
+ a.age
+ end
+ # unfortunately, any method can be called on an empty array
+ assert_equal [], [].age
+ end
+
+ class MMBaseClass < RGen::MetamodelBuilder::MMBase
+ has_attr 'name'
+ has_attr 'age', Integer
+ end
+
+ def test_with_mmbase
+ e1 = MMBaseClass.new
+ e1.name = "MyName"
+ e1.age = 33
+ e2 = MMBaseClass.new
+ e2.name = "YourName"
+ e2.age = 22
+ a = [e1, e2]
+ assert_equal ["MyName", "YourName"], a >> :name
+ assert_equal ["MyName", "YourName"], a.name
+ assert_equal [33, 22], a>>:age
+ assert_equal [33, 22], a.age
+ # put something into the array that is not an MMBase
+ a << "not a MMBase"
+ # the dot operator will tell that there is something not a MMBase
+ assert_raise StandardError do
+ a.age
+ end
+ # the >> operator will try to call the method anyway
+ assert_raise NoMethodError do
+ a >> :age
+ end
+ end
+
+ def test_hash_square
+ assert_equal({}, Hash[[]])
+ end
+
+ def test_to_str_on_empty_array
+ assert_raise NoMethodError do
+ [].to_str
+ end
+ end
+
+end
diff --git a/lib/puppet/vendor/rgen/test/ea_instantiator_test.rb b/lib/puppet/vendor/rgen/test/ea_instantiator_test.rb
new file mode 100644
index 000000000..bafae8af2
--- /dev/null
+++ b/lib/puppet/vendor/rgen/test/ea_instantiator_test.rb
@@ -0,0 +1,35 @@
+$:.unshift File.join(File.dirname(__FILE__),"..","lib")
+
+require 'test/unit'
+require 'rgen/environment'
+require 'metamodels/uml13_metamodel'
+require 'ea_support/ea_support'
+require 'transformers/uml13_to_ecore'
+require 'testmodel/class_model_checker'
+require 'testmodel/object_model_checker'
+require 'testmodel/ecore_model_checker'
+
+class EAInstantiatorTest < Test::Unit::TestCase
+
+ include Testmodel::ClassModelChecker
+ include Testmodel::ObjectModelChecker
+ include Testmodel::ECoreModelChecker
+
+ MODEL_DIR = File.join(File.dirname(__FILE__),"testmodel")
+
+ def test_instantiator
+ envUML = RGen::Environment.new
+ EASupport.instantiateUML13FromXMI11(envUML, MODEL_DIR+"/ea_testmodel.xml")
+ checkClassModel(envUML)
+ checkObjectModel(envUML)
+ envECore = RGen::Environment.new
+ UML13ToECore.new(envUML, envECore).transform
+ checkECoreModel(envECore)
+ end
+
+ def test_partial
+ envUML = RGen::Environment.new
+ EASupport.instantiateUML13FromXMI11(envUML, MODEL_DIR+"/ea_testmodel_partial.xml")
+ checkClassModelPartial(envUML)
+ end
+end \ No newline at end of file
diff --git a/lib/puppet/vendor/rgen/test/ea_serializer_test.rb b/lib/puppet/vendor/rgen/test/ea_serializer_test.rb
new file mode 100644
index 000000000..c5ecc2755
--- /dev/null
+++ b/lib/puppet/vendor/rgen/test/ea_serializer_test.rb
@@ -0,0 +1,23 @@
+$:.unshift File.join(File.dirname(__FILE__),"..","lib")
+
+require 'test/unit'
+require 'rgen/environment'
+require 'metamodels/uml13_metamodel'
+require 'ea_support/ea_support'
+require 'rgen/serializer/xmi11_serializer'
+
+class EASerializerTest < Test::Unit::TestCase
+
+ MODEL_DIR = File.join(File.dirname(__FILE__),"testmodel")
+ TEST_DIR = File.join(File.dirname(__FILE__),"ea_serializer_test")
+
+ def test_serializer
+ envUML = RGen::Environment.new
+ EASupport.instantiateUML13FromXMI11(envUML, MODEL_DIR+"/ea_testmodel.xml")
+ models = envUML.find(:class => UML13::Model)
+ assert_equal 1, models.size
+
+ EASupport.serializeUML13ToXMI11(envUML, MODEL_DIR+"/ea_testmodel_regenerated.xml")
+ end
+
+end \ No newline at end of file
diff --git a/lib/puppet/vendor/rgen/test/ecore_self_test.rb b/lib/puppet/vendor/rgen/test/ecore_self_test.rb
new file mode 100644
index 000000000..ae523faa8
--- /dev/null
+++ b/lib/puppet/vendor/rgen/test/ecore_self_test.rb
@@ -0,0 +1,54 @@
+$:.unshift File.join(File.dirname(__FILE__),"..","lib")
+
+require 'test/unit'
+require 'rgen/ecore/ecore'
+require 'rgen/array_extensions'
+
+class ECoreSelfTest < Test::Unit::TestCase
+ include RGen::ECore
+
+ def test_simple
+ assert_equal \
+ %w(lowerBound ordered unique upperBound many required eType).sort,
+ ETypedElement.ecore.eStructuralFeatures.name.sort
+
+ assert_equal \
+ EClassifier.ecore,
+ ETypedElement.ecore.eStructuralFeatures.find{|f| f.name=="eType"}.eType
+ assert_equal %w(ENamedElement), ETypedElement.ecore.eSuperTypes.name
+
+ assert_equal \
+ EModelElement.ecore,
+ EModelElement.ecore.eStructuralFeatures.find{|f| f.name=="eAnnotations"}.eOpposite.eType
+
+ assert_equal \
+ %w(eType),
+ ETypedElement.ecore.eReferences.name
+
+ assert_equal \
+ %w(lowerBound ordered unique upperBound many required).sort,
+ ETypedElement.ecore.eAttributes.name.sort
+
+ assert RGen::ECore.ecore.is_a?(EPackage)
+ assert_equal "ECore", RGen::ECore.ecore.name
+ assert_equal "RGen", RGen::ECore.ecore.eSuperPackage.name
+ assert_equal %w(ECore), RGen.ecore.eSubpackages.name
+ assert_equal\
+ %w(EObject EModelElement EAnnotation ENamedElement ETypedElement
+ EStructuralFeature EAttribute EClassifier EDataType EEnum EEnumLiteral EFactory
+ EOperation EPackage EParameter EReference EStringToStringMapEntry EClass
+ ETypeArgument EGenericType).sort,
+ RGen::ECore.ecore.eClassifiers.name.sort
+
+ assert_equal "false", EAttribute.ecore.eAllAttributes.
+ find{|a|a.name == "derived"}.defaultValueLiteral
+ assert_equal false, EAttribute.ecore.eAllAttributes.
+ find{|a|a.name == "derived"}.defaultValue
+
+ assert_nil EAttribute.ecore.eAllAttributes.
+ find{|a|a.name == "defaultValueLiteral"}.defaultValueLiteral
+ assert_nil EAttribute.ecore.eAllAttributes.
+ find{|a|a.name == "defaultValueLiteral"}.defaultValue
+
+ end
+end
diff --git a/lib/puppet/vendor/rgen/test/environment_test.rb b/lib/puppet/vendor/rgen/test/environment_test.rb
new file mode 100644
index 000000000..aefd480e5
--- /dev/null
+++ b/lib/puppet/vendor/rgen/test/environment_test.rb
@@ -0,0 +1,90 @@
+$:.unshift File.join(File.dirname(__FILE__),"..","lib")
+
+require 'test/unit'
+require 'rgen/environment'
+require 'rgen/metamodel_builder'
+
+class EnvironmentTest < Test::Unit::TestCase
+
+ class Model
+ attr_accessor :name
+ end
+
+ class ModelSub < Model
+ end
+
+ class ClassSuperA < RGen::MetamodelBuilder::MMBase
+ end
+
+ class ClassSuperB < RGen::MetamodelBuilder::MMBase
+ end
+
+ class ClassC < RGen::MetamodelBuilder::MMMultiple(ClassSuperA, ClassSuperB)
+ has_attr 'name', String
+ end
+
+ class ClassSubD < ClassC
+ end
+
+ class ClassSubE < ClassC
+ end
+
+ def test_find_mmbase
+ env = RGen::Environment.new
+ mA1 = env.new(ClassSuperA)
+ mB1 = env.new(ClassSuperB)
+ mD1 = env.new(ClassSubD, :name => "mD1")
+ mD2 = env.new(ClassSubD, :name => "mD2")
+ mE = env.new(ClassSubE, :name => "mE")
+
+ resultA = env.find(:class => ClassSuperA)
+ assert_equal sortById([mA1,mD1,mD2,mE]), sortById(resultA)
+ resultNamedA = env.find(:class => ClassSuperA, :name => "mD1")
+ assert_equal sortById([mD1]), sortById(resultNamedA)
+
+ resultB = env.find(:class => ClassSuperB)
+ assert_equal sortById([mB1,mD1,mD2,mE]), sortById(resultB)
+ resultNamedB = env.find(:class => ClassSuperB, :name => "mD1")
+ assert_equal sortById([mD1]), sortById(resultNamedB)
+
+ resultC = env.find(:class => ClassC)
+ assert_equal sortById([mD1,mD2,mE]), sortById(resultC)
+
+ resultD = env.find(:class => ClassSubD)
+ assert_equal sortById([mD1,mD2]), sortById(resultD)
+ end
+
+ def test_find
+ m1 = Model.new
+ m1.name = "M1"
+ m2 = ModelSub.new
+ m2.name = "M2"
+ m3 = "justAString"
+ env = RGen::Environment.new << m1 << m2 << m3
+
+ result = env.find(:class => Model, :name => "M1")
+ assert result.is_a?(Array)
+ assert_equal 1, result.size
+ assert_equal m1, result.first
+
+ result = env.find(:class => Model)
+ assert result.is_a?(Array)
+ assert_equal sortById([m1,m2]), sortById(result)
+
+ result = env.find(:name => "M2")
+ assert result.is_a?(Array)
+ assert_equal 1, result.size
+ assert_equal m2, result.first
+
+ result = env.find(:class => [Model, String])
+ assert result.is_a?(Array)
+ assert_equal sortById([m1,m2,m3]), sortById(result)
+ end
+
+ private
+
+ def sortById(array)
+ array.sort{|a,b| a.object_id <=> b.object_id}
+ end
+
+end
diff --git a/lib/puppet/vendor/rgen/test/json_test.rb b/lib/puppet/vendor/rgen/test/json_test.rb
new file mode 100644
index 000000000..0c73b9723
--- /dev/null
+++ b/lib/puppet/vendor/rgen/test/json_test.rb
@@ -0,0 +1,171 @@
+$:.unshift File.join(File.dirname(__FILE__),"..","lib")
+
+require 'test/unit'
+require 'rgen/environment'
+require 'rgen/metamodel_builder'
+require 'rgen/serializer/json_serializer'
+require 'rgen/instantiator/json_instantiator'
+
+class JsonTest < Test::Unit::TestCase
+
+ module TestMM
+ extend RGen::MetamodelBuilder::ModuleExtension
+ class TestNode < RGen::MetamodelBuilder::MMBase
+ has_attr 'text', String
+ has_attr 'integer', Integer
+ has_attr 'float', Float
+ has_one 'other', TestNode
+ contains_many 'childs', TestNode, 'parent'
+ end
+ end
+
+ module TestMMData
+ extend RGen::MetamodelBuilder::ModuleExtension
+ # class "Data" exists in the standard Ruby namespace
+ class Data < RGen::MetamodelBuilder::MMBase
+ has_attr 'notTheBuiltin', String
+ end
+ end
+
+ module TestMMSubpackage
+ extend RGen::MetamodelBuilder::ModuleExtension
+ module SubPackage
+ extend RGen::MetamodelBuilder::ModuleExtension
+ class Data < RGen::MetamodelBuilder::MMBase
+ has_attr 'notTheBuiltin', String
+ end
+ class Data2 < RGen::MetamodelBuilder::MMBase
+ has_attr 'data2', String
+ end
+ end
+ end
+
+ class StringWriter < String
+ alias write concat
+ end
+
+ def test_json_serializer
+ testModel = TestMM::TestNode.new(:text => "some text", :childs => [
+ TestMM::TestNode.new(:text => "child")])
+
+ output = StringWriter.new
+ ser = RGen::Serializer::JsonSerializer.new(output)
+
+ assert_equal %q({ "_class": "TestNode", "text": "some text", "childs": [
+ { "_class": "TestNode", "text": "child" }] }), ser.serialize(testModel)
+ end
+
+ def test_json_instantiator
+ env = RGen::Environment.new
+ inst = RGen::Instantiator::JsonInstantiator.new(env, TestMM)
+ inst.instantiate(%q({ "_class": "TestNode", "text": "some text", "childs": [
+ { "_class": "TestNode", "text": "child" }] }))
+ root = env.find(:class => TestMM::TestNode, :text => "some text").first
+ assert_not_nil root
+ assert_equal 1, root.childs.size
+ assert_equal TestMM::TestNode, root.childs.first.class
+ assert_equal "child", root.childs.first.text
+ end
+
+ def test_json_serializer_escapes
+ testModel = TestMM::TestNode.new(:text => %Q(some " \\ \\" text \r xx \n xx \r\n xx \t xx \b xx \f))
+ output = StringWriter.new
+ ser = RGen::Serializer::JsonSerializer.new(output)
+
+ assert_equal %q({ "_class": "TestNode", "text": "some \" \\\\ \\\\\" text \r xx \n xx \r\n xx \t xx \b xx \f" }),
+ ser.serialize(testModel)
+ end
+
+ def test_json_instantiator_escapes
+ env = RGen::Environment.new
+ inst = RGen::Instantiator::JsonInstantiator.new(env, TestMM)
+ inst.instantiate(%q({ "_class": "TestNode", "text": "some \" \\\\ \\\\\" text \r xx \n xx \r\n xx \t xx \b xx \f" }))
+ assert_equal %Q(some " \\ \\" text \r xx \n xx \r\n xx \t xx \b xx \f), env.elements.first.text
+ end
+
+ def test_json_instantiator_escape_single_backslash
+ env = RGen::Environment.new
+ inst = RGen::Instantiator::JsonInstantiator.new(env, TestMM)
+ inst.instantiate(%q({ "_class": "TestNode", "text": "a single \\ will be just itself" }))
+ assert_equal %q(a single \\ will be just itself), env.elements.first.text
+ end
+
+ def test_json_serializer_integer
+ testModel = TestMM::TestNode.new(:integer => 7)
+ output = StringWriter.new
+ ser = RGen::Serializer::JsonSerializer.new(output)
+ assert_equal %q({ "_class": "TestNode", "integer": 7 }), ser.serialize(testModel)
+ end
+
+ def test_json_instantiator_integer
+ env = RGen::Environment.new
+ inst = RGen::Instantiator::JsonInstantiator.new(env, TestMM)
+ inst.instantiate(%q({ "_class": "TestNode", "integer": 7 }))
+ assert_equal 7, env.elements.first.integer
+ end
+
+ def test_json_serializer_float
+ testModel = TestMM::TestNode.new(:float => 1.23)
+ output = StringWriter.new
+ ser = RGen::Serializer::JsonSerializer.new(output)
+ assert_equal %q({ "_class": "TestNode", "float": 1.23 }), ser.serialize(testModel)
+ end
+
+ def test_json_instantiator_float
+ env = RGen::Environment.new
+ inst = RGen::Instantiator::JsonInstantiator.new(env, TestMM)
+ inst.instantiate(%q({ "_class": "TestNode", "float": 1.23 }))
+ assert_equal 1.23, env.elements.first.float
+ end
+
+ def test_json_instantiator_conflict_builtin
+ env = RGen::Environment.new
+ inst = RGen::Instantiator::JsonInstantiator.new(env, TestMMData)
+ inst.instantiate(%q({ "_class": "Data", "notTheBuiltin": "for sure" }))
+ assert_equal "for sure", env.elements.first.notTheBuiltin
+ end
+
+ def test_json_serializer_subpacakge
+ testModel = TestMMSubpackage::SubPackage::Data2.new(:data2 => "xxx")
+ output = StringWriter.new
+ ser = RGen::Serializer::JsonSerializer.new(output)
+ assert_equal %q({ "_class": "Data2", "data2": "xxx" }), ser.serialize(testModel)
+ end
+
+ def test_json_instantiator_builtin_in_subpackage
+ env = RGen::Environment.new
+ inst = RGen::Instantiator::JsonInstantiator.new(env, TestMMSubpackage)
+ inst.instantiate(%q({ "_class": "Data", "notTheBuiltin": "for sure" }))
+ assert_equal "for sure", env.elements.first.notTheBuiltin
+ end
+
+ def test_json_instantiator_subpackage
+ env = RGen::Environment.new
+ inst = RGen::Instantiator::JsonInstantiator.new(env, TestMMSubpackage)
+ inst.instantiate(%q({ "_class": "Data2", "data2": "something" }))
+ assert_equal "something", env.elements.first.data2
+ end
+
+ def test_json_instantiator_subpackage_no_shortname_opt
+ env = RGen::Environment.new
+ inst = RGen::Instantiator::JsonInstantiator.new(env, TestMMSubpackage, :short_class_names => false)
+ assert_raise RuntimeError do
+ inst.instantiate(%q({ "_class": "Data2", "data2": "something" }))
+ end
+ end
+
+ def test_json_instantiator_references
+ env = RGen::Environment.new
+ inst = RGen::Instantiator::JsonInstantiator.new(env, TestMM, :nameAttribute => "text")
+ inst.instantiate(%q([
+ { "_class": "TestNode", "text": "A", "childs": [
+ { "_class": "TestNode", "text": "B" } ]},
+ { "_class": "TestNode", "text": "C", "other": "/A/B"}]
+ ))
+ nodeA = env.find(:class => TestMM::TestNode, :text => "A").first
+ nodeC = env.find(:class => TestMM::TestNode, :text => "C").first
+ assert_equal 1, nodeA.childs.size
+ assert_equal nodeA.childs[0], nodeC.other
+ end
+end
+
diff --git a/lib/puppet/vendor/rgen/test/metamodel_builder_test.rb b/lib/puppet/vendor/rgen/test/metamodel_builder_test.rb
new file mode 100644
index 000000000..4f0308b44
--- /dev/null
+++ b/lib/puppet/vendor/rgen/test/metamodel_builder_test.rb
@@ -0,0 +1,1482 @@
+$:.unshift File.join(File.dirname(__FILE__),"..","lib")
+
+require 'test/unit'
+require 'rgen/metamodel_builder'
+require 'rgen/array_extensions'
+require 'bigdecimal'
+
+class MetamodelBuilderTest < Test::Unit::TestCase
+
+ module TestMetamodel
+ extend RGen::MetamodelBuilder::ModuleExtension
+
+ class SimpleClass < RGen::MetamodelBuilder::MMBase
+ KindType = RGen::MetamodelBuilder::DataTypes::Enum.new([:simple, :extended])
+ has_attr 'name' # default is String
+ has_attr 'stringWithDefault', String, :defaultValueLiteral => "xtest"
+ has_attr 'integerWithDefault', Integer, :defaultValueLiteral => "123"
+ has_attr 'longWithDefault', Long, :defaultValueLiteral => "1234567890"
+ has_attr 'floatWithDefault', Float, :defaultValueLiteral => "0.123"
+ has_attr 'boolWithDefault', Boolean, :defaultValueLiteral => "true"
+ has_attr 'anything', Object
+ has_attr 'allowed', RGen::MetamodelBuilder::DataTypes::Boolean
+ has_attr 'kind', KindType
+ has_attr 'kindWithDefault', KindType, :defaultValueLiteral => "extended"
+ end
+
+ class ManyAttrClass < RGen::MetamodelBuilder::MMBase
+ has_many_attr 'literals', String
+ has_many_attr 'bools', Boolean
+ has_many_attr 'integers', Integer
+ has_many_attr 'enums', RGen::MetamodelBuilder::DataTypes::Enum.new([:a, :b, :c])
+ has_many_attr 'limitTest', Integer, :upperBound => 2
+ end
+
+ class ClassA < RGen::MetamodelBuilder::MMBase
+ # metamodel accessors must work independent of the ==() method
+ module ClassModule
+ def ==(o)
+ o.is_a?(ClassA)
+ end
+ end
+ end
+
+ class ClassB < RGen::MetamodelBuilder::MMBase
+ end
+
+ class ClassC < RGen::MetamodelBuilder::MMBase
+ end
+
+ class HasOneTestClass < RGen::MetamodelBuilder::MMBase
+ has_one 'classA', ClassA
+ has_one 'classB', ClassB
+ end
+
+ class HasManyTestClass < RGen::MetamodelBuilder::MMBase
+ has_many 'classA', ClassA
+ end
+
+ class OneClass < RGen::MetamodelBuilder::MMBase
+ end
+ class ManyClass < RGen::MetamodelBuilder::MMBase
+ end
+ OneClass.one_to_many 'manyClasses', ManyClass, 'oneClass', :upperBound => 5
+
+ class AClassMM < RGen::MetamodelBuilder::MMBase
+ end
+ class BClassMM < RGen::MetamodelBuilder::MMBase
+ end
+ AClassMM.many_to_many 'bClasses', BClassMM, 'aClasses'
+
+ module SomePackage
+ extend RGen::MetamodelBuilder::ModuleExtension
+
+ class ClassA < RGen::MetamodelBuilder::MMBase
+ end
+
+ module SubPackage
+ extend RGen::MetamodelBuilder::ModuleExtension
+
+ class ClassB < RGen::MetamodelBuilder::MMBase
+ end
+ end
+ end
+
+ class OneClass2 < RGen::MetamodelBuilder::MMBase
+ end
+ class ManyClass2 < RGen::MetamodelBuilder::MMBase
+ end
+ ManyClass2.many_to_one 'oneClass', OneClass2, 'manyClasses'
+
+ class AClassOO < RGen::MetamodelBuilder::MMBase
+ end
+ class BClassOO < RGen::MetamodelBuilder::MMBase
+ end
+ AClassOO.one_to_one 'bClass', BClassOO, 'aClass'
+
+ class SomeSuperClass < RGen::MetamodelBuilder::MMBase
+ has_attr "name"
+ has_many "classAs", ClassA
+ end
+
+ class SomeSubClass < SomeSuperClass
+ has_attr "subname"
+ has_many "classBs", ClassB
+ end
+
+ class OtherSubClass < SomeSuperClass
+ has_attr "othersubname"
+ has_many "classCs", ClassC
+ end
+
+ class SubSubClass < RGen::MetamodelBuilder::MMMultiple(SomeSubClass, OtherSubClass)
+ has_attr "subsubname"
+ end
+
+ module AnnotatedModule
+ extend RGen::MetamodelBuilder::ModuleExtension
+
+ annotation "moduletag" => "modulevalue"
+
+ class AnnotatedClass < RGen::MetamodelBuilder::MMBase
+ annotation "sometag" => "somevalue", "othertag" => "othervalue"
+ annotation :source => "rgen/test", :details => {"thirdtag" => "thirdvalue"}
+
+ has_attr "boolAttr", Boolean do
+ annotation "attrtag" => "attrval"
+ annotation :source => "rgen/test2", :details => {"attrtag2" => "attrvalue2", "attrtag3" => "attrvalue3"}
+ end
+
+ has_many "others", AnnotatedClass do
+ annotation "reftag" => "refval"
+ annotation :source => "rgen/test3", :details => {"reftag2" => "refvalue2", "reftag3" => "refvalue3"}
+ end
+
+ many_to_many "m2m", AnnotatedClass, "m2mback" do
+ annotation "m2mtag" => "m2mval"
+ opposite_annotation "opposite_m2mtag" => "opposite_m2mval"
+ end
+ end
+
+ end
+
+ class AbstractClass < RGen::MetamodelBuilder::MMBase
+ abstract
+ end
+
+ class ContainedClass < RGen::MetamodelBuilder::MMBase
+ end
+
+ class ContainerClass < RGen::MetamodelBuilder::MMBase
+ contains_one_uni 'oneChildUni', ContainedClass
+ contains_one_uni 'oneChildUni2', ContainedClass
+ contains_one 'oneChild', ContainedClass, 'parentOne'
+ contains_one 'oneChild2', ContainedClass, 'parentOne2'
+ contains_many_uni 'manyChildUni', ContainedClass
+ contains_many_uni 'manyChildUni2', ContainedClass
+ contains_many 'manyChild', ContainedClass, 'parentMany'
+ contains_many 'manyChild2', ContainedClass, 'parentMany2'
+ end
+
+ class NestedContainerClass < ContainedClass
+ contains_one_uni 'oneChildUni', ContainedClass
+ end
+
+ class OppositeRefAssocA < RGen::MetamodelBuilder::MMBase
+ end
+ class OppositeRefAssocB < RGen::MetamodelBuilder::MMBase
+ end
+ OppositeRefAssocA.one_to_one 'bClass', OppositeRefAssocB, 'aClass'
+
+ end
+
+ def mm
+ TestMetamodel
+ end
+
+ def test_has_attr
+ sc = mm::SimpleClass.new
+
+ assert_respond_to sc, :name
+ assert_respond_to sc, :name=
+ sc.name = "TestName"
+ assert_equal "TestName", sc.name
+ sc.name = nil
+ assert_equal nil, sc.name
+ err = assert_raise StandardError do
+ sc.name = 5
+ end
+ assert_match /In (\w+::)+SimpleClass : Can not use a Fixnum where a String is expected/, err.message
+ assert_equal "EString", mm::SimpleClass.ecore.eAttributes.find{|a| a.name=="name"}.eType.name
+
+ assert_equal "xtest", sc.stringWithDefault
+ assert_equal :extended, sc.kindWithDefault
+ assert_equal 123, sc.integerWithDefault
+ assert_equal 1234567890, sc.longWithDefault
+ assert_equal 0.123, sc.floatWithDefault
+ assert_equal true, sc.boolWithDefault
+
+ # setting nil should not make the default value appear on next read
+ sc.stringWithDefault = nil
+ assert_nil sc.stringWithDefault
+
+ sc.anything = :asymbol
+ assert_equal :asymbol, sc.anything
+ sc.anything = self # a class
+ assert_equal self, sc.anything
+
+ assert_respond_to sc, :allowed
+ assert_respond_to sc, :allowed=
+ sc.allowed = true
+ assert_equal true, sc.allowed
+ sc.allowed = false
+ assert_equal false, sc.allowed
+ sc.allowed = nil
+ assert_equal nil, sc.allowed
+ err = assert_raise StandardError do
+ sc.allowed = :someSymbol
+ end
+ assert_match /In (\w+::)+SimpleClass : Can not use a Symbol\(:someSymbol\) where a \[true,false\] is expected/, err.message
+ err = assert_raise StandardError do
+ sc.allowed = "a string"
+ end
+ assert_match /In (\w+::)+SimpleClass : Can not use a String where a \[true,false\] is expected/, err.message
+ assert_equal "EBoolean", mm::SimpleClass.ecore.eAttributes.find{|a| a.name=="allowed"}.eType.name
+
+ assert_respond_to sc, :kind
+ assert_respond_to sc, :kind=
+ sc.kind = :simple
+ assert_equal :simple, sc.kind
+ sc.kind = :extended
+ assert_equal :extended, sc.kind
+ sc.kind = nil
+ assert_equal nil, sc.kind
+ err = assert_raise StandardError do
+ sc.kind = :false
+ end
+ assert_match /In (\w+::)+SimpleClass : Can not use a Symbol\(:false\) where a \[:simple,:extended\] is expected/, err.message
+ err = assert_raise StandardError do
+ sc.kind = "a string"
+ end
+ assert_match /In (\w+::)+SimpleClass : Can not use a String where a \[:simple,:extended\] is expected/, err.message
+
+ enum = mm::SimpleClass.ecore.eAttributes.find{|a| a.name=="kind"}.eType
+ assert_equal ["extended", "simple"], enum.eLiterals.name.sort
+ end
+
+ def test_float
+ sc = mm::SimpleClass.new
+ sc.floatWithDefault = 7.89
+ assert_equal 7.89, sc.floatWithDefault
+ if BigDecimal.double_fig == 16
+ sc.floatWithDefault = 123456789012345678.0
+ # loss of precision
+ assert_equal "123456789012345680.0", sprintf("%.1f", sc.floatWithDefault)
+ end
+ sc.floatWithDefault = nil
+ sc.floatWithDefault = BigDecimal.new("123456789012345678.0")
+ assert sc.floatWithDefault.is_a?(BigDecimal)
+ assert_equal "123456789012345678.0", sc.floatWithDefault.to_s("F")
+
+ dump = Marshal.dump(sc)
+ sc2 = Marshal.load(dump)
+ assert sc2.floatWithDefault.is_a?(BigDecimal)
+ assert_equal "123456789012345678.0", sc2.floatWithDefault.to_s("F")
+ end
+
+ def test_long
+ sc = mm::SimpleClass.new
+ sc.longWithDefault = 5
+ assert_equal 5, sc.longWithDefault
+ sc.longWithDefault = 1234567890
+ assert_equal 1234567890, sc.longWithDefault
+ assert sc.longWithDefault.is_a?(Bignum)
+ assert sc.longWithDefault.is_a?(Integer)
+ err = assert_raise StandardError do
+ sc.longWithDefault = "a string"
+ end
+ assert_match /In (\w+::)+SimpleClass : Can not use a String where a Integer is expected/, err.message
+ end
+
+ def test_many_attr
+ o = mm::ManyAttrClass.new
+
+ assert_respond_to o, :literals
+ assert_respond_to o, :addLiterals
+ assert_respond_to o, :removeLiterals
+
+ err = assert_raise(StandardError) do
+ o.addLiterals(1)
+ end
+ assert_match /In (\w+::)+ManyAttrClass : Can not use a Fixnum where a String is expected/, err.message
+
+ assert_equal [], o.literals
+ o.addLiterals("a")
+ assert_equal ["a"], o.literals
+ o.addLiterals("b")
+ assert_equal ["a", "b"], o.literals
+ o.addLiterals("b")
+ assert_equal ["a", "b", "b"], o.literals
+ # attributes allow the same object several times
+ o.addLiterals(o.literals.first)
+ assert_equal ["a", "b", "b", "a"], o.literals
+ assert o.literals[0].object_id == o.literals[3].object_id
+ # removing works by object identity, so providing a new string won't delete an existing one
+ o.removeLiterals("a")
+ assert_equal ["a", "b", "b", "a"], o.literals
+ theA = o.literals.first
+ # each remove command removes only one element: remove first "a"
+ o.removeLiterals(theA)
+ assert_equal ["b", "b", "a"], o.literals
+ # remove second "a" (same object)
+ o.removeLiterals(theA)
+ assert_equal ["b", "b"], o.literals
+ o.removeLiterals(o.literals.first)
+ assert_equal ["b"], o.literals
+ o.removeLiterals(o.literals.first)
+ assert_equal [], o.literals
+
+ # setting multiple elements at a time
+ o.literals = ["a", "b", "c"]
+ assert_equal ["a", "b", "c"], o.literals
+ # can only take enumerables
+ err = assert_raise(StandardError) do
+ o.literals = 1
+ end
+ assert_match /In (\w+::)+ManyAttrClass : Can not use a Fixnum where a Enumerable is expected/, err.message
+
+ o.bools = [true, false, true, false]
+ assert_equal [true, false, true, false], o.bools
+
+ o.integers = [1, 2, 2, 3, 3]
+ assert_equal [1, 2, 2, 3, 3], o.integers
+
+ o.enums = [:a, :a, :b, :c, :c]
+ assert_equal [:a, :a, :b, :c, :c], o.enums
+
+ lit = mm::ManyAttrClass.ecore.eAttributes.find{|a| a.name == "literals"}
+ assert lit.is_a?(RGen::ECore::EAttribute)
+ assert lit.many
+
+ lim = mm::ManyAttrClass.ecore.eAttributes.find{|a| a.name == "limitTest"}
+ assert lit.many
+ assert_equal 2, lim.upperBound
+ end
+
+ def test_many_attr_insert
+ o = mm::ManyAttrClass.new
+ o.addLiterals("a")
+ o.addLiterals("b", 0)
+ o.addLiterals("c", 1)
+ assert_equal ["b", "c", "a"], o.literals
+ end
+
+ def test_has_one
+ sc = mm::HasOneTestClass.new
+ assert_respond_to sc, :classA
+ assert_respond_to sc, :classA=
+ ca = mm::ClassA.new
+ sc.classA = ca
+ assert_equal ca, sc.classA
+ sc.classA = nil
+ assert_equal nil, sc.classA
+
+ assert_respond_to sc, :classB
+ assert_respond_to sc, :classB=
+ cb = mm::ClassB.new
+ sc.classB = cb
+ assert_equal cb, sc.classB
+
+ err = assert_raise StandardError do
+ sc.classB = ca
+ end
+ assert_match /In (\w+::)+HasOneTestClass : Can not use a (\w+::)+ClassA where a (\w+::)+ClassB is expected/, err.message
+
+ assert_equal [], mm::ClassA.ecore.eReferences
+ assert_equal [], mm::ClassB.ecore.eReferences
+ assert_equal ["classA", "classB"].sort, mm::HasOneTestClass.ecore.eReferences.name.sort
+ assert_equal [], mm::HasOneTestClass.ecore.eReferences.select { |a| a.many == true }
+ assert_equal [], mm::HasOneTestClass.ecore.eAttributes
+ end
+
+ def test_has_many
+ o = mm::HasManyTestClass.new
+ ca1 = mm::ClassA.new
+ ca2 = mm::ClassA.new
+ ca3 = mm::ClassA.new
+ o.addClassA(ca1)
+ o.addClassA(ca2)
+ assert_equal [ca1, ca2], o.classA
+ # make sure we get a copy
+ o.classA.clear
+ assert_equal [ca1, ca2], o.classA
+ o.removeClassA(ca3)
+ assert_equal [ca1, ca2], o.classA
+ o.removeClassA(ca2)
+ assert_equal [ca1], o.classA
+ err = assert_raise StandardError do
+ o.addClassA(mm::ClassB.new)
+ end
+ assert_match /In (\w+::)+HasManyTestClass : Can not use a (\w+::)+ClassB where a (\w+::)+ClassA is expected/, err.message
+ assert_equal [], mm::HasManyTestClass.ecore.eReferences.select{|r| r.many == false}
+ assert_equal ["classA"], mm::HasManyTestClass.ecore.eReferences.select{|r| r.many == true}.name
+ end
+
+ def test_has_many_insert
+ o = mm::HasManyTestClass.new
+ ca1 = mm::ClassA.new
+ ca2 = mm::ClassA.new
+ ca3 = mm::ClassA.new
+ ca4 = mm::ClassA.new
+ ca5 = mm::ClassA.new
+ o.addClassA(ca1)
+ o.addClassA(ca2)
+ o.addClassA(ca3,0)
+ o.addClassA(ca4,1)
+ o.addGeneric("classA",ca5,2)
+ assert_equal [ca3, ca4, ca5, ca1, ca2], o.classA
+ end
+
+ def test_one_to_many
+ oc = mm::OneClass.new
+ assert_respond_to oc, :manyClasses
+ assert oc.manyClasses.empty?
+
+ mc = mm::ManyClass.new
+ assert_respond_to mc, :oneClass
+ assert_respond_to mc, :oneClass=
+ assert_nil mc.oneClass
+
+ # put the OneClass into the ManyClass
+ mc.oneClass = oc
+ assert_equal oc, mc.oneClass
+ assert oc.manyClasses.include?(mc)
+
+ # remove the OneClass from the ManyClass
+ mc.oneClass = nil
+ assert_equal nil, mc.oneClass
+ assert !oc.manyClasses.include?(mc)
+
+ # put the ManyClass into the OneClass
+ oc.addManyClasses mc
+ assert oc.manyClasses.include?(mc)
+ assert_equal oc, mc.oneClass
+
+ # remove the ManyClass from the OneClass
+ oc.removeManyClasses mc
+ assert !oc.manyClasses.include?(mc)
+ assert_equal nil, mc.oneClass
+
+ assert_equal [], mm::OneClass.ecore.eReferences.select{|r| r.many == false}
+ assert_equal ["manyClasses"], mm::OneClass.ecore.eReferences.select{|r| r.many == true}.name
+ assert_equal 5, mm::OneClass.ecore.eReferences.find{|r| r.many == true}.upperBound
+ assert_equal ["oneClass"], mm::ManyClass.ecore.eReferences.select{|r| r.many == false}.name
+ assert_equal [], mm::ManyClass.ecore.eReferences.select{|r| r.many == true}
+ end
+
+ def test_one_to_many_replace1
+ oc1 = mm::OneClass.new
+ oc2 = mm::OneClass.new
+ mc = mm::ManyClass.new
+
+ oc1.manyClasses = [mc]
+ assert_equal [mc], oc1.manyClasses
+ assert_equal [], oc2.manyClasses
+ assert_equal oc1, mc.oneClass
+
+ oc2.manyClasses = [mc]
+ assert_equal [mc], oc2.manyClasses
+ assert_equal [], oc1.manyClasses
+ assert_equal oc2, mc.oneClass
+ end
+
+ def test_one_to_many_replace2
+ oc = mm::OneClass.new
+ mc1 = mm::ManyClass.new
+ mc2 = mm::ManyClass.new
+
+ mc1.oneClass = oc
+ assert_equal [mc1], oc.manyClasses
+ assert_equal oc, mc1.oneClass
+ assert_equal nil, mc2.oneClass
+
+ mc2.oneClass = oc
+ assert_equal [mc1, mc2], oc.manyClasses
+ assert_equal oc, mc1.oneClass
+ assert_equal oc, mc2.oneClass
+ end
+
+ def test_one_to_many_insert
+ oc = mm::OneClass.new
+ mc1 = mm::ManyClass.new
+ mc2 = mm::ManyClass.new
+
+ oc.addManyClasses(mc1, 0)
+ oc.addManyClasses(mc2, 0)
+ assert_equal [mc2, mc1], oc.manyClasses
+ assert_equal oc, mc1.oneClass
+ assert_equal oc, mc2.oneClass
+ end
+
+ def test_one_to_many2
+ oc = mm::OneClass2.new
+ assert_respond_to oc, :manyClasses
+ assert oc.manyClasses.empty?
+
+ mc = mm::ManyClass2.new
+ assert_respond_to mc, :oneClass
+ assert_respond_to mc, :oneClass=
+ assert_nil mc.oneClass
+
+ # put the OneClass into the ManyClass
+ mc.oneClass = oc
+ assert_equal oc, mc.oneClass
+ assert oc.manyClasses.include?(mc)
+
+ # remove the OneClass from the ManyClass
+ mc.oneClass = nil
+ assert_equal nil, mc.oneClass
+ assert !oc.manyClasses.include?(mc)
+
+ # put the ManyClass into the OneClass
+ oc.addManyClasses mc
+ assert oc.manyClasses.include?(mc)
+ assert_equal oc, mc.oneClass
+
+ # remove the ManyClass from the OneClass
+ oc.removeManyClasses mc
+ assert !oc.manyClasses.include?(mc)
+ assert_equal nil, mc.oneClass
+
+ assert_equal [], mm::OneClass2.ecore.eReferences.select{|r| r.many == false}
+ assert_equal ["manyClasses"], mm::OneClass2.ecore.eReferences.select{|r| r.many == true}.name
+ assert_equal ["oneClass"], mm::ManyClass2.ecore.eReferences.select{|r| r.many == false}.name
+ assert_equal [], mm::ManyClass2.ecore.eReferences.select{|r| r.many == true}
+ end
+
+ def test_one_to_one
+ ac = mm::AClassOO.new
+ assert_respond_to ac, :bClass
+ assert_respond_to ac, :bClass=
+ assert_nil ac.bClass
+
+ bc = mm::BClassOO.new
+ assert_respond_to bc, :aClass
+ assert_respond_to bc, :aClass=
+ assert_nil bc.aClass
+
+ # put the AClass into the BClass
+ bc.aClass = ac
+ assert_equal ac, bc.aClass
+ assert_equal bc, ac.bClass
+
+ # remove the AClass from the BClass
+ bc.aClass = nil
+ assert_equal nil, bc.aClass
+ assert_equal nil, ac.bClass
+
+ # put the BClass into the AClass
+ ac.bClass = bc
+ assert_equal bc, ac.bClass
+ assert_equal ac, bc.aClass
+
+ # remove the BClass from the AClass
+ ac.bClass = nil
+ assert_equal nil, ac.bClass
+ assert_equal nil, bc.aClass
+
+ assert_equal ["bClass"], mm::AClassOO.ecore.eReferences.select{|r| r.many == false}.name
+ assert_equal [], mm::AClassOO.ecore.eReferences.select{|r| r.many == true}
+ assert_equal ["aClass"], mm::BClassOO.ecore.eReferences.select{|r| r.many == false}.name
+ assert_equal [], mm::BClassOO.ecore.eReferences.select{|r| r.many == true}
+ end
+
+ def test_one_to_one_replace
+ a = mm::AClassOO.new
+ b1 = mm::BClassOO.new
+ b2 = mm::BClassOO.new
+
+ a.bClass = b1
+ assert_equal b1, a.bClass
+ assert_equal a, b1.aClass
+ assert_equal nil, b2.aClass
+
+ a.bClass = b2
+ assert_equal b2, a.bClass
+ assert_equal nil, b1.aClass
+ assert_equal a, b2.aClass
+ end
+
+ def test_many_to_many
+
+ ac = mm::AClassMM.new
+ assert_respond_to ac, :bClasses
+ assert ac.bClasses.empty?
+
+ bc = mm::BClassMM.new
+ assert_respond_to bc, :aClasses
+ assert bc.aClasses.empty?
+
+ # put the AClass into the BClass
+ bc.addAClasses ac
+ assert bc.aClasses.include?(ac)
+ assert ac.bClasses.include?(bc)
+
+ # put something else into the BClass
+ err = assert_raise StandardError do
+ bc.addAClasses :notaaclass
+ end
+ assert_match /In (\w+::)+BClassMM : Can not use a Symbol\(:notaaclass\) where a (\w+::)+AClassMM is expected/, err.message
+
+ # remove the AClass from the BClass
+ bc.removeAClasses ac
+ assert !bc.aClasses.include?(ac)
+ assert !ac.bClasses.include?(bc)
+
+ # put the BClass into the AClass
+ ac.addBClasses bc
+ assert ac.bClasses.include?(bc)
+ assert bc.aClasses.include?(ac)
+
+ # put something else into the AClass
+ err = assert_raise StandardError do
+ ac.addBClasses :notabclass
+ end
+ assert_match /In (\w+::)+AClassMM : Can not use a Symbol\(:notabclass\) where a (\w+::)+BClassMM is expected/, err.message
+
+ # remove the BClass from the AClass
+ ac.removeBClasses bc
+ assert !ac.bClasses.include?(bc)
+ assert !bc.aClasses.include?(ac)
+
+ assert_equal [], mm::AClassMM.ecore.eReferences.select{|r| r.many == false}
+ assert_equal ["bClasses"], mm::AClassMM.ecore.eReferences.select{|r| r.many == true}.name
+ assert_equal [], mm::BClassMM.ecore.eReferences.select{|r| r.many == false}
+ assert_equal ["aClasses"], mm::BClassMM.ecore.eReferences.select{|r| r.many == true}.name
+ end
+
+ def test_many_to_many_insert
+ ac1 = mm::AClassMM.new
+ ac2 = mm::AClassMM.new
+ bc1= mm::BClassMM.new
+ bc2= mm::BClassMM.new
+
+ ac1.addBClasses(bc1)
+ ac1.addBClasses(bc2, 0)
+ ac2.addBClasses(bc1)
+ ac2.addBClasses(bc2, 0)
+
+ assert_equal [bc2, bc1], ac1.bClasses
+ assert_equal [bc2, bc1], ac2.bClasses
+ assert_equal [ac1, ac2], bc1.aClasses
+ assert_equal [ac1, ac2], bc2.aClasses
+ end
+
+ def test_inheritance
+ assert_equal ["name"], mm::SomeSuperClass.ecore.eAllAttributes.name
+ assert_equal ["classAs"], mm::SomeSuperClass.ecore.eAllReferences.name
+ assert_equal ["name", "subname"], mm::SomeSubClass.ecore.eAllAttributes.name.sort
+ assert_equal ["classAs", "classBs"], mm::SomeSubClass.ecore.eAllReferences.name.sort
+ assert_equal ["name", "othersubname"], mm::OtherSubClass.ecore.eAllAttributes.name.sort
+ assert_equal ["classAs", "classCs"], mm::OtherSubClass.ecore.eAllReferences.name.sort
+ assert mm::SomeSubClass.new.is_a?(mm::SomeSuperClass)
+ assert_equal ["name", "othersubname", "subname", "subsubname"], mm::SubSubClass.ecore.eAllAttributes.name.sort
+ assert_equal ["classAs", "classBs", "classCs"], mm::SubSubClass.ecore.eAllReferences.name.sort
+ assert mm::SubSubClass.new.is_a?(mm::SomeSuperClass)
+ assert mm::SubSubClass.new.is_a?(mm::SomeSubClass)
+ assert mm::SubSubClass.new.is_a?(mm::OtherSubClass)
+ end
+
+ def test_annotations
+ assert_equal 1, mm::AnnotatedModule.ecore.eAnnotations.size
+ anno = mm::AnnotatedModule.ecore.eAnnotations.first
+ checkAnnotation(anno, nil, {"moduletag" => "modulevalue"})
+
+ eClass = mm::AnnotatedModule::AnnotatedClass.ecore
+ assert_equal 2, eClass.eAnnotations.size
+ anno = eClass.eAnnotations.find{|a| a.source == "rgen/test"}
+ checkAnnotation(anno, "rgen/test", {"thirdtag" => "thirdvalue"})
+ anno = eClass.eAnnotations.find{|a| a.source == nil}
+ checkAnnotation(anno, nil, {"sometag" => "somevalue", "othertag" => "othervalue"})
+
+ eAttr = eClass.eAttributes.first
+ assert_equal 2, eAttr.eAnnotations.size
+ anno = eAttr.eAnnotations.find{|a| a.source == "rgen/test2"}
+ checkAnnotation(anno, "rgen/test2", {"attrtag2" => "attrvalue2", "attrtag3" => "attrvalue3"})
+ anno = eAttr.eAnnotations.find{|a| a.source == nil}
+ checkAnnotation(anno, nil, {"attrtag" => "attrval"})
+
+ eRef = eClass.eReferences.find{|r| !r.eOpposite}
+ assert_equal 2, eRef.eAnnotations.size
+ anno = eRef.eAnnotations.find{|a| a.source == "rgen/test3"}
+ checkAnnotation(anno, "rgen/test3", {"reftag2" => "refvalue2", "reftag3" => "refvalue3"})
+ anno = eRef.eAnnotations.find{|a| a.source == nil}
+ checkAnnotation(anno, nil, {"reftag" => "refval"})
+
+ eRef = eClass.eReferences.find{|r| r.eOpposite}
+ assert_equal 1, eRef.eAnnotations.size
+ anno = eRef.eAnnotations.first
+ checkAnnotation(anno, nil, {"m2mtag" => "m2mval"})
+ eRef = eRef.eOpposite
+ assert_equal 1, eRef.eAnnotations.size
+ anno = eRef.eAnnotations.first
+ checkAnnotation(anno, nil, {"opposite_m2mtag" => "opposite_m2mval"})
+ end
+
+ def checkAnnotation(anno, source, hash)
+ assert anno.is_a?(RGen::ECore::EAnnotation)
+ assert_equal source, anno.source
+ assert_equal hash.size, anno.details.size
+ hash.each_pair do |k, v|
+ detail = anno.details.find{|d| d.key == k}
+ assert detail.is_a?(RGen::ECore::EStringToStringMapEntry)
+ assert_equal v, detail.value
+ end
+ end
+
+ def test_ecore_identity
+ subPackage = mm::SomePackage::SubPackage.ecore
+ assert_equal subPackage.eClassifiers.first.object_id, mm::SomePackage::SubPackage::ClassB.ecore.object_id
+
+ somePackage = mm::SomePackage.ecore
+ assert_equal somePackage.eSubpackages.first.object_id, subPackage.object_id
+ end
+
+ def test_proxy
+ p = RGen::MetamodelBuilder::MMProxy.new("test")
+ assert_equal "test", p.targetIdentifier
+ p.targetIdentifier = 123
+ assert_equal 123, p.targetIdentifier
+ p.data = "additional info"
+ assert_equal "additional info", p.data
+ q = RGen::MetamodelBuilder::MMProxy.new("ident", "data")
+ assert_equal "data", q.data
+ end
+
+ def test_proxies_has_one
+ e = mm::HasOneTestClass.new
+ proxy = RGen::MetamodelBuilder::MMProxy.new
+ e.classA = proxy
+ assert_equal proxy, e.classA
+ a = mm::ClassA.new
+ # displace proxy
+ e.classA = a
+ assert_equal a, e.classA
+ # displace by proxy
+ e.classA = proxy
+ assert_equal proxy, e.classA
+ end
+
+ def test_proxies_has_many
+ e = mm::HasManyTestClass.new
+ proxy = RGen::MetamodelBuilder::MMProxy.new
+ e.addClassA(proxy)
+ assert_equal [proxy], e.classA
+ # again
+ e.addClassA(proxy)
+ assert_equal [proxy], e.classA
+ proxy2 = RGen::MetamodelBuilder::MMProxy.new
+ e.addClassA(proxy2)
+ assert_equal [proxy, proxy2], e.classA
+ e.removeClassA(proxy)
+ assert_equal [proxy2], e.classA
+ # again
+ e.removeClassA(proxy)
+ assert_equal [proxy2], e.classA
+ e.removeClassA(proxy2)
+ assert_equal [], e.classA
+ end
+
+ def test_proxies_one_to_one
+ ea = mm::AClassOO.new
+ eb = mm::BClassOO.new
+ proxy1 = RGen::MetamodelBuilder::MMProxy.new
+ proxy2 = RGen::MetamodelBuilder::MMProxy.new
+ ea.bClass = proxy1
+ eb.aClass = proxy2
+ assert_equal proxy1, ea.bClass
+ assert_equal proxy2, eb.aClass
+ # displace proxies
+ ea.bClass = eb
+ assert_equal eb, ea.bClass
+ assert_equal ea, eb.aClass
+ # displace by proxy
+ ea.bClass = proxy1
+ assert_equal proxy1, ea.bClass
+ assert_nil eb.aClass
+ end
+
+ def test_proxies_one_to_many
+ eo = mm::OneClass.new
+ em = mm::ManyClass.new
+ proxy1 = RGen::MetamodelBuilder::MMProxy.new
+ proxy2 = RGen::MetamodelBuilder::MMProxy.new
+ eo.addManyClasses(proxy1)
+ assert_equal [proxy1], eo.manyClasses
+ em.oneClass = proxy2
+ assert_equal proxy2, em.oneClass
+ # displace proxies at many side
+ # adding em will set em.oneClass to eo and displace the proxy from em.oneClass
+ eo.addManyClasses(em)
+ assert_equal [proxy1, em], eo.manyClasses
+ assert_equal eo, em.oneClass
+ eo.removeManyClasses(proxy1)
+ assert_equal [em], eo.manyClasses
+ assert_equal eo, em.oneClass
+ # displace by proxy
+ em.oneClass = proxy2
+ assert_equal [], eo.manyClasses
+ assert_equal proxy2, em.oneClass
+ # displace proxies at one side
+ em.oneClass = eo
+ assert_equal [em], eo.manyClasses
+ assert_equal eo, em.oneClass
+ end
+
+ def test_proxies_many_to_many
+ e1 = mm::AClassMM.new
+ e2 = mm::BClassMM.new
+ proxy1 = RGen::MetamodelBuilder::MMProxy.new
+ proxy2 = RGen::MetamodelBuilder::MMProxy.new
+ e1.addBClasses(proxy1)
+ e2.addAClasses(proxy2)
+ assert_equal [proxy1], e1.bClasses
+ assert_equal [proxy2], e2.aClasses
+ e1.addBClasses(e2)
+ assert_equal [proxy1, e2], e1.bClasses
+ assert_equal [proxy2, e1], e2.aClasses
+ e1.removeBClasses(proxy1)
+ e2.removeAClasses(proxy2)
+ assert_equal [e2], e1.bClasses
+ assert_equal [e1], e2.aClasses
+ end
+
+ # Multiplicity agnostic convenience methods
+
+ def test_genericAccess
+ e1 = mm::OneClass.new
+ e2 = mm::ManyClass.new
+ e3 = mm::OneClass.new
+ e4 = mm::ManyClass.new
+ # use on "many" feature
+ e1.setOrAddGeneric("manyClasses", e2)
+ assert_equal [e2], e1.manyClasses
+ assert_equal [e2], e1.getGeneric("manyClasses")
+ assert_equal [e2], e1.getGenericAsArray("manyClasses")
+ # use on "one" feature
+ e2.setOrAddGeneric("oneClass", e3)
+ assert_equal e3, e2.oneClass
+ assert_equal e3, e2.getGeneric("oneClass")
+ assert_equal [e3], e2.getGenericAsArray("oneClass")
+ assert_nil e4.getGeneric("oneClass")
+ assert_equal [], e4.getGenericAsArray("oneClass")
+ end
+
+ def test_setNilOrRemoveGeneric
+ e1 = mm::OneClass.new
+ e2 = mm::ManyClass.new
+ e3 = mm::OneClass.new
+ # use on "many" feature
+ e1.addManyClasses(e2)
+ assert_equal [e2], e1.manyClasses
+ e1.setNilOrRemoveGeneric("manyClasses", e2)
+ assert_equal [], e1.manyClasses
+ # use on "one" feature
+ e2.oneClass = e3
+ assert_equal e3, e2.oneClass
+ e2.setNilOrRemoveGeneric("oneClass", e3)
+ assert_nil e2.oneClass
+ end
+
+ def test_setNilOrRemoveAllGeneric
+ e1 = mm::OneClass.new
+ e2 = mm::ManyClass.new
+ e3 = mm::OneClass.new
+ e4 = mm::ManyClass.new
+ # use on "many" feature
+ e1.addManyClasses(e2)
+ e1.addManyClasses(e4)
+ assert_equal [e2, e4], e1.manyClasses
+ e1.setNilOrRemoveAllGeneric("manyClasses")
+ assert_equal [], e1.manyClasses
+ # use on "one" feature
+ e2.oneClass = e3
+ assert_equal e3, e2.oneClass
+ e2.setNilOrRemoveAllGeneric("oneClass")
+ assert_nil e2.oneClass
+ end
+
+ def test_abstract
+ err = assert_raise StandardError do
+ mm::AbstractClass.new
+ end
+ assert_match /Class (\w+::)+AbstractClass is abstract/, err.message
+ end
+
+ module BadDefaultValueLiteralContainer
+ Test1 = proc do
+ class BadClass < RGen::MetamodelBuilder::MMBase
+ has_attr 'integerWithDefault', Integer, :defaultValueLiteral => "1.1"
+ end
+ end
+ Test2 = proc do
+ class BadClass < RGen::MetamodelBuilder::MMBase
+ has_attr 'integerWithDefault', Integer, :defaultValueLiteral => "x"
+ end
+ end
+ Test3 = proc do
+ class BadClass < RGen::MetamodelBuilder::MMBase
+ has_attr 'boolWithDefault', Boolean, :defaultValueLiteral => "1"
+ end
+ end
+ Test4 = proc do
+ class BadClass < RGen::MetamodelBuilder::MMBase
+ has_attr 'floatWithDefault', Float, :defaultValueLiteral => "1"
+ end
+ end
+ Test5 = proc do
+ class BadClass < RGen::MetamodelBuilder::MMBase
+ has_attr 'floatWithDefault', Float, :defaultValueLiteral => "true"
+ end
+ end
+ Test6 = proc do
+ class BadClass < RGen::MetamodelBuilder::MMBase
+ kindType = RGen::MetamodelBuilder::DataTypes::Enum.new([:simple, :extended])
+ has_attr 'enumWithDefault', kindType, :defaultValueLiteral => "xxx"
+ end
+ end
+ Test7 = proc do
+ class BadClass < RGen::MetamodelBuilder::MMBase
+ kindType = RGen::MetamodelBuilder::DataTypes::Enum.new([:simple, :extended])
+ has_attr 'enumWithDefault', kindType, :defaultValueLiteral => "7"
+ end
+ end
+ Test8 = proc do
+ class BadClass < RGen::MetamodelBuilder::MMBase
+ has_attr 'longWithDefault', Integer, :defaultValueLiteral => "1.1"
+ end
+ end
+ end
+
+ def test_bad_default_value_literal
+ err = assert_raise StandardError do
+ BadDefaultValueLiteralContainer::Test1.call
+ end
+ assert_equal "Property integerWithDefault can not take value 1.1, expected an Integer", err.message
+ err = assert_raise StandardError do
+ BadDefaultValueLiteralContainer::Test2.call
+ end
+ assert_equal "Property integerWithDefault can not take value x, expected an Integer", err.message
+ err = assert_raise StandardError do
+ BadDefaultValueLiteralContainer::Test3.call
+ end
+ assert_equal "Property boolWithDefault can not take value 1, expected true or false", err.message
+ err = assert_raise StandardError do
+ BadDefaultValueLiteralContainer::Test4.call
+ end
+ assert_equal "Property floatWithDefault can not take value 1, expected a Float", err.message
+ err = assert_raise StandardError do
+ BadDefaultValueLiteralContainer::Test5.call
+ end
+ assert_equal "Property floatWithDefault can not take value true, expected a Float", err.message
+ err = assert_raise StandardError do
+ BadDefaultValueLiteralContainer::Test6.call
+ end
+ assert_equal "Property enumWithDefault can not take value xxx, expected one of :simple, :extended", err.message
+ err = assert_raise StandardError do
+ BadDefaultValueLiteralContainer::Test7.call
+ end
+ assert_equal "Property enumWithDefault can not take value 7, expected one of :simple, :extended", err.message
+ err = assert_raise StandardError do
+ BadDefaultValueLiteralContainer::Test8.call
+ end
+ assert_equal "Property longWithDefault can not take value 1.1, expected an Integer", err.message
+ end
+
+ def test_isset_set_to_nil
+ e = mm::SimpleClass.new
+ assert_respond_to e, :name
+ assert !e.eIsSet(:name)
+ assert !e.eIsSet("name")
+ e.name = nil
+ assert e.eIsSet(:name)
+ end
+
+ def test_isset_set_to_default
+ e = mm::SimpleClass.new
+ assert !e.eIsSet(:stringWithDefault)
+ # set the default value
+ e.name = "xtest"
+ assert e.eIsSet(:name)
+ end
+
+ def test_isset_many_add
+ e = mm::ManyAttrClass.new
+ assert_equal [], e.literals
+ assert !e.eIsSet(:literals)
+ e.addLiterals("x")
+ assert e.eIsSet(:literals)
+ end
+
+ def test_isset_many_remove
+ e = mm::ManyAttrClass.new
+ assert_equal [], e.literals
+ assert !e.eIsSet(:literals)
+ # removing a value which is not there
+ e.removeLiterals("x")
+ assert e.eIsSet(:literals)
+ end
+
+ def test_isset_ref
+ ac = mm::AClassOO.new
+ bc = mm::BClassOO.new
+ assert !bc.eIsSet(:aClass)
+ assert !ac.eIsSet(:bClass)
+ bc.aClass = ac
+ assert bc.eIsSet(:aClass)
+ assert ac.eIsSet(:bClass)
+ end
+
+ def test_isset_ref_many
+ ac = mm::AClassMM.new
+ bc = mm::BClassMM.new
+ assert !bc.eIsSet(:aClasses)
+ assert !ac.eIsSet(:bClasses)
+ bc.aClasses = [ac]
+ assert bc.eIsSet(:aClasses)
+ assert ac.eIsSet(:bClasses)
+ end
+
+ def test_unset_nil
+ e = mm::SimpleClass.new
+ e.name = nil
+ assert e.eIsSet(:name)
+ e.eUnset(:name)
+ assert !e.eIsSet(:name)
+ end
+
+ def test_unset_string
+ e = mm::SimpleClass.new
+ e.name = "someone"
+ assert e.eIsSet(:name)
+ e.eUnset(:name)
+ assert !e.eIsSet(:name)
+ end
+
+ def test_unset_ref
+ ac = mm::AClassOO.new
+ bc = mm::BClassOO.new
+ bc.aClass = ac
+ assert bc.eIsSet(:aClass)
+ assert ac.eIsSet(:bClass)
+ assert_equal bc, ac.bClass
+ bc.eUnset(:aClass)
+ assert_nil bc.aClass
+ assert_nil ac.bClass
+ assert !bc.eIsSet(:aClass)
+ # opposite ref is nil but still "set"
+ assert ac.eIsSet(:bClass)
+ end
+
+ def test_unset_ref_many
+ ac = mm::AClassMM.new
+ bc = mm::BClassMM.new
+ bc.aClasses = [ac]
+ assert bc.eIsSet(:aClasses)
+ assert ac.eIsSet(:bClasses)
+ assert_equal [bc], ac.bClasses
+ bc.eUnset(:aClasses)
+ assert_equal [], bc.aClasses
+ assert_equal [], ac.bClasses
+ assert !bc.eIsSet(:aClasses)
+ # opposite ref is empty but still "set"
+ assert ac.eIsSet(:bClasses)
+ end
+
+ def test_unset_marshal
+ e = mm::SimpleClass.new
+ e.name = "someone"
+ e.eUnset(:name)
+ e2 = Marshal.load(Marshal.dump(e))
+ assert e.object_id != e2.object_id
+ assert !e2.eIsSet(:name)
+ end
+
+ def test_conainer_one_uni
+ a = mm::ContainerClass.new
+ b = mm::ContainedClass.new
+ c = mm::ContainedClass.new
+ assert_equal [], a.eContents
+ assert_equal [], a.eAllContents
+ assert_nil b.eContainer
+ assert_nil b.eContainingFeature
+ a.oneChildUni = b
+ assert_equal a, b.eContainer
+ assert_equal :oneChildUni, b.eContainingFeature
+ assert_equal [b], a.eContents
+ assert_equal [b], a.eAllContents
+ a.oneChildUni = c
+ assert_nil b.eContainer
+ assert_nil b.eContainingFeature
+ assert_equal a, c.eContainer
+ assert_equal :oneChildUni, c.eContainingFeature
+ assert_equal [c], a.eContents
+ assert_equal [c], a.eAllContents
+ a.oneChildUni = nil
+ assert_nil c.eContainer
+ assert_nil c.eContainingFeature
+ assert_equal [], a.eContents
+ assert_equal [], a.eAllContents
+ end
+
+ def test_container_many_uni
+ a = mm::ContainerClass.new
+ b = mm::ContainedClass.new
+ c = mm::ContainedClass.new
+ assert_equal [], a.eContents
+ assert_equal [], a.eAllContents
+ a.addManyChildUni(b)
+ assert_equal a, b.eContainer
+ assert_equal :manyChildUni, b.eContainingFeature
+ assert_equal [b], a.eContents
+ assert_equal [b], a.eAllContents
+ a.addManyChildUni(c)
+ assert_equal a, c.eContainer
+ assert_equal :manyChildUni, c.eContainingFeature
+ assert_equal [b, c], a.eContents
+ assert_equal [b, c], a.eAllContents
+ a.removeManyChildUni(b)
+ assert_nil b.eContainer
+ assert_nil b.eContainingFeature
+ assert_equal a, c.eContainer
+ assert_equal :manyChildUni, c.eContainingFeature
+ assert_equal [c], a.eContents
+ assert_equal [c], a.eAllContents
+ a.removeManyChildUni(c)
+ assert_nil c.eContainer
+ assert_nil c.eContainingFeature
+ assert_equal [], a.eContents
+ assert_equal [], a.eAllContents
+ end
+
+ def test_conainer_one_bi
+ a = mm::ContainerClass.new
+ b = mm::ContainedClass.new
+ c = mm::ContainerClass.new
+ d = mm::ContainedClass.new
+ a.oneChild = b
+ assert_equal a, b.eContainer
+ assert_equal :oneChild, b.eContainingFeature
+ assert_equal [b], a.eContents
+ assert_equal [b], a.eAllContents
+ c.oneChild = d
+ assert_equal c, d.eContainer
+ assert_equal :oneChild, d.eContainingFeature
+ assert_equal [d], c.eContents
+ assert_equal [d], c.eAllContents
+ a.oneChild = d
+ assert_nil b.eContainer
+ assert_nil b.eContainingFeature
+ assert_equal a, d.eContainer
+ assert_equal :oneChild, d.eContainingFeature
+ assert_equal [d], a.eContents
+ assert_equal [d], a.eAllContents
+ assert_equal [], c.eContents
+ assert_equal [], c.eAllContents
+ end
+
+ def test_conainer_one_bi_rev
+ a = mm::ContainerClass.new
+ b = mm::ContainedClass.new
+ c = mm::ContainerClass.new
+ d = mm::ContainedClass.new
+ a.oneChild = b
+ assert_equal a, b.eContainer
+ assert_equal :oneChild, b.eContainingFeature
+ assert_equal [b], a.eContents
+ assert_equal [b], a.eAllContents
+ c.oneChild = d
+ assert_equal c, d.eContainer
+ assert_equal :oneChild, d.eContainingFeature
+ assert_equal [d], c.eContents
+ assert_equal [d], c.eAllContents
+ d.parentOne = a
+ assert_nil b.eContainer
+ assert_nil b.eContainingFeature
+ assert_equal a, d.eContainer
+ assert_equal :oneChild, d.eContainingFeature
+ assert_equal [d], a.eContents
+ assert_equal [d], a.eAllContents
+ assert_equal [], c.eContents
+ assert_equal [], c.eAllContents
+ end
+
+ def test_conainer_one_bi_nil
+ a = mm::ContainerClass.new
+ b = mm::ContainedClass.new
+ a.oneChild = b
+ assert_equal a, b.eContainer
+ assert_equal :oneChild, b.eContainingFeature
+ assert_equal [b], a.eContents
+ assert_equal [b], a.eAllContents
+ a.oneChild = nil
+ assert_nil b.eContainer
+ assert_nil b.eContainingFeature
+ assert_equal [], a.eContents
+ assert_equal [], a.eAllContents
+ end
+
+ def test_conainer_one_bi_nil_rev
+ a = mm::ContainerClass.new
+ b = mm::ContainedClass.new
+ a.oneChild = b
+ assert_equal a, b.eContainer
+ assert_equal :oneChild, b.eContainingFeature
+ assert_equal [b], a.eContents
+ assert_equal [b], a.eAllContents
+ b.parentOne = nil
+ assert_nil b.eContainer
+ assert_nil b.eContainingFeature
+ assert_equal [], a.eContents
+ assert_equal [], a.eAllContents
+ end
+
+ def test_container_many_bi
+ a = mm::ContainerClass.new
+ b = mm::ContainedClass.new
+ c = mm::ContainedClass.new
+ a.addManyChild(b)
+ a.addManyChild(c)
+ assert_equal a, b.eContainer
+ assert_equal :manyChild, b.eContainingFeature
+ assert_equal a, c.eContainer
+ assert_equal :manyChild, c.eContainingFeature
+ assert_equal [b, c], a.eContents
+ assert_equal [b, c], a.eAllContents
+ a.removeManyChild(b)
+ assert_nil b.eContainer
+ assert_nil b.eContainingFeature
+ assert_equal [c], a.eContents
+ assert_equal [c], a.eAllContents
+ end
+
+ def test_conainer_many_bi_steal
+ a = mm::ContainerClass.new
+ b = mm::ContainedClass.new
+ c = mm::ContainedClass.new
+ d = mm::ContainerClass.new
+ a.addManyChild(b)
+ a.addManyChild(c)
+ assert_equal a, b.eContainer
+ assert_equal :manyChild, b.eContainingFeature
+ assert_equal a, c.eContainer
+ assert_equal :manyChild, c.eContainingFeature
+ assert_equal [b, c], a.eContents
+ assert_equal [b, c], a.eAllContents
+ d.addManyChild(b)
+ assert_equal d, b.eContainer
+ assert_equal :manyChild, b.eContainingFeature
+ assert_equal [c], a.eContents
+ assert_equal [c], a.eAllContents
+ assert_equal [b], d.eContents
+ assert_equal [b], d.eAllContents
+ end
+
+ def test_conainer_many_bi_steal_rev
+ a = mm::ContainerClass.new
+ b = mm::ContainedClass.new
+ c = mm::ContainedClass.new
+ d = mm::ContainerClass.new
+ a.addManyChild(b)
+ a.addManyChild(c)
+ assert_equal a, b.eContainer
+ assert_equal :manyChild, b.eContainingFeature
+ assert_equal a, c.eContainer
+ assert_equal :manyChild, c.eContainingFeature
+ assert_equal [b, c], a.eContents
+ assert_equal [b, c], a.eAllContents
+ b.parentMany = d
+ assert_equal d, b.eContainer
+ assert_equal :manyChild, b.eContainingFeature
+ assert_equal [c], a.eContents
+ assert_equal [c], a.eAllContents
+ assert_equal [b], d.eContents
+ assert_equal [b], d.eAllContents
+ end
+
+ def test_all_contents
+ a = mm::ContainerClass.new
+ b = mm::NestedContainerClass.new
+ c = mm::ContainedClass.new
+ a.oneChildUni = b
+ b.oneChildUni = c
+ assert_equal [b, c], a.eAllContents
+ end
+
+ def test_all_contents_with_block
+ a = mm::ContainerClass.new
+ b = mm::NestedContainerClass.new
+ c = mm::ContainedClass.new
+ a.oneChildUni = b
+ b.oneChildUni = c
+ yielded = []
+ a.eAllContents do |e|
+ yielded << e
+ end
+ assert_equal [b, c], yielded
+ end
+
+ def test_all_contents_prune
+ a = mm::ContainerClass.new
+ b = mm::NestedContainerClass.new
+ c = mm::ContainedClass.new
+ a.oneChildUni = b
+ b.oneChildUni = c
+ yielded = []
+ a.eAllContents do |e|
+ yielded << e
+ :prune
+ end
+ assert_equal [b], yielded
+ end
+
+ def test_container_generic
+ a = mm::ContainerClass.new
+ assert_nothing_raised do
+ a.oneChild = RGen::MetamodelBuilder::MMGeneric.new
+ end
+ end
+
+ def test_opposite_assoc_on_first_write
+ ac = mm::OppositeRefAssocA.new
+ bc = mm::OppositeRefAssocB.new
+
+ # no access to 'aClass' or 'bClass' methods before
+ # test if on-demand metamodel building creates opposite ref association on first write
+ bc.aClass = ac
+ assert_equal ac, bc.aClass
+ assert_equal bc, ac.bClass
+ end
+
+ def test_clear_by_array_assignment
+ oc1 = mm::OneClass.new
+ mc1 = mm::ManyClass.new
+ mc2 = mm::ManyClass.new
+ mc3 = mm::ManyClass.new
+
+ oc1.manyClasses = [mc1, mc2]
+ assert_equal [mc1, mc2], oc1.manyClasses
+ oc1.manyClasses = []
+ assert_equal [], oc1.manyClasses
+ end
+
+ def test_clear_by_array_assignment_uni
+ a = mm::ContainerClass.new
+ b = mm::ContainedClass.new
+ c = mm::ContainedClass.new
+
+ a.manyChildUni = [b, c]
+ assert_equal [b, c], a.manyChildUni
+ a.manyChildUni = []
+ assert_equal [], a.manyChildUni
+ end
+
+ def test_disconnectContainer_one_uni
+ a = mm::ContainerClass.new
+ b = mm::ContainedClass.new
+ a.oneChildUni = b
+ b.disconnectContainer
+ assert_nil a.oneChildUni
+ end
+
+ def test_disconnectContainer_one
+ a = mm::ContainerClass.new
+ b = mm::ContainedClass.new
+ a.oneChild = b
+ b.disconnectContainer
+ assert_nil a.oneChild
+ assert_nil b.parentOne
+ end
+
+ def test_disconnectContainer_many_uni
+ a = mm::ContainerClass.new
+ b = mm::ContainedClass.new
+ c = mm::ContainedClass.new
+ a.addManyChildUni(b)
+ a.addManyChildUni(c)
+ b.disconnectContainer
+ assert_equal [c], a.manyChildUni
+ end
+
+ def test_disconnectContainer_many
+ a = mm::ContainerClass.new
+ b = mm::ContainedClass.new
+ c = mm::ContainedClass.new
+ a.addManyChild(b)
+ a.addManyChild(c)
+ b.disconnectContainer
+ assert_nil b.parentMany
+ assert_equal [c], a.manyChild
+ end
+
+ # Duplicate Containment Tests
+ #
+ # Testing that no element is contained in two different containers at a time.
+ # This must also work for uni-directional containments as well as
+ # for containments via different roles.
+
+ # here the bi-dir reference disconnects from the previous container
+ def test_duplicate_containment_bidir_samerole_one
+ a1 = mm::ContainerClass.new
+ a2 = mm::ContainerClass.new
+ b = mm::ContainedClass.new
+ a1.oneChild = b
+ a2.oneChild = b
+ assert_nil a1.oneChild
+ end
+
+ # here the bi-dir reference disconnects from the previous container
+ def test_duplicate_containment_bidir_samerole_many
+ a1 = mm::ContainerClass.new
+ a2 = mm::ContainerClass.new
+ b = mm::ContainedClass.new
+ a1.addManyChild(b)
+ a2.addManyChild(b)
+ assert_equal [], a1.manyChild
+ end
+
+ def test_duplicate_containment_unidir_samerole_one
+ a1 = mm::ContainerClass.new
+ a2 = mm::ContainerClass.new
+ b = mm::ContainedClass.new
+ a1.oneChildUni = b
+ a2.oneChildUni = b
+ assert_nil a1.oneChildUni
+ end
+
+ def test_duplicate_containment_unidir_samerole_many
+ a1 = mm::ContainerClass.new
+ a2 = mm::ContainerClass.new
+ b = mm::ContainedClass.new
+ a1.addManyChildUni(b)
+ a2.addManyChildUni(b)
+ assert_equal [], a1.manyChildUni
+ end
+
+ def test_duplicate_containment_bidir_otherrole_one
+ a1 = mm::ContainerClass.new
+ a2 = mm::ContainerClass.new
+ b = mm::ContainedClass.new
+ a1.oneChild = b
+ a2.oneChild2 = b
+ assert_nil a1.oneChild
+ end
+
+ def test_duplicate_containment_bidir_otherrole_many
+ a1 = mm::ContainerClass.new
+ a2 = mm::ContainerClass.new
+ b = mm::ContainedClass.new
+ a1.addManyChild(b)
+ a2.addManyChild2(b)
+ assert_equal [], a1.manyChild
+ end
+
+ def test_duplicate_containment_unidir_otherrole_one
+ a1 = mm::ContainerClass.new
+ a2 = mm::ContainerClass.new
+ b = mm::ContainedClass.new
+ a1.oneChildUni = b
+ a2.oneChildUni2 = b
+ assert_nil a1.oneChildUni
+ end
+
+ def test_duplicate_containment_unidir_otherrole_many
+ a1 = mm::ContainerClass.new
+ a2 = mm::ContainerClass.new
+ b = mm::ContainedClass.new
+ a1.addManyChildUni(b)
+ a2.addManyChildUni2(b)
+ assert_equal [], a1.manyChildUni
+ end
+
+end
diff --git a/lib/puppet/vendor/rgen/test/metamodel_from_ecore_test.rb b/lib/puppet/vendor/rgen/test/metamodel_from_ecore_test.rb
new file mode 100644
index 000000000..6617f652d
--- /dev/null
+++ b/lib/puppet/vendor/rgen/test/metamodel_from_ecore_test.rb
@@ -0,0 +1,57 @@
+$:.unshift File.join(File.dirname(__FILE__),"..","test")
+
+require 'metamodel_builder_test'
+require 'rgen/ecore/ecore_to_ruby'
+
+# this test suite runs all the tests of MetamodelBuilderTest with the TestMetamodel
+# replaced by the result of feeding its ecore model through ECoreToRuby
+#
+class MetamodelFromEcoreTest < MetamodelBuilderTest
+
+ # clone the ecore model, because it will be modified below
+ test_ecore = Marshal.load(Marshal.dump(TestMetamodel.ecore))
+ # some EEnum types are not hooked into the EPackage because they do not
+ # appear with a constant assignment in TestMetamodel
+ # fix this by explicitly assigning the ePackage
+ # also fix the name of anonymous enums
+ test_ecore.eClassifiers.find{|c| c.name == "SimpleClass"}.
+ eAttributes.select{|a| a.name == "kind" || a.name == "kindWithDefault"}.each{|a|
+ a.eType.name = "KindType"
+ a.eType.ePackage = test_ecore}
+ test_ecore.eClassifiers.find{|c| c.name == "ManyAttrClass"}.
+ eAttributes.select{|a| a.name == "enums"}.each{|a|
+ a.eType.name = "ABCEnum"
+ a.eType.ePackage = test_ecore}
+
+ MetamodelFromEcore = RGen::ECore::ECoreToRuby.new.create_module(test_ecore)
+
+ def mm
+ MetamodelFromEcore
+ end
+
+ # alternative implementation for dynamic variant
+ def test_bad_default_value_literal
+ package = RGen::ECore::EPackage.new(:name => "Package1", :eClassifiers => [
+ RGen::ECore::EClass.new(:name => "Class1", :eStructuralFeatures => [
+ RGen::ECore::EAttribute.new(:name => "value", :eType => RGen::ECore::EInt, :defaultValueLiteral => "x")])])
+ mod = RGen::ECore::ECoreToRuby.new.create_module(package)
+ obj = mod::Class1.new
+ # the error is raised only when the feature is lazily constructed
+ assert_raise StandardError do
+ obj.value
+ end
+ end
+
+ # define all the test methods explicitly in the subclass
+ # otherwise minitest is smart enough to run the tests only in the superclass context
+ MetamodelBuilderTest.instance_methods.select{|m| m.to_s =~ /^test_/}.each do |m|
+ next if instance_methods(false).include?(m)
+ module_eval <<-END
+ def #{m}
+ super
+ end
+ END
+ end
+
+end
+
diff --git a/lib/puppet/vendor/rgen/test/metamodel_order_test.rb b/lib/puppet/vendor/rgen/test/metamodel_order_test.rb
new file mode 100644
index 000000000..a81cad4b5
--- /dev/null
+++ b/lib/puppet/vendor/rgen/test/metamodel_order_test.rb
@@ -0,0 +1,131 @@
+$:.unshift File.join(File.dirname(__FILE__),"..","lib")
+
+require 'test/unit'
+require 'rgen/ecore/ecore'
+require 'rgen/array_extensions'
+
+class MetamodelOrderTest < Test::Unit::TestCase
+ include RGen::ECore
+
+ module TestMM1
+ extend RGen::MetamodelBuilder::ModuleExtension
+
+ class Class11 < RGen::MetamodelBuilder::MMBase
+ end
+
+ module Module11
+ extend RGen::MetamodelBuilder::ModuleExtension
+
+ DataType111 = RGen::MetamodelBuilder::DataTypes::Enum.new(:name => "DataType111" ,:literals => {:b => 1})
+ DataType112 = RGen::MetamodelBuilder::DataTypes::Enum.new(:name => "DataType112", :literals => {:b => 1})
+
+ class Class111 < RGen::MetamodelBuilder::MMBase
+ end
+
+ # anonymous classes won't be handled by the order helper, but will be in eClassifiers
+ Class112 = Class.new(RGen::MetamodelBuilder::MMBase)
+
+ # classes that are not MMBase won't be handled
+ class Class113
+ end
+
+ # modules that are not extended by the ModuleExtension are not handled
+ module Module111
+ end
+
+ # however it can be extendend later on
+ module Module112
+ # this one is not handled by the order helper since Module112 doesn't have the ModuleExtension yet
+ # however, it will be in eClassifiers
+ class Class1121 < RGen::MetamodelBuilder::MMBase
+ end
+ end
+ # this datatype must be in Module11 not Module112
+ DataType113 = RGen::MetamodelBuilder::DataTypes::Enum.new(:name => "DataType113", :literals => {:b => 1})
+
+ Module112.extend(RGen::MetamodelBuilder::ModuleExtension)
+ # this datatype must be in Module11 not Module112
+ DataType114 = RGen::MetamodelBuilder::DataTypes::Enum.new(:name => "DataType114", :literals => {:b => 1})
+ module Module112
+ # this one is handled because now Module112 is extended
+ class Class1122 < RGen::MetamodelBuilder::MMBase
+ end
+ end
+
+ DataType115 = RGen::MetamodelBuilder::DataTypes::Enum.new(:name => "DataType115", :literals => {:b => 1})
+ DataType116 = RGen::MetamodelBuilder::DataTypes::Enum.new(:name => "DataType116", :literals => {:b => 1})
+ end
+
+ DataType11 = RGen::MetamodelBuilder::DataTypes::Enum.new(:name => "DataType11", :literals => {:a => 1})
+
+ class Class12 < RGen::MetamodelBuilder::MMBase
+ end
+
+ class Class13 < RGen::MetamodelBuilder::MMBase
+ end
+ end
+
+ # datatypes outside of a module won't be handled
+ DataType1 = RGen::MetamodelBuilder::DataTypes::Enum.new(:name => "DataType1", :literals => {:b => 1})
+
+ # classes outside of a module won't be handled
+ class Class1 < RGen::MetamodelBuilder::MMBase
+ end
+
+ module TestMM2
+ extend RGen::MetamodelBuilder::ModuleExtension
+
+ TestMM1::Module11.extend(RGen::MetamodelBuilder::ModuleExtension)
+ # this is a critical case: because of the previous extension of Module11 which is in a different
+ # hierarchy, DataType21 is looked for in Module11 and its parents; finally it is not
+ # found and the definition is ignored for order calculation
+ DataType21 = RGen::MetamodelBuilder::DataTypes::Enum.new(:name => "DataType21", :literals => {:b => 1})
+
+ module Module21
+ extend RGen::MetamodelBuilder::ModuleExtension
+ end
+
+ module Module22
+ extend RGen::MetamodelBuilder::ModuleExtension
+ end
+
+ module Module23
+ extend RGen::MetamodelBuilder::ModuleExtension
+ end
+
+ # if there is no other class or module after the last datatype, it won't show up in _constantOrder
+ # however, the order of eClassifiers can still be reconstructed
+ # note that this can not be tested if the test is executed as part of the whole testsuite
+ # since there will be classes and modules created within other test files
+ DataType22 = RGen::MetamodelBuilder::DataTypes::Enum.new(:name => "DataType22", :literals => {:b => 1})
+ end
+
+ def test_constant_order
+ assert_equal ["Class11", "Module11", "DataType11", "Class12", "Class13"], TestMM1._constantOrder
+ assert_equal ["DataType111", "DataType112", "Class111", "DataType113", "Module112", "DataType114", "DataType115", "DataType116"], TestMM1::Module11._constantOrder
+ assert_equal ["Class1122"], TestMM1::Module11::Module112._constantOrder
+ if File.basename($0) == "metamodel_order_test.rb"
+ # this won't work if run in the whole test suite (see comment at DataType22)
+ assert_equal ["Module21", "Module22", "Module23"], TestMM2._constantOrder
+ end
+ end
+
+ def test_classifier_order
+ # eClassifiers also contains the ones which where ignored in order calculation, these are expected at the end
+ # (in an arbitrary order)
+ assert_equal ["Class11", "DataType11", "Class12", "Class13"], TestMM1.ecore.eClassifiers.name
+ assert_equal ["DataType111", "DataType112", "Class111", "DataType113", "DataType114", "DataType115", "DataType116", "Class112"], TestMM1::Module11.ecore.eClassifiers.name
+ assert_equal ["Class1122", "Class1121"], TestMM1::Module11::Module112.ecore.eClassifiers.name
+ # no classifiers in TestMM2._constantOrder, so the datatypes can appear in arbitrary order
+ assert_equal ["DataType21","DataType22"], TestMM2.ecore.eClassifiers.name.sort
+ end
+
+ def test_subpackage_order
+ assert_equal ["Module11"], TestMM1.ecore.eSubpackages.name
+ assert_equal ["Module112"], TestMM1::Module11.ecore.eSubpackages.name
+ assert_equal [], TestMM1::Module11::Module112.ecore.eSubpackages.name
+ assert_equal ["Module21", "Module22", "Module23"], TestMM2.ecore.eSubpackages.name
+ end
+end
+
+
diff --git a/lib/puppet/vendor/rgen/test/metamodel_roundtrip_test.rb b/lib/puppet/vendor/rgen/test/metamodel_roundtrip_test.rb
new file mode 100644
index 000000000..f0be7cf43
--- /dev/null
+++ b/lib/puppet/vendor/rgen/test/metamodel_roundtrip_test.rb
@@ -0,0 +1,98 @@
+$:.unshift File.join(File.dirname(__FILE__),"..","lib")
+
+require 'test/unit'
+require 'rgen/array_extensions'
+require 'rgen/util/model_comparator'
+require 'mmgen/metamodel_generator'
+require 'rgen/instantiator/ecore_xml_instantiator'
+require 'rgen/serializer/xmi20_serializer'
+
+class MetamodelRoundtripTest < Test::Unit::TestCase
+
+ TEST_DIR = File.dirname(__FILE__)+"/metamodel_roundtrip_test"
+
+ include MMGen::MetamodelGenerator
+ include RGen::Util::ModelComparator
+
+ module Regenerated
+ Inside = binding
+ end
+
+ def test_generator
+ require TEST_DIR+"/TestModel.rb"
+ outfile = TEST_DIR+"/TestModel_Regenerated.rb"
+ generateMetamodel(HouseMetamodel.ecore, outfile)
+
+ File.open(outfile) do |f|
+ eval(f.read, Regenerated::Inside)
+ end
+
+ assert modelEqual?(HouseMetamodel.ecore, Regenerated::HouseMetamodel.ecore, ["instanceClassName"])
+ end
+
+ module UMLRegenerated
+ Inside = binding
+ end
+
+ def test_generate_from_ecore
+ outfile = TEST_DIR+"/houseMetamodel_from_ecore.rb"
+
+ env = RGen::Environment.new
+ File.open(TEST_DIR+"/houseMetamodel.ecore") { |f|
+ ECoreXMLInstantiator.new(env).instantiate(f.read)
+ }
+ rootpackage = env.find(:class => RGen::ECore::EPackage).first
+ rootpackage.name = "HouseMetamodel"
+ generateMetamodel(rootpackage, outfile)
+
+ File.open(outfile) do |f|
+ eval(f.read, UMLRegenerated::Inside, "test_eval", 0)
+ end
+ end
+
+ def test_ecore_serializer
+ require TEST_DIR+"/TestModel.rb"
+ File.open(TEST_DIR+"/houseMetamodel_Regenerated.ecore","w") do |f|
+ ser = RGen::Serializer::XMI20Serializer.new(f)
+ ser.serialize(HouseMetamodel.ecore)
+ end
+ end
+
+ BuiltinTypesTestEcore = TEST_DIR+"/using_builtin_types.ecore"
+
+ def test_ecore_serializer_builtin_types
+ mm = RGen::ECore::EPackage.new(:name => "P1", :eClassifiers => [
+ RGen::ECore::EClass.new(:name => "C1", :eStructuralFeatures => [
+ RGen::ECore::EAttribute.new(:name => "a1", :eType => RGen::ECore::EString),
+ RGen::ECore::EAttribute.new(:name => "a2", :eType => RGen::ECore::EInt),
+ RGen::ECore::EAttribute.new(:name => "a3", :eType => RGen::ECore::ELong),
+ RGen::ECore::EAttribute.new(:name => "a4", :eType => RGen::ECore::EFloat),
+ RGen::ECore::EAttribute.new(:name => "a5", :eType => RGen::ECore::EBoolean)
+ ])
+ ])
+ outfile = TEST_DIR+"/using_builtin_types_serialized.ecore"
+ File.open(outfile, "w") do |f|
+ ser = RGen::Serializer::XMI20Serializer.new(f)
+ ser.serialize(mm)
+ end
+ assert_equal(File.read(BuiltinTypesTestEcore), File.read(outfile))
+ end
+
+ def test_ecore_instantiator_builtin_types
+ env = RGen::Environment.new
+ File.open(BuiltinTypesTestEcore) { |f|
+ ECoreXMLInstantiator.new(env).instantiate(f.read)
+ }
+ a1 = env.find(:class => RGen::ECore::EAttribute, :name => "a1").first
+ assert_equal(RGen::ECore::EString, a1.eType)
+ a2 = env.find(:class => RGen::ECore::EAttribute, :name => "a2").first
+ assert_equal(RGen::ECore::EInt, a2.eType)
+ a3 = env.find(:class => RGen::ECore::EAttribute, :name => "a3").first
+ assert_equal(RGen::ECore::ELong, a3.eType)
+ a4 = env.find(:class => RGen::ECore::EAttribute, :name => "a4").first
+ assert_equal(RGen::ECore::EFloat, a4.eType)
+ a5 = env.find(:class => RGen::ECore::EAttribute, :name => "a5").first
+ assert_equal(RGen::ECore::EBoolean, a5.eType)
+ end
+
+end
diff --git a/lib/puppet/vendor/rgen/test/metamodel_roundtrip_test/TestModel.rb b/lib/puppet/vendor/rgen/test/metamodel_roundtrip_test/TestModel.rb
new file mode 100644
index 000000000..01342286f
--- /dev/null
+++ b/lib/puppet/vendor/rgen/test/metamodel_roundtrip_test/TestModel.rb
@@ -0,0 +1,70 @@
+require 'rgen/metamodel_builder'
+
+module HouseMetamodel
+ extend RGen::MetamodelBuilder::ModuleExtension
+ include RGen::MetamodelBuilder::DataTypes
+
+ SexEnum = Enum.new(:name => "SexEnum", :literals => [ :male, :female ])
+ # TODO: Datatypes
+ # AggregationKind = Enum.new([ :none, :aggregate, :composite ])
+
+ class MeetingPlace < RGen::MetamodelBuilder::MMBase
+ annotation :source => "testmodel", :details => { 'complexity' => '1', 'date_created' => '2006-07-12 08:40:46', 'date_modified' => '2006-07-12 08:44:02', 'ea_ntype' => '0', 'ea_stype' => 'Class', 'gentype' => 'Java', 'isSpecification' => 'false', 'package' => 'EAPK_A1B83D59_CAE1_422c_BA5F_D3624D7156AD', 'package_name' => 'HouseMetamodel', 'phase' => '1.0', 'status' => 'Proposed', 'style' => 'BackColor=-1;BorderColor=-1;BorderWidth=-1;FontColor=-1;VSwimLanes=0;HSwimLanes=0;BorderStyle=0;', 'tagged' => '0', 'version' => '1.0' }
+ end
+
+ class Person < RGen::MetamodelBuilder::MMBase
+ annotation 'complexity' => '1', 'date_created' => '2006-06-27 08:34:23', 'date_modified' => '2006-06-27 08:34:26', 'ea_ntype' => '0', 'ea_stype' => 'Class', 'gentype' => 'Java', 'isSpecification' => 'false', 'package' => 'EAPK_A1B83D59_CAE1_422c_BA5F_D3624D7156AD', 'package_name' => 'HouseMetamodel', 'phase' => '1.0', 'status' => 'Proposed', 'style' => 'BackColor=-1;BorderColor=-1;BorderWidth=-1;FontColor=-1;VSwimLanes=0;HSwimLanes=0;BorderStyle=0;', 'tagged' => '0', 'version' => '1.0'
+ has_attr 'sex', SexEnum
+ has_attr 'id', Long
+ has_many_attr 'nicknames', String
+ end
+
+ class House < RGen::MetamodelBuilder::MMBase
+ annotation 'complexity' => '1', 'date_created' => '2005-09-16 19:52:18', 'date_modified' => '2006-02-28 08:29:19', 'ea_ntype' => '0', 'ea_stype' => 'Class', 'gentype' => 'Java', 'isSpecification' => 'false', 'package' => 'EAPK_A1B83D59_CAE1_422c_BA5F_D3624D7156AD', 'package_name' => 'HouseMetamodel', 'phase' => '1.0', 'status' => 'Proposed', 'stereotype' => 'dummy', 'style' => 'BackColor=-1;BorderColor=-1;BorderWidth=-1;FontColor=-1;VSwimLanes=0;HSwimLanes=0;BorderStyle=0;', 'tagged' => '0', 'version' => '1.0'
+ has_attr 'size', Integer
+ has_attr 'module'
+ has_attr 'address', String, :changeable => false do
+ annotation 'collection' => 'false', 'containment' => 'Not Specified', 'derived' => '0', 'duplicates' => '0', 'ea_guid' => '{A8DF581B-9AC6-4f75-AB48-8FAEDFC6E068}', 'lowerBound' => '1', 'ordered' => '0', 'position' => '0', 'styleex' => 'volatile=0;', 'type' => 'String', 'upperBound' => '1'
+ end
+
+ end
+
+
+ module Rooms
+ extend RGen::MetamodelBuilder::ModuleExtension
+
+
+ class Room < RGen::MetamodelBuilder::MMBase
+ abstract
+ annotation 'complexity' => '1', 'date_created' => '2005-09-16 19:52:28', 'date_modified' => '2006-06-22 21:15:25', 'ea_ntype' => '0', 'ea_stype' => 'Class', 'gentype' => 'Java', 'isSpecification' => 'false', 'package' => 'EAPK_F9D8C6E3_4DAD_4aa2_AD47_D0ABA4E93E08', 'package_name' => 'Rooms', 'phase' => '1.0', 'status' => 'Proposed', 'style' => 'BackColor=-1;BorderColor=-1;BorderWidth=-1;FontColor=-1;VSwimLanes=0;HSwimLanes=0;BorderStyle=0;', 'tagged' => '0', 'version' => '1.0'
+ end
+
+ class Bathroom < Room
+ annotation 'complexity' => '1', 'date_created' => '2006-06-27 08:32:25', 'date_modified' => '2006-06-27 08:34:23', 'ea_ntype' => '0', 'ea_stype' => 'Class', 'gentype' => 'Java', 'isSpecification' => 'false', 'package' => 'EAPK_F9D8C6E3_4DAD_4aa2_AD47_D0ABA4E93E08', 'package_name' => 'Rooms', 'phase' => '1.0', 'status' => 'Proposed', 'style' => 'BackColor=-1;BorderColor=-1;BorderWidth=-1;FontColor=-1;VSwimLanes=0;HSwimLanes=0;BorderStyle=0;', 'tagged' => '0', 'version' => '1.0'
+ end
+
+ class Kitchen < RGen::MetamodelBuilder::MMMultiple(HouseMetamodel::MeetingPlace, Room)
+ annotation 'complexity' => '1', 'date_created' => '2005-11-30 19:26:13', 'date_modified' => '2006-06-22 21:15:34', 'ea_ntype' => '0', 'ea_stype' => 'Class', 'gentype' => 'Java', 'isSpecification' => 'false', 'package' => 'EAPK_F9D8C6E3_4DAD_4aa2_AD47_D0ABA4E93E08', 'package_name' => 'Rooms', 'phase' => '1.0', 'status' => 'Proposed', 'style' => 'BackColor=-1;BorderColor=-1;BorderWidth=-1;FontColor=-1;VSwimLanes=0;HSwimLanes=0;BorderStyle=0;', 'tagged' => '0', 'version' => '1.0'
+ end
+
+ end
+
+ module DependingOnRooms
+ extend RGen::MetamodelBuilder::ModuleExtension
+ class RoomSub < Rooms::Room
+ end
+ end
+end
+
+HouseMetamodel::Person.has_many 'home', HouseMetamodel::House do
+ annotation 'containment' => 'Unspecified'
+end
+HouseMetamodel::House.has_one 'bathroom', HouseMetamodel::Rooms::Bathroom, :lowerBound => 1, :transient => true
+HouseMetamodel::House.one_to_one 'kitchen', HouseMetamodel::Rooms::Kitchen, 'house', :lowerBound => 1, :opposite_lowerBound => 1 do
+ annotation 'containment' => 'Unspecified'
+ opposite_annotation 'containment' => 'Unspecified'
+end
+HouseMetamodel::House.contains_many 'room', HouseMetamodel::Rooms::Room, 'house', :lowerBound => 1 do
+ # only an opposite annotation
+ opposite_annotation 'containment' => 'Unspecified'
+end
diff --git a/lib/puppet/vendor/rgen/test/metamodel_roundtrip_test/houseMetamodel.ecore b/lib/puppet/vendor/rgen/test/metamodel_roundtrip_test/houseMetamodel.ecore
new file mode 100644
index 000000000..6f5c01b03
--- /dev/null
+++ b/lib/puppet/vendor/rgen/test/metamodel_roundtrip_test/houseMetamodel.ecore
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ecore:EPackage xmi:version="2.0"
+ xmlns:xmi="http://www.omg.org/XMI" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:ecore="http://www.eclipse.org/emf/2002/Ecore" name="HouseMetamodel">
+ <eClassifiers xsi:type="ecore:EClass" name="House">
+ <eAnnotations source="bla">
+ <details key="a" value="b"/>
+ </eAnnotations>
+ <eStructuralFeatures xsi:type="ecore:EAttribute" name="address" eType="#//String"
+ changeable="false"/>
+ <eStructuralFeatures xsi:type="ecore:EReference" name="bathroom" lowerBound="1"
+ eType="#//Rooms/Bathroom"/>
+ <eStructuralFeatures xsi:type="ecore:EReference" name="kitchen" lowerBound="1"
+ eType="#//Rooms/Kitchen" eOpposite="#//Rooms/Kitchen/house"/>
+ <eStructuralFeatures xsi:type="ecore:EReference" name="room" upperBound="-1" eType="#//Rooms/Room"
+ containment="true" eOpposite="#//Rooms/Room/house"/>
+ </eClassifiers>
+ <eClassifiers xsi:type="ecore:EClass" name="MeetingPlace"/>
+ <eClassifiers xsi:type="ecore:EClass" name="Person">
+ <eStructuralFeatures xsi:type="ecore:EReference" name="house" upperBound="-1"
+ eType="#//House"/>
+ <eStructuralFeatures xsi:type="ecore:EAttribute" name="sex" eType="#//SexEnum"/>
+ <eStructuralFeatures xsi:type="ecore:EAttribute" name="id" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//ELong"/>
+ <eStructuralFeatures xsi:type="ecore:EAttribute" name="nicknames" upperBound="-1" eType="#//String"/>
+ </eClassifiers>
+ <eClassifiers xsi:type="ecore:EDataType" name="String" instanceClassName="java.lang.String"/>
+ <eClassifiers xsi:type="ecore:EEnum" name="SexEnum">
+ <eLiterals name="male"/>
+ <eLiterals name="female"/>
+ </eClassifiers>
+ <eSubpackages name="Rooms">
+ <eClassifiers xsi:type="ecore:EClass" name="Room">
+ <eStructuralFeatures xsi:type="ecore:EReference" name="house" eType="#//House"
+ defaultValueLiteral="" eOpposite="#//House/room"/>
+ </eClassifiers>
+ <eClassifiers xsi:type="ecore:EClass" name="Bathroom" eSuperTypes="#//Rooms/Room"/>
+ <eClassifiers xsi:type="ecore:EClass" name="Kitchen" eSuperTypes="#//Rooms/Room #//MeetingPlace">
+ <eStructuralFeatures xsi:type="ecore:EReference" name="house" eType="#//House"
+ eOpposite="#//House/kitchen"/>
+ </eClassifiers>
+ </eSubpackages>
+</ecore:EPackage>
diff --git a/lib/puppet/vendor/rgen/test/metamodel_roundtrip_test/houseMetamodel_from_ecore.rb b/lib/puppet/vendor/rgen/test/metamodel_roundtrip_test/houseMetamodel_from_ecore.rb
new file mode 100644
index 000000000..a7a1104d6
--- /dev/null
+++ b/lib/puppet/vendor/rgen/test/metamodel_roundtrip_test/houseMetamodel_from_ecore.rb
@@ -0,0 +1,44 @@
+require 'rgen/metamodel_builder'
+
+module HouseMetamodel
+ extend RGen::MetamodelBuilder::ModuleExtension
+ include RGen::MetamodelBuilder::DataTypes
+
+ SexEnum = Enum.new(:name => 'SexEnum', :literals =>[ :male, :female ])
+
+ class House < RGen::MetamodelBuilder::MMBase
+ annotation :source => "bla", :details => {'a' => 'b'}
+ has_attr 'address', String, :changeable => false
+ end
+
+ class MeetingPlace < RGen::MetamodelBuilder::MMBase
+ end
+
+ class Person < RGen::MetamodelBuilder::MMBase
+ has_attr 'sex', HouseMetamodel::SexEnum
+ has_attr 'id', Long
+ has_many_attr 'nicknames', String
+ end
+
+
+ module Rooms
+ extend RGen::MetamodelBuilder::ModuleExtension
+ include RGen::MetamodelBuilder::DataTypes
+
+
+ class Room < RGen::MetamodelBuilder::MMBase
+ end
+
+ class Bathroom < Room
+ end
+
+ class Kitchen < RGen::MetamodelBuilder::MMMultiple(Room, HouseMetamodel::MeetingPlace)
+ end
+
+ end
+end
+
+HouseMetamodel::House.has_one 'bathroom', HouseMetamodel::Rooms::Bathroom, :lowerBound => 1
+HouseMetamodel::House.one_to_one 'kitchen', HouseMetamodel::Rooms::Kitchen, 'house', :lowerBound => 1
+HouseMetamodel::House.contains_many 'room', HouseMetamodel::Rooms::Room, 'house'
+HouseMetamodel::Person.has_many 'house', HouseMetamodel::House
diff --git a/lib/puppet/vendor/rgen/test/metamodel_roundtrip_test/using_builtin_types.ecore b/lib/puppet/vendor/rgen/test/metamodel_roundtrip_test/using_builtin_types.ecore
new file mode 100644
index 000000000..2f93239c4
--- /dev/null
+++ b/lib/puppet/vendor/rgen/test/metamodel_roundtrip_test/using_builtin_types.ecore
@@ -0,0 +1,9 @@
+<ecore:EPackage name="P1" xmi:version="2.0" xmlns:xmi="http://www.omg.org/XMI" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ecore="http://www.eclipse.org/emf/2002/Ecore">
+ <eClassifiers name="C1" xsi:type="ecore:EClass">
+ <eStructuralFeatures iD="false" changeable="true" derived="false" transient="false" unsettable="false" volatile="false" lowerBound="0" ordered="true" unique="true" upperBound="1" name="a1" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString" xsi:type="ecore:EAttribute"/>
+ <eStructuralFeatures iD="false" changeable="true" derived="false" transient="false" unsettable="false" volatile="false" lowerBound="0" ordered="true" unique="true" upperBound="1" name="a2" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EInt" xsi:type="ecore:EAttribute"/>
+ <eStructuralFeatures iD="false" changeable="true" derived="false" transient="false" unsettable="false" volatile="false" lowerBound="0" ordered="true" unique="true" upperBound="1" name="a3" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//ELong" xsi:type="ecore:EAttribute"/>
+ <eStructuralFeatures iD="false" changeable="true" derived="false" transient="false" unsettable="false" volatile="false" lowerBound="0" ordered="true" unique="true" upperBound="1" name="a4" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EFloat" xsi:type="ecore:EAttribute"/>
+ <eStructuralFeatures iD="false" changeable="true" derived="false" transient="false" unsettable="false" volatile="false" lowerBound="0" ordered="true" unique="true" upperBound="1" name="a5" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EBoolean" xsi:type="ecore:EAttribute"/>
+ </eClassifiers>
+</ecore:EPackage>
diff --git a/lib/puppet/vendor/rgen/test/method_delegation_test.rb b/lib/puppet/vendor/rgen/test/method_delegation_test.rb
new file mode 100644
index 000000000..9b540ea2b
--- /dev/null
+++ b/lib/puppet/vendor/rgen/test/method_delegation_test.rb
@@ -0,0 +1,178 @@
+$:.unshift File.dirname(__FILE__) + "/../lib"
+
+require 'test/unit'
+require 'rgen/util/method_delegation'
+
+class MethodDelegationTest < Test::Unit::TestCase
+ include RGen
+
+ class TestDelegate
+ attr_accessor :mode, :callcount
+ def common_delegated(delegator)
+ @callcount ||= 0
+ @callcount += 1
+ case @mode
+ when :continue
+ throw :continue
+ when :delegatorId
+ delegator.object_id
+ when :return7
+ 7
+ end
+ end
+ alias to_s_delegated common_delegated
+ alias methodInSingleton_delegated common_delegated
+ alias class_delegated common_delegated
+ alias artificialMethod_delegated common_delegated
+ end
+
+ class ConstPathElement < Module
+ def self.const_missing_delegated(delegator, const)
+ ConstPathElement.new(const)
+ end
+ def initialize(name, parent=nil)
+ @name = name.to_s
+ @parent = parent
+ end
+ def const_missing(const)
+ ConstPathElement.new(const, self)
+ end
+ def to_s
+ if @parent
+ @parent.to_s+"::"+@name
+ else
+ @name
+ end
+ end
+ end
+
+ # missing: check with multiple params and block param
+
+ def test_method_defined_in_singleton
+ # delegator is an Array
+ delegator = []
+ # delegating method is a method defined in the singleton class
+ class << delegator
+ def methodInSingleton
+ "result from method in singleton"
+ end
+ end
+ checkDelegation(delegator, "methodInSingleton", "result from method in singleton")
+ end
+
+ def test_method_defined_in_class
+ # delegator is a String
+ delegator = "Delegator1"
+ checkDelegation(delegator, "to_s", "Delegator1")
+ end
+
+ def test_method_defined_in_superclass
+ # delegator is an instance of a new anonymous class
+ delegator = Class.new.new
+ # delegating method is +object_id+ which is defined in the superclass
+ checkDelegation(delegator, "class", delegator.class)
+ end
+
+ def test_new_method
+ # delegator is an String
+ delegator = "Delegator2"
+ # delegating method is a new method which does not exist on String
+ checkDelegation(delegator, "artificialMethod", delegator.object_id, true)
+ end
+
+ def test_const_missing
+ surroundingModule = Module.nesting.first
+ Util::MethodDelegation.registerDelegate(ConstPathElement, surroundingModule, "const_missing")
+
+ assert_equal "SomeArbitraryConst", SomeArbitraryConst.to_s
+ assert_equal "AnotherConst::A::B::C", AnotherConst::A::B::C.to_s
+
+ Util::MethodDelegation.unregisterDelegate(ConstPathElement, surroundingModule, "const_missing")
+ assert_raise NameError do
+ SomeArbitraryConst
+ end
+ end
+
+ def checkDelegation(delegator, method, originalResult, newMethod=false)
+ delegate1 = TestDelegate.new
+ delegate2 = TestDelegate.new
+
+ Util::MethodDelegation.registerDelegate(delegate1, delegator, method)
+ Util::MethodDelegation.registerDelegate(delegate2, delegator, method)
+
+ assert delegator.respond_to?(:_methodDelegates)
+ if newMethod
+ assert !delegator.respond_to?("#{method}_delegate_original".to_sym)
+ else
+ assert delegator.respond_to?("#{method}_delegate_original".to_sym)
+ end
+
+ # check delegator parameter
+ delegate1.mode = :delegatorId
+ assert_equal delegator.object_id, delegator.send(method)
+
+ delegate1.callcount = 0
+ delegate2.callcount = 0
+
+ delegate1.mode = :return7
+ # delegate1 returns a value
+ assert_equal 7, delegator.send(method)
+ assert_equal 1, delegate1.callcount
+ # delegate2 is not called
+ assert_equal 0, delegate2.callcount
+
+ delegate1.mode = :nothing
+ # delegate1 just exits and thus returns nil
+ assert_equal nil, delegator.send(method)
+ assert_equal 2, delegate1.callcount
+ # delegate2 is not called
+ assert_equal 0, delegate2.callcount
+
+ delegate1.mode = :continue
+ delegate2.mode = :return7
+ # delegate1 is called but continues
+ # delegate2 returns a value
+ assert_equal 7, delegator.send(method)
+ assert_equal 3, delegate1.callcount
+ assert_equal 1, delegate2.callcount
+
+ delegate1.mode = :continue
+ delegate2.mode = :continue
+ # both delegates continue, the original method returns its value
+ checkCallOriginal(delegator, method, originalResult, newMethod)
+ # both delegates are called though
+ assert_equal 4, delegate1.callcount
+ assert_equal 2, delegate2.callcount
+
+ # calling unregister with a non existing method has no effect
+ Util::MethodDelegation.unregisterDelegate(delegate1, delegator, "xxx")
+ Util::MethodDelegation.unregisterDelegate(delegate1, delegator, method)
+
+ checkCallOriginal(delegator, method, originalResult, newMethod)
+ # delegate1 not called any more
+ assert_equal 4, delegate1.callcount
+ # delegate2 is still called
+ assert_equal 3, delegate2.callcount
+
+ Util::MethodDelegation.unregisterDelegate(delegate2, delegator, method)
+
+ checkCallOriginal(delegator, method, originalResult, newMethod)
+ # both delegates not called any more
+ assert_equal 4, delegate1.callcount
+ assert_equal 3, delegate2.callcount
+
+ # after all delegates were unregistered, singleton class should be clean
+ assert !delegator.respond_to?(:_methodDelegates)
+ end
+
+ def checkCallOriginal(delegator, method, originalResult, newMethod)
+ if newMethod
+ assert_raise NoMethodError do
+ result = delegator.send(method)
+ end
+ else
+ result = delegator.send(method)
+ assert_equal originalResult, result
+ end
+ end
+end
diff --git a/lib/puppet/vendor/rgen/test/model_builder/builder_context_test.rb b/lib/puppet/vendor/rgen/test/model_builder/builder_context_test.rb
new file mode 100644
index 000000000..7cb62b3d5
--- /dev/null
+++ b/lib/puppet/vendor/rgen/test/model_builder/builder_context_test.rb
@@ -0,0 +1,59 @@
+$:.unshift File.dirname(__FILE__)+"/../lib"
+
+require 'test/unit'
+require 'rgen/ecore/ecore'
+require 'rgen/model_builder/builder_context'
+
+class BuilderContextTest < Test::Unit::TestCase
+
+ module BuilderExtension1
+ module PackageA
+ def inPackAExt
+ 3
+ end
+ module PackageB
+ def inPackBExt
+ 5
+ end
+ end
+ end
+ end
+
+ class BuilderContext
+ def inBuilderContext
+ 7
+ end
+ end
+
+ def test_extensionContainerFactory
+ aboveRoot = RGen::ECore::EPackage.new(:name => "AboveRoot")
+ root = RGen::ECore::EPackage.new(:name => "Root", :eSuperPackage => aboveRoot)
+ packageA = RGen::ECore::EPackage.new(:name => "PackageA", :eSuperPackage => root)
+ packageB = RGen::ECore::EPackage.new(:name => "PackageB", :eSuperPackage => packageA)
+ packageC = RGen::ECore::EPackage.new(:name => "PackageBC", :eSuperPackage => packageA)
+
+ factory = RGen::ModelBuilder::BuilderContext::ExtensionContainerFactory.new(root, BuilderExtension1, BuilderContext.new)
+
+ assert_equal BuilderExtension1::PackageA, factory.moduleForPackage(packageA)
+
+ packAExt = factory.extensionContainer(packageA)
+ assert packAExt.respond_to?(:inPackAExt)
+ assert !packAExt.respond_to?(:inPackBExt)
+ assert_equal 3, packAExt.inPackAExt
+ assert_equal 7, packAExt.inBuilderContext
+
+ assert_equal BuilderExtension1::PackageA::PackageB, factory.moduleForPackage(packageB)
+
+ packBExt = factory.extensionContainer(packageB)
+ assert !packBExt.respond_to?(:inPackAExt)
+ assert packBExt.respond_to?(:inPackBExt)
+ assert_equal 5, packBExt.inPackBExt
+ assert_equal 7, packBExt.inBuilderContext
+
+ assert_raise RuntimeError do
+ # aboveRoot is not contained within root
+ assert_nil factory.moduleForPackage(aboveRoot)
+ end
+ assert_nil factory.moduleForPackage(packageC)
+ end
+end \ No newline at end of file
diff --git a/lib/puppet/vendor/rgen/test/model_builder/builder_test.rb b/lib/puppet/vendor/rgen/test/model_builder/builder_test.rb
new file mode 100644
index 000000000..ce4225f94
--- /dev/null
+++ b/lib/puppet/vendor/rgen/test/model_builder/builder_test.rb
@@ -0,0 +1,242 @@
+$:.unshift File.dirname(__FILE__) + "/../lib"
+
+require 'test/unit'
+require 'rgen/ecore/ecore'
+require 'rgen/ecore/ecore_builder_methods'
+require 'rgen/environment'
+require 'rgen/model_builder'
+require 'model_builder/statemachine_metamodel'
+
+class ModelBuilderTest < Test::Unit::TestCase
+
+ def test_statemachine
+ result = RGen::ModelBuilder.build(StatemachineMetamodel) do
+ statemachine "Airconditioner" do
+ state "Off", :kind => :START
+ compositeState "On" do
+ state "Heating" do
+ transition :as => :outgoingTransition, :targetState => "Cooling",
+ :statemachine => "Airconditioner"
+ end
+ state "Cooling" do
+ end
+ end
+ transition :sourceState => "On.Cooling", :targetState => "On.Heating" do
+ _using Condition::TimeCondition do
+ timeCondition :as => :condition, :timeout => 100
+ end
+ Condition::TimeCondition.timeCondition :as => :condition, :timeout => 10
+ end
+ end
+ _using Condition do
+ statemachine "AirconExtension" do
+ s = state "StartState"
+ transition :sourceState => s, :targetState => "Airconditioner.Off"
+ end
+ end
+ end
+
+ assert result.is_a?(Array)
+ assert_equal 2, result.size
+
+ sm1 = result[0]
+ assert sm1.is_a?(StatemachineMetamodel::Statemachine)
+ assert_equal "Airconditioner", sm1.name
+
+ assert_equal 2, sm1.state.size
+ offState = sm1.state[0]
+ assert offState.is_a?(StatemachineMetamodel::State)
+ assert_equal "Off", offState.name
+ assert_equal :START, offState.kind
+
+ onState = sm1.state[1]
+ assert onState.is_a?(StatemachineMetamodel::CompositeState)
+ assert_equal "On", onState.name
+
+ assert_equal 2, onState.state.size
+ hState = onState.state[0]
+ assert hState.is_a?(StatemachineMetamodel::State)
+ assert_equal "Heating", hState.name
+
+ cState = onState.state[1]
+ assert cState.is_a?(StatemachineMetamodel::State)
+ assert_equal "Cooling", cState.name
+
+ assert_equal 1, hState.outgoingTransition.size
+ hOutTrans = hState.outgoingTransition[0]
+ assert hOutTrans.is_a?(StatemachineMetamodel::Transition)
+ assert_equal cState, hOutTrans.targetState
+ assert_equal sm1, hOutTrans.statemachine
+
+ assert_equal 1, hState.incomingTransition.size
+ hInTrans = hState.incomingTransition[0]
+ assert hInTrans.is_a?(StatemachineMetamodel::Transition)
+ assert_equal cState, hInTrans.sourceState
+ assert_equal sm1, hInTrans.statemachine
+
+ assert_equal 2, hInTrans.condition.size
+ assert hInTrans.condition[0].is_a?(StatemachineMetamodel::Condition::TimeCondition::TimeCondition)
+ assert_equal 100, hInTrans.condition[0].timeout
+ assert hInTrans.condition[1].is_a?(StatemachineMetamodel::Condition::TimeCondition::TimeCondition)
+ assert_equal 10, hInTrans.condition[1].timeout
+
+ sm2 = result[1]
+ assert sm2.is_a?(StatemachineMetamodel::Statemachine)
+ assert_equal "AirconExtension", sm2.name
+
+ assert_equal 1, sm2.state.size
+ sState = sm2.state[0]
+ assert sState.is_a?(StatemachineMetamodel::State)
+ assert_equal "StartState", sState.name
+
+ assert_equal 1, sState.outgoingTransition.size
+ assert sState.outgoingTransition[0].is_a?(StatemachineMetamodel::Transition)
+ assert_equal offState, sState.outgoingTransition[0].targetState
+ assert_equal sm2, sState.outgoingTransition[0].statemachine
+ end
+
+ def test_dynamic
+ numStates = 5
+ env = RGen::Environment.new
+ result = RGen::ModelBuilder.build(StatemachineMetamodel, env) do
+ sm = statemachine "SM#{numStates}" do
+ (1..numStates).each do |i|
+ state "State#{i}" do
+ transition :as => :outgoingTransition, :targetState => "State#{i < numStates ? i+1 : 1}",
+ :statemachine => sm
+ end
+ end
+ end
+ end
+ assert_equal 11, env.elements.size
+ assert_equal "SM5", result[0].name
+ state = result[0].state.first
+ assert_equal "State1", state.name
+ state = state.outgoingTransition.first.targetState
+ assert_equal "State2", state.name
+ state = state.outgoingTransition.first.targetState
+ assert_equal "State3", state.name
+ state = state.outgoingTransition.first.targetState
+ assert_equal "State4", state.name
+ state = state.outgoingTransition.first.targetState
+ assert_equal "State5", state.name
+ assert_equal result[0].state[0], state.outgoingTransition.first.targetState
+ end
+
+ def test_multiref
+ result = RGen::ModelBuilder.build(StatemachineMetamodel) do
+ a = transition
+ transition "b"
+ transition "c"
+ state :outgoingTransition => [a, "b", "c"]
+ end
+
+ assert result[0].is_a?(StatemachineMetamodel::Transition)
+ assert result[1].is_a?(StatemachineMetamodel::Transition)
+ assert !result[1].respond_to?(:name)
+ assert result[2].is_a?(StatemachineMetamodel::Transition)
+ assert !result[2].respond_to?(:name)
+ state = result[3]
+ assert state.is_a?(StatemachineMetamodel::State)
+ assert_equal result[0], state.outgoingTransition[0]
+ assert_equal result[1], state.outgoingTransition[1]
+ assert_equal result[2], state.outgoingTransition[2]
+ end
+
+ module TestMetamodel
+ extend RGen::MetamodelBuilder::ModuleExtension
+
+ # these classes have no name
+ class TestA < RGen::MetamodelBuilder::MMBase
+ end
+ class TestB < RGen::MetamodelBuilder::MMBase
+ end
+ class TestC < RGen::MetamodelBuilder::MMBase
+ end
+ TestA.contains_many 'testB', TestB, 'testA'
+ TestC.has_one 'testB', TestB
+ end
+
+ def test_helper_names
+ result = RGen::ModelBuilder.build(TestMetamodel) do
+ testA "_a" do
+ testB "_b"
+ end
+ testC :testB => "_a._b"
+ end
+ assert result[0].is_a?(TestMetamodel::TestA)
+ assert result[1].is_a?(TestMetamodel::TestC)
+ assert_equal result[0].testB[0], result[1].testB
+ end
+
+ def test_ecore
+ result = RGen::ModelBuilder.build(RGen::ECore, nil, RGen::ECore::ECoreBuilderMethods) do
+ ePackage "TestPackage1" do
+ eClass "TestClass1" do
+ eAttribute "attr1", :eType => RGen::ECore::EString
+ eAttr "attr2", RGen::ECore::EInt
+ eBiRef "biRef1", "TestClass2", "testClass1"
+ contains_1toN 'testClass2', "TestClass2", "tc1Parent"
+ end
+ eClass "TestClass2" do
+ eRef "ref1", "TestClass1"
+ end
+ end
+ end
+
+ assert result.is_a?(Array)
+ assert_equal 1, result.size
+ p1 = result.first
+
+ assert p1.is_a?(RGen::ECore::EPackage)
+ assert_equal "TestPackage1", p1.name
+
+ # TestClass1
+ class1 = p1.eClassifiers.find{|c| c.name == "TestClass1"}
+ assert_not_nil class1
+ assert class1.is_a?(RGen::ECore::EClass)
+
+ # TestClass1.attr1
+ attr1 = class1.eAllAttributes.find{|a| a.name == "attr1"}
+ assert_not_nil attr1
+ assert_equal RGen::ECore::EString, attr1.eType
+
+ # TestClass1.attr2
+ attr2 = class1.eAllAttributes.find{|a| a.name == "attr2"}
+ assert_not_nil attr2
+ assert_equal RGen::ECore::EInt, attr2.eType
+
+ # TestClass2
+ class2 = p1.eClassifiers.find{|c| c.name == "TestClass2"}
+ assert_not_nil class2
+ assert class2.is_a?(RGen::ECore::EClass)
+
+ # TestClass2.ref1
+ ref1 = class2.eAllReferences.find{|a| a.name == "ref1"}
+ assert_not_nil ref1
+ assert_equal class1, ref1.eType
+
+ # TestClass1.biRef1
+ biRef1 = class1.eAllReferences.find{|r| r.name == "biRef1"}
+ assert_not_nil biRef1
+ assert_equal class2, biRef1.eType
+ biRef1Opp = class2.eAllReferences.find {|r| r.name == "testClass1"}
+ assert_not_nil biRef1Opp
+ assert_equal class1, biRef1Opp.eType
+ assert_equal biRef1Opp, biRef1.eOpposite
+ assert_equal biRef1, biRef1Opp.eOpposite
+
+ # TestClass1.testClass2
+ tc2Ref = class1.eAllReferences.find{|r| r.name == "testClass2"}
+ assert_not_nil tc2Ref
+ assert_equal class2, tc2Ref.eType
+ assert tc2Ref.containment
+ assert_equal -1, tc2Ref.upperBound
+ tc2RefOpp = class2.eAllReferences.find{|r| r.name == "tc1Parent"}
+ assert_not_nil tc2RefOpp
+ assert_equal class1, tc2RefOpp.eType
+ assert !tc2RefOpp.containment
+ assert_equal 1, tc2RefOpp.upperBound
+ end
+
+end \ No newline at end of file
diff --git a/lib/puppet/vendor/rgen/test/model_builder/ecore_original.rb b/lib/puppet/vendor/rgen/test/model_builder/ecore_original.rb
new file mode 100644
index 000000000..bf3a1a5a1
--- /dev/null
+++ b/lib/puppet/vendor/rgen/test/model_builder/ecore_original.rb
@@ -0,0 +1,163 @@
+ePackage "ecore", :nsPrefix => "ecore", :nsURI => "http://www.eclipse.org/emf/2002/Ecore" do
+ eClass "EAttribute", :eSuperTypes => ["EStructuralFeature"] do
+ eAttribute "iD"
+ eReference "eAttributeType", :changeable => false, :derived => true, :transient => true, :volatile => true, :lowerBound => 1, :eType => "EDataType"
+ end
+ eClass "EAnnotation", :eSuperTypes => ["EModelElement"] do
+ eAttribute "source"
+ eReference "details", :containment => true, :resolveProxies => false, :upperBound => -1, :eType => "EStringToStringMapEntry"
+ eReference "eModelElement", :resolveProxies => false, :eOpposite => "EModelElement.eAnnotations", :transient => true, :eType => "EModelElement"
+ eReference "contents", :containment => true, :resolveProxies => false, :upperBound => -1, :eType => "EObject"
+ eReference "references", :upperBound => -1, :eType => "EObject"
+ end
+ eClass "EClass", :eSuperTypes => ["EClassifier"] do
+ eAttribute "abstract"
+ eAttribute "interface"
+ eReference "eSuperTypes", :unsettable => true, :upperBound => -1, :eType => "EClass"
+ eReference "eOperations", :containment => true, :resolveProxies => false, :eOpposite => "EOperation.eContainingClass", :upperBound => -1, :eType => "EOperation"
+ eReference "eAllAttributes", :changeable => false, :derived => true, :transient => true, :volatile => true, :upperBound => -1, :eType => "EAttribute"
+ eReference "eAllReferences", :changeable => false, :derived => true, :transient => true, :volatile => true, :upperBound => -1, :eType => "EReference"
+ eReference "eReferences", :changeable => false, :derived => true, :transient => true, :volatile => true, :upperBound => -1, :eType => "EReference"
+ eReference "eAttributes", :changeable => false, :derived => true, :transient => true, :volatile => true, :upperBound => -1, :eType => "EAttribute"
+ eReference "eAllContainments", :changeable => false, :derived => true, :transient => true, :volatile => true, :upperBound => -1, :eType => "EReference"
+ eReference "eAllOperations", :changeable => false, :derived => true, :transient => true, :volatile => true, :upperBound => -1, :eType => "EOperation"
+ eReference "eAllStructuralFeatures", :changeable => false, :derived => true, :transient => true, :volatile => true, :upperBound => -1, :eType => "EStructuralFeature"
+ eReference "eAllSuperTypes", :changeable => false, :derived => true, :transient => true, :volatile => true, :upperBound => -1, :eType => "EClass"
+ eReference "eIDAttribute", :resolveProxies => false, :changeable => false, :derived => true, :transient => true, :volatile => true, :eType => "EAttribute"
+ eReference "eStructuralFeatures", :containment => true, :resolveProxies => false, :eOpposite => "EStructuralFeature.eContainingClass", :upperBound => -1, :eType => "EStructuralFeature"
+ eReference "eGenericSuperTypes", :containment => true, :resolveProxies => false, :unsettable => true, :upperBound => -1, :eType => "EGenericType"
+ eReference "eAllGenericSuperTypes", :changeable => false, :derived => true, :transient => true, :volatile => true, :upperBound => -1, :eType => "EGenericType"
+ end
+ eClass "EClassifier", :abstract => true, :eSuperTypes => ["ENamedElement"], :eSubTypes => ["EClass", "EDataType"] do
+ eAttribute "instanceClassName", :unsettable => true, :volatile => true
+ eAttribute "instanceClass", :changeable => false, :derived => true, :transient => true, :volatile => true
+ eAttribute "defaultValue", :changeable => false, :derived => true, :transient => true, :volatile => true, :eType => "EJavaObject"
+ eAttribute "instanceTypeName", :unsettable => true, :volatile => true
+ eReference "ePackage", :eOpposite => "EPackage.eClassifiers", :changeable => false, :transient => true, :eType => "EPackage"
+ eReference "eTypeParameters", :containment => true, :upperBound => -1, :eType => "ETypeParameter"
+ end
+ eClass "EDataType", :eSuperTypes => ["EClassifier"], :eSubTypes => ["EEnum"] do
+ eAttribute "serializable", :defaultValueLiteral => "true"
+ end
+ eClass "EEnum", :eSuperTypes => ["EDataType"] do
+ eReference "eLiterals", :containment => true, :resolveProxies => false, :eOpposite => "EEnumLiteral.eEnum", :upperBound => -1, :eType => "EEnumLiteral"
+ end
+ eClass "EEnumLiteral", :eSuperTypes => ["ENamedElement"] do
+ eAttribute "value"
+ eAttribute "instance", :transient => true, :eType => "EEnumerator"
+ eAttribute "literal"
+ eReference "eEnum", :resolveProxies => false, :eOpposite => "EEnum.eLiterals", :changeable => false, :transient => true, :eType => "EEnum"
+ end
+ eClass "EFactory", :eSuperTypes => ["EModelElement"] do
+ eReference "ePackage", :resolveProxies => false, :eOpposite => "EPackage.eFactoryInstance", :transient => true, :lowerBound => 1, :eType => "EPackage"
+ end
+ eClass "EModelElement", :abstract => true, :eSuperTypes => ["EObject"], :eSubTypes => ["EAnnotation", "EFactory", "ENamedElement"] do
+ eReference "eAnnotations", :containment => true, :resolveProxies => false, :eOpposite => "EAnnotation.eModelElement", :upperBound => -1, :eType => "EAnnotation"
+ end
+ eClass "ENamedElement", :abstract => true, :eSuperTypes => ["EModelElement"], :eSubTypes => ["EClassifier", "EEnumLiteral", "EPackage", "ETypedElement", "ETypeParameter"] do
+ eAttribute "name"
+ end
+ eClass "EObject", :eSubTypes => ["EModelElement", "EGenericType"]
+ eClass "EOperation", :eSuperTypes => ["ETypedElement"] do
+ eReference "eContainingClass", :resolveProxies => false, :eOpposite => "EClass.eOperations", :changeable => false, :transient => true, :eType => "EClass"
+ eReference "eTypeParameters", :containment => true, :upperBound => -1, :eType => "ETypeParameter"
+ eReference "eParameters", :containment => true, :resolveProxies => false, :eOpposite => "EParameter.eOperation", :upperBound => -1, :eType => "EParameter"
+ eReference "eExceptions", :unsettable => true, :upperBound => -1, :eType => "EClassifier"
+ eReference "eGenericExceptions", :containment => true, :resolveProxies => false, :unsettable => true, :upperBound => -1, :eType => "EGenericType"
+ end
+ eClass "EPackage", :eSuperTypes => ["ENamedElement"] do
+ eAttribute "nsURI"
+ eAttribute "nsPrefix"
+ eReference "eFactoryInstance", :resolveProxies => false, :eOpposite => "EFactory.ePackage", :transient => true, :lowerBound => 1, :eType => "EFactory"
+ eReference "eClassifiers", :containment => true, :eOpposite => "EClassifier.ePackage", :upperBound => -1, :eType => "EClassifier"
+ eReference "eSubpackages", :containment => true, :eOpposite => "eSuperPackage", :upperBound => -1, :eType => "EPackage"
+ eReference "eSuperPackage", :eOpposite => "eSubpackages", :changeable => false, :transient => true, :eType => "EPackage"
+ end
+ eClass "EParameter", :eSuperTypes => ["ETypedElement"] do
+ eReference "eOperation", :resolveProxies => false, :eOpposite => "EOperation.eParameters", :changeable => false, :transient => true, :eType => "EOperation"
+ end
+ eClass "EReference", :eSuperTypes => ["EStructuralFeature"] do
+ eAttribute "containment"
+ eAttribute "container", :changeable => false, :derived => true, :transient => true, :volatile => true
+ eAttribute "resolveProxies", :defaultValueLiteral => "true"
+ eReference "eOpposite", :eType => "EReference"
+ eReference "eReferenceType", :changeable => false, :derived => true, :transient => true, :volatile => true, :lowerBound => 1, :eType => "EClass"
+ eReference "eKeys", :upperBound => -1, :eType => "EAttribute"
+ end
+ eClass "EStructuralFeature", :abstract => true, :eSuperTypes => ["ETypedElement"], :eSubTypes => ["EAttribute", "EReference"] do
+ eAttribute "changeable", :defaultValueLiteral => "true"
+ eAttribute "volatile"
+ eAttribute "transient"
+ eAttribute "defaultValueLiteral"
+ eAttribute "defaultValue", :changeable => false, :derived => true, :transient => true, :volatile => true, :eType => "EJavaObject"
+ eAttribute "unsettable"
+ eAttribute "derived"
+ eReference "eContainingClass", :resolveProxies => false, :eOpposite => "EClass.eStructuralFeatures", :changeable => false, :transient => true, :eType => "EClass"
+ end
+ eClass "ETypedElement", :abstract => true, :eSuperTypes => ["ENamedElement"], :eSubTypes => ["EOperation", "EParameter", "EStructuralFeature"] do
+ eAttribute "ordered", :defaultValueLiteral => "true"
+ eAttribute "unique", :defaultValueLiteral => "true"
+ eAttribute "lowerBound"
+ eAttribute "upperBound", :defaultValueLiteral => "1"
+ eAttribute "many", :changeable => false, :derived => true, :transient => true, :volatile => true
+ eAttribute "required", :changeable => false, :derived => true, :transient => true, :volatile => true
+ eReference "eType", :unsettable => true, :volatile => true, :eType => "EClassifier"
+ eReference "eGenericType", :containment => true, :resolveProxies => false, :unsettable => true, :volatile => true, :eType => "EGenericType"
+ end
+ eDataType "EBigDecimal", :instanceClassName => "java.math.BigDecimal"
+ eDataType "EBigInteger", :instanceClassName => "java.math.BigInteger"
+ eDataType "EBoolean", :instanceClassName => "boolean"
+ eDataType "EBooleanObject", :instanceClassName => "java.lang.Boolean"
+ eDataType "EByte", :instanceClassName => "byte"
+ eDataType "EByteArray", :instanceClassName => "byte[]"
+ eDataType "EByteObject", :instanceClassName => "java.lang.Byte"
+ eDataType "EChar", :instanceClassName => "char"
+ eDataType "ECharacterObject", :instanceClassName => "java.lang.Character"
+ eDataType "EDate", :instanceClassName => "java.util.Date"
+ eDataType "EDiagnosticChain", :serializable => false, :instanceClassName => "org.eclipse.emf.common.util.DiagnosticChain"
+ eDataType "EDouble", :instanceClassName => "double"
+ eDataType "EDoubleObject", :instanceClassName => "java.lang.Double"
+ eDataType "EEList", :serializable => false, :instanceClassName => "org.eclipse.emf.common.util.EList" do
+ eTypeParameter "E"
+ end
+ eDataType "EEnumerator", :serializable => false, :instanceClassName => "org.eclipse.emf.common.util.Enumerator"
+ eDataType "EFeatureMap", :serializable => false, :instanceClassName => "org.eclipse.emf.ecore.util.FeatureMap"
+ eDataType "EFeatureMapEntry", :serializable => false, :instanceClassName => "org.eclipse.emf.ecore.util.FeatureMap$Entry"
+ eDataType "EFloat", :instanceClassName => "float"
+ eDataType "EFloatObject", :instanceClassName => "java.lang.Float"
+ eDataType "EInt", :instanceClassName => "int"
+ eDataType "EIntegerObject", :instanceClassName => "java.lang.Integer"
+ eDataType "EJavaClass", :instanceClassName => "java.lang.Class" do
+ eTypeParameter "T"
+ end
+ eDataType "EJavaObject", :instanceClassName => "java.lang.Object"
+ eDataType "ELong", :instanceClassName => "long"
+ eDataType "ELongObject", :instanceClassName => "java.lang.Long"
+ eDataType "EMap", :serializable => false, :instanceClassName => "java.util.Map" do
+ eTypeParameter "K"
+ eTypeParameter "V"
+ end
+ eDataType "EResource", :serializable => false, :instanceClassName => "org.eclipse.emf.ecore.resource.Resource"
+ eDataType "EResourceSet", :serializable => false, :instanceClassName => "org.eclipse.emf.ecore.resource.ResourceSet"
+ eDataType "EShort", :instanceClassName => "short"
+ eDataType "EShortObject", :instanceClassName => "java.lang.Short"
+ eDataType "EString", :instanceClassName => "java.lang.String"
+ eClass "EStringToStringMapEntry", :instanceClassName => "java.util.Map$Entry" do
+ eAttribute "key"
+ eAttribute "value"
+ end
+ eDataType "ETreeIterator", :serializable => false, :instanceClassName => "org.eclipse.emf.common.util.TreeIterator" do
+ eTypeParameter "E"
+ end
+ eClass "EGenericType", :eSuperTypes => ["EObject"] do
+ eReference "eUpperBound", :containment => true, :resolveProxies => false, :eType => "EGenericType"
+ eReference "eTypeArguments", :containment => true, :resolveProxies => false, :upperBound => -1, :eType => "EGenericType"
+ eReference "eRawType", :changeable => false, :derived => true, :transient => true, :lowerBound => 1, :eType => "EClassifier"
+ eReference "eLowerBound", :containment => true, :resolveProxies => false, :eType => "EGenericType"
+ eReference "eTypeParameter", :resolveProxies => false, :eType => "ETypeParameter"
+ eReference "eClassifier", :eType => "EClassifier"
+ end
+ eClass "ETypeParameter", :eSuperTypes => ["ENamedElement"] do
+ eReference "eBounds", :containment => true, :resolveProxies => false, :upperBound => -1, :eType => "EGenericType"
+ end
+end
diff --git a/lib/puppet/vendor/rgen/test/model_builder/ecore_original_regenerated.rb b/lib/puppet/vendor/rgen/test/model_builder/ecore_original_regenerated.rb
new file mode 100644
index 000000000..1eda80f6c
--- /dev/null
+++ b/lib/puppet/vendor/rgen/test/model_builder/ecore_original_regenerated.rb
@@ -0,0 +1,163 @@
+ePackage "ecore", :nsPrefix => "ecore", :nsURI => "http://www.eclipse.org/emf/2002/Ecore" do
+ eClass "EAttribute" do
+ eAttribute "iD"
+ eReference "eAttributeType", :changeable => false, :derived => true, :transient => true, :volatile => true, :lowerBound => 1
+ end
+ eClass "EAnnotation" do
+ eAttribute "source"
+ eReference "details", :containment => true, :resolveProxies => false, :upperBound => -1
+ eReference "eModelElement", :resolveProxies => false, :transient => true
+ eReference "contents", :containment => true, :resolveProxies => false, :upperBound => -1
+ eReference "references", :upperBound => -1
+ end
+ eClass "EClass" do
+ eAttribute "abstract"
+ eAttribute "interface"
+ eReference "eSuperTypes", :unsettable => true, :upperBound => -1
+ eReference "eOperations", :containment => true, :resolveProxies => false, :upperBound => -1
+ eReference "eAllAttributes", :changeable => false, :derived => true, :transient => true, :volatile => true, :upperBound => -1
+ eReference "eAllReferences", :changeable => false, :derived => true, :transient => true, :volatile => true, :upperBound => -1
+ eReference "eReferences", :changeable => false, :derived => true, :transient => true, :volatile => true, :upperBound => -1
+ eReference "eAttributes", :changeable => false, :derived => true, :transient => true, :volatile => true, :upperBound => -1
+ eReference "eAllContainments", :changeable => false, :derived => true, :transient => true, :volatile => true, :upperBound => -1
+ eReference "eAllOperations", :changeable => false, :derived => true, :transient => true, :volatile => true, :upperBound => -1
+ eReference "eAllStructuralFeatures", :changeable => false, :derived => true, :transient => true, :volatile => true, :upperBound => -1
+ eReference "eAllSuperTypes", :changeable => false, :derived => true, :transient => true, :volatile => true, :upperBound => -1
+ eReference "eIDAttribute", :resolveProxies => false, :changeable => false, :derived => true, :transient => true, :volatile => true
+ eReference "eStructuralFeatures", :containment => true, :resolveProxies => false, :upperBound => -1
+ eReference "eGenericSuperTypes", :containment => true, :resolveProxies => false, :unsettable => true, :upperBound => -1
+ eReference "eAllGenericSuperTypes", :changeable => false, :derived => true, :transient => true, :volatile => true, :upperBound => -1
+ end
+ eClass "EClassifier", :abstract => true do
+ eAttribute "instanceClassName", :unsettable => true, :volatile => true
+ eAttribute "instanceClass", :changeable => false, :derived => true, :transient => true, :volatile => true
+ eAttribute "defaultValue", :changeable => false, :derived => true, :transient => true, :volatile => true
+ eAttribute "instanceTypeName", :unsettable => true, :volatile => true
+ eReference "ePackage", :changeable => false, :transient => true
+ eReference "eTypeParameters", :containment => true, :upperBound => -1
+ end
+ eClass "EDataType" do
+ eAttribute "serializable", :defaultValueLiteral => "true"
+ end
+ eClass "EEnum" do
+ eReference "eLiterals", :containment => true, :resolveProxies => false, :upperBound => -1
+ end
+ eClass "EEnumLiteral" do
+ eAttribute "value"
+ eAttribute "instance", :transient => true
+ eAttribute "literal"
+ eReference "eEnum", :resolveProxies => false, :changeable => false, :transient => true
+ end
+ eClass "EFactory" do
+ eReference "ePackage", :resolveProxies => false, :transient => true, :lowerBound => 1
+ end
+ eClass "EModelElement", :abstract => true do
+ eReference "eAnnotations", :containment => true, :resolveProxies => false, :upperBound => -1
+ end
+ eClass "ENamedElement", :abstract => true do
+ eAttribute "name"
+ end
+ eClass "EObject"
+ eClass "EOperation" do
+ eReference "eContainingClass", :resolveProxies => false, :changeable => false, :transient => true
+ eReference "eTypeParameters", :containment => true, :upperBound => -1
+ eReference "eParameters", :containment => true, :resolveProxies => false, :upperBound => -1
+ eReference "eExceptions", :unsettable => true, :upperBound => -1
+ eReference "eGenericExceptions", :containment => true, :resolveProxies => false, :unsettable => true, :upperBound => -1
+ end
+ eClass "EPackage" do
+ eAttribute "nsURI"
+ eAttribute "nsPrefix"
+ eReference "eFactoryInstance", :resolveProxies => false, :transient => true, :lowerBound => 1
+ eReference "eClassifiers", :containment => true, :upperBound => -1
+ eReference "eSubpackages", :containment => true, :upperBound => -1
+ eReference "eSuperPackage", :changeable => false, :transient => true
+ end
+ eClass "EParameter" do
+ eReference "eOperation", :resolveProxies => false, :changeable => false, :transient => true
+ end
+ eClass "EReference" do
+ eAttribute "containment"
+ eAttribute "container", :changeable => false, :derived => true, :transient => true, :volatile => true
+ eAttribute "resolveProxies", :defaultValueLiteral => "true"
+ eReference "eOpposite"
+ eReference "eReferenceType", :changeable => false, :derived => true, :transient => true, :volatile => true, :lowerBound => 1
+ eReference "eKeys", :upperBound => -1
+ end
+ eClass "EStructuralFeature", :abstract => true do
+ eAttribute "changeable", :defaultValueLiteral => "true"
+ eAttribute "volatile"
+ eAttribute "transient"
+ eAttribute "defaultValueLiteral"
+ eAttribute "defaultValue", :changeable => false, :derived => true, :transient => true, :volatile => true
+ eAttribute "unsettable"
+ eAttribute "derived"
+ eReference "eContainingClass", :resolveProxies => false, :changeable => false, :transient => true
+ end
+ eClass "ETypedElement", :abstract => true do
+ eAttribute "ordered", :defaultValueLiteral => "true"
+ eAttribute "unique", :defaultValueLiteral => "true"
+ eAttribute "lowerBound"
+ eAttribute "upperBound", :defaultValueLiteral => "1"
+ eAttribute "many", :changeable => false, :derived => true, :transient => true, :volatile => true
+ eAttribute "required", :changeable => false, :derived => true, :transient => true, :volatile => true
+ eReference "eType", :unsettable => true, :volatile => true
+ eReference "eGenericType", :containment => true, :resolveProxies => false, :unsettable => true, :volatile => true
+ end
+ eDataType "EBigDecimal", :instanceClassName => "java.math.BigDecimal"
+ eDataType "EBigInteger", :instanceClassName => "java.math.BigInteger"
+ eDataType "EBoolean", :instanceClassName => "boolean"
+ eDataType "EBooleanObject", :instanceClassName => "java.lang.Boolean"
+ eDataType "EByte", :instanceClassName => "byte"
+ eDataType "EByteArray", :instanceClassName => "byte[]"
+ eDataType "EByteObject", :instanceClassName => "java.lang.Byte"
+ eDataType "EChar", :instanceClassName => "char"
+ eDataType "ECharacterObject", :instanceClassName => "java.lang.Character"
+ eDataType "EDate", :instanceClassName => "java.util.Date"
+ eDataType "EDiagnosticChain", :serializable => false, :instanceClassName => "org.eclipse.emf.common.util.DiagnosticChain"
+ eDataType "EDouble", :instanceClassName => "double"
+ eDataType "EDoubleObject", :instanceClassName => "java.lang.Double"
+ eDataType "EEList", :serializable => false, :instanceClassName => "org.eclipse.emf.common.util.EList" do
+ eTypeParameter "E"
+ end
+ eDataType "EEnumerator", :serializable => false, :instanceClassName => "org.eclipse.emf.common.util.Enumerator"
+ eDataType "EFeatureMap", :serializable => false, :instanceClassName => "org.eclipse.emf.ecore.util.FeatureMap"
+ eDataType "EFeatureMapEntry", :serializable => false, :instanceClassName => "org.eclipse.emf.ecore.util.FeatureMap$Entry"
+ eDataType "EFloat", :instanceClassName => "float"
+ eDataType "EFloatObject", :instanceClassName => "java.lang.Float"
+ eDataType "EInt", :instanceClassName => "int"
+ eDataType "EIntegerObject", :instanceClassName => "java.lang.Integer"
+ eDataType "EJavaClass", :instanceClassName => "java.lang.Class" do
+ eTypeParameter "T"
+ end
+ eDataType "EJavaObject", :instanceClassName => "java.lang.Object"
+ eDataType "ELong", :instanceClassName => "long"
+ eDataType "ELongObject", :instanceClassName => "java.lang.Long"
+ eDataType "EMap", :serializable => false, :instanceClassName => "java.util.Map" do
+ eTypeParameter "K"
+ eTypeParameter "V"
+ end
+ eDataType "EResource", :serializable => false, :instanceClassName => "org.eclipse.emf.ecore.resource.Resource"
+ eDataType "EResourceSet", :serializable => false, :instanceClassName => "org.eclipse.emf.ecore.resource.ResourceSet"
+ eDataType "EShort", :instanceClassName => "short"
+ eDataType "EShortObject", :instanceClassName => "java.lang.Short"
+ eDataType "EString", :instanceClassName => "java.lang.String"
+ eClass "EStringToStringMapEntry", :instanceClassName => "java.util.Map$Entry" do
+ eAttribute "key"
+ eAttribute "value"
+ end
+ eDataType "ETreeIterator", :serializable => false, :instanceClassName => "org.eclipse.emf.common.util.TreeIterator" do
+ eTypeParameter "E"
+ end
+ eClass "EGenericType" do
+ eReference "eUpperBound", :containment => true, :resolveProxies => false
+ eReference "eTypeArguments", :containment => true, :resolveProxies => false, :upperBound => -1
+ eReference "eRawType", :changeable => false, :derived => true, :transient => true, :lowerBound => 1
+ eReference "eLowerBound", :containment => true, :resolveProxies => false
+ eReference "eTypeParameter", :resolveProxies => false
+ eReference "eClassifier"
+ end
+ eClass "ETypeParameter" do
+ eReference "eBounds", :containment => true, :resolveProxies => false, :upperBound => -1
+ end
+end
diff --git a/lib/puppet/vendor/rgen/test/model_builder/reference_resolver_test.rb b/lib/puppet/vendor/rgen/test/model_builder/reference_resolver_test.rb
new file mode 100644
index 000000000..099335340
--- /dev/null
+++ b/lib/puppet/vendor/rgen/test/model_builder/reference_resolver_test.rb
@@ -0,0 +1,156 @@
+$:.unshift File.dirname(__FILE__)+"/../lib"
+
+require 'test/unit'
+require 'rgen/metamodel_builder'
+require 'rgen/model_builder/reference_resolver'
+
+class ReferenceResolverTest < Test::Unit::TestCase
+
+ class ClassA < RGen::MetamodelBuilder::MMBase
+ has_attr "name"
+ end
+
+ class ClassB < RGen::MetamodelBuilder::MMBase
+ has_attr "name"
+ end
+
+ class ClassC < RGen::MetamodelBuilder::MMBase
+ has_attr "name"
+ end
+
+ ClassA.contains_many 'childB', ClassB, 'parentA'
+ ClassB.contains_many 'childC', ClassC, 'parentB'
+ ClassA.has_one 'refC', ClassC
+ ClassB.has_one 'refC', ClassC
+ ClassC.has_many 'refCs', ClassC
+ ClassC.has_one 'refA', ClassA
+ ClassC.has_one 'refB', ClassB
+
+ def testModel
+ a1 = ClassA.new(:name => "a1")
+ a2 = ClassA.new(:name => "a2")
+ b1 = ClassB.new(:name => "b1", :parentA => a1)
+ b2 = ClassB.new(:name => "b2", :parentA => a1)
+ c1 = ClassC.new(:name => "c1", :parentB => b1)
+ c2 = ClassC.new(:name => "c2", :parentB => b1)
+ c3 = ClassC.new(:name => "c3", :parentB => b1)
+ [a1, a2, b1, b2, c1, c2, c3]
+ end
+
+ def setElementNames(resolver, elements)
+ elements.each do |e|
+ resolver.setElementName(e, e.name)
+ end
+ end
+
+ def createJob(hash)
+ raise "Invalid arguments" unless \
+ hash.is_a?(Hash) && (hash.keys & [:receiver, :reference, :namespace, :string]).size == 4
+ RGen::ModelBuilder::ReferenceResolver::ResolverJob.new(
+ hash[:receiver], hash[:reference], hash[:namespace], hash[:string])
+ end
+
+ def test_resolve_same_namespace
+ a1, a2, b1, b2, c1, c2, c3 = testModel
+
+ toplevelNamespace = [a1, a2]
+ resolver = RGen::ModelBuilder::ReferenceResolver.new
+ setElementNames(resolver, [a1, a2, b1, b2, c1, c2, c3])
+ resolver.addJob(createJob(
+ :receiver => c2,
+ :reference => ClassC.ecore.eReferences.find{|r| r.name == "refCs"},
+ :namespace => b1,
+ :string => "c1"))
+ resolver.addJob(createJob(
+ :receiver => b2,
+ :reference => ClassB.ecore.eReferences.find{|r| r.name == "refC"},
+ :namespace => a1,
+ :string => "b1.c1"))
+ resolver.addJob(createJob(
+ :receiver => a2,
+ :reference => ClassA.ecore.eReferences.find{|r| r.name == "refC"},
+ :namespace => nil,
+ :string => "a1.b1.c1"))
+ resolver.resolve(toplevelNamespace)
+
+ assert_equal [c1], c2.refCs
+ assert_equal c1, b2.refC
+ assert_equal c1, a2.refC
+ end
+
+ def test_resolve_parent_namespace
+ a1, a2, b1, b2, c1, c2, c3 = testModel
+
+ toplevelNamespace = [a1, a2]
+ resolver = RGen::ModelBuilder::ReferenceResolver.new
+ setElementNames(resolver, [a1, a2, b1, b2, c1, c2, c3])
+ resolver.addJob(createJob(
+ :receiver => c2,
+ :reference => ClassC.ecore.eReferences.find{|r| r.name == "refA"},
+ :namespace => b1,
+ :string => "a1"))
+ resolver.addJob(createJob(
+ :receiver => c2,
+ :reference => ClassC.ecore.eReferences.find{|r| r.name == "refB"},
+ :namespace => b1,
+ :string => "b1"))
+ resolver.addJob(createJob(
+ :receiver => c2,
+ :reference => ClassC.ecore.eReferences.find{|r| r.name == "refCs"},
+ :namespace => b1,
+ :string => "b1.c1"))
+ resolver.addJob(createJob(
+ :receiver => c2,
+ :reference => ClassC.ecore.eReferences.find{|r| r.name == "refCs"},
+ :namespace => b1,
+ :string => "a1.b1.c3"))
+ resolver.resolve(toplevelNamespace)
+
+ assert_equal a1, c2.refA
+ assert_equal b1, c2.refB
+ assert_equal [c1, c3], c2.refCs
+ end
+
+ def test_resolve_faulty
+ a1, a2, b1, b2, c1, c2, c3 = testModel
+
+ toplevelNamespace = [a1, a2]
+ resolver = RGen::ModelBuilder::ReferenceResolver.new
+ setElementNames(resolver, [a1, a2, b1, b2, c1, c2, c3])
+ resolver.addJob(createJob(
+ :receiver => c2,
+ :reference => ClassC.ecore.eReferences.find{|r| r.name == "refCs"},
+ :namespace => b1,
+ :string => "b1.c5"))
+ assert_raise RGen::ModelBuilder::ReferenceResolver::ResolverException do
+ resolver.resolve(toplevelNamespace)
+ end
+ end
+
+ def test_ambiguous_prefix
+ a = ClassA.new(:name => "name1")
+ b1 = ClassB.new(:name => "name1", :parentA => a)
+ b2 = ClassB.new(:name => "target", :parentA => a)
+ c1 = ClassC.new(:name => "name21", :parentB => b1)
+ c2 = ClassC.new(:name => "name22", :parentB => b1)
+
+ toplevelNamespace = [a]
+ resolver = RGen::ModelBuilder::ReferenceResolver.new
+ setElementNames(resolver, [a, b1, b2, c1, c2])
+ resolver.addJob(createJob(
+ :receiver => c2,
+ :reference => ClassC.ecore.eReferences.find{|r| r.name == "refCs"},
+ :namespace => b1,
+ :string => "name1.name1.name21"))
+ resolver.addJob(createJob(
+ :receiver => c2,
+ :reference => ClassC.ecore.eReferences.find{|r| r.name == "refB"},
+ :namespace => b1,
+ :string => "name1.target"))
+ resolver.resolve(toplevelNamespace)
+
+ assert_equal [c1], c2.refCs
+ assert_equal b2, c2.refB
+ end
+
+end \ No newline at end of file
diff --git a/lib/puppet/vendor/rgen/test/model_builder/serializer_test.rb b/lib/puppet/vendor/rgen/test/model_builder/serializer_test.rb
new file mode 100644
index 000000000..46eab3d05
--- /dev/null
+++ b/lib/puppet/vendor/rgen/test/model_builder/serializer_test.rb
@@ -0,0 +1,94 @@
+$:.unshift File.dirname(__FILE__) + "/../lib"
+
+require 'test/unit'
+require 'rgen/ecore/ecore'
+
+# The following would also influence other tests...
+#
+#module RGen::ECore
+# class EGenericType < EObject
+# contains_many_uni 'eTypeArguments', EGenericType
+# end
+# class ETypeParameter < ENamedElement
+# end
+# class EClassifier
+# contains_many_uni 'eTypeParameters', ETypeParameter
+# end
+# class ETypedElement
+# has_one 'eGenericType', EGenericType
+# end
+#end
+#
+#RGen::ECore::ECoreInterface.clear_ecore_cache
+#RGen::ECore::EString.ePackage = RGen::ECore.ecore
+
+require 'rgen/environment'
+require 'rgen/model_builder/model_serializer'
+require 'rgen/instantiator/ecore_xml_instantiator'
+require 'rgen/model_builder'
+require 'model_builder/statemachine_metamodel'
+
+class ModelSerializerTest < Test::Unit::TestCase
+ def test_ecore_internal
+ File.open(File.dirname(__FILE__)+"/ecore_internal.rb","w") do |f|
+ serializer = RGen::ModelBuilder::ModelSerializer.new(f, RGen::ECore.ecore)
+ serializer.serialize(RGen::ECore.ecore)
+ end
+ end
+
+ def test_roundtrip
+ model = %{\
+statemachine "Airconditioner" do
+ state "Off", :kind => :START
+ compositeState "On" do
+ state "Heating"
+ state "Cooling"
+ state "Dumm"
+ end
+ transition "_Transition1", :sourceState => "On.Cooling", :targetState => "On.Heating"
+ transition "_Transition2", :sourceState => "On.Heating", :targetState => "On.Cooling"
+end
+}
+ check_roundtrip(StatemachineMetamodel, model)
+ end
+
+ module AmbiguousRoleMM
+ extend RGen::MetamodelBuilder::ModuleExtension
+ class A < RGen::MetamodelBuilder::MMBase
+ end
+ class B < RGen::MetamodelBuilder::MMBase
+ end
+ class C < B
+ end
+ A.contains_many 'role1', B, 'back1'
+ A.contains_many 'role2', B, 'back2'
+ end
+
+ def test_roundtrip_ambiguous_role
+ model = %{\
+a "_A1" do
+ b "_B1", :as => :role1
+ b "_B2", :as => :role2
+ c "_C1", :as => :role2
+end
+}
+ check_roundtrip(AmbiguousRoleMM, model)
+ end
+
+ private
+
+ def build_model(mm, model)
+ RGen::ModelBuilder.build(mm) do
+ eval(model)
+ end
+ end
+
+ def check_roundtrip(mm, model)
+ sm = build_model(mm, model)
+ f = StringIO.new
+ serializer = RGen::ModelBuilder::ModelSerializer.new(f, mm.ecore)
+ serializer.serialize(sm)
+ assert_equal model, f.string
+ end
+
+end
diff --git a/lib/puppet/vendor/rgen/test/model_builder/statemachine_metamodel.rb b/lib/puppet/vendor/rgen/test/model_builder/statemachine_metamodel.rb
new file mode 100644
index 000000000..f7a42cb0e
--- /dev/null
+++ b/lib/puppet/vendor/rgen/test/model_builder/statemachine_metamodel.rb
@@ -0,0 +1,42 @@
+# a test metamodel used by the following tests
+module StatemachineMetamodel
+ extend RGen::MetamodelBuilder::ModuleExtension
+
+ module Condition
+ extend RGen::MetamodelBuilder::ModuleExtension
+
+ class Condition < RGen::MetamodelBuilder::MMBase
+ end
+
+ module TimeCondition
+ extend RGen::MetamodelBuilder::ModuleExtension
+
+ class TimeCondition < Condition
+ has_attr 'timeout', Integer
+ end
+ end
+ end
+
+ class Statemachine < RGen::MetamodelBuilder::MMBase
+ has_attr 'name'
+ end
+
+ class State < RGen::MetamodelBuilder::MMBase
+ has_attr 'name'
+ has_attr 'kind', RGen::MetamodelBuilder::DataTypes::Enum.new([:START])
+ end
+
+ class CompositeState < State
+ has_attr 'name'
+ contains_many 'state', State, 'compositeState'
+ end
+
+ class Transition < RGen::MetamodelBuilder::MMBase
+ many_to_one 'sourceState', State, 'outgoingTransition'
+ many_to_one 'targetState', State, 'incomingTransition'
+ has_many 'condition', Condition::Condition
+ end
+
+ Statemachine.contains_many 'state', State, 'statemachine'
+ Statemachine.contains_many 'transition', Transition, 'statemachine'
+end
diff --git a/lib/puppet/vendor/rgen/test/model_builder/test_model/statemachine1.rb b/lib/puppet/vendor/rgen/test/model_builder/test_model/statemachine1.rb
new file mode 100644
index 000000000..9f44b7018
--- /dev/null
+++ b/lib/puppet/vendor/rgen/test/model_builder/test_model/statemachine1.rb
@@ -0,0 +1,23 @@
+statemachine "Airconditioner" do
+ state "Off", :kind => :START
+ compositeState "On" do
+ state "Heating" do
+ transition :as => :outgoingTransition, :targetState => "Cooling",
+ :statemachine => "Airconditioner"
+ end
+ state "Cooling" do
+ end
+ end
+ transition :sourceState => "On.Cooling", :targetState => "On.Heating" do
+ _using Condition::TimeCondition do
+ timeCondition :as => :condition, :timeout => 100
+ end
+ Condition::TimeCondition.timeCondition :as => :condition, :timeout => 10
+ end
+end
+_using Condition do
+ statemachine "AirconExtension" do
+ s = state "StartState"
+ transition :sourceState => s, :targetState => "Airconditioner.Off"
+ end
+end
diff --git a/lib/puppet/vendor/rgen/test/model_builder_test.rb b/lib/puppet/vendor/rgen/test/model_builder_test.rb
new file mode 100644
index 000000000..a8e550c88
--- /dev/null
+++ b/lib/puppet/vendor/rgen/test/model_builder_test.rb
@@ -0,0 +1,6 @@
+$:.unshift File.dirname(__FILE__) + "/../lib"
+
+require 'model_builder/builder_test'
+require 'model_builder/serializer_test'
+require 'model_builder/builder_context_test'
+require 'model_builder/reference_resolver_test'
diff --git a/lib/puppet/vendor/rgen/test/model_fragment_test.rb b/lib/puppet/vendor/rgen/test/model_fragment_test.rb
new file mode 100644
index 000000000..deadf587d
--- /dev/null
+++ b/lib/puppet/vendor/rgen/test/model_fragment_test.rb
@@ -0,0 +1,30 @@
+$:.unshift File.join(File.dirname(__FILE__),"..","lib")
+
+require 'test/unit'
+require 'rgen/metamodel_builder'
+require 'rgen/fragment/model_fragment'
+
+class ModelFragmentTest < Test::Unit::TestCase
+
+module TestMetamodel
+ extend RGen::MetamodelBuilder::ModuleExtension
+
+ class SimpleClass < RGen::MetamodelBuilder::MMBase
+ has_attr 'name', String
+ contains_many 'subclass', SimpleClass, 'parent'
+ end
+end
+
+def test_elements
+ root = TestMetamodel::SimpleClass.new(:name => "parent",
+ :subclass => [TestMetamodel::SimpleClass.new(:name => "child")])
+
+ frag = RGen::Fragment::ModelFragment.new("location")
+ frag.set_root_elements([root])
+
+ assert_equal 2, frag.elements.size
+end
+
+end
+
+
diff --git a/lib/puppet/vendor/rgen/test/output_handler_test.rb b/lib/puppet/vendor/rgen/test/output_handler_test.rb
new file mode 100644
index 000000000..f00a8af71
--- /dev/null
+++ b/lib/puppet/vendor/rgen/test/output_handler_test.rb
@@ -0,0 +1,58 @@
+$:.unshift File.join(File.dirname(__FILE__),"..","lib")
+
+require 'test/unit'
+require 'rgen/template_language/output_handler'
+
+class MetamodelBuilderTest < Test::Unit::TestCase
+ def test_direct_nl
+ h = RGen::TemplateLanguage::OutputHandler.new
+ h.mode = :direct
+ h << "Test"
+ h.ignoreNextNL
+ h << "\nContent"
+ assert_equal "TestContent", h.to_s
+ end
+ def test_direct_ws
+ h = RGen::TemplateLanguage::OutputHandler.new
+ h.mode = :direct
+ h << "Test"
+ h.ignoreNextWS
+ h << " \n Content"
+ assert_equal "TestContent", h.to_s
+ end
+ def test_explicit_indent
+ h = RGen::TemplateLanguage::OutputHandler.new
+ h.mode = :explicit
+ h.indent = 1
+ h << "Start"
+ h << " \n "
+ h << "Test"
+ h << " \n \n Content"
+ assert_equal " Start\n Test\n Content", h.to_s
+ end
+ def test_explicit_endswithws
+ h = RGen::TemplateLanguage::OutputHandler.new
+ h.mode = :explicit
+ h.indent = 1
+ h << "Start \n\n"
+ assert_equal " Start\n", h.to_s
+ end
+ def test_performance
+ h = RGen::TemplateLanguage::OutputHandler.new
+ h.mode = :explicit
+ h.indent = 1
+ line = (1..50).collect{|w| "someword"}.join(" ")+"\n"
+ # repeat more often to make performance differences visible
+ 20.times do
+ h << line
+ end
+ end
+ def test_indent_string
+ h = RGen::TemplateLanguage::OutputHandler.new(1, "\t", :explicit)
+ h << "Start"
+ h << " \n "
+ h << "Test"
+ h << " \n \n Content"
+ assert_equal "\tStart\n\tTest\n\tContent", h.to_s
+ end
+end \ No newline at end of file
diff --git a/lib/puppet/vendor/rgen/test/qualified_name_provider_test.rb b/lib/puppet/vendor/rgen/test/qualified_name_provider_test.rb
new file mode 100644
index 000000000..c59cfce47
--- /dev/null
+++ b/lib/puppet/vendor/rgen/test/qualified_name_provider_test.rb
@@ -0,0 +1,48 @@
+$:.unshift File.join(File.dirname(__FILE__),"..","lib")
+
+require 'test/unit'
+require 'rgen/metamodel_builder'
+require 'rgen/serializer/qualified_name_provider'
+
+class QualifiedNameProviderTest < Test::Unit::TestCase
+
+ class AbstractTestNode < RGen::MetamodelBuilder::MMBase
+ contains_many 'children', AbstractTestNode, "parent"
+ end
+
+ class NamedNode < AbstractTestNode
+ has_attr 'n', String
+ end
+
+ class UnnamedNode < AbstractTestNode
+ end
+
+ def test_simple
+ root = NamedNode.new(:n => "root", :children => [
+ NamedNode.new(:n => "a", :children => [
+ NamedNode.new(:n => "a1")
+ ]),
+ UnnamedNode.new(:children => [
+ NamedNode.new(:n => "b1")
+ ])
+ ])
+
+ qnp = RGen::Serializer::QualifiedNameProvider.new(:attribute_name => "n")
+
+ assert_equal "/root", qnp.identifier(root)
+ assert_equal "/root/a", qnp.identifier(root.children[0])
+ assert_equal "/root/a/a1", qnp.identifier(root.children[0].children[0])
+ assert_equal "/root", qnp.identifier(root.children[1])
+ assert_equal "/root/b1", qnp.identifier(root.children[1].children[0])
+ end
+
+ def test_unnamed_root
+ root = UnnamedNode.new
+
+ qnp = RGen::Serializer::QualifiedNameProvider.new(:attribute_name => "n")
+
+ assert_equal "/", qnp.identifier(root)
+ end
+
+end
+
diff --git a/lib/puppet/vendor/rgen/test/qualified_name_resolver_test.rb b/lib/puppet/vendor/rgen/test/qualified_name_resolver_test.rb
new file mode 100644
index 000000000..f14929226
--- /dev/null
+++ b/lib/puppet/vendor/rgen/test/qualified_name_resolver_test.rb
@@ -0,0 +1,102 @@
+$:.unshift File.join(File.dirname(__FILE__),"..","lib")
+
+require 'test/unit'
+require 'rgen/metamodel_builder'
+require 'rgen/instantiator/qualified_name_resolver'
+
+class QualifiedNameResolverTest < Test::Unit::TestCase
+
+ class TestNode < RGen::MetamodelBuilder::MMBase
+ has_attr 'name', String
+ has_one 'nextSibling', TestNode
+ contains_many 'children', TestNode, "parent"
+ end
+
+ class TestNode2 < RGen::MetamodelBuilder::MMBase
+ has_attr 'cname', String
+ has_one 'nextSibling', TestNode2
+ contains_many 'children', TestNode2, "parent"
+ end
+
+ class TestNode3 < RGen::MetamodelBuilder::MMBase
+ has_attr 'name', String
+ contains_one 'child', TestNode3, "parent"
+ end
+
+ def testModel
+ [TestNode.new(:name => "Root1", :children => [
+ TestNode.new(:name => "Sub11"),
+ TestNode.new(:name => "Sub12", :children => [
+ TestNode.new(:name => "Sub121")])]),
+ TestNode.new(:name => "Root2", :children => [
+ TestNode.new(:name => "Sub21", :children => [
+ TestNode.new(:name => "Sub211")])]),
+ TestNode.new(:name => "Root3"),
+ TestNode.new(:name => "Root3")
+ ]
+ end
+
+ def testModel2
+ [TestNode2.new(:cname => "Root1", :children => [
+ TestNode2.new(:cname => "Sub11")])]
+ end
+
+ def testModel3
+ [TestNode3.new(:name => "Root1", :child =>
+ TestNode3.new(:name => "Sub11", :child =>
+ TestNode3.new(:name => "Sub111")))]
+ end
+
+ def test_customNameAttribute
+ model = testModel2
+ res = RGen::Instantiator::QualifiedNameResolver.new(model, :nameAttribute => "cname")
+ assert_equal model[0], res.resolveIdentifier("/Root1")
+ assert_equal model[0].children[0], res.resolveIdentifier("/Root1/Sub11")
+ end
+
+ def test_customSeparator
+ model = testModel
+ res = RGen::Instantiator::QualifiedNameResolver.new(model, :separator => "|")
+ assert_equal model[0], res.resolveIdentifier("|Root1")
+ assert_nil res.resolveIdentifier("/Root1")
+ assert_equal model[0].children[0], res.resolveIdentifier("|Root1|Sub11")
+ end
+
+ def test_noLeadingSeparator
+ model = testModel
+ res = RGen::Instantiator::QualifiedNameResolver.new(model, :leadingSeparator => false)
+ assert_equal model[0], res.resolveIdentifier("Root1")
+ assert_nil res.resolveIdentifier("/Root1")
+ assert_equal model[0].children[0], res.resolveIdentifier("Root1/Sub11")
+ end
+
+ def test_resolve
+ model = testModel
+ res = RGen::Instantiator::QualifiedNameResolver.new(model)
+ assert_equal model[0], res.resolveIdentifier("/Root1")
+ # again
+ assert_equal model[0], res.resolveIdentifier("/Root1")
+ assert_equal model[0].children[0], res.resolveIdentifier("/Root1/Sub11")
+ # again
+ assert_equal model[0].children[0], res.resolveIdentifier("/Root1/Sub11")
+ assert_equal model[0].children[1], res.resolveIdentifier("/Root1/Sub12")
+ assert_equal model[0].children[1].children[0], res.resolveIdentifier("/Root1/Sub12/Sub121")
+ assert_equal model[1], res.resolveIdentifier("/Root2")
+ assert_equal model[1].children[0], res.resolveIdentifier("/Root2/Sub21")
+ assert_equal model[1].children[0].children[0], res.resolveIdentifier("/Root2/Sub21/Sub211")
+ # duplicate name yields two result elements
+ assert_equal [model[2], model[3]], res.resolveIdentifier("/Root3")
+ assert_equal nil, res.resolveIdentifier("/RootX")
+ assert_equal nil, res.resolveIdentifier("/Root1/SubX")
+ end
+
+ def test_oneChild
+ model = testModel3
+ res = RGen::Instantiator::QualifiedNameResolver.new(model)
+ assert_equal model[0], res.resolveIdentifier("/Root1")
+ assert_equal model[0].child, res.resolveIdentifier("/Root1/Sub11")
+ assert_equal model[0].child.child, res.resolveIdentifier("/Root1/Sub11/Sub111")
+ end
+
+end
+
diff --git a/lib/puppet/vendor/rgen/test/reference_resolver_test.rb b/lib/puppet/vendor/rgen/test/reference_resolver_test.rb
new file mode 100644
index 000000000..b3dd58d73
--- /dev/null
+++ b/lib/puppet/vendor/rgen/test/reference_resolver_test.rb
@@ -0,0 +1,117 @@
+$:.unshift File.join(File.dirname(__FILE__),"..","lib")
+
+require 'test/unit'
+require 'rgen/metamodel_builder'
+require 'rgen/instantiator/reference_resolver'
+
+class ReferenceResolverTest < Test::Unit::TestCase
+
+ class TestNode < RGen::MetamodelBuilder::MMBase
+ has_attr 'name', String
+ has_one 'other', TestNode
+ has_many 'others', TestNode
+ end
+
+ class TestNode2 < RGen::MetamodelBuilder::MMBase
+ has_attr 'name', String
+ end
+
+ def test_identifier_resolver
+ nodeA, nodeB, nodeC, unresolved_refs = create_model
+ resolver = RGen::Instantiator::ReferenceResolver.new(
+ :identifier_resolver => proc do |ident|
+ {:a => nodeA, :b => nodeB, :c => nodeC}[ident]
+ end)
+ urefs = resolver.resolve(unresolved_refs)
+ check_model(nodeA, nodeB, nodeC)
+ assert urefs.empty?
+ end
+
+ def test_add_identifier
+ nodeA, nodeB, nodeC, unresolved_refs = create_model
+ resolver = RGen::Instantiator::ReferenceResolver.new
+ resolver.add_identifier(:a, nodeA)
+ resolver.add_identifier(:b, nodeB)
+ resolver.add_identifier(:c, nodeC)
+ urefs = resolver.resolve(unresolved_refs)
+ check_model(nodeA, nodeB, nodeC)
+ assert urefs.empty?
+ end
+
+ def test_problems
+ nodeA, nodeB, nodeC, unresolved_refs = create_model
+ resolver = RGen::Instantiator::ReferenceResolver.new(
+ :identifier_resolver => proc do |ident|
+ {:a => [nodeA, nodeB], :c => nodeC}[ident]
+ end)
+ problems = []
+ urefs = resolver.resolve(unresolved_refs, :problems => problems)
+ assert_equal ["identifier b not found", "identifier a not uniq"], problems
+ assert_equal 2, urefs.size
+ assert urefs.all?{|ur| !ur.target_type_error}
+ end
+
+ def test_on_resolve_proc
+ nodeA, nodeB, nodeC, unresolved_refs = create_model
+ resolver = RGen::Instantiator::ReferenceResolver.new
+ resolver.add_identifier(:a, nodeA)
+ resolver.add_identifier(:b, nodeB)
+ resolver.add_identifier(:c, nodeC)
+ data = []
+ resolver.resolve(unresolved_refs,
+ :on_resolve => proc {|ur, e| data << [ ur, e ]})
+ assert data[0][0].is_a?(RGen::Instantiator::ReferenceResolver::UnresolvedReference)
+ assert_equal nodeA, data[0][0].element
+ assert_equal "other", data[0][0].feature_name
+ assert_equal :b, data[0][0].proxy.targetIdentifier
+ assert_equal nodeB, data[0][1]
+ end
+
+ def test_target_type_error
+ nodeA, nodeB, nodeC, unresolved_refs = create_model
+ resolver = RGen::Instantiator::ReferenceResolver.new(
+ :identifier_resolver => proc do |ident|
+ {:a => TestNode2.new, :b => TestNode2.new, :c => nodeC}[ident]
+ end)
+ problems = []
+ urefs = resolver.resolve(unresolved_refs, :problems => problems)
+ assert_equal 2, problems.size
+ assert problems[0] =~ /invalid target type .*TestNode2/
+ assert problems[1] =~ /invalid target type .*TestNode2/
+ assert_equal 2, urefs.uniq.size
+ assert urefs[0].target_type_error
+ assert urefs[1].target_type_error
+ assert urefs.any?{|ur| ur.proxy.object_id == nodeA.other.object_id}
+ assert urefs.any?{|ur| ur.proxy.object_id == nodeB.others[0].object_id}
+ end
+
+ private
+
+ def create_model
+ nodeA = TestNode.new(:name => "NodeA")
+ nodeB = TestNode.new(:name => "NodeB")
+ nodeC = TestNode.new(:name => "NodeC")
+ bProxy = RGen::MetamodelBuilder::MMProxy.new(:b)
+ nodeA.other = bProxy
+ aProxy = RGen::MetamodelBuilder::MMProxy.new(:a)
+ cProxy = RGen::MetamodelBuilder::MMProxy.new(:c)
+ nodeB.others = [aProxy, cProxy]
+ unresolved_refs = [
+ RGen::Instantiator::ReferenceResolver::UnresolvedReference.new(nodeA, "other", bProxy),
+ RGen::Instantiator::ReferenceResolver::UnresolvedReference.new(nodeB, "others", aProxy),
+ RGen::Instantiator::ReferenceResolver::UnresolvedReference.new(nodeB, "others", cProxy)
+ ]
+ return nodeA, nodeB, nodeC, unresolved_refs
+ end
+
+ def check_model(nodeA, nodeB, nodeC)
+ assert_equal nodeB, nodeA.other
+ assert_equal [], nodeA.others
+ assert_equal nil, nodeB.other
+ assert_equal [nodeA, nodeC], nodeB.others
+ assert_equal nil, nodeC.other
+ assert_equal [], nodeC.others
+ end
+
+end
+
diff --git a/lib/puppet/vendor/rgen/test/rgen_test.rb b/lib/puppet/vendor/rgen/test/rgen_test.rb
new file mode 100644
index 000000000..68f1ac565
--- /dev/null
+++ b/lib/puppet/vendor/rgen/test/rgen_test.rb
@@ -0,0 +1,26 @@
+$:.unshift File.dirname(__FILE__)
+
+require 'test/unit'
+
+require 'array_extensions_test'
+require 'ea_instantiator_test'
+require 'ecore_self_test'
+require 'environment_test'
+require 'metamodel_builder_test'
+require 'metamodel_roundtrip_test'
+require 'output_handler_test'
+require 'template_language_test'
+require 'transformer_test'
+require 'xml_instantiator_test'
+require 'ea_serializer_test'
+require 'model_builder_test'
+require 'method_delegation_test'
+require 'json_test'
+require 'reference_resolver_test'
+require 'qualified_name_resolver_test'
+require 'metamodel_order_test'
+require 'metamodel_from_ecore_test'
+require 'util_test'
+require 'model_fragment_test'
+require 'qualified_name_provider_test'
+
diff --git a/lib/puppet/vendor/rgen/test/template_language_test.rb b/lib/puppet/vendor/rgen/test/template_language_test.rb
new file mode 100644
index 000000000..a1f14dda8
--- /dev/null
+++ b/lib/puppet/vendor/rgen/test/template_language_test.rb
@@ -0,0 +1,163 @@
+$:.unshift File.join(File.dirname(__FILE__),"..","lib")
+
+require 'test/unit'
+require 'rgen/template_language'
+require 'rgen/metamodel_builder'
+
+class TemplateContainerTest < Test::Unit::TestCase
+
+ TEMPLATES_DIR = File.dirname(__FILE__)+"/template_language_test/templates"
+ OUTPUT_DIR = File.dirname(__FILE__)+"/template_language_test"
+
+ module MyMM
+
+ class Chapter
+ attr_reader :title
+ def initialize(title)
+ @title = title
+ end
+ end
+
+ class Document
+ attr_reader :title, :authors, :chapters
+ attr_accessor :sampleArray
+ def initialize(title)
+ @title = title
+ @chapters = []
+ @authors = []
+ end
+ end
+
+ class Author
+ attr_reader :name, :email
+ def initialize(name, email)
+ @name, @email = name, email
+ end
+ end
+
+ end
+
+ module CCodeMM
+ class CArray < RGen::MetamodelBuilder::MMBase
+ has_attr 'name'
+ has_attr 'size', Integer
+ has_attr 'type'
+ end
+ class PrimitiveInitValue < RGen::MetamodelBuilder::MMBase
+ has_attr 'value', Integer
+ end
+ CArray.has_many 'initvalue', PrimitiveInitValue
+ end
+
+ TEST_MODEL = MyMM::Document.new("SomeDocument")
+ TEST_MODEL.authors << MyMM::Author.new("Martin", "martin@somewhe.re")
+ TEST_MODEL.authors << MyMM::Author.new("Otherguy", "other@somewhereel.se")
+ TEST_MODEL.chapters << MyMM::Chapter.new("Intro")
+ TEST_MODEL.chapters << MyMM::Chapter.new("MainPart")
+ TEST_MODEL.chapters << MyMM::Chapter.new("Summary")
+ TEST_MODEL.sampleArray = CCodeMM::CArray.new(:name => "myArray", :type => "int", :size => 5,
+ :initvalue => (1..5).collect { |v| CCodeMM::PrimitiveInitValue.new(:value => v) })
+
+ def test_with_model
+ tc = RGen::TemplateLanguage::DirectoryTemplateContainer.new([MyMM, CCodeMM], OUTPUT_DIR)
+ tc.load(TEMPLATES_DIR)
+ File.delete(OUTPUT_DIR+"/testout.txt") if File.exists? OUTPUT_DIR+"/testout.txt"
+ tc.expand('root::Root', :for => TEST_MODEL, :indent => 1)
+ result = expected = ""
+ File.open(OUTPUT_DIR+"/testout.txt") {|f| result = f.read}
+ File.open(OUTPUT_DIR+"/expected_result1.txt") {|f| expected = f.read}
+ assert_equal expected, result
+ end
+
+ def test_immediate_result
+ tc = RGen::TemplateLanguage::DirectoryTemplateContainer.new([MyMM, CCodeMM], OUTPUT_DIR)
+ tc.load(TEMPLATES_DIR)
+ expected = ""
+ File.open(OUTPUT_DIR+"/expected_result2.txt","rb") {|f| expected = f.read}
+ assert_equal expected, tc.expand('code/array::ArrayDefinition', :for => TEST_MODEL.sampleArray).to_s
+ end
+
+ def test_indent_string
+ tc = RGen::TemplateLanguage::DirectoryTemplateContainer.new([MyMM, CCodeMM], OUTPUT_DIR)
+ tc.load(TEMPLATES_DIR)
+ tc.indentString = " " # 2 spaces instead of 3 (default)
+ tc.expand('indent_string_test::IndentStringTest', :for => :dummy)
+ File.open(OUTPUT_DIR+"/indentStringTestDefaultIndent.out","rb") do |f|
+ assert_equal " <- your default here\r\n", f.read
+ end
+ File.open(OUTPUT_DIR+"/indentStringTestTabIndent.out","rb") do |f|
+ assert_equal "\t<- tab\r\n", f.read
+ end
+ end
+
+ def test_null_context
+ tc = RGen::TemplateLanguage::DirectoryTemplateContainer.new([MyMM, CCodeMM], OUTPUT_DIR)
+ tc.load(TEMPLATES_DIR)
+ assert_raise StandardError do
+ # the template must raise an exception because it calls expand :for => nil
+ tc.expand('null_context_test::NullContextTestBad', :for => :dummy)
+ end
+ assert_raise StandardError do
+ # the template must raise an exception because it calls expand :foreach => nil
+ tc.expand('null_context_test::NullContextTestBad2', :for => :dummy)
+ end
+ assert_nothing_raised do
+ tc.expand('null_context_test::NullContextTestOk', :for => :dummy)
+ end
+ end
+
+ def test_no_indent
+ tc = RGen::TemplateLanguage::DirectoryTemplateContainer.new([MyMM, CCodeMM], OUTPUT_DIR)
+ tc.load(TEMPLATES_DIR)
+ assert_equal " xxx<---\r\n xxx<---\r\n xxx<---\r\n xxx<---\r\n", tc.expand('no_indent_test/test::Test', :for => :dummy)
+ end
+
+ def test_no_indent2
+ tc = RGen::TemplateLanguage::DirectoryTemplateContainer.new([MyMM, CCodeMM], OUTPUT_DIR)
+ tc.load(TEMPLATES_DIR)
+ assert_equal " return xxxx;\r\n", tc.expand("no_indent_test/test2::Test", :for => :dummy)
+ end
+
+ def test_no_indent3
+ tc = RGen::TemplateLanguage::DirectoryTemplateContainer.new([MyMM, CCodeMM], OUTPUT_DIR)
+ tc.load(TEMPLATES_DIR)
+ assert_equal " l1<---\r\n l2\r\n\r\n", tc.expand("no_indent_test/test3::Test", :for => :dummy)
+ end
+
+ def test_template_resolution
+ tc = RGen::TemplateLanguage::DirectoryTemplateContainer.new([MyMM, CCodeMM], OUTPUT_DIR)
+ tc.load(TEMPLATES_DIR)
+ assert_equal "Sub1\r\nSub1 in sub1\r\n", tc.expand('template_resolution_test/test::Test', :for => :dummy)
+ assert_equal "Sub1\r\nSub1\r\nSub1 in sub1\r\n", tc.expand('template_resolution_test/sub1::Test', :for => :dummy)
+ end
+
+ def test_evaluate
+ tc = RGen::TemplateLanguage::DirectoryTemplateContainer.new([MyMM, CCodeMM], OUTPUT_DIR)
+ tc.load(TEMPLATES_DIR)
+ assert_equal "xx1xxxx2xxxx3xxxx4xx\r\n", tc.expand('evaluate_test/test::Test', :for => :dummy)
+ end
+
+ def test_define_local
+ tc = RGen::TemplateLanguage::DirectoryTemplateContainer.new([MyMM, CCodeMM], OUTPUT_DIR)
+ tc.load(TEMPLATES_DIR)
+ assert_equal "Local1\r\n", tc.expand('define_local_test/test::Test', :for => :dummy)
+ assert_raise StandardError do
+ tc.expand('define_local_test/test::TestForbidden', :for => :dummy)
+ end
+ end
+
+ def test_no_backslash_r
+ tc = RGen::TemplateLanguage::DirectoryTemplateContainer.new([MyMM, CCodeMM], OUTPUT_DIR)
+ tc.load(TEMPLATES_DIR)
+ expected = ""
+ File.open(OUTPUT_DIR+"/expected_result3.txt") {|f| expected = f.read}
+ assert_equal expected, tc.expand('no_backslash_r_test::Test', :for => :dummy).to_s
+ end
+
+ def test_callback_indent
+ tc = RGen::TemplateLanguage::DirectoryTemplateContainer.new([MyMM, CCodeMM], OUTPUT_DIR)
+ tc.load(TEMPLATES_DIR)
+ assert_equal("|before callback\r\n |in callback\r\n|after callback\r\n |after iinc\r\n",
+ tc.expand('callback_indent_test/a::caller', :for => :dummy))
+ end
+end
diff --git a/lib/puppet/vendor/rgen/test/template_language_test/expected_result1.txt b/lib/puppet/vendor/rgen/test/template_language_test/expected_result1.txt
new file mode 100644
index 000000000..551d273a5
--- /dev/null
+++ b/lib/puppet/vendor/rgen/test/template_language_test/expected_result1.txt
@@ -0,0 +1,29 @@
+ Document: SomeDocument
+
+ by Martin, EMail: martin(at)somewhe.re and Otherguy, EMail: other(at)somewhereel.se
+
+ Index:
+ 1 Intro in SomeDocument
+ 2 MainPart in SomeDocument
+ 3 Summary in SomeDocument
+
+ ----------------
+
+ Chapters in one line:
+ *** Intro ***, *** MainPart ***, *** Summary ***
+
+ Chapters each in one line:
+ *** Intro ***,
+ *** MainPart ***,
+ *** Summary ***
+
+ Here are some code examples:
+ int myArray[5] = {
+ 1,
+ 2,
+ 3,
+ 4,
+ 5
+ };
+ Text from Root
+ Text from Root
diff --git a/lib/puppet/vendor/rgen/test/template_language_test/expected_result2.txt b/lib/puppet/vendor/rgen/test/template_language_test/expected_result2.txt
new file mode 100644
index 000000000..b16ec5237
--- /dev/null
+++ b/lib/puppet/vendor/rgen/test/template_language_test/expected_result2.txt
@@ -0,0 +1,9 @@
+int myArray[5] = {
+ 1,
+ 2,
+ 3,
+ 4,
+ 5
+};
+Text from Root
+Text from Root
diff --git a/lib/puppet/vendor/rgen/test/template_language_test/expected_result3.txt b/lib/puppet/vendor/rgen/test/template_language_test/expected_result3.txt
new file mode 100644
index 000000000..79e6f8d1c
--- /dev/null
+++ b/lib/puppet/vendor/rgen/test/template_language_test/expected_result3.txt
@@ -0,0 +1,4 @@
+This file was created on Linux and does not contain \r before \n
+The next blank line is done by the "nl" command which shall only add a \n, no \r:
+
+END
diff --git a/lib/puppet/vendor/rgen/test/template_language_test/indentStringTestDefaultIndent.out b/lib/puppet/vendor/rgen/test/template_language_test/indentStringTestDefaultIndent.out
new file mode 100644
index 000000000..6cf7396e1
--- /dev/null
+++ b/lib/puppet/vendor/rgen/test/template_language_test/indentStringTestDefaultIndent.out
@@ -0,0 +1 @@
+ <- your default here
diff --git a/lib/puppet/vendor/rgen/test/template_language_test/indentStringTestTabIndent.out b/lib/puppet/vendor/rgen/test/template_language_test/indentStringTestTabIndent.out
new file mode 100644
index 000000000..f70482c3b
--- /dev/null
+++ b/lib/puppet/vendor/rgen/test/template_language_test/indentStringTestTabIndent.out
@@ -0,0 +1 @@
+ <- tab
diff --git a/lib/puppet/vendor/rgen/test/template_language_test/templates/callback_indent_test/a.tpl b/lib/puppet/vendor/rgen/test/template_language_test/templates/callback_indent_test/a.tpl
new file mode 100644
index 000000000..30fd9d6df
--- /dev/null
+++ b/lib/puppet/vendor/rgen/test/template_language_test/templates/callback_indent_test/a.tpl
@@ -0,0 +1,12 @@
+<% define 'caller', :for => Object do %>
+ |before callback
+ <% expand 'b::do_callback' %>
+ |after callback
+ <%iinc%>
+ |after iinc
+<% end %>
+
+<% define 'callback', :for => Object do %>
+ |in callback
+<% end %>
+
diff --git a/lib/puppet/vendor/rgen/test/template_language_test/templates/callback_indent_test/b.tpl b/lib/puppet/vendor/rgen/test/template_language_test/templates/callback_indent_test/b.tpl
new file mode 100644
index 000000000..293edd87e
--- /dev/null
+++ b/lib/puppet/vendor/rgen/test/template_language_test/templates/callback_indent_test/b.tpl
@@ -0,0 +1,5 @@
+<% define 'do_callback', :for => Object do %>
+ <%iinc%>
+ <% expand 'a::callback' %>
+ <%idec%>
+<% end %>
diff --git a/lib/puppet/vendor/rgen/test/template_language_test/templates/code/array.tpl b/lib/puppet/vendor/rgen/test/template_language_test/templates/code/array.tpl
new file mode 100644
index 000000000..78d3a8b0f
--- /dev/null
+++ b/lib/puppet/vendor/rgen/test/template_language_test/templates/code/array.tpl
@@ -0,0 +1,11 @@
+<% define 'ArrayDefinition', :for => CArray do %>
+ <%= getType %> <%= name %>[<%= size %>] = {<%iinc%>
+ <% expand 'InitValue', :foreach => initvalue, :separator => ",\r\n" %><%nl%><%idec%>
+ };
+ <% expand '../root::TextFromRoot' %>
+ <% expand '/root::TextFromRoot' %>
+<% end %>
+
+<% define 'InitValue', :for => PrimitiveInitValue do %>
+ <%= value %><%nows%>
+<% end %>
diff --git a/lib/puppet/vendor/rgen/test/template_language_test/templates/content/author.tpl b/lib/puppet/vendor/rgen/test/template_language_test/templates/content/author.tpl
new file mode 100644
index 000000000..8c03422b1
--- /dev/null
+++ b/lib/puppet/vendor/rgen/test/template_language_test/templates/content/author.tpl
@@ -0,0 +1,7 @@
+<% define 'Author', :for => Author do %>
+ <% expand 'SubAuthor' %>
+<% end %>
+
+<% define 'SubAuthor', :for => Author do %>
+ <%= name %>, EMail: <%= email.sub('@','(at)') %><%nows%>
+<% end %>
diff --git a/lib/puppet/vendor/rgen/test/template_language_test/templates/content/chapter.tpl b/lib/puppet/vendor/rgen/test/template_language_test/templates/content/chapter.tpl
new file mode 100644
index 000000000..53ada2239
--- /dev/null
+++ b/lib/puppet/vendor/rgen/test/template_language_test/templates/content/chapter.tpl
@@ -0,0 +1,5 @@
+<% define 'Root', :for => Chapter do %>
+ *** <%= title %> ***<%nows%>
+
+
+<% end %> \ No newline at end of file
diff --git a/lib/puppet/vendor/rgen/test/template_language_test/templates/define_local_test/local.tpl b/lib/puppet/vendor/rgen/test/template_language_test/templates/define_local_test/local.tpl
new file mode 100644
index 000000000..5e4aad74b
--- /dev/null
+++ b/lib/puppet/vendor/rgen/test/template_language_test/templates/define_local_test/local.tpl
@@ -0,0 +1,8 @@
+<% define 'CallLocal1', :for => Object do %>
+ <% expand 'Local1' %>
+<% end %>
+
+<% define_local 'Local1', :for => Object do %>
+ Local1
+<% end %>
+
diff --git a/lib/puppet/vendor/rgen/test/template_language_test/templates/define_local_test/test.tpl b/lib/puppet/vendor/rgen/test/template_language_test/templates/define_local_test/test.tpl
new file mode 100644
index 000000000..8511132e9
--- /dev/null
+++ b/lib/puppet/vendor/rgen/test/template_language_test/templates/define_local_test/test.tpl
@@ -0,0 +1,8 @@
+<% define 'Test', :for => Object do %>
+ <% expand 'local::CallLocal1' %>
+<% end %>
+
+<% define 'TestForbidden', :for => Object do %>
+ <% expand 'local::Local1' %>
+<% end %>
+ \ No newline at end of file
diff --git a/lib/puppet/vendor/rgen/test/template_language_test/templates/evaluate_test/test.tpl b/lib/puppet/vendor/rgen/test/template_language_test/templates/evaluate_test/test.tpl
new file mode 100644
index 000000000..7cc20b2e6
--- /dev/null
+++ b/lib/puppet/vendor/rgen/test/template_language_test/templates/evaluate_test/test.tpl
@@ -0,0 +1,7 @@
+<% define 'Test', :for => Object do %>
+ <%= [1,2,3,4].collect{|n| evaluate 'Eval', :for => n}.join %>
+<% end %>
+
+<% define 'Eval', :for => Object do %>
+ xx<%= this %>xx<%nows%>
+<% end %>
diff --git a/lib/puppet/vendor/rgen/test/template_language_test/templates/indent_string_test.tpl b/lib/puppet/vendor/rgen/test/template_language_test/templates/indent_string_test.tpl
new file mode 100644
index 000000000..a57840cc8
--- /dev/null
+++ b/lib/puppet/vendor/rgen/test/template_language_test/templates/indent_string_test.tpl
@@ -0,0 +1,12 @@
+<% define 'IndentStringTest', :for => Object do %>
+ <% file 'indentStringTestDefaultIndent.out' do %>
+ <%iinc%>
+ <- your default here
+ <%idec%>
+ <% end %>
+ <% file 'indentStringTestTabIndent.out', "\t" do %>
+ <%iinc%>
+ <- tab
+ <%idec%>
+ <% end %>
+<% end %> \ No newline at end of file
diff --git a/lib/puppet/vendor/rgen/test/template_language_test/templates/index/c/cmod.tpl b/lib/puppet/vendor/rgen/test/template_language_test/templates/index/c/cmod.tpl
new file mode 100644
index 000000000..411d68c4b
--- /dev/null
+++ b/lib/puppet/vendor/rgen/test/template_language_test/templates/index/c/cmod.tpl
@@ -0,0 +1 @@
+<% define 'cmod' do %>Module C is special !<% end %> \ No newline at end of file
diff --git a/lib/puppet/vendor/rgen/test/template_language_test/templates/index/chapter.tpl b/lib/puppet/vendor/rgen/test/template_language_test/templates/index/chapter.tpl
new file mode 100644
index 000000000..7396f611c
--- /dev/null
+++ b/lib/puppet/vendor/rgen/test/template_language_test/templates/index/chapter.tpl
@@ -0,0 +1,3 @@
+<% define 'Root' do |idx, doc| %>
+ <%= idx%> <%= title %> in <%= doc.title %>
+<% end %> \ No newline at end of file
diff --git a/lib/puppet/vendor/rgen/test/template_language_test/templates/no_backslash_r_test.tpl b/lib/puppet/vendor/rgen/test/template_language_test/templates/no_backslash_r_test.tpl
new file mode 100644
index 000000000..3c06203d9
--- /dev/null
+++ b/lib/puppet/vendor/rgen/test/template_language_test/templates/no_backslash_r_test.tpl
@@ -0,0 +1,5 @@
+<% define 'Test', :for => Object do %>
+This file was created on Linux and does not contain \r before \n
+The next blank line is done by the "nl" command which shall only add a \n, no \r:
+<%nl%>END
+<% end %> \ No newline at end of file
diff --git a/lib/puppet/vendor/rgen/test/template_language_test/templates/no_indent_test/no_indent.tpl b/lib/puppet/vendor/rgen/test/template_language_test/templates/no_indent_test/no_indent.tpl
new file mode 100644
index 000000000..9bdacde57
--- /dev/null
+++ b/lib/puppet/vendor/rgen/test/template_language_test/templates/no_indent_test/no_indent.tpl
@@ -0,0 +1,3 @@
+<% define 'NoIndent', :for => Object do %>
+ <---<%nows%>
+<% end %>
diff --git a/lib/puppet/vendor/rgen/test/template_language_test/templates/no_indent_test/sub1/no_indent.tpl b/lib/puppet/vendor/rgen/test/template_language_test/templates/no_indent_test/sub1/no_indent.tpl
new file mode 100644
index 000000000..9bdacde57
--- /dev/null
+++ b/lib/puppet/vendor/rgen/test/template_language_test/templates/no_indent_test/sub1/no_indent.tpl
@@ -0,0 +1,3 @@
+<% define 'NoIndent', :for => Object do %>
+ <---<%nows%>
+<% end %>
diff --git a/lib/puppet/vendor/rgen/test/template_language_test/templates/no_indent_test/test.tpl b/lib/puppet/vendor/rgen/test/template_language_test/templates/no_indent_test/test.tpl
new file mode 100644
index 000000000..0acec2774
--- /dev/null
+++ b/lib/puppet/vendor/rgen/test/template_language_test/templates/no_indent_test/test.tpl
@@ -0,0 +1,24 @@
+<% define 'Test', :for => Object do %>
+ <%iinc%>
+ xxx<% expand 'NoIndent1' %>
+ xxx<% expand 'NoIndent2' %>
+ xxx<% expand 'NoIndent3' %>
+ xxx<% expand 'NoIndent4' %>
+ <%idec%>
+<% end %>
+
+<% define 'NoIndent1', :for => Object do %>
+ <---<%nows%>
+<% end %>
+
+<% define 'NoIndent2', :for => Object do %>
+ <% expand 'NoIndent1' %>
+<% end %>
+
+<% define 'NoIndent3', :for => Object do %>
+ <% expand 'no_indent::NoIndent' %>
+<% end %>
+
+<% define 'NoIndent4', :for => Object do %>
+ <% expand 'sub1/no_indent::NoIndent' %>
+<% end %>
diff --git a/lib/puppet/vendor/rgen/test/template_language_test/templates/no_indent_test/test2.tpl b/lib/puppet/vendor/rgen/test/template_language_test/templates/no_indent_test/test2.tpl
new file mode 100644
index 000000000..63cf99263
--- /dev/null
+++ b/lib/puppet/vendor/rgen/test/template_language_test/templates/no_indent_test/test2.tpl
@@ -0,0 +1,13 @@
+<% define 'Test', :for => Object do %>
+<%iinc%><%iinc%>
+return <% expand 'Call1' %>;
+<%idec%><%idec%>
+<% end %>
+
+<% define 'Call1', :for => Object do %>
+ x<% expand 'Call2' %><%nows%>
+<% end %>
+
+<% define 'Call2', :for => Object do %>
+ xxx<%nows%>
+<% end %> \ No newline at end of file
diff --git a/lib/puppet/vendor/rgen/test/template_language_test/templates/no_indent_test/test3.tpl b/lib/puppet/vendor/rgen/test/template_language_test/templates/no_indent_test/test3.tpl
new file mode 100644
index 000000000..8c4c3eea5
--- /dev/null
+++ b/lib/puppet/vendor/rgen/test/template_language_test/templates/no_indent_test/test3.tpl
@@ -0,0 +1,10 @@
+<% define 'Test', :for => Object do %>
+<%iinc%>
+ l1<% expand 'Call1' %>
+<%idec%>
+<% end %>
+
+<% define 'Call1', :for => Object do %>
+ <---
+ l2
+<% end %>
diff --git a/lib/puppet/vendor/rgen/test/template_language_test/templates/null_context_test.tpl b/lib/puppet/vendor/rgen/test/template_language_test/templates/null_context_test.tpl
new file mode 100644
index 000000000..856b77109
--- /dev/null
+++ b/lib/puppet/vendor/rgen/test/template_language_test/templates/null_context_test.tpl
@@ -0,0 +1,17 @@
+<% define 'NullContextTestBad', :for => Object do %>
+ <%# this must raise an exception %>
+ <% expand 'Callee', :for => nil %>
+<% end %>
+
+<% define 'NullContextTestBad2', :for => Object do %>
+ <%# this must raise an exception %>
+ <% expand 'Callee', :foreach => nil %>
+<% end %>
+
+<% define 'NullContextTestOk', :for => Object do %>
+ <%# however this is ok %>
+ <% expand 'Callee' %>
+<% end %>
+
+<% define 'Callee', :for => Object do %>
+<% end %> \ No newline at end of file
diff --git a/lib/puppet/vendor/rgen/test/template_language_test/templates/root.tpl b/lib/puppet/vendor/rgen/test/template_language_test/templates/root.tpl
new file mode 100644
index 000000000..8b70e31a4
--- /dev/null
+++ b/lib/puppet/vendor/rgen/test/template_language_test/templates/root.tpl
@@ -0,0 +1,31 @@
+<% define 'Root' do %>
+ <% file 'testout.txt' do %>
+ Document: <%= title %>
+ <%nl%>
+ <%iinc%>
+ by <% expand 'content/author::Author', :foreach => authors, :separator => ' and ' %>
+ <%idec%>
+ <%nl%>
+ Index:<%iinc%>
+ <% for c in chapters %>
+ <% nr = (nr || 0); nr += 1 %>
+ <% expand 'index/chapter::Root', nr, this, :for => c %>
+ <% end %><%idec%>
+ <%nl%>
+ ----------------
+ <%nl%>
+ Chapters in one line:
+ <% expand 'content/chapter::Root', :foreach => chapters, :separator => ", " %><%nl%>
+ <%nl%>
+ Chapters each in one line:
+ <% expand 'content/chapter::Root', :foreach => chapters, :separator => ",\r\n" %><%nl%>
+ <%nl%>
+ Here are some code examples:
+ <% expand 'code/array::ArrayDefinition', :for => sampleArray %>
+ <% end %>
+<% end %>
+
+<% define 'TextFromRoot' do %>
+ Text from Root
+<% end %>
+
diff --git a/lib/puppet/vendor/rgen/test/template_language_test/templates/template_resolution_test/sub1.tpl b/lib/puppet/vendor/rgen/test/template_language_test/templates/template_resolution_test/sub1.tpl
new file mode 100644
index 000000000..3c7a5cfa2
--- /dev/null
+++ b/lib/puppet/vendor/rgen/test/template_language_test/templates/template_resolution_test/sub1.tpl
@@ -0,0 +1,9 @@
+<% define 'Sub1', :for => Object do %>
+ Sub1
+<% end %>
+
+<% define 'Test', :for => Object do %>
+ <% expand 'Sub1' %>
+ <% expand 'sub1::Sub1' %>
+ <% expand 'sub1/sub1::Sub1' %>
+<% end %>
diff --git a/lib/puppet/vendor/rgen/test/template_language_test/templates/template_resolution_test/sub1/sub1.tpl b/lib/puppet/vendor/rgen/test/template_language_test/templates/template_resolution_test/sub1/sub1.tpl
new file mode 100644
index 000000000..1b6dc9700
--- /dev/null
+++ b/lib/puppet/vendor/rgen/test/template_language_test/templates/template_resolution_test/sub1/sub1.tpl
@@ -0,0 +1,3 @@
+<% define 'Sub1', :for => Object do %>
+ Sub1 in sub1
+<% end %> \ No newline at end of file
diff --git a/lib/puppet/vendor/rgen/test/template_language_test/templates/template_resolution_test/test.tpl b/lib/puppet/vendor/rgen/test/template_language_test/templates/template_resolution_test/test.tpl
new file mode 100644
index 000000000..b1a970a01
--- /dev/null
+++ b/lib/puppet/vendor/rgen/test/template_language_test/templates/template_resolution_test/test.tpl
@@ -0,0 +1,4 @@
+<% define 'Test', :for => Object do %>
+ <% expand 'sub1::Sub1' %>
+ <% expand 'sub1/sub1::Sub1' %>
+<% end %> \ No newline at end of file
diff --git a/lib/puppet/vendor/rgen/test/template_language_test/testout.txt b/lib/puppet/vendor/rgen/test/template_language_test/testout.txt
new file mode 100644
index 000000000..551d273a5
--- /dev/null
+++ b/lib/puppet/vendor/rgen/test/template_language_test/testout.txt
@@ -0,0 +1,29 @@
+ Document: SomeDocument
+
+ by Martin, EMail: martin(at)somewhe.re and Otherguy, EMail: other(at)somewhereel.se
+
+ Index:
+ 1 Intro in SomeDocument
+ 2 MainPart in SomeDocument
+ 3 Summary in SomeDocument
+
+ ----------------
+
+ Chapters in one line:
+ *** Intro ***, *** MainPart ***, *** Summary ***
+
+ Chapters each in one line:
+ *** Intro ***,
+ *** MainPart ***,
+ *** Summary ***
+
+ Here are some code examples:
+ int myArray[5] = {
+ 1,
+ 2,
+ 3,
+ 4,
+ 5
+ };
+ Text from Root
+ Text from Root
diff --git a/lib/puppet/vendor/rgen/test/testmodel/class_model_checker.rb b/lib/puppet/vendor/rgen/test/testmodel/class_model_checker.rb
new file mode 100644
index 000000000..0bebbdc6b
--- /dev/null
+++ b/lib/puppet/vendor/rgen/test/testmodel/class_model_checker.rb
@@ -0,0 +1,119 @@
+require 'metamodels/uml13_metamodel'
+require 'metamodels/uml13_metamodel_ext'
+
+module Testmodel
+
+# Checks the UML Class model elements from the example model
+#
+module ClassModelChecker
+
+ def checkClassModel(envUML)
+
+ # check main package
+ mainPackage = envUML.find(:class => UML13::Package, :name => "HouseMetamodel").first
+ assert_not_nil mainPackage
+
+ # check Rooms package
+ subs = mainPackage.ownedElement.select{|e| e.is_a?(UML13::Package)}
+ assert_equal 1, subs.size
+ roomsPackage = subs.first
+ assert_equal "Rooms", roomsPackage.name
+
+ # check main package classes
+ classes = mainPackage.ownedElement.select{|e| e.is_a?(UML13::Class)}
+ assert_equal 3, classes.size
+ houseClass = classes.find{|c| c.name == "House"}
+ personClass = classes.find{|c| c.name == "Person"}
+ meetingPlaceClass = classes.find{|c| c.name == "MeetingPlace"}
+ cookingPlaceInterface = mainPackage.ownedElement.find{|e| e.is_a?(UML13::Interface) && e.name == "CookingPlace"}
+ assert_not_nil houseClass
+ assert_not_nil personClass
+ assert_not_nil meetingPlaceClass
+ assert_not_nil cookingPlaceInterface
+
+ # check Rooms package classes
+ classes = roomsPackage.ownedElement.select{|e| e.is_a?(UML13::Class)}
+ assert_equal 3, classes.size
+ roomClass = classes.find{|c| c.name == "Room"}
+ kitchenClass = classes.find{|c| c.name == "Kitchen"}
+ bathroomClass = classes.find{|c| c.name == "Bathroom"}
+ assert_not_nil roomClass
+ assert_not_nil kitchenClass
+ assert_not_nil bathroomClass
+
+ # check Room inheritance
+ assert_equal 2, roomClass.specialization.child.size
+ assert_not_nil roomClass.specialization.child.find{|c| c.name == "Kitchen"}
+ assert_not_nil roomClass.specialization.child.find{|c| c.name == "Bathroom"}
+ assert_equal 2, kitchenClass.generalization.parent.size
+ assert_equal roomClass.object_id, kitchenClass.generalization.parent.find{|c| c.name == "Room"}.object_id
+ assert_equal meetingPlaceClass.object_id, kitchenClass.generalization.parent.find{|c| c.name == "MeetingPlace"}.object_id
+ assert_equal 1, bathroomClass.generalization.parent.size
+ assert_equal roomClass.object_id, bathroomClass.generalization.parent.first.object_id
+ assert_not_nil kitchenClass.clientDependency.find{|d| d.stereotype.name == "implements"}
+ assert_equal cookingPlaceInterface.object_id, kitchenClass.clientDependency.supplier.find{|c| c.name == "CookingPlace"}.object_id
+ assert_equal kitchenClass.object_id, cookingPlaceInterface.supplierDependency.client.find{|c| c.name == "Kitchen"}.object_id
+
+ # check House-Room "part of" association
+ assert_equal 1, houseClass.localCompositeEnd.size
+ roomEnd = houseClass.localCompositeEnd.first.otherEnd
+ assert_equal UML13::Association, roomEnd.association.class
+ assert_equal roomClass.object_id, roomEnd.type.object_id
+ assert_equal "room", roomEnd.name
+ assert_equal UML13::Multiplicity, roomEnd.multiplicity.class
+ assert_equal "1", roomEnd.multiplicity.range.first.lower
+ assert_equal "*", roomEnd.multiplicity.range.first.upper
+
+ assert_equal 1, roomClass.remoteCompositeEnd.size
+ assert_equal houseClass.object_id, roomClass.remoteCompositeEnd.first.type.object_id
+ assert_equal "house", roomClass.remoteCompositeEnd.first.name
+
+ # check House OUT associations
+ assert_equal 2, houseClass.remoteNavigableEnd.size
+ bathEnd = houseClass.remoteNavigableEnd.find{|e| e.name == "bathroom"}
+ kitchenEnd = houseClass.remoteNavigableEnd.find{|e| e.name== "kitchen"}
+ assert_not_nil bathEnd
+ assert_not_nil kitchenEnd
+ assert_equal UML13::Association, bathEnd.association.class
+ assert_equal UML13::Association, kitchenEnd.association.class
+ assert_equal "1", kitchenEnd.multiplicity.range.first.lower
+ assert_equal "1", kitchenEnd.multiplicity.range.first.upper
+
+ # check House IN associations
+ assert_equal 3, houseClass.localNavigableEnd.size
+ homeEnd = houseClass.localNavigableEnd.find{|e| e.name == "home"}
+ assert_not_nil homeEnd
+ assert_equal UML13::Association, homeEnd.association.class
+ assert_equal "0", homeEnd.multiplicity.range.first.lower
+ assert_equal "*", homeEnd.multiplicity.range.first.upper
+
+ # check House all associations
+ assert_equal 4, houseClass.associationEnd.size
+ end
+
+ def checkClassModelPartial(envUML)
+ # HouseMetamodel package is not part of the partial export
+ mainPackage = envUML.find(:class => UML13::Package, :name => "HouseMetamodel").first
+ assert_nil mainPackage
+
+ roomsPackage = envUML.find(:class => UML13::Package, :name => "Rooms").first
+ assert_not_nil roomsPackage
+
+ roomClass = envUML.find(:class => UML13::Class, :name => "Room").first
+ assert_not_nil roomClass
+
+ # House is created from an EAStub
+ houseClass = roomClass.remoteCompositeEnd.first.type
+ assert_not_nil houseClass
+ assert_equal "House", houseClass.name
+ # House is not in a package since it's just a stub
+ assert houseClass.namespace.nil?
+
+ # in the partial model, House has only 3 (not 4) associations
+ # since the fourth class (Person) is not in Rooms package
+ assert_equal 3, houseClass.associationEnd.size
+ end
+
+end
+
+end \ No newline at end of file
diff --git a/lib/puppet/vendor/rgen/test/testmodel/ea_testmodel.eap b/lib/puppet/vendor/rgen/test/testmodel/ea_testmodel.eap
new file mode 100644
index 000000000..737b49cdf
--- /dev/null
+++ b/lib/puppet/vendor/rgen/test/testmodel/ea_testmodel.eap
Binary files differ
diff --git a/lib/puppet/vendor/rgen/test/testmodel/ea_testmodel.xml b/lib/puppet/vendor/rgen/test/testmodel/ea_testmodel.xml
new file mode 100644
index 000000000..b2bee1593
--- /dev/null
+++ b/lib/puppet/vendor/rgen/test/testmodel/ea_testmodel.xml
@@ -0,0 +1,1029 @@
+<?xml version="1.0" encoding="windows-1252"?>
+<XMI xmi.version="1.1" xmlns:UML="omg.org/UML1.3" timestamp="2007-06-14 08:25:40">
+ <XMI.header>
+ <XMI.documentation>
+ <XMI.exporter>Enterprise Architect</XMI.exporter>
+ <XMI.exporterVersion>2.5</XMI.exporterVersion>
+ </XMI.documentation>
+ </XMI.header>
+ <XMI.content>
+ <UML:Model name="EA Model" xmi.id="MX_EAID_B216CC15_6D9C_4c10_9015_96740F924D1C">
+ <UML:Namespace.ownedElement>
+ <UML:Class name="EARootClass" xmi.id="EAID_11111111_5487_4080_A7F4_41526CB0AA00" isRoot="true" isLeaf="false" isAbstract="false"/>
+ <UML:Package name="Views" xmi.id="EAPK_B216CC15_6D9C_4c10_9015_96740F924D1C" isRoot="true" isLeaf="false" isAbstract="false" visibility="public">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="ea_package_id" value="1"/>
+ <UML:TaggedValue tag="iscontrolled" value="FALSE"/>
+ <UML:TaggedValue tag="isprotected" value="FALSE"/>
+ <UML:TaggedValue tag="usedtd" value="FALSE"/>
+ <UML:TaggedValue tag="logxml" value="FALSE"/>
+ </UML:ModelElement.taggedValue>
+ <UML:Namespace.ownedElement>
+ <UML:Collaboration xmi.id="EAID_B216CC15_6D9C_4c10_9015_96740F924D1C_Collaboration" name="Collaborations">
+ <UML:Namespace.ownedElement>
+ <UML:ClassifierRole name="HouseMetamodel" xmi.id="EAID_A1B83D59_CAE1_422c_BA5F_D3624D7156AD" visibility="public" base="EAID_11111111_5487_4080_A7F4_41526CB0AA00">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="isSpecification" value="false"/>
+ <UML:TaggedValue tag="ea_stype" value="Package"/>
+ <UML:TaggedValue tag="ea_ntype" value="0"/>
+ <UML:TaggedValue tag="version" value="1.0"/>
+ <UML:TaggedValue tag="package" value="EAPK_B216CC15_6D9C_4c10_9015_96740F924D1C"/>
+ <UML:TaggedValue tag="date_created" value="2006-06-26 08:41:49"/>
+ <UML:TaggedValue tag="date_modified" value="2006-06-26 08:41:49"/>
+ <UML:TaggedValue tag="gentype" value="Java"/>
+ <UML:TaggedValue tag="tagged" value="0"/>
+ <UML:TaggedValue tag="package2" value="EAID_A1B83D59_CAE1_422c_BA5F_D3624D7156AD"/>
+ <UML:TaggedValue tag="package_name" value="Views"/>
+ <UML:TaggedValue tag="phase" value="1.0"/>
+ <UML:TaggedValue tag="complexity" value="1"/>
+ <UML:TaggedValue tag="status" value="Proposed"/>
+ <UML:TaggedValue tag="style" value="BackColor=-1;BorderColor=-1;BorderWidth=-1;FontColor=-1;VSwimLanes=0;HSwimLanes=0;BorderStyle=0;"/>
+ </UML:ModelElement.taggedValue>
+ </UML:ClassifierRole>
+ <UML:ClassifierRole name="HouseExampleModel" xmi.id="EAID_06C9C958_C14A_41f4_89A9_6873CCED37A7" visibility="public" base="EAID_11111111_5487_4080_A7F4_41526CB0AA00">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="isSpecification" value="false"/>
+ <UML:TaggedValue tag="ea_stype" value="Package"/>
+ <UML:TaggedValue tag="ea_ntype" value="0"/>
+ <UML:TaggedValue tag="version" value="1.0"/>
+ <UML:TaggedValue tag="package" value="EAPK_B216CC15_6D9C_4c10_9015_96740F924D1C"/>
+ <UML:TaggedValue tag="date_created" value="2006-07-20 18:34:43"/>
+ <UML:TaggedValue tag="date_modified" value="2006-07-20 18:34:43"/>
+ <UML:TaggedValue tag="gentype" value="Java"/>
+ <UML:TaggedValue tag="tagged" value="0"/>
+ <UML:TaggedValue tag="package2" value="EAID_06C9C958_C14A_41f4_89A9_6873CCED37A7"/>
+ <UML:TaggedValue tag="package_name" value="Views"/>
+ <UML:TaggedValue tag="phase" value="1.0"/>
+ <UML:TaggedValue tag="complexity" value="1"/>
+ <UML:TaggedValue tag="status" value="Proposed"/>
+ <UML:TaggedValue tag="style" value="BackColor=-1;BorderColor=-1;BorderWidth=-1;FontColor=-1;VSwimLanes=0;HSwimLanes=0;BorderStyle=0;"/>
+ </UML:ModelElement.taggedValue>
+ </UML:ClassifierRole>
+ </UML:Namespace.ownedElement>
+ <UML:Collaboration.interaction/>
+ </UML:Collaboration>
+ <UML:Package name="HouseMetamodel" xmi.id="EAPK_A1B83D59_CAE1_422c_BA5F_D3624D7156AD" isRoot="true" isLeaf="false" isAbstract="false" visibility="public">
+ <UML:ModelElement.name>HouseMetamodel</UML:ModelElement.name>
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="created" value="2006-06-26 00:00:00"/>
+ <UML:TaggedValue tag="modified" value="2006-06-26 00:00:00"/>
+ <UML:TaggedValue tag="iscontrolled" value="FALSE"/>
+ <UML:TaggedValue tag="isprotected" value="FALSE"/>
+ <UML:TaggedValue tag="usedtd" value="FALSE"/>
+ <UML:TaggedValue tag="logxml" value="FALSE"/>
+ <UML:TaggedValue tag="ea_package_id" value="44"/>
+ <UML:TaggedValue tag="phase" value="1.0"/>
+ <UML:TaggedValue tag="status" value="Proposed"/>
+ <UML:TaggedValue tag="complexity" value="1"/>
+ <UML:TaggedValue tag="ea_stype" value="Public"/>
+ </UML:ModelElement.taggedValue>
+ <UML:Namespace.ownedElement>
+ <UML:Class name="House" xmi.id="EAID_436D81AD_A9B0_44d8_8AD1_86BB0808DA32" visibility="public" namespace="EAPK_A1B83D59_CAE1_422c_BA5F_D3624D7156AD" isRoot="false" isLeaf="false" isAbstract="false" isActive="false">
+ <UML:ModelElement.stereotype>
+ <UML:Stereotype name="dummy"/>
+ </UML:ModelElement.stereotype>
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="isSpecification" value="false"/>
+ <UML:TaggedValue tag="ea_stype" value="Class"/>
+ <UML:TaggedValue tag="ea_ntype" value="0"/>
+ <UML:TaggedValue tag="version" value="1.0"/>
+ <UML:TaggedValue tag="package" value="EAPK_A1B83D59_CAE1_422c_BA5F_D3624D7156AD"/>
+ <UML:TaggedValue tag="date_created" value="2005-09-16 19:52:18"/>
+ <UML:TaggedValue tag="date_modified" value="2006-02-28 08:29:19"/>
+ <UML:TaggedValue tag="gentype" value="Java"/>
+ <UML:TaggedValue tag="tagged" value="0"/>
+ <UML:TaggedValue tag="package_name" value="HouseMetamodel"/>
+ <UML:TaggedValue tag="phase" value="1.0"/>
+ <UML:TaggedValue tag="complexity" value="1"/>
+ <UML:TaggedValue tag="status" value="Proposed"/>
+ <UML:TaggedValue tag="stereotype" value="dummy"/>
+ <UML:TaggedValue tag="style" value="BackColor=-1;BorderColor=-1;BorderWidth=-1;FontColor=-1;VSwimLanes=0;HSwimLanes=0;BorderStyle=0;"/>
+ </UML:ModelElement.taggedValue>
+ <UML:Classifier.feature>
+ <UML:Attribute name="address" visibility="private" ownerScope="instance" changeable="none" targetScope="instance">
+ <UML:Attribute.initialValue>
+ <UML:Expression/>
+ </UML:Attribute.initialValue>
+ <UML:StructuralFeature.type>
+ <UML:Classifier xmi.idref="eaxmiid0"/>
+ </UML:StructuralFeature.type>
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="type" value="String"/>
+ <UML:TaggedValue tag="derived" value="0"/>
+ <UML:TaggedValue tag="containment" value="Not Specified"/>
+ <UML:TaggedValue tag="ordered" value="0"/>
+ <UML:TaggedValue tag="collection" value="false"/>
+ <UML:TaggedValue tag="position" value="0"/>
+ <UML:TaggedValue tag="lowerBound" value="1"/>
+ <UML:TaggedValue tag="upperBound" value="1"/>
+ <UML:TaggedValue tag="duplicates" value="0"/>
+ <UML:TaggedValue tag="ea_guid" value="{A8DF581B-9AC6-4f75-AB48-8FAEDFC6E068}"/>
+ <UML:TaggedValue tag="styleex" value="volatile=0;"/>
+ </UML:ModelElement.taggedValue>
+ </UML:Attribute>
+ </UML:Classifier.feature>
+ </UML:Class>
+ <UML:Association xmi.id="EAID_161BF4FA_8F48_441f_AF1F_D36014D57A1F" visibility="public" isRoot="false" isLeaf="false" isAbstract="false">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="style" value="3"/>
+ <UML:TaggedValue tag="ea_type" value="Association"/>
+ <UML:TaggedValue tag="direction" value="Source -&gt; Destination"/>
+ <UML:TaggedValue tag="linemode" value="3"/>
+ <UML:TaggedValue tag="seqno" value="0"/>
+ <UML:TaggedValue tag="headStyle" value="0"/>
+ <UML:TaggedValue tag="lineStyle" value="0"/>
+ <UML:TaggedValue tag="privatedata5" value="SX=8;SY=5;EX=8;EY=5;"/>
+ <UML:TaggedValue tag="virtualInheritance" value="0"/>
+ </UML:ModelElement.taggedValue>
+ <UML:Association.connection>
+ <UML:AssociationEnd visibility="public" aggregation="none" isOrdered="false" isNavigable="false" type="EAID_436D81AD_A9B0_44d8_8AD1_86BB0808DA32">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="containment" value="Unspecified"/>
+ </UML:ModelElement.taggedValue>
+ </UML:AssociationEnd>
+ <UML:AssociationEnd visibility="public" multiplicity="1" name="bathroom" aggregation="none" isOrdered="false" isNavigable="true" type="EAID_244CC9E5_1F81_4164_9215_C048EC679543">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="containment" value="Unspecified"/>
+ </UML:ModelElement.taggedValue>
+ </UML:AssociationEnd>
+ </UML:Association.connection>
+ </UML:Association>
+ <UML:Association xmi.id="EAID_9F0FF178_300F_45f4_B31F_9633AF770E44" visibility="public" isRoot="false" isLeaf="false" isAbstract="false">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="style" value="3"/>
+ <UML:TaggedValue tag="ea_type" value="Association"/>
+ <UML:TaggedValue tag="direction" value="Bi-Directional"/>
+ <UML:TaggedValue tag="linemode" value="3"/>
+ <UML:TaggedValue tag="seqno" value="0"/>
+ <UML:TaggedValue tag="headStyle" value="0"/>
+ <UML:TaggedValue tag="lineStyle" value="0"/>
+ <UML:TaggedValue tag="privatedata5" value="SX=6;SY=-12;EX=-44;EY=-9;EDGE=1;"/>
+ <UML:TaggedValue tag="virtualInheritance" value="0"/>
+ </UML:ModelElement.taggedValue>
+ <UML:Association.connection>
+ <UML:AssociationEnd visibility="public" multiplicity="1" name="kitchen" aggregation="none" isOrdered="false" isNavigable="true" type="EAID_9CE44C59_37E4_4117_8C05_F87C4DC33529">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="containment" value="Unspecified"/>
+ </UML:ModelElement.taggedValue>
+ </UML:AssociationEnd>
+ <UML:AssociationEnd visibility="public" multiplicity="1" name="house" aggregation="none" isOrdered="false" isNavigable="true" type="EAID_436D81AD_A9B0_44d8_8AD1_86BB0808DA32">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="containment" value="Unspecified"/>
+ </UML:ModelElement.taggedValue>
+ </UML:AssociationEnd>
+ </UML:Association.connection>
+ </UML:Association>
+ <UML:Association xmi.id="EAID_A9112D1C_DCB6_4040_B466_D5F560898B33" visibility="public" isRoot="false" isLeaf="false" isAbstract="false">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="style" value="3"/>
+ <UML:TaggedValue tag="ea_type" value="Association"/>
+ <UML:TaggedValue tag="direction" value="Source -&gt; Destination"/>
+ <UML:TaggedValue tag="linemode" value="3"/>
+ <UML:TaggedValue tag="seqno" value="0"/>
+ <UML:TaggedValue tag="headStyle" value="0"/>
+ <UML:TaggedValue tag="lineStyle" value="0"/>
+ <UML:TaggedValue tag="virtualInheritance" value="0"/>
+ </UML:ModelElement.taggedValue>
+ <UML:Association.connection>
+ <UML:AssociationEnd visibility="public" aggregation="none" isOrdered="false" isNavigable="false" type="EAID_2F1D9CC6_F38F_4a34_B3C4_B92915108384">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="containment" value="Unspecified"/>
+ </UML:ModelElement.taggedValue>
+ </UML:AssociationEnd>
+ <UML:AssociationEnd visibility="public" multiplicity="0..*" name="home" aggregation="none" isOrdered="false" isNavigable="true" type="EAID_436D81AD_A9B0_44d8_8AD1_86BB0808DA32">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="containment" value="Unspecified"/>
+ </UML:ModelElement.taggedValue>
+ </UML:AssociationEnd>
+ </UML:Association.connection>
+ </UML:Association>
+ <UML:Association xmi.id="EAID_F15A2B25_0DA9_4203_8FC9_25645610B5E5" visibility="public" isRoot="false" isLeaf="false" isAbstract="false">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="style" value="3"/>
+ <UML:TaggedValue tag="ea_type" value="Aggregation"/>
+ <UML:TaggedValue tag="direction" value="Source -&gt; Destination"/>
+ <UML:TaggedValue tag="linemode" value="3"/>
+ <UML:TaggedValue tag="seqno" value="0"/>
+ <UML:TaggedValue tag="subtype" value="Strong"/>
+ <UML:TaggedValue tag="headStyle" value="0"/>
+ <UML:TaggedValue tag="lineStyle" value="0"/>
+ <UML:TaggedValue tag="privatedata5" value="SX=-2;SY=-1;EX=-2;EY=-1;"/>
+ <UML:TaggedValue tag="virtualInheritance" value="0"/>
+ </UML:ModelElement.taggedValue>
+ <UML:Association.connection>
+ <UML:AssociationEnd visibility="public" multiplicity="1..*" name="room" aggregation="none" isOrdered="false" isNavigable="false" type="EAID_14DB5E54_CD7B_4c84_998C_44960049D7E0">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="containment" value="Unspecified"/>
+ </UML:ModelElement.taggedValue>
+ </UML:AssociationEnd>
+ <UML:AssociationEnd visibility="public" multiplicity="1" name="house" aggregation="composite" isOrdered="false" isNavigable="true" type="EAID_436D81AD_A9B0_44d8_8AD1_86BB0808DA32">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="containment" value="Unspecified"/>
+ </UML:ModelElement.taggedValue>
+ </UML:AssociationEnd>
+ </UML:Association.connection>
+ </UML:Association>
+ <UML:Collaboration xmi.id="EAID_A1B83D59_CAE1_422c_BA5F_D3624D7156AD_Collaboration" name="Collaborations">
+ <UML:Namespace.ownedElement>
+ <UML:ClassifierRole name="Rooms" xmi.id="EAID_F9D8C6E3_4DAD_4aa2_AD47_D0ABA4E93E08" visibility="public" base="EAID_11111111_5487_4080_A7F4_41526CB0AA00">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="isSpecification" value="false"/>
+ <UML:TaggedValue tag="ea_stype" value="Package"/>
+ <UML:TaggedValue tag="ea_ntype" value="0"/>
+ <UML:TaggedValue tag="version" value="1.0"/>
+ <UML:TaggedValue tag="package" value="EAPK_A1B83D59_CAE1_422c_BA5F_D3624D7156AD"/>
+ <UML:TaggedValue tag="date_created" value="2006-06-23 08:28:49"/>
+ <UML:TaggedValue tag="date_modified" value="2006-06-23 08:28:49"/>
+ <UML:TaggedValue tag="gentype" value="Java"/>
+ <UML:TaggedValue tag="tagged" value="0"/>
+ <UML:TaggedValue tag="package2" value="EAID_F9D8C6E3_4DAD_4aa2_AD47_D0ABA4E93E08"/>
+ <UML:TaggedValue tag="package_name" value="HouseMetamodel"/>
+ <UML:TaggedValue tag="phase" value="1.0"/>
+ <UML:TaggedValue tag="complexity" value="1"/>
+ <UML:TaggedValue tag="status" value="Proposed"/>
+ <UML:TaggedValue tag="style" value="BackColor=-1;BorderColor=-1;BorderWidth=-1;FontColor=-1;VSwimLanes=0;HSwimLanes=0;BorderStyle=0;"/>
+ </UML:ModelElement.taggedValue>
+ </UML:ClassifierRole>
+ </UML:Namespace.ownedElement>
+ <UML:Collaboration.interaction/>
+ </UML:Collaboration>
+ <UML:Class name="Person" xmi.id="EAID_2F1D9CC6_F38F_4a34_B3C4_B92915108384" visibility="public" namespace="EAPK_A1B83D59_CAE1_422c_BA5F_D3624D7156AD" isRoot="false" isLeaf="false" isAbstract="false" isActive="false">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="isSpecification" value="false"/>
+ <UML:TaggedValue tag="ea_stype" value="Class"/>
+ <UML:TaggedValue tag="ea_ntype" value="0"/>
+ <UML:TaggedValue tag="version" value="1.0"/>
+ <UML:TaggedValue tag="package" value="EAPK_A1B83D59_CAE1_422c_BA5F_D3624D7156AD"/>
+ <UML:TaggedValue tag="date_created" value="2006-06-27 08:34:23"/>
+ <UML:TaggedValue tag="date_modified" value="2006-06-27 08:34:26"/>
+ <UML:TaggedValue tag="gentype" value="Java"/>
+ <UML:TaggedValue tag="tagged" value="0"/>
+ <UML:TaggedValue tag="package_name" value="HouseMetamodel"/>
+ <UML:TaggedValue tag="phase" value="1.0"/>
+ <UML:TaggedValue tag="complexity" value="1"/>
+ <UML:TaggedValue tag="status" value="Proposed"/>
+ <UML:TaggedValue tag="style" value="BackColor=-1;BorderColor=-1;BorderWidth=-1;FontColor=-1;VSwimLanes=0;HSwimLanes=0;BorderStyle=0;"/>
+ </UML:ModelElement.taggedValue>
+ </UML:Class>
+ <UML:Comment xmi.id="EAID_9D7C072D_2F74_4348_B1D9_BA3CFA72FC6F" visibility="public" namespace="EAPK_A1B83D59_CAE1_422c_BA5F_D3624D7156AD">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="isSpecification" value="false"/>
+ <UML:TaggedValue tag="ea_stype" value="Note"/>
+ <UML:TaggedValue tag="ea_ntype" value="1"/>
+ <UML:TaggedValue tag="version" value="1.0"/>
+ <UML:TaggedValue tag="package" value="EAPK_A1B83D59_CAE1_422c_BA5F_D3624D7156AD"/>
+ <UML:TaggedValue tag="date_created" value="2006-07-07 08:08:53"/>
+ <UML:TaggedValue tag="date_modified" value="2006-07-07 08:09:48"/>
+ <UML:TaggedValue tag="gentype" value="&lt;none&gt;"/>
+ <UML:TaggedValue tag="tagged" value="0"/>
+ <UML:TaggedValue tag="package_name" value="HouseMetamodel"/>
+ <UML:TaggedValue tag="phase" value="1.0"/>
+ <UML:TaggedValue tag="complexity" value="1"/>
+ <UML:TaggedValue tag="documentation" value="this aggregation is navigable from room to house (EA does not show it)"/>
+ <UML:TaggedValue tag="status" value="Proposed"/>
+ <UML:TaggedValue tag="relatedlinks" value="idref1=EAID_F15A2B25_0DA9_4203_8FC9_25645610B5E5;"/>
+ <UML:TaggedValue tag="style" value="BackColor=-1;BorderColor=-1;BorderWidth=-1;FontColor=-1;VSwimLanes=0;HSwimLanes=0;BorderStyle=0;"/>
+ </UML:ModelElement.taggedValue>
+ </UML:Comment>
+ <UML:Class name="MeetingPlace" xmi.id="EAID_CB9E84D4_9064_49d0_BF1E_EE53C05853EF" visibility="public" namespace="EAPK_A1B83D59_CAE1_422c_BA5F_D3624D7156AD" isRoot="false" isLeaf="false" isAbstract="false" isActive="false">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="isSpecification" value="false"/>
+ <UML:TaggedValue tag="ea_stype" value="Class"/>
+ <UML:TaggedValue tag="ea_ntype" value="0"/>
+ <UML:TaggedValue tag="version" value="1.0"/>
+ <UML:TaggedValue tag="package" value="EAPK_A1B83D59_CAE1_422c_BA5F_D3624D7156AD"/>
+ <UML:TaggedValue tag="date_created" value="2006-07-12 08:40:46"/>
+ <UML:TaggedValue tag="date_modified" value="2007-06-14 08:22:21"/>
+ <UML:TaggedValue tag="gentype" value="Java"/>
+ <UML:TaggedValue tag="tagged" value="0"/>
+ <UML:TaggedValue tag="package_name" value="HouseMetamodel"/>
+ <UML:TaggedValue tag="phase" value="1.0"/>
+ <UML:TaggedValue tag="complexity" value="1"/>
+ <UML:TaggedValue tag="status" value="Proposed"/>
+ <UML:TaggedValue tag="style" value="BackColor=-1;BorderColor=-1;BorderWidth=-1;FontColor=-1;VSwimLanes=0;HSwimLanes=0;BorderStyle=0;"/>
+ </UML:ModelElement.taggedValue>
+ </UML:Class>
+ <UML:Generalization subtype="EAID_9CE44C59_37E4_4117_8C05_F87C4DC33529" supertype="EAID_CB9E84D4_9064_49d0_BF1E_EE53C05853EF" xmi.id="EAID_E824691D_2AE7_4c9c_8408_881A2B85516F" visibility="public">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="style" value="3"/>
+ <UML:TaggedValue tag="ea_type" value="Generalization"/>
+ <UML:TaggedValue tag="direction" value="Source -&gt; Destination"/>
+ <UML:TaggedValue tag="linemode" value="3"/>
+ <UML:TaggedValue tag="seqno" value="0"/>
+ <UML:TaggedValue tag="headStyle" value="0"/>
+ <UML:TaggedValue tag="lineStyle" value="0"/>
+ <UML:TaggedValue tag="src_visibility" value="Public"/>
+ <UML:TaggedValue tag="src_isOrdered" value="false"/>
+ <UML:TaggedValue tag="src_isNavigable" value="false"/>
+ <UML:TaggedValue tag="dst_visibility" value="Public"/>
+ <UML:TaggedValue tag="dst_isOrdered" value="false"/>
+ <UML:TaggedValue tag="dst_isNavigable" value="true"/>
+ <UML:TaggedValue tag="$ea_xref_property" value="$XREFPROP=$XID={8AAF19F7-EC93-4dda-ADD1-69A07CC671D7}$XID;$NAM=CustomProperties$NAM;$TYP=connector property$TYP;$VIS=Public$VIS;$DES=@PROP=@NAME=isSubstitutable@ENDNAME;@TYPE=boolean@ENDTYPE;@VALU=@ENDVALU;@PRMT=@ENDPRMT;@ENDPROP;$DES;$CLT={E824691D-2AE7-4c9c-8408-881A2B85516F}$CLT;$SUP=&lt;none&gt;$SUP;$ENDXREF;"/>
+ <UML:TaggedValue tag="privatedata5" value="SX=-24;SY=-16;"/>
+ </UML:ModelElement.taggedValue>
+ </UML:Generalization>
+ <UML:Interface name="CookingPlace" xmi.id="EAID_E21E535F_E300_4405_A917_28FFB4B2DB6E" visibility="public" namespace="EAPK_A1B83D59_CAE1_422c_BA5F_D3624D7156AD" isRoot="false" isLeaf="false" isAbstract="true">
+ <UML:ModelElement.stereotype>
+ <UML:Stereotype name="interface"/>
+ </UML:ModelElement.stereotype>
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="isSpecification" value="false"/>
+ <UML:TaggedValue tag="ea_stype" value="Interface"/>
+ <UML:TaggedValue tag="ea_ntype" value="0"/>
+ <UML:TaggedValue tag="version" value="1.0"/>
+ <UML:TaggedValue tag="package" value="EAPK_A1B83D59_CAE1_422c_BA5F_D3624D7156AD"/>
+ <UML:TaggedValue tag="date_created" value="2007-06-14 08:22:21"/>
+ <UML:TaggedValue tag="date_modified" value="2007-06-14 08:23:36"/>
+ <UML:TaggedValue tag="gentype" value="Java"/>
+ <UML:TaggedValue tag="tagged" value="0"/>
+ <UML:TaggedValue tag="package_name" value="HouseMetamodel"/>
+ <UML:TaggedValue tag="phase" value="1.0"/>
+ <UML:TaggedValue tag="complexity" value="1"/>
+ <UML:TaggedValue tag="status" value="Proposed"/>
+ <UML:TaggedValue tag="stereotype" value="interface"/>
+ <UML:TaggedValue tag="style" value="BackColor=-1;BorderColor=-1;BorderWidth=-1;FontColor=-1;VSwimLanes=0;HSwimLanes=0;BorderStyle=0;"/>
+ </UML:ModelElement.taggedValue>
+ </UML:Interface>
+ <UML:Dependency client="EAID_9CE44C59_37E4_4117_8C05_F87C4DC33529" supplier="EAID_E21E535F_E300_4405_A917_28FFB4B2DB6E" xmi.id="EAID_9613E9DD_8709_4e9e_92D8_2F6CA9835851" visibility="public">
+ <UML:ModelElement.stereotype>
+ <UML:Stereotype name="implements"/>
+ </UML:ModelElement.stereotype>
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="style" value="3"/>
+ <UML:TaggedValue tag="ea_type" value="Dependency"/>
+ <UML:TaggedValue tag="direction" value="Source -&gt; Destination"/>
+ <UML:TaggedValue tag="linemode" value="3"/>
+ <UML:TaggedValue tag="seqno" value="0"/>
+ <UML:TaggedValue tag="stereotype" value="implements"/>
+ <UML:TaggedValue tag="headStyle" value="0"/>
+ <UML:TaggedValue tag="lineStyle" value="0"/>
+ <UML:TaggedValue tag="conditional" value="«implements»"/>
+ <UML:TaggedValue tag="src_visibility" value="Public"/>
+ <UML:TaggedValue tag="src_aggregation" value="0"/>
+ <UML:TaggedValue tag="src_isOrdered" value="false"/>
+ <UML:TaggedValue tag="src_isNavigable" value="false"/>
+ <UML:TaggedValue tag="src_containment" value="Unspecified"/>
+ <UML:TaggedValue tag="dst_visibility" value="Public"/>
+ <UML:TaggedValue tag="dst_aggregation" value="0"/>
+ <UML:TaggedValue tag="dst_isOrdered" value="false"/>
+ <UML:TaggedValue tag="dst_isNavigable" value="true"/>
+ <UML:TaggedValue tag="dst_containment" value="Unspecified"/>
+ <UML:TaggedValue tag="virtualInheritance" value="0"/>
+ <UML:TaggedValue tag="mb" value="«implements»"/>
+ </UML:ModelElement.taggedValue>
+ </UML:Dependency>
+ <UML:Package name="Rooms" xmi.id="EAPK_F9D8C6E3_4DAD_4aa2_AD47_D0ABA4E93E08" isRoot="true" isLeaf="false" isAbstract="false" visibility="public">
+ <UML:ModelElement.name>Rooms</UML:ModelElement.name>
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="created" value="2006-06-23 00:00:00"/>
+ <UML:TaggedValue tag="modified" value="2006-06-23 00:00:00"/>
+ <UML:TaggedValue tag="iscontrolled" value="FALSE"/>
+ <UML:TaggedValue tag="isprotected" value="FALSE"/>
+ <UML:TaggedValue tag="usedtd" value="FALSE"/>
+ <UML:TaggedValue tag="logxml" value="FALSE"/>
+ <UML:TaggedValue tag="phase" value="1.0"/>
+ <UML:TaggedValue tag="status" value="Proposed"/>
+ <UML:TaggedValue tag="complexity" value="1"/>
+ <UML:TaggedValue tag="ea_stype" value="Public"/>
+ </UML:ModelElement.taggedValue>
+ <UML:Namespace.ownedElement>
+ <UML:Class name="Room" xmi.id="EAID_14DB5E54_CD7B_4c84_998C_44960049D7E0" visibility="public" namespace="EAPK_F9D8C6E3_4DAD_4aa2_AD47_D0ABA4E93E08" isRoot="false" isLeaf="false" isAbstract="false" isActive="false">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="isSpecification" value="false"/>
+ <UML:TaggedValue tag="ea_stype" value="Class"/>
+ <UML:TaggedValue tag="ea_ntype" value="0"/>
+ <UML:TaggedValue tag="version" value="1.0"/>
+ <UML:TaggedValue tag="package" value="EAPK_F9D8C6E3_4DAD_4aa2_AD47_D0ABA4E93E08"/>
+ <UML:TaggedValue tag="date_created" value="2005-09-16 19:52:28"/>
+ <UML:TaggedValue tag="date_modified" value="2006-06-22 21:15:25"/>
+ <UML:TaggedValue tag="gentype" value="Java"/>
+ <UML:TaggedValue tag="tagged" value="0"/>
+ <UML:TaggedValue tag="package_name" value="Rooms"/>
+ <UML:TaggedValue tag="phase" value="1.0"/>
+ <UML:TaggedValue tag="complexity" value="1"/>
+ <UML:TaggedValue tag="status" value="Proposed"/>
+ <UML:TaggedValue tag="style" value="BackColor=-1;BorderColor=-1;BorderWidth=-1;FontColor=-1;VSwimLanes=0;HSwimLanes=0;BorderStyle=0;"/>
+ </UML:ModelElement.taggedValue>
+ <UML:Classifier.feature>
+ <UML:Operation name="enter" visibility="public" ownerScope="instance" isQuery="false" concurrency="sequential">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="type" value="void"/>
+ <UML:TaggedValue tag="const" value="false"/>
+ <UML:TaggedValue tag="synchronised" value="0"/>
+ <UML:TaggedValue tag="concurrency" value="Sequential"/>
+ <UML:TaggedValue tag="position" value="0"/>
+ <UML:TaggedValue tag="returnarray" value="0"/>
+ <UML:TaggedValue tag="pure" value="0"/>
+ <UML:TaggedValue tag="ea_guid" value="{A8C01177-4523-44c4-87F7-F2B8F9F3C915}"/>
+ </UML:ModelElement.taggedValue>
+ <UML:BehavioralFeature.parameter>
+ <UML:Parameter kind="return" visibility="public">
+ <UML:Parameter.type>
+ <UML:Classifier xmi.idref="eaxmiid1"/>
+ </UML:Parameter.type>
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="pos" value="0"/>
+ <UML:TaggedValue tag="type" value="void"/>
+ <UML:TaggedValue tag="const" value="0"/>
+ <UML:TaggedValue tag="ea_guid" value="{A8C01177-4523-44c4-87F7-F2B8F9F3C915}"/>
+ </UML:ModelElement.taggedValue>
+ <UML:Parameter.defaultValue>
+ <UML:Expression/>
+ </UML:Parameter.defaultValue>
+ </UML:Parameter>
+ </UML:BehavioralFeature.parameter>
+ </UML:Operation>
+ </UML:Classifier.feature>
+ </UML:Class>
+ <UML:Generalization subtype="EAID_244CC9E5_1F81_4164_9215_C048EC679543" supertype="EAID_14DB5E54_CD7B_4c84_998C_44960049D7E0" xmi.id="EAID_03052911_3625_4472_8C6B_5E1742FED753" visibility="public">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="style" value="3"/>
+ <UML:TaggedValue tag="ea_type" value="Generalization"/>
+ <UML:TaggedValue tag="direction" value="Source -&gt; Destination"/>
+ <UML:TaggedValue tag="linemode" value="3"/>
+ <UML:TaggedValue tag="seqno" value="0"/>
+ <UML:TaggedValue tag="headStyle" value="0"/>
+ <UML:TaggedValue tag="lineStyle" value="0"/>
+ <UML:TaggedValue tag="src_visibility" value="Public"/>
+ <UML:TaggedValue tag="src_isOrdered" value="false"/>
+ <UML:TaggedValue tag="src_isNavigable" value="false"/>
+ <UML:TaggedValue tag="dst_visibility" value="Public"/>
+ <UML:TaggedValue tag="dst_isOrdered" value="false"/>
+ <UML:TaggedValue tag="dst_isNavigable" value="true"/>
+ <UML:TaggedValue tag="$ea_xref_property" value="$XREFPROP=$XID={DF243A69-1029-4c4c-A1A0-C821A1DF9623}$XID;$NAM=CustomProperties$NAM;$TYP=connector property$TYP;$VIS=Public$VIS;$DES=@PROP=@NAME=isSubstitutable@ENDNAME;@TYPE=boolean@ENDTYPE;@VALU=@ENDVALU;@PRMT=@ENDPRMT;@ENDPROP;$DES;$CLT={03052911-3625-4472-8C6B-5E1742FED753}$CLT;$SUP=&lt;none&gt;$SUP;$ENDXREF;"/>
+ </UML:ModelElement.taggedValue>
+ </UML:Generalization>
+ <UML:Generalization subtype="EAID_9CE44C59_37E4_4117_8C05_F87C4DC33529" supertype="EAID_14DB5E54_CD7B_4c84_998C_44960049D7E0" xmi.id="EAID_C3D18D92_3A5C_4112_9958_926A259BAE24" visibility="public">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="style" value="3"/>
+ <UML:TaggedValue tag="ea_type" value="Generalization"/>
+ <UML:TaggedValue tag="direction" value="Source -&gt; Destination"/>
+ <UML:TaggedValue tag="linemode" value="3"/>
+ <UML:TaggedValue tag="seqno" value="0"/>
+ <UML:TaggedValue tag="headStyle" value="0"/>
+ <UML:TaggedValue tag="lineStyle" value="0"/>
+ <UML:TaggedValue tag="src_visibility" value="Public"/>
+ <UML:TaggedValue tag="src_isOrdered" value="false"/>
+ <UML:TaggedValue tag="src_isNavigable" value="false"/>
+ <UML:TaggedValue tag="dst_visibility" value="Public"/>
+ <UML:TaggedValue tag="dst_isOrdered" value="false"/>
+ <UML:TaggedValue tag="dst_isNavigable" value="true"/>
+ <UML:TaggedValue tag="$ea_xref_property" value="$XREFPROP=$XID={DB1C706A-470A-45ed-89DA-601821419545}$XID;$NAM=CustomProperties$NAM;$TYP=connector property$TYP;$VIS=Public$VIS;$DES=@PROP=@NAME=isSubstitutable@ENDNAME;@TYPE=boolean@ENDTYPE;@VALU=@ENDVALU;@PRMT=@ENDPRMT;@ENDPROP;$DES;$CLT={C3D18D92-3A5C-4112-9958-926A259BAE24}$CLT;$SUP=&lt;none&gt;$SUP;$ENDXREF;"/>
+ <UML:TaggedValue tag="privatedata5" value="EX=37;EY=3;"/>
+ </UML:ModelElement.taggedValue>
+ </UML:Generalization>
+ <UML:Class name="Kitchen" xmi.id="EAID_9CE44C59_37E4_4117_8C05_F87C4DC33529" visibility="public" namespace="EAPK_F9D8C6E3_4DAD_4aa2_AD47_D0ABA4E93E08" isRoot="false" isLeaf="false" isAbstract="false" isActive="false">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="isSpecification" value="false"/>
+ <UML:TaggedValue tag="ea_stype" value="Class"/>
+ <UML:TaggedValue tag="ea_ntype" value="0"/>
+ <UML:TaggedValue tag="version" value="1.0"/>
+ <UML:TaggedValue tag="package" value="EAPK_F9D8C6E3_4DAD_4aa2_AD47_D0ABA4E93E08"/>
+ <UML:TaggedValue tag="date_created" value="2005-11-30 19:26:13"/>
+ <UML:TaggedValue tag="date_modified" value="2006-06-22 21:15:34"/>
+ <UML:TaggedValue tag="gentype" value="Java"/>
+ <UML:TaggedValue tag="tagged" value="0"/>
+ <UML:TaggedValue tag="package_name" value="Rooms"/>
+ <UML:TaggedValue tag="phase" value="1.0"/>
+ <UML:TaggedValue tag="complexity" value="1"/>
+ <UML:TaggedValue tag="status" value="Proposed"/>
+ <UML:TaggedValue tag="style" value="BackColor=-1;BorderColor=-1;BorderWidth=-1;FontColor=-1;VSwimLanes=0;HSwimLanes=0;BorderStyle=0;"/>
+ </UML:ModelElement.taggedValue>
+ </UML:Class>
+ <UML:Class name="Bathroom" xmi.id="EAID_244CC9E5_1F81_4164_9215_C048EC679543" visibility="public" namespace="EAPK_F9D8C6E3_4DAD_4aa2_AD47_D0ABA4E93E08" isRoot="false" isLeaf="false" isAbstract="false" isActive="false">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="isSpecification" value="false"/>
+ <UML:TaggedValue tag="ea_stype" value="Class"/>
+ <UML:TaggedValue tag="ea_ntype" value="0"/>
+ <UML:TaggedValue tag="version" value="1.0"/>
+ <UML:TaggedValue tag="package" value="EAPK_F9D8C6E3_4DAD_4aa2_AD47_D0ABA4E93E08"/>
+ <UML:TaggedValue tag="date_created" value="2006-06-27 08:32:25"/>
+ <UML:TaggedValue tag="date_modified" value="2006-06-27 08:34:23"/>
+ <UML:TaggedValue tag="gentype" value="Java"/>
+ <UML:TaggedValue tag="tagged" value="0"/>
+ <UML:TaggedValue tag="package_name" value="Rooms"/>
+ <UML:TaggedValue tag="phase" value="1.0"/>
+ <UML:TaggedValue tag="complexity" value="1"/>
+ <UML:TaggedValue tag="status" value="Proposed"/>
+ <UML:TaggedValue tag="style" value="BackColor=-1;BorderColor=-1;BorderWidth=-1;FontColor=-1;VSwimLanes=0;HSwimLanes=0;BorderStyle=0;"/>
+ </UML:ModelElement.taggedValue>
+ </UML:Class>
+ </UML:Namespace.ownedElement>
+ </UML:Package>
+ </UML:Namespace.ownedElement>
+ </UML:Package>
+ <UML:Package name="HouseExampleModel" xmi.id="EAPK_06C9C958_C14A_41f4_89A9_6873CCED37A7" isRoot="true" isLeaf="false" isAbstract="false" visibility="public">
+ <UML:ModelElement.name>HouseExampleModel</UML:ModelElement.name>
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="created" value="2006-07-20 00:00:00"/>
+ <UML:TaggedValue tag="modified" value="2006-07-20 00:00:00"/>
+ <UML:TaggedValue tag="iscontrolled" value="FALSE"/>
+ <UML:TaggedValue tag="isprotected" value="FALSE"/>
+ <UML:TaggedValue tag="usedtd" value="FALSE"/>
+ <UML:TaggedValue tag="logxml" value="FALSE"/>
+ <UML:TaggedValue tag="ea_package_id" value="46"/>
+ <UML:TaggedValue tag="phase" value="1.0"/>
+ <UML:TaggedValue tag="status" value="Proposed"/>
+ <UML:TaggedValue tag="complexity" value="1"/>
+ <UML:TaggedValue tag="ea_stype" value="Public"/>
+ </UML:ModelElement.taggedValue>
+ <UML:Namespace.ownedElement>
+ <UML:Collaboration xmi.id="EAID_06C9C958_C14A_41f4_89A9_6873CCED37A7_Collaboration" name="Collaborations">
+ <UML:Namespace.ownedElement>
+ <UML:ClassifierRole name="SomeonesHouse" xmi.id="EAID_3D59084A_BBC6_4d6b_B8E5_9A764D27563B" visibility="public" base="EAID_11111111_5487_4080_A7F4_41526CB0AA00">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="isSpecification" value="false"/>
+ <UML:TaggedValue tag="ea_stype" value="Object"/>
+ <UML:TaggedValue tag="ea_ntype" value="0"/>
+ <UML:TaggedValue tag="version" value="1.0"/>
+ <UML:TaggedValue tag="classifier" value="EAID_436D81AD_A9B0_44d8_8AD1_86BB0808DA32"/>
+ <UML:TaggedValue tag="package" value="EAPK_06C9C958_C14A_41f4_89A9_6873CCED37A7"/>
+ <UML:TaggedValue tag="classname" value="House"/>
+ <UML:TaggedValue tag="date_created" value="2006-07-20 18:35:22"/>
+ <UML:TaggedValue tag="date_modified" value="2006-07-21 19:34:53"/>
+ <UML:TaggedValue tag="gentype" value="Java"/>
+ <UML:TaggedValue tag="tagged" value="0"/>
+ <UML:TaggedValue tag="package_name" value="HouseExampleModel"/>
+ <UML:TaggedValue tag="phase" value="1.0"/>
+ <UML:TaggedValue tag="complexity" value="1"/>
+ <UML:TaggedValue tag="status" value="Proposed"/>
+ <UML:TaggedValue tag="style" value="BackColor=-1;BorderColor=-1;BorderWidth=-1;FontColor=-1;VSwimLanes=0;HSwimLanes=0;BorderStyle=0;"/>
+ </UML:ModelElement.taggedValue>
+ </UML:ClassifierRole>
+ <UML:AssociationRole xmi.id="EAID_AFD66AB6_337B_49c5_9FA9_0CFAFEB77AA7" visibility="public" isRoot="false" isLeaf="false" isAbstract="false">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="style" value="3"/>
+ <UML:TaggedValue tag="ea_type" value="Association"/>
+ <UML:TaggedValue tag="direction" value="Unspecified"/>
+ <UML:TaggedValue tag="linemode" value="3"/>
+ <UML:TaggedValue tag="seqno" value="0"/>
+ <UML:TaggedValue tag="headStyle" value="0"/>
+ <UML:TaggedValue tag="lineStyle" value="0"/>
+ <UML:TaggedValue tag="src_visibility" value="Public"/>
+ <UML:TaggedValue tag="src_aggregation" value="0"/>
+ <UML:TaggedValue tag="src_isOrdered" value="false"/>
+ <UML:TaggedValue tag="src_isNavigable" value="true"/>
+ <UML:TaggedValue tag="src_containment" value="Unspecified"/>
+ <UML:TaggedValue tag="dst_visibility" value="Public"/>
+ <UML:TaggedValue tag="dst_name" value="home"/>
+ <UML:TaggedValue tag="dst_aggregation" value="0"/>
+ <UML:TaggedValue tag="dst_isOrdered" value="false"/>
+ <UML:TaggedValue tag="dst_isNavigable" value="true"/>
+ <UML:TaggedValue tag="dst_containment" value="Unspecified"/>
+ <UML:TaggedValue tag="virtualInheritance" value="0"/>
+ </UML:ModelElement.taggedValue>
+ <UML:Association.connection>
+ <UML:AssociationEndRole visibility="public" aggregation="none" isOrdered="false" isNavigable="true" type="EAID_960E757D_3D81_43e2_BA2E_5F7341421EA5">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="containment" value="Unspecified"/>
+ </UML:ModelElement.taggedValue>
+ </UML:AssociationEndRole>
+ <UML:AssociationEndRole visibility="public" name="home" aggregation="none" isOrdered="false" isNavigable="true" type="EAID_3D59084A_BBC6_4d6b_B8E5_9A764D27563B">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="containment" value="Unspecified"/>
+ </UML:ModelElement.taggedValue>
+ </UML:AssociationEndRole>
+ </UML:Association.connection>
+ </UML:AssociationRole>
+ <UML:ClassifierRole name="GreenRoom" xmi.id="EAID_597D270D_65EB_4941_908B_A42419BD7C3F" visibility="public" base="EAID_11111111_5487_4080_A7F4_41526CB0AA00">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="isSpecification" value="false"/>
+ <UML:TaggedValue tag="ea_stype" value="Object"/>
+ <UML:TaggedValue tag="ea_ntype" value="0"/>
+ <UML:TaggedValue tag="version" value="1.0"/>
+ <UML:TaggedValue tag="classifier" value="EAID_14DB5E54_CD7B_4c84_998C_44960049D7E0"/>
+ <UML:TaggedValue tag="package" value="EAPK_06C9C958_C14A_41f4_89A9_6873CCED37A7"/>
+ <UML:TaggedValue tag="classname" value="Room"/>
+ <UML:TaggedValue tag="date_created" value="2006-07-20 18:35:32"/>
+ <UML:TaggedValue tag="date_modified" value="2006-07-21 19:34:11"/>
+ <UML:TaggedValue tag="gentype" value="Java"/>
+ <UML:TaggedValue tag="tagged" value="0"/>
+ <UML:TaggedValue tag="package_name" value="HouseExampleModel"/>
+ <UML:TaggedValue tag="phase" value="1.0"/>
+ <UML:TaggedValue tag="complexity" value="1"/>
+ <UML:TaggedValue tag="status" value="Proposed"/>
+ <UML:TaggedValue tag="style" value="BackColor=-1;BorderColor=-1;BorderWidth=-1;FontColor=-1;VSwimLanes=0;HSwimLanes=0;BorderStyle=0;"/>
+ </UML:ModelElement.taggedValue>
+ </UML:ClassifierRole>
+ <UML:ClassifierRole name="YellowRoom" xmi.id="EAID_5D770C43_247F_41e6_BC35_93C5A9204E76" visibility="public" base="EAID_11111111_5487_4080_A7F4_41526CB0AA00">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="isSpecification" value="false"/>
+ <UML:TaggedValue tag="ea_stype" value="Object"/>
+ <UML:TaggedValue tag="ea_ntype" value="0"/>
+ <UML:TaggedValue tag="version" value="1.0"/>
+ <UML:TaggedValue tag="classifier" value="EAID_14DB5E54_CD7B_4c84_998C_44960049D7E0"/>
+ <UML:TaggedValue tag="package" value="EAPK_06C9C958_C14A_41f4_89A9_6873CCED37A7"/>
+ <UML:TaggedValue tag="classname" value="Room"/>
+ <UML:TaggedValue tag="date_created" value="2006-07-21 19:22:58"/>
+ <UML:TaggedValue tag="date_modified" value="2006-07-21 19:34:27"/>
+ <UML:TaggedValue tag="gentype" value="Java"/>
+ <UML:TaggedValue tag="tagged" value="0"/>
+ <UML:TaggedValue tag="package_name" value="HouseExampleModel"/>
+ <UML:TaggedValue tag="phase" value="1.0"/>
+ <UML:TaggedValue tag="complexity" value="1"/>
+ <UML:TaggedValue tag="status" value="Proposed"/>
+ <UML:TaggedValue tag="style" value="BackColor=-1;BorderColor=-1;BorderWidth=-1;FontColor=-1;VSwimLanes=0;HSwimLanes=0;BorderStyle=0;"/>
+ </UML:ModelElement.taggedValue>
+ </UML:ClassifierRole>
+ <UML:ClassifierRole name="HotRoom" xmi.id="EAID_9907C98A_7E63_47dc_8A4D_ED020B68C41D" visibility="public" base="EAID_11111111_5487_4080_A7F4_41526CB0AA00">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="isSpecification" value="false"/>
+ <UML:TaggedValue tag="ea_stype" value="Object"/>
+ <UML:TaggedValue tag="ea_ntype" value="0"/>
+ <UML:TaggedValue tag="version" value="1.0"/>
+ <UML:TaggedValue tag="classifier" value="EAID_9CE44C59_37E4_4117_8C05_F87C4DC33529"/>
+ <UML:TaggedValue tag="package" value="EAPK_06C9C958_C14A_41f4_89A9_6873CCED37A7"/>
+ <UML:TaggedValue tag="classname" value="Kitchen"/>
+ <UML:TaggedValue tag="date_created" value="2006-07-21 19:27:21"/>
+ <UML:TaggedValue tag="date_modified" value="2006-07-21 19:33:56"/>
+ <UML:TaggedValue tag="gentype" value="Java"/>
+ <UML:TaggedValue tag="tagged" value="0"/>
+ <UML:TaggedValue tag="package_name" value="HouseExampleModel"/>
+ <UML:TaggedValue tag="phase" value="1.0"/>
+ <UML:TaggedValue tag="complexity" value="1"/>
+ <UML:TaggedValue tag="status" value="Proposed"/>
+ <UML:TaggedValue tag="style" value="BackColor=-1;BorderColor=-1;BorderWidth=-1;FontColor=-1;VSwimLanes=0;HSwimLanes=0;BorderStyle=0;"/>
+ </UML:ModelElement.taggedValue>
+ </UML:ClassifierRole>
+ <UML:ClassifierRole name="Someone" xmi.id="EAID_960E757D_3D81_43e2_BA2E_5F7341421EA5" visibility="public" base="EAID_11111111_5487_4080_A7F4_41526CB0AA00">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="isSpecification" value="false"/>
+ <UML:TaggedValue tag="ea_stype" value="Object"/>
+ <UML:TaggedValue tag="ea_ntype" value="0"/>
+ <UML:TaggedValue tag="version" value="1.0"/>
+ <UML:TaggedValue tag="classifier" value="EAID_2F1D9CC6_F38F_4a34_B3C4_B92915108384"/>
+ <UML:TaggedValue tag="package" value="EAPK_06C9C958_C14A_41f4_89A9_6873CCED37A7"/>
+ <UML:TaggedValue tag="classname" value="Person"/>
+ <UML:TaggedValue tag="date_created" value="2006-07-21 19:29:45"/>
+ <UML:TaggedValue tag="date_modified" value="2006-07-21 19:35:00"/>
+ <UML:TaggedValue tag="gentype" value="Java"/>
+ <UML:TaggedValue tag="tagged" value="0"/>
+ <UML:TaggedValue tag="package_name" value="HouseExampleModel"/>
+ <UML:TaggedValue tag="phase" value="1.0"/>
+ <UML:TaggedValue tag="complexity" value="1"/>
+ <UML:TaggedValue tag="status" value="Proposed"/>
+ <UML:TaggedValue tag="style" value="BackColor=-1;BorderColor=-1;BorderWidth=-1;FontColor=-1;VSwimLanes=0;HSwimLanes=0;BorderStyle=0;"/>
+ </UML:ModelElement.taggedValue>
+ </UML:ClassifierRole>
+ <UML:ClassifierRole name="WetRoom" xmi.id="EAID_783325F8_9AA7_4836_A0D9_DDA9103CDC71" visibility="public" base="EAID_11111111_5487_4080_A7F4_41526CB0AA00">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="isSpecification" value="false"/>
+ <UML:TaggedValue tag="ea_stype" value="Object"/>
+ <UML:TaggedValue tag="ea_ntype" value="0"/>
+ <UML:TaggedValue tag="version" value="1.0"/>
+ <UML:TaggedValue tag="classifier" value="EAID_244CC9E5_1F81_4164_9215_C048EC679543"/>
+ <UML:TaggedValue tag="package" value="EAPK_06C9C958_C14A_41f4_89A9_6873CCED37A7"/>
+ <UML:TaggedValue tag="classname" value="Bathroom"/>
+ <UML:TaggedValue tag="date_created" value="2006-07-21 19:31:18"/>
+ <UML:TaggedValue tag="date_modified" value="2006-07-21 19:34:02"/>
+ <UML:TaggedValue tag="gentype" value="Java"/>
+ <UML:TaggedValue tag="tagged" value="0"/>
+ <UML:TaggedValue tag="package_name" value="HouseExampleModel"/>
+ <UML:TaggedValue tag="phase" value="1.0"/>
+ <UML:TaggedValue tag="complexity" value="1"/>
+ <UML:TaggedValue tag="status" value="Proposed"/>
+ <UML:TaggedValue tag="style" value="BackColor=-1;BorderColor=-1;BorderWidth=-1;FontColor=-1;VSwimLanes=0;HSwimLanes=0;BorderStyle=0;"/>
+ </UML:ModelElement.taggedValue>
+ </UML:ClassifierRole>
+ </UML:Namespace.ownedElement>
+ <UML:Collaboration.interaction/>
+ </UML:Collaboration>
+ <UML:Association xmi.id="EAID_3F0E9949_A749_4958_9A1B_40E3F0E12393" visibility="public" isRoot="false" isLeaf="false" isAbstract="false">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="style" value="3"/>
+ <UML:TaggedValue tag="ea_type" value="Aggregation"/>
+ <UML:TaggedValue tag="direction" value="Source -&gt; Destination"/>
+ <UML:TaggedValue tag="linemode" value="3"/>
+ <UML:TaggedValue tag="seqno" value="0"/>
+ <UML:TaggedValue tag="subtype" value="Strong"/>
+ <UML:TaggedValue tag="headStyle" value="0"/>
+ <UML:TaggedValue tag="lineStyle" value="0"/>
+ <UML:TaggedValue tag="virtualInheritance" value="0"/>
+ </UML:ModelElement.taggedValue>
+ <UML:Association.connection>
+ <UML:AssociationEnd visibility="public" name="room" aggregation="none" isOrdered="false" isNavigable="false" type="EAID_597D270D_65EB_4941_908B_A42419BD7C3F">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="containment" value="Unspecified"/>
+ </UML:ModelElement.taggedValue>
+ </UML:AssociationEnd>
+ <UML:AssociationEnd visibility="public" aggregation="composite" isOrdered="false" isNavigable="true" type="EAID_3D59084A_BBC6_4d6b_B8E5_9A764D27563B">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="containment" value="Unspecified"/>
+ </UML:ModelElement.taggedValue>
+ </UML:AssociationEnd>
+ </UML:Association.connection>
+ </UML:Association>
+ <UML:Association xmi.id="EAID_9DDBD985_D9D5_408c_A8F4_40085F2E8E5B" visibility="public" isRoot="false" isLeaf="false" isAbstract="false">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="style" value="3"/>
+ <UML:TaggedValue tag="ea_type" value="Aggregation"/>
+ <UML:TaggedValue tag="direction" value="Source -&gt; Destination"/>
+ <UML:TaggedValue tag="linemode" value="3"/>
+ <UML:TaggedValue tag="seqno" value="0"/>
+ <UML:TaggedValue tag="subtype" value="Strong"/>
+ <UML:TaggedValue tag="headStyle" value="0"/>
+ <UML:TaggedValue tag="lineStyle" value="0"/>
+ <UML:TaggedValue tag="virtualInheritance" value="0"/>
+ </UML:ModelElement.taggedValue>
+ <UML:Association.connection>
+ <UML:AssociationEnd visibility="public" name="room" aggregation="none" isOrdered="false" isNavigable="false" type="EAID_9907C98A_7E63_47dc_8A4D_ED020B68C41D">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="containment" value="Unspecified"/>
+ </UML:ModelElement.taggedValue>
+ </UML:AssociationEnd>
+ <UML:AssociationEnd visibility="public" aggregation="composite" isOrdered="false" isNavigable="true" type="EAID_3D59084A_BBC6_4d6b_B8E5_9A764D27563B">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="containment" value="Unspecified"/>
+ </UML:ModelElement.taggedValue>
+ </UML:AssociationEnd>
+ </UML:Association.connection>
+ </UML:Association>
+ <UML:Association xmi.id="EAID_D775B2C1_C7AF_4a19_A4D5_7B3AC816664C" visibility="public" isRoot="false" isLeaf="false" isAbstract="false">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="style" value="3"/>
+ <UML:TaggedValue tag="ea_type" value="Aggregation"/>
+ <UML:TaggedValue tag="direction" value="Source -&gt; Destination"/>
+ <UML:TaggedValue tag="linemode" value="3"/>
+ <UML:TaggedValue tag="seqno" value="0"/>
+ <UML:TaggedValue tag="subtype" value="Strong"/>
+ <UML:TaggedValue tag="headStyle" value="0"/>
+ <UML:TaggedValue tag="lineStyle" value="0"/>
+ <UML:TaggedValue tag="virtualInheritance" value="0"/>
+ </UML:ModelElement.taggedValue>
+ <UML:Association.connection>
+ <UML:AssociationEnd visibility="public" name="room" aggregation="none" isOrdered="false" isNavigable="false" type="EAID_5D770C43_247F_41e6_BC35_93C5A9204E76">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="containment" value="Unspecified"/>
+ </UML:ModelElement.taggedValue>
+ </UML:AssociationEnd>
+ <UML:AssociationEnd visibility="public" aggregation="composite" isOrdered="false" isNavigable="true" type="EAID_3D59084A_BBC6_4d6b_B8E5_9A764D27563B">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="containment" value="Unspecified"/>
+ </UML:ModelElement.taggedValue>
+ </UML:AssociationEnd>
+ </UML:Association.connection>
+ </UML:Association>
+ <UML:Association xmi.id="EAID_FF2ECD20_22FF_4734_9DB4_8316A98DDC16" visibility="public" isRoot="false" isLeaf="false" isAbstract="false">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="style" value="3"/>
+ <UML:TaggedValue tag="ea_type" value="Aggregation"/>
+ <UML:TaggedValue tag="direction" value="Source -&gt; Destination"/>
+ <UML:TaggedValue tag="linemode" value="3"/>
+ <UML:TaggedValue tag="seqno" value="0"/>
+ <UML:TaggedValue tag="subtype" value="Strong"/>
+ <UML:TaggedValue tag="headStyle" value="0"/>
+ <UML:TaggedValue tag="lineStyle" value="0"/>
+ <UML:TaggedValue tag="privatedata5" value="SX=36;SY=-9;EX=45;EY=-9;"/>
+ <UML:TaggedValue tag="virtualInheritance" value="0"/>
+ </UML:ModelElement.taggedValue>
+ <UML:Association.connection>
+ <UML:AssociationEnd visibility="public" name="room" aggregation="none" isOrdered="false" isNavigable="false" type="EAID_783325F8_9AA7_4836_A0D9_DDA9103CDC71">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="containment" value="Unspecified"/>
+ </UML:ModelElement.taggedValue>
+ </UML:AssociationEnd>
+ <UML:AssociationEnd visibility="public" aggregation="composite" isOrdered="false" isNavigable="true" type="EAID_3D59084A_BBC6_4d6b_B8E5_9A764D27563B">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="containment" value="Unspecified"/>
+ </UML:ModelElement.taggedValue>
+ </UML:AssociationEnd>
+ </UML:Association.connection>
+ </UML:Association>
+ </UML:Namespace.ownedElement>
+ </UML:Package>
+ </UML:Namespace.ownedElement>
+ </UML:Package>
+ <UML:DataType xmi.id="eaxmiid0" name="String" visibility="private" isRoot="false" isLeaf="false" isAbstract="false"/>
+ <UML:DataType xmi.id="eaxmiid1" name="void" visibility="private" isRoot="false" isLeaf="false" isAbstract="false"/>
+ </UML:Namespace.ownedElement>
+ </UML:Model>
+ <UML:DiagramElement geometry="Left=260;Top=103;Right=359;Bottom=173;" subject="EAID_436D81AD_A9B0_44d8_8AD1_86BB0808DA32" seqno="7" style="DUID=CDDEF3BC;LBL=;">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="diagram_guid" value="EAID_2FBE7B3F_756C_46a0_90EE_93429FDA065D"/>
+ </UML:ModelElement.taggedValue>
+ </UML:DiagramElement>
+ <UML:DiagramElement geometry="Left=472;Top=163;Right=562;Bottom=233;" subject="EAID_2F1D9CC6_F38F_4a34_B3C4_B92915108384" seqno="3" style="DUID=12DDDFF9;LBL=;">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="diagram_guid" value="EAID_2FBE7B3F_756C_46a0_90EE_93429FDA065D"/>
+ </UML:ModelElement.taggedValue>
+ </UML:DiagramElement>
+ <UML:DiagramElement geometry="Left=415;Top=252;Right=530;Bottom=323;" subject="EAID_9D7C072D_2F74_4348_B1D9_BA3CFA72FC6F" seqno="8" style="DUID=FAD2A1B5;LBL=;">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="diagram_guid" value="EAID_2FBE7B3F_756C_46a0_90EE_93429FDA065D"/>
+ </UML:ModelElement.taggedValue>
+ </UML:DiagramElement>
+ <UML:DiagramElement geometry="Left=21;Top=241;Right=111;Bottom=311;" subject="EAID_CB9E84D4_9064_49d0_BF1E_EE53C05853EF" seqno="2" style="DUID=E58486A8;LBL=;">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="diagram_guid" value="EAID_2FBE7B3F_756C_46a0_90EE_93429FDA065D"/>
+ </UML:ModelElement.taggedValue>
+ </UML:DiagramElement>
+ <UML:DiagramElement geometry="Left=108;Top=132;Right=198;Bottom=222;" subject="EAID_E21E535F_E300_4405_A917_28FFB4B2DB6E" seqno="1" style="DUID=1CE36E20;">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="diagram_guid" value="EAID_2FBE7B3F_756C_46a0_90EE_93429FDA065D"/>
+ </UML:ModelElement.taggedValue>
+ </UML:DiagramElement>
+ <UML:DiagramElement geometry="Left=265;Top=280;Right=355;Bottom=350;" subject="EAID_14DB5E54_CD7B_4c84_998C_44960049D7E0" seqno="6" style="DUID=BFDAC143;LBL=;">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="diagram_guid" value="EAID_2FBE7B3F_756C_46a0_90EE_93429FDA065D"/>
+ </UML:ModelElement.taggedValue>
+ </UML:DiagramElement>
+ <UML:DiagramElement geometry="Left=145;Top=402;Right=235;Bottom=472;" subject="EAID_9CE44C59_37E4_4117_8C05_F87C4DC33529" seqno="5" style="DUID=BDEA24A9;LBL=;">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="diagram_guid" value="EAID_2FBE7B3F_756C_46a0_90EE_93429FDA065D"/>
+ </UML:ModelElement.taggedValue>
+ </UML:DiagramElement>
+ <UML:DiagramElement geometry="Left=394;Top=412;Right=484;Bottom=482;" subject="EAID_244CC9E5_1F81_4164_9215_C048EC679543" seqno="4" style="DUID=5DECEEC9;LBL=;">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="diagram_guid" value="EAID_2FBE7B3F_756C_46a0_90EE_93429FDA065D"/>
+ </UML:ModelElement.taggedValue>
+ </UML:DiagramElement>
+ <UML:DiagramElement geometry="Left=242;Top=137;Right=530;Bottom=187;" subject="EAID_3D59084A_BBC6_4d6b_B8E5_9A764D27563B" seqno="6" style="DUID=3D79E0A4;">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="diagram_guid" value="EAID_2DBA38A4_BB85_46dc_A5ED_C43AD3050EC4"/>
+ </UML:ModelElement.taggedValue>
+ </UML:DiagramElement>
+ <UML:DiagramElement geometry="Left=145;Top=288;Right=235;Bottom=338;" subject="EAID_597D270D_65EB_4941_908B_A42419BD7C3F" seqno="5" style="DUID=E585EE32;">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="diagram_guid" value="EAID_2DBA38A4_BB85_46dc_A5ED_C43AD3050EC4"/>
+ </UML:ModelElement.taggedValue>
+ </UML:DiagramElement>
+ <UML:DiagramElement geometry="Left=279;Top=289;Right=369;Bottom=339;" subject="EAID_5D770C43_247F_41e6_BC35_93C5A9204E76" seqno="4" style="DUID=DFC93F21;">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="diagram_guid" value="EAID_2DBA38A4_BB85_46dc_A5ED_C43AD3050EC4"/>
+ </UML:ModelElement.taggedValue>
+ </UML:DiagramElement>
+ <UML:DiagramElement geometry="Left=414;Top=289;Right=504;Bottom=339;" subject="EAID_9907C98A_7E63_47dc_8A4D_ED020B68C41D" seqno="3" style="DUID=EE2397B7;">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="diagram_guid" value="EAID_2DBA38A4_BB85_46dc_A5ED_C43AD3050EC4"/>
+ </UML:ModelElement.taggedValue>
+ </UML:DiagramElement>
+ <UML:DiagramElement geometry="Left=46;Top=136;Right=136;Bottom=186;" subject="EAID_960E757D_3D81_43e2_BA2E_5F7341421EA5" seqno="2" style="DUID=31760B86;">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="diagram_guid" value="EAID_2DBA38A4_BB85_46dc_A5ED_C43AD3050EC4"/>
+ </UML:ModelElement.taggedValue>
+ </UML:DiagramElement>
+ <UML:DiagramElement geometry="Left=543;Top=289;Right=633;Bottom=339;" subject="EAID_783325F8_9AA7_4836_A0D9_DDA9103CDC71" seqno="1" style="DUID=81FBAF85;">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="diagram_guid" value="EAID_2DBA38A4_BB85_46dc_A5ED_C43AD3050EC4"/>
+ </UML:ModelElement.taggedValue>
+ </UML:DiagramElement>
+ <UML:DiagramElement geometry="SX=8;SY=5;EX=8;EY=5;EDGE=3;$LLB=;LLT=;LMT=;LMB=;LRT=CX=61:CY=15:OX=0:OY=0:HDN=0:BLD=0:ITA=0:UND=0:CLR=-1:ALN=0:DIR=0:;LRB=CX=16:CY=15:OX=0:OY=0:HDN=0:BLD=0:ITA=0:UND=0:CLR=-1:ALN=0:DIR=0:;" subject="EAID_161BF4FA_8F48_441f_AF1F_D36014D57A1F" style="Mode=3;EOID=5DECEEC9;SOID=CDDEF3BC;">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="diagram_guid" value="EAID_2FBE7B3F_756C_46a0_90EE_93429FDA065D"/>
+ <UML:TaggedValue tag="hidden" value="0"/>
+ </UML:ModelElement.taggedValue>
+ </UML:DiagramElement>
+ <UML:DiagramElement geometry="SX=6;SY=-12;EX=-44;EY=-9;EDGE=1;$LLB=CX=16:CY=15:OX=13:OY=-3:HDN=0:BLD=0:ITA=0:UND=0:CLR=-1:ALN=0:DIR=0:;LLT=CX=49:CY=15:OX=0:OY=-14:HDN=0:BLD=0:ITA=0:UND=0:CLR=-1:ALN=0:DIR=0:;LMT=;LMB=;LRT=CX=44:CY=15:OX=-10:OY=-1:HDN=0:BLD=0:ITA=0:UND=0:CLR=-1:ALN=0:DIR=0:;LRB=CX=16:CY=15:OX=-23:OY=-4:HDN=0:BLD=0:ITA=0:UND=0:CLR=-1:ALN=0:DIR=0:;" subject="EAID_9F0FF178_300F_45f4_B31F_9633AF770E44" style="Mode=3;EOID=CDDEF3BC;SOID=BDEA24A9;">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="diagram_guid" value="EAID_2FBE7B3F_756C_46a0_90EE_93429FDA065D"/>
+ <UML:TaggedValue tag="hidden" value="0"/>
+ </UML:ModelElement.taggedValue>
+ </UML:DiagramElement>
+ <UML:DiagramElement geometry="EDGE=4;$LLB=;LLT=;LMT=;LMB=;LRT=CX=43:CY=15:OX=0:OY=0:HDN=0:BLD=0:ITA=0:UND=0:CLR=-1:ALN=0:DIR=0:;LRB=CX=26:CY=15:OX=0:OY=0:HDN=0:BLD=0:ITA=0:UND=0:CLR=-1:ALN=0:DIR=0:;" subject="EAID_A9112D1C_DCB6_4040_B466_D5F560898B33" style="Mode=3;EOID=CDDEF3BC;SOID=12DDDFF9;">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="diagram_guid" value="EAID_2FBE7B3F_756C_46a0_90EE_93429FDA065D"/>
+ <UML:TaggedValue tag="hidden" value="0"/>
+ </UML:ModelElement.taggedValue>
+ </UML:DiagramElement>
+ <UML:DiagramElement geometry="SX=-2;SY=-1;EX=-2;EY=-1;EDGE=1;$LLB=CX=26:CY=15:OX=0:OY=0:HDN=0:BLD=0:ITA=0:UND=0:CLR=-1:ALN=0:DIR=0:;LLT=CX=40:CY=15:OX=-12:OY=-3:HDN=0:BLD=0:ITA=0:UND=0:CLR=-1:ALN=0:DIR=0:;LMT=;LMB=;LRT=CX=44:CY=15:OX=-6:OY=-11:HDN=0:BLD=0:ITA=0:UND=0:CLR=-1:ALN=0:DIR=0:;LRB=CX=16:CY=15:OX=-6:OY=-6:HDN=0:BLD=0:ITA=0:UND=0:CLR=-1:ALN=0:DIR=0:;" subject="EAID_F15A2B25_0DA9_4203_8FC9_25645610B5E5" style="Mode=3;EOID=CDDEF3BC;SOID=BFDAC143;">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="diagram_guid" value="EAID_2FBE7B3F_756C_46a0_90EE_93429FDA065D"/>
+ <UML:TaggedValue tag="hidden" value="0"/>
+ </UML:ModelElement.taggedValue>
+ </UML:DiagramElement>
+ <UML:DiagramElement geometry="EDGE=4;$LLB=;LLT=;LMT=;LMB=;LRT=CX=43:CY=15:OX=0:OY=0:HDN=0:BLD=0:ITA=0:UND=0:CLR=-1:ALN=0:DIR=0:;LRB=CX=26:CY=15:OX=0:OY=0:HDN=0:BLD=0:ITA=0:UND=0:CLR=-1:ALN=0:DIR=0:;" subject="EAID_A9112D1C_DCB6_4040_B466_D5F560898B33" style="Mode=3;EOID=CDDEF3BC;SOID=12DDDFF9;">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="diagram_guid" value="EAID_2FBE7B3F_756C_46a0_90EE_93429FDA065D"/>
+ <UML:TaggedValue tag="hidden" value="0"/>
+ </UML:ModelElement.taggedValue>
+ </UML:DiagramElement>
+ <UML:DiagramElement geometry="SX=-24;SY=-16;EDGE=4;$LLB=;LLT=;LMT=;LMB=;LRT=;LRB=;" subject="EAID_E824691D_2AE7_4c9c_8408_881A2B85516F" style="Mode=3;EOID=E58486A8;SOID=BDEA24A9;">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="diagram_guid" value="EAID_2FBE7B3F_756C_46a0_90EE_93429FDA065D"/>
+ <UML:TaggedValue tag="hidden" value="0"/>
+ </UML:ModelElement.taggedValue>
+ </UML:DiagramElement>
+ <UML:DiagramElement geometry="EDGE=1;$LLB=;LLT=;LMT=;LMB=CX=77:CY=15:OX=0:OY=0:HDN=0:BLD=0:ITA=0:UND=0:CLR=-1:ALN=0:DIR=0:;LRT=;LRB=;" subject="EAID_9613E9DD_8709_4e9e_92D8_2F6CA9835851" style="Mode=3;EOID=1CE36E20;SOID=BDEA24A9;">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="diagram_guid" value="EAID_2FBE7B3F_756C_46a0_90EE_93429FDA065D"/>
+ <UML:TaggedValue tag="hidden" value="0"/>
+ </UML:ModelElement.taggedValue>
+ </UML:DiagramElement>
+ <UML:DiagramElement geometry="SX=-2;SY=-1;EX=-2;EY=-1;EDGE=1;$LLB=CX=26:CY=15:OX=0:OY=0:HDN=0:BLD=0:ITA=0:UND=0:CLR=-1:ALN=0:DIR=0:;LLT=CX=40:CY=15:OX=-12:OY=-3:HDN=0:BLD=0:ITA=0:UND=0:CLR=-1:ALN=0:DIR=0:;LMT=;LMB=;LRT=CX=44:CY=15:OX=-6:OY=-11:HDN=0:BLD=0:ITA=0:UND=0:CLR=-1:ALN=0:DIR=0:;LRB=CX=16:CY=15:OX=-6:OY=-6:HDN=0:BLD=0:ITA=0:UND=0:CLR=-1:ALN=0:DIR=0:;" subject="EAID_F15A2B25_0DA9_4203_8FC9_25645610B5E5" style="Mode=3;EOID=CDDEF3BC;SOID=BFDAC143;">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="diagram_guid" value="EAID_2FBE7B3F_756C_46a0_90EE_93429FDA065D"/>
+ <UML:TaggedValue tag="hidden" value="0"/>
+ </UML:ModelElement.taggedValue>
+ </UML:DiagramElement>
+ <UML:DiagramElement geometry="EDGE=1;$LLB=;LLT=;LMT=;LMB=;LRT=;LRB=;" subject="EAID_03052911_3625_4472_8C6B_5E1742FED753" style="Mode=3;EOID=BFDAC143;SOID=5DECEEC9;">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="diagram_guid" value="EAID_2FBE7B3F_756C_46a0_90EE_93429FDA065D"/>
+ <UML:TaggedValue tag="hidden" value="0"/>
+ </UML:ModelElement.taggedValue>
+ </UML:DiagramElement>
+ <UML:DiagramElement geometry="EX=37;EY=3;EDGE=1;$LLB=;LLT=;LMT=;LMB=;LRT=;LRB=;" subject="EAID_C3D18D92_3A5C_4112_9958_926A259BAE24" style="Mode=3;EOID=BFDAC143;SOID=BDEA24A9;">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="diagram_guid" value="EAID_2FBE7B3F_756C_46a0_90EE_93429FDA065D"/>
+ <UML:TaggedValue tag="hidden" value="0"/>
+ </UML:ModelElement.taggedValue>
+ </UML:DiagramElement>
+ <UML:DiagramElement geometry="EDGE=1;$LLB=;LLT=;LMT=;LMB=CX=77:CY=15:OX=0:OY=0:HDN=0:BLD=0:ITA=0:UND=0:CLR=-1:ALN=0:DIR=0:;LRT=;LRB=;" subject="EAID_9613E9DD_8709_4e9e_92D8_2F6CA9835851" style="Mode=3;EOID=1CE36E20;SOID=BDEA24A9;">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="diagram_guid" value="EAID_2FBE7B3F_756C_46a0_90EE_93429FDA065D"/>
+ <UML:TaggedValue tag="hidden" value="0"/>
+ </UML:ModelElement.taggedValue>
+ </UML:DiagramElement>
+ <UML:DiagramElement geometry="SX=6;SY=-12;EX=-44;EY=-9;EDGE=1;$LLB=CX=16:CY=15:OX=13:OY=-3:HDN=0:BLD=0:ITA=0:UND=0:CLR=-1:ALN=0:DIR=0:;LLT=CX=49:CY=15:OX=0:OY=-14:HDN=0:BLD=0:ITA=0:UND=0:CLR=-1:ALN=0:DIR=0:;LMT=;LMB=;LRT=CX=44:CY=15:OX=-10:OY=-1:HDN=0:BLD=0:ITA=0:UND=0:CLR=-1:ALN=0:DIR=0:;LRB=CX=16:CY=15:OX=-23:OY=-4:HDN=0:BLD=0:ITA=0:UND=0:CLR=-1:ALN=0:DIR=0:;" subject="EAID_9F0FF178_300F_45f4_B31F_9633AF770E44" style="Mode=3;EOID=CDDEF3BC;SOID=BDEA24A9;">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="diagram_guid" value="EAID_2FBE7B3F_756C_46a0_90EE_93429FDA065D"/>
+ <UML:TaggedValue tag="hidden" value="0"/>
+ </UML:ModelElement.taggedValue>
+ </UML:DiagramElement>
+ <UML:DiagramElement geometry="EX=37;EY=3;EDGE=1;$LLB=;LLT=;LMT=;LMB=;LRT=;LRB=;" subject="EAID_C3D18D92_3A5C_4112_9958_926A259BAE24" style="Mode=3;EOID=BFDAC143;SOID=BDEA24A9;">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="diagram_guid" value="EAID_2FBE7B3F_756C_46a0_90EE_93429FDA065D"/>
+ <UML:TaggedValue tag="hidden" value="0"/>
+ </UML:ModelElement.taggedValue>
+ </UML:DiagramElement>
+ <UML:DiagramElement geometry="SX=-24;SY=-16;EDGE=4;$LLB=;LLT=;LMT=;LMB=;LRT=;LRB=;" subject="EAID_E824691D_2AE7_4c9c_8408_881A2B85516F" style="Mode=3;EOID=E58486A8;SOID=BDEA24A9;">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="diagram_guid" value="EAID_2FBE7B3F_756C_46a0_90EE_93429FDA065D"/>
+ <UML:TaggedValue tag="hidden" value="0"/>
+ </UML:ModelElement.taggedValue>
+ </UML:DiagramElement>
+ <UML:DiagramElement geometry="EDGE=1;$LLB=;LLT=;LMT=;LMB=;LRT=;LRB=;" subject="EAID_03052911_3625_4472_8C6B_5E1742FED753" style="Mode=3;EOID=BFDAC143;SOID=5DECEEC9;">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="diagram_guid" value="EAID_2FBE7B3F_756C_46a0_90EE_93429FDA065D"/>
+ <UML:TaggedValue tag="hidden" value="0"/>
+ </UML:ModelElement.taggedValue>
+ </UML:DiagramElement>
+ <UML:DiagramElement geometry="SX=8;SY=5;EX=8;EY=5;EDGE=3;$LLB=;LLT=;LMT=;LMB=;LRT=CX=61:CY=15:OX=0:OY=0:HDN=0:BLD=0:ITA=0:UND=0:CLR=-1:ALN=0:DIR=0:;LRB=CX=16:CY=15:OX=0:OY=0:HDN=0:BLD=0:ITA=0:UND=0:CLR=-1:ALN=0:DIR=0:;" subject="EAID_161BF4FA_8F48_441f_AF1F_D36014D57A1F" style="Mode=3;EOID=5DECEEC9;SOID=CDDEF3BC;">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="diagram_guid" value="EAID_2FBE7B3F_756C_46a0_90EE_93429FDA065D"/>
+ <UML:TaggedValue tag="hidden" value="0"/>
+ </UML:ModelElement.taggedValue>
+ </UML:DiagramElement>
+ <UML:DiagramElement geometry="EDGE=1;$LLB=;LLT=CX=40:CY=15:OX=0:OY=0:HDN=0:BLD=0:ITA=0:UND=0:CLR=-1:ALN=0:DIR=0:;LMT=;LMB=;LRT=;LRB=;" subject="EAID_3F0E9949_A749_4958_9A1B_40E3F0E12393" style="Mode=3;EOID=3D79E0A4;SOID=E585EE32;">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="diagram_guid" value="EAID_2DBA38A4_BB85_46dc_A5ED_C43AD3050EC4"/>
+ <UML:TaggedValue tag="hidden" value="0"/>
+ </UML:ModelElement.taggedValue>
+ </UML:DiagramElement>
+ <UML:DiagramElement geometry="EDGE=1;$LLB=;LLT=CX=40:CY=15:OX=0:OY=0:HDN=0:BLD=0:ITA=0:UND=0:CLR=-1:ALN=0:DIR=0:;LMT=;LMB=;LRT=;LRB=;" subject="EAID_9DDBD985_D9D5_408c_A8F4_40085F2E8E5B" style="Mode=3;EOID=3D79E0A4;SOID=EE2397B7;">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="diagram_guid" value="EAID_2DBA38A4_BB85_46dc_A5ED_C43AD3050EC4"/>
+ <UML:TaggedValue tag="hidden" value="0"/>
+ </UML:ModelElement.taggedValue>
+ </UML:DiagramElement>
+ <UML:DiagramElement geometry="EDGE=2;$LLB=;LLT=;LMT=;LMB=;LRT=CX=43:CY=15:OX=0:OY=0:HDN=0:BLD=0:ITA=0:UND=0:CLR=-1:ALN=0:DIR=0:;LRB=;" subject="EAID_AFD66AB6_337B_49c5_9FA9_0CFAFEB77AA7" style="Mode=3;EOID=3D79E0A4;SOID=31760B86;">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="diagram_guid" value="EAID_2DBA38A4_BB85_46dc_A5ED_C43AD3050EC4"/>
+ <UML:TaggedValue tag="hidden" value="0"/>
+ </UML:ModelElement.taggedValue>
+ </UML:DiagramElement>
+ <UML:DiagramElement geometry="EDGE=1;$LLB=;LLT=CX=40:CY=15:OX=0:OY=0:HDN=0:BLD=0:ITA=0:UND=0:CLR=-1:ALN=0:DIR=0:;LMT=;LMB=;LRT=;LRB=;" subject="EAID_D775B2C1_C7AF_4a19_A4D5_7B3AC816664C" style="Mode=3;EOID=3D79E0A4;SOID=DFC93F21;">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="diagram_guid" value="EAID_2DBA38A4_BB85_46dc_A5ED_C43AD3050EC4"/>
+ <UML:TaggedValue tag="hidden" value="0"/>
+ </UML:ModelElement.taggedValue>
+ </UML:DiagramElement>
+ <UML:DiagramElement geometry="SX=36;SY=-9;EX=45;EY=-9;EDGE=1;$LLB=;LLT=CX=40:CY=15:OX=-17:OY=-1:HDN=0:BLD=0:ITA=0:UND=0:CLR=-1:ALN=0:DIR=0:;LMT=;LMB=;LRT=;LRB=;" subject="EAID_FF2ECD20_22FF_4734_9DB4_8316A98DDC16" style="Mode=3;EOID=3D79E0A4;SOID=81FBAF85;">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="diagram_guid" value="EAID_2DBA38A4_BB85_46dc_A5ED_C43AD3050EC4"/>
+ <UML:TaggedValue tag="hidden" value="0"/>
+ </UML:ModelElement.taggedValue>
+ </UML:DiagramElement>
+ <UML:DiagramElement geometry="EDGE=1;$LLB=;LLT=CX=40:CY=15:OX=0:OY=0:HDN=0:BLD=0:ITA=0:UND=0:CLR=-1:ALN=0:DIR=0:;LMT=;LMB=;LRT=;LRB=;" subject="EAID_3F0E9949_A749_4958_9A1B_40E3F0E12393" style="Mode=3;EOID=3D79E0A4;SOID=E585EE32;">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="diagram_guid" value="EAID_2DBA38A4_BB85_46dc_A5ED_C43AD3050EC4"/>
+ <UML:TaggedValue tag="hidden" value="0"/>
+ </UML:ModelElement.taggedValue>
+ </UML:DiagramElement>
+ <UML:DiagramElement geometry="EDGE=1;$LLB=;LLT=CX=40:CY=15:OX=0:OY=0:HDN=0:BLD=0:ITA=0:UND=0:CLR=-1:ALN=0:DIR=0:;LMT=;LMB=;LRT=;LRB=;" subject="EAID_D775B2C1_C7AF_4a19_A4D5_7B3AC816664C" style="Mode=3;EOID=3D79E0A4;SOID=DFC93F21;">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="diagram_guid" value="EAID_2DBA38A4_BB85_46dc_A5ED_C43AD3050EC4"/>
+ <UML:TaggedValue tag="hidden" value="0"/>
+ </UML:ModelElement.taggedValue>
+ </UML:DiagramElement>
+ <UML:DiagramElement geometry="EDGE=1;$LLB=;LLT=CX=40:CY=15:OX=0:OY=0:HDN=0:BLD=0:ITA=0:UND=0:CLR=-1:ALN=0:DIR=0:;LMT=;LMB=;LRT=;LRB=;" subject="EAID_9DDBD985_D9D5_408c_A8F4_40085F2E8E5B" style="Mode=3;EOID=3D79E0A4;SOID=EE2397B7;">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="diagram_guid" value="EAID_2DBA38A4_BB85_46dc_A5ED_C43AD3050EC4"/>
+ <UML:TaggedValue tag="hidden" value="0"/>
+ </UML:ModelElement.taggedValue>
+ </UML:DiagramElement>
+ <UML:DiagramElement geometry="EDGE=2;$LLB=;LLT=;LMT=;LMB=;LRT=CX=43:CY=15:OX=0:OY=0:HDN=0:BLD=0:ITA=0:UND=0:CLR=-1:ALN=0:DIR=0:;LRB=;" subject="EAID_AFD66AB6_337B_49c5_9FA9_0CFAFEB77AA7" style="Mode=3;EOID=3D79E0A4;SOID=31760B86;">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="diagram_guid" value="EAID_2DBA38A4_BB85_46dc_A5ED_C43AD3050EC4"/>
+ <UML:TaggedValue tag="hidden" value="0"/>
+ </UML:ModelElement.taggedValue>
+ </UML:DiagramElement>
+ <UML:DiagramElement geometry="SX=36;SY=-9;EX=45;EY=-9;EDGE=1;$LLB=;LLT=CX=40:CY=15:OX=-17:OY=-1:HDN=0:BLD=0:ITA=0:UND=0:CLR=-1:ALN=0:DIR=0:;LMT=;LMB=;LRT=;LRB=;" subject="EAID_FF2ECD20_22FF_4734_9DB4_8316A98DDC16" style="Mode=3;EOID=3D79E0A4;SOID=81FBAF85;">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="diagram_guid" value="EAID_2DBA38A4_BB85_46dc_A5ED_C43AD3050EC4"/>
+ <UML:TaggedValue tag="hidden" value="0"/>
+ </UML:ModelElement.taggedValue>
+ </UML:DiagramElement>
+ </XMI.content>
+ <XMI.difference/>
+ <XMI.extensions xmi.extender="Enterprise Architect 2.5"/>
+</XMI>
diff --git a/lib/puppet/vendor/rgen/test/testmodel/ea_testmodel_partial.xml b/lib/puppet/vendor/rgen/test/testmodel/ea_testmodel_partial.xml
new file mode 100644
index 000000000..3ad796eb5
--- /dev/null
+++ b/lib/puppet/vendor/rgen/test/testmodel/ea_testmodel_partial.xml
@@ -0,0 +1,317 @@
+<?xml version="1.0" encoding="windows-1252"?>
+<XMI xmi.version="1.1" xmlns:UML="omg.org/UML1.3" timestamp="2007-06-10 12:35:22">
+ <XMI.header>
+ <XMI.documentation>
+ <XMI.exporter>Enterprise Architect</XMI.exporter>
+ <XMI.exporterVersion>2.5</XMI.exporterVersion>
+ </XMI.documentation>
+ </XMI.header>
+ <XMI.content>
+ <UML:Model name="EA Model" xmi.id="MX_EAID_F9D8C6E3_4DAD_4aa2_AD47_D0ABA4E93E08">
+ <UML:Namespace.ownedElement>
+ <UML:Class name="EARootClass" xmi.id="EAID_11111111_5487_4080_A7F4_41526CB0AA00" isRoot="true" isLeaf="false" isAbstract="false"/>
+ <UML:Package name="Rooms" xmi.id="EAPK_F9D8C6E3_4DAD_4aa2_AD47_D0ABA4E93E08" isRoot="true" isLeaf="false" isAbstract="false" visibility="public">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="created" value="2006-06-23 00:00:00"/>
+ <UML:TaggedValue tag="modified" value="2006-06-23 00:00:00"/>
+ <UML:TaggedValue tag="iscontrolled" value="FALSE"/>
+ <UML:TaggedValue tag="isprotected" value="FALSE"/>
+ <UML:TaggedValue tag="usedtd" value="FALSE"/>
+ <UML:TaggedValue tag="logxml" value="FALSE"/>
+ <UML:TaggedValue tag="phase" value="1.0"/>
+ <UML:TaggedValue tag="status" value="Proposed"/>
+ <UML:TaggedValue tag="complexity" value="1"/>
+ <UML:TaggedValue tag="ea_stype" value="Public"/>
+ </UML:ModelElement.taggedValue>
+ <UML:Namespace.ownedElement>
+ <UML:Class name="Room" xmi.id="EAID_14DB5E54_CD7B_4c84_998C_44960049D7E0" visibility="public" namespace="EAPK_F9D8C6E3_4DAD_4aa2_AD47_D0ABA4E93E08" isRoot="false" isLeaf="false" isAbstract="false" isActive="false">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="isSpecification" value="false"/>
+ <UML:TaggedValue tag="ea_stype" value="Class"/>
+ <UML:TaggedValue tag="ea_ntype" value="0"/>
+ <UML:TaggedValue tag="version" value="1.0"/>
+ <UML:TaggedValue tag="package" value="EAPK_F9D8C6E3_4DAD_4aa2_AD47_D0ABA4E93E08"/>
+ <UML:TaggedValue tag="date_created" value="2005-09-16 19:52:28"/>
+ <UML:TaggedValue tag="date_modified" value="2006-06-22 21:15:25"/>
+ <UML:TaggedValue tag="gentype" value="Java"/>
+ <UML:TaggedValue tag="tagged" value="0"/>
+ <UML:TaggedValue tag="package_name" value="Rooms"/>
+ <UML:TaggedValue tag="phase" value="1.0"/>
+ <UML:TaggedValue tag="complexity" value="1"/>
+ <UML:TaggedValue tag="status" value="Proposed"/>
+ <UML:TaggedValue tag="style" value="BackColor=-1;BorderColor=-1;BorderWidth=-1;FontColor=-1;VSwimLanes=0;HSwimLanes=0;BorderStyle=0;"/>
+ </UML:ModelElement.taggedValue>
+ <UML:Classifier.feature>
+ <UML:Operation name="enter" visibility="public" ownerScope="instance" isQuery="false" concurrency="sequential">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="type" value="void"/>
+ <UML:TaggedValue tag="const" value="false"/>
+ <UML:TaggedValue tag="synchronised" value="0"/>
+ <UML:TaggedValue tag="concurrency" value="Sequential"/>
+ <UML:TaggedValue tag="position" value="0"/>
+ <UML:TaggedValue tag="returnarray" value="0"/>
+ <UML:TaggedValue tag="pure" value="0"/>
+ <UML:TaggedValue tag="ea_guid" value="{A8C01177-4523-44c4-87F7-F2B8F9F3C915}"/>
+ </UML:ModelElement.taggedValue>
+ <UML:BehavioralFeature.parameter>
+ <UML:Parameter kind="return" visibility="public">
+ <UML:Parameter.type>
+ <UML:Classifier xmi.idref="eaxmiid0"/>
+ </UML:Parameter.type>
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="pos" value="0"/>
+ <UML:TaggedValue tag="type" value="void"/>
+ <UML:TaggedValue tag="const" value="0"/>
+ <UML:TaggedValue tag="ea_guid" value="{A8C01177-4523-44c4-87F7-F2B8F9F3C915}"/>
+ </UML:ModelElement.taggedValue>
+ <UML:Parameter.defaultValue>
+ <UML:Expression/>
+ </UML:Parameter.defaultValue>
+ </UML:Parameter>
+ </UML:BehavioralFeature.parameter>
+ </UML:Operation>
+ </UML:Classifier.feature>
+ </UML:Class>
+ <UML:Association xmi.id="EAID_F15A2B25_0DA9_4203_8FC9_25645610B5E5" visibility="public" isRoot="false" isLeaf="false" isAbstract="false">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="style" value="3"/>
+ <UML:TaggedValue tag="ea_type" value="Aggregation"/>
+ <UML:TaggedValue tag="direction" value="Source -&gt; Destination"/>
+ <UML:TaggedValue tag="linemode" value="3"/>
+ <UML:TaggedValue tag="seqno" value="0"/>
+ <UML:TaggedValue tag="subtype" value="Strong"/>
+ <UML:TaggedValue tag="headStyle" value="0"/>
+ <UML:TaggedValue tag="lineStyle" value="0"/>
+ <UML:TaggedValue tag="privatedata5" value="SX=-2;SY=-1;EX=-2;EY=-1;"/>
+ <UML:TaggedValue tag="virtualInheritance" value="0"/>
+ </UML:ModelElement.taggedValue>
+ <UML:Association.connection>
+ <UML:AssociationEnd visibility="public" multiplicity="1..*" name="room" aggregation="none" isOrdered="false" isNavigable="false" type="EAID_14DB5E54_CD7B_4c84_998C_44960049D7E0">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="containment" value="Unspecified"/>
+ </UML:ModelElement.taggedValue>
+ </UML:AssociationEnd>
+ <UML:AssociationEnd visibility="public" multiplicity="1" name="house" aggregation="composite" isOrdered="false" isNavigable="true" type="EAID_436D81AD_A9B0_44d8_8AD1_86BB0808DA32">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="containment" value="Unspecified"/>
+ </UML:ModelElement.taggedValue>
+ </UML:AssociationEnd>
+ </UML:Association.connection>
+ </UML:Association>
+ <UML:Generalization subtype="EAID_244CC9E5_1F81_4164_9215_C048EC679543" supertype="EAID_14DB5E54_CD7B_4c84_998C_44960049D7E0" xmi.id="EAID_03052911_3625_4472_8C6B_5E1742FED753" visibility="public">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="style" value="3"/>
+ <UML:TaggedValue tag="ea_type" value="Generalization"/>
+ <UML:TaggedValue tag="direction" value="Source -&gt; Destination"/>
+ <UML:TaggedValue tag="linemode" value="3"/>
+ <UML:TaggedValue tag="seqno" value="0"/>
+ <UML:TaggedValue tag="headStyle" value="0"/>
+ <UML:TaggedValue tag="lineStyle" value="0"/>
+ <UML:TaggedValue tag="src_visibility" value="Public"/>
+ <UML:TaggedValue tag="src_isOrdered" value="false"/>
+ <UML:TaggedValue tag="src_isNavigable" value="false"/>
+ <UML:TaggedValue tag="dst_visibility" value="Public"/>
+ <UML:TaggedValue tag="dst_isOrdered" value="false"/>
+ <UML:TaggedValue tag="dst_isNavigable" value="true"/>
+ <UML:TaggedValue tag="$ea_xref_property" value="$XREFPROP=$XID={DF243A69-1029-4c4c-A1A0-C821A1DF9623}$XID;$NAM=CustomProperties$NAM;$TYP=connector property$TYP;$VIS=Public$VIS;$DES=@PROP=@NAME=isSubstitutable@ENDNAME;@TYPE=boolean@ENDTYPE;@VALU=@ENDVALU;@PRMT=@ENDPRMT;@ENDPROP;$DES;$CLT={03052911-3625-4472-8C6B-5E1742FED753}$CLT;$SUP=&lt;none&gt;$SUP;$ENDXREF;"/>
+ </UML:ModelElement.taggedValue>
+ </UML:Generalization>
+ <UML:Generalization subtype="EAID_9CE44C59_37E4_4117_8C05_F87C4DC33529" supertype="EAID_14DB5E54_CD7B_4c84_998C_44960049D7E0" xmi.id="EAID_C3D18D92_3A5C_4112_9958_926A259BAE24" visibility="public">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="style" value="3"/>
+ <UML:TaggedValue tag="ea_type" value="Generalization"/>
+ <UML:TaggedValue tag="direction" value="Source -&gt; Destination"/>
+ <UML:TaggedValue tag="linemode" value="3"/>
+ <UML:TaggedValue tag="seqno" value="0"/>
+ <UML:TaggedValue tag="headStyle" value="0"/>
+ <UML:TaggedValue tag="lineStyle" value="0"/>
+ <UML:TaggedValue tag="src_visibility" value="Public"/>
+ <UML:TaggedValue tag="src_isOrdered" value="false"/>
+ <UML:TaggedValue tag="src_isNavigable" value="false"/>
+ <UML:TaggedValue tag="dst_visibility" value="Public"/>
+ <UML:TaggedValue tag="dst_isOrdered" value="false"/>
+ <UML:TaggedValue tag="dst_isNavigable" value="true"/>
+ <UML:TaggedValue tag="$ea_xref_property" value="$XREFPROP=$XID={DB1C706A-470A-45ed-89DA-601821419545}$XID;$NAM=CustomProperties$NAM;$TYP=connector property$TYP;$VIS=Public$VIS;$DES=@PROP=@NAME=isSubstitutable@ENDNAME;@TYPE=boolean@ENDTYPE;@VALU=@ENDVALU;@PRMT=@ENDPRMT;@ENDPROP;$DES;$CLT={C3D18D92-3A5C-4112-9958-926A259BAE24}$CLT;$SUP=&lt;none&gt;$SUP;$ENDXREF;"/>
+ <UML:TaggedValue tag="privatedata5" value="EX=37;EY=3;"/>
+ </UML:ModelElement.taggedValue>
+ </UML:Generalization>
+ <UML:Class name="Kitchen" xmi.id="EAID_9CE44C59_37E4_4117_8C05_F87C4DC33529" visibility="public" namespace="EAPK_F9D8C6E3_4DAD_4aa2_AD47_D0ABA4E93E08" isRoot="false" isLeaf="false" isAbstract="false" isActive="false">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="isSpecification" value="false"/>
+ <UML:TaggedValue tag="ea_stype" value="Class"/>
+ <UML:TaggedValue tag="ea_ntype" value="0"/>
+ <UML:TaggedValue tag="version" value="1.0"/>
+ <UML:TaggedValue tag="package" value="EAPK_F9D8C6E3_4DAD_4aa2_AD47_D0ABA4E93E08"/>
+ <UML:TaggedValue tag="date_created" value="2005-11-30 19:26:13"/>
+ <UML:TaggedValue tag="date_modified" value="2006-06-22 21:15:34"/>
+ <UML:TaggedValue tag="gentype" value="Java"/>
+ <UML:TaggedValue tag="tagged" value="0"/>
+ <UML:TaggedValue tag="package_name" value="Rooms"/>
+ <UML:TaggedValue tag="phase" value="1.0"/>
+ <UML:TaggedValue tag="complexity" value="1"/>
+ <UML:TaggedValue tag="status" value="Proposed"/>
+ <UML:TaggedValue tag="style" value="BackColor=-1;BorderColor=-1;BorderWidth=-1;FontColor=-1;VSwimLanes=0;HSwimLanes=0;BorderStyle=0;"/>
+ </UML:ModelElement.taggedValue>
+ </UML:Class>
+ <UML:Association xmi.id="EAID_9F0FF178_300F_45f4_B31F_9633AF770E44" visibility="public" isRoot="false" isLeaf="false" isAbstract="false">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="style" value="3"/>
+ <UML:TaggedValue tag="ea_type" value="Association"/>
+ <UML:TaggedValue tag="direction" value="Bi-Directional"/>
+ <UML:TaggedValue tag="linemode" value="3"/>
+ <UML:TaggedValue tag="seqno" value="0"/>
+ <UML:TaggedValue tag="headStyle" value="0"/>
+ <UML:TaggedValue tag="lineStyle" value="0"/>
+ <UML:TaggedValue tag="privatedata5" value="SX=-39;SY=-10;EX=-43;EY=-10;EDGE=1;"/>
+ <UML:TaggedValue tag="virtualInheritance" value="0"/>
+ </UML:ModelElement.taggedValue>
+ <UML:Association.connection>
+ <UML:AssociationEnd visibility="public" multiplicity="1" name="kitchen" aggregation="none" isOrdered="false" isNavigable="true" type="EAID_9CE44C59_37E4_4117_8C05_F87C4DC33529">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="containment" value="Unspecified"/>
+ </UML:ModelElement.taggedValue>
+ </UML:AssociationEnd>
+ <UML:AssociationEnd visibility="public" multiplicity="1" name="house" aggregation="none" isOrdered="false" isNavigable="true" type="EAID_436D81AD_A9B0_44d8_8AD1_86BB0808DA32">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="containment" value="Unspecified"/>
+ </UML:ModelElement.taggedValue>
+ </UML:AssociationEnd>
+ </UML:Association.connection>
+ </UML:Association>
+ <UML:Generalization subtype="EAID_9CE44C59_37E4_4117_8C05_F87C4DC33529" supertype="EAID_CB9E84D4_9064_49d0_BF1E_EE53C05853EF" xmi.id="EAID_E824691D_2AE7_4c9c_8408_881A2B85516F" visibility="public">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="style" value="3"/>
+ <UML:TaggedValue tag="ea_type" value="Generalization"/>
+ <UML:TaggedValue tag="direction" value="Source -&gt; Destination"/>
+ <UML:TaggedValue tag="linemode" value="3"/>
+ <UML:TaggedValue tag="seqno" value="0"/>
+ <UML:TaggedValue tag="headStyle" value="0"/>
+ <UML:TaggedValue tag="lineStyle" value="0"/>
+ <UML:TaggedValue tag="src_visibility" value="Public"/>
+ <UML:TaggedValue tag="src_isOrdered" value="false"/>
+ <UML:TaggedValue tag="src_isNavigable" value="false"/>
+ <UML:TaggedValue tag="dst_visibility" value="Public"/>
+ <UML:TaggedValue tag="dst_isOrdered" value="false"/>
+ <UML:TaggedValue tag="dst_isNavigable" value="true"/>
+ <UML:TaggedValue tag="$ea_xref_property" value="$XREFPROP=$XID={8AAF19F7-EC93-4dda-ADD1-69A07CC671D7}$XID;$NAM=CustomProperties$NAM;$TYP=connector property$TYP;$VIS=Public$VIS;$DES=@PROP=@NAME=isSubstitutable@ENDNAME;@TYPE=boolean@ENDTYPE;@VALU=@ENDVALU;@PRMT=@ENDPRMT;@ENDPROP;$DES;$CLT={E824691D-2AE7-4c9c-8408-881A2B85516F}$CLT;$SUP=&lt;none&gt;$SUP;$ENDXREF;"/>
+ <UML:TaggedValue tag="privatedata5" value="SX=-24;SY=-16;"/>
+ </UML:ModelElement.taggedValue>
+ </UML:Generalization>
+ <UML:Class name="Bathroom" xmi.id="EAID_244CC9E5_1F81_4164_9215_C048EC679543" visibility="public" namespace="EAPK_F9D8C6E3_4DAD_4aa2_AD47_D0ABA4E93E08" isRoot="false" isLeaf="false" isAbstract="false" isActive="false">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="isSpecification" value="false"/>
+ <UML:TaggedValue tag="ea_stype" value="Class"/>
+ <UML:TaggedValue tag="ea_ntype" value="0"/>
+ <UML:TaggedValue tag="version" value="1.0"/>
+ <UML:TaggedValue tag="package" value="EAPK_F9D8C6E3_4DAD_4aa2_AD47_D0ABA4E93E08"/>
+ <UML:TaggedValue tag="date_created" value="2006-06-27 08:32:25"/>
+ <UML:TaggedValue tag="date_modified" value="2006-06-27 08:34:23"/>
+ <UML:TaggedValue tag="gentype" value="Java"/>
+ <UML:TaggedValue tag="tagged" value="0"/>
+ <UML:TaggedValue tag="package_name" value="Rooms"/>
+ <UML:TaggedValue tag="phase" value="1.0"/>
+ <UML:TaggedValue tag="complexity" value="1"/>
+ <UML:TaggedValue tag="status" value="Proposed"/>
+ <UML:TaggedValue tag="style" value="BackColor=-1;BorderColor=-1;BorderWidth=-1;FontColor=-1;VSwimLanes=0;HSwimLanes=0;BorderStyle=0;"/>
+ </UML:ModelElement.taggedValue>
+ </UML:Class>
+ <UML:Association xmi.id="EAID_161BF4FA_8F48_441f_AF1F_D36014D57A1F" visibility="public" isRoot="false" isLeaf="false" isAbstract="false">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="style" value="3"/>
+ <UML:TaggedValue tag="ea_type" value="Association"/>
+ <UML:TaggedValue tag="direction" value="Source -&gt; Destination"/>
+ <UML:TaggedValue tag="linemode" value="3"/>
+ <UML:TaggedValue tag="seqno" value="0"/>
+ <UML:TaggedValue tag="headStyle" value="0"/>
+ <UML:TaggedValue tag="lineStyle" value="0"/>
+ <UML:TaggedValue tag="privatedata5" value="SX=8;SY=5;EX=8;EY=5;"/>
+ <UML:TaggedValue tag="virtualInheritance" value="0"/>
+ </UML:ModelElement.taggedValue>
+ <UML:Association.connection>
+ <UML:AssociationEnd visibility="public" aggregation="none" isOrdered="false" isNavigable="false" type="EAID_436D81AD_A9B0_44d8_8AD1_86BB0808DA32">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="containment" value="Unspecified"/>
+ </UML:ModelElement.taggedValue>
+ </UML:AssociationEnd>
+ <UML:AssociationEnd visibility="public" multiplicity="1" name="bathroom" aggregation="none" isOrdered="false" isNavigable="true" type="EAID_244CC9E5_1F81_4164_9215_C048EC679543">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="containment" value="Unspecified"/>
+ </UML:ModelElement.taggedValue>
+ </UML:AssociationEnd>
+ </UML:Association.connection>
+ </UML:Association>
+ </UML:Namespace.ownedElement>
+ </UML:Package>
+ <UML:DataType xmi.id="eaxmiid0" name="void" visibility="private" isRoot="false" isLeaf="false" isAbstract="false"/>
+ </UML:Namespace.ownedElement>
+ </UML:Model>
+ <UML:DiagramElement geometry="Left=265;Top=280;Right=355;Bottom=350;" subject="EAID_14DB5E54_CD7B_4c84_998C_44960049D7E0" seqno="5" style="DUID=BFDAC143;LBL=;">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="diagram_guid" value="EAID_2FBE7B3F_756C_46a0_90EE_93429FDA065D"/>
+ </UML:ModelElement.taggedValue>
+ </UML:DiagramElement>
+ <UML:DiagramElement geometry="Left=145;Top=402;Right=235;Bottom=472;" subject="EAID_9CE44C59_37E4_4117_8C05_F87C4DC33529" seqno="4" style="DUID=BDEA24A9;LBL=;">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="diagram_guid" value="EAID_2FBE7B3F_756C_46a0_90EE_93429FDA065D"/>
+ </UML:ModelElement.taggedValue>
+ </UML:DiagramElement>
+ <UML:DiagramElement geometry="Left=394;Top=412;Right=484;Bottom=482;" subject="EAID_244CC9E5_1F81_4164_9215_C048EC679543" seqno="3" style="DUID=5DECEEC9;LBL=;">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="diagram_guid" value="EAID_2FBE7B3F_756C_46a0_90EE_93429FDA065D"/>
+ </UML:ModelElement.taggedValue>
+ </UML:DiagramElement>
+ <UML:DiagramElement geometry="SX=-2;SY=-1;EX=-2;EY=-1;EDGE=1;$LLB=CX=26:CY=15:OX=0:OY=0:HDN=0:BLD=0:ITA=0:UND=0:CLR=-1:ALN=0:DIR=0:;LLT=CX=40:CY=15:OX=-12:OY=-3:HDN=0:BLD=0:ITA=0:UND=0:CLR=-1:ALN=0:DIR=0:;LMT=;LMB=;LRT=CX=44:CY=15:OX=-6:OY=-11:HDN=0:BLD=0:ITA=0:UND=0:CLR=-1:ALN=0:DIR=0:;LRB=CX=16:CY=15:OX=-6:OY=-6:HDN=0:BLD=0:ITA=0:UND=0:CLR=-1:ALN=0:DIR=0:;" subject="EAID_F15A2B25_0DA9_4203_8FC9_25645610B5E5" style="Mode=3;EOID=CDDEF3BC;SOID=BFDAC143;">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="diagram_guid" value="EAID_2FBE7B3F_756C_46a0_90EE_93429FDA065D"/>
+ <UML:TaggedValue tag="hidden" value="0"/>
+ </UML:ModelElement.taggedValue>
+ </UML:DiagramElement>
+ <UML:DiagramElement geometry="EDGE=1;$LLB=;LLT=;LMT=;LMB=;LRT=;LRB=;" subject="EAID_03052911_3625_4472_8C6B_5E1742FED753" style="Mode=3;EOID=BFDAC143;SOID=5DECEEC9;">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="diagram_guid" value="EAID_2FBE7B3F_756C_46a0_90EE_93429FDA065D"/>
+ <UML:TaggedValue tag="hidden" value="0"/>
+ </UML:ModelElement.taggedValue>
+ </UML:DiagramElement>
+ <UML:DiagramElement geometry="EX=37;EY=3;EDGE=1;$LLB=;LLT=;LMT=;LMB=;LRT=;LRB=;" subject="EAID_C3D18D92_3A5C_4112_9958_926A259BAE24" style="Mode=3;EOID=BFDAC143;SOID=BDEA24A9;">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="diagram_guid" value="EAID_2FBE7B3F_756C_46a0_90EE_93429FDA065D"/>
+ <UML:TaggedValue tag="hidden" value="0"/>
+ </UML:ModelElement.taggedValue>
+ </UML:DiagramElement>
+ <UML:DiagramElement geometry="SX=-44;SY=-9;EX=-44;EY=-9;EDGE=1;$LLB=CX=16:CY=15:OX=13:OY=-3:HDN=0:BLD=0:ITA=0:UND=0:CLR=-1:ALN=0:DIR=0:;LLT=CX=49:CY=15:OX=0:OY=-14:HDN=0:BLD=0:ITA=0:UND=0:CLR=-1:ALN=0:DIR=0:;LMT=;LMB=;LRT=CX=44:CY=15:OX=-10:OY=-1:HDN=0:BLD=0:ITA=0:UND=0:CLR=-1:ALN=0:DIR=0:;LRB=CX=16:CY=15:OX=-23:OY=-4:HDN=0:BLD=0:ITA=0:UND=0:CLR=-1:ALN=0:DIR=0:;" subject="EAID_9F0FF178_300F_45f4_B31F_9633AF770E44" style="Mode=3;EOID=CDDEF3BC;SOID=BDEA24A9;">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="diagram_guid" value="EAID_2FBE7B3F_756C_46a0_90EE_93429FDA065D"/>
+ <UML:TaggedValue tag="hidden" value="0"/>
+ </UML:ModelElement.taggedValue>
+ </UML:DiagramElement>
+ <UML:DiagramElement geometry="EX=37;EY=3;EDGE=1;$LLB=;LLT=;LMT=;LMB=;LRT=;LRB=;" subject="EAID_C3D18D92_3A5C_4112_9958_926A259BAE24" style="Mode=3;EOID=BFDAC143;SOID=BDEA24A9;">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="diagram_guid" value="EAID_2FBE7B3F_756C_46a0_90EE_93429FDA065D"/>
+ <UML:TaggedValue tag="hidden" value="0"/>
+ </UML:ModelElement.taggedValue>
+ </UML:DiagramElement>
+ <UML:DiagramElement geometry="SX=-24;SY=-16;EDGE=1;$LLB=;LLT=;LMT=;LMB=;LRT=;LRB=;" subject="EAID_E824691D_2AE7_4c9c_8408_881A2B85516F" style="Mode=3;EOID=E58486A8;SOID=BDEA24A9;">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="diagram_guid" value="EAID_2FBE7B3F_756C_46a0_90EE_93429FDA065D"/>
+ <UML:TaggedValue tag="hidden" value="0"/>
+ </UML:ModelElement.taggedValue>
+ </UML:DiagramElement>
+ <UML:DiagramElement geometry="EDGE=1;$LLB=;LLT=;LMT=;LMB=;LRT=;LRB=;" subject="EAID_03052911_3625_4472_8C6B_5E1742FED753" style="Mode=3;EOID=BFDAC143;SOID=5DECEEC9;">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="diagram_guid" value="EAID_2FBE7B3F_756C_46a0_90EE_93429FDA065D"/>
+ <UML:TaggedValue tag="hidden" value="0"/>
+ </UML:ModelElement.taggedValue>
+ </UML:DiagramElement>
+ <UML:DiagramElement geometry="SX=8;SY=5;EX=8;EY=5;EDGE=3;$LLB=;LLT=;LMT=;LMB=;LRT=CX=61:CY=15:OX=0:OY=0:HDN=0:BLD=0:ITA=0:UND=0:CLR=-1:ALN=0:DIR=0:;LRB=CX=16:CY=15:OX=0:OY=0:HDN=0:BLD=0:ITA=0:UND=0:CLR=-1:ALN=0:DIR=0:;" subject="EAID_161BF4FA_8F48_441f_AF1F_D36014D57A1F" style="Mode=3;EOID=5DECEEC9;SOID=CDDEF3BC;">
+ <UML:ModelElement.taggedValue>
+ <UML:TaggedValue tag="diagram_guid" value="EAID_2FBE7B3F_756C_46a0_90EE_93429FDA065D"/>
+ <UML:TaggedValue tag="hidden" value="0"/>
+ </UML:ModelElement.taggedValue>
+ </UML:DiagramElement>
+ </XMI.content>
+ <XMI.difference/>
+ <XMI.extensions xmi.extender="Enterprise Architect 2.5">
+ <EAStub xmi.id="EAID_436D81AD_A9B0_44d8_8AD1_86BB0808DA32" name="House" UMLType="Class"/>
+ <EAStub xmi.id="EAID_CB9E84D4_9064_49d0_BF1E_EE53C05853EF" name="MeetingPlace" UMLType="Class"/>
+ </XMI.extensions>
+</XMI>
diff --git a/lib/puppet/vendor/rgen/test/testmodel/ecore_model_checker.rb b/lib/puppet/vendor/rgen/test/testmodel/ecore_model_checker.rb
new file mode 100644
index 000000000..3fe1f1e6c
--- /dev/null
+++ b/lib/puppet/vendor/rgen/test/testmodel/ecore_model_checker.rb
@@ -0,0 +1,101 @@
+require 'rgen/ecore/ecore'
+
+module Testmodel
+
+# Checks the ECore model elements created by transformation from the
+# UML Class model elements from the example model
+#
+module ECoreModelChecker
+ include RGen::ECore
+
+ def checkECoreModel(env)
+
+ # check main package
+ mainPackage = env.elements.select {|e| e.is_a? EPackage and e.name == "HouseMetamodel"}.first
+ assert_not_nil mainPackage
+
+ # check Rooms package
+ assert mainPackage.eSubpackages.is_a?(Array)
+ assert_equal 1, mainPackage.eSubpackages.size
+ assert mainPackage.eSubpackages[0].is_a?(EPackage)
+ roomsPackage = mainPackage.eSubpackages[0]
+ assert_equal "Rooms", roomsPackage.name
+
+ # check main package classes
+ assert mainPackage.eClassifiers.is_a?(Array)
+ assert_equal 4, mainPackage.eClassifiers.size
+ assert mainPackage.eClassifiers.all?{|c| c.is_a?(EClass)}
+ houseClass = mainPackage.eClassifiers.select{|c| c.name == "House"}.first
+ personClass = mainPackage.eClassifiers.select{|c| c.name == "Person"}.first
+ meetingPlaceClass = mainPackage.eClassifiers.select{|c| c.name == "MeetingPlace"}.first
+ cookingPlaceInterface = mainPackage.eClassifiers.select{|c| c.name == "CookingPlace"}.first
+ assert_not_nil houseClass
+ assert_not_nil personClass
+ assert_not_nil meetingPlaceClass
+ assert_not_nil cookingPlaceInterface
+
+ # check Rooms package classes
+ assert roomsPackage.eClassifiers.is_a?(Array)
+ assert_equal 3, roomsPackage.eClassifiers.size
+ assert roomsPackage.eClassifiers.all?{|c| c.is_a?(EClass)}
+ roomClass = roomsPackage.eClassifiers.select{|c| c.name == "Room"}.first
+ kitchenClass = roomsPackage.eClassifiers.select{|c| c.name == "Kitchen"}.first
+ bathroomClass = roomsPackage.eClassifiers.select{|c| c.name == "Bathroom"}.first
+ assert_not_nil roomClass
+ assert_not_nil kitchenClass
+ assert_not_nil bathroomClass
+
+ # check Room inheritance
+ assert kitchenClass.eSuperTypes.is_a?(Array)
+ assert_equal 3, kitchenClass.eSuperTypes.size
+ assert_equal roomClass.object_id, kitchenClass.eSuperTypes.select{|c| c.name == "Room"}.first.object_id
+ assert_equal meetingPlaceClass.object_id, kitchenClass.eSuperTypes.select{|c| c.name == "MeetingPlace"}.first.object_id
+ assert_equal cookingPlaceInterface.object_id, kitchenClass.eSuperTypes.select{|c| c.name == "CookingPlace"}.first.object_id
+ assert bathroomClass.eSuperTypes.is_a?(Array)
+ assert_equal 1, bathroomClass.eSuperTypes.size
+ assert_equal roomClass.object_id, bathroomClass.eSuperTypes[0].object_id
+
+ # check House-Room "part of" association
+ assert houseClass.eAllContainments.eType.is_a?(Array)
+ assert_equal 1, houseClass.eAllContainments.eType.size
+ roomRef = houseClass.eAllContainments.first
+ assert_equal roomClass.object_id, roomRef.eType.object_id
+ assert_equal "room", roomRef.name
+ assert_equal 1, roomRef.lowerBound
+ assert_equal(-1, roomRef.upperBound)
+ assert_not_nil roomRef.eOpposite
+ assert_equal houseClass.object_id, roomRef.eOpposite.eType.object_id
+
+ partOfRefs = roomClass.eReferences.select{|r| r.eOpposite && r.eOpposite.containment}
+ assert_equal 1, partOfRefs.size
+ assert_equal houseClass.object_id, partOfRefs.first.eType.object_id
+ assert_equal "house", partOfRefs.first.name
+ assert_equal roomRef.object_id, partOfRefs.first.eOpposite.object_id
+
+ # check House OUT associations
+ assert houseClass.eReferences.is_a?(Array)
+ assert_equal 3, houseClass.eReferences.size
+ bathRef = houseClass.eReferences.find {|e| e.name == "bathroom"}
+ kitchenRef = houseClass.eReferences.find {|e| e.name == "kitchen"}
+ roomRef = houseClass.eReferences.find {|e| e.name == "room"}
+ assert_not_nil bathRef
+ assert_nil bathRef.eOpposite
+ assert_not_nil kitchenRef
+ assert_not_nil roomRef
+ assert_equal 1, kitchenRef.lowerBound
+ assert_equal 1, kitchenRef.upperBound
+ assert_equal 1, roomRef.lowerBound
+ assert_equal(-1, roomRef.upperBound)
+
+ # check House IN associations
+ houseInRefs = env.find(:class => EReference, :eType => houseClass)
+ assert_equal 3, houseInRefs.size
+ homeEnd = houseInRefs.find{|e| e.name == "home"}
+ assert_not_nil homeEnd
+ assert_equal 0, homeEnd.lowerBound
+ assert_equal(-1, homeEnd.upperBound)
+
+ end
+end
+
+end \ No newline at end of file
diff --git a/lib/puppet/vendor/rgen/test/testmodel/manual_testmodel.xml b/lib/puppet/vendor/rgen/test/testmodel/manual_testmodel.xml
new file mode 100644
index 000000000..d45a0a611
--- /dev/null
+++ b/lib/puppet/vendor/rgen/test/testmodel/manual_testmodel.xml
@@ -0,0 +1,22 @@
+<TestModel xmlns:MNS="testmodel.org/myNamespace" >
+ <MNS:House>
+ before kitchen
+ <MNS:Room id="1" name="Kitchen" />
+ after kitchen
+ <MNS:Room id="2" name="TomsRoom">within toms room</MNS:Room>
+ after toms room
+ </MNS:House>
+ <Person name="Tom" room="2">
+ <Parents>
+ <Person name="Kate">
+ <Parents>
+ <Person name="Maria" />
+ <Person name="Will" />
+ </Parents>
+ </Person>
+ </Parents>
+ </Person>
+ <MULTI-PART-NAME>
+ <INSIDE_MULTI_PART/>
+ </MULTI-PART-NAME>
+</TestModel>
diff --git a/lib/puppet/vendor/rgen/test/testmodel/object_model_checker.rb b/lib/puppet/vendor/rgen/test/testmodel/object_model_checker.rb
new file mode 100644
index 000000000..415a8d3b2
--- /dev/null
+++ b/lib/puppet/vendor/rgen/test/testmodel/object_model_checker.rb
@@ -0,0 +1,67 @@
+require 'metamodels/uml13_metamodel'
+require 'metamodels/uml13_metamodel_ext'
+
+module Testmodel
+
+# Checks the UML Object model elements from the example model
+#
+module ObjectModelChecker
+
+ # convenient extension for this test only
+ module UML13::ClassifierRole::ClassModule
+ def classname
+ taggedValue.find{|tv| tv.tag == "classname"}.value
+ end
+ end
+
+ def checkObjectModel(envUML)
+
+ # check main package
+ mainPackage = envUML.find(:class => UML13::Package, :name => "HouseExampleModel").first
+ assert_not_nil mainPackage
+
+ eaRootCollaboration = mainPackage.ownedElement.find{|e| e.is_a?(UML13::Collaboration) && e.name == "Collaborations"}
+ assert_not_nil eaRootCollaboration
+
+ # check main package objects
+ objects = eaRootCollaboration.ownedElement.select{|e| e.is_a?(UML13::ClassifierRole)}
+ assert_equal 6, objects.size
+
+ someone = objects.find{|o| o.name == "Someone"}
+ assert_equal "Person", someone.classname
+
+ someonesHouse = objects.find{|o| o.name == "SomeonesHouse"}
+ assert_equal "House", someonesHouse.classname
+
+ greenRoom = objects.find{|o| o.name == "GreenRoom"}
+ assert_equal "Room", greenRoom.classname
+
+ yellowRoom = objects.find{|o| o.name == "YellowRoom"}
+ assert_equal "Room", yellowRoom.classname
+
+ hotRoom = objects.find{|o| o.name == "HotRoom"}
+ assert_equal "Kitchen", hotRoom.classname
+
+ wetRoom = objects.find{|o| o.name == "WetRoom"}
+ assert_equal "Bathroom", wetRoom.classname
+
+ # Someone to SomeonesHouse
+ assert someone.associationEnd.otherEnd.getType.is_a?(Array)
+ assert_equal 1, someone.associationEnd.otherEnd.getType.size
+ houseEnd = someone.associationEnd.otherEnd[0]
+ assert_equal someonesHouse.object_id, houseEnd.getType.object_id
+ assert_equal "home", houseEnd.name
+
+ # Someone to SomeonesHouse
+ assert someonesHouse.localCompositeEnd.otherEnd.is_a?(Array)
+ assert_equal 4, someonesHouse.localCompositeEnd.otherEnd.size
+ assert someonesHouse.localCompositeEnd.otherEnd.all?{|e| e.name == "room"}
+ assert_not_nil someonesHouse.localCompositeEnd.otherEnd.getType.find{|o| o == yellowRoom}
+ assert_not_nil someonesHouse.localCompositeEnd.otherEnd.getType.find{|o| o == greenRoom}
+ assert_not_nil someonesHouse.localCompositeEnd.otherEnd.getType.find{|o| o == hotRoom}
+ assert_not_nil someonesHouse.localCompositeEnd.otherEnd.getType.find{|o| o == wetRoom}
+
+ end
+end
+
+end \ No newline at end of file
diff --git a/lib/puppet/vendor/rgen/test/transformer_test.rb b/lib/puppet/vendor/rgen/test/transformer_test.rb
new file mode 100644
index 000000000..afbf5fba0
--- /dev/null
+++ b/lib/puppet/vendor/rgen/test/transformer_test.rb
@@ -0,0 +1,254 @@
+$:.unshift File.join(File.dirname(__FILE__),"..","lib")
+$:.unshift File.join(File.dirname(__FILE__),"..","test")
+
+require 'test/unit'
+require 'rgen/transformer'
+require 'rgen/environment'
+require 'rgen/util/model_comparator'
+require 'metamodels/uml13_metamodel'
+require 'testmodel/class_model_checker'
+
+class TransformerTest < Test::Unit::TestCase
+
+ class ModelIn
+ attr_accessor :name
+ end
+
+ class ModelInSub < ModelIn
+ end
+
+ class ModelAIn
+ attr_accessor :name
+ attr_accessor :modelB
+ end
+
+ class ModelBIn
+ attr_accessor :name
+ attr_accessor :modelA
+ end
+
+ class ModelCIn
+ attr_accessor :number
+ end
+
+ class ModelOut
+ attr_accessor :name
+ end
+
+ class ModelAOut
+ attr_accessor :name
+ attr_accessor :modelB
+ end
+
+ class ModelBOut
+ attr_accessor :name
+ attr_accessor :modelA
+ end
+
+ class ModelCOut
+ attr_accessor :number
+ end
+
+ class MyTransformer < RGen::Transformer
+ attr_reader :modelInTrans_count
+ attr_reader :modelAInTrans_count
+ attr_reader :modelBInTrans_count
+
+ transform ModelIn, :to => ModelOut do
+ # aribitrary ruby code may be placed before the hash creating the output element
+ @modelInTrans_count ||= 0; @modelInTrans_count += 1
+ { :name => name }
+ end
+
+ transform ModelAIn, :to => ModelAOut do
+ @modelAInTrans_count ||= 0; @modelAInTrans_count += 1
+ { :name => name, :modelB => trans(modelB) }
+ end
+
+ transform ModelBIn, :to => ModelBOut do
+ @modelBInTrans_count ||= 0; @modelBInTrans_count += 1
+ { :name => name, :modelA => trans(modelA) }
+ end
+
+ transform ModelCIn, :to => ModelCOut, :if => :largeNumber do
+ # a method can be called anywhere in a transformer block
+ { :number => duplicateNumber }
+ end
+
+ transform ModelCIn, :to => ModelCOut, :if => :smallNumber do
+ { :number => number / 2 }
+ end
+
+ method :largeNumber do
+ number > 1000
+ end
+
+ method :smallNumber do
+ number < 500
+ end
+
+ method :duplicateNumber do
+ number * 2;
+ end
+
+ end
+
+ class MyTransformer2 < RGen::Transformer
+ # check that subclasses are independent (i.e. do not share the rules)
+ transform ModelIn, :to => ModelOut do
+ { :name => name }
+ end
+ end
+
+ def test_transformer
+ from = ModelIn.new
+ from.name = "TestName"
+ env_out = RGen::Environment.new
+ t = MyTransformer.new(:env_in, env_out)
+ assert t.trans(from).is_a?(ModelOut)
+ assert_equal "TestName", t.trans(from).name
+ assert_equal 1, env_out.elements.size
+ assert_equal env_out.elements.first, t.trans(from)
+ assert_equal 1, t.modelInTrans_count
+ end
+
+ def test_transformer_chain
+ from = ModelIn.new
+ from.name = "Test1"
+ from2 = ModelIn.new
+ from2.name = "Test2"
+ from3 = ModelIn.new
+ from3.name = "Test3"
+ env_out = RGen::Environment.new
+ elementMap = {}
+ t1 = MyTransformer.new(:env_in, env_out, elementMap)
+ assert t1.trans(from).is_a?(ModelOut)
+ assert_equal "Test1", t1.trans(from).name
+ assert_equal 1, t1.modelInTrans_count
+ # modifying the element map means that following calls of +trans+ will be affected
+ assert_equal( {from => t1.trans(from)}, elementMap )
+ elementMap.merge!({from2 => :dummy})
+ assert_equal :dummy, t1.trans(from2)
+ # second transformer based on the element map of the first
+ t2 = MyTransformer.new(:env_in, env_out, elementMap)
+ # second transformer returns same objects
+ assert_equal t1.trans(from).object_id, t2.trans(from).object_id
+ assert_equal :dummy, t2.trans(from2)
+ # and no transformer rule is evaluated at this point
+ assert_equal nil, t2.modelInTrans_count
+ # now transform a new object in second transformer
+ assert t2.trans(from3).is_a?(ModelOut)
+ assert_equal "Test3", t2.trans(from3).name
+ assert_equal 1, t2.modelInTrans_count
+ # the first transformer returns the same object without evaluation of a transformer rule
+ assert_equal t1.trans(from3).object_id, t2.trans(from3).object_id
+ assert_equal 1, t1.modelInTrans_count
+ end
+
+ def test_transformer_subclass
+ from = ModelInSub.new
+ from.name = "TestName"
+ t = MyTransformer.new
+ assert t.trans(from).is_a?(ModelOut)
+ assert_equal "TestName", t.trans(from).name
+ assert_equal 1, t.modelInTrans_count
+ end
+
+ def test_transformer_array
+ froms = [ModelIn.new, ModelIn.new]
+ froms[0].name = "M1"
+ froms[1].name = "M2"
+ env_out = RGen::Environment.new
+ t = MyTransformer.new(:env_in, env_out)
+ assert t.trans(froms).is_a?(Array)
+ assert t.trans(froms)[0].is_a?(ModelOut)
+ assert_equal "M1", t.trans(froms)[0].name
+ assert t.trans(froms)[1].is_a?(ModelOut)
+ assert_equal "M2", t.trans(froms)[1].name
+ assert_equal 2, env_out.elements.size
+ assert (t.trans(froms)-env_out.elements).empty?
+ assert_equal 2, t.modelInTrans_count
+ end
+
+ def test_transformer_cyclic
+ # setup a cyclic dependency between fromA and fromB
+ fromA = ModelAIn.new
+ fromB = ModelBIn.new
+ fromA.modelB = fromB
+ fromA.name = "ModelA"
+ fromB.modelA = fromA
+ fromB.name = "ModelB"
+ env_out = RGen::Environment.new
+ t = MyTransformer.new(:env_in, env_out)
+ # check that trans resolves the cycle correctly (no endless loop)
+ # both elements, fromA and fromB will be transformed with the transformation
+ # of the first element, either fromA or fromB
+ assert t.trans(fromA).is_a?(ModelAOut)
+ assert_equal "ModelA", t.trans(fromA).name
+ assert t.trans(fromA).modelB.is_a?(ModelBOut)
+ assert_equal "ModelB", t.trans(fromA).modelB.name
+ assert_equal t.trans(fromA), t.trans(fromA).modelB.modelA
+ assert_equal t.trans(fromB), t.trans(fromA).modelB
+ assert_equal 2, env_out.elements.size
+ assert (env_out.elements - [t.trans(fromA), t.trans(fromB)]).empty?
+ assert_equal 1, t.modelAInTrans_count
+ assert_equal 1, t.modelBInTrans_count
+ end
+
+ def test_transformer_conditional
+ froms = [ModelCIn.new, ModelCIn.new, ModelCIn.new]
+ froms[0].number = 100
+ froms[1].number = 1000
+ froms[2].number = 2000
+
+ env_out = RGen::Environment.new
+ t = MyTransformer.new(:env_in, env_out)
+
+ assert t.trans(froms).is_a?(Array)
+ assert_equal 2, t.trans(froms).size
+
+ # this one matched the smallNumber rule
+ assert t.trans(froms[0]).is_a?(ModelCOut)
+ assert_equal 50, t.trans(froms[0]).number
+
+ # this one did not match any rule
+ assert t.trans(froms[1]).nil?
+
+ # this one matched the largeNumber rule
+ assert t.trans(froms[2]).is_a?(ModelCOut)
+ assert_equal 4000, t.trans(froms[2]).number
+
+ # elements in environment are the same as the ones returned
+ assert_equal 2, env_out.elements.size
+ assert (t.trans(froms)-env_out.elements).empty?
+ end
+
+ class CopyTransformer < RGen::Transformer
+ include UML13
+ def transform
+ trans(:class => UML13::Package)
+ end
+ UML13.ecore.eClassifiers.each do |c|
+ copy c.instanceClass
+ end
+ end
+
+ MODEL_DIR = File.join(File.dirname(__FILE__),"testmodel")
+
+ include Testmodel::ClassModelChecker
+ include RGen::Util::ModelComparator
+
+ def test_copyTransformer
+ envIn = RGen::Environment.new
+ envOut = RGen::Environment.new
+
+ EASupport.instantiateUML13FromXMI11(envIn, MODEL_DIR+"/ea_testmodel.xml")
+
+ CopyTransformer.new(envIn, envOut).transform
+ checkClassModel(envOut)
+ assert modelEqual?(
+ envIn.find(:class => UML13::Model).first,
+ envOut.find(:class => UML13::Model).first)
+ end
+
+end
diff --git a/lib/puppet/vendor/rgen/test/util/file_cache_map_test.rb b/lib/puppet/vendor/rgen/test/util/file_cache_map_test.rb
new file mode 100644
index 000000000..fcb4e32f2
--- /dev/null
+++ b/lib/puppet/vendor/rgen/test/util/file_cache_map_test.rb
@@ -0,0 +1,99 @@
+$:.unshift(File.dirname(__FILE__)+"/../../lib")
+
+require 'test/unit'
+require 'fileutils'
+require 'rgen/util/file_cache_map'
+
+class FileCacheMapTest < Test::Unit::TestCase
+
+ TestDir = File.dirname(__FILE__)+"/file_cache_map_test/testdir"
+
+ def setup
+ FileUtils.rm_r(Dir[TestDir+"/*"])
+ # * doesn't include dot files
+ FileUtils.rm_r(Dir[TestDir+"/.cache"])
+ @cm = RGen::Util::FileCacheMap.new(".cache", ".test")
+ end
+
+ def test_nocache
+ reasons = []
+ assert_equal(:invalid, @cm.load_data(TestDir+"/fileA", :invalidation_reasons => reasons))
+ assert_equal [:no_cachefile], reasons
+ end
+
+ def test_storeload
+ keyFile = TestDir+"/fileA"
+ File.open(keyFile, "w") {|f| f.write("somedata")}
+ @cm.store_data(keyFile, "valuedata")
+ assert(File.exist?(TestDir+"/.cache/fileA.test"))
+ assert_equal("valuedata", @cm.load_data(keyFile))
+ end
+
+ def test_storeload_subdir
+ keyFile = TestDir+"/subdir/fileA"
+ FileUtils.mkdir(TestDir+"/subdir")
+ File.open(keyFile, "w") {|f| f.write("somedata")}
+ @cm.store_data(keyFile, "valuedata")
+ assert(File.exist?(TestDir+"/subdir/.cache/fileA.test"))
+ assert_equal("valuedata", @cm.load_data(keyFile))
+ end
+
+ def test_storeload_postfix
+ keyFile = TestDir+"/fileB.txt"
+ File.open(keyFile, "w") {|f| f.write("somedata")}
+ @cm.store_data(keyFile, "valuedata")
+ assert(File.exist?(TestDir+"/.cache/fileB.txt.test"))
+ assert_equal("valuedata", @cm.load_data(keyFile))
+ end
+
+ def test_storeload_empty
+ keyFile = TestDir+"/fileA"
+ File.open(keyFile, "w") {|f| f.write("")}
+ @cm.store_data(keyFile, "valuedata")
+ assert(File.exist?(TestDir+"/.cache/fileA.test"))
+ assert_equal("valuedata", @cm.load_data(keyFile))
+ end
+
+ def test_corruptcache
+ keyFile = TestDir+"/fileA"
+ File.open(keyFile, "w") {|f| f.write("somedata")}
+ @cm.store_data(keyFile, "valuedata")
+ File.open(TestDir+"/.cache/fileA.test","a") {|f| f.write("more data")}
+ reasons = []
+ assert_equal(:invalid, @cm.load_data(keyFile, :invalidation_reasons => reasons))
+ assert_equal [:cachefile_corrupted], reasons
+ end
+
+ def test_changedcontent
+ keyFile = TestDir+"/fileA"
+ File.open(keyFile, "w") {|f| f.write("somedata")}
+ @cm.store_data(keyFile, "valuedata")
+ File.open(keyFile, "a") {|f| f.write("more data")}
+ reasons = []
+ assert_equal(:invalid, @cm.load_data(keyFile, :invalidation_reasons => reasons))
+ assert_equal [:keyfile_changed], reasons
+ end
+
+ def test_versioninfo
+ keyFile = TestDir+"/fileA"
+ File.open(keyFile, "w") {|f| f.write("somedata")}
+ @cm.version_info = "123"
+ @cm.store_data(keyFile, "valuedata")
+ assert(File.exist?(TestDir+"/.cache/fileA.test"))
+ assert_equal("valuedata", @cm.load_data(keyFile))
+ end
+
+ def test_changed_version
+ keyFile = TestDir+"/fileA"
+ File.open(keyFile, "w") {|f| f.write("somedata")}
+ @cm.version_info = "123"
+ @cm.store_data(keyFile, "valuedata")
+ @cm.version_info = "456"
+ reasons = []
+ assert_equal(:invalid, @cm.load_data(keyFile, :invalidation_reasons => reasons))
+ assert_equal [:keyfile_changed], reasons
+ end
+
+end
+
+
diff --git a/lib/puppet/vendor/rgen/test/util/pattern_matcher_test.rb b/lib/puppet/vendor/rgen/test/util/pattern_matcher_test.rb
new file mode 100644
index 000000000..156856492
--- /dev/null
+++ b/lib/puppet/vendor/rgen/test/util/pattern_matcher_test.rb
@@ -0,0 +1,97 @@
+$:.unshift(File.dirname(__FILE__)+"/../../lib")
+
+require 'test/unit'
+require 'rgen/environment'
+require 'rgen/metamodel_builder'
+require 'rgen/model_builder'
+require 'rgen/util/pattern_matcher'
+
+class PatternMatcherTest < Test::Unit::TestCase
+
+module TestMM
+ extend RGen::MetamodelBuilder::ModuleExtension
+
+ class Node < RGen::MetamodelBuilder::MMBase
+ has_attr 'name', String
+ contains_many 'children', Node, 'parent'
+ end
+end
+
+def modelA
+ env = RGen::Environment.new
+ RGen::ModelBuilder.build(TestMM, env) do
+ node "A" do
+ node "AA"
+ end
+ node "B" do
+ node "B1"
+ node "B2"
+ node "B3"
+ end
+ node "C" do
+ node "C1"
+ node "C2"
+ end
+ node "D" do
+ node "DD"
+ end
+ end
+ env
+end
+
+def test_simple
+ matcher = RGen::Util::PatternMatcher.new
+ matcher.add_pattern("simple") do |env, c|
+ TestMM::Node.new(:name => "A", :children => [
+ TestMM::Node.new(:name => "AA")])
+ end
+ matcher.add_pattern("bad") do |env, c|
+ TestMM::Node.new(:name => "X")
+ end
+ env = modelA
+
+ match = matcher.find_pattern(env, "simple")
+ assert_not_nil match
+ assert_equal "A", match.root.name
+ assert_equal env.find(:class => TestMM::Node, :name => "A").first.object_id, match.root.object_id
+ assert_equal 2, match.elements.size
+ assert_equal [nil], match.bound_values
+
+ assert_nil matcher.find_pattern(env, "bad")
+end
+
+def test_value_binding
+ matcher = RGen::Util::PatternMatcher.new
+ matcher.add_pattern("single_child") do |env, name, child|
+ TestMM::Node.new(:name => name, :children => [ child ])
+ end
+ matcher.add_pattern("double_child") do |env, name, child1, child2|
+ TestMM::Node.new(:name => name, :children => [ child1, child2 ])
+ end
+ matcher.add_pattern("child_pattern") do |env, child_name|
+ TestMM::Node.new(:name => "A", :children => [
+ TestMM::Node.new(:name => child_name)])
+ end
+ env = modelA
+
+ match = matcher.find_pattern(env, "single_child")
+ assert_not_nil match
+ assert_equal "A", match.root.name
+ assert_equal "AA", match.bound_values[1].name
+
+ match = matcher.find_pattern(env, "single_child", "D")
+ assert_not_nil match
+ assert_equal "D", match.root.name
+ assert_equal "DD", match.bound_values[0].name
+
+ match = matcher.find_pattern(env, "double_child")
+ assert_not_nil match
+ assert_equal "C", match.root.name
+
+ match = matcher.find_pattern(env, "child_pattern")
+ assert_not_nil match
+ assert_equal ["AA"], match.bound_values
+end
+
+end
+
diff --git a/lib/puppet/vendor/rgen/test/util_test.rb b/lib/puppet/vendor/rgen/test/util_test.rb
new file mode 100644
index 000000000..d36e366db
--- /dev/null
+++ b/lib/puppet/vendor/rgen/test/util_test.rb
@@ -0,0 +1,5 @@
+$:.unshift File.dirname(__FILE__)
+
+require 'util/file_cache_map_test'
+require 'util/pattern_matcher_test'
+
diff --git a/lib/puppet/vendor/rgen/test/xml_instantiator_test.rb b/lib/puppet/vendor/rgen/test/xml_instantiator_test.rb
new file mode 100644
index 000000000..145058250
--- /dev/null
+++ b/lib/puppet/vendor/rgen/test/xml_instantiator_test.rb
@@ -0,0 +1,160 @@
+$:.unshift File.join(File.dirname(__FILE__),"..","lib")
+
+require 'test/unit'
+require 'rgen/instantiator/default_xml_instantiator'
+require 'rgen/environment'
+require 'rgen/util/model_dumper'
+require 'xml_instantiator_test/simple_xmi_ecore_instantiator'
+require 'xml_instantiator_test/simple_ecore_model_checker'
+
+module EmptyMM
+end
+
+module DefaultMM
+ module MNS
+ class Room < RGen::MetamodelBuilder::MMBase; end
+ end
+ class Person < RGen::MetamodelBuilder::MMBase; end
+ Person.one_to_one 'personalRoom', MNS::Room, 'inhabitant'
+end
+
+class XMLInstantiatorTest < Test::Unit::TestCase
+
+ XML_DIR = File.join(File.dirname(__FILE__),"testmodel")
+
+ include RGen::Util::ModelDumper
+
+ class MyInstantiator < RGen::Instantiator::DefaultXMLInstantiator
+
+ map_tag_ns "testmodel.org/myNamespace", DefaultMM::MNS
+
+ def class_name(str)
+ camelize(str)
+ end
+
+# resolve :type do
+# @env.find(:xmi_id => getType).first
+# end
+
+ resolve_by_id :personalRoom, :id => :getId, :src => :room
+
+ end
+
+ class PruneTestInstantiator < RGen::Instantiator::NodebasedXMLInstantiator
+ attr_reader :max_depth
+
+ set_prune_level 2
+
+ def initialize(env)
+ super(env)
+ @max_depth = 0
+ end
+
+ def on_descent(node)
+ end
+
+ def on_ascent(node)
+ calc_max_depth(node, 0)
+ end
+
+ def calc_max_depth(node, offset)
+ if node.children.nil? || node.children.size == 0
+ @max_depth = offset if offset > @max_depth
+ else
+ node.children.each do |c|
+ calc_max_depth(c, offset+1)
+ end
+ end
+ end
+ end
+
+ module PruneTestMM
+ end
+
+ def test_pruning
+ env = RGen::Environment.new
+
+ # prune level 2 is set in the class body
+ inst = PruneTestInstantiator.new(env)
+ inst.instantiate_file(File.join(XML_DIR,"manual_testmodel.xml"))
+ assert_equal 2, inst.max_depth
+
+ PruneTestInstantiator.set_prune_level(0)
+ inst = PruneTestInstantiator.new(env)
+ inst.instantiate_file(File.join(XML_DIR,"manual_testmodel.xml"))
+ assert_equal 5, inst.max_depth
+
+ PruneTestInstantiator.set_prune_level(1)
+ inst = PruneTestInstantiator.new(env)
+ inst.instantiate_file(File.join(XML_DIR,"manual_testmodel.xml"))
+ assert_equal 1, inst.max_depth
+ end
+
+ def test_custom
+ env = RGen::Environment.new
+ inst = MyInstantiator.new(env, DefaultMM, true)
+ inst.instantiate_file(File.join(XML_DIR,"manual_testmodel.xml"))
+
+ house = env.find(:class => DefaultMM::MNS::House).first
+ assert_not_nil house
+ assert_equal 2, house.room.size
+
+ rooms = env.find(:class => DefaultMM::MNS::Room)
+ assert_equal 2, rooms.size
+ assert_equal 0, (house.room - rooms).size
+ rooms.each {|r| assert r.parent == house}
+ tomsRoom = rooms.select{|r| r.name == "TomsRoom"}.first
+ assert_not_nil tomsRoom
+
+ persons = env.find(:class => DefaultMM::Person)
+ assert_equal 4, persons.size
+ tom = persons.select{|p| p.name == "Tom"}.first
+ assert_not_nil tom
+
+ assert tom.personalRoom == tomsRoom
+
+ mpns = env.find(:class => DefaultMM::MultiPartName)
+ assert mpns.first.respond_to?("insideMultiPart")
+ end
+
+ def test_default
+ env = RGen::Environment.new
+ inst = RGen::Instantiator::DefaultXMLInstantiator.new(env, EmptyMM, true)
+ inst.instantiate_file(File.join(XML_DIR,"manual_testmodel.xml"))
+
+ house = env.find(:class => EmptyMM::MNS_House).first
+ assert_not_nil house
+ assert_equal 2, house.mNS_Room.size
+ assert_equal "before kitchen", remove_whitespace_elements(house.chardata)[0].strip
+ assert_equal "after kitchen", remove_whitespace_elements(house.chardata)[1].strip
+ assert_equal "after toms room", remove_whitespace_elements(house.chardata)[2].strip
+
+ rooms = env.find(:class => EmptyMM::MNS_Room)
+ assert_equal 2, rooms.size
+ assert_equal 0, (house.mNS_Room - rooms).size
+ rooms.each {|r| assert r.parent == house}
+ tomsRoom = rooms.select{|r| r.name == "TomsRoom"}.first
+ assert_not_nil tomsRoom
+ assert_equal "within toms room", remove_whitespace_elements(tomsRoom.chardata)[0]
+
+ persons = env.find(:class => EmptyMM::Person)
+ assert_equal 4, persons.size
+ tom = persons.select{|p| p.name == "Tom"}.first
+ assert_not_nil tom
+ end
+
+ def remove_whitespace_elements(elements)
+ elements.reject{|e| e.strip == ""}
+ end
+
+ include SimpleECoreModelChecker
+
+ def test_simle_xmi_ecore_instantiator
+ envECore = RGen::Environment.new
+ File.open(XML_DIR+"/ea_testmodel.xml") { |f|
+ SimpleXMIECoreInstantiator.new.instantiateECoreModel(envECore, f.read)
+ }
+ checkECoreModel(envECore)
+ end
+
+end
diff --git a/lib/puppet/vendor/rgen/test/xml_instantiator_test/simple_ecore_model_checker.rb b/lib/puppet/vendor/rgen/test/xml_instantiator_test/simple_ecore_model_checker.rb
new file mode 100644
index 000000000..986af3309
--- /dev/null
+++ b/lib/puppet/vendor/rgen/test/xml_instantiator_test/simple_ecore_model_checker.rb
@@ -0,0 +1,94 @@
+require 'rgen/ecore/ecore'
+
+# This "light" version of the ECore model checker is used to check the
+# model produced by the XMLInstantiatorTest only.
+#
+module SimpleECoreModelChecker
+ include RGen::ECore
+
+ def checkECoreModel(env)
+
+ # check main package
+ mainPackage = env.elements.select {|e| e.is_a? EPackage and e.name == "HouseMetamodel"}.first
+ assert_not_nil mainPackage
+
+ # check Rooms package
+ assert mainPackage.eSubpackages.is_a?(Array)
+ assert_equal 1, mainPackage.eSubpackages.size
+ assert mainPackage.eSubpackages[0].is_a?(EPackage)
+ roomsPackage = mainPackage.eSubpackages[0]
+ assert_equal "Rooms", roomsPackage.name
+
+ # check main package classes
+ assert mainPackage.eClassifiers.is_a?(Array)
+ assert_equal 3, mainPackage.eClassifiers.size
+ assert mainPackage.eClassifiers.all?{|c| c.is_a?(EClass)}
+ houseClass = mainPackage.eClassifiers.select{|c| c.name == "House"}.first
+ personClass = mainPackage.eClassifiers.select{|c| c.name == "Person"}.first
+ meetingPlaceClass = mainPackage.eClassifiers.select{|c| c.name == "MeetingPlace"}.first
+ assert_not_nil houseClass
+ assert_not_nil personClass
+ assert_not_nil meetingPlaceClass
+
+ # check Rooms package classes
+ assert roomsPackage.eClassifiers.is_a?(Array)
+ assert_equal 3, roomsPackage.eClassifiers.size
+ assert roomsPackage.eClassifiers.all?{|c| c.is_a?(EClass)}
+ roomClass = roomsPackage.eClassifiers.select{|c| c.name == "Room"}.first
+ kitchenClass = roomsPackage.eClassifiers.select{|c| c.name == "Kitchen"}.first
+ bathroomClass = roomsPackage.eClassifiers.select{|c| c.name == "Bathroom"}.first
+ assert_not_nil roomClass
+ assert_not_nil kitchenClass
+ assert_not_nil bathroomClass
+
+ # check Room inheritance
+ assert kitchenClass.eSuperTypes.is_a?(Array)
+ assert_equal 2, kitchenClass.eSuperTypes.size
+ assert_equal roomClass.object_id, kitchenClass.eSuperTypes.select{|c| c.name == "Room"}.first.object_id
+ assert_equal meetingPlaceClass.object_id, kitchenClass.eSuperTypes.select{|c| c.name == "MeetingPlace"}.first.object_id
+ assert bathroomClass.eSuperTypes.is_a?(Array)
+ assert_equal 1, bathroomClass.eSuperTypes.size
+ assert_equal roomClass.object_id, bathroomClass.eSuperTypes[0].object_id
+
+ # check House-Room "part of" association
+ assert houseClass.eAllContainments.eType.is_a?(Array)
+ assert_equal 1, houseClass.eAllContainments.eType.size
+ roomRef = houseClass.eAllContainments.first
+ assert_equal roomClass.object_id, roomRef.eType.object_id
+ assert_equal "room", roomRef.name
+ assert_equal 1, roomRef.lowerBound
+ assert_equal(-1, roomRef.upperBound)
+ assert_not_nil roomRef.eOpposite
+ assert_equal houseClass.object_id, roomRef.eOpposite.eType.object_id
+
+ partOfRefs = roomClass.eReferences.select{|r| r.eOpposite && r.eOpposite.containment}
+ assert_equal 1, partOfRefs.size
+ assert_equal houseClass.object_id, partOfRefs.first.eType.object_id
+ assert_equal "house", partOfRefs.first.name
+ assert_equal roomRef.object_id, partOfRefs.first.eOpposite.object_id
+
+ # check House OUT associations
+ assert houseClass.eReferences.is_a?(Array)
+ assert_equal 3, houseClass.eReferences.size
+ bathRef = houseClass.eReferences.find {|e| e.name == "bathroom"}
+ kitchenRef = houseClass.eReferences.find {|e| e.name == "kitchen"}
+ roomRef = houseClass.eReferences.find {|e| e.name == "room"}
+ assert_not_nil bathRef
+ assert_nil bathRef.eOpposite
+ assert_not_nil kitchenRef
+ assert_not_nil roomRef
+ assert_equal 1, kitchenRef.lowerBound
+ assert_equal 1, kitchenRef.upperBound
+ assert_equal 1, roomRef.lowerBound
+ assert_equal(-1, roomRef.upperBound)
+
+ # check House IN associations
+ houseInRefs = env.find(:class => EReference, :eType => houseClass)
+ assert_equal 3, houseInRefs.size
+ homeEnd = houseInRefs.find{|e| e.name == "home"}
+ assert_not_nil homeEnd
+ assert_equal 0, homeEnd.lowerBound
+ assert_equal(-1, homeEnd.upperBound)
+
+ end
+end
diff --git a/lib/puppet/vendor/rgen/test/xml_instantiator_test/simple_xmi_ecore_instantiator.rb b/lib/puppet/vendor/rgen/test/xml_instantiator_test/simple_xmi_ecore_instantiator.rb
new file mode 100644
index 000000000..33cf97759
--- /dev/null
+++ b/lib/puppet/vendor/rgen/test/xml_instantiator_test/simple_xmi_ecore_instantiator.rb
@@ -0,0 +1,53 @@
+require 'rgen/instantiator/default_xml_instantiator'
+require 'rgen/environment'
+require 'rgen/ecore/ecore'
+require 'xml_instantiator_test/simple_xmi_metamodel'
+
+# SimpleXMIECoreInstantiator demonstrates the usage of the DefaultXMLInstantiator.
+# It can be used to instantiate an ECore model from an XMI description
+# produced by Enterprise Architect.
+#
+# Note however, that this is *not* the recommended way to read an EA model.
+# See EAInstantiatorTest for the clean way to do this.
+#
+# This example shows how arbitrary XML content can be used to instantiate
+# an implicit metamodel. The resulting model is transformed into a simple
+# ECore model.
+#
+# See XMLInstantiatorTest for an example of how to use this class.
+#
+class SimpleXMIECoreInstantiator < RGen::Instantiator::DefaultXMLInstantiator
+
+ map_tag_ns "omg.org/UML1.3", SimpleXMIMetaModel::UML
+
+ resolve_by_id :typeClass, :src => :type, :id => :xmi_id
+ resolve_by_id :subtypeClass, :src => :subtype, :id => :xmi_id
+ resolve_by_id :supertypeClass, :src => :supertype, :id => :xmi_id
+
+ def initialize
+ @envXMI = RGen::Environment.new
+ super(@envXMI, SimpleXMIMetaModel, true)
+ end
+
+ def new_object(node)
+ if node.tag == "EAStub"
+ class_name = saneClassName(node.attributes["UMLType"])
+ mod = XMIMetaModel::UML
+ build_on_error(NameError, :build_class, class_name, mod) do
+ mod.const_get(class_name).new
+ end
+ else
+ super
+ end
+ end
+
+ # This method does the actual work.
+ def instantiateECoreModel(envOut, str)
+ instantiate(str)
+
+ require 'xml_instantiator_test/simple_xmi_to_ecore'
+
+ SimpleXmiToECore.new(@envXMI,envOut).transform
+ end
+
+end
diff --git a/lib/puppet/vendor/rgen/test/xml_instantiator_test/simple_xmi_metamodel.rb b/lib/puppet/vendor/rgen/test/xml_instantiator_test/simple_xmi_metamodel.rb
new file mode 100644
index 000000000..38bc0ceb4
--- /dev/null
+++ b/lib/puppet/vendor/rgen/test/xml_instantiator_test/simple_xmi_metamodel.rb
@@ -0,0 +1,49 @@
+# This is an extension of the implicit metamodel created by the
+# DefaultXMLInstantiator when it reads an Enterprise Architect
+# XMI file.
+#
+module SimpleXMIMetaModel
+
+ module UML
+ include RGen::MetamodelBuilder
+
+ class Classifier_feature < MMBase
+ end
+
+ class ClassifierRole < MMBase
+ end
+
+ class Clazz < ClassifierRole
+ end
+
+ class Interface < ClassifierRole
+ end
+
+ class Operation < MMBase
+ end
+
+ class Generalization < MMBase
+ end
+
+ class ModelElement_stereotype < MMBase
+ end
+
+ class AssociationEnd < MMBase
+ module ClassModule
+ def otherEnd
+ parent.associationEnd.find{|ae| ae != self}
+ end
+ end
+ end
+
+ class AssociationEndRole < MMBase
+ end
+
+ ClassifierRole.one_to_many 'associationEnds', AssociationEnd, 'typeClass'
+ ClassifierRole.one_to_many 'associationEndRoles', AssociationEndRole, 'typeClass'
+ Clazz.one_to_many 'generalizationsAsSubtype', Generalization, 'subtypeClass'
+ Clazz.one_to_many 'generalizationsAsSupertype', Generalization, 'supertypeClass'
+
+ end
+
+end
diff --git a/lib/puppet/vendor/rgen/test/xml_instantiator_test/simple_xmi_to_ecore.rb b/lib/puppet/vendor/rgen/test/xml_instantiator_test/simple_xmi_to_ecore.rb
new file mode 100644
index 000000000..d95477562
--- /dev/null
+++ b/lib/puppet/vendor/rgen/test/xml_instantiator_test/simple_xmi_to_ecore.rb
@@ -0,0 +1,75 @@
+require 'rgen/transformer'
+require 'rgen/ecore/ecore'
+require 'rgen/array_extensions'
+require 'xml_instantiator_test/simple_xmi_metamodel'
+
+class SimpleXmiToECore < RGen::Transformer
+ include RGen::ECore
+
+ class MapHelper
+ def initialize(keyMethod,valueMethod,elements)
+ @keyMethod, @valueMethod, @elements = keyMethod, valueMethod, elements
+ end
+ def [](key)
+ return @elements.select{|e| e.send(@keyMethod) == key}.first.send(@valueMethod) rescue NoMethodError
+ nil
+ end
+ end
+
+ class TaggedValueHelper < MapHelper
+ def initialize(element)
+ super('tag','value',element.modelElement_taggedValue.taggedValue)
+ end
+ end
+
+ # Do the actual transformation.
+ # Input and output environment have to be provided to the transformer constructor.
+ def transform
+ trans(:class => SimpleXMIMetaModel::UML::Clazz)
+ end
+
+ transform SimpleXMIMetaModel::UML::Package, :to => EPackage do
+ { :name => name,
+ :eSuperPackage => trans(parent.parent.is_a?(SimpleXMIMetaModel::UML::Package) ? parent.parent : nil) }
+ end
+
+ transform SimpleXMIMetaModel::UML::Clazz, :to => EClass do
+ { :name => name,
+ :ePackage => trans(parent.parent.is_a?(SimpleXMIMetaModel::UML::Package) ? parent.parent : nil),
+ :eStructuralFeatures => trans(classifier_feature.attribute + associationEnds),
+ :eOperations => trans(classifier_feature.operation),
+ :eSuperTypes => trans(generalizationsAsSubtype.supertypeClass),
+ :eAnnotations => [ EAnnotation.new(:details => trans(modelElement_taggedValue.taggedValue)) ] }
+ end
+
+ transform SimpleXMIMetaModel::UML::TaggedValue, :to => EStringToStringMapEntry do
+ { :key => tag, :value => value}
+ end
+
+ transform SimpleXMIMetaModel::UML::Attribute, :to => EAttribute do
+ typemap = { "String" => EString, "boolean" => EBoolean, "int" => EInt, "long" => ELong, "float" => EFloat }
+ tv = TaggedValueHelper.new(@current_object)
+ { :name => name, :eType => typemap[tv['type']],
+ :eAnnotations => [ EAnnotation.new(:details => trans(modelElement_taggedValue.taggedValue)) ] }
+ end
+
+ transform SimpleXMIMetaModel::UML::Operation, :to => EOperation do
+ { :name => name }
+ end
+
+ transform SimpleXMIMetaModel::UML::AssociationEnd, :to => EReference, :if => :isReference do
+ { :eType => trans(otherEnd.typeClass),
+ :name => otherEnd.name,
+ :eOpposite => trans(otherEnd),
+ :lowerBound => (otherEnd.multiplicity || '0').split('..').first.to_i,
+ :upperBound => (otherEnd.multiplicity || '1').split('..').last.gsub('*','-1').to_i,
+ :containment => (aggregation == 'composite'),
+ :eAnnotations => [ EAnnotation.new(:details => trans(modelElement_taggedValue.taggedValue)) ] }
+ end
+
+ method :isReference do
+ otherEnd.isNavigable == 'true' ||
+ # composite assocations are bidirectional
+ aggregation == 'composite' || otherEnd.aggregation == 'composite'
+ end
+end
diff --git a/lib/puppet/vendor/safe_yaml/PUPPET_README.md b/lib/puppet/vendor/safe_yaml/PUPPET_README.md
new file mode 100644
index 000000000..55be8867c
--- /dev/null
+++ b/lib/puppet/vendor/safe_yaml/PUPPET_README.md
@@ -0,0 +1,6 @@
+SafeYAML
+=============================================
+
+safe_yaml version 0.9.2
+
+Copied from https://github.com/dtao/safe_yaml/tree/c5591b790472f7db413aaa28716f86ddf4929f48
diff --git a/lib/puppet/vendor/semantic/PUPPET_README.md b/lib/puppet/vendor/semantic/PUPPET_README.md
new file mode 100644
index 000000000..dc9d4ae62
--- /dev/null
+++ b/lib/puppet/vendor/semantic/PUPPET_README.md
@@ -0,0 +1,6 @@
+Semantic
+========
+
+Semantic 0.0.1
+
+Copied from https://github.com/puppetlabs/semantic/tree/6d17d5283e0868cfc3d74d1e9d37b572e1739b6e
diff --git a/lib/puppet/version.rb b/lib/puppet/version.rb
index 932d4f7ae..41fe9ed3c 100644
--- a/lib/puppet/version.rb
+++ b/lib/puppet/version.rb
@@ -7,7 +7,7 @@
module Puppet
- PUPPETVERSION = '3.6.1'
+ PUPPETVERSION = '3.7.0'
##
# version is a public API method intended to always provide a fast and