summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CONTRIBUTING.md5
-rw-r--r--Gemfile24
-rw-r--r--README.md2
-rwxr-xr-xbin/puppet4
-rw-r--r--ext/build_defaults.yaml17
-rw-r--r--ext/debian/changelog6
-rw-r--r--ext/debian/control5
-rw-r--r--ext/debian/puppet-common.dirs1
-rw-r--r--ext/debian/puppet-common.postinst16
-rw-r--r--ext/debian/puppet-common.postrm3
-rw-r--r--ext/debian/puppetmaster-passenger.postinst132
-rw-r--r--ext/ips/puppet.p5m4
-rw-r--r--ext/project_data.yaml28
-rw-r--r--ext/rack/example-passenger-vhost.conf8
-rw-r--r--ext/redhat/puppet.spec17
-rwxr-xr-xext/windows/service/daemon.rb88
-rwxr-xr-xinstall.rb2
-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
-rw-r--r--spec/fixtures/integration/node/environment/sitedir2/00_a.pp2
-rw-r--r--spec/fixtures/integration/node/environment/sitedir2/02_folder/01_b.pp6
-rw-r--r--spec/fixtures/integration/node/environment/sitedir2/03_c.pp1
-rw-r--r--spec/fixtures/integration/node/environment/sitedir2/04_include.pp2
-rw-r--r--spec/fixtures/releases/jamtur01-apache/manifests/vhost.pp2
-rw-r--r--spec/fixtures/unit/indirector/hiera/global.yaml10
-rw-r--r--spec/fixtures/unit/indirector/hiera/invalid.yaml1
-rw-r--r--spec/fixtures/unit/parser/functions/create_resources/foo/manifests/init.pp3
-rw-r--r--spec/fixtures/unit/parser/functions/create_resources/foo/manifests/wrongdefine.pp3
-rw-r--r--spec/fixtures/unit/parser/lexer/argumentdefaults.pp4
-rw-r--r--spec/fixtures/unit/parser/lexer/casestatement.pp28
-rw-r--r--spec/fixtures/unit/parser/lexer/classheirarchy.pp6
-rw-r--r--spec/fixtures/unit/parser/lexer/classincludes.pp6
-rw-r--r--spec/fixtures/unit/parser/lexer/classpathtest.pp2
-rw-r--r--spec/fixtures/unit/parser/lexer/collection_override.pp2
-rw-r--r--spec/fixtures/unit/parser/lexer/componentrequire.pp4
-rw-r--r--spec/fixtures/unit/parser/lexer/deepclassheirarchy.pp10
-rw-r--r--spec/fixtures/unit/parser/lexer/defineoverrides.pp4
-rw-r--r--spec/fixtures/unit/parser/lexer/filecreate.pp4
-rw-r--r--spec/fixtures/unit/parser/lexer/ifexpression.pp2
-rw-r--r--spec/fixtures/unit/parser/lexer/implicititeration.pp8
-rw-r--r--spec/fixtures/unit/parser/lexer/multipleinstances.pp6
-rw-r--r--spec/fixtures/unit/parser/lexer/multisubs.pp4
-rw-r--r--spec/fixtures/unit/parser/lexer/namevartest.pp4
-rw-r--r--spec/fixtures/unit/parser/lexer/simpledefaults.pp2
-rw-r--r--spec/fixtures/unit/pops/parser/lexer/argumentdefaults.pp2
-rw-r--r--spec/fixtures/unit/pops/parser/lexer/casestatement.pp28
-rw-r--r--spec/fixtures/unit/pops/parser/lexer/classheirarchy.pp6
-rw-r--r--spec/fixtures/unit/pops/parser/lexer/classincludes.pp6
-rw-r--r--spec/fixtures/unit/pops/parser/lexer/classpathtest.pp2
-rw-r--r--spec/fixtures/unit/pops/parser/lexer/collection_override.pp2
-rw-r--r--spec/fixtures/unit/pops/parser/lexer/componentrequire.pp4
-rw-r--r--spec/fixtures/unit/pops/parser/lexer/deepclassheirarchy.pp10
-rw-r--r--spec/fixtures/unit/pops/parser/lexer/defineoverrides.pp4
-rw-r--r--spec/fixtures/unit/pops/parser/lexer/filecreate.pp4
-rw-r--r--spec/fixtures/unit/pops/parser/lexer/ifexpression.pp2
-rw-r--r--spec/fixtures/unit/pops/parser/lexer/implicititeration.pp8
-rw-r--r--spec/fixtures/unit/pops/parser/lexer/multipleinstances.pp6
-rw-r--r--spec/fixtures/unit/pops/parser/lexer/multisubs.pp4
-rw-r--r--spec/fixtures/unit/pops/parser/lexer/namevartest.pp4
-rw-r--r--spec/fixtures/unit/pops/parser/lexer/simpledefaults.pp2
-rw-r--r--spec/fixtures/unit/provider/package/gem/gem-list-single-package4
-rw-r--r--spec/fixtures/unit/type/user/authorized_keys2
-rwxr-xr-xspec/integration/agent/logging_spec.rb4
-rwxr-xr-xspec/integration/application/doc_spec.rb7
-rwxr-xr-xspec/integration/configurer_spec.rb14
-rwxr-xr-xspec/integration/defaults_spec.rb26
-rw-r--r--spec/integration/environments/default_manifest_spec.rb274
-rwxr-xr-xspec/integration/faces/documentation_spec.rb4
-rw-r--r--spec/integration/file_bucket/file_spec.rb21
-rwxr-xr-xspec/integration/indirector/catalog/compiler_spec.rb2
-rwxr-xr-xspec/integration/indirector/catalog/queue_spec.rb2
-rw-r--r--spec/integration/indirector/facts/facter_spec.rb2
-rwxr-xr-xspec/integration/indirector/file_content/file_server_spec.rb4
-rwxr-xr-xspec/integration/node/environment_spec.rb32
-rw-r--r--spec/integration/parser/catalog_spec.rb20
-rw-r--r--spec/integration/parser/class_spec.rb37
-rwxr-xr-xspec/integration/parser/collector_spec.rb309
-rwxr-xr-xspec/integration/parser/compiler_spec.rb774
-rw-r--r--spec/integration/parser/conditionals_spec.rb117
-rw-r--r--spec/integration/parser/future_compiler_spec.rb396
-rw-r--r--spec/integration/parser/node_spec.rb185
-rw-r--r--spec/integration/parser/resource_expressions_spec.rb286
-rwxr-xr-xspec/integration/parser/ruby_manifest_spec.rb4
-rw-r--r--spec/integration/parser/scope_spec.rb245
-rw-r--r--spec/integration/provider/cron/crontab_spec.rb285
-rwxr-xr-xspec/integration/ssl/certificate_authority_spec.rb26
-rwxr-xr-xspec/integration/ssl/certificate_request_spec.rb6
-rwxr-xr-xspec/integration/ssl/certificate_revocation_list_spec.rb2
-rwxr-xr-xspec/integration/ssl/host_spec.rb2
-rwxr-xr-xspec/integration/transaction_spec.rb16
-rwxr-xr-xspec/integration/type/file_spec.rb27
-rw-r--r--spec/integration/type/nagios_spec.rb21
-rw-r--r--spec/integration/type/sshkey_spec.rb22
-rwxr-xr-xspec/integration/type/tidy_spec.rb3
-rw-r--r--spec/integration/type/user_spec.rb36
-rwxr-xr-xspec/integration/util/autoload_spec.rb12
-rwxr-xr-xspec/integration/util/rdoc/parser_spec.rb7
-rw-r--r--spec/integration/util/windows/process_spec.rb12
-rwxr-xr-xspec/integration/util/windows/security_spec.rb71
-rwxr-xr-xspec/integration/util/windows/user_spec.rb84
-rwxr-xr-xspec/integration/util_spec.rb4
-rw-r--r--spec/lib/matchers/resource.rb1
-rw-r--r--spec/lib/puppet_spec/compiler.rb17
-rwxr-xr-xspec/lib/puppet_spec/files.rb10
-rw-r--r--spec/lib/puppet_spec/language.rb74
-rw-r--r--spec/lib/puppet_spec/matchers.rb91
-rw-r--r--spec/lib/puppet_spec/module_tool/stub_source.rb3
-rw-r--r--spec/shared_behaviours/hiera_indirections.rb99
-rw-r--r--spec/shared_behaviours/iterative_functions.rb69
-rwxr-xr-xspec/unit/application/apply_spec.rb6
-rwxr-xr-xspec/unit/application/doc_spec.rb30
-rwxr-xr-xspec/unit/application/master_spec.rb62
-rwxr-xr-xspec/unit/application/resource_spec.rb5
-rwxr-xr-xspec/unit/configurer/downloader_factory_spec.rb96
-rwxr-xr-xspec/unit/configurer/downloader_spec.rb129
-rwxr-xr-xspec/unit/configurer/plugin_handler_spec.rb44
-rwxr-xr-xspec/unit/configurer_spec.rb4
-rw-r--r--spec/unit/defaults_spec.rb30
-rwxr-xr-xspec/unit/face/certificate_request_spec.rb7
-rwxr-xr-xspec/unit/face/certificate_revocation_list_spec.rb7
-rwxr-xr-xspec/unit/face/config_spec.rb3
-rwxr-xr-xspec/unit/face/key_spec.rb7
-rw-r--r--spec/unit/face/module/build_spec.rb4
-rw-r--r--spec/unit/face/module/install_spec.rb16
-rw-r--r--spec/unit/face/parser_spec.rb104
-rwxr-xr-xspec/unit/face/report_spec.rb7
-rwxr-xr-xspec/unit/face/resource_spec.rb7
-rwxr-xr-xspec/unit/face/resource_type_spec.rb7
-rwxr-xr-xspec/unit/file_bucket/file_spec.rb4
-rw-r--r--spec/unit/file_system/tempfile_spec.rb48
-rw-r--r--spec/unit/file_system/uniquefile_spec.rb184
-rw-r--r--spec/unit/forge/errors_spec.rb10
-rw-r--r--spec/unit/forge/module_release_spec.rb267
-rw-r--r--spec/unit/forge/repository_spec.rb112
-rw-r--r--spec/unit/forge_spec.rb42
-rw-r--r--spec/unit/functions/assert_type_spec.rb25
-rw-r--r--spec/unit/functions/each_spec.rb (renamed from spec/unit/parser/methods/each_spec.rb)22
-rw-r--r--spec/unit/functions/epp_spec.rb (renamed from spec/unit/parser/functions/epp_spec.rb)70
-rw-r--r--spec/unit/functions/filter_spec.rb (renamed from spec/unit/parser/methods/filter_spec.rb)72
-rw-r--r--spec/unit/functions/inline_epp_spec.rb (renamed from spec/unit/parser/functions/inline_epp_spec.rb)21
-rw-r--r--spec/unit/functions/map_spec.rb169
-rw-r--r--spec/unit/functions/match_spec.rb57
-rw-r--r--spec/unit/functions/reduce_spec.rb (renamed from spec/unit/parser/methods/reduce_spec.rb)28
-rw-r--r--spec/unit/functions/slice_spec.rb (renamed from spec/unit/parser/methods/slice_spec.rb)55
-rw-r--r--spec/unit/functions/with_spec.rb35
-rw-r--r--spec/unit/functions4_spec.rb15
-rwxr-xr-xspec/unit/indirector/catalog/compiler_spec.rb2
-rw-r--r--spec/unit/indirector/catalog/static_compiler_spec.rb11
-rw-r--r--spec/unit/indirector/data_binding/hiera_spec.rb97
-rwxr-xr-xspec/unit/indirector/facts/facter_spec.rb171
-rw-r--r--spec/unit/indirector/hiera_spec.rb17
-rwxr-xr-xspec/unit/indirector/request_spec.rb6
-rwxr-xr-xspec/unit/indirector/resource/ral_spec.rb5
-rwxr-xr-xspec/unit/indirector/resource_type/parser_spec.rb29
-rwxr-xr-xspec/unit/indirector/rest_spec.rb42
-rwxr-xr-xspec/unit/interface/face_collection_spec.rb4
-rw-r--r--spec/unit/module_tool/applications/builder_spec.rb378
-rw-r--r--spec/unit/module_tool/applications/uninstaller_spec.rb22
-rw-r--r--spec/unit/module_tool/applications/unpacker_spec.rb40
-rw-r--r--spec/unit/module_tool/applications/upgrader_spec.rb22
-rw-r--r--spec/unit/module_tool/installed_modules_spec.rb49
-rw-r--r--spec/unit/module_tool/metadata_spec.rb76
-rw-r--r--spec/unit/module_tool/tar/mini_spec.rb3
-rwxr-xr-xspec/unit/network/authentication_spec.rb4
-rw-r--r--spec/unit/network/http/api/v2/environments_spec.rb27
-rwxr-xr-x[-rw-r--r--]spec/unit/network/http/connection_spec.rb219
-rwxr-xr-xspec/unit/network/http/factory_spec.rb82
-rwxr-xr-xspec/unit/network/http/handler_spec.rb36
-rwxr-xr-xspec/unit/network/http/nocache_pool_spec.rb43
-rwxr-xr-xspec/unit/network/http/pool_spec.rb269
-rwxr-xr-xspec/unit/network/http/rack/rest_spec.rb2
-rwxr-xr-xspec/unit/network/http/session_spec.rb43
-rwxr-xr-xspec/unit/network/http/site_spec.rb90
-rwxr-xr-xspec/unit/network/http/webrick_spec.rb2
-rwxr-xr-xspec/unit/network/http_pool_spec.rb15
-rwxr-xr-xspec/unit/network/http_spec.rb10
-rwxr-xr-xspec/unit/node/environment_spec.rb90
-rwxr-xr-xspec/unit/node_spec.rb8
-rwxr-xr-xspec/unit/parser/compiler_spec.rb11
-rw-r--r--spec/unit/parser/eparser_adapter_spec.rb407
-rwxr-xr-xspec/unit/parser/files_spec.rb19
-rw-r--r--spec/unit/parser/functions/contain_spec.rb51
-rwxr-xr-xspec/unit/parser/functions/create_resources_spec.rb9
-rwxr-xr-xspec/unit/parser/functions/digest_spec.rb31
-rwxr-xr-xspec/unit/parser/functions/file_spec.rb53
-rwxr-xr-xspec/unit/parser/functions/include_spec.rb16
-rwxr-xr-xspec/unit/parser/functions/realize_spec.rb78
-rwxr-xr-xspec/unit/parser/functions/require_spec.rb24
-rwxr-xr-xspec/unit/parser/functions/search_spec.rb5
-rw-r--r--spec/unit/parser/functions/shared.rb82
-rwxr-xr-xspec/unit/parser/functions_spec.rb7
-rwxr-xr-xspec/unit/parser/lexer_spec.rb11
-rw-r--r--spec/unit/parser/methods/map_spec.rb184
-rw-r--r--spec/unit/parser/methods/shared.rb45
-rwxr-xr-xspec/unit/parser/type_loader_spec.rb1
-rw-r--r--spec/unit/pops/benchmark_spec.rb2
-rw-r--r--spec/unit/pops/binder/bindings_composer_spec.rb46
-rw-r--r--spec/unit/pops/binder/injector_spec.rb14
-rw-r--r--spec/unit/pops/evaluator/access_ops_spec.rb6
-rw-r--r--spec/unit/pops/evaluator/comparison_ops_spec.rb11
-rw-r--r--spec/unit/pops/evaluator/evaluating_parser_spec.rb280
-rw-r--r--spec/unit/pops/evaluator/logical_ops_spec.rb4
-rw-r--r--spec/unit/pops/evaluator/variables_spec.rb105
-rw-r--r--spec/unit/pops/issues_spec.rb170
-rw-r--r--spec/unit/pops/loaders/dependency_loader_spec.rb17
-rw-r--r--spec/unit/pops/loaders/loader_paths_spec.rb19
-rw-r--r--spec/unit/pops/loaders/loaders_spec.rb42
-rw-r--r--spec/unit/pops/loaders/module_loaders_spec.rb29
-rw-r--r--spec/unit/pops/loaders/static_loader_spec.rb6
-rw-r--r--spec/unit/pops/parser/epp_parser_spec.rb47
-rw-r--r--spec/unit/pops/parser/evaluating_parser_spec.rb1
-rw-r--r--spec/unit/pops/parser/lexer2_spec.rb25
-rwxr-xr-xspec/unit/pops/parser/lexer_spec.rb840
-rw-r--r--spec/unit/pops/parser/parse_basic_expressions_spec.rb5
-rw-r--r--spec/unit/pops/parser/parse_calls_spec.rb9
-rw-r--r--spec/unit/pops/parser/parse_conditionals_spec.rb17
-rw-r--r--spec/unit/pops/parser/parse_containers_spec.rb69
-rw-r--r--spec/unit/pops/parser/parse_resource_spec.rb228
-rw-r--r--spec/unit/pops/parser/parser_spec.rb16
-rw-r--r--spec/unit/pops/parser/parsing_typed_parameters_spec.rb72
-rw-r--r--spec/unit/pops/transformer/transform_calls_spec.rb2
-rw-r--r--spec/unit/pops/transformer/transform_resource_spec.rb185
-rw-r--r--spec/unit/pops/types/type_calculator_spec.rb311
-rw-r--r--spec/unit/pops/types/type_factory_spec.rb11
-rw-r--r--spec/unit/pops/types/type_parser_spec.rb31
-rw-r--r--spec/unit/pops/validator/validator_spec.rb170
-rwxr-xr-xspec/unit/provider/exec/posix_spec.rb36
-rwxr-xr-xspec/unit/provider/exec/shell_spec.rb4
-rwxr-xr-xspec/unit/provider/file/windows_spec.rb14
-rw-r--r--spec/unit/provider/group/windows_adsi_spec.rb34
-rwxr-xr-xspec/unit/provider/package/gem_spec.rb10
-rwxr-xr-xspec/unit/provider/package/openbsd_spec.rb75
-rwxr-xr-xspec/unit/provider/package/pacman_spec.rb161
-rwxr-xr-xspec/unit/provider/package/windows/package_spec.rb27
-rwxr-xr-xspec/unit/provider/package/yum_spec.rb1
-rwxr-xr-xspec/unit/provider/parsedfile_spec.rb2
-rw-r--r--spec/unit/provider/scheduled_task/win32_taskscheduler_spec.rb18
-rwxr-xr-x[-rw-r--r--]spec/unit/provider/service/openbsd_spec.rb28
-rwxr-xr-xspec/unit/provider/service/upstart_spec.rb13
-rwxr-xr-xspec/unit/provider/ssh_authorized_key/parsed_spec.rb6
-rwxr-xr-xspec/unit/provider/user/user_role_add_spec.rb24
-rwxr-xr-xspec/unit/provider/user/windows_adsi_spec.rb34
-rwxr-xr-xspec/unit/reports/store_spec.rb16
-rwxr-xr-xspec/unit/resource/catalog_spec.rb5
-rwxr-xr-xspec/unit/resource_spec.rb4
-rw-r--r--spec/unit/settings/array_setting_spec.rb39
-rw-r--r--spec/unit/settings/autosign_setting_spec.rb4
-rw-r--r--spec/unit/settings/environment_conf_spec.rb87
-rwxr-xr-xspec/unit/settings/file_setting_spec.rb9
-rwxr-xr-xspec/unit/settings/priority_setting_spec.rb8
-rwxr-xr-xspec/unit/settings_spec.rb189
-rwxr-xr-xspec/unit/ssl/certificate_authority_spec.rb27
-rwxr-xr-xspec/unit/ssl/inventory_spec.rb13
-rw-r--r--spec/unit/ssl/validator_spec.rb1
-rwxr-xr-xspec/unit/transaction/resource_harness_spec.rb64
-rwxr-xr-xspec/unit/transaction_spec.rb145
-rwxr-xr-xspec/unit/type/cron_spec.rb6
-rwxr-xr-xspec/unit/type/exec_spec.rb9
-rwxr-xr-xspec/unit/type/file/content_spec.rb125
-rwxr-xr-xspec/unit/type/file/mode_spec.rb27
-rwxr-xr-xspec/unit/type/file/source_spec.rb30
-rwxr-xr-xspec/unit/type/file_spec.rb6
-rwxr-xr-xspec/unit/type/nagios_spec.rb15
-rwxr-xr-xspec/unit/type/resources_spec.rb94
-rwxr-xr-xspec/unit/type/user_spec.rb21
-rwxr-xr-x[-rw-r--r--]spec/unit/type/yumrepo_spec.rb136
-rwxr-xr-xspec/unit/type/zone_spec.rb45
-rwxr-xr-xspec/unit/type_spec.rb20
-rwxr-xr-xspec/unit/util/colors_spec.rb22
-rwxr-xr-xspec/unit/util/command_line_spec.rb18
-rwxr-xr-xspec/unit/util/execution_spec.rb77
-rwxr-xr-xspec/unit/util/feature_spec.rb12
-rw-r--r--spec/unit/util/http_proxy_spec.rb44
-rwxr-xr-xspec/unit/util/log/destinations_spec.rb46
-rwxr-xr-xspec/unit/util/logging_spec.rb44
-rw-r--r--spec/unit/util/pidlock_spec.rb38
-rw-r--r--spec/unit/util/profiler/aggregate_spec.rb59
-rw-r--r--spec/unit/util/profiler/around_profiler_spec.rb61
-rw-r--r--spec/unit/util/profiler/logging_spec.rb47
-rw-r--r--spec/unit/util/profiler/none_spec.rb12
-rw-r--r--spec/unit/util/profiler/wall_clock_spec.rb2
-rw-r--r--spec/unit/util/profiler_spec.rb55
-rwxr-xr-xspec/unit/util/queue_spec.rb1
-rwxr-xr-xspec/unit/util/rdoc/parser_spec.rb20
-rwxr-xr-xspec/unit/util/tagging_spec.rb33
-rw-r--r--spec/unit/util/windows/access_control_entry_spec.rb2
-rwxr-xr-xspec/unit/util/windows/adsi_spec.rb (renamed from spec/unit/util/adsi_spec.rb)219
-rw-r--r--spec/unit/util/windows/api_types_spec.rb28
-rwxr-xr-xspec/unit/util/windows/registry_spec.rb13
-rwxr-xr-xspec/unit/util/windows/sid_spec.rb9
-rw-r--r--spec/unit/util/windows/string_spec.rb4
-rwxr-xr-xspec/unit/util/zaml_spec.rb6
-rw-r--r--tasks/benchmark.rake45
-rw-r--r--tasks/parser.rake18
-rw-r--r--tasks/yard.rake2
758 files changed, 40720 insertions, 13060 deletions
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 3c6dc52b5..e7ccf7747 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -22,9 +22,8 @@ top of things.
* This is usually the master branch.
* Only target release branches if you are certain your fix must be on that
branch.
- * To quickly create a topic branch based on master; `git branch
- fix/master/my_contribution master` then checkout the new branch with `git
- checkout fix/master/my_contribution`. Please avoid working directly on the
+ * To quickly create a topic branch based on master; `git checkout -b
+ fix/master/my_contribution master`. Please avoid working directly on the
`master` branch.
* Make commits of logical units.
* Check for unnecessary whitespace with `git diff --check` before committing.
diff --git a/Gemfile b/Gemfile
index 671d333fa..e2f137121 100644
--- a/Gemfile
+++ b/Gemfile
@@ -27,13 +27,9 @@ gem "puppet", :path => File.dirname(__FILE__), :require => false
gem "facter", *location_for(ENV['FACTER_LOCATION'] || ['> 1.6', '< 3'])
gem "hiera", *location_for(ENV['HIERA_LOCATION'] || '~> 1.0')
gem "rake", "10.1.1", :require => false
-gem "rgen", "0.6.5", :require => false
group(:development, :test) do
-
- # Jenkins workers may be using RSpec 2.9, so RSpec 2.11 syntax
- # (like `expect(value).to eq matcher`) should be avoided.
- gem "rspec", "~> 2.11.0", :require => false
+ gem "rspec", "~> 2.14.0", :require => false
# Mocha is not compatible across minor version changes; because of this only
# versions matching ~> 0.10.5 are supported. All other versions are unsupported
@@ -49,11 +45,13 @@ group(:development, :test) do
end
group(:development) do
- case RUBY_VERSION
- when /^1.8/
- gem 'ruby-prof', "~> 0.13.1", :require => false
- else
- gem 'ruby-prof', :require => false
+ if RUBY_PLATFORM != 'java'
+ case RUBY_VERSION
+ when /^1.8/
+ gem 'ruby-prof', "~> 0.13.1", :require => false
+ else
+ gem 'ruby-prof', :require => false
+ end
end
end
@@ -63,6 +61,9 @@ group(:extra) do
gem "couchrest", '~> 1.0', :require => false
gem "net-ssh", '~> 2.1', :require => false
gem "puppetlabs_spec_helper", :require => false
+ # rest-client is used only by couchrest, so when
+ # that dependency goes away, this one can also
+ gem "rest-client", '1.6.7', :require => false
gem "stomp", :require => false
gem "tzinfo", :require => false
case RUBY_PLATFORM
@@ -78,7 +79,10 @@ end
require 'yaml'
data = YAML.load_file(File.join(File.dirname(__FILE__), 'ext', 'project_data.yaml'))
bundle_platforms = data['bundle_platforms']
+x64_platform = Gem::Platform.local.cpu == 'x64'
data['gem_platform_dependencies'].each_pair do |gem_platform, info|
+ next if gem_platform == 'x86-mingw32' && x64_platform
+ next if gem_platform == 'x64-mingw32' && !x64_platform
if bundle_deps = info['gem_runtime_dependencies']
bundle_platform = bundle_platforms[gem_platform] or raise "Missing bundle_platform"
platform(bundle_platform.intern) do
diff --git a/README.md b/README.md
index 94f1cea29..a0747d1df 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@ Puppet
======
[![Build Status](https://travis-ci.org/puppetlabs/puppet.png?branch=master)](https://travis-ci.org/puppetlabs/puppet)
-[![Inline docs](http://inch-pages.github.io/github/puppetlabs/puppet.png)](http://inch-pages.github.io/github/puppetlabs/puppet)
+[![Inline docs](http://inch-ci.org/github/puppetlabs/puppet.png)](http://inch-ci.org/github/puppetlabs/puppet)
Puppet, an automated administrative engine for your Linux, Unix, and Windows systems, performs
administrative tasks (such as adding users, installing packages, and updating server
diff --git a/bin/puppet b/bin/puppet
index c03070291..c80174f60 100755
--- a/bin/puppet
+++ b/bin/puppet
@@ -1,4 +1,8 @@
#!/usr/bin/env ruby
+# For security reasons, ensure that '.' is not on the load path
+# This is primarily for 1.8.7 since 1.9.2+ doesn't put '.' on the load path
+$LOAD_PATH.delete '.'
+
require 'puppet/util/command_line'
Puppet::Util::CommandLine.new.execute
diff --git a/ext/build_defaults.yaml b/ext/build_defaults.yaml
index 376b09733..d62e70555 100644
--- a/ext/build_defaults.yaml
+++ b/ext/build_defaults.yaml
@@ -2,7 +2,7 @@
packaging_url: 'git://github.com/puppetlabs/packaging.git --branch=master'
packaging_repo: 'packaging'
default_cow: 'base-squeeze-i386.cow'
-cows: 'base-lucid-i386.cow base-precise-i386.cow base-quantal-i386.cow base-squeeze-i386.cow base-stable-i386.cow base-testing-i386.cow base-trusty-i386.cow base-wheezy-i386.cow base-saucy-i386.cow'
+cows: 'base-lucid-i386.cow base-precise-i386.cow base-squeeze-i386.cow base-stable-i386.cow base-testing-i386.cow base-trusty-i386.cow base-wheezy-i386.cow'
pbuild_conf: '/etc/pbuilderrc'
packager: 'puppetlabs'
gpg_name: 'info@puppetlabs.com'
@@ -14,6 +14,21 @@ yum_host: 'yum.puppetlabs.com'
yum_repo_path: '/opt/repository/yum/'
build_gem: TRUE
build_dmg: TRUE
+build_msi:
+ puppet_for_the_win:
+ ref: 'c61dda253b09bd855488f7b6dd7332fdf84d3039'
+ repo: 'git://github.com/puppetlabs/puppet_for_the_win.git'
+ facter:
+ ref: 'refs/tags/2.2.0'
+ repo: 'git://github.com/puppetlabs/facter.git'
+ hiera:
+ ref: 'refs/tags/1.3.4'
+ repo: 'git://github.com/puppetlabs/hiera.git'
+ sys:
+ ref:
+ x86: '890ad47c3b70de4e4956d2699b54d481d5a1b72e'
+ x64: '2820779a3f4281534a6792b0ab20b2cf213647dc'
+ repo: 'git://github.com/puppetlabs/puppet-win32-ruby.git'
apt_host: 'apt.puppetlabs.com'
apt_repo_url: 'http://apt.puppetlabs.com'
apt_repo_path: '/opt/repository/incoming'
diff --git a/ext/debian/changelog b/ext/debian/changelog
index 048113605..de2ac6b06 100644
--- a/ext/debian/changelog
+++ b/ext/debian/changelog
@@ -1,8 +1,8 @@
-puppet (3.6.1-1puppetlabs1) hardy lucid natty oneiric unstable sid squeeze wheezy precise; urgency=low
+puppet (3.7.0-1puppetlabs1) hardy lucid natty oneiric unstable sid squeeze wheezy precise; urgency=low
- * Update to version 3.6.1-1puppetlabs1
+ * Update to version 3.7.0-1puppetlabs1
- -- Puppet Labs Release <info@puppetlabs.com> Thu, 22 May 2014 11:48:08 -0700
+ -- Puppet Labs Release <info@puppetlabs.com> Wed, 03 Sep 2014 15:22:51 -0700
puppet (3.2.3-0.1rc0puppetlabs1) lucid unstable sid squeeze wheezy precise quantal raring; urgency=low
diff --git a/ext/debian/control b/ext/debian/control
index 6f0e8467b..02c1f8f42 100644
--- a/ext/debian/control
+++ b/ext/debian/control
@@ -11,12 +11,12 @@ Homepage: http://projects.puppetlabs.com/projects/puppet
Package: puppet-common
Architecture: all
-Depends: ${misc:Depends}, ruby | ruby-interpreter, libopenssl-ruby | libopenssl-ruby1.9.1 | libruby (>= 1:1.9.3.4), ruby-shadow | libshadow-ruby1.8, libaugeas-ruby | libaugeas-ruby1.9.1 | libaugeas-ruby1.8, adduser, lsb-base, sysv-rc (>= 2.86) | file-rc, hiera (>= 1.0.0), facter (>= 1.7.0), ruby-rgen (>= 0.6.5), libjson-ruby | ruby-json
+Depends: ${misc:Depends}, ruby | ruby-interpreter, libopenssl-ruby | libopenssl-ruby1.9.1 | libruby (>= 1:1.9.3.4), ruby-shadow | libshadow-ruby1.8, libaugeas-ruby | libaugeas-ruby1.9.1 | libaugeas-ruby1.8, adduser, lsb-base, sysv-rc (>= 2.86) | file-rc, hiera (>= 1.0.0), facter (>= 1.7.0), libjson-ruby | ruby-json
Recommends: lsb-release, debconf-utils
Suggests: ruby-selinux | libselinux-ruby1.8, librrd-ruby1.9.1 | librrd-ruby1.8
Breaks: puppet (<< 2.6.0~rc2-1), puppetmaster (<< 0.25.4-1)
Provides: hiera-puppet
-Conflicts: hiera-puppet
+Conflicts: hiera-puppet, puppet (<< 3.3.0-1puppetlabs1)
Replaces: hiera-puppet
Description: Centralized configuration management
Puppet lets you centrally manage every important aspect of your system
@@ -39,6 +39,7 @@ Architecture: all
Depends: ${misc:Depends}, puppet-common (= ${binary:Version}), ruby | ruby-interpreter
Recommends: rdoc
Suggests: puppet-el, vim-puppet
+Conflicts: puppet-common (<< 3.3.0-1puppetlabs1)
Description: Centralized configuration management - agent startup and compatibility scripts
This package contains the startup script and compatbility scripts for the
puppet agent, which is the process responsible for configuring the local node.
diff --git a/ext/debian/puppet-common.dirs b/ext/debian/puppet-common.dirs
index 4f1bbfa8d..7b920412c 100644
--- a/ext/debian/puppet-common.dirs
+++ b/ext/debian/puppet-common.dirs
@@ -10,3 +10,4 @@ usr/lib/ruby/vendor_ruby
usr/share/puppet/ext
var/lib/puppet
var/log/puppet
+var/run/puppet
diff --git a/ext/debian/puppet-common.postinst b/ext/debian/puppet-common.postinst
index b9021a7bb..f90524c48 100644
--- a/ext/debian/puppet-common.postinst
+++ b/ext/debian/puppet-common.postinst
@@ -1,4 +1,4 @@
-#!/bin/sh
+#!/bin/bash
set -e
@@ -13,18 +13,18 @@ if [ "$1" = "configure" ]; then
fi
# Set correct permissions and ownership for puppet directories
- if ! dpkg-statoverride --list /var/log/puppet >/dev/null 2>&1; then
- dpkg-statoverride --update --add puppet puppet 0750 /var/log/puppet
- fi
-
- if ! dpkg-statoverride --list /var/lib/puppet >/dev/null 2>&1; then
- dpkg-statoverride --update --add puppet puppet 0750 /var/lib/puppet
- fi
+ for dir in /var/{run,lib,log}/puppet; do
+ if ! dpkg-statoverride --list "$dir" >/dev/null 2>&1; then
+ dpkg-statoverride --update --add puppet puppet 0750 "$dir"
+ fi
+ done
# Create folders common to "puppet" and "puppetmaster", which need
# to be owned by the "puppet" user
install --owner puppet --group puppet --directory \
/var/lib/puppet/state
+ install --owner puppet --group puppet --directory \
+ /var/lib/puppet/reports
# Handle
if [ -d /etc/puppet/ssl ] && [ ! -e /var/lib/puppet/ssl ] && grep -q 'ssldir=/var/lib/puppet/ssl' /etc/puppet/puppet.conf; then
diff --git a/ext/debian/puppet-common.postrm b/ext/debian/puppet-common.postrm
index 841995675..fa2fff52d 100644
--- a/ext/debian/puppet-common.postrm
+++ b/ext/debian/puppet-common.postrm
@@ -6,9 +6,10 @@ case "$1" in
rm -f /etc/puppet/puppetd.conf
# Remove puppet state directory created by the postinst script.
- # This directory can be removed without causing harm
+ # This directory can be removed without causing harm
# according to upstream documentation.
rm -rf /var/lib/puppet/state
+ rm -rf /var/lib/puppet/reports
if [ -d /var/lib/puppet ]; then
rmdir --ignore-fail-on-non-empty /var/lib/puppet
fi
diff --git a/ext/debian/puppetmaster-passenger.postinst b/ext/debian/puppetmaster-passenger.postinst
index 2c9f20c3f..02c71c349 100644
--- a/ext/debian/puppetmaster-passenger.postinst
+++ b/ext/debian/puppetmaster-passenger.postinst
@@ -3,6 +3,7 @@
set -e
sitename="puppetmaster"
+apache2_version="$(dpkg-query --showformat='${Version}\n' --show apache2)"
# The debian provided a2* utils in Apache 2.4 uses "site name" as
# argument, while the version in Apache 2.2 uses "file name".
@@ -14,7 +15,6 @@ sitename="puppetmaster"
# This will end in tears…
# Can be removed when we only support apache >= 2.4
apache2_puppetmaster_sitename() {
- apache2_version="$(dpkg-query --showformat='${Version}\n' --show apache2)"
if dpkg --compare-versions "$apache2_version" gt "2.4~"; then
echo "${sitename}.conf"
else
@@ -49,6 +49,91 @@ update_vhost_for_passenger4() {
fi
}
+# In Apache 2.2, if either the SSLCARevocationFile or SSLCARevocationPath
+# directives were specified then the specified file(s) would be checked when
+# establishing an SSL connection. Apache 2.4+ the SSLCARevocationCheck directive
+# was added to control how CRLs were checked when verifying a connection and had
+# a default value of none. This means that Apache defaults to ignoring CRLs even
+# if paths are specified to CRL files.
+#
+# This function automatically uncomments the SSLCARevocationCheck directive when
+# the currently installed version of Apache is 2.4.
+update_vhost_for_apache24() {
+ if dpkg --compare-versions "$apache2_version" gt "2.4~"; then
+ sed -r -i \
+ -e "/# SSLCARevocationCheck/s/# //" \
+ $tempfile
+ fi
+}
+
+# Update an existing vhost definition with the SSLCARevocationCheck directive
+# on Apache 2.4+. This scans an existing vhost file for the SSLCARevocationCheck
+# directive and adds it to the file after the SSLCARevocationFile directive.
+#
+# See https://tickets.puppetlabs.com/browse/PUP-2533 for more information.
+update_vhost_for_apache24_upgrade() {
+ APACHE2_SITE_FILE="/etc/apache2/sites-available/$(apache2_puppetmaster_sitename)"
+
+ if dpkg --compare-versions "$apache2_version" gt "2.4~"; then
+ if ! grep -q "^[[:space:]]*SSLCARevocationCheck" $APACHE2_SITE_FILE ; then
+ tempfile=$(mktemp)
+ sed -r \
+ -e "/SSLCARevocationFile/a\\ SSLCARevocationCheck chain" \
+ $APACHE2_SITE_FILE > $tempfile
+ mv $tempfile $APACHE2_SITE_FILE
+ fi
+ fi
+}
+
+
+create_initial_puppetmaster_vhost() {
+ # Check that puppet master --configprint works properly
+ # If it doesn't the following steps to update the vhost will produce a very unhelpful and broken vhost
+ if [ $(puppet master --configprint all 2>&1 | grep "Could not parse" | wc -l) != "0" ]; then
+ echo "Puppet config print not working properly, exiting"
+ exit 1
+ fi
+
+ # Initialize puppetmaster CA and generate the master certificate
+ # only if the host doesn't already have any puppet ssl certificate.
+ # The ssl key and cert need to be available (eg generated) before
+ # apache2 is configured and started since apache2 ssl configuration
+ # uses the puppetmaster ssl files.
+ if [ ! -e "$(puppet master --configprint hostcert)" ]; then
+ puppet cert generate $(puppet master --configprint certname)
+ fi
+
+ # Setup apache2 configuration files
+ APACHE2_SITE_FILE="/etc/apache2/sites-available/$(apache2_puppetmaster_sitename)"
+ if [ ! -e "${APACHE2_SITE_FILE}" ]; then
+ tempfile=$(mktemp)
+ sed -r \
+ -e "s|(SSLCertificateFile\s+).+$|\1$(puppet master --configprint hostcert)|" \
+ -e "s|(SSLCertificateKeyFile\s+).+$|\1$(puppet master --configprint hostprivkey)|" \
+ -e "s|(SSLCACertificateFile\s+).+$|\1$(puppet master --configprint localcacert)|" \
+ -e "s|(SSLCertificateChainFile\s+).+$|\1$(puppet master --configprint localcacert)|" \
+ -e "s|(SSLCARevocationFile\s+).+$|\1$(puppet master --configprint cacrl)|" \
+ -e "s|DocumentRoot /etc/puppet/rack/public|DocumentRoot /usr/share/puppet/rack/puppetmasterd/public|" \
+ -e "s|<Directory /etc/puppet/rack/>|<Directory /usr/share/puppet/rack/puppetmasterd/>|" \
+ /usr/share/puppetmaster-passenger/apache2.site.conf.tmpl > $tempfile
+ update_vhost_for_passenger4
+ update_vhost_for_apache24
+ mv $tempfile "${APACHE2_SITE_FILE}"
+ fi
+
+ # Enable needed modules
+ a2enmod ssl
+ a2enmod headers
+ a2ensite ${sitename}
+ restart_apache2
+}
+
+update_existing_puppetmaster_vhost() {
+ if dpkg --compare-versions "${1}" lt "3.6.2~"; then
+ update_vhost_for_apache24_upgrade
+ fi
+}
+
if [ "$1" = "configure" ]; then
# Change the owner of the rack config.ru to be the puppet user
@@ -57,47 +142,12 @@ if [ "$1" = "configure" ]; then
then
dpkg-statoverride --update --add puppet puppet 0644 /usr/share/puppet/rack/puppetmasterd/config.ru
fi
- # Setup passenger configuration
- if [ "$2" = "" ]; then
- # Check that puppet master --configprint works properly
- # If it doesn't the following steps to update the vhost will produce a very unhelpful and broken vhost
- if [ $(puppet master --configprint all 2>&1 | grep "Could not parse" | wc -l) != "0" ]; then
- echo "Puppet config print not working properly, exiting"
- exit 1
- fi
-
- # Initialize puppetmaster CA and generate the master certificate
- # only if the host doesn't already have any puppet ssl certificate.
- # The ssl key and cert need to be available (eg generated) before
- # apache2 is configured and started since apache2 ssl configuration
- # uses the puppetmaster ssl files.
- if [ ! -e "$(puppet master --configprint hostcert)" ]; then
- puppet cert generate $(puppet master --configprint certname)
- fi
-
- # Setup apache2 configuration files
- APACHE2_SITE_FILE="/etc/apache2/sites-available/$(apache2_puppetmaster_sitename)"
- if [ ! -e "${APACHE2_SITE_FILE}" ]; then
- tempfile=$(mktemp)
- sed -r \
- -e "s|(SSLCertificateFile\s+).+$|\1$(puppet master --configprint hostcert)|" \
- -e "s|(SSLCertificateKeyFile\s+).+$|\1$(puppet master --configprint hostprivkey)|" \
- -e "s|(SSLCACertificateFile\s+).+$|\1$(puppet master --configprint localcacert)|" \
- -e "s|(SSLCertificateChainFile\s+).+$|\1$(puppet master --configprint localcacert)|" \
- -e "s|(SSLCARevocationFile\s+).+$|\1$(puppet master --configprint cacrl)|" \
- -e "s|DocumentRoot /etc/puppet/rack/public|DocumentRoot /usr/share/puppet/rack/puppetmasterd/public|" \
- -e "s|<Directory /etc/puppet/rack/>|<Directory /usr/share/puppet/rack/puppetmasterd/>|" \
- /usr/share/puppetmaster-passenger/apache2.site.conf.tmpl > $tempfile
- update_vhost_for_passenger4
- mv $tempfile "${APACHE2_SITE_FILE}"
- fi
-
- # Enable needed modules
- a2enmod ssl
- a2enmod headers
- a2ensite ${sitename}
- restart_apache2
+ # Setup puppetmaster passenger vhost
+ if [ "$2" = "" ]; then
+ create_initial_puppetmaster_vhost
+ else
+ update_existing_puppetmaster_vhost $2
fi
# Fix CRL file on upgrade to use the CA crl file instead of the host crl.
diff --git a/ext/ips/puppet.p5m b/ext/ips/puppet.p5m
index 422f1116d..7645152d6 100644
--- a/ext/ips/puppet.p5m
+++ b/ext/ips/puppet.p5m
@@ -1,6 +1,6 @@
-set name=pkg.fmri value=pkg://puppetlabs.com/system/management/@3.6.1,13.1.0-0
+set name=pkg.fmri value=pkg://puppetlabs.com/system/management/@3.7.0,13.3.0-0
set name=pkg.summary value="Puppet, an automated configuration management tool"
-set name=pkg.human-version value="3.6.1"
+set name=pkg.human-version value="3.7.0"
set name=pkg.description value="Puppet, an automated configuration management tool"
set name=info.classification value="org.opensolaris.category.2008:System/Administration and Configuration"
set name=org.opensolaris.consolidation value="puppet"
diff --git a/ext/project_data.yaml b/ext/project_data.yaml
index ee3fc71f5..292d8de26 100644
--- a/ext/project_data.yaml
+++ b/ext/project_data.yaml
@@ -17,7 +17,6 @@ gem_forge_project: 'puppet'
gem_runtime_dependencies:
facter: ['> 1.6', '< 3']
hiera: '~> 1.0'
- rgen: '~> 0.6.5'
json_pure:
gem_rdoc_options:
- --title
@@ -29,18 +28,23 @@ gem_platform_dependencies:
x86-mingw32:
gem_runtime_dependencies:
# Pinning versions that require native extensions
- ffi: '1.9.0'
- sys-admin: '1.5.6'
- win32-api: '1.4.8'
- win32-dir: '~> 0.4.3'
- win32-eventlog: '~> 0.5.3'
- win32-process: '~> 0.6.5'
- win32-security: '~> 0.1.4'
- win32-service: '0.7.2'
- win32-taskscheduler: '~> 0.2.2'
+ ffi: '1.9.3'
+ win32-dir: '~> 0.4.9'
+ win32-eventlog: '~> 0.6.1'
+ win32-process: '~> 0.7.4'
+ win32-security: '~> 0.2.5'
+ win32-service: '~> 0.8.4'
win32console: '1.3.2'
- windows-api: '~> 0.4.2'
- windows-pr: '~> 1.2.2'
+ minitar: '~> 0.5.4'
+ x64-mingw32:
+ gem_runtime_dependencies:
+ ffi: '1.9.3'
+ win32-dir: '~> 0.4.9'
+ win32-eventlog: '~> 0.6.1'
+ win32-process: '~> 0.7.4'
+ win32-security: '~> 0.2.5'
+ win32-service: '~> 0.8.4'
minitar: '~> 0.5.4'
bundle_platforms:
x86-mingw32: mingw
+ x64-mingw32: x64_mingw
diff --git a/ext/rack/example-passenger-vhost.conf b/ext/rack/example-passenger-vhost.conf
index c14f3cd98..e8c2102e8 100644
--- a/ext/rack/example-passenger-vhost.conf
+++ b/ext/rack/example-passenger-vhost.conf
@@ -18,8 +18,8 @@ Listen 8140
<VirtualHost *:8140>
SSLEngine on
- SSLProtocol ALL -SSLv2
- SSLCipherSuite ALL:!aNULL:!eNULL:!DES:!3DES:!IDEA:!SEED:!DSS:!PSK:!RC4:!MD5:+HIGH:+MEDIUM:!LOW:!SSLv2:!EXP
+ SSLProtocol ALL -SSLv2 -SSLv3
+ SSLCipherSuite EDH+CAMELLIA:EDH+aRSA:EECDH+aRSA+AESGCM:EECDH+aRSA+SHA384:EECDH+aRSA+SHA256:EECDH:+CAMELLIA256:+AES256:+CAMELLIA128:+AES128:+SSLv3:!aNULL:!eNULL:!LOW:!3DES:!MD5:!EXP:!PSK:!DSS:!RC4:!SEED:!IDEA:!ECDSA:kEDH:CAMELLIA256-SHA:AES256-SHA:CAMELLIA128-SHA:AES128-SHA
SSLHonorCipherOrder on
SSLCertificateFile /etc/puppet/ssl/certs/squigley.namespace.at.pem
@@ -29,6 +29,10 @@ Listen 8140
# If Apache complains about invalid signatures on the CRL, you can try disabling
# CRL checking by commenting the next line, but this is not recommended.
SSLCARevocationFile /etc/puppet/ssl/ca/ca_crl.pem
+ # Apache 2.4 introduces the SSLCARevocationCheck directive and sets it to none
+ # which effectively disables CRL checking; if you are using Apache 2.4+ you must
+ # specify 'SSLCARevocationCheck chain' to actually use the CRL.
+ # SSLCARevocationCheck chain
SSLVerifyClient optional
SSLVerifyDepth 1
# The `ExportCertData` option is needed for agent certificate expiration warnings
diff --git a/ext/redhat/puppet.spec b/ext/redhat/puppet.spec
index 399c5389e..ac5222659 100644
--- a/ext/redhat/puppet.spec
+++ b/ext/redhat/puppet.spec
@@ -3,7 +3,7 @@
# Fedora 17 ships with ruby 1.9, RHEL 7 with ruby 2.0, which use vendorlibdir instead
# of sitelibdir. Adjust our target if installing on f17 or rhel7.
-%if 0%{?fedora} >= 17 || 0%{?rhel} >= 7
+%if 0%{?fedora} >= 17 || 0%{?rhel} >= 7 || 0%{?amzn} >= 1
%global puppet_libdir %(ruby -rrbconfig -e 'puts RbConfig::CONFIG["vendorlibdir"]')
%else
%global puppet_libdir %(ruby -rrbconfig -e 'puts RbConfig::CONFIG["sitelibdir"]')
@@ -16,8 +16,8 @@
%endif
# VERSION is subbed out during rake srpm process
-%global realversion 3.6.1
-%global rpmversion 3.6.1
+%global realversion 3.7.0
+%global rpmversion 3.7.0
%global confdir ext/redhat
%global pending_upgrade_path %{_localstatedir}/lib/rpm-state/puppet
@@ -49,7 +49,7 @@ Requires: rubygem-json
%if 0%{?fedora} || 0%{?rhel} >= 6
%{!?_without_selinux:Requires: ruby(selinux), libselinux-utils}
%else
-%if 0%{?rhel} && 0%{?rhel} == 5
+%if ( 0%{?rhel} && 0%{?rhel} == 5 ) || 0%{?amzn} >= 1
%{!?_without_selinux:Requires: libselinux-ruby, libselinux-utils}
%endif
%endif
@@ -59,7 +59,6 @@ Requires: facter >= 1:1.7.0
# Ruby 1.8.7 available for el5 at: yum.puppetlabs.com/el/5/devel/$ARCH
Requires: ruby >= 1.8.7
Requires: hiera >= 1.0.0
-Requires: ruby-rgen >= 0.6.5
Obsoletes: hiera-puppet < 1.0.0
Provides: hiera-puppet >= 1.0.0
%{!?_without_augeas:Requires: ruby-augeas}
@@ -120,6 +119,8 @@ install -d -m0755 %{buildroot}%{_sysconfdir}/puppet/environments/example_env/mod
install -d -m0755 %{buildroot}%{_sysconfdir}/puppet/manifests
install -d -m0755 %{buildroot}%{_datadir}/%{name}/modules
install -d -m0755 %{buildroot}%{_localstatedir}/lib/puppet
+install -d -m0755 %{buildroot}%{_localstatedir}/lib/puppet/state
+install -d -m0755 %{buildroot}%{_localstatedir}/lib/puppet/reports
install -d -m0755 %{buildroot}%{_localstatedir}/run/puppet
# As per redhat bz #495096
@@ -260,6 +261,8 @@ cp -pr ext/puppet-nm-dispatcher \
%defattr(-, puppet, puppet, 0750)
%{_localstatedir}/log/puppet
%{_localstatedir}/lib/puppet
+%{_localstatedir}/lib/puppet/state
+%{_localstatedir}/lib/puppet/reports
# Return the default attributes to 0755 to
# prevent incorrect permission assignment on EL6
%defattr(-, root, root, 0755)
@@ -443,8 +446,8 @@ fi
rm -rf %{buildroot}
%changelog
-* Thu May 22 2014 Puppet Labs Release <info@puppetlabs.com> - 3.6.1-1
-- Build for 3.6.1
+* Wed Sep 03 2014 Puppet Labs Release <info@puppetlabs.com> - 3.7.0-1
+- Build for 3.7.0
* Wed Oct 2 2013 Jason Antman <jason@jasonantman.com>
- Move systemd service and unit file names back to "puppet" from erroneous "puppetagent"
diff --git a/ext/windows/service/daemon.rb b/ext/windows/service/daemon.rb
index 84e4a283d..45f8cd050 100755
--- a/ext/windows/service/daemon.rb
+++ b/ext/windows/service/daemon.rb
@@ -6,14 +6,13 @@ require 'win32/dir'
require 'win32/process'
require 'win32/eventlog'
-require 'windows/synchronize'
-require 'windows/handle'
-
class WindowsDaemon < Win32::Daemon
- include Windows::Synchronize
- include Windows::Handle
- include Windows::Process
+ CREATE_NEW_CONSOLE = 0x00000010
+ EVENTLOG_ERROR_TYPE = 0x0001
+ EVENTLOG_WARNING_TYPE = 0x0002
+ EVENTLOG_INFORMATION_TYPE = 0x0004
+ @run_thread = nil
@LOG_TO_FILE = false
LOG_FILE = File.expand_path(File.join(Dir::COMMON_APPDATA, 'PuppetLabs', 'puppet', 'var', 'log', 'windows.log'))
LEVELS = [:debug, :info, :notice, :err]
@@ -63,46 +62,42 @@ class WindowsDaemon < Win32::Daemon
log_notice('Service started')
- while running? do
+ service = self
+ @run_thread = Thread.new do
begin
- runinterval = %x{ "#{puppet}" agent --configprint runinterval }.to_i
- if runinterval == 0
- runinterval = 1800
- log_err("Failed to determine runinterval, defaulting to #{runinterval} seconds")
+ while service.running? do
+ runinterval = service.parse_runinterval(puppet)
+ if service.state == RUNNING or service.state == IDLE
+ service.log_notice("Executing agent with arguments: #{args}")
+ pid = Process.create(:command_line => "\"#{puppet}\" agent --onetime #{args}", :creation_flags => CREATE_NEW_CONSOLE).process_id
+ service.log_debug("Process created: #{pid}")
+ else
+ service.log_debug("Service is paused. Not invoking Puppet agent")
+ end
+
+ service.log_debug("Service worker thread waiting for #{runinterval} seconds")
+ sleep(runinterval)
+ service.log_debug('Service worker thread woken up')
end
rescue Exception => e
- log_exception(e)
- runinterval = 1800
- end
-
- if state == RUNNING or state == IDLE
- log_notice("Executing agent with arguments: #{args}")
- pid = Process.create(:command_line => "\"#{puppet}\" agent --onetime #{args}", :creation_flags => Process::CREATE_NEW_CONSOLE).process_id
- log_debug("Process created: #{pid}")
- else
- log_debug("Service is paused. Not invoking Puppet agent")
+ service.log_exception(e)
end
-
- log_debug("Service waiting for #{runinterval} seconds")
- sleep(runinterval)
- log_debug('Service woken up')
end
+ @run_thread.join
- log_notice('Service stopped')
rescue Exception => e
log_exception(e)
+ ensure
+ log_notice('Service stopped')
end
def service_stop
- log_notice('Service stopping')
- Thread.main.wakeup
+ log_notice('Service stopping / killing worker thread')
+ @run_thread.kill if @run_thread
end
def service_pause
- # The service will not stay in a paused stated, instead it will go back into a running state after a short period of time. This is an issue in the Win32-Service ruby code
- # Raised bug https://github.com/djberg96/win32-service/issues/11 and is fixed in version 0.8.3.
- # Because the Pause feature is so rarely used, there is no point in creating a workaround until puppet uses 0.8.3.
- log_notice('Service pausing. The service will not stay paused. See Puppet Issue PUP-1471 for more information')
+ log_notice('Service pausing')
end
def service_resume
@@ -130,16 +125,12 @@ class WindowsDaemon < Win32::Daemon
end
case level
- when :debug
- report_windows_event(Win32::EventLog::INFO,0x01,msg.to_s)
- when :info
- report_windows_event(Win32::EventLog::INFO,0x01,msg.to_s)
- when :notice
- report_windows_event(Win32::EventLog::INFO,0x01,msg.to_s)
+ when :debug, :info, :notice
+ report_windows_event(EVENTLOG_INFORMATION_TYPE,0x01,msg.to_s)
when :err
- report_windows_event(Win32::EventLog::ERR,0x03,msg.to_s)
+ report_windows_event(EVENTLOG_ERROR_TYPE,0x03,msg.to_s)
else
- report_windows_event(Win32::EventLog::WARN,0x02,msg.to_s)
+ report_windows_event(EVENTLOG_WARNING_TYPE,0x02,msg.to_s)
end
end
end
@@ -150,7 +141,7 @@ class WindowsDaemon < Win32::Daemon
eventlog = Win32::EventLog.open("Application")
eventlog.report_event(
:source => "Puppet",
- :event_type => type, # Win32::EventLog::INFO or WARN, ERROR
+ :event_type => type, # EVENTLOG_ERROR_TYPE, etc
:event_id => id, # 0x01 or 0x02, 0x03 etc.
:data => message # "the message"
)
@@ -162,6 +153,21 @@ class WindowsDaemon < Win32::Daemon
end
end
end
+
+ def parse_runinterval(puppet_path)
+ begin
+ runinterval = %x{ "#{puppet_path}" agent --configprint runinterval }.to_i
+ if runinterval == 0
+ runinterval = 1800
+ log_err("Failed to determine runinterval, defaulting to #{runinterval} seconds")
+ end
+ rescue Exception => e
+ log_exception(e)
+ runinterval = 1800
+ end
+
+ runinterval
+ end
end
if __FILE__ == $0
diff --git a/install.rb b/install.rb
index 1663686f2..9bdfa2586 100755
--- a/install.rb
+++ b/install.rb
@@ -250,7 +250,7 @@ def prepare_installation
begin
require 'win32/dir'
rescue LoadError => e
- puts "Cannot run on Microsoft Windows without the sys-admin, win32-process, win32-dir & win32-service gems: #{e}"
+ puts "Cannot run on Microsoft Windows without the win32-process, win32-dir & win32-service gems: #{e}"
exit -1
end
configdir = File.join(Dir::COMMON_APPDATA, "PuppetLabs", "puppet", "etc")
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
diff --git a/spec/fixtures/integration/node/environment/sitedir2/00_a.pp b/spec/fixtures/integration/node/environment/sitedir2/00_a.pp
new file mode 100644
index 000000000..508992fc3
--- /dev/null
+++ b/spec/fixtures/integration/node/environment/sitedir2/00_a.pp
@@ -0,0 +1,2 @@
+class a {}
+$a = 10 \ No newline at end of file
diff --git a/spec/fixtures/integration/node/environment/sitedir2/02_folder/01_b.pp b/spec/fixtures/integration/node/environment/sitedir2/02_folder/01_b.pp
new file mode 100644
index 000000000..25e339b4c
--- /dev/null
+++ b/spec/fixtures/integration/node/environment/sitedir2/02_folder/01_b.pp
@@ -0,0 +1,6 @@
+class b {}
+
+# if the files are evaluated in the wrong order, the file 'b' has a reference
+# to $a (set in file 'a') and with strict variable lookup should raise an error
+# and fail this test.
+$b = $a # error if $a not set in strict mode
diff --git a/spec/fixtures/integration/node/environment/sitedir2/03_c.pp b/spec/fixtures/integration/node/environment/sitedir2/03_c.pp
new file mode 100644
index 000000000..33393065c
--- /dev/null
+++ b/spec/fixtures/integration/node/environment/sitedir2/03_c.pp
@@ -0,0 +1 @@
+$c = $a + $b \ No newline at end of file
diff --git a/spec/fixtures/integration/node/environment/sitedir2/04_include.pp b/spec/fixtures/integration/node/environment/sitedir2/04_include.pp
new file mode 100644
index 000000000..0187959f0
--- /dev/null
+++ b/spec/fixtures/integration/node/environment/sitedir2/04_include.pp
@@ -0,0 +1,2 @@
+include a, b
+notify { "variables": message => "a: $a, b: $b c: $c" }
diff --git a/spec/fixtures/releases/jamtur01-apache/manifests/vhost.pp b/spec/fixtures/releases/jamtur01-apache/manifests/vhost.pp
index 2fe6ed204..a210f3557 100644
--- a/spec/fixtures/releases/jamtur01-apache/manifests/vhost.pp
+++ b/spec/fixtures/releases/jamtur01-apache/manifests/vhost.pp
@@ -8,7 +8,7 @@ define apache::vhost( $port, $docroot, $ssl=true, $template='apache/vhost-defaul
content => template($template),
owner => 'root',
group => 'root',
- mode => '777',
+ mode => '0777',
require => Package['httpd'],
notify => Service['httpd'],
}
diff --git a/spec/fixtures/unit/indirector/hiera/global.yaml b/spec/fixtures/unit/indirector/hiera/global.yaml
new file mode 100644
index 000000000..0853e0ec1
--- /dev/null
+++ b/spec/fixtures/unit/indirector/hiera/global.yaml
@@ -0,0 +1,10 @@
+---
+integer: 3000
+string: 'apache'
+hash:
+ user: 'Hightower'
+ group: 'admin'
+ mode: '0644'
+array:
+ - '0.ntp.puppetlabs.com'
+ - '1.ntp.puppetlabs.com'
diff --git a/spec/fixtures/unit/indirector/hiera/invalid.yaml b/spec/fixtures/unit/indirector/hiera/invalid.yaml
new file mode 100644
index 000000000..9a84fa87c
--- /dev/null
+++ b/spec/fixtures/unit/indirector/hiera/invalid.yaml
@@ -0,0 +1 @@
+{ invalid:
diff --git a/spec/fixtures/unit/parser/functions/create_resources/foo/manifests/init.pp b/spec/fixtures/unit/parser/functions/create_resources/foo/manifests/init.pp
new file mode 100644
index 000000000..1772d0bae
--- /dev/null
+++ b/spec/fixtures/unit/parser/functions/create_resources/foo/manifests/init.pp
@@ -0,0 +1,3 @@
+class foo {
+ create_resources('foo::wrongdefine', {'blah'=>{'one'=>'two'}})
+}
diff --git a/spec/fixtures/unit/parser/functions/create_resources/foo/manifests/wrongdefine.pp b/spec/fixtures/unit/parser/functions/create_resources/foo/manifests/wrongdefine.pp
new file mode 100644
index 000000000..c933d89ec
--- /dev/null
+++ b/spec/fixtures/unit/parser/functions/create_resources/foo/manifests/wrongdefine.pp
@@ -0,0 +1,3 @@
+define foo::wrongdefine($one){
+ $foo = $one,
+}
diff --git a/spec/fixtures/unit/parser/lexer/argumentdefaults.pp b/spec/fixtures/unit/parser/lexer/argumentdefaults.pp
index eac9dd757..a296c2fb0 100644
--- a/spec/fixtures/unit/parser/lexer/argumentdefaults.pp
+++ b/spec/fixtures/unit/parser/lexer/argumentdefaults.pp
@@ -1,6 +1,6 @@
# $Id$
-define testargs($file, $mode = 755) {
+define testargs($file, $mode = '0755') {
file { $file: ensure => file, mode => $mode }
}
@@ -10,5 +10,5 @@ testargs { "testingname":
testargs { "testingother":
file => "/tmp/argumenttest2",
- mode => 644
+ mode => '0644'
}
diff --git a/spec/fixtures/unit/parser/lexer/casestatement.pp b/spec/fixtures/unit/parser/lexer/casestatement.pp
index 66ecd72b9..c17242791 100644
--- a/spec/fixtures/unit/parser/lexer/casestatement.pp
+++ b/spec/fixtures/unit/parser/lexer/casestatement.pp
@@ -4,10 +4,10 @@ $var = "value"
case $var {
"nope": {
- file { "/tmp/fakefile": mode => 644, ensure => file }
+ file { "/tmp/fakefile": mode => '0644', ensure => file }
}
"value": {
- file { "/tmp/existsfile": mode => 755, ensure => file }
+ file { "/tmp/existsfile": mode => '0755', ensure => file }
}
}
@@ -15,15 +15,15 @@ $ovar = "yayness"
case $ovar {
"fooness": {
- file { "/tmp/nostillexistsfile": mode => 644, ensure => file }
+ file { "/tmp/nostillexistsfile": mode => '0644', ensure => file }
}
"booness", "yayness": {
case $var {
"nep": {
- file { "/tmp/noexistsfile": mode => 644, ensure => file }
+ file { "/tmp/noexistsfile": mode => '0644', ensure => file }
}
"value": {
- file { "/tmp/existsfile2": mode => 755, ensure => file }
+ file { "/tmp/existsfile2": mode => '0755', ensure => file }
}
}
}
@@ -31,10 +31,10 @@ case $ovar {
case $ovar {
"fooness": {
- file { "/tmp/nostillexistsfile": mode => 644, ensure => file }
+ file { "/tmp/nostillexistsfile": mode => '0644', ensure => file }
}
default: {
- file { "/tmp/existsfile3": mode => 755, ensure => file }
+ file { "/tmp/existsfile3": mode => '0755', ensure => file }
}
}
@@ -42,7 +42,7 @@ $bool = true
case $bool {
true: {
- file { "/tmp/existsfile4": mode => 755, ensure => file }
+ file { "/tmp/existsfile4": mode => '0755', ensure => file }
}
}
@@ -51,15 +51,15 @@ $a = yay
$b = boo
case $yay {
- $a: { file { "/tmp/existsfile5": mode => 755, ensure => file } }
- $b: { file { "/tmp/existsfile5": mode => 644, ensure => file } }
- default: { file { "/tmp/existsfile5": mode => 711, ensure => file } }
+ $a: { file { "/tmp/existsfile5": mode => '0755', ensure => file } }
+ $b: { file { "/tmp/existsfile5": mode => '0644', ensure => file } }
+ default: { file { "/tmp/existsfile5": mode => '0711', ensure => file } }
}
$regexvar = "exists regex"
case $regexvar {
- "no match": { file { "/tmp/existsfile6": mode => 644, ensure => file } }
- /(.*) regex$/: { file { "/tmp/${1}file6": mode => 755, ensure => file } }
- default: { file { "/tmp/existsfile6": mode => 711, ensure => file } }
+ "no match": { file { "/tmp/existsfile6": mode => '0644', ensure => file } }
+ /(.*) regex$/: { file { "/tmp/${1}file6": mode => '0755', ensure => file } }
+ default: { file { "/tmp/existsfile6": mode => '0711', ensure => file } }
}
diff --git a/spec/fixtures/unit/parser/lexer/classheirarchy.pp b/spec/fixtures/unit/parser/lexer/classheirarchy.pp
index 36619d8b9..5a51a8229 100644
--- a/spec/fixtures/unit/parser/lexer/classheirarchy.pp
+++ b/spec/fixtures/unit/parser/lexer/classheirarchy.pp
@@ -1,15 +1,15 @@
# $Id$
class base {
- file { "/tmp/classheir1": ensure => file, mode => 755 }
+ file { "/tmp/classheir1": ensure => file, mode => '0755' }
}
class sub1 inherits base {
- file { "/tmp/classheir2": ensure => file, mode => 755 }
+ file { "/tmp/classheir2": ensure => file, mode => '0755' }
}
class sub2 inherits base {
- file { "/tmp/classheir3": ensure => file, mode => 755 }
+ file { "/tmp/classheir3": ensure => file, mode => '0755' }
}
include sub1, sub2
diff --git a/spec/fixtures/unit/parser/lexer/classincludes.pp b/spec/fixtures/unit/parser/lexer/classincludes.pp
index bd5b44ed7..0b1bb07a8 100644
--- a/spec/fixtures/unit/parser/lexer/classincludes.pp
+++ b/spec/fixtures/unit/parser/lexer/classincludes.pp
@@ -1,15 +1,15 @@
# $Id$
class base {
- file { "/tmp/classincludes1": ensure => file, mode => 755 }
+ file { "/tmp/classincludes1": ensure => file, mode => '0755' }
}
class sub1 inherits base {
- file { "/tmp/classincludes2": ensure => file, mode => 755 }
+ file { "/tmp/classincludes2": ensure => file, mode => '0755' }
}
class sub2 inherits base {
- file { "/tmp/classincludes3": ensure => file, mode => 755 }
+ file { "/tmp/classincludes3": ensure => file, mode => '0755' }
}
$sub = "sub2"
diff --git a/spec/fixtures/unit/parser/lexer/classpathtest.pp b/spec/fixtures/unit/parser/lexer/classpathtest.pp
index 580333369..fae7cadbf 100644
--- a/spec/fixtures/unit/parser/lexer/classpathtest.pp
+++ b/spec/fixtures/unit/parser/lexer/classpathtest.pp
@@ -1,7 +1,7 @@
# $Id$
define mytype {
- file { "/tmp/classtest": ensure => file, mode => 755 }
+ file { "/tmp/classtest": ensure => file, mode => '0755' }
}
class testing {
diff --git a/spec/fixtures/unit/parser/lexer/collection_override.pp b/spec/fixtures/unit/parser/lexer/collection_override.pp
index b1b39ab16..f1063bf97 100644
--- a/spec/fixtures/unit/parser/lexer/collection_override.pp
+++ b/spec/fixtures/unit/parser/lexer/collection_override.pp
@@ -4,5 +4,5 @@
}
File<| |> {
- mode => 0600
+ mode => '0600'
}
diff --git a/spec/fixtures/unit/parser/lexer/componentrequire.pp b/spec/fixtures/unit/parser/lexer/componentrequire.pp
index a61d2050c..8d67ab161 100644
--- a/spec/fixtures/unit/parser/lexer/componentrequire.pp
+++ b/spec/fixtures/unit/parser/lexer/componentrequire.pp
@@ -2,7 +2,7 @@ define testfile($mode) {
file { $name: mode => $mode, ensure => present }
}
-testfile { "/tmp/testing_component_requires2": mode => 755 }
+testfile { "/tmp/testing_component_requires2": mode => '0755' }
-file { "/tmp/testing_component_requires1": mode => 755, ensure => present,
+file { "/tmp/testing_component_requires1": mode => '0755', ensure => present,
require => Testfile["/tmp/testing_component_requires2"] }
diff --git a/spec/fixtures/unit/parser/lexer/deepclassheirarchy.pp b/spec/fixtures/unit/parser/lexer/deepclassheirarchy.pp
index 249e6334d..8e477f7b7 100644
--- a/spec/fixtures/unit/parser/lexer/deepclassheirarchy.pp
+++ b/spec/fixtures/unit/parser/lexer/deepclassheirarchy.pp
@@ -1,23 +1,23 @@
# $Id$
class base {
- file { "/tmp/deepclassheir1": ensure => file, mode => 755 }
+ file { "/tmp/deepclassheir1": ensure => file, mode => '0755' }
}
class sub1 inherits base {
- file { "/tmp/deepclassheir2": ensure => file, mode => 755 }
+ file { "/tmp/deepclassheir2": ensure => file, mode => '0755' }
}
class sub2 inherits sub1 {
- file { "/tmp/deepclassheir3": ensure => file, mode => 755 }
+ file { "/tmp/deepclassheir3": ensure => file, mode => '0755' }
}
class sub3 inherits sub2 {
- file { "/tmp/deepclassheir4": ensure => file, mode => 755 }
+ file { "/tmp/deepclassheir4": ensure => file, mode => '0755' }
}
class sub4 inherits sub3 {
- file { "/tmp/deepclassheir5": ensure => file, mode => 755 }
+ file { "/tmp/deepclassheir5": ensure => file, mode => '0755' }
}
include sub4
diff --git a/spec/fixtures/unit/parser/lexer/defineoverrides.pp b/spec/fixtures/unit/parser/lexer/defineoverrides.pp
index c68b139e3..bc2b5647e 100644
--- a/spec/fixtures/unit/parser/lexer/defineoverrides.pp
+++ b/spec/fixtures/unit/parser/lexer/defineoverrides.pp
@@ -7,11 +7,11 @@ define myfile($mode) {
}
class base {
- myfile { $file: mode => 644 }
+ myfile { $file: mode => '0644' }
}
class sub inherits base {
- Myfile[$file] { mode => 755, } # test the end-comma
+ Myfile[$file] { mode => '0755', } # test the end-comma
}
include sub
diff --git a/spec/fixtures/unit/parser/lexer/filecreate.pp b/spec/fixtures/unit/parser/lexer/filecreate.pp
index d7972c234..b8edcd540 100644
--- a/spec/fixtures/unit/parser/lexer/filecreate.pp
+++ b/spec/fixtures/unit/parser/lexer/filecreate.pp
@@ -1,8 +1,8 @@
# $Id$
file {
- "/tmp/createatest": ensure => file, mode => 755;
- "/tmp/createbtest": ensure => file, mode => 755
+ "/tmp/createatest": ensure => file, mode => '0755';
+ "/tmp/createbtest": ensure => file, mode => '0755'
}
file {
diff --git a/spec/fixtures/unit/parser/lexer/ifexpression.pp b/spec/fixtures/unit/parser/lexer/ifexpression.pp
index 29a637291..131530dcb 100644
--- a/spec/fixtures/unit/parser/lexer/ifexpression.pp
+++ b/spec/fixtures/unit/parser/lexer/ifexpression.pp
@@ -7,6 +7,6 @@ if ($one < $two) and (($two < 3) or ($two == 2)) {
if "test regex" =~ /(.*) regex/ {
file {
- "/tmp/${1}iftest": ensure => file, mode => 0755
+ "/tmp/${1}iftest": ensure => file, mode => '0755'
}
}
diff --git a/spec/fixtures/unit/parser/lexer/implicititeration.pp b/spec/fixtures/unit/parser/lexer/implicititeration.pp
index 6f34cb29c..75719e985 100644
--- a/spec/fixtures/unit/parser/lexer/implicititeration.pp
+++ b/spec/fixtures/unit/parser/lexer/implicititeration.pp
@@ -2,14 +2,14 @@
$files = ["/tmp/iterationatest", "/tmp/iterationbtest"]
-file { $files: ensure => file, mode => 755 }
+file { $files: ensure => file, mode => '0755' }
file { ["/tmp/iterationctest", "/tmp/iterationdtest"]:
ensure => file,
- mode => 755
+ mode => '0755'
}
file {
- ["/tmp/iterationetest", "/tmp/iterationftest"]: ensure => file, mode => 755;
- ["/tmp/iterationgtest", "/tmp/iterationhtest"]: ensure => file, mode => 755;
+ ["/tmp/iterationetest", "/tmp/iterationftest"]: ensure => file, mode => '0755';
+ ["/tmp/iterationgtest", "/tmp/iterationhtest"]: ensure => file, mode => '0755';
}
diff --git a/spec/fixtures/unit/parser/lexer/multipleinstances.pp b/spec/fixtures/unit/parser/lexer/multipleinstances.pp
index 2f9b3c2e8..bc0cdee24 100644
--- a/spec/fixtures/unit/parser/lexer/multipleinstances.pp
+++ b/spec/fixtures/unit/parser/lexer/multipleinstances.pp
@@ -1,7 +1,7 @@
# $Id$
file {
- "/tmp/multipleinstancesa": ensure => file, mode => 755;
- "/tmp/multipleinstancesb": ensure => file, mode => 755;
- "/tmp/multipleinstancesc": ensure => file, mode => 755;
+ "/tmp/multipleinstancesa": ensure => file, mode => '0755';
+ "/tmp/multipleinstancesb": ensure => file, mode => '0755';
+ "/tmp/multipleinstancesc": ensure => file, mode => '0755';
}
diff --git a/spec/fixtures/unit/parser/lexer/multisubs.pp b/spec/fixtures/unit/parser/lexer/multisubs.pp
index bcec69e2a..079edf336 100644
--- a/spec/fixtures/unit/parser/lexer/multisubs.pp
+++ b/spec/fixtures/unit/parser/lexer/multisubs.pp
@@ -1,9 +1,9 @@
class base {
- file { "/tmp/multisubtest": content => "base", mode => 644 }
+ file { "/tmp/multisubtest": content => "base", mode => '0644' }
}
class sub1 inherits base {
- File["/tmp/multisubtest"] { mode => 755 }
+ File["/tmp/multisubtest"] { mode => '0755' }
}
class sub2 inherits base {
diff --git a/spec/fixtures/unit/parser/lexer/namevartest.pp b/spec/fixtures/unit/parser/lexer/namevartest.pp
index dbee1c356..d080cb467 100644
--- a/spec/fixtures/unit/parser/lexer/namevartest.pp
+++ b/spec/fixtures/unit/parser/lexer/namevartest.pp
@@ -5,5 +5,5 @@ define filetest($mode, $ensure = file) {
}
}
-filetest { "/tmp/testfiletest": mode => 644}
-filetest { "/tmp/testdirtest": mode => 755, ensure => directory}
+filetest { "/tmp/testfiletest": mode => '0644'}
+filetest { "/tmp/testdirtest": mode => '0755', ensure => directory}
diff --git a/spec/fixtures/unit/parser/lexer/simpledefaults.pp b/spec/fixtures/unit/parser/lexer/simpledefaults.pp
index 63d199a68..bbe61e25c 100644
--- a/spec/fixtures/unit/parser/lexer/simpledefaults.pp
+++ b/spec/fixtures/unit/parser/lexer/simpledefaults.pp
@@ -1,5 +1,5 @@
# $Id$
-File { mode => 755 }
+File { mode => '0755' }
file { "/tmp/defaulttest": ensure => file }
diff --git a/spec/fixtures/unit/pops/parser/lexer/argumentdefaults.pp b/spec/fixtures/unit/pops/parser/lexer/argumentdefaults.pp
index eac9dd757..29c7227ea 100644
--- a/spec/fixtures/unit/pops/parser/lexer/argumentdefaults.pp
+++ b/spec/fixtures/unit/pops/parser/lexer/argumentdefaults.pp
@@ -10,5 +10,5 @@ testargs { "testingname":
testargs { "testingother":
file => "/tmp/argumenttest2",
- mode => 644
+ mode => '0644'
}
diff --git a/spec/fixtures/unit/pops/parser/lexer/casestatement.pp b/spec/fixtures/unit/pops/parser/lexer/casestatement.pp
index 66ecd72b9..c17242791 100644
--- a/spec/fixtures/unit/pops/parser/lexer/casestatement.pp
+++ b/spec/fixtures/unit/pops/parser/lexer/casestatement.pp
@@ -4,10 +4,10 @@ $var = "value"
case $var {
"nope": {
- file { "/tmp/fakefile": mode => 644, ensure => file }
+ file { "/tmp/fakefile": mode => '0644', ensure => file }
}
"value": {
- file { "/tmp/existsfile": mode => 755, ensure => file }
+ file { "/tmp/existsfile": mode => '0755', ensure => file }
}
}
@@ -15,15 +15,15 @@ $ovar = "yayness"
case $ovar {
"fooness": {
- file { "/tmp/nostillexistsfile": mode => 644, ensure => file }
+ file { "/tmp/nostillexistsfile": mode => '0644', ensure => file }
}
"booness", "yayness": {
case $var {
"nep": {
- file { "/tmp/noexistsfile": mode => 644, ensure => file }
+ file { "/tmp/noexistsfile": mode => '0644', ensure => file }
}
"value": {
- file { "/tmp/existsfile2": mode => 755, ensure => file }
+ file { "/tmp/existsfile2": mode => '0755', ensure => file }
}
}
}
@@ -31,10 +31,10 @@ case $ovar {
case $ovar {
"fooness": {
- file { "/tmp/nostillexistsfile": mode => 644, ensure => file }
+ file { "/tmp/nostillexistsfile": mode => '0644', ensure => file }
}
default: {
- file { "/tmp/existsfile3": mode => 755, ensure => file }
+ file { "/tmp/existsfile3": mode => '0755', ensure => file }
}
}
@@ -42,7 +42,7 @@ $bool = true
case $bool {
true: {
- file { "/tmp/existsfile4": mode => 755, ensure => file }
+ file { "/tmp/existsfile4": mode => '0755', ensure => file }
}
}
@@ -51,15 +51,15 @@ $a = yay
$b = boo
case $yay {
- $a: { file { "/tmp/existsfile5": mode => 755, ensure => file } }
- $b: { file { "/tmp/existsfile5": mode => 644, ensure => file } }
- default: { file { "/tmp/existsfile5": mode => 711, ensure => file } }
+ $a: { file { "/tmp/existsfile5": mode => '0755', ensure => file } }
+ $b: { file { "/tmp/existsfile5": mode => '0644', ensure => file } }
+ default: { file { "/tmp/existsfile5": mode => '0711', ensure => file } }
}
$regexvar = "exists regex"
case $regexvar {
- "no match": { file { "/tmp/existsfile6": mode => 644, ensure => file } }
- /(.*) regex$/: { file { "/tmp/${1}file6": mode => 755, ensure => file } }
- default: { file { "/tmp/existsfile6": mode => 711, ensure => file } }
+ "no match": { file { "/tmp/existsfile6": mode => '0644', ensure => file } }
+ /(.*) regex$/: { file { "/tmp/${1}file6": mode => '0755', ensure => file } }
+ default: { file { "/tmp/existsfile6": mode => '0711', ensure => file } }
}
diff --git a/spec/fixtures/unit/pops/parser/lexer/classheirarchy.pp b/spec/fixtures/unit/pops/parser/lexer/classheirarchy.pp
index 36619d8b9..5a51a8229 100644
--- a/spec/fixtures/unit/pops/parser/lexer/classheirarchy.pp
+++ b/spec/fixtures/unit/pops/parser/lexer/classheirarchy.pp
@@ -1,15 +1,15 @@
# $Id$
class base {
- file { "/tmp/classheir1": ensure => file, mode => 755 }
+ file { "/tmp/classheir1": ensure => file, mode => '0755' }
}
class sub1 inherits base {
- file { "/tmp/classheir2": ensure => file, mode => 755 }
+ file { "/tmp/classheir2": ensure => file, mode => '0755' }
}
class sub2 inherits base {
- file { "/tmp/classheir3": ensure => file, mode => 755 }
+ file { "/tmp/classheir3": ensure => file, mode => '0755' }
}
include sub1, sub2
diff --git a/spec/fixtures/unit/pops/parser/lexer/classincludes.pp b/spec/fixtures/unit/pops/parser/lexer/classincludes.pp
index bd5b44ed7..0b1bb07a8 100644
--- a/spec/fixtures/unit/pops/parser/lexer/classincludes.pp
+++ b/spec/fixtures/unit/pops/parser/lexer/classincludes.pp
@@ -1,15 +1,15 @@
# $Id$
class base {
- file { "/tmp/classincludes1": ensure => file, mode => 755 }
+ file { "/tmp/classincludes1": ensure => file, mode => '0755' }
}
class sub1 inherits base {
- file { "/tmp/classincludes2": ensure => file, mode => 755 }
+ file { "/tmp/classincludes2": ensure => file, mode => '0755' }
}
class sub2 inherits base {
- file { "/tmp/classincludes3": ensure => file, mode => 755 }
+ file { "/tmp/classincludes3": ensure => file, mode => '0755' }
}
$sub = "sub2"
diff --git a/spec/fixtures/unit/pops/parser/lexer/classpathtest.pp b/spec/fixtures/unit/pops/parser/lexer/classpathtest.pp
index 580333369..fae7cadbf 100644
--- a/spec/fixtures/unit/pops/parser/lexer/classpathtest.pp
+++ b/spec/fixtures/unit/pops/parser/lexer/classpathtest.pp
@@ -1,7 +1,7 @@
# $Id$
define mytype {
- file { "/tmp/classtest": ensure => file, mode => 755 }
+ file { "/tmp/classtest": ensure => file, mode => '0755' }
}
class testing {
diff --git a/spec/fixtures/unit/pops/parser/lexer/collection_override.pp b/spec/fixtures/unit/pops/parser/lexer/collection_override.pp
index b1b39ab16..f1063bf97 100644
--- a/spec/fixtures/unit/pops/parser/lexer/collection_override.pp
+++ b/spec/fixtures/unit/pops/parser/lexer/collection_override.pp
@@ -4,5 +4,5 @@
}
File<| |> {
- mode => 0600
+ mode => '0600'
}
diff --git a/spec/fixtures/unit/pops/parser/lexer/componentrequire.pp b/spec/fixtures/unit/pops/parser/lexer/componentrequire.pp
index a61d2050c..8d67ab161 100644
--- a/spec/fixtures/unit/pops/parser/lexer/componentrequire.pp
+++ b/spec/fixtures/unit/pops/parser/lexer/componentrequire.pp
@@ -2,7 +2,7 @@ define testfile($mode) {
file { $name: mode => $mode, ensure => present }
}
-testfile { "/tmp/testing_component_requires2": mode => 755 }
+testfile { "/tmp/testing_component_requires2": mode => '0755' }
-file { "/tmp/testing_component_requires1": mode => 755, ensure => present,
+file { "/tmp/testing_component_requires1": mode => '0755', ensure => present,
require => Testfile["/tmp/testing_component_requires2"] }
diff --git a/spec/fixtures/unit/pops/parser/lexer/deepclassheirarchy.pp b/spec/fixtures/unit/pops/parser/lexer/deepclassheirarchy.pp
index 249e6334d..8e477f7b7 100644
--- a/spec/fixtures/unit/pops/parser/lexer/deepclassheirarchy.pp
+++ b/spec/fixtures/unit/pops/parser/lexer/deepclassheirarchy.pp
@@ -1,23 +1,23 @@
# $Id$
class base {
- file { "/tmp/deepclassheir1": ensure => file, mode => 755 }
+ file { "/tmp/deepclassheir1": ensure => file, mode => '0755' }
}
class sub1 inherits base {
- file { "/tmp/deepclassheir2": ensure => file, mode => 755 }
+ file { "/tmp/deepclassheir2": ensure => file, mode => '0755' }
}
class sub2 inherits sub1 {
- file { "/tmp/deepclassheir3": ensure => file, mode => 755 }
+ file { "/tmp/deepclassheir3": ensure => file, mode => '0755' }
}
class sub3 inherits sub2 {
- file { "/tmp/deepclassheir4": ensure => file, mode => 755 }
+ file { "/tmp/deepclassheir4": ensure => file, mode => '0755' }
}
class sub4 inherits sub3 {
- file { "/tmp/deepclassheir5": ensure => file, mode => 755 }
+ file { "/tmp/deepclassheir5": ensure => file, mode => '0755' }
}
include sub4
diff --git a/spec/fixtures/unit/pops/parser/lexer/defineoverrides.pp b/spec/fixtures/unit/pops/parser/lexer/defineoverrides.pp
index c68b139e3..bc2b5647e 100644
--- a/spec/fixtures/unit/pops/parser/lexer/defineoverrides.pp
+++ b/spec/fixtures/unit/pops/parser/lexer/defineoverrides.pp
@@ -7,11 +7,11 @@ define myfile($mode) {
}
class base {
- myfile { $file: mode => 644 }
+ myfile { $file: mode => '0644' }
}
class sub inherits base {
- Myfile[$file] { mode => 755, } # test the end-comma
+ Myfile[$file] { mode => '0755', } # test the end-comma
}
include sub
diff --git a/spec/fixtures/unit/pops/parser/lexer/filecreate.pp b/spec/fixtures/unit/pops/parser/lexer/filecreate.pp
index d7972c234..b8edcd540 100644
--- a/spec/fixtures/unit/pops/parser/lexer/filecreate.pp
+++ b/spec/fixtures/unit/pops/parser/lexer/filecreate.pp
@@ -1,8 +1,8 @@
# $Id$
file {
- "/tmp/createatest": ensure => file, mode => 755;
- "/tmp/createbtest": ensure => file, mode => 755
+ "/tmp/createatest": ensure => file, mode => '0755';
+ "/tmp/createbtest": ensure => file, mode => '0755'
}
file {
diff --git a/spec/fixtures/unit/pops/parser/lexer/ifexpression.pp b/spec/fixtures/unit/pops/parser/lexer/ifexpression.pp
index 29a637291..131530dcb 100644
--- a/spec/fixtures/unit/pops/parser/lexer/ifexpression.pp
+++ b/spec/fixtures/unit/pops/parser/lexer/ifexpression.pp
@@ -7,6 +7,6 @@ if ($one < $two) and (($two < 3) or ($two == 2)) {
if "test regex" =~ /(.*) regex/ {
file {
- "/tmp/${1}iftest": ensure => file, mode => 0755
+ "/tmp/${1}iftest": ensure => file, mode => '0755'
}
}
diff --git a/spec/fixtures/unit/pops/parser/lexer/implicititeration.pp b/spec/fixtures/unit/pops/parser/lexer/implicititeration.pp
index 6f34cb29c..75719e985 100644
--- a/spec/fixtures/unit/pops/parser/lexer/implicititeration.pp
+++ b/spec/fixtures/unit/pops/parser/lexer/implicititeration.pp
@@ -2,14 +2,14 @@
$files = ["/tmp/iterationatest", "/tmp/iterationbtest"]
-file { $files: ensure => file, mode => 755 }
+file { $files: ensure => file, mode => '0755' }
file { ["/tmp/iterationctest", "/tmp/iterationdtest"]:
ensure => file,
- mode => 755
+ mode => '0755'
}
file {
- ["/tmp/iterationetest", "/tmp/iterationftest"]: ensure => file, mode => 755;
- ["/tmp/iterationgtest", "/tmp/iterationhtest"]: ensure => file, mode => 755;
+ ["/tmp/iterationetest", "/tmp/iterationftest"]: ensure => file, mode => '0755';
+ ["/tmp/iterationgtest", "/tmp/iterationhtest"]: ensure => file, mode => '0755';
}
diff --git a/spec/fixtures/unit/pops/parser/lexer/multipleinstances.pp b/spec/fixtures/unit/pops/parser/lexer/multipleinstances.pp
index 2f9b3c2e8..bc0cdee24 100644
--- a/spec/fixtures/unit/pops/parser/lexer/multipleinstances.pp
+++ b/spec/fixtures/unit/pops/parser/lexer/multipleinstances.pp
@@ -1,7 +1,7 @@
# $Id$
file {
- "/tmp/multipleinstancesa": ensure => file, mode => 755;
- "/tmp/multipleinstancesb": ensure => file, mode => 755;
- "/tmp/multipleinstancesc": ensure => file, mode => 755;
+ "/tmp/multipleinstancesa": ensure => file, mode => '0755';
+ "/tmp/multipleinstancesb": ensure => file, mode => '0755';
+ "/tmp/multipleinstancesc": ensure => file, mode => '0755';
}
diff --git a/spec/fixtures/unit/pops/parser/lexer/multisubs.pp b/spec/fixtures/unit/pops/parser/lexer/multisubs.pp
index bcec69e2a..079edf336 100644
--- a/spec/fixtures/unit/pops/parser/lexer/multisubs.pp
+++ b/spec/fixtures/unit/pops/parser/lexer/multisubs.pp
@@ -1,9 +1,9 @@
class base {
- file { "/tmp/multisubtest": content => "base", mode => 644 }
+ file { "/tmp/multisubtest": content => "base", mode => '0644' }
}
class sub1 inherits base {
- File["/tmp/multisubtest"] { mode => 755 }
+ File["/tmp/multisubtest"] { mode => '0755' }
}
class sub2 inherits base {
diff --git a/spec/fixtures/unit/pops/parser/lexer/namevartest.pp b/spec/fixtures/unit/pops/parser/lexer/namevartest.pp
index dbee1c356..d080cb467 100644
--- a/spec/fixtures/unit/pops/parser/lexer/namevartest.pp
+++ b/spec/fixtures/unit/pops/parser/lexer/namevartest.pp
@@ -5,5 +5,5 @@ define filetest($mode, $ensure = file) {
}
}
-filetest { "/tmp/testfiletest": mode => 644}
-filetest { "/tmp/testdirtest": mode => 755, ensure => directory}
+filetest { "/tmp/testfiletest": mode => '0644'}
+filetest { "/tmp/testdirtest": mode => '0755', ensure => directory}
diff --git a/spec/fixtures/unit/pops/parser/lexer/simpledefaults.pp b/spec/fixtures/unit/pops/parser/lexer/simpledefaults.pp
index 63d199a68..bbe61e25c 100644
--- a/spec/fixtures/unit/pops/parser/lexer/simpledefaults.pp
+++ b/spec/fixtures/unit/pops/parser/lexer/simpledefaults.pp
@@ -1,5 +1,5 @@
# $Id$
-File { mode => 755 }
+File { mode => '0755' }
file { "/tmp/defaulttest": ensure => file }
diff --git a/spec/fixtures/unit/provider/package/gem/gem-list-single-package b/spec/fixtures/unit/provider/package/gem/gem-list-single-package
new file mode 100644
index 000000000..41576e3e6
--- /dev/null
+++ b/spec/fixtures/unit/provider/package/gem/gem-list-single-package
@@ -0,0 +1,4 @@
+
+*** REMOTE GEMS ***
+
+bundler (1.6.2)
diff --git a/spec/fixtures/unit/type/user/authorized_keys b/spec/fixtures/unit/type/user/authorized_keys
index 265173ef4..dd1807e56 100644
--- a/spec/fixtures/unit/type/user/authorized_keys
+++ b/spec/fixtures/unit/type/user/authorized_keys
@@ -1,5 +1,5 @@
# fixture for testing ssh key purging
-ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDTXvM7AslzjNUYrPLiNVBsF5VnqL2RmqrkzscdVdHzVxvieNwmLGeUkg8EfXPiz7j5F/Lr0J8oItTCWzyN2KmM+DhUMjvP4AbELO/VYbnVrZICRiUNYSO3EN9/uapKAuiev88d7ynbonCU0VZoTPg/ug4OondOrLCtcGri5ltF+mausGfAYiFAQVEWqXV+1tyejoawJ884etb3n4ilpsrH9JK6AtOkEWVD3TDrNi29O1mQQ/Cn88g472zAJ+DhsIn+iehtfX5nmOtDNN/1t1bGMIBzkSYEAYwUiRJbRXvbobT7qKZQPA3dh0m8AYQS5/hd4/c4pmlxL8kgr24SnBY5 key1 keyname1
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDTXvM7AslzjNUYrPLiNVBsF5VnqL2RmqrkzscdVdHzVxvieNwmLGeUkg8EfXPiz7j5F/Lr0J8oItTCWzyN2KmM+DhUMjvP4AbELO/VYbnVrZICRiUNYSO3EN9/uapKAuiev88d7ynbonCU0VZoTPg/ug4OondOrLCtcGri5ltF+mausGfAYiFAQVEWqXV+1tyejoawJ884etb3n4ilpsrH9JK6AtOkEWVD3TDrNi29O1mQQ/Cn88g472zAJ+DhsIn+iehtfX5nmOtDNN/1t1bGMIBzkSYEAYwUiRJbRXvbobT7qKZQPA3dh0m8AYQS5/hd4/c4pmlxL8kgr24SnBY5 key1 name
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDTXvM7AslzjNUYrPLiNVBsF5VnqL2RmqrkzscdVdHzVxvieNwmLGeUkg8EfXPiz7j5F/Lr0J8oItTCWzyN2KmM+DhUMjvP4AbELO/VYbnVrZICRiUNYSO3EN9/uapKAuiev88d7ynbonCU0VZoTPg/ug4OondOrLCtcGri5ltF+mausGfAYiFAQVEWqXV+1tyejoawJ884etb3n4ilpsrH9JK6AtOkEWVD3TDrNi29O1mQQ/Cn88g472zAJ+DhsIn+iehtfX5nmOtDNN/1t1bGMIBzkSYEAYwUiRJbRXvbobT7qKZQPA3dh0m8AYQS5/hd4/c4pmlxL8kgr24SnBY5 keyname2
#ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDTXvM7AslzjNUYrPLiNVBsF5VnqL2RmqrkzscdVdHzVxvieNwmLGeUkg8EfXPiz7j5F/Lr0J8oItTCWzyN2KmM+DhUMjvP4AbELO/VYbnVrZICRiUNYSO3EN9/uapKAuiev88d7ynbonCU0VZoTPg/ug4OondOrLCtcGri5ltF+mausGfAYiFAQVEWqXV+1tyejoawJ884etb3n4ilpsrH9JK6AtOkEWVD3TDrNi29O1mQQ/Cn88g472zAJ+DhsIn+iehtfX5nmOtDNN/1t1bGMIBzkSYEAYwUiRJbRXvbobT7qKZQPA3dh0m8AYQS5/hd4/c4pmlxL8kgr24SnBY5 keyname3
diff --git a/spec/integration/agent/logging_spec.rb b/spec/integration/agent/logging_spec.rb
index c686397c8..1c4394a6e 100755
--- a/spec/integration/agent/logging_spec.rb
+++ b/spec/integration/agent/logging_spec.rb
@@ -88,6 +88,10 @@ describe 'agent logging' do
else
it "when evoked with #{argv}, logs to #{expected[:loggers].inspect} at level #{expected[:level]}" do
+ if Facter.value(:kernelmajversion).to_f < 6.0
+ pending("requires win32-eventlog gem upgrade to 0.6.2 on Windows 2003")
+ end
+
# This logger is created by the Puppet::Settings object which creates and
# applies a catalog to ensure that configuration files and users are in
# place.
diff --git a/spec/integration/application/doc_spec.rb b/spec/integration/application/doc_spec.rb
index 77fc38625..040dde72d 100755
--- a/spec/integration/application/doc_spec.rb
+++ b/spec/integration/application/doc_spec.rb
@@ -35,11 +35,12 @@ describe Puppet::Application::Doc do
end
puppet = Puppet::Application[:doc]
- Puppet[:modulepath] = modules_dir
- Puppet[:manifest] = site_file
puppet.options[:mode] = :rdoc
- expect { puppet.run_command }.to exit_with 0
+ env = Puppet::Node::Environment.create(:rdoc, [modules_dir], site_file)
+ Puppet.override(:current_environment => env) do
+ expect { puppet.run_command }.to exit_with 0
+ end
Puppet::FileSystem.exist?('doc').should be_true
ensure
diff --git a/spec/integration/configurer_spec.rb b/spec/integration/configurer_spec.rb
index 2e0c7370e..be1f34ca3 100755
--- a/spec/integration/configurer_spec.rb
+++ b/spec/integration/configurer_spec.rb
@@ -6,20 +6,6 @@ require 'puppet/configurer'
describe Puppet::Configurer do
include PuppetSpec::Files
- describe "when downloading plugins" do
- it "should use the :pluginsignore setting, split on whitespace, for ignoring remote files" do
- Puppet.settings.stubs(:use)
- resource = Puppet::Type.type(:notify).new :name => "yay"
- Puppet::Type.type(:file).expects(:new).at_most(2).with do |args|
- args[:ignore] == Puppet[:pluginsignore].split(/\s+/)
- end.returns resource
-
- configurer = Puppet::Configurer.new
- configurer.stubs(:download_plugins?).returns true
- configurer.download_plugins(Puppet::Node::Environment.remote(:testing))
- end
- end
-
describe "when running" do
before(:each) do
@catalog = Puppet::Resource::Catalog.new("testing", Puppet.lookup(:environments).get(Puppet[:environment]))
diff --git a/spec/integration/defaults_spec.rb b/spec/integration/defaults_spec.rb
index 8c8432b6f..734785230 100755
--- a/spec/integration/defaults_spec.rb
+++ b/spec/integration/defaults_spec.rb
@@ -5,6 +5,32 @@ require 'puppet/defaults'
require 'puppet/rails'
describe "Puppet defaults" do
+
+ describe "when default_manifest is set" do
+ it "returns ./manifests by default" do
+ expect(Puppet[:default_manifest]).to eq('./manifests')
+ end
+
+ it "errors when $environment is part of the value" do
+ expect {
+ Puppet[:default_manifest] = '/$environment/manifest.pp'
+ }.to raise_error Puppet::Settings::ValidationError, /cannot interpolate.*\$environment/
+ end
+ end
+
+ describe "when disable_per_environment_manifest is set" do
+ it "returns false by default" do
+ expect(Puppet[:disable_per_environment_manifest]).to eq(false)
+ end
+
+ it "errors when set to true and default_manifest is not an absolute path" do
+ expect {
+ Puppet[:default_manifest] = './some/relative/manifest.pp'
+ Puppet[:disable_per_environment_manifest] = true
+ }.to raise_error Puppet::Settings::ValidationError, /'default_manifest' setting must be.*absolute/
+ end
+ end
+
describe "when setting the :factpath" do
it "should add the :factpath to Facter's search paths" do
Facter.expects(:search).with("/my/fact/path")
diff --git a/spec/integration/environments/default_manifest_spec.rb b/spec/integration/environments/default_manifest_spec.rb
new file mode 100644
index 000000000..6d4564037
--- /dev/null
+++ b/spec/integration/environments/default_manifest_spec.rb
@@ -0,0 +1,274 @@
+require 'spec_helper'
+
+module EnvironmentsDefaultManifestsSpec
+describe "default manifests" do
+ FS = Puppet::FileSystem
+
+ shared_examples_for "puppet with default_manifest settings" do
+ let(:confdir) { Puppet[:confdir] }
+ let(:environmentpath) { File.expand_path("envdir", confdir) }
+
+ context "relative default" do
+ let(:testingdir) { File.join(environmentpath, "testing") }
+
+ before(:each) do
+ FileUtils.mkdir_p(testingdir)
+ end
+
+ it "reads manifest from ./manifest of a basic directory environment" do
+ manifestsdir = File.join(testingdir, "manifests")
+ FileUtils.mkdir_p(manifestsdir)
+
+ File.open(File.join(manifestsdir, "site.pp"), "w") do |f|
+ f.puts("notify { 'ManifestFromRelativeDefault': }")
+ end
+
+ File.open(File.join(confdir, "puppet.conf"), "w") do |f|
+ f.puts("environmentpath=#{environmentpath}")
+ end
+
+ expect(a_catalog_compiled_for_environment('testing')).to(
+ include_resource('Notify[ManifestFromRelativeDefault]')
+ )
+ end
+ end
+
+ context "set absolute" do
+ let(:testingdir) { File.join(environmentpath, "testing") }
+
+ before(:each) do
+ FileUtils.mkdir_p(testingdir)
+ end
+
+ it "reads manifest from an absolute default_manifest" do
+ manifestsdir = File.expand_path("manifests", confdir)
+ FileUtils.mkdir_p(manifestsdir)
+
+ File.open(File.join(confdir, "puppet.conf"), "w") do |f|
+ f.puts(<<-EOF)
+ environmentpath=#{environmentpath}
+ default_manifest=#{manifestsdir}
+ EOF
+ end
+
+ File.open(File.join(manifestsdir, "site.pp"), "w") do |f|
+ f.puts("notify { 'ManifestFromAbsoluteDefaultManifest': }")
+ end
+
+ expect(a_catalog_compiled_for_environment('testing')).to(
+ include_resource('Notify[ManifestFromAbsoluteDefaultManifest]')
+ )
+ end
+
+ it "reads manifest from directory environment manifest when environment.conf manifest set" do
+ default_manifestsdir = File.expand_path("manifests", confdir)
+ File.open(File.join(confdir, "puppet.conf"), "w") do |f|
+ f.puts(<<-EOF)
+ environmentpath=#{environmentpath}
+ default_manifest=#{default_manifestsdir}
+ EOF
+ end
+
+ manifestsdir = File.join(testingdir, "special_manifests")
+ FileUtils.mkdir_p(manifestsdir)
+
+ File.open(File.join(manifestsdir, "site.pp"), "w") do |f|
+ f.puts("notify { 'ManifestFromEnvironmentConfManifest': }")
+ end
+
+ File.open(File.join(testingdir, "environment.conf"), "w") do |f|
+ f.puts("manifest=./special_manifests")
+ end
+
+ expect(a_catalog_compiled_for_environment('testing')).to(
+ include_resource('Notify[ManifestFromEnvironmentConfManifest]')
+ )
+ expect(Puppet[:default_manifest]).to eq(default_manifestsdir)
+ end
+
+ it "ignores manifests in the local ./manifests if default_manifest specifies another directory" do
+ default_manifestsdir = File.expand_path("manifests", confdir)
+ FileUtils.mkdir_p(default_manifestsdir)
+
+ File.open(File.join(confdir, "puppet.conf"), "w") do |f|
+ f.puts(<<-EOF)
+ environmentpath=#{environmentpath}
+ default_manifest=#{default_manifestsdir}
+ EOF
+ end
+
+ File.open(File.join(default_manifestsdir, "site.pp"), "w") do |f|
+ f.puts("notify { 'ManifestFromAbsoluteDefaultManifest': }")
+ end
+
+ implicit_manifestsdir = File.join(testingdir, "manifests")
+ FileUtils.mkdir_p(implicit_manifestsdir)
+
+ File.open(File.join(implicit_manifestsdir, "site.pp"), "w") do |f|
+ f.puts("notify { 'ManifestFromImplicitRelativeEnvironmentManifestDirectory': }")
+ end
+
+ expect(a_catalog_compiled_for_environment('testing')).to(
+ include_resource('Notify[ManifestFromAbsoluteDefaultManifest]')
+ )
+ end
+
+ it "raises an exception if default_manifest has $environment in it" do
+ File.open(File.join(confdir, "puppet.conf"), "w") do |f|
+ f.puts(<<-EOF)
+ environmentpath=#{environmentpath}
+ default_manifest=/foo/$environment
+ EOF
+ end
+
+ expect { Puppet.initialize_settings }.to raise_error(Puppet::Settings::ValidationError, /cannot interpolate.*\$environment.*in.*default_manifest/)
+ end
+ end
+
+ context "with disable_per_environment_manifest true" do
+ let(:manifestsdir) { File.expand_path("manifests", confdir) }
+ let(:testingdir) { File.join(environmentpath, "testing") }
+
+ before(:each) do
+ FileUtils.mkdir_p(testingdir)
+ end
+
+ before(:each) do
+ FileUtils.mkdir_p(manifestsdir)
+
+ File.open(File.join(confdir, "puppet.conf"), "w") do |f|
+ f.puts(<<-EOF)
+ environmentpath=#{environmentpath}
+ default_manifest=#{manifestsdir}
+ disable_per_environment_manifest=true
+ EOF
+ end
+
+ File.open(File.join(manifestsdir, "site.pp"), "w") do |f|
+ f.puts("notify { 'ManifestFromAbsoluteDefaultManifest': }")
+ end
+ end
+
+ it "reads manifest from the default manifest setting" do
+ expect(a_catalog_compiled_for_environment('testing')).to(
+ include_resource('Notify[ManifestFromAbsoluteDefaultManifest]')
+ )
+ end
+
+ it "refuses to compile if environment.conf specifies a different manifest" do
+ File.open(File.join(testingdir, "environment.conf"), "w") do |f|
+ f.puts("manifest=./special_manifests")
+ end
+
+ expect { a_catalog_compiled_for_environment('testing') }.to(
+ raise_error(Puppet::Error, /disable_per_environment_manifest.*environment.conf.*manifest.*conflict/)
+ )
+ end
+
+ it "reads manifest from default_manifest setting when environment.conf has manifest set if setting equals default_manifest setting" do
+ File.open(File.join(testingdir, "environment.conf"), "w") do |f|
+ f.puts("manifest=#{manifestsdir}")
+ end
+
+ expect(a_catalog_compiled_for_environment('testing')).to(
+ include_resource('Notify[ManifestFromAbsoluteDefaultManifest]')
+ )
+ end
+
+ it "logs errors if environment.conf specifies a different manifest" do
+ File.open(File.join(testingdir, "environment.conf"), "w") do |f|
+ f.puts("manifest=./special_manifests")
+ end
+
+ Puppet.initialize_settings
+ expect(Puppet[:environmentpath]).to eq(environmentpath)
+ environment = Puppet.lookup(:environments).get('testing')
+ expect(environment.manifest).to eq(manifestsdir)
+ expect(@logs.first.to_s).to match(%r{disable_per_environment_manifest.*is true, but.*environment.*at #{testingdir}.*has.*environment.conf.*manifest.*#{testingdir}/special_manifests})
+ end
+
+ it "raises an error if default_manifest is not absolute" do
+ File.open(File.join(confdir, "puppet.conf"), "w") do |f|
+ f.puts(<<-EOF)
+ environmentpath=#{environmentpath}
+ default_manifest=./relative
+ disable_per_environment_manifest=true
+ EOF
+ end
+
+ expect { Puppet.initialize_settings }.to raise_error(Puppet::Settings::ValidationError, /default_manifest.*must be.*absolute.*when.*disable_per_environment_manifest.*true/)
+ end
+ end
+
+ context "in legacy environments" do
+ let(:environmentpath) { '' }
+ let(:manifestsdir) { File.expand_path("default_manifests", confdir) }
+ let(:legacy_manifestsdir) { File.expand_path('manifests', confdir) }
+
+ before(:each) do
+ FileUtils.mkdir_p(manifestsdir)
+
+ File.open(File.join(confdir, "puppet.conf"), "w") do |f|
+ f.puts(<<-EOF)
+ default_manifest=#{manifestsdir}
+ disable_per_environment_manifest=true
+ manifest=#{legacy_manifestsdir}
+ EOF
+ end
+
+ File.open(File.join(manifestsdir, "site.pp"), "w") do |f|
+ f.puts("notify { 'ManifestFromAbsoluteDefaultManifest': }")
+ end
+ end
+
+ it "has no effect on compilation" do
+ FileUtils.mkdir_p(legacy_manifestsdir)
+
+ File.open(File.join(legacy_manifestsdir, "site.pp"), "w") do |f|
+ f.puts("notify { 'ManifestFromLegacy': }")
+ end
+
+ expect(a_catalog_compiled_for_environment('testing')).to(
+ include_resource('Notify[ManifestFromLegacy]')
+ )
+ end
+ end
+ end
+
+ describe 'using future parser' do
+ before :each do
+ Puppet[:parser] = 'future'
+ end
+ it_behaves_like 'puppet with default_manifest settings'
+ end
+
+ describe 'using current parser' do
+ before :each do
+ Puppet[:parser] = 'current'
+ end
+ it_behaves_like 'puppet with default_manifest settings'
+ end
+
+ RSpec::Matchers.define :include_resource do |expected|
+ match do |actual|
+ actual.resources.map(&:ref).include?(expected)
+ end
+
+ def failure_message_for_should
+ "expected #{@actual.resources.map(&:ref)} to include #{expected}"
+ end
+
+ def failure_message_for_should_not
+ "expected #{@actual.resources.map(&:ref)} not to include #{expected}"
+ end
+ end
+
+ def a_catalog_compiled_for_environment(envname)
+ Puppet.initialize_settings
+ expect(Puppet[:environmentpath]).to eq(environmentpath)
+ node = Puppet::Node.new('testnode', :environment => 'testing')
+ expect(node.environment).to eq(Puppet.lookup(:environments).get('testing'))
+ Puppet::Parser::Compiler.compile(node)
+ end
+end
+end
diff --git a/spec/integration/faces/documentation_spec.rb b/spec/integration/faces/documentation_spec.rb
index bd1f8008a..803d61599 100755
--- a/spec/integration/faces/documentation_spec.rb
+++ b/spec/integration/faces/documentation_spec.rb
@@ -16,10 +16,6 @@ describe "documentation of faces" do
# bug in it, triggered in something the user might do.
context "face help messages" do
- # we need to set a bunk module path here, because without doing so,
- # the autoloader will try to use it before it is initialized.
- Puppet[:modulepath] = "/dev/null"
-
Puppet::Face.faces.sort.each do |face_name|
# REVISIT: We should walk all versions of the face here...
let :help do Puppet::Face[:help, :current] end
diff --git a/spec/integration/file_bucket/file_spec.rb b/spec/integration/file_bucket/file_spec.rb
index 2c411fdf7..f0dbecaa3 100644
--- a/spec/integration/file_bucket/file_spec.rb
+++ b/spec/integration/file_bucket/file_spec.rb
@@ -41,4 +41,25 @@ describe Puppet::FileBucket::File do
end
end
end
+
+ describe "saving binary files" do
+ describe "on Ruby 1.8.7", :if => RUBY_VERSION.match(/^1\.8/) do
+ let(:binary) { "\xD1\xF2\r\n\x81NuSc\x00" }
+
+ it "does not error when the same contents are saved twice" do
+ bucket_file = Puppet::FileBucket::File.new(binary)
+ Puppet::FileBucket::File.indirection.save(bucket_file, bucket_file.name)
+ Puppet::FileBucket::File.indirection.save(bucket_file, bucket_file.name)
+ end
+ end
+ describe "on Ruby 1.9+", :if => RUBY_VERSION.match(/^1\.9|^2/) do
+ let(:binary) { "\xD1\xF2\r\n\x81NuSc\x00".force_encoding(Encoding::ASCII_8BIT) }
+
+ it "does not error when the same contents are saved twice" do
+ bucket_file = Puppet::FileBucket::File.new(binary)
+ Puppet::FileBucket::File.indirection.save(bucket_file, bucket_file.name)
+ Puppet::FileBucket::File.indirection.save(bucket_file, bucket_file.name)
+ end
+ end
+ end
end
diff --git a/spec/integration/indirector/catalog/compiler_spec.rb b/spec/integration/indirector/catalog/compiler_spec.rb
index 1e7f17298..11c448575 100755
--- a/spec/integration/indirector/catalog/compiler_spec.rb
+++ b/spec/integration/indirector/catalog/compiler_spec.rb
@@ -13,8 +13,6 @@ describe Puppet::Resource::Catalog::Compiler do
@catalog.add_resource(@two = Puppet::Resource.new(:file, "/two"))
end
- after { Puppet.settings.clear }
-
it "should remove virtual resources when filtering" do
@one.virtual = true
Puppet::Resource::Catalog.indirection.terminus.filter(@catalog).resource_refs.should == [ @two.ref ]
diff --git a/spec/integration/indirector/catalog/queue_spec.rb b/spec/integration/indirector/catalog/queue_spec.rb
index fe359236f..00f3f2e53 100755
--- a/spec/integration/indirector/catalog/queue_spec.rb
+++ b/spec/integration/indirector/catalog/queue_spec.rb
@@ -17,8 +17,6 @@ describe "Puppet::Resource::Catalog::Queue" do
Puppet[:trace] = true
end
- after { Puppet.settings.clear }
-
it "should render catalogs to pson and publish them via the queue client when catalogs are saved" do
terminus = Puppet::Resource::Catalog.indirection.terminus(:queue)
diff --git a/spec/integration/indirector/facts/facter_spec.rb b/spec/integration/indirector/facts/facter_spec.rb
index c71ff0937..b552431f9 100644
--- a/spec/integration/indirector/facts/facter_spec.rb
+++ b/spec/integration/indirector/facts/facter_spec.rb
@@ -13,7 +13,7 @@ describe Puppet::Node::Facts::Facter do
end
end
- Facter.stubs(:clear)
+ Facter.stubs(:reset)
cat = compile_to_catalog('notify { $downcase_test: }',
Puppet::Node.indirection.find('foo'))
diff --git a/spec/integration/indirector/file_content/file_server_spec.rb b/spec/integration/indirector/file_content/file_server_spec.rb
index 103a028a2..ee0db17a9 100755
--- a/spec/integration/indirector/file_content/file_server_spec.rb
+++ b/spec/integration/indirector/file_content/file_server_spec.rb
@@ -48,9 +48,9 @@ describe Puppet::Indirector::FileContent::FileServer, " when finding files" do
file = File.join(modpath, "files", "myfile")
File.open(file, "wb") { |f| f.write "1\r\n" }
- Puppet.settings[:modulepath] = path
+ env = Puppet::Node::Environment.create(:foo, [path])
- result = Puppet::FileServing::Content.indirection.find("modules/mymod/myfile")
+ result = Puppet::FileServing::Content.indirection.find("modules/mymod/myfile", :environment => env)
result.should_not be_nil
result.should be_instance_of(Puppet::FileServing::Content)
diff --git a/spec/integration/node/environment_spec.rb b/spec/integration/node/environment_spec.rb
index f8a7ace7d..797e4105c 100755
--- a/spec/integration/node/environment_spec.rb
+++ b/spec/integration/node/environment_spec.rb
@@ -80,6 +80,30 @@ describe Puppet::Node::Environment do
end
end
+ shared_examples_for "the environment's initial import in the future" do |settings|
+ it "a manifest referring to a directory invokes recursive parsing of all its files in sorted order" do
+ settings.each do |name, value|
+ Puppet[name] = value
+ end
+
+ # fixture has three files 00_a.pp, 01_b.pp, and 02_c.pp. The 'b' file
+ # depends on 'a' being evaluated first. The 'c' file is empty (to ensure
+ # empty things do not break the directory import).
+ #
+ dirname = my_fixture('sitedir2')
+
+ # Set the manifest to the directory to make it parse and combine them when compiling
+ node = Puppet::Node.new('testnode',
+ :environment => Puppet::Node::Environment.create(:testing, [], dirname))
+
+ catalog = Puppet::Parser::Compiler.compile(node)
+
+ expect(catalog).to have_resource('Class[A]')
+ expect(catalog).to have_resource('Class[B]')
+ expect(catalog).to have_resource('Notify[variables]').with_parameter(:message, "a: 10, b: 10 c: 20")
+ end
+ end
+
describe 'using classic parser' do
it_behaves_like "the environment's initial import",
:parser => 'current',
@@ -92,18 +116,10 @@ describe Puppet::Node::Environment do
describe 'using future parser' do
it_behaves_like "the environment's initial import",
:parser => 'future',
- :evaluator => 'future',
# Turned off because currently future parser turns on the binder which
# causes lookup of facts that are uninitialized and it will fail with
# errors for 'osfamily' etc. This can be turned back on when the binder
# is taken out of the equation.
:strict_variables => false
-
- context 'and evaluator current' do
- it_behaves_like "the environment's initial import",
- :parser => 'future',
- :evaluator => 'current',
- :strict_variables => false
- end
end
end
diff --git a/spec/integration/parser/catalog_spec.rb b/spec/integration/parser/catalog_spec.rb
index e37eb591a..39aeb394e 100644
--- a/spec/integration/parser/catalog_spec.rb
+++ b/spec/integration/parser/catalog_spec.rb
@@ -75,6 +75,14 @@ describe "A catalog" do
expect(resources_in(agent_catalog)).to_not include(*exported_resources)
end
end
+ end
+
+ describe 'using classic parser' do
+ before :each do
+ Puppet[:parser] = 'current'
+ end
+ it_behaves_like 'when compiled' do
+ end
it "compiles resource creation from appended array as two separate resources" do
# moved here from acceptance test "jeff_append_to_array.rb"
@@ -92,14 +100,6 @@ describe "A catalog" do
end
end
- describe 'using classic parser' do
- before :each do
- Puppet[:parser] = 'current'
- end
- it_behaves_like 'when compiled' do
- end
- end
-
describe 'using future parser' do
before :each do
Puppet[:parser] = 'future'
@@ -113,9 +113,9 @@ describe "A catalog" do
end
def master_and_agent_catalogs_for(manifest)
- master_catalog = Puppet::Resource::Catalog::Compiler.new.filter(compile_to_catalog(manifest))
+ compiler = Puppet::Resource::Catalog::Compiler.new
+ master_catalog = compiler.filter(compile_to_catalog(manifest))
agent_catalog = Puppet::Resource::Catalog.convert_from(:pson, master_catalog.render(:pson))
-
[master_catalog, agent_catalog]
end
diff --git a/spec/integration/parser/class_spec.rb b/spec/integration/parser/class_spec.rb
new file mode 100644
index 000000000..9f63eb083
--- /dev/null
+++ b/spec/integration/parser/class_spec.rb
@@ -0,0 +1,37 @@
+require 'spec_helper'
+require 'puppet_spec/language'
+
+describe "Class expressions" do
+ extend PuppetSpec::Language
+
+ before :each do
+ Puppet[:parser] = 'future'
+ end
+
+ produces(
+ "class hi { }" => '!defined(Class[Hi])',
+
+ "class hi { } include hi" => 'defined(Class[Hi])',
+ "include(hi) class hi { }" => 'defined(Class[Hi])',
+
+ "class hi { } class { hi: }" => 'defined(Class[Hi])',
+ "class { hi: } class hi { }" => 'defined(Class[Hi])',
+
+ "class bye { } class hi inherits bye { } include hi" => 'defined(Class[Hi]) and defined(Class[Bye])')
+
+ produces(<<-EXAMPLE => 'defined(Notify[foo]) and defined(Notify[bar]) and !defined(Notify[foo::bar])')
+ class bar { notify { 'bar': } }
+ class foo::bar { notify { 'foo::bar': } }
+ class foo inherits bar { notify { 'foo': } }
+
+ include foo
+ EXAMPLE
+
+ produces(<<-EXAMPLE => 'defined(Notify[foo]) and defined(Notify[bar]) and !defined(Notify[foo::bar])')
+ class bar { notify { 'bar': } }
+ class foo::bar { notify { 'foo::bar': } }
+ class foo inherits ::bar { notify { 'foo': } }
+
+ include foo
+ EXAMPLE
+end
diff --git a/spec/integration/parser/collector_spec.rb b/spec/integration/parser/collector_spec.rb
index 49ce74583..55ac66a5b 100755
--- a/spec/integration/parser/collector_spec.rb
+++ b/spec/integration/parser/collector_spec.rb
@@ -14,104 +14,263 @@ describe Puppet::Parser::Collector do
messages.should include(*expected_messages)
end
- it "matches on title" do
- expect_the_message_to_be(["the message"], <<-MANIFEST)
- @notify { "testing": message => "the message" }
+ shared_examples_for "virtual resource collection" do
+ it "matches everything when no query given" do
+ expect_the_message_to_be(["the other message", "the message"], <<-MANIFEST)
+ @notify { "testing": message => "the message" }
+ @notify { "other": message => "the other message" }
- Notify <| title == "testing" |>
- MANIFEST
- end
+ Notify <| |>
+ MANIFEST
+ end
- it "matches on other parameters" do
- expect_the_message_to_be(["the message"], <<-MANIFEST)
- @notify { "testing": message => "the message" }
- @notify { "other testing": message => "the wrong message" }
+ it "matches regular resources " do
+ expect_the_message_to_be(["changed", "changed"], <<-MANIFEST)
+ notify { "testing": message => "the message" }
+ notify { "other": message => "the other message" }
- Notify <| message == "the message" |>
- MANIFEST
- end
+ Notify <| |> { message => "changed" }
+ MANIFEST
+ end
- it "allows criteria to be combined with 'and'" do
- expect_the_message_to_be(["the message"], <<-MANIFEST)
- @notify { "testing": message => "the message" }
- @notify { "other": message => "the message" }
+ it "matches on tags" do
+ expect_the_message_to_be(["wanted"], <<-MANIFEST)
+ @notify { "testing": tag => ["one"], message => "wanted" }
+ @notify { "other": tag => ["two"], message => "unwanted" }
- Notify <| title == "testing" and message == "the message" |>
- MANIFEST
- end
+ Notify <| tag == one |>
+ MANIFEST
+ end
- it "allows criteria to be combined with 'or'" do
- expect_the_message_to_be(["the message", "other message"], <<-MANIFEST)
- @notify { "testing": message => "the message" }
- @notify { "other": message => "other message" }
- @notify { "yet another": message => "different message" }
+ it "matches on title" do
+ expect_the_message_to_be(["the message"], <<-MANIFEST)
+ @notify { "testing": message => "the message" }
- Notify <| title == "testing" or message == "other message" |>
- MANIFEST
- end
+ Notify <| title == "testing" |>
+ MANIFEST
+ end
- it "allows criteria to be combined with 'or'" do
- expect_the_message_to_be(["the message", "other message"], <<-MANIFEST)
- @notify { "testing": message => "the message" }
- @notify { "other": message => "other message" }
- @notify { "yet another": message => "different message" }
+ it "matches on other parameters" do
+ expect_the_message_to_be(["the message"], <<-MANIFEST)
+ @notify { "testing": message => "the message" }
+ @notify { "other testing": message => "the wrong message" }
- Notify <| title == "testing" or message == "other message" |>
- MANIFEST
- end
+ Notify <| message == "the message" |>
+ MANIFEST
+ end
- it "allows criteria to be grouped with parens" do
- expect_the_message_to_be(["the message", "different message"], <<-MANIFEST)
- @notify { "testing": message => "different message", withpath => true }
- @notify { "other": message => "the message" }
- @notify { "yet another": message => "the message", withpath => true }
+ it "matches against elements of an array valued parameter" do
+ expect_the_message_to_be([["the", "message"]], <<-MANIFEST)
+ @notify { "testing": message => ["the", "message"] }
+ @notify { "other testing": message => ["not", "here"] }
- Notify <| (title == "testing" or message == "the message") and withpath == true |>
- MANIFEST
- end
+ Notify <| message == "message" |>
+ MANIFEST
+ end
- it "does not do anything if nothing matches" do
- expect_the_message_to_be([], <<-MANIFEST)
- @notify { "testing": message => "different message" }
+ it "allows criteria to be combined with 'and'" do
+ expect_the_message_to_be(["the message"], <<-MANIFEST)
+ @notify { "testing": message => "the message" }
+ @notify { "other": message => "the message" }
- Notify <| title == "does not exist" |>
- MANIFEST
- end
+ Notify <| title == "testing" and message == "the message" |>
+ MANIFEST
+ end
- it "excludes items with inequalities" do
- expect_the_message_to_be(["good message"], <<-MANIFEST)
- @notify { "testing": message => "good message" }
- @notify { "the wrong one": message => "bad message" }
+ it "allows criteria to be combined with 'or'" do
+ expect_the_message_to_be(["the message", "other message"], <<-MANIFEST)
+ @notify { "testing": message => "the message" }
+ @notify { "other": message => "other message" }
+ @notify { "yet another": message => "different message" }
- Notify <| title != "the wrong one" |>
- MANIFEST
- end
+ Notify <| title == "testing" or message == "other message" |>
+ MANIFEST
+ end
- context "issue #10963" do
- it "collects with override when inside a class" do
- expect_the_message_to_be(["overridden message"], <<-MANIFEST)
- @notify { "testing": message => "original message" }
+ it "allows criteria to be combined with 'or'" do
+ expect_the_message_to_be(["the message", "other message"], <<-MANIFEST)
+ @notify { "testing": message => "the message" }
+ @notify { "other": message => "other message" }
+ @notify { "yet another": message => "different message" }
- include collector_test
- class collector_test {
- Notify <| |> {
- message => "overridden message"
- }
- }
+ Notify <| title == "testing" or message == "other message" |>
+ MANIFEST
+ end
+
+ it "allows criteria to be grouped with parens" do
+ expect_the_message_to_be(["the message", "different message"], <<-MANIFEST)
+ @notify { "testing": message => "different message", withpath => true }
+ @notify { "other": message => "the message" }
+ @notify { "yet another": message => "the message", withpath => true }
+
+ Notify <| (title == "testing" or message == "the message") and withpath == true |>
MANIFEST
end
- it "collects with override when inside a define" do
- expect_the_message_to_be(["overridden message"], <<-MANIFEST)
- @notify { "testing": message => "original message" }
+ it "does not do anything if nothing matches" do
+ expect_the_message_to_be([], <<-MANIFEST)
+ @notify { "testing": message => "different message" }
+
+ Notify <| title == "does not exist" |>
+ MANIFEST
+ end
+
+ it "excludes items with inequalities" do
+ expect_the_message_to_be(["good message"], <<-MANIFEST)
+ @notify { "testing": message => "good message" }
+ @notify { "the wrong one": message => "bad message" }
+
+ Notify <| title != "the wrong one" |>
+ MANIFEST
+ end
+
+ it "does not exclude resources with unequal arrays" do
+ expect_the_message_to_be(["message", ["not this message", "or this one"]], <<-MANIFEST)
+ @notify { "testing": message => "message" }
+ @notify { "the wrong one": message => ["not this message", "or this one"] }
+
+ Notify <| message != "not this message" |>
+ MANIFEST
+ end
+
+ it "does not exclude tags with inequalities" do
+ expect_the_message_to_be(["wanted message", "the way it works"], <<-MANIFEST)
+ @notify { "testing": tag => ["wanted"], message => "wanted message" }
+ @notify { "other": tag => ["why"], message => "the way it works" }
+
+ Notify <| tag != "why" |>
+ MANIFEST
+ end
+
+ it "does not collect classes" do
+ node = Puppet::Node.new('the node')
+ expect do
+ catalog = compile_to_catalog(<<-MANIFEST, node)
+ class theclass {
+ @notify { "testing": message => "good message" }
+ }
+ Class <| |>
+ MANIFEST
+ end.to raise_error(/Classes cannot be collected/)
+ end
+
+ context "overrides" do
+ it "modifies an existing array" do
+ expect_the_message_to_be([["original message", "extra message"]], <<-MANIFEST)
+ @notify { "testing": message => ["original message"] }
- collector_test { testing: }
- define collector_test() {
Notify <| |> {
- message => "overridden message"
+ message +> "extra message"
}
- }
- MANIFEST
+ MANIFEST
+ end
+
+ it "converts a scalar to an array" do
+ expect_the_message_to_be([["original message", "extra message"]], <<-MANIFEST)
+ @notify { "testing": message => "original message" }
+
+ Notify <| |> {
+ message +> "extra message"
+ }
+ MANIFEST
+ end
+
+ it "collects with override when inside a class (#10963)" do
+ expect_the_message_to_be(["overridden message"], <<-MANIFEST)
+ @notify { "testing": message => "original message" }
+
+ include collector_test
+ class collector_test {
+ Notify <| |> {
+ message => "overridden message"
+ }
+ }
+ MANIFEST
+ end
+
+ it "collects with override when inside a define (#10963)" do
+ expect_the_message_to_be(["overridden message"], <<-MANIFEST)
+ @notify { "testing": message => "original message" }
+
+ collector_test { testing: }
+ define collector_test() {
+ Notify <| |> {
+ message => "overridden message"
+ }
+ }
+ MANIFEST
+ end
+
+ # Catches regression in implemented behavior, this is not to be taken as this is the wanted behavior
+ # but it has been this way for a long time.
+ it "collects and overrides user defined resources immediately (before queue is evaluated)" do
+ expect_the_message_to_be(["overridden"], <<-MANIFEST)
+ define foo($message) {
+ notify { "testing": message => $message }
+ }
+ foo { test: message => 'given' }
+ Foo <| |> { message => 'overridden' }
+ MANIFEST
+ end
+
+ # Catches regression in implemented behavior, this is not to be taken as this is the wanted behavior
+ # but it has been this way for a long time.
+ it "collects and overrides user defined resources immediately (virtual resources not queued)" do
+ expect_the_message_to_be(["overridden"], <<-MANIFEST)
+ define foo($message) {
+ @notify { "testing": message => $message }
+ }
+ foo { test: message => 'given' }
+ Notify <| |> # must be collected or the assertion does not find it
+ Foo <| |> { message => 'overridden' }
+ MANIFEST
+ end
+
+ # Catches regression in implemented behavior, this is not to be taken as this is the wanted behavior
+ # but it has been this way for a long time.
+ # Note difference from none +> case where the override takes effect
+ it "collects and overrides user defined resources with +>" do
+ expect_the_message_to_be([["given", "overridden"]], <<-MANIFEST)
+ define foo($message) {
+ notify { "$name": message => $message }
+ }
+ foo { test: message => ['given'] }
+ Notify <| |> { message +> ['overridden'] }
+ MANIFEST
+ end
+
+ it "collects and overrides virtual resources multiple times using multiple collects" do
+ expect_the_message_to_be(["overridden2"], <<-MANIFEST)
+ @notify { "testing": message => "original" }
+ Notify <| |> { message => 'overridden1' }
+ Notify <| |> { message => 'overridden2' }
+ MANIFEST
+ end
+
+ it "collects and overrides non virtual resources multiple times using multiple collects" do
+ expect_the_message_to_be(["overridden2"], <<-MANIFEST)
+ notify { "testing": message => "original" }
+ Notify <| |> { message => 'overridden1' }
+ Notify <| |> { message => 'overridden2' }
+ MANIFEST
+ end
+
end
end
+
+ describe "in the current parser" do
+ before :each do
+ Puppet[:parser] = 'current'
+ end
+
+ it_behaves_like "virtual resource collection"
+ end
+
+ describe "in the future parser" do
+ before :each do
+ Puppet[:parser] = 'future'
+ end
+
+ it_behaves_like "virtual resource collection"
+ end
end
diff --git a/spec/integration/parser/compiler_spec.rb b/spec/integration/parser/compiler_spec.rb
index f10ce1adc..e6069d834 100755
--- a/spec/integration/parser/compiler_spec.rb
+++ b/spec/integration/parser/compiler_spec.rb
@@ -15,499 +15,511 @@ describe "Puppet::Parser::Compiler" do
@scope = stub 'scope', :resource => @scope_resource, :source => mock("source")
end
- after do
- Puppet.settings.clear
- end
-
- # shared because tests are invoked both for classic and future parser
- #
- shared_examples_for "the compiler" do
- it "should be able to determine the configuration version from a local version control repository" do
- pending("Bug #14071 about semantics of Puppet::Util::Execute on Windows", :if => Puppet.features.microsoft_windows?) do
- # This should always work, because we should always be
- # in the puppet repo when we run this.
- version = %x{git rev-parse HEAD}.chomp
+ it "should be able to determine the configuration version from a local version control repository" do
+ pending("Bug #14071 about semantics of Puppet::Util::Execute on Windows", :if => Puppet.features.microsoft_windows?) do
+ # This should always work, because we should always be
+ # in the puppet repo when we run this.
+ version = %x{git rev-parse HEAD}.chomp
- Puppet.settings[:config_version] = 'git rev-parse HEAD'
+ Puppet.settings[:config_version] = 'git rev-parse HEAD'
- @parser = Puppet::Parser::ParserFactory.parser "development"
- @compiler = Puppet::Parser::Compiler.new(@node)
+ @parser = Puppet::Parser::ParserFactory.parser "development"
+ @compiler = Puppet::Parser::Compiler.new(@node)
- @compiler.catalog.version.should == version
- end
+ @compiler.catalog.version.should == version
end
+ end
+
+ it "should not create duplicate resources when a class is referenced both directly and indirectly by the node classifier (4792)" do
+ Puppet[:code] = <<-PP
+ class foo
+ {
+ notify { foo_notify: }
+ include bar
+ }
+ class bar
+ {
+ notify { bar_notify: }
+ }
+ PP
+
+ @node.stubs(:classes).returns(['foo', 'bar'])
+
+ catalog = Puppet::Parser::Compiler.compile(@node)
+
+ catalog.resource("Notify[foo_notify]").should_not be_nil
+ catalog.resource("Notify[bar_notify]").should_not be_nil
+ end
- it "should not create duplicate resources when a class is referenced both directly and indirectly by the node classifier (4792)" do
+ describe "when resolving class references" do
+ it "should favor local scope, even if there's an included class in topscope" do
Puppet[:code] = <<-PP
- class foo
- {
- notify { foo_notify: }
- include bar
+ class experiment {
+ class baz {
+ }
+ notify {"x" : require => Class[Baz] }
}
- class bar
- {
- notify { bar_notify: }
+ class baz {
}
+ include baz
+ include experiment
+ include experiment::baz
PP
- @node.stubs(:classes).returns(['foo', 'bar'])
+ catalog = Puppet::Parser::Compiler.compile(Puppet::Node.new("mynode"))
- catalog = Puppet::Parser::Compiler.compile(@node)
+ notify_resource = catalog.resource( "Notify[x]" )
- catalog.resource("Notify[foo_notify]").should_not be_nil
- catalog.resource("Notify[bar_notify]").should_not be_nil
+ notify_resource[:require].title.should == "Experiment::Baz"
end
- describe "when resolving class references" do
- it "should favor local scope, even if there's an included class in topscope" do
- Puppet[:code] = <<-PP
- class experiment {
- class baz {
- }
- notify {"x" : require => Class[Baz] }
- }
+ it "should favor local scope, even if there's an unincluded class in topscope" do
+ Puppet[:code] = <<-PP
+ class experiment {
class baz {
}
- include baz
- include experiment
- include experiment::baz
- PP
-
- catalog = Puppet::Parser::Compiler.compile(Puppet::Node.new("mynode"))
+ notify {"x" : require => Class[Baz] }
+ }
+ class baz {
+ }
+ include experiment
+ include experiment::baz
+ PP
- notify_resource = catalog.resource( "Notify[x]" )
+ catalog = Puppet::Parser::Compiler.compile(Puppet::Node.new("mynode"))
- notify_resource[:require].title.should == "Experiment::Baz"
- end
+ notify_resource = catalog.resource( "Notify[x]" )
- it "should favor local scope, even if there's an unincluded class in topscope" do
- Puppet[:code] = <<-PP
- class experiment {
- class baz {
+ notify_resource[:require].title.should == "Experiment::Baz"
+ end
+ end
+ describe "(ticket #13349) when explicitly specifying top scope" do
+ ["class {'::bar::baz':}", "include ::bar::baz"].each do |include|
+ describe "with #{include}" do
+ it "should find the top level class" do
+ Puppet[:code] = <<-MANIFEST
+ class { 'foo::test': }
+ class foo::test {
+ #{include}
}
- notify {"x" : require => Class[Baz] }
- }
- class baz {
- }
- include experiment
- include experiment::baz
- PP
-
- catalog = Puppet::Parser::Compiler.compile(Puppet::Node.new("mynode"))
+ class bar::baz {
+ notify { 'good!': }
+ }
+ class foo::bar::baz {
+ notify { 'bad!': }
+ }
+ MANIFEST
- notify_resource = catalog.resource( "Notify[x]" )
+ catalog = Puppet::Parser::Compiler.compile(Puppet::Node.new("mynode"))
- notify_resource[:require].title.should == "Experiment::Baz"
- end
- end
- describe "(ticket #13349) when explicitly specifying top scope" do
- ["class {'::bar::baz':}", "include ::bar::baz"].each do |include|
- describe "with #{include}" do
- it "should find the top level class" do
- Puppet[:code] = <<-MANIFEST
- class { 'foo::test': }
- class foo::test {
- #{include}
- }
- class bar::baz {
- notify { 'good!': }
- }
- class foo::bar::baz {
- notify { 'bad!': }
- }
- MANIFEST
-
- catalog = Puppet::Parser::Compiler.compile(Puppet::Node.new("mynode"))
-
- catalog.resource("Class[Bar::Baz]").should_not be_nil
- catalog.resource("Notify[good!]").should_not be_nil
- catalog.resource("Class[Foo::Bar::Baz]").should be_nil
- catalog.resource("Notify[bad!]").should be_nil
- end
+ catalog.resource("Class[Bar::Baz]").should_not be_nil
+ catalog.resource("Notify[good!]").should_not be_nil
+ catalog.resource("Class[Foo::Bar::Baz]").should be_nil
+ catalog.resource("Notify[bad!]").should be_nil
end
end
end
+ end
- it "should recompute the version after input files are re-parsed" do
- Puppet[:code] = 'class foo { }'
- Time.stubs(:now).returns(1)
- node = Puppet::Node.new('mynode')
- Puppet::Parser::Compiler.compile(node).version.should == 1
- Time.stubs(:now).returns(2)
- Puppet::Parser::Compiler.compile(node).version.should == 1 # no change because files didn't change
- Puppet::Resource::TypeCollection.any_instance.stubs(:stale?).returns(true).then.returns(false) # pretend change
- Puppet::Parser::Compiler.compile(node).version.should == 2
- end
-
- ['class', 'define', 'node'].each do |thing|
- it "should not allow '#{thing}' inside evaluated conditional constructs" do
- Puppet[:code] = <<-PP
- if true {
- #{thing} foo {
- }
- notify { decoy: }
- }
- PP
-
- begin
- Puppet::Parser::Compiler.compile(Puppet::Node.new("mynode"))
- raise "compilation should have raised Puppet::Error"
- rescue Puppet::Error => e
- e.message.should =~ /at line 2/
- end
- end
- end
+ it "should recompute the version after input files are re-parsed" do
+ Puppet[:code] = 'class foo { }'
+ Time.stubs(:now).returns(1)
+ node = Puppet::Node.new('mynode')
+ Puppet::Parser::Compiler.compile(node).version.should == 1
+ Time.stubs(:now).returns(2)
+ Puppet::Parser::Compiler.compile(node).version.should == 1 # no change because files didn't change
+ Puppet::Resource::TypeCollection.any_instance.stubs(:stale?).returns(true).then.returns(false) # pretend change
+ Puppet::Parser::Compiler.compile(node).version.should == 2
+ end
- it "should not allow classes inside unevaluated conditional constructs" do
+ ['class', 'define', 'node'].each do |thing|
+ it "should not allow '#{thing}' inside evaluated conditional constructs" do
Puppet[:code] = <<-PP
- if false {
- class foo {
+ if true {
+ #{thing} foo {
}
+ notify { decoy: }
}
PP
- lambda { Puppet::Parser::Compiler.compile(Puppet::Node.new("mynode")) }.should raise_error(Puppet::Error)
+ begin
+ Puppet::Parser::Compiler.compile(Puppet::Node.new("mynode"))
+ raise "compilation should have raised Puppet::Error"
+ rescue Puppet::Error => e
+ e.message.should =~ /at line 2/
+ end
end
+ end
- describe "when defining relationships" do
- def extract_name(ref)
- ref.sub(/File\[(\w+)\]/, '\1')
- end
+ it "should not allow classes inside unevaluated conditional constructs" do
+ Puppet[:code] = <<-PP
+ if false {
+ class foo {
+ }
+ }
+ PP
- let(:node) { Puppet::Node.new('mynode') }
- let(:code) do
- <<-MANIFEST
- file { [a,b,c]:
- mode => 0644,
- }
- file { [d,e]:
- mode => 0755,
- }
- MANIFEST
- end
- let(:expected_relationships) { [] }
- let(:expected_subscriptions) { [] }
+ lambda { Puppet::Parser::Compiler.compile(Puppet::Node.new("mynode")) }.should raise_error(Puppet::Error)
+ end
- before :each do
- Puppet[:code] = code
- end
+ describe "when defining relationships" do
+ def extract_name(ref)
+ ref.sub(/File\[(\w+)\]/, '\1')
+ end
+
+ let(:node) { Puppet::Node.new('mynode') }
+ let(:code) do
+ <<-MANIFEST
+ file { [a,b,c]:
+ mode => '0644',
+ }
+ file { [d,e]:
+ mode => '0755',
+ }
+ MANIFEST
+ end
+ let(:expected_relationships) { [] }
+ let(:expected_subscriptions) { [] }
- after :each do
- catalog = Puppet::Parser::Compiler.compile(node)
+ before :each do
+ Puppet[:code] = code
+ end
- resources = catalog.resources.select { |res| res.type == 'File' }
+ after :each do
+ catalog = Puppet::Parser::Compiler.compile(node)
- actual_relationships, actual_subscriptions = [:before, :notify].map do |relation|
- resources.map do |res|
- dependents = Array(res[relation])
- dependents.map { |ref| [res.title, extract_name(ref)] }
- end.inject(&:concat)
- end
+ resources = catalog.resources.select { |res| res.type == 'File' }
- actual_relationships.should =~ expected_relationships
- actual_subscriptions.should =~ expected_subscriptions
+ actual_relationships, actual_subscriptions = [:before, :notify].map do |relation|
+ resources.map do |res|
+ dependents = Array(res[relation])
+ dependents.map { |ref| [res.title, extract_name(ref)] }
+ end.inject(&:concat)
end
- it "should create a relationship" do
- code << "File[a] -> File[b]"
+ actual_relationships.should =~ expected_relationships
+ actual_subscriptions.should =~ expected_subscriptions
+ end
- expected_relationships << ['a','b']
- end
+ it "should create a relationship" do
+ code << "File[a] -> File[b]"
- it "should create a subscription" do
- code << "File[a] ~> File[b]"
+ expected_relationships << ['a','b']
+ end
- expected_subscriptions << ['a', 'b']
- end
+ it "should create a subscription" do
+ code << "File[a] ~> File[b]"
- it "should create relationships using title arrays" do
- code << "File[a,b] -> File[c,d]"
+ expected_subscriptions << ['a', 'b']
+ end
- expected_relationships.concat [
- ['a', 'c'],
- ['b', 'c'],
- ['a', 'd'],
- ['b', 'd'],
- ]
- end
+ it "should create relationships using title arrays" do
+ code << "File[a,b] -> File[c,d]"
- it "should create relationships using collection expressions" do
- code << "File <| mode == 0644 |> -> File <| mode == 0755 |>"
-
- expected_relationships.concat [
- ['a', 'd'],
- ['b', 'd'],
- ['c', 'd'],
- ['a', 'e'],
- ['b', 'e'],
- ['c', 'e'],
- ]
- end
+ expected_relationships.concat [
+ ['a', 'c'],
+ ['b', 'c'],
+ ['a', 'd'],
+ ['b', 'd'],
+ ]
+ end
- it "should create relationships using resource names" do
- code << "'File[a]' -> 'File[b]'"
+ it "should create relationships using collection expressions" do
+ code << "File <| mode == 0644 |> -> File <| mode == 0755 |>"
+
+ expected_relationships.concat [
+ ['a', 'd'],
+ ['b', 'd'],
+ ['c', 'd'],
+ ['a', 'e'],
+ ['b', 'e'],
+ ['c', 'e'],
+ ]
+ end
- expected_relationships << ['a', 'b']
- end
+ it "should create relationships using resource names" do
+ code << "'File[a]' -> 'File[b]'"
- it "should create relationships using variables" do
- code << <<-MANIFEST
- $var = File[a]
- $var -> File[b]
- MANIFEST
+ expected_relationships << ['a', 'b']
+ end
- expected_relationships << ['a', 'b']
- end
+ it "should create relationships using variables" do
+ code << <<-MANIFEST
+ $var = File[a]
+ $var -> File[b]
+ MANIFEST
- it "should create relationships using case statements" do
- code << <<-MANIFEST
- $var = 10
- case $var {
- 10: {
- file { s1: }
- }
- 12: {
- file { s2: }
- }
+ expected_relationships << ['a', 'b']
+ end
+
+ it "should create relationships using case statements" do
+ code << <<-MANIFEST
+ $var = 10
+ case $var {
+ 10: {
+ file { s1: }
}
- ->
- case $var + 2 {
- 10: {
- file { t1: }
- }
- 12: {
- file { t2: }
- }
+ 12: {
+ file { s2: }
}
- MANIFEST
+ }
+ ->
+ case $var + 2 {
+ 10: {
+ file { t1: }
+ }
+ 12: {
+ file { t2: }
+ }
+ }
+ MANIFEST
- expected_relationships << ['s1', 't2']
- end
+ expected_relationships << ['s1', 't2']
+ end
- it "should create relationships using array members" do
- code << <<-MANIFEST
- $var = [ [ [ File[a], File[b] ] ] ]
- $var[0][0][0] -> $var[0][0][1]
- MANIFEST
+ it "should create relationships using array members" do
+ code << <<-MANIFEST
+ $var = [ [ [ File[a], File[b] ] ] ]
+ $var[0][0][0] -> $var[0][0][1]
+ MANIFEST
- expected_relationships << ['a', 'b']
- end
+ expected_relationships << ['a', 'b']
+ end
- it "should create relationships using hash members" do
- code << <<-MANIFEST
- $var = {'foo' => {'bar' => {'source' => File[a], 'target' => File[b]}}}
- $var[foo][bar][source] -> $var[foo][bar][target]
- MANIFEST
+ it "should create relationships using hash members" do
+ code << <<-MANIFEST
+ $var = {'foo' => {'bar' => {'source' => File[a], 'target' => File[b]}}}
+ $var[foo][bar][source] -> $var[foo][bar][target]
+ MANIFEST
- expected_relationships << ['a', 'b']
- end
+ expected_relationships << ['a', 'b']
+ end
- it "should create relationships using resource declarations" do
- code << "file { l: } -> file { r: }"
+ it "should create relationships using resource declarations" do
+ code << "file { l: } -> file { r: }"
- expected_relationships << ['l', 'r']
- end
+ expected_relationships << ['l', 'r']
+ end
- it "should chain relationships" do
- code << "File[a] -> File[b] ~> File[c] <- File[d] <~ File[e]"
+ it "should chain relationships" do
+ code << "File[a] -> File[b] ~> File[c] <- File[d] <~ File[e]"
- expected_relationships << ['a', 'b'] << ['d', 'c']
- expected_subscriptions << ['b', 'c'] << ['e', 'd']
- end
+ expected_relationships << ['a', 'b'] << ['d', 'c']
+ expected_subscriptions << ['b', 'c'] << ['e', 'd']
end
+ end
- context 'when working with immutable node data' do
- context 'and have opted in to immutable_node_data' do
- before :each do
- Puppet[:immutable_node_data] = true
- end
+ context 'when working with immutable node data' do
+ context 'and have opted in to immutable_node_data' do
+ before :each do
+ Puppet[:immutable_node_data] = true
+ end
- def node_with_facts(facts)
- Puppet[:facts_terminus] = :memory
- Puppet::Node::Facts.indirection.save(Puppet::Node::Facts.new("testing", facts))
- node = Puppet::Node.new("testing")
- node.fact_merge
- node
- end
+ def node_with_facts(facts)
+ Puppet[:facts_terminus] = :memory
+ Puppet::Node::Facts.indirection.save(Puppet::Node::Facts.new("testing", facts))
+ node = Puppet::Node.new("testing")
+ node.fact_merge
+ node
+ end
- matcher :fail_compile_with do |node, message_regex|
- match do |manifest|
- @error = nil
- begin
- compile_to_catalog(manifest, node)
- false
- rescue Puppet::Error => e
- @error = e
- message_regex.match(e.message)
- end
+ matcher :fail_compile_with do |node, message_regex|
+ match do |manifest|
+ @error = nil
+ begin
+ PuppetSpec::Compiler.compile_to_catalog(manifest, node)
+ false
+ rescue Puppet::Error => e
+ @error = e
+ message_regex.match(e.message)
end
+ end
- failure_message_for_should do
- if @error
- "failed with #{@error}\n#{@error.backtrace}"
- else
- "did not fail"
- end
+ failure_message_for_should do
+ if @error
+ "failed with #{@error}\n#{@error.backtrace}"
+ else
+ "did not fail"
end
end
+ end
- it 'should make $facts available' do
- node = node_with_facts('the_facts' => 'straight')
+ it 'should make $facts available' do
+ node = node_with_facts('the_facts' => 'straight')
- catalog = compile_to_catalog(<<-MANIFEST, node)
- notify { 'test': message => $facts[the_facts] }
- MANIFEST
+ catalog = compile_to_catalog(<<-MANIFEST, node)
+ notify { 'test': message => $facts[the_facts] }
+ MANIFEST
- catalog.resource("Notify[test]")[:message].should == "straight"
- end
+ catalog.resource("Notify[test]")[:message].should == "straight"
+ end
- it 'should make $facts reserved' do
- node = node_with_facts('the_facts' => 'straight')
+ it 'should make $facts reserved' do
+ node = node_with_facts('the_facts' => 'straight')
- expect('$facts = {}').to fail_compile_with(node, /assign to a reserved variable name: 'facts'/)
- expect('class a { $facts = {} } include a').to fail_compile_with(node, /assign to a reserved variable name: 'facts'/)
- end
+ expect('$facts = {}').to fail_compile_with(node, /assign to a reserved variable name: 'facts'/)
+ expect('class a { $facts = {} } include a').to fail_compile_with(node, /assign to a reserved variable name: 'facts'/)
+ end
- it 'should make $facts immutable' do
- node = node_with_facts('string' => 'value', 'array' => ['string'], 'hash' => { 'a' => 'string' }, 'number' => 1, 'boolean' => true)
+ it 'should make $facts immutable' do
+ node = node_with_facts('string' => 'value', 'array' => ['string'], 'hash' => { 'a' => 'string' }, 'number' => 1, 'boolean' => true)
- expect('$i=inline_template("<% @facts[%q{new}] = 2 %>")').to fail_compile_with(node, /frozen Hash/i)
- expect('$i=inline_template("<% @facts[%q{string}].chop! %>")').to fail_compile_with(node, /frozen String/i)
+ expect('$i=inline_template("<% @facts[%q{new}] = 2 %>")').to fail_compile_with(node, /frozen Hash/i)
+ expect('$i=inline_template("<% @facts[%q{string}].chop! %>")').to fail_compile_with(node, /frozen String/i)
- expect('$i=inline_template("<% @facts[%q{array}][0].chop! %>")').to fail_compile_with(node, /frozen String/i)
- expect('$i=inline_template("<% @facts[%q{array}][1] = 2 %>")').to fail_compile_with(node, /frozen Array/i)
+ expect('$i=inline_template("<% @facts[%q{array}][0].chop! %>")').to fail_compile_with(node, /frozen String/i)
+ expect('$i=inline_template("<% @facts[%q{array}][1] = 2 %>")').to fail_compile_with(node, /frozen Array/i)
- expect('$i=inline_template("<% @facts[%q{hash}][%q{a}].chop! %>")').to fail_compile_with(node, /frozen String/i)
- expect('$i=inline_template("<% @facts[%q{hash}][%q{b}] = 2 %>")').to fail_compile_with(node, /frozen Hash/i)
- end
+ expect('$i=inline_template("<% @facts[%q{hash}][%q{a}].chop! %>")').to fail_compile_with(node, /frozen String/i)
+ expect('$i=inline_template("<% @facts[%q{hash}][%q{b}] = 2 %>")').to fail_compile_with(node, /frozen Hash/i)
+ end
- it 'should make $facts available even if there are no facts' do
- Puppet[:facts_terminus] = :memory
- node = Puppet::Node.new("testing2")
- node.fact_merge
+ it 'should make $facts available even if there are no facts' do
+ Puppet[:facts_terminus] = :memory
+ node = Puppet::Node.new("testing2")
+ node.fact_merge
- catalog = compile_to_catalog(<<-MANIFEST, node)
- notify { 'test': message => $facts }
- MANIFEST
+ catalog = compile_to_catalog(<<-MANIFEST, node)
+ notify { 'test': message => $facts }
+ MANIFEST
- expect(catalog).to have_resource("Notify[test]").with_parameter(:message, {})
- end
+ expect(catalog).to have_resource("Notify[test]").with_parameter(:message, {})
end
+ end
- context 'and have not opted in to immutable_node_data' do
- before :each do
- Puppet[:immutable_node_data] = false
- end
+ context 'and have not opted in to immutable_node_data' do
+ before :each do
+ Puppet[:immutable_node_data] = false
+ end
- it 'should not make $facts available' do
- Puppet[:facts_terminus] = :memory
- facts = Puppet::Node::Facts.new("testing", 'the_facts' => 'straight')
- Puppet::Node::Facts.indirection.save(facts)
- node = Puppet::Node.new("testing")
- node.fact_merge
+ it 'should not make $facts available' do
+ Puppet[:facts_terminus] = :memory
+ facts = Puppet::Node::Facts.new("testing", 'the_facts' => 'straight')
+ Puppet::Node::Facts.indirection.save(facts)
+ node = Puppet::Node.new("testing")
+ node.fact_merge
- catalog = compile_to_catalog(<<-MANIFEST, node)
- notify { 'test': message => "An $facts space" }
- MANIFEST
+ catalog = compile_to_catalog(<<-MANIFEST, node)
+ notify { 'test': message => "An $facts space" }
+ MANIFEST
- catalog.resource("Notify[test]")[:message].should == "An space"
- end
+ catalog.resource("Notify[test]")[:message].should == "An space"
end
end
+ end
- context 'when working with the trusted data hash' do
- context 'and have opted in to trusted_node_data' do
- before :each do
- Puppet[:trusted_node_data] = true
- end
-
- it 'should make $trusted available' do
- node = Puppet::Node.new("testing")
- node.trusted_data = { "data" => "value" }
+ context 'when working with the trusted data hash' do
+ context 'and have opted in to trusted_node_data' do
+ before :each do
+ Puppet[:trusted_node_data] = true
+ end
- catalog = compile_to_catalog(<<-MANIFEST, node)
- notify { 'test': message => $trusted[data] }
- MANIFEST
+ it 'should make $trusted available' do
+ node = Puppet::Node.new("testing")
+ node.trusted_data = { "data" => "value" }
- catalog.resource("Notify[test]")[:message].should == "value"
- end
+ catalog = compile_to_catalog(<<-MANIFEST, node)
+ notify { 'test': message => $trusted[data] }
+ MANIFEST
- it 'should not allow assignment to $trusted' do
- node = Puppet::Node.new("testing")
- node.trusted_data = { "data" => "value" }
-
- expect do
- catalog = compile_to_catalog(<<-MANIFEST, node)
- $trusted = 'changed'
- notify { 'test': message => $trusted == 'changed' }
- MANIFEST
- catalog.resource("Notify[test]")[:message].should == true
- end.to raise_error(Puppet::Error, /Attempt to assign to a reserved variable name: 'trusted'/)
- end
+ catalog.resource("Notify[test]")[:message].should == "value"
+ end
- it 'should not allow addition to $trusted hash' do
- node = Puppet::Node.new("testing")
- node.trusted_data = { "data" => "value" }
-
- expect do
- catalog = compile_to_catalog(<<-MANIFEST, node)
- $trusted['extra'] = 'added'
- notify { 'test': message => $trusted['extra'] == 'added' }
- MANIFEST
- catalog.resource("Notify[test]")[:message].should == true
- # different errors depending on regular or future parser
- end.to raise_error(Puppet::Error, /(can't modify frozen [hH]ash)|(Illegal attempt to assign)/)
- end
+ it 'should not allow assignment to $trusted' do
+ node = Puppet::Node.new("testing")
+ node.trusted_data = { "data" => "value" }
- it 'should not allow addition to $trusted hash via Ruby inline template' do
- node = Puppet::Node.new("testing")
- node.trusted_data = { "data" => "value" }
-
- expect do
- catalog = compile_to_catalog(<<-MANIFEST, node)
- $dummy = inline_template("<% @trusted['extra'] = 'added' %> lol")
- notify { 'test': message => $trusted['extra'] == 'added' }
- MANIFEST
- catalog.resource("Notify[test]")[:message].should == true
- end.to raise_error(Puppet::Error, /can't modify frozen [hH]ash/)
- end
+ expect do
+ catalog = compile_to_catalog(<<-MANIFEST, node)
+ $trusted = 'changed'
+ notify { 'test': message => $trusted == 'changed' }
+ MANIFEST
+ catalog.resource("Notify[test]")[:message].should == true
+ end.to raise_error(Puppet::Error, /Attempt to assign to a reserved variable name: 'trusted'/)
end
- context 'and have not opted in to trusted_node_data' do
- before :each do
- Puppet[:trusted_node_data] = false
- end
-
- it 'should not make $trusted available' do
- node = Puppet::Node.new("testing")
- node.trusted_data = { "data" => "value" }
+ it 'should not allow addition to $trusted hash' do
+ node = Puppet::Node.new("testing")
+ node.trusted_data = { "data" => "value" }
+ expect do
catalog = compile_to_catalog(<<-MANIFEST, node)
- notify { 'test': message => $trusted == undef }
+ $trusted['extra'] = 'added'
+ notify { 'test': message => $trusted['extra'] == 'added' }
MANIFEST
-
catalog.resource("Notify[test]")[:message].should == true
- end
+ # different errors depending on regular or future parser
+ end.to raise_error(Puppet::Error, /(can't modify frozen [hH]ash)|(Illegal attempt to assign)/)
+ end
- it 'should allow assignment to $trusted' do
- node = Puppet::Node.new("testing")
+ it 'should not allow addition to $trusted hash via Ruby inline template' do
+ node = Puppet::Node.new("testing")
+ node.trusted_data = { "data" => "value" }
+ expect do
catalog = compile_to_catalog(<<-MANIFEST, node)
- $trusted = 'changed'
- notify { 'test': message => $trusted == 'changed' }
+ $dummy = inline_template("<% @trusted['extra'] = 'added' %> lol")
+ notify { 'test': message => $trusted['extra'] == 'added' }
MANIFEST
-
catalog.resource("Notify[test]")[:message].should == true
- end
+ end.to raise_error(Puppet::Error, /can't modify frozen [hH]ash/)
end
end
- end
- describe 'using classic parser' do
- before :each do
- Puppet[:parser] = 'current'
+ context 'and have not opted in to trusted_node_data' do
+ before :each do
+ Puppet[:trusted_node_data] = false
+ end
+
+ it 'should not make $trusted available' do
+ node = Puppet::Node.new("testing")
+ node.trusted_data = { "data" => "value" }
+
+ catalog = compile_to_catalog(<<-MANIFEST, node)
+ notify { 'test': message => $trusted == undef }
+ MANIFEST
+
+ catalog.resource("Notify[test]")[:message].should == true
+ end
+
+ it 'should allow assignment to $trusted' do
+ node = Puppet::Node.new("testing")
+
+ catalog = compile_to_catalog(<<-MANIFEST, node)
+ $trusted = 'changed'
+ notify { 'test': message => $trusted == 'changed' }
+ MANIFEST
+
+ catalog.resource("Notify[test]")[:message].should == true
+ end
end
- it_behaves_like 'the compiler' do
+ end
+
+ context 'when evaluating collection' do
+ it 'matches on container inherited tags' do
+ Puppet[:code] = <<-MANIFEST
+ class xport_test {
+ tag 'foo_bar'
+ @notify { 'nbr1':
+ message => 'explicitly tagged',
+ tag => 'foo_bar'
+ }
+
+ @notify { 'nbr2':
+ message => 'implicitly tagged'
+ }
+
+ Notify <| tag == 'foo_bar' |> {
+ message => 'overridden'
+ }
+ }
+ include xport_test
+ MANIFEST
+
+ catalog = Puppet::Parser::Compiler.compile(Puppet::Node.new("mynode"))
+
+ expect(catalog).to have_resource("Notify[nbr1]").with_parameter(:message, 'overridden')
+ expect(catalog).to have_resource("Notify[nbr2]").with_parameter(:message, 'overridden')
end
end
end
diff --git a/spec/integration/parser/conditionals_spec.rb b/spec/integration/parser/conditionals_spec.rb
new file mode 100644
index 000000000..82d950a06
--- /dev/null
+++ b/spec/integration/parser/conditionals_spec.rb
@@ -0,0 +1,117 @@
+require 'spec_helper'
+require 'puppet_spec/compiler'
+require 'matchers/resource'
+
+describe "Evaluation of Conditionals" do
+ include PuppetSpec::Compiler
+ include Matchers::Resource
+
+ shared_examples_for "a catalog built with conditionals" do
+ it "evaluates an if block correctly" do
+ catalog = compile_to_catalog(<<-CODE)
+ if( 1 == 1) {
+ notify { 'if': }
+ } elsif(2 == 2) {
+ notify { 'elsif': }
+ } else {
+ notify { 'else': }
+ }
+ CODE
+ expect(catalog).to have_resource("Notify[if]")
+ end
+
+ it "evaluates elsif block" do
+ catalog = compile_to_catalog(<<-CODE)
+ if( 1 == 3) {
+ notify { 'if': }
+ } elsif(2 == 2) {
+ notify { 'elsif': }
+ } else {
+ notify { 'else': }
+ }
+ CODE
+ expect(catalog).to have_resource("Notify[elsif]")
+ end
+
+ it "reaches the else clause if no expressions match" do
+ catalog = compile_to_catalog(<<-CODE)
+ if( 1 == 2) {
+ notify { 'if': }
+ } elsif(2 == 3) {
+ notify { 'elsif': }
+ } else {
+ notify { 'else': }
+ }
+ CODE
+ expect(catalog).to have_resource("Notify[else]")
+ end
+
+ it "evalutes false to false" do
+ catalog = compile_to_catalog(<<-CODE)
+ if false {
+ } else {
+ notify { 'false': }
+ }
+ CODE
+ expect(catalog).to have_resource("Notify[false]")
+ end
+
+ it "evaluates the string 'false' as true" do
+ catalog = compile_to_catalog(<<-CODE)
+ if 'false' {
+ notify { 'true': }
+ } else {
+ notify { 'false': }
+ }
+ CODE
+ expect(catalog).to have_resource("Notify[true]")
+ end
+
+ it "evaluates undefined variables as false" do
+ catalog = compile_to_catalog(<<-CODE)
+ if $undef_var {
+ } else {
+ notify { 'undef': }
+ }
+ CODE
+ expect(catalog).to have_resource("Notify[undef]")
+ end
+ end
+
+ context "current parser" do
+ before(:each) do
+ Puppet[:parser] = 'current'
+ end
+
+ it_behaves_like "a catalog built with conditionals"
+
+ it "evaluates empty string as false" do
+ catalog = compile_to_catalog(<<-CODE)
+ if '' {
+ notify { 'true': }
+ } else {
+ notify { 'empty': }
+ }
+ CODE
+ expect(catalog).to have_resource("Notify[empty]")
+ end
+ end
+
+ context "future parser" do
+ before(:each) do
+ Puppet[:parser] = 'future'
+ end
+ it_behaves_like "a catalog built with conditionals"
+
+ it "evaluates empty string as true" do
+ catalog = compile_to_catalog(<<-CODE)
+ if '' {
+ notify { 'true': }
+ } else {
+ notify { 'empty': }
+ }
+ CODE
+ expect(catalog).to have_resource("Notify[true]")
+ end
+ end
+end
diff --git a/spec/integration/parser/future_compiler_spec.rb b/spec/integration/parser/future_compiler_spec.rb
index 9d4d98776..d0fcfcdec 100644
--- a/spec/integration/parser/future_compiler_spec.rb
+++ b/spec/integration/parser/future_compiler_spec.rb
@@ -61,13 +61,73 @@ describe "Puppet::Parser::Compiler" do
expect(catalog).to have_resource("Notify[check_me]").with_parameter(:message, "evoe")
end
+ it 'Applies defaults from dynamic scopes (3x and future with reverted PUP-867)' do
+ catalog = compile_to_catalog(<<-CODE)
+ class a {
+ Notify { message => "defaulted" }
+ include b
+ notify { bye: }
+ }
+ class b { notify { hi: } }
+
+ include a
+ CODE
+ expect(catalog).to have_resource("Notify[hi]").with_parameter(:message, "defaulted")
+ expect(catalog).to have_resource("Notify[bye]").with_parameter(:message, "defaulted")
+ end
+
+ it 'gets default from inherited class (PUP-867)' do
+ catalog = compile_to_catalog(<<-CODE)
+ class a {
+ Notify { message => "defaulted" }
+ include c
+ notify { bye: }
+ }
+ class b { Notify { message => "inherited" } }
+ class c inherits b { notify { hi: } }
+
+ include a
+ CODE
+
+ expect(catalog).to have_resource("Notify[hi]").with_parameter(:message, "inherited")
+ expect(catalog).to have_resource("Notify[bye]").with_parameter(:message, "defaulted")
+ end
+
+ it 'looks up default parameter values from inherited class (PUP-2532)' do
+ catalog = compile_to_catalog(<<-CODE)
+ class a {
+ Notify { message => "defaulted" }
+ include c
+ notify { bye: }
+ }
+ class b { Notify { message => "inherited" } }
+ class c inherits b { notify { hi: } }
+
+ include a
+ notify {hi_test: message => Notify[hi][message] }
+ notify {bye_test: message => Notify[bye][message] }
+ CODE
+
+ expect(catalog).to have_resource("Notify[hi_test]").with_parameter(:message, "inherited")
+ expect(catalog).to have_resource("Notify[bye_test]").with_parameter(:message, "defaulted")
+ end
+
+ it 'does not allow override of class parameters using a resource override expression' do
+ expect do
+ compile_to_catalog(<<-CODE)
+ Class[a] { x => 2}
+ CODE
+ end.to raise_error(/Resource Override can only.*got: Class\[a\].*/)
+ end
+
describe "when resolving class references" do
- it "should favor local scope, even if there's an included class in topscope" do
+ it "should not favor local scope (with class included in topscope)" do
catalog = compile_to_catalog(<<-PP)
class experiment {
class baz {
}
notify {"x" : require => Class[Baz] }
+ notify {"y" : require => Class[Experiment::Baz] }
}
class baz {
}
@@ -76,15 +136,17 @@ describe "Puppet::Parser::Compiler" do
include experiment::baz
PP
- expect(catalog).to have_resource("Notify[x]").with_parameter(:require, be_resource("Class[Experiment::Baz]"))
+ expect(catalog).to have_resource("Notify[x]").with_parameter(:require, be_resource("Class[Baz]"))
+ expect(catalog).to have_resource("Notify[y]").with_parameter(:require, be_resource("Class[Experiment::Baz]"))
end
- it "should favor local scope, even if there's an unincluded class in topscope" do
+ it "should not favor local scope, (with class not included in topscope)" do
catalog = compile_to_catalog(<<-PP)
class experiment {
class baz {
}
notify {"x" : require => Class[Baz] }
+ notify {"y" : require => Class[Experiment::Baz] }
}
class baz {
}
@@ -92,7 +154,8 @@ describe "Puppet::Parser::Compiler" do
include experiment::baz
PP
- expect(catalog).to have_resource("Notify[x]").with_parameter(:require, be_resource("Class[Experiment::Baz]"))
+ expect(catalog).to have_resource("Notify[x]").with_parameter(:require, be_resource("Class[Baz]"))
+ expect(catalog).to have_resource("Notify[y]").with_parameter(:require, be_resource("Class[Experiment::Baz]"))
end
end
@@ -167,10 +230,10 @@ describe "Puppet::Parser::Compiler" do
def assert_creates_relationships(relationship_code, expectations)
base_manifest = <<-MANIFEST
file { [a,b,c]:
- mode => 0644,
+ mode => '0644',
}
file { [d,e]:
- mode => 0755,
+ mode => '0755',
}
MANIFEST
catalog = compile_to_catalog(base_manifest + relationship_code)
@@ -301,12 +364,13 @@ describe "Puppet::Parser::Compiler" do
end
it 'a missing variable as default value becomes undef' do
+ # strict variables not on
catalog = compile_to_catalog(<<-MANIFEST)
- class a ($b=$x) { notify {$b: message=>'meh'} }
+ class a ($b=$x) { notify {test: message=>"yes ${undef == $b}" } }
include a
MANIFEST
- expect(catalog).to have_resource("Notify[undef]").with_parameter(:message, "meh")
+ expect(catalog).to have_resource("Notify[test]").with_parameter(:message, "yes true")
end
end
@@ -366,5 +430,321 @@ describe "Puppet::Parser::Compiler" do
end
end
end
+
+ context 'when using typed parameters in definition' do
+ it 'accepts type compliant arguments' do
+ catalog = compile_to_catalog(<<-MANIFEST)
+ define foo(String $x) { }
+ foo { 'test': x =>'say friend' }
+ MANIFEST
+ expect(catalog).to have_resource("Foo[test]").with_parameter(:x, 'say friend')
+ end
+
+ it 'accepts anything when parameters are untyped' do
+ expect do
+ catalog = compile_to_catalog(<<-MANIFEST)
+ define foo($a, $b, $c) { }
+ foo { 'test': a => String, b=>10, c=>undef }
+ MANIFEST
+ end.to_not raise_error()
+ end
+
+ it 'denies non type compliant arguments' do
+ expect do
+ catalog = compile_to_catalog(<<-MANIFEST)
+ define foo(Integer $x) { }
+ foo { 'test': x =>'say friend' }
+ MANIFEST
+ end.to raise_error(/type Integer, got String/)
+ end
+
+ it 'denies non type compliant default argument' do
+ expect do
+ catalog = compile_to_catalog(<<-MANIFEST)
+ define foo(Integer $x = 'pow') { }
+ foo { 'test': }
+ MANIFEST
+ end.to raise_error(/type Integer, got String/)
+ end
+
+ it 'accepts a Resource as a Type' do
+ catalog = compile_to_catalog(<<-MANIFEST)
+ define foo(Type[Bar] $x) {
+ notify { 'test': message => $x[text] }
+ }
+ define bar($text) { }
+ bar { 'joke': text => 'knock knock' }
+ foo { 'test': x => Bar[joke] }
+ MANIFEST
+ expect(catalog).to have_resource("Notify[test]").with_parameter(:message, 'knock knock')
+ end
+ end
+
+ context 'when using typed parameters in class' do
+ it 'accepts type compliant arguments' do
+ catalog = compile_to_catalog(<<-MANIFEST)
+ class foo(String $x) { }
+ class { 'foo': x =>'say friend' }
+ MANIFEST
+ expect(catalog).to have_resource("Class[Foo]").with_parameter(:x, 'say friend')
+ end
+
+ it 'accepts anything when parameters are untyped' do
+ expect do
+ catalog = compile_to_catalog(<<-MANIFEST)
+ class foo($a, $b, $c) { }
+ class { 'foo': a => String, b=>10, c=>undef }
+ MANIFEST
+ end.to_not raise_error()
+ end
+
+ it 'denies non type compliant arguments' do
+ expect do
+ catalog = compile_to_catalog(<<-MANIFEST)
+ class foo(Integer $x) { }
+ class { 'foo': x =>'say friend' }
+ MANIFEST
+ end.to raise_error(/type Integer, got String/)
+ end
+
+ it 'denies non type compliant default argument' do
+ expect do
+ catalog = compile_to_catalog(<<-MANIFEST)
+ class foo(Integer $x = 'pow') { }
+ class { 'foo': }
+ MANIFEST
+ end.to raise_error(/type Integer, got String/)
+ end
+
+ it 'accepts a Resource as a Type' do
+ catalog = compile_to_catalog(<<-MANIFEST)
+ class foo(Type[Bar] $x) {
+ notify { 'test': message => $x[text] }
+ }
+ define bar($text) { }
+ bar { 'joke': text => 'knock knock' }
+ class { 'foo': x => Bar[joke] }
+ MANIFEST
+ expect(catalog).to have_resource("Notify[test]").with_parameter(:message, 'knock knock')
+ end
+ end
+
+ context 'when using typed parameters in lambdas' do
+ it 'accepts type compliant arguments' do
+ catalog = compile_to_catalog(<<-MANIFEST)
+ with('value') |String $x| { notify { "$x": } }
+ MANIFEST
+ expect(catalog).to have_resource("Notify[value]")
+ end
+
+ it 'handles an array as a single argument' do
+ catalog = compile_to_catalog(<<-MANIFEST)
+ with(['value', 'second']) |$x| { notify { "${x[0]} ${x[1]}": } }
+ MANIFEST
+ expect(catalog).to have_resource("Notify[value second]")
+ end
+
+ it 'denies when missing required arguments' do
+ expect do
+ compile_to_catalog(<<-MANIFEST)
+ with(1) |$x, $y| { }
+ MANIFEST
+ end.to raise_error(/Parameter \$y is required but no value was given/m)
+ end
+
+ it 'accepts anything when parameters are untyped' do
+ catalog = compile_to_catalog(<<-MANIFEST)
+ ['value', 1, true, undef].each |$x| { notify { "value: $x": } }
+ MANIFEST
+
+ expect(catalog).to have_resource("Notify[value: value]")
+ expect(catalog).to have_resource("Notify[value: 1]")
+ expect(catalog).to have_resource("Notify[value: true]")
+ expect(catalog).to have_resource("Notify[value: ]")
+ end
+
+ it 'accepts type-compliant, slurped arguments' do
+ catalog = compile_to_catalog(<<-MANIFEST)
+ with(1, 2) |Integer *$x| { notify { "${$x[0] + $x[1]}": } }
+ MANIFEST
+ expect(catalog).to have_resource("Notify[3]")
+ end
+
+ it 'denies non-type-compliant arguments' do
+ expect do
+ compile_to_catalog(<<-MANIFEST)
+ with(1) |String $x| { }
+ MANIFEST
+ end.to raise_error(/expected.*String.*actual.*Integer/m)
+ end
+
+ it 'denies non-type-compliant, slurped arguments' do
+ expect do
+ compile_to_catalog(<<-MANIFEST)
+ with(1, "hello") |Integer *$x| { }
+ MANIFEST
+ end.to raise_error(/called with mis-matched arguments.*expected.*Integer.*actual.*Integer, String/m)
+ end
+
+ it 'denies non-type-compliant default argument' do
+ expect do
+ compile_to_catalog(<<-MANIFEST)
+ with(1) |$x, String $defaulted = 1| { notify { "${$x + $defaulted}": }}
+ MANIFEST
+ end.to raise_error(/expected.*Any.*String.*actual.*Integer.*Integer/m)
+ end
+
+ it 'raises an error when a default argument value is an incorrect type and there are no arguments passed' do
+ expect do
+ compile_to_catalog(<<-MANIFEST)
+ with() |String $defaulted = 1| {}
+ MANIFEST
+ end.to raise_error(/expected.*String.*actual.*Integer/m)
+ end
+
+ it 'raises an error when the default argument for a slurped parameter is an incorrect type' do
+ expect do
+ compile_to_catalog(<<-MANIFEST)
+ with() |String *$defaulted = 1| {}
+ MANIFEST
+ end.to raise_error(/expected.*String.*actual.*Integer/m)
+ end
+
+ it 'allows using an array as the default slurped value' do
+ catalog = compile_to_catalog(<<-MANIFEST)
+ with() |String *$defaulted = [hi]| { notify { $defaulted[0]: } }
+ MANIFEST
+
+ expect(catalog).to have_resource('Notify[hi]')
+ end
+
+ it 'allows using a value of the type as the default slurped value' do
+ catalog = compile_to_catalog(<<-MANIFEST)
+ with() |String *$defaulted = hi| { notify { $defaulted[0]: } }
+ MANIFEST
+
+ expect(catalog).to have_resource('Notify[hi]')
+ end
+
+ it 'allows specifying the type of a slurped parameter as an array' do
+ catalog = compile_to_catalog(<<-MANIFEST)
+ with() |Array[String] *$defaulted = hi| { notify { $defaulted[0]: } }
+ MANIFEST
+
+ expect(catalog).to have_resource('Notify[hi]')
+ end
+
+ it 'raises an error when the number of default values does not match the parameter\'s size specification' do
+ expect do
+ compile_to_catalog(<<-MANIFEST)
+ with() |Array[String, 2] *$defaulted = hi| { }
+ MANIFEST
+ end.to raise_error(/expected.*arg count \{2,\}.*actual.*arg count \{1\}/m)
+ end
+
+ it 'raises an error when the number of passed values does not match the parameter\'s size specification' do
+ expect do
+ compile_to_catalog(<<-MANIFEST)
+ with(hi) |Array[String, 2] *$passed| { }
+ MANIFEST
+ end.to raise_error(/expected.*arg count \{2,\}.*actual.*arg count \{1\}/m)
+ end
+
+ it 'matches when the number of arguments passed for a slurp parameter match the size specification' do
+ catalog = compile_to_catalog(<<-MANIFEST)
+ with(hi, bye) |Array[String, 2] *$passed| {
+ $passed.each |$n| { notify { $n: } }
+ }
+ MANIFEST
+
+ expect(catalog).to have_resource('Notify[hi]')
+ expect(catalog).to have_resource('Notify[bye]')
+ end
+
+ it 'raises an error when the number of allowed slurp parameters exceeds the size constraint' do
+ expect do
+ compile_to_catalog(<<-MANIFEST)
+ with(hi, bye) |Array[String, 1, 1] *$passed| { }
+ MANIFEST
+ end.to raise_error(/expected.*arg count \{1\}.*actual.*arg count \{2\}/m)
+ end
+
+ it 'allows passing slurped arrays by specifying an array of arrays' do
+ catalog = compile_to_catalog(<<-MANIFEST)
+ with([hi], [bye]) |Array[Array[String, 1, 1]] *$passed| {
+ notify { $passed[0][0]: }
+ notify { $passed[1][0]: }
+ }
+ MANIFEST
+
+ expect(catalog).to have_resource('Notify[hi]')
+ expect(catalog).to have_resource('Notify[bye]')
+ end
+
+ it 'raises an error when a required argument follows an optional one' do
+ expect do
+ compile_to_catalog(<<-MANIFEST)
+ with() |$y = first, $x, Array[String, 1] *$passed = bye| {}
+ MANIFEST
+ end.to raise_error(/Parameter \$x is required/)
+ end
+
+ it 'raises an error when the minimum size of a slurped argument makes it required and it follows an optional argument' do
+ expect do
+ compile_to_catalog(<<-MANIFEST)
+ with() |$x = first, Array[String, 1] *$passed| {}
+ MANIFEST
+ end.to raise_error(/Parameter \$passed is required/)
+ end
+
+ it 'allows slurped arguments with a minimum size of 0 after an optional argument' do
+ catalog = compile_to_catalog(<<-MANIFEST)
+ with() |$x = first, Array[String, 0] *$passed| {
+ notify { $x: }
+ }
+ MANIFEST
+
+ expect(catalog).to have_resource('Notify[first]')
+ end
+
+ it 'accepts a Resource as a Type' do
+ catalog = compile_to_catalog(<<-MANIFEST)
+ define bar($text) { }
+ bar { 'joke': text => 'knock knock' }
+
+ with(Bar[joke]) |Type[Bar] $joke| { notify { "${joke[text]}": } }
+ MANIFEST
+ expect(catalog).to have_resource("Notify[knock knock]")
+ end
+ end
end
+
+ context 'when evaluating collection' do
+ it 'matches on container inherited tags' do
+ Puppet[:code] = <<-MANIFEST
+ class xport_test {
+ tag('foo_bar')
+ @notify { 'nbr1':
+ message => 'explicitly tagged',
+ tag => 'foo_bar'
+ }
+
+ @notify { 'nbr2':
+ message => 'implicitly tagged'
+ }
+
+ Notify <| tag == 'foo_bar' |> {
+ message => 'overridden'
+ }
+ }
+ include xport_test
+ MANIFEST
+
+ catalog = Puppet::Parser::Compiler.compile(Puppet::Node.new("mynode"))
+
+ expect(catalog).to have_resource("Notify[nbr1]").with_parameter(:message, 'overridden')
+ expect(catalog).to have_resource("Notify[nbr2]").with_parameter(:message, 'overridden')
+ end
+ end
+
end
diff --git a/spec/integration/parser/node_spec.rb b/spec/integration/parser/node_spec.rb
new file mode 100644
index 000000000..7ce58f152
--- /dev/null
+++ b/spec/integration/parser/node_spec.rb
@@ -0,0 +1,185 @@
+require 'spec_helper'
+require 'puppet_spec/compiler'
+require 'matchers/resource'
+
+describe 'node statements' do
+ include PuppetSpec::Compiler
+ include Matchers::Resource
+
+ shared_examples_for 'nodes' do
+ it 'selects a node where the name is just a number' do
+ # Future parser doesn't allow a number in this position
+ catalog = compile_to_catalog(<<-MANIFEST, Puppet::Node.new("5"))
+ node 5 { notify { 'matched': } }
+ MANIFEST
+
+ expect(catalog).to have_resource('Notify[matched]')
+ end
+
+ it 'selects the node with a matching name' do
+ catalog = compile_to_catalog(<<-MANIFEST, Puppet::Node.new("nodename"))
+ node noden {}
+ node nodename { notify { matched: } }
+ node name {}
+ MANIFEST
+
+ expect(catalog).to have_resource('Notify[matched]')
+ end
+
+ it 'prefers a node with a literal name over one with a regex' do
+ catalog = compile_to_catalog(<<-MANIFEST, Puppet::Node.new("nodename"))
+ node /noden.me/ { notify { ignored: } }
+ node nodename { notify { matched: } }
+ MANIFEST
+
+ expect(catalog).to have_resource('Notify[matched]')
+ end
+
+ it 'selects a node where one of the names matches' do
+ catalog = compile_to_catalog(<<-MANIFEST, Puppet::Node.new("nodename"))
+ node different, nodename, other { notify { matched: } }
+ MANIFEST
+
+ expect(catalog).to have_resource('Notify[matched]')
+ end
+
+ it 'arbitrarily selects one of the matching nodes' do
+ catalog = compile_to_catalog(<<-MANIFEST, Puppet::Node.new("nodename"))
+ node /not/ { notify { 'is not matched': } }
+ node /name.*/ { notify { 'could be matched': } }
+ node /na.e/ { notify { 'could also be matched': } }
+ MANIFEST
+
+ expect([catalog.resource('Notify[could be matched]'), catalog.resource('Notify[could also be matched]')].compact).to_not be_empty
+ end
+
+ it 'selects a node where one of the names matches with a mixture of literals and regex' do
+ catalog = compile_to_catalog(<<-MANIFEST, Puppet::Node.new("nodename"))
+ node different, /name/, other { notify { matched: } }
+ MANIFEST
+
+ expect(catalog).to have_resource('Notify[matched]')
+ end
+
+ it 'errors when two nodes with regexes collide after some regex syntax is removed' do
+ expect do
+ compile_to_catalog(<<-MANIFEST)
+ node /a.*(c)?/ { }
+ node 'a.c' { }
+ MANIFEST
+ end.to raise_error(Puppet::Error, /Node 'a.c' is already defined/)
+ end
+
+ it 'provides captures from the regex in the node body' do
+ catalog = compile_to_catalog(<<-MANIFEST, Puppet::Node.new("nodename"))
+ node /(.*)/ { notify { "$1": } }
+ MANIFEST
+
+ expect(catalog).to have_resource('Notify[nodename]')
+ end
+
+ it 'selects the node with the matching regex' do
+ catalog = compile_to_catalog(<<-MANIFEST, Puppet::Node.new("nodename"))
+ node /node.*/ { notify { matched: } }
+ MANIFEST
+
+ expect(catalog).to have_resource('Notify[matched]')
+ end
+
+ it 'selects a node that is a literal string' do
+ catalog = compile_to_catalog(<<-MANIFEST, Puppet::Node.new("node.name"))
+ node 'node.name' { notify { matched: } }
+ MANIFEST
+
+ expect(catalog).to have_resource('Notify[matched]')
+ end
+
+ it 'selects a node that is a prefix of the agent name' do
+ Puppet[:strict_hostname_checking] = false
+ catalog = compile_to_catalog(<<-MANIFEST, Puppet::Node.new("node.name.com"))
+ node 'node.name' { notify { matched: } }
+ MANIFEST
+
+ expect(catalog).to have_resource('Notify[matched]')
+ end
+
+ it 'does not treat regex symbols as a regex inside a string literal' do
+ catalog = compile_to_catalog(<<-MANIFEST, Puppet::Node.new("nodexname"))
+ node 'node.name' { notify { 'not matched': } }
+ node 'nodexname' { notify { 'matched': } }
+ MANIFEST
+
+ expect(catalog).to have_resource('Notify[matched]')
+ end
+
+ it 'errors when two nodes have the same name' do
+ expect do
+ compile_to_catalog(<<-MANIFEST)
+ node name { }
+ node 'name' { }
+ MANIFEST
+ end.to raise_error(Puppet::Error, /Node 'name' is already defined/)
+ end
+ end
+
+ describe 'using classic parser' do
+ before :each do
+ Puppet[:parser] = 'current'
+ end
+
+ it_behaves_like 'nodes'
+
+ it 'includes the inherited nodes of the matching node' do
+ catalog = compile_to_catalog(<<-MANIFEST, Puppet::Node.new("nodename"))
+ node notmatched1 { notify { inherited: } }
+ node nodename inherits notmatched1 { notify { matched: } }
+ node notmatched2 { notify { ignored: } }
+ MANIFEST
+
+ expect(catalog).to have_resource('Notify[matched]')
+ expect(catalog).to have_resource('Notify[inherited]')
+ end
+
+ it 'raises deprecation warning for node inheritance for 3x parser' do
+ Puppet.expects(:warning).at_least_once
+ Puppet.expects(:warning).with(regexp_matches(/Deprecation notice\: Node inheritance is not supported in Puppet >= 4\.0\.0/))
+
+ catalog = compile_to_catalog(<<-MANIFEST, Puppet::Node.new("1.2.3.4"))
+ node default {}
+ node '1.2.3.4' inherits default { }
+ MANIFEST
+ end
+ end
+
+ describe 'using future parser' do
+ before :each do
+ Puppet[:parser] = 'future'
+ end
+
+ it_behaves_like 'nodes'
+
+ it 'is unable to parse a name that is an invalid number' do
+ expect do
+ compile_to_catalog('node 5name {} ')
+ end.to raise_error(Puppet::Error, /Illegal number/)
+ end
+
+ it 'parses a node name that is dotted numbers' do
+ catalog = compile_to_catalog(<<-MANIFEST, Puppet::Node.new("1.2.3.4"))
+ node 1.2.3.4 { notify { matched: } }
+ MANIFEST
+
+ expect(catalog).to have_resource('Notify[matched]')
+ end
+
+ it 'raises error for node inheritance' do
+ expect do
+ compile_to_catalog(<<-MANIFEST, Puppet::Node.new("nodename"))
+ node default {}
+ node nodename inherits default { }
+ MANIFEST
+ end.to raise_error(/Node inheritance is not supported in Puppet >= 4\.0\.0/)
+ end
+
+ end
+end
diff --git a/spec/integration/parser/resource_expressions_spec.rb b/spec/integration/parser/resource_expressions_spec.rb
new file mode 100644
index 000000000..c753f5a91
--- /dev/null
+++ b/spec/integration/parser/resource_expressions_spec.rb
@@ -0,0 +1,286 @@
+require 'spec_helper'
+require 'puppet_spec/language'
+
+describe "Puppet resource expressions" do
+ extend PuppetSpec::Language
+
+ describe "future parser" do
+ before :each do
+ Puppet[:parser] = 'future'
+ end
+
+ produces(
+ "$a = notify
+ $b = example
+ $c = { message => hello }
+ @@Resource[$a] {
+ $b:
+ * => $c
+ }
+ realize(Resource[$a, $b])
+ " => "Notify[example][message] == 'hello'")
+
+
+ context "resource titles" do
+ produces(
+ "notify { thing: }" => "defined(Notify[thing])",
+ "$x = thing notify { $x: }" => "defined(Notify[thing])",
+
+ "notify { [thing]: }" => "defined(Notify[thing])",
+ "$x = [thing] notify { $x: }" => "defined(Notify[thing])",
+
+ "notify { [[nested, array]]: }" => "defined(Notify[nested]) and defined(Notify[array])",
+ "$x = [[nested, array]] notify { $x: }" => "defined(Notify[nested]) and defined(Notify[array])",
+
+ "notify { []: }" => [], # this asserts nothing added
+ "$x = [] notify { $x: }" => [], # this asserts nothing added
+
+ "notify { default: }" => "!defined(Notify['default'])", # nothing created because this is just a local default
+ "$x = default notify { $x: }" => "!defined(Notify['default'])")
+
+ fails(
+ "notify { '': }" => /Empty string title/,
+ "$x = '' notify { $x: }" => /Empty string title/,
+
+ "notify { 1: }" => /Illegal title type.*Expected String, got Integer/,
+ "$x = 1 notify { $x: }" => /Illegal title type.*Expected String, got Integer/,
+
+ "notify { [1]: }" => /Illegal title type.*Expected String, got Integer/,
+ "$x = [1] notify { $x: }" => /Illegal title type.*Expected String, got Integer/,
+
+ "notify { 3.0: }" => /Illegal title type.*Expected String, got Float/,
+ "$x = 3.0 notify { $x: }" => /Illegal title type.*Expected String, got Float/,
+
+ "notify { [3.0]: }" => /Illegal title type.*Expected String, got Float/,
+ "$x = [3.0] notify { $x: }" => /Illegal title type.*Expected String, got Float/,
+
+ "notify { true: }" => /Illegal title type.*Expected String, got Boolean/,
+ "$x = true notify { $x: }" => /Illegal title type.*Expected String, got Boolean/,
+
+ "notify { [true]: }" => /Illegal title type.*Expected String, got Boolean/,
+ "$x = [true] notify { $x: }" => /Illegal title type.*Expected String, got Boolean/,
+
+ "notify { [false]: }" => /Illegal title type.*Expected String, got Boolean/,
+ "$x = [false] notify { $x: }" => /Illegal title type.*Expected String, got Boolean/,
+
+ "notify { undef: }" => /Missing title.*undef/,
+ "$x = undef notify { $x: }" => /Missing title.*undef/,
+
+ "notify { [undef]: }" => /Missing title.*undef/,
+ "$x = [undef] notify { $x: }" => /Missing title.*undef/,
+
+ "notify { {nested => hash}: }" => /Illegal title type.*Expected String, got Hash/,
+ "$x = {nested => hash} notify { $x: }" => /Illegal title type.*Expected String, got Hash/,
+
+ "notify { [{nested => hash}]: }" => /Illegal title type.*Expected String, got Hash/,
+ "$x = [{nested => hash}] notify { $x: }" => /Illegal title type.*Expected String, got Hash/,
+
+ "notify { /regexp/: }" => /Illegal title type.*Expected String, got Regexp/,
+ "$x = /regexp/ notify { $x: }" => /Illegal title type.*Expected String, got Regexp/,
+
+ "notify { [/regexp/]: }" => /Illegal title type.*Expected String, got Regexp/,
+ "$x = [/regexp/] notify { $x: }" => /Illegal title type.*Expected String, got Regexp/,
+
+ "notify { [dupe, dupe]: }" => /The title 'dupe' has already been used/,
+ "notify { dupe:; dupe: }" => /The title 'dupe' has already been used/,
+ "notify { [dupe]:; dupe: }" => /The title 'dupe' has already been used/,
+ "notify { [default, default]:}" => /The title 'default' has already been used/,
+ "notify { default:; default:}" => /The title 'default' has already been used/,
+ "notify { [default]:; default:}" => /The title 'default' has already been used/)
+ end
+
+ context "type names" do
+ produces( "notify { testing: }" => "defined(Notify[testing])")
+ produces( "$a = notify; Resource[$a] { testing: }" => "defined(Notify[testing])")
+ produces( "Resource['notify'] { testing: }" => "defined(Notify[testing])")
+ produces( "Resource[sprintf('%s', 'notify')] { testing: }" => "defined(Notify[testing])")
+ produces( "$a = ify; Resource[\"not$a\"] { testing: }" => "defined(Notify[testing])")
+
+ produces( "Notify { testing: }" => "defined(Notify[testing])")
+ produces( "Resource[Notify] { testing: }" => "defined(Notify[testing])")
+ produces( "Resource['Notify'] { testing: }" => "defined(Notify[testing])")
+
+ produces( "class a { notify { testing: } } class { a: }" => "defined(Notify[testing])")
+ produces( "class a { notify { testing: } } Class { a: }" => "defined(Notify[testing])")
+ produces( "class a { notify { testing: } } Resource['class'] { a: }" => "defined(Notify[testing])")
+
+ produces( "define a::b { notify { testing: } } a::b { title: }" => "defined(Notify[testing])")
+ produces( "define a::b { notify { testing: } } A::B { title: }" => "defined(Notify[testing])")
+ produces( "define a::b { notify { testing: } } Resource['a::b'] { title: }" => "defined(Notify[testing])")
+
+ fails( "'class' { a: }" => /Illegal Resource Type expression.*got String/)
+ fails( "'' { testing: }" => /Illegal Resource Type expression.*got String/)
+ fails( "1 { testing: }" => /Illegal Resource Type expression.*got Integer/)
+ fails( "3.0 { testing: }" => /Illegal Resource Type expression.*got Float/)
+ fails( "true { testing: }" => /Illegal Resource Type expression.*got Boolean/)
+ fails( "'not correct' { testing: }" => /Illegal Resource Type expression.*got String/)
+
+ fails( "Notify[hi] { testing: }" => /Illegal Resource Type expression.*got Notify\['hi'\]/)
+ fails( "[Notify, File] { testing: }" => /Illegal Resource Type expression.*got Array\[Type\[Resource\]\]/)
+
+ fails( "define a::b { notify { testing: } } 'a::b' { title: }" => /Illegal Resource Type expression.*got String/)
+
+ fails( "Does::Not::Exist { title: }" => /Invalid resource type does::not::exist/)
+ end
+
+ context "local defaults" do
+ produces(
+ "notify { example:; default: message => defaulted }" => "Notify[example][message] == 'defaulted'",
+ "notify { example: message => specific; default: message => defaulted }" => "Notify[example][message] == 'specific'",
+ "notify { example: message => undef; default: message => defaulted }" => "Notify[example][message] == undef",
+ "notify { [example, other]: ; default: message => defaulted }" => "Notify[example][message] == 'defaulted' and Notify[other][message] == 'defaulted'",
+ "notify { [example, default]: message => set; other: }" => "Notify[example][message] == 'set' and Notify[other][message] == 'set'")
+ end
+
+ context "order of evaluation" do
+ fails("notify { hi: message => value; bye: message => Notify[hi][message] }" => /Resource not found: Notify\['hi'\]/)
+
+ produces("notify { hi: message => (notify { param: message => set }); bye: message => Notify[param][message] }" => "defined(Notify[hi]) and Notify[bye][message] == 'set'")
+ fails("notify { bye: message => Notify[param][message]; hi: message => (notify { param: message => set }) }" => /Resource not found: Notify\['param'\]/)
+ end
+
+ context "parameters" do
+ produces(
+ "notify { title: message => set }" => "Notify[title][message] == 'set'",
+ "$x = set notify { title: message => $x }" => "Notify[title][message] == 'set'",
+
+ "notify { title: *=> { message => set } }" => "Notify[title][message] == 'set'",
+
+ "$x = { message => set } notify { title: * => $x }" => "Notify[title][message] == 'set'",
+
+ # picks up defaults
+ "$x = { owner => the_x }
+ $y = { mode => '0666' }
+ $t = '/tmp/x'
+ file {
+ default:
+ * => $x;
+ $t:
+ path => '/somewhere',
+ * => $y }" => "File[$t][mode] == '0666' and File[$t][owner] == 'the_x' and File[$t][path] == '/somewhere'",
+
+ # explicit wins over default - no error
+ "$x = { owner => the_x, mode => '0777' }
+ $y = { mode => '0666' }
+ $t = '/tmp/x'
+ file {
+ default:
+ * => $x;
+ $t:
+ path => '/somewhere',
+ * => $y }" => "File[$t][mode] == '0666' and File[$t][owner] == 'the_x' and File[$t][path] == '/somewhere'")
+
+ fails("notify { title: unknown => value }" => /Invalid parameter unknown/)
+
+ # this really needs to be a better error message.
+ fails("notify { title: * => { hash => value }, message => oops }" => /Invalid parameter hash/)
+
+ # should this be a better error message?
+ fails("notify { title: message => oops, * => { hash => value } }" => /Invalid parameter hash/)
+
+ fails("notify { title: * => { unknown => value } }" => /Invalid parameter unknown/)
+ fails("
+ $x = { mode => '0666' }
+ $y = { owner => the_y }
+ $t = '/tmp/x'
+ file { $t:
+ * => $x,
+ * => $y }" => /Unfolding of attributes from Hash can only be used once per resource body/)
+ end
+
+ context "virtual" do
+ produces(
+ "@notify { example: }" => "!defined(Notify[example])",
+
+ "@notify { example: }
+ realize(Notify[example])" => "defined(Notify[example])",
+
+ "@notify { virtual: message => set }
+ notify { real:
+ message => Notify[virtual][message] }" => "Notify[real][message] == 'set'")
+ end
+
+ context "exported" do
+ produces(
+ "@@notify { example: }" => "!defined(Notify[example])",
+ "@@notify { example: } realize(Notify[example])" => "defined(Notify[example])",
+ "@@notify { exported: message => set } notify { real: message => Notify[exported][message] }" => "Notify[real][message] == 'set'")
+ end
+ end
+
+ describe "current parser" do
+ before :each do
+ Puppet[:parser] = 'current'
+ end
+
+ produces(
+ "notify { thing: }" => ["Notify[thing]"],
+ "$x = thing notify { $x: }" => ["Notify[thing]"],
+
+ "notify { [thing]: }" => ["Notify[thing]"],
+ "$x = [thing] notify { $x: }" => ["Notify[thing]"],
+
+ "notify { [[nested, array]]: }" => ["Notify[nested]", "Notify[array]"],
+ "$x = [[nested, array]] notify { $x: }" => ["Notify[nested]", "Notify[array]"],
+
+ # deprecate?
+ "notify { 1: }" => ["Notify[1]"],
+ "$x = 1 notify { $x: }" => ["Notify[1]"],
+
+ # deprecate?
+ "notify { [1]: }" => ["Notify[1]"],
+ "$x = [1] notify { $x: }" => ["Notify[1]"],
+
+ # deprecate?
+ "notify { 3.0: }" => ["Notify[3.0]"],
+ "$x = 3.0 notify { $x: }" => ["Notify[3.0]"],
+
+ # deprecate?
+ "notify { [3.0]: }" => ["Notify[3.0]"],
+ "$x = [3.0] notify { $x: }" => ["Notify[3.0]"])
+
+ # :(
+ fails( "notify { true: }" => /Syntax error/)
+ produces("$x = true notify { $x: }" => ["Notify[true]"])
+
+ # this makes no sense given the [false] case
+ produces(
+ "notify { [true]: }" => ["Notify[true]"],
+ "$x = [true] notify { $x: }" => ["Notify[true]"])
+
+ # *sigh*
+ fails(
+ "notify { false: }" => /Syntax error/,
+ "$x = false notify { $x: }" => /No title provided and :notify is not a valid resource reference/,
+
+ "notify { [false]: }" => /No title provided and :notify is not a valid resource reference/,
+ "$x = [false] notify { $x: }" => /No title provided and :notify is not a valid resource reference/)
+
+ # works for variable value, not for literal. deprecate?
+ fails("notify { undef: }" => /Syntax error/)
+ produces(
+ "$x = undef notify { $x: }" => ["Notify[undef]"],
+
+ # deprecate?
+ "notify { [undef]: }" => ["Notify[undef]"],
+ "$x = [undef] notify { $x: }" => ["Notify[undef]"])
+
+ fails("notify { {nested => hash}: }" => /Syntax error/)
+ #produces("$x = {nested => hash} notify { $x: }" => ["Notify[{nested => hash}]"]) #it is created, but isn't possible to reference the resource. deprecate?
+ #produces("notify { [{nested => hash}]: }" => ["Notify[{nested => hash}]"]) #it is created, but isn't possible to reference the resource. deprecate?
+ #produces("$x = [{nested => hash}] notify { $x: }" => ["Notify[{nested => hash}]"]) #it is created, but isn't possible to reference the resource. deprecate?
+
+ fails(
+ "notify { /regexp/: }" => /Syntax error/,
+ "$x = /regexp/ notify { $x: }" => /Syntax error/,
+
+ "notify { [/regexp/]: }" => /Syntax error/,
+ "$x = [/regexp/] notify { $x: }" => /Syntax error/,
+
+ "notify { default: }" => /Syntax error/,
+ "$x = default notify { $x: }" => /Syntax error/,
+
+ "notify { [default]: }" => /Syntax error/,
+ "$x = [default] notify { $x: }" => /Syntax error/)
+ end
+end
diff --git a/spec/integration/parser/ruby_manifest_spec.rb b/spec/integration/parser/ruby_manifest_spec.rb
index d0bd5f0e7..29e9c6379 100755
--- a/spec/integration/parser/ruby_manifest_spec.rb
+++ b/spec/integration/parser/ruby_manifest_spec.rb
@@ -11,10 +11,6 @@ describe "Pure ruby manifests" do
@test_dir = tmpdir('ruby_manifest_test')
end
- after do
- Puppet.settings.clear
- end
-
def write_file(name, contents)
path = File.join(@test_dir, name)
File.open(path, "w") { |f| f.write(contents) }
diff --git a/spec/integration/parser/scope_spec.rb b/spec/integration/parser/scope_spec.rb
index 1fd84f421..c10caa7f0 100644
--- a/spec/integration/parser/scope_spec.rb
+++ b/spec/integration/parser/scope_spec.rb
@@ -39,23 +39,27 @@ describe "Two step scoping for variables" do
Puppet[:parser] = 'future'
end
- describe "using plussignment to change in a new scope" do
- it "does not change a string in the parent scope" do
- # Expects to be able to concatenate string using +=
+ describe "using unsupported operators" do
+ it "issues an error for +=" do
expect do
- catalog = compile_to_catalog(<<-MANIFEST, Puppet::Node.new('the node'))
- $var = "top_msg"
- class override {
- $var += "override"
- include foo
- }
- class foo {
- notify { 'something': message => $var, }
+ catalog = compile_to_catalog(<<-MANIFEST)
+ $var = ["top_msg"]
+ node default {
+ $var += ["override"]
}
+ MANIFEST
+ end.to raise_error(/The operator '\+=' is no longer supported/)
+ end
- include override
+ it "issues an error for -=" do
+ expect do
+ catalog = compile_to_catalog(<<-MANIFEST)
+ $var = ["top_msg"]
+ node default {
+ $var -= ["top_msg"]
+ }
MANIFEST
- end.to raise_error(/The value 'top_msg' cannot be converted to Numeric/)
+ end.to raise_error(/The operator '-=' is no longer supported/)
end
end
@@ -184,54 +188,6 @@ describe "Two step scoping for variables" do
end
describe "when using shadowing and inheritance" do
- it "finds value define in the inherited node" do
- expect_the_message_to_be('parent_msg') do <<-MANIFEST
- $var = "top_msg"
- node parent {
- $var = "parent_msg"
- }
- node default inherits parent {
- include foo
- }
- class foo {
- notify { 'something': message => $var, }
- }
- MANIFEST
- end
- end
-
- it "finds top scope when the class is included before the node defines the var" do
- expect_the_message_to_be('top_msg') do <<-MANIFEST
- $var = "top_msg"
- node parent {
- include foo
- }
- node default inherits parent {
- $var = "default_msg"
- }
- class foo {
- notify { 'something': message => $var, }
- }
- MANIFEST
- end
- end
-
- it "finds top scope when the class is included before the node defines the var" do
- expect_the_message_to_be('top_msg') do <<-MANIFEST
- $var = "top_msg"
- node parent {
- include foo
- }
- node default inherits parent {
- $var = "default_msg"
- }
- class foo {
- notify { 'something': message => $var, }
- }
- MANIFEST
- end
- end
-
it "finds values in its local scope" do
expect_the_message_to_be('local_msg') do <<-MANIFEST
node default {
@@ -363,7 +319,7 @@ describe "Two step scoping for variables" do
$var = "inner baz"
}
- class bar inherits baz {
+ class bar inherits foo::baz {
notify { 'something': message => $var, }
}
}
@@ -651,80 +607,6 @@ describe "Two step scoping for variables" do
end
end
- describe "using plussignment to change in a new scope" do
-
- it "does not change an array in the parent scope" do
- expect_the_message_to_be('top_msg') do <<-MANIFEST
- $var = ["top_msg"]
- class override {
- $var += ["override"]
- include foo
- }
- class foo {
- notify { 'something': message => $var, }
- }
-
- include override
- MANIFEST
- end
- end
-
- it "concatenates two arrays" do
- expect_the_message_to_be(['top_msg', 'override']) do <<-MANIFEST
- $var = ["top_msg"]
- class override {
- $var += ["override"]
- notify { 'something': message => $var, }
- }
-
- include override
- MANIFEST
- end
- end
-
- it "leaves an array of arrays unflattened" do
- expect_the_message_to_be([['top_msg'], ['override']]) do <<-MANIFEST
- $var = [["top_msg"]]
- class override {
- $var += [["override"]]
- notify { 'something': message => $var, }
- }
-
- include override
- MANIFEST
- end
- end
-
- it "does not change a hash in the parent scope" do
- expect_the_message_to_be({"key"=>"top_msg"}) do <<-MANIFEST
- $var = { "key" => "top_msg" }
- class override {
- $var += { "other" => "override" }
- include foo
- }
- class foo {
- notify { 'something': message => $var, }
- }
-
- include override
- MANIFEST
- end
- end
-
- it "replaces a value of a key in the hash instead of merging the values" do
- expect_the_message_to_be({"key"=>"override"}) do <<-MANIFEST
- $var = { "key" => "top_msg" }
- class override {
- $var += { "key" => "override" }
- notify { 'something': message => $var, }
- }
-
- include override
- MANIFEST
- end
- end
- end
-
describe "when using an enc" do
it "places enc parameters in top scope" do
enc_node = Puppet::Node.new("the node", { :parameters => { "var" => 'from_enc' } })
@@ -757,20 +639,16 @@ describe "Two step scoping for variables" do
end
end
- it "evaluates enc classes in the node scope when there is a matching node" do
- enc_node = Puppet::Node.new("the_node", { :classes => ['foo'] })
-
- expect_the_message_to_be('from matching node', enc_node) do <<-MANIFEST
- node inherited {
- $var = "from inherited"
- }
+ it "overrides enc variables from a node scope var" do
+ enc_node = Puppet::Node.new("the_node", { :classes => ['foo'], :parameters => { 'enc_var' => 'Set from ENC.' } })
- node the_node inherits inherited {
- $var = "from matching node"
+ expect_the_message_to_be('ENC overridden in node', enc_node) do <<-MANIFEST
+ node the_node {
+ $enc_var = "ENC overridden in node"
}
class foo {
- notify { 'something': message => $var, }
+ notify { 'something': message => $enc_var, }
}
MANIFEST
end
@@ -782,7 +660,74 @@ describe "Two step scoping for variables" do
before :each do
Puppet[:parser] = 'current'
end
- it_behaves_like 'the scope' do
+
+ it_behaves_like 'the scope'
+
+ it "finds value define in the inherited node" do
+ expect_the_message_to_be('parent_msg') do <<-MANIFEST
+ $var = "top_msg"
+ node parent {
+ $var = "parent_msg"
+ }
+ node default inherits parent {
+ include foo
+ }
+ class foo {
+ notify { 'something': message => $var, }
+ }
+ MANIFEST
+ end
+ end
+
+ it "finds top scope when the class is included before the node defines the var" do
+ expect_the_message_to_be('top_msg') do <<-MANIFEST
+ $var = "top_msg"
+ node parent {
+ include foo
+ }
+ node default inherits parent {
+ $var = "default_msg"
+ }
+ class foo {
+ notify { 'something': message => $var, }
+ }
+ MANIFEST
+ end
+ end
+
+ it "finds top scope when the class is included before the node defines the var" do
+ expect_the_message_to_be('top_msg') do <<-MANIFEST
+ $var = "top_msg"
+ node parent {
+ include foo
+ }
+ node default inherits parent {
+ $var = "default_msg"
+ }
+ class foo {
+ notify { 'something': message => $var, }
+ }
+ MANIFEST
+ end
+ end
+
+ it "evaluates enc classes in the node scope when there is a matching node" do
+ enc_node = Puppet::Node.new("the_node", { :classes => ['foo'] })
+
+ expect_the_message_to_be('from matching node', enc_node) do <<-MANIFEST
+ node inherited {
+ $var = "from inherited"
+ }
+
+ node the_node inherits inherited {
+ $var = "from matching node"
+ }
+
+ class foo {
+ notify { 'something': message => $var, }
+ }
+ MANIFEST
+ end
end
end
@@ -790,9 +735,7 @@ describe "Two step scoping for variables" do
before :each do
Puppet[:parser] = 'future'
end
- it_behaves_like 'the scope' do
- end
- end
+ it_behaves_like 'the scope'
+ end
end
-
diff --git a/spec/integration/provider/cron/crontab_spec.rb b/spec/integration/provider/cron/crontab_spec.rb
index a88eb1c76..e75523cac 100644
--- a/spec/integration/provider/cron/crontab_spec.rb
+++ b/spec/integration/provider/cron/crontab_spec.rb
@@ -2,9 +2,11 @@
require 'spec_helper'
require 'puppet/file_bucket/dipper'
+require 'puppet_spec/compiler'
describe Puppet::Type.type(:cron).provider(:crontab), '(integration)', :unless => Puppet.features.microsoft_windows? do
include PuppetSpec::Files
+ include PuppetSpec::Compiler
before :each do
Puppet::Type.type(:cron).stubs(:defaultprovider).returns described_class
@@ -33,20 +35,6 @@ describe Puppet::Type.type(:cron).provider(:crontab), '(integration)', :unless =
tmpfile('cron_integration_specs')
end
- def run_in_catalog(*resources)
- catalog = Puppet::Resource::Catalog.new
- catalog.host_config = false
- resources.each do |resource|
- resource.expects(:err).never
- catalog.add_resource(resource)
- end
-
- # the resources are not properly contained and generated resources
- # will end up with dangling edges without this stubbing:
- catalog.stubs(:container_of).returns resources[0]
- catalog.apply
- end
-
def expect_output(fixture_name)
File.read(crontab_user1).should == File.read(my_fixture(fixture_name))
end
@@ -54,56 +42,53 @@ describe Puppet::Type.type(:cron).provider(:crontab), '(integration)', :unless =
describe "when managing a cron entry" do
it "should be able to purge unmanaged entries" do
- resource = Puppet::Type.type(:cron).new(
- :name => 'only managed entry',
- :ensure => :present,
- :command => '/bin/true',
- :target => crontab_user1,
- :user => crontab_user1
- )
- resources = Puppet::Type.type(:resources).new(
- :name => 'cron',
- :purge => 'true'
- )
- run_in_catalog(resource, resources)
+ apply_with_error_check(<<-MANIFEST)
+ cron {
+ 'only managed entry':
+ ensure => 'present',
+ command => '/bin/true',
+ target => '#{crontab_user1}',
+ }
+ resources { 'cron': purge => 'true' }
+ MANIFEST
expect_output('purged')
end
describe "with ensure absent" do
it "should do nothing if entry already absent" do
- resource = Puppet::Type.type(:cron).new(
- :name => 'no_such_entry',
- :ensure => :absent,
- :target => crontab_user1,
- :user => crontab_user1
- )
- run_in_catalog(resource)
+ apply_with_error_check(<<-MANIFEST)
+ cron {
+ 'no_such_entry':
+ ensure => 'absent',
+ target => '#{crontab_user1}',
+ }
+ MANIFEST
expect_output('crontab_user1')
end
it "should remove the resource from crontab if present" do
- resource = Puppet::Type.type(:cron).new(
- :name => 'My daily failure',
- :ensure => :absent,
- :target => crontab_user1,
- :user => crontab_user1
- )
- run_in_catalog(resource)
+ apply_with_error_check(<<-MANIFEST)
+ cron {
+ 'My daily failure':
+ ensure => 'absent',
+ target => '#{crontab_user1}',
+ }
+ MANIFEST
expect_output('remove_named_resource')
end
it "should remove a matching cronentry if present" do
- resource = Puppet::Type.type(:cron).new(
- :name => 'no_such_named_resource_in_crontab',
- :ensure => :absent,
- :minute => [ '17-19', '22' ],
- :hour => [ '0-23/2' ],
- :weekday => 'Tue',
- :command => '/bin/unnamed_regular_command',
- :target => crontab_user1,
- :user => crontab_user1
- )
- run_in_catalog(resource)
+ apply_with_error_check(<<-MANIFEST)
+ cron {
+ 'no_such_named_resource_in_crontab':
+ ensure => absent,
+ minute => [ '17-19', '22' ],
+ hour => [ '0-23/2' ],
+ weekday => 'Tue',
+ command => '/bin/unnamed_regular_command',
+ target => '#{crontab_user1}',
+ }
+ MANIFEST
expect_output('remove_unnamed_resource')
end
end
@@ -112,137 +97,141 @@ describe Puppet::Type.type(:cron).provider(:crontab), '(integration)', :unless =
context "and no command specified" do
it "should work if the resource is already present" do
- resource = Puppet::Type.type(:cron).new(
- :name => 'My daily failure',
- :special => 'daily',
- :target => crontab_user1,
- :user => crontab_user1
- )
- run_in_catalog(resource)
+ apply_with_error_check(<<-MANIFEST)
+ cron {
+ 'My daily failure':
+ special => 'daily',
+ target => '#{crontab_user1}',
+ }
+ MANIFEST
expect_output('crontab_user1')
end
it "should fail if the resource needs creating" do
- resource = Puppet::Type.type(:cron).new(
- :name => 'Entirely new resource',
- :special => 'daily',
- :target => crontab_user1,
- :user => crontab_user1
- )
- resource.expects(:err).with(regexp_matches(/no command/))
- run_in_catalog(resource)
+ manifest = <<-MANIFEST
+ cron {
+ 'Entirely new resource':
+ special => 'daily',
+ target => '#{crontab_user1}',
+ }
+ MANIFEST
+ apply_compiled_manifest(manifest) do |res|
+ if res.ref == 'Cron[Entirely new resource]'
+ res.expects(:err).with(regexp_matches(/no command/))
+ else
+ res.expects(:err).never
+ end
+ end
end
end
it "should do nothing if entry already present" do
- resource = Puppet::Type.type(:cron).new(
- :name => 'My daily failure',
- :special => 'daily',
- :command => '/bin/false',
- :target => crontab_user1,
- :user => crontab_user1
- )
- run_in_catalog(resource)
+ apply_with_error_check(<<-MANIFEST)
+ cron {
+ 'My daily failure':
+ special => 'daily',
+ command => '/bin/false',
+ target => '#{crontab_user1}',
+ }
+ MANIFEST
expect_output('crontab_user1')
end
it "should work correctly when managing 'target' but not 'user'" do
- resource = Puppet::Type.type(:cron).new(
- :name => 'My daily failure',
- :special => 'daily',
- :command => '/bin/false',
- :target => crontab_user1
- )
- run_in_catalog(resource)
+ apply_with_error_check(<<-MANIFEST)
+ cron {
+ 'My daily failure':
+ special => 'daily',
+ command => '/bin/false',
+ target => '#{crontab_user1}',
+ }
+ MANIFEST
expect_output('crontab_user1')
end
it "should do nothing if a matching entry already present" do
- resource = Puppet::Type.type(:cron).new(
- :name => 'no_such_named_resource_in_crontab',
- :ensure => :present,
- :minute => [ '17-19', '22' ],
- :hour => [ '0-23/2' ],
- :command => '/bin/unnamed_regular_command',
- :target => crontab_user1,
- :user => crontab_user1
- )
- run_in_catalog(resource)
+ apply_with_error_check(<<-MANIFEST)
+ cron {
+ 'no_such_named_resource_in_crontab':
+ ensure => present,
+ minute => [ '17-19', '22' ],
+ hour => [ '0-23/2' ],
+ command => '/bin/unnamed_regular_command',
+ target => '#{crontab_user1}',
+ }
+ MANIFEST
expect_output('crontab_user1')
end
it "should add a new normal entry if currently absent" do
- resource = Puppet::Type.type(:cron).new(
- :name => 'new entry',
- :ensure => :present,
- :minute => '12',
- :weekday => 'Tue',
- :command => '/bin/new',
- :environment => [
- 'MAILTO=""',
- 'SHELL=/bin/bash'
- ],
- :target => crontab_user1,
- :user => crontab_user1
- )
- run_in_catalog(resource)
+ apply_with_error_check(<<-MANIFEST)
+ cron {
+ 'new entry':
+ ensure => present,
+ minute => '12',
+ weekday => 'Tue',
+ command => '/bin/new',
+ environment => [
+ 'MAILTO=""',
+ 'SHELL=/bin/bash'
+ ],
+ target => '#{crontab_user1}',
+ }
+ MANIFEST
expect_output('create_normal_entry')
end
it "should add a new special entry if currently absent" do
- resource = Puppet::Type.type(:cron).new(
- :name => 'new special entry',
- :ensure => :present,
- :special => 'reboot',
- :command => 'echo "Booted" 1>&2',
- :environment => 'MAILTO=bob@company.com',
- :target => crontab_user1,
- :user => crontab_user1
- )
- run_in_catalog(resource)
+ apply_with_error_check(<<-MANIFEST)
+ cron {
+ 'new special entry':
+ ensure => present,
+ special => 'reboot',
+ command => 'echo "Booted" 1>&2',
+ environment => 'MAILTO=bob@company.com',
+ target => '#{crontab_user1}',
+ }
+ MANIFEST
expect_output('create_special_entry')
end
it "should change existing entry if out of sync" do
- resource = Puppet::Type.type(:cron).new(
- :name => 'Monthly job',
- :ensure => :present,
- :special => 'monthly',
-# :minute => ['22'],
- :command => '/usr/bin/monthly',
- :environment => [],
- :target => crontab_user1,
- :user => crontab_user1
- )
- run_in_catalog(resource)
+ apply_with_error_check(<<-MANIFEST)
+ cron {
+ 'Monthly job':
+ ensure => present,
+ special => 'monthly',
+ #minute => ['22'],
+ command => '/usr/bin/monthly',
+ environment => [],
+ target => '#{crontab_user1}',
+ }
+ MANIFEST
expect_output('modify_entry')
end
it "should change a special schedule to numeric if requested" do
- resource = Puppet::Type.type(:cron).new(
- :name => 'My daily failure',
- :special => 'absent',
- :command => '/bin/false',
- :target => crontab_user1,
- :user => crontab_user1
- )
- run_in_catalog(resource)
+ apply_with_error_check(<<-MANIFEST)
+ cron {
+ 'My daily failure':
+ special => 'absent',
+ command => '/bin/false',
+ target => '#{crontab_user1}',
+ }
+ MANIFEST
expect_output('unspecialized')
end
it "should not try to move an entry from one file to another" do
# force the parsedfile provider to also parse user1's crontab
- random_resource = Puppet::Type.type(:cron).new(
- :name => 'foo',
- :ensure => :absent,
- :target => crontab_user1,
- :user => crontab_user1
- )
- resource = Puppet::Type.type(:cron).new(
- :name => 'My daily failure',
- :special => 'daily',
- :command => "/bin/false",
- :target => crontab_user2,
- :user => crontab_user2
- )
- run_in_catalog(resource)
+ apply_with_error_check(<<-MANIFEST)
+ cron {
+ 'foo':
+ ensure => absent,
+ target => '#{crontab_user1}';
+ 'My daily failure':
+ special => 'daily',
+ command => "/bin/false",
+ target => '#{crontab_user2}',
+ }
+ MANIFEST
File.read(crontab_user1).should == File.read(my_fixture('moved_cronjob_input1'))
File.read(crontab_user2).should == File.read(my_fixture('moved_cronjob_input2'))
end
diff --git a/spec/integration/ssl/certificate_authority_spec.rb b/spec/integration/ssl/certificate_authority_spec.rb
index acbc38cbc..3cf494afa 100755
--- a/spec/integration/ssl/certificate_authority_spec.rb
+++ b/spec/integration/ssl/certificate_authority_spec.rb
@@ -99,6 +99,32 @@ describe Puppet::SSL::CertificateAuthority, :unless => Puppet.features.microsoft
end
end
+ describe "when revoking certificate" do
+ it "should work for one certificate" do
+ certificate_request_for("luke.madstop.com")
+
+ ca.sign("luke.madstop.com")
+ ca.revoke("luke.madstop.com")
+
+ expect { ca.verify("luke.madstop.com") }.to raise_error(
+ Puppet::SSL::CertificateAuthority::CertificateVerificationError,
+ "certificate revoked"
+ )
+ end
+
+ it "should work for several certificates" do
+ 3.times.each do |c|
+ certificate_request_for("luke.madstop.com")
+ ca.sign("luke.madstop.com")
+ ca.destroy("luke.madstop.com")
+ end
+ ca.revoke("luke.madstop.com")
+
+ ca.crl.content.revoked.map { |r| r.serial }.should == [2,3,4] # ca has serial 1
+ end
+
+ end
+
it "allows autosigning certificates concurrently", :unless => Puppet::Util::Platform.windows? do
Puppet[:autosign] = true
hosts = (0..4).collect { |i| certificate_request_for("host#{i}") }
diff --git a/spec/integration/ssl/certificate_request_spec.rb b/spec/integration/ssl/certificate_request_spec.rb
index 4a035d532..eeb29da79 100755
--- a/spec/integration/ssl/certificate_request_spec.rb
+++ b/spec/integration/ssl/certificate_request_spec.rb
@@ -10,8 +10,6 @@ describe Puppet::SSL::CertificateRequest do
# Get a safe temporary file
dir = tmpdir("csr_integration_testing")
- Puppet.settings.clear
-
Puppet.settings[:confdir] = dir
Puppet.settings[:vardir] = dir
Puppet.settings[:group] = Process.gid
@@ -26,10 +24,6 @@ describe Puppet::SSL::CertificateRequest do
Puppet::SSL::CertificateRequest.indirection.termini.clear
end
- after do
- Puppet.settings.clear
- end
-
it "should be able to generate CSRs" do
@csr.generate(@key)
end
diff --git a/spec/integration/ssl/certificate_revocation_list_spec.rb b/spec/integration/ssl/certificate_revocation_list_spec.rb
index 06a69a741..ec344926b 100755
--- a/spec/integration/ssl/certificate_revocation_list_spec.rb
+++ b/spec/integration/ssl/certificate_revocation_list_spec.rb
@@ -20,8 +20,6 @@ describe Puppet::SSL::CertificateRevocationList do
after {
Puppet::SSL::Host.ca_location = :none
- Puppet.settings.clear
-
# This is necessary so the terminus instances don't lie around.
Puppet::SSL::Host.indirection.termini.clear
}
diff --git a/spec/integration/ssl/host_spec.rb b/spec/integration/ssl/host_spec.rb
index fbb108db7..18f0d17fc 100755
--- a/spec/integration/ssl/host_spec.rb
+++ b/spec/integration/ssl/host_spec.rb
@@ -22,8 +22,6 @@ describe Puppet::SSL::Host do
after {
Puppet::SSL::Host.ca_location = :none
-
- Puppet.settings.clear
}
it "should be considered a CA host if its name is equal to 'ca'" do
diff --git a/spec/integration/transaction_spec.rb b/spec/integration/transaction_spec.rb
index fc1fff228..35557b8f2 100755
--- a/spec/integration/transaction_spec.rb
+++ b/spec/integration/transaction_spec.rb
@@ -193,6 +193,22 @@ describe Puppet::Transaction do
Puppet::FileSystem.exist?(file2).should be_true
end
+ it "should apply no resources whatsoever if a pre_run_check fails" do
+ path = tmpfile("path")
+ file = Puppet::Type.type(:file).new(
+ :path => path,
+ :ensure => "file"
+ )
+ notify = Puppet::Type.type(:notify).new(
+ :title => "foo"
+ )
+ notify.expects(:pre_run_check).raises(Puppet::Error, "fail for testing")
+
+ catalog = mk_catalog(file, notify)
+ catalog.apply
+ Puppet::FileSystem.exist?(path).should_not be_true
+ end
+
it "should not let one failed refresh result in other refreshes failing" do
path = tmpfile("path")
newfile = tmpfile("file")
diff --git a/spec/integration/type/file_spec.rb b/spec/integration/type/file_spec.rb
index ed8a9768f..d1862e241 100755
--- a/spec/integration/type/file_spec.rb
+++ b/spec/integration/type/file_spec.rb
@@ -78,7 +78,7 @@ describe Puppet::Type.type(:file), :uses_checksums => true do
it "should not attempt to manage files that do not exist if no means of creating the file is specified" do
source = tmpfile('source')
- catalog.add_resource described_class.new :path => source, :mode => 0755
+ catalog.add_resource described_class.new :path => source, :mode => '0755'
status = catalog.apply.report.resource_statuses["File[#{source}]"]
status.should_not be_failed
@@ -155,7 +155,7 @@ describe Puppet::Type.type(:file), :uses_checksums => true do
it "should set executable bits for existing readable directories" do
set_mode(0600, target)
- catalog.add_resource described_class.new(:path => target, :ensure => :directory, :mode => 0644)
+ catalog.add_resource described_class.new(:path => target, :ensure => :directory, :mode => '0644')
catalog.apply
(get_mode(target) & 07777).should == 0755
@@ -992,13 +992,13 @@ describe Puppet::Type.type(:file), :uses_checksums => true do
describe "on Windows systems", :if => Puppet.features.microsoft_windows? do
def expects_sid_granted_full_access_explicitly(path, sid)
- inherited_ace = Windows::Security::INHERITED_ACE
+ inherited_ace = Puppet::Util::Windows::AccessControlEntry::INHERITED_ACE
aces = get_aces_for_path_by_sid(path, sid)
aces.should_not be_empty
aces.each do |ace|
- ace.mask.should == Windows::File::FILE_ALL_ACCESS
+ ace.mask.should == Puppet::Util::Windows::File::FILE_ALL_ACCESS
(ace.flags & inherited_ace).should_not == inherited_ace
end
end
@@ -1008,13 +1008,13 @@ describe Puppet::Type.type(:file), :uses_checksums => true do
end
def expects_at_least_one_inherited_ace_grants_full_access(path, sid)
- inherited_ace = Windows::Security::INHERITED_ACE
+ inherited_ace = Puppet::Util::Windows::AccessControlEntry::INHERITED_ACE
aces = get_aces_for_path_by_sid(path, sid)
aces.should_not be_empty
aces.any? do |ace|
- ace.mask == Windows::File::FILE_ALL_ACCESS &&
+ ace.mask == Puppet::Util::Windows::File::FILE_ALL_ACCESS &&
(ace.flags & inherited_ace) == inherited_ace
end.should be_true
end
@@ -1045,10 +1045,10 @@ describe Puppet::Type.type(:file), :uses_checksums => true do
describe "when processing SYSTEM ACEs" do
before do
@sids = {
- :current_user => Puppet::Util::Windows::Security.name_to_sid(Sys::Admin.get_login),
+ :current_user => Puppet::Util::Windows::SID.name_to_sid(Puppet::Util::Windows::ADSI::User.current_user_name),
:system => Win32::Security::SID::LocalSystem,
- :admin => Puppet::Util::Windows::Security.name_to_sid("Administrator"),
- :guest => Puppet::Util::Windows::Security.name_to_sid("Guest"),
+ :admin => Puppet::Util::Windows::SID.name_to_sid("Administrator"),
+ :guest => Puppet::Util::Windows::SID.name_to_sid("Guest"),
:users => Win32::Security::SID::BuiltinUsers,
:power_users => Win32::Security::SID::PowerUsers,
:none => Win32::Security::SID::Nobody
@@ -1132,7 +1132,7 @@ describe Puppet::Type.type(:file), :uses_checksums => true do
system_aces.should_not be_empty
system_aces.each do |ace|
- ace.mask.should == Windows::File::FILE_GENERIC_READ
+ ace.mask.should == Puppet::Util::Windows::File::FILE_GENERIC_READ
end
end
@@ -1173,8 +1173,9 @@ describe Puppet::Type.type(:file), :uses_checksums => true do
sd = Puppet::Util::Windows::Security.get_security_descriptor(path)
sd.dacl.allow(
'S-1-1-0', #everyone
- Windows::File::FILE_ALL_ACCESS,
- Windows::File::OBJECT_INHERIT_ACE | Windows::File::CONTAINER_INHERIT_ACE)
+ Puppet::Util::Windows::File::FILE_ALL_ACCESS,
+ Puppet::Util::Windows::AccessControlEntry::OBJECT_INHERIT_ACE |
+ Puppet::Util::Windows::AccessControlEntry::CONTAINER_INHERIT_ACE)
Puppet::Util::Windows::Security.set_security_descriptor(path, sd)
end
@@ -1252,7 +1253,7 @@ describe Puppet::Type.type(:file), :uses_checksums => true do
system_aces.each do |ace|
# unlike files, Puppet sets execute bit on directories that are readable
- ace.mask.should == Windows::File::FILE_GENERIC_READ | Windows::File::FILE_GENERIC_EXECUTE
+ ace.mask.should == Puppet::Util::Windows::File::FILE_GENERIC_READ | Puppet::Util::Windows::File::FILE_GENERIC_EXECUTE
end
end
diff --git a/spec/integration/type/nagios_spec.rb b/spec/integration/type/nagios_spec.rb
index 818b61649..9610daefa 100644
--- a/spec/integration/type/nagios_spec.rb
+++ b/spec/integration/type/nagios_spec.rb
@@ -6,9 +6,11 @@ require 'puppet/file_bucket/dipper'
describe "Nagios file creation" do
include PuppetSpec::Files
+ let(:initial_mode) { 0600 }
+
before :each do
FileUtils.touch(target_file)
- File.chmod(0600, target_file)
+ Puppet::FileSystem.chmod(initial_mode, target_file)
Puppet::FileBucket::Dipper.any_instance.stubs(:backup) # Don't backup to filebucket
end
@@ -33,13 +35,6 @@ describe "Nagios file creation" do
catalog.apply
end
- # These three helpers are from file_spec.rb
- #
- # @todo Define those centrally as well?
- def get_mode(file)
- Puppet::FileSystem.stat(file).mode
- end
-
context "when creating a nagios config file" do
context "which is not managed" do
it "should choose the file mode if requested" do
@@ -51,14 +46,12 @@ describe "Nagios file creation" do
:mode => '0640'
)
run_in_catalog(resource)
- # sticky bit only applies to directories in Windows
- mode = Puppet.features.microsoft_windows? ? "640" : "100640"
- ( "%o" % get_mode(target_file) ).should == mode
+ expect_file_mode(target_file, "640")
end
end
context "which is managed" do
- it "should not the mode" do
+ it "should not override the mode" do
file_res = Puppet::Type.type(:file).new(
:name => target_file,
:ensure => :present
@@ -71,10 +64,8 @@ describe "Nagios file creation" do
:mode => '0640'
)
run_in_catalog(file_res, nag_res)
- ( "%o" % get_mode(target_file) ).should_not == "100640"
+ expect_file_mode(target_file, initial_mode.to_s(8))
end
end
-
end
-
end
diff --git a/spec/integration/type/sshkey_spec.rb b/spec/integration/type/sshkey_spec.rb
new file mode 100644
index 000000000..d1b1e01c7
--- /dev/null
+++ b/spec/integration/type/sshkey_spec.rb
@@ -0,0 +1,22 @@
+#! /usr/bin/env ruby
+require 'spec_helper'
+require 'puppet_spec/files'
+require 'puppet_spec/compiler'
+
+describe Puppet::Type.type(:sshkey), '(integration)', :unless => Puppet.features.microsoft_windows? do
+ include PuppetSpec::Files
+ include PuppetSpec::Compiler
+
+ let(:target) { tmpfile('ssh_known_hosts') }
+ let(:manifest) { "sshkey { 'test':
+ ensure => 'present',
+ type => 'rsa',
+ key => 'TESTKEY',
+ target => '#{target}' }"
+ }
+
+ it "should create a new known_hosts file with mode 0644" do
+ apply_compiled_manifest(manifest)
+ expect_file_mode(target, "644")
+ end
+end
diff --git a/spec/integration/type/tidy_spec.rb b/spec/integration/type/tidy_spec.rb
index 9c044d703..7dbefb6ca 100755
--- a/spec/integration/type/tidy_spec.rb
+++ b/spec/integration/type/tidy_spec.rb
@@ -23,6 +23,9 @@ describe Puppet::Type.type(:tidy) do
catalog = Puppet::Resource::Catalog.new
catalog.add_resource(tidy)
+ # avoid crude failures because of nil resources that result
+ # from implicit containment and lacking containers
+ catalog.stubs(:container_of).returns tidy
catalog.apply
diff --git a/spec/integration/type/user_spec.rb b/spec/integration/type/user_spec.rb
new file mode 100644
index 000000000..4724fe9d5
--- /dev/null
+++ b/spec/integration/type/user_spec.rb
@@ -0,0 +1,36 @@
+#! /usr/bin/env ruby
+require 'spec_helper'
+require 'puppet_spec/files'
+require 'puppet_spec/compiler'
+
+describe Puppet::Type.type(:user), '(integration)', :unless => Puppet.features.microsoft_windows? do
+ include PuppetSpec::Files
+ include PuppetSpec::Compiler
+
+ context "when set to purge ssh keys from a file" do
+ let(:tempfile) { file_containing('user_spec', "# comment\nssh-rsa KEY-DATA key-name\nssh-rsa KEY-DATA key name\n") }
+ # must use an existing user, or the generated key resource
+ # will fail on account of an invalid user for the key
+ # - root should be a safe default
+ let(:manifest) { "user { 'root': purge_ssh_keys => '#{tempfile}' }" }
+
+ it "should purge authorized ssh keys" do
+ apply_compiled_manifest(manifest)
+ File.read(tempfile).should_not =~ /key-name/
+ end
+
+ it "should purge keys with spaces in the comment string" do
+ apply_compiled_manifest(manifest)
+ File.read(tempfile).should_not =~ /key name/
+ end
+
+ context "with other prefetching resources evaluated first" do
+ let(:manifest) { "host { 'test': before => User[root] } user { 'root': purge_ssh_keys => '#{tempfile}' }" }
+
+ it "should purge authorized ssh keys" do
+ apply_compiled_manifest(manifest)
+ File.read(tempfile).should_not =~ /key-name/
+ end
+ end
+ end
+end
diff --git a/spec/integration/util/autoload_spec.rb b/spec/integration/util/autoload_spec.rb
index bfe8b67d2..c352fea9e 100755
--- a/spec/integration/util/autoload_spec.rb
+++ b/spec/integration/util/autoload_spec.rb
@@ -95,12 +95,12 @@ describe Puppet::Util::Autoload do
file = File.join(libdir, "plugin.rb")
- Puppet[:modulepath] = modulepath
-
- with_loader("foo", "foo") do |dir, loader|
- with_file(:plugin, file.split("/")) do
- loader.load(:plugin)
- loader.class.should be_loaded("foo/plugin.rb")
+ Puppet.override(:environments => Puppet::Environments::Static.new(Puppet::Node::Environment.create(:production, [modulepath]))) do
+ with_loader("foo", "foo") do |dir, loader|
+ with_file(:plugin, file.split("/")) do
+ loader.load(:plugin)
+ loader.class.should be_loaded("foo/plugin.rb")
+ end
end
end
end
diff --git a/spec/integration/util/rdoc/parser_spec.rb b/spec/integration/util/rdoc/parser_spec.rb
index d3bbef45c..2fdaf8019 100755
--- a/spec/integration/util/rdoc/parser_spec.rb
+++ b/spec/integration/util/rdoc/parser_spec.rb
@@ -127,6 +127,13 @@ end
end.should_not(be_empty, "Could not match #{content_patterns} in any of the files found in #{glob}")
end
+ around(:each) do |example|
+ env = Puppet::Node::Environment.create(:doc_test_env, [modules_dir], manifests_dir)
+ Puppet.override({:environments => Puppet::Environments::Static.new(env), :current_environment => env}) do
+ example.run
+ end
+ end
+
before :each do
prepare_manifests_and_modules
Puppet.settings[:document_all] = document_all
diff --git a/spec/integration/util/windows/process_spec.rb b/spec/integration/util/windows/process_spec.rb
index 6dc54d228..60eae3443 100644
--- a/spec/integration/util/windows/process_spec.rb
+++ b/spec/integration/util/windows/process_spec.rb
@@ -18,5 +18,17 @@ describe "Puppet::Util::Windows::Process", :if => Puppet.features.microsoft_wind
Puppet::Util::Windows::User.should be_admin
Puppet::Util::Windows::Process.process_privilege_symlink?.should be_false
end
+
+ it "should be able to lookup a standard Windows process privilege" do
+ Puppet::Util::Windows::Process.lookup_privilege_value('SeShutdownPrivilege') do |luid|
+ luid.should_not be_nil
+ luid.should be_instance_of(Puppet::Util::Windows::Process::LUID)
+ end
+ end
+
+ it "should raise an error for an unknown privilege name" do
+ fail_msg = /LookupPrivilegeValue\(, foo, .*\): A specified privilege does not exist/
+ expect { Puppet::Util::Windows::Process.lookup_privilege_value('foo') }.to raise_error(Puppet::Util::Windows::Error, fail_msg)
+ end
end
end
diff --git a/spec/integration/util/windows/security_spec.rb b/spec/integration/util/windows/security_spec.rb
index fa0eadc0d..7f7aa7cb6 100755
--- a/spec/integration/util/windows/security_spec.rb
+++ b/spec/integration/util/windows/security_spec.rb
@@ -1,8 +1,6 @@
#!/usr/bin/env ruby
require 'spec_helper'
-require 'puppet/util/adsi'
-
if Puppet.features.microsoft_windows?
class WindowsSecurityTester
require 'puppet/util/windows/security'
@@ -15,11 +13,11 @@ describe "Puppet::Util::Windows::Security", :if => Puppet.features.microsoft_win
before :all do
@sids = {
- :current_user => Puppet::Util::Windows::Security.name_to_sid(Sys::Admin.get_login),
+ :current_user => Puppet::Util::Windows::SID.name_to_sid(Puppet::Util::Windows::ADSI::User.current_user_name),
:system => Win32::Security::SID::LocalSystem,
- :admin => Puppet::Util::Windows::Security.name_to_sid("Administrator"),
+ :admin => Puppet::Util::Windows::SID.name_to_sid("Administrator"),
:administrators => Win32::Security::SID::BuiltinAdministrators,
- :guest => Puppet::Util::Windows::Security.name_to_sid("Guest"),
+ :guest => Puppet::Util::Windows::SID.name_to_sid("Guest"),
:users => Win32::Security::SID::BuiltinUsers,
:power_users => Win32::Security::SID::PowerUsers,
:none => Win32::Security::SID::Nobody,
@@ -31,11 +29,12 @@ describe "Puppet::Util::Windows::Security", :if => Puppet.features.microsoft_win
# (like \\localhost) to fail with unhelpful error messages.
# Put a check for this upfront to aid debug should this strike again.
service = Puppet::Type.type(:service).new(:name => 'lmhosts')
- service.provider.status.should == :running
+ expect(service.provider.status).to eq(:running), 'lmhosts service is not running'
end
let (:sids) { @sids }
let (:winsec) { WindowsSecurityTester.new }
+ let (:klass) { Puppet::Util::Windows::File }
def set_group_depending_on_current_user(path)
if sids[:current_user] == sids[:system]
@@ -53,8 +52,8 @@ describe "Puppet::Util::Windows::Security", :if => Puppet.features.microsoft_win
def grant_everyone_full_access(path)
sd = winsec.get_security_descriptor(path)
everyone = 'S-1-1-0'
- inherit = WindowsSecurityTester::OBJECT_INHERIT_ACE | WindowsSecurityTester::CONTAINER_INHERIT_ACE
- sd.dacl.allow(everyone, Windows::File::FILE_ALL_ACCESS, inherit)
+ inherit = Puppet::Util::Windows::AccessControlEntry::OBJECT_INHERIT_ACE | Puppet::Util::Windows::AccessControlEntry::CONTAINER_INHERIT_ACE
+ sd.dacl.allow(everyone, klass::FILE_ALL_ACCESS, inherit)
winsec.set_security_descriptor(path, sd)
end
@@ -178,7 +177,7 @@ describe "Puppet::Util::Windows::Security", :if => Puppet.features.microsoft_win
# when running under SYSTEM account, multiple ACEs come back
# so we only care that we have at least one of these
system_aces.any? do |ace|
- ace.mask == Windows::File::FILE_ALL_ACCESS
+ ace.mask == klass::FILE_ALL_ACCESS
end.should be_true
# changing the owner/group will no longer make the SD protected
@@ -186,7 +185,7 @@ describe "Puppet::Util::Windows::Security", :if => Puppet.features.microsoft_win
winsec.set_owner(sids[:administrators], path)
system_aces.find do |ace|
- ace.mask == Windows::File::FILE_ALL_ACCESS && ace.inherited?
+ ace.mask == klass::FILE_ALL_ACCESS && ace.inherited?
end.should_not be_nil
end
@@ -227,7 +226,7 @@ describe "Puppet::Util::Windows::Security", :if => Puppet.features.microsoft_win
# when running under SYSTEM account, multiple ACEs come back
# so we only care that we have at least one of these
system_aces.any? do |ace|
- ace.mask == WindowsSecurityTester::FILE_ALL_ACCESS
+ ace.mask == klass::FILE_ALL_ACCESS
end.should be_true
# changing the mode will make the SD protected
@@ -237,7 +236,7 @@ describe "Puppet::Util::Windows::Security", :if => Puppet.features.microsoft_win
# and should have a non-inherited SYSTEM ACE(s)
system_aces = winsec.get_aces_for_path_by_sid(path, sids[:system])
system_aces.each do |ace|
- ace.mask.should == Windows::File::FILE_ALL_ACCESS && ! ace.inherited?
+ ace.mask.should == klass::FILE_ALL_ACCESS && ! ace.inherited?
end
end
@@ -259,25 +258,25 @@ describe "Puppet::Util::Windows::Security", :if => Puppet.features.microsoft_win
before :each do
winsec.set_group(sids[:none], path)
winsec.set_mode(0600, path)
- winsec.add_attributes(path, WindowsSecurityTester::FILE_ATTRIBUTE_READONLY)
- (winsec.get_attributes(path) & WindowsSecurityTester::FILE_ATTRIBUTE_READONLY).should be_nonzero
+ Puppet::Util::Windows::File.add_attributes(path, klass::FILE_ATTRIBUTE_READONLY)
+ (Puppet::Util::Windows::File.get_attributes(path) & klass::FILE_ATTRIBUTE_READONLY).should be_nonzero
end
it "should make them writable if any sid has write permission" do
winsec.set_mode(WindowsSecurityTester::S_IWUSR, path)
- (winsec.get_attributes(path) & WindowsSecurityTester::FILE_ATTRIBUTE_READONLY).should == 0
+ (Puppet::Util::Windows::File.get_attributes(path) & klass::FILE_ATTRIBUTE_READONLY).should == 0
end
it "should leave them read-only if no sid has write permission and should allow full access for SYSTEM" do
winsec.set_mode(WindowsSecurityTester::S_IRUSR | WindowsSecurityTester::S_IXGRP, path)
- (winsec.get_attributes(path) & WindowsSecurityTester::FILE_ATTRIBUTE_READONLY).should be_nonzero
+ (Puppet::Util::Windows::File.get_attributes(path) & klass::FILE_ATTRIBUTE_READONLY).should be_nonzero
system_aces = winsec.get_aces_for_path_by_sid(path, sids[:system])
# when running under SYSTEM account, and set_group / set_owner hasn't been called
# SYSTEM full access will be restored
system_aces.any? do |ace|
- ace.mask == Windows::File::FILE_ALL_ACCESS
+ ace.mask == klass::FILE_ALL_ACCESS
end.should be_true
end
end
@@ -291,7 +290,7 @@ describe "Puppet::Util::Windows::Security", :if => Puppet.features.microsoft_win
it "should report when extra aces are encounted" do
sd = winsec.get_security_descriptor(path)
(544..547).each do |rid|
- sd.dacl.allow("S-1-5-32-#{rid}", WindowsSecurityTester::STANDARD_RIGHTS_ALL)
+ sd.dacl.allow("S-1-5-32-#{rid}", klass::STANDARD_RIGHTS_ALL)
end
winsec.set_security_descriptor(path, sd)
@@ -301,12 +300,12 @@ describe "Puppet::Util::Windows::Security", :if => Puppet.features.microsoft_win
it "should return deny aces" do
sd = winsec.get_security_descriptor(path)
- sd.dacl.deny(sids[:guest], WindowsSecurityTester::FILE_GENERIC_WRITE)
+ sd.dacl.deny(sids[:guest], klass::FILE_GENERIC_WRITE)
winsec.set_security_descriptor(path, sd)
guest_aces = winsec.get_aces_for_path_by_sid(path, sids[:guest])
guest_aces.find do |ace|
- ace.type == WindowsSecurityTester::ACCESS_DENIED_ACE_TYPE
+ ace.type == Puppet::Util::Windows::AccessControlEntry::ACCESS_DENIED_ACE_TYPE
end.should_not be_nil
end
@@ -314,12 +313,12 @@ describe "Puppet::Util::Windows::Security", :if => Puppet.features.microsoft_win
sd = winsec.get_security_descriptor(path)
dacl = Puppet::Util::Windows::AccessControlList.new
dacl.allow(
- sids[:current_user], WindowsSecurityTester::STANDARD_RIGHTS_ALL | WindowsSecurityTester::SPECIFIC_RIGHTS_ALL
+ sids[:current_user], klass::STANDARD_RIGHTS_ALL | klass::SPECIFIC_RIGHTS_ALL
)
dacl.allow(
sids[:everyone],
- WindowsSecurityTester::FILE_GENERIC_READ,
- WindowsSecurityTester::INHERIT_ONLY_ACE | WindowsSecurityTester::OBJECT_INHERIT_ACE
+ klass::FILE_GENERIC_READ,
+ Puppet::Util::Windows::AccessControlEntry::INHERIT_ONLY_ACE | Puppet::Util::Windows::AccessControlEntry::OBJECT_INHERIT_ACE
)
winsec.set_security_descriptor(path, sd)
@@ -344,8 +343,8 @@ describe "Puppet::Util::Windows::Security", :if => Puppet.features.microsoft_win
it "should be present when the access control list is unprotected" do
# add a bunch of aces to the parent with permission to add children
- allow = WindowsSecurityTester::STANDARD_RIGHTS_ALL | WindowsSecurityTester::SPECIFIC_RIGHTS_ALL
- inherit = WindowsSecurityTester::OBJECT_INHERIT_ACE | WindowsSecurityTester::CONTAINER_INHERIT_ACE
+ allow = klass::STANDARD_RIGHTS_ALL | klass::SPECIFIC_RIGHTS_ALL
+ inherit = Puppet::Util::Windows::AccessControlEntry::OBJECT_INHERIT_ACE | Puppet::Util::Windows::AccessControlEntry::CONTAINER_INHERIT_ACE
sd = winsec.get_security_descriptor(parent)
sd.dacl.allow(
@@ -356,7 +355,7 @@ describe "Puppet::Util::Windows::Security", :if => Puppet.features.microsoft_win
(544..547).each do |rid|
sd.dacl.allow(
"S-1-5-32-#{rid}",
- WindowsSecurityTester::STANDARD_RIGHTS_ALL,
+ klass::STANDARD_RIGHTS_ALL,
inherit
)
end
@@ -371,10 +370,12 @@ describe "Puppet::Util::Windows::Security", :if => Puppet.features.microsoft_win
describe "for an administrator", :if => Puppet.features.root? do
before :each do
+ is_dir = Puppet::FileSystem.directory?(path)
winsec.set_mode(WindowsSecurityTester::S_IRWXU | WindowsSecurityTester::S_IRWXG, path)
set_group_depending_on_current_user(path)
winsec.set_owner(sids[:guest], path)
- lambda { File.open(path, 'r') }.should raise_error(Errno::EACCES)
+ expected_error = RUBY_VERSION =~ /^2\./ && is_dir ? Errno::EISDIR : Errno::EACCES
+ lambda { File.open(path, 'r') }.should raise_error(expected_error)
end
after :each do
@@ -446,14 +447,14 @@ describe "Puppet::Util::Windows::Security", :if => Puppet.features.microsoft_win
describe "when the sid refers to a deleted trustee" do
it "should retrieve the user sid" do
sid = nil
- user = Puppet::Util::ADSI::User.create("delete_me_user")
+ user = Puppet::Util::Windows::ADSI::User.create("delete_me_user")
user.commit
begin
- sid = Sys::Admin::get_user(user.name).sid
+ sid = Puppet::Util::Windows::ADSI::User.new(user.name).sid.to_s
winsec.set_owner(sid, path)
winsec.set_mode(WindowsSecurityTester::S_IRWXU, path)
ensure
- Puppet::Util::ADSI::User.delete(user.name)
+ Puppet::Util::Windows::ADSI::User.delete(user.name)
end
winsec.get_owner(path).should == sid
@@ -462,14 +463,14 @@ describe "Puppet::Util::Windows::Security", :if => Puppet.features.microsoft_win
it "should retrieve the group sid" do
sid = nil
- group = Puppet::Util::ADSI::Group.create("delete_me_group")
+ group = Puppet::Util::Windows::ADSI::Group.create("delete_me_group")
group.commit
begin
- sid = Sys::Admin::get_group(group.name).sid
+ sid = Puppet::Util::Windows::ADSI::Group.new(group.name).sid.to_s
winsec.set_group(sid, path)
winsec.set_mode(WindowsSecurityTester::S_IRWXG, path)
ensure
- Puppet::Util::ADSI::Group.delete(group.name)
+ Puppet::Util::Windows::ADSI::Group.delete(group.name)
end
winsec.get_group(path).should == sid
winsec.get_mode(path).should == WindowsSecurityTester::S_IRWXG
@@ -813,7 +814,7 @@ describe "Puppet::Util::Windows::Security", :if => Puppet.features.microsoft_win
Puppet::Util::Windows::AccessControlEntry::CONTAINER_INHERIT_ACE
sd = winsec.get_security_descriptor(dir)
- sd.dacl.allow(sd.owner, Windows::File::FILE_ALL_ACCESS, inherit_flags)
+ sd.dacl.allow(sd.owner, klass::FILE_ALL_ACCESS, inherit_flags)
winsec.set_security_descriptor(dir, sd)
sd = winsec.get_security_descriptor(dir)
@@ -834,7 +835,7 @@ describe "Puppet::Util::Windows::Security", :if => Puppet.features.microsoft_win
Puppet::Util::Windows::AccessControlEntry::CONTAINER_INHERIT_ACE
sd = winsec.get_security_descriptor(dir)
- sd.dacl.deny(sids[:guest], Windows::File::FILE_ALL_ACCESS, inherit_flags)
+ sd.dacl.deny(sids[:guest], klass::FILE_ALL_ACCESS, inherit_flags)
winsec.set_security_descriptor(dir, sd)
sd = winsec.get_security_descriptor(dir)
diff --git a/spec/integration/util/windows/user_spec.rb b/spec/integration/util/windows/user_spec.rb
index 0435b2cdc..4e873b34c 100755
--- a/spec/integration/util/windows/user_spec.rb
+++ b/spec/integration/util/windows/user_spec.rb
@@ -10,23 +10,23 @@ describe "Puppet::Util::Windows::User", :if => Puppet.features.microsoft_windows
it "should be an admin if user's token contains the Administrators SID" do
Puppet::Util::Windows::User.expects(:check_token_membership).returns(true)
- Win32::Security.expects(:elevated_security?).never
+ Puppet::Util::Windows::Process.expects(:elevated_security?).never
Puppet::Util::Windows::User.should be_admin
end
it "should not be an admin if user's token doesn't contain the Administrators SID" do
Puppet::Util::Windows::User.expects(:check_token_membership).returns(false)
- Win32::Security.expects(:elevated_security?).never
+ Puppet::Util::Windows::Process.expects(:elevated_security?).never
Puppet::Util::Windows::User.should_not be_admin
end
it "should raise an exception if we can't check token membership" do
- Puppet::Util::Windows::User.expects(:check_token_membership).raises(Win32::Security::Error, "Access denied.")
- Win32::Security.expects(:elevated_security?).never
+ Puppet::Util::Windows::User.expects(:check_token_membership).raises(Puppet::Util::Windows::Error, "Access denied.")
+ Puppet::Util::Windows::Process.expects(:elevated_security?).never
- lambda { Puppet::Util::Windows::User.admin? }.should raise_error(Win32::Security::Error, /Access denied./)
+ lambda { Puppet::Util::Windows::User.admin? }.should raise_error(Puppet::Util::Windows::Error, /Access denied./)
end
end
@@ -36,24 +36,90 @@ describe "Puppet::Util::Windows::User", :if => Puppet.features.microsoft_windows
end
it "should be an admin if user is running with elevated privileges" do
- Win32::Security.stubs(:elevated_security?).returns(true)
+ Puppet::Util::Windows::Process.stubs(:elevated_security?).returns(true)
Puppet::Util::Windows::User.expects(:check_token_membership).never
Puppet::Util::Windows::User.should be_admin
end
it "should not be an admin if user is not running with elevated privileges" do
- Win32::Security.stubs(:elevated_security?).returns(false)
+ Puppet::Util::Windows::Process.stubs(:elevated_security?).returns(false)
Puppet::Util::Windows::User.expects(:check_token_membership).never
Puppet::Util::Windows::User.should_not be_admin
end
it "should raise an exception if the process fails to open the process token" do
- Win32::Security.stubs(:elevated_security?).raises(Win32::Security::Error, "Access denied.")
+ Puppet::Util::Windows::Process.stubs(:elevated_security?).raises(Puppet::Util::Windows::Error, "Access denied.")
Puppet::Util::Windows::User.expects(:check_token_membership).never
- lambda { Puppet::Util::Windows::User.admin? }.should raise_error(Win32::Security::Error, /Access denied./)
+ lambda { Puppet::Util::Windows::User.admin? }.should raise_error(Puppet::Util::Windows::Error, /Access denied./)
+ end
+ end
+
+ describe "module function" do
+ let(:username) { 'fabio' }
+ let(:bad_password) { 'goldilocks' }
+ let(:logon_fail_msg) { /Failed to logon user "fabio": Logon failure: unknown user name or bad password./ }
+
+ def expect_logon_failure_error(&block)
+ expect {
+ yield
+ }.to raise_error { |error|
+ expect(error).to be_a(Puppet::Util::Windows::Error)
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/ms681385(v=vs.85).aspx
+ # ERROR_LOGON_FAILURE 1326
+ expect(error.code).to eq(1326)
+ }
+ end
+
+ describe "load_profile" do
+ it "should raise an error when provided with an incorrect username and password" do
+ expect_logon_failure_error {
+ Puppet::Util::Windows::User.load_profile(username, bad_password)
+ }
+ end
+
+ it "should raise an error when provided with an incorrect username and nil password" do
+ expect_logon_failure_error {
+ Puppet::Util::Windows::User.load_profile(username, nil)
+ }
+ end
+ end
+
+ describe "logon_user" do
+ it "should raise an error when provided with an incorrect username and password" do
+ expect_logon_failure_error {
+ Puppet::Util::Windows::User.logon_user(username, bad_password)
+ }
+ end
+
+ it "should raise an error when provided with an incorrect username and nil password" do
+ expect_logon_failure_error {
+ Puppet::Util::Windows::User.logon_user(username, nil)
+ }
+ end
+ end
+
+ describe "password_is?" do
+ it "should return false given an incorrect username and password" do
+ Puppet::Util::Windows::User.password_is?(username, bad_password).should be_false
+ end
+
+ it "should return false given an incorrect username and nil password" do
+ Puppet::Util::Windows::User.password_is?(username, nil).should be_false
+ end
+
+ it "should return false given a nil username and an incorrect password" do
+ Puppet::Util::Windows::User.password_is?(nil, bad_password).should be_false
+ end
+ end
+
+ describe "check_token_membership" do
+ it "should not raise an error" do
+ # added just to call an FFI code path on all platforms
+ lambda { Puppet::Util::Windows::User.check_token_membership }.should_not raise_error
+ end
end
end
end
diff --git a/spec/integration/util_spec.rb b/spec/integration/util_spec.rb
index d8d96aad8..84f155123 100755
--- a/spec/integration/util_spec.rb
+++ b/spec/integration/util_spec.rb
@@ -36,7 +36,7 @@ describe Puppet::Util do
admins = 'S-1-5-32-544'
dacl = Puppet::Util::Windows::AccessControlList.new
- dacl.allow(admins, Windows::File::FILE_ALL_ACCESS)
+ dacl.allow(admins, Puppet::Util::Windows::File::FILE_ALL_ACCESS)
protect = true
expected_sd = Puppet::Util::Windows::SecurityDescriptor.new(admins, admins, dacl, protect)
Puppet::Util::Windows::Security.set_security_descriptor(file, expected_sd)
@@ -45,7 +45,7 @@ describe Puppet::Util do
Puppet::Util.replace_file(file, ignored_mode) do |temp_file|
ignored_sd = Puppet::Util::Windows::Security.get_security_descriptor(temp_file.path)
users = 'S-1-5-11'
- ignored_sd.dacl.allow(users, Windows::File::FILE_GENERIC_READ)
+ ignored_sd.dacl.allow(users, Puppet::Util::Windows::File::FILE_GENERIC_READ)
Puppet::Util::Windows::Security.set_security_descriptor(temp_file.path, ignored_sd)
end
diff --git a/spec/lib/matchers/resource.rb b/spec/lib/matchers/resource.rb
index 4498f959e..fc305d0d2 100644
--- a/spec/lib/matchers/resource.rb
+++ b/spec/lib/matchers/resource.rb
@@ -64,4 +64,5 @@ module Matchers; module Resource
@mismatch.empty? ? @matcher.failure_message_for_should : @mismatch
end
end
+ module_function :have_resource
end; end
diff --git a/spec/lib/puppet_spec/compiler.rb b/spec/lib/puppet_spec/compiler.rb
index cf4851807..4757705aa 100644
--- a/spec/lib/puppet_spec/compiler.rb
+++ b/spec/lib/puppet_spec/compiler.rb
@@ -1,7 +1,10 @@
module PuppetSpec::Compiler
+ module_function
+
def compile_to_catalog(string, node = Puppet::Node.new('foonode'))
Puppet[:code] = string
- Puppet::Parser::Compiler.compile(node)
+ # see lib/puppet/indirector/catalog/compiler.rb#filter
+ Puppet::Parser::Compiler.compile(node).filter { |r| r.virtual? }
end
def compile_to_ral(manifest)
@@ -19,7 +22,11 @@ module PuppetSpec::Compiler
end
def apply_compiled_manifest(manifest, prioritizer = Puppet::Graph::SequentialPrioritizer.new)
- transaction = Puppet::Transaction.new(compile_to_ral(manifest),
+ catalog = compile_to_ral(manifest)
+ if block_given?
+ catalog.resources.each { |res| yield res }
+ end
+ transaction = Puppet::Transaction.new(catalog,
Puppet::Transaction::Report.new("apply"),
prioritizer)
transaction.evaluate
@@ -28,6 +35,12 @@ module PuppetSpec::Compiler
transaction
end
+ def apply_with_error_check(manifest)
+ apply_compiled_manifest(manifest) do |res|
+ res.expects(:err).never
+ end
+ end
+
def order_resources_traversed_in(relationships)
order_seen = []
relationships.traverse { |resource| order_seen << resource.ref }
diff --git a/spec/lib/puppet_spec/files.rb b/spec/lib/puppet_spec/files.rb
index 1e1076b91..312c4fc95 100755
--- a/spec/lib/puppet_spec/files.rb
+++ b/spec/lib/puppet_spec/files.rb
@@ -75,4 +75,14 @@ module PuppetSpec::Files
$global_tempfiles ||= []
$global_tempfiles << tmp
end
+
+ def expect_file_mode(file, mode)
+ actual_mode = "%o" % Puppet::FileSystem.stat(file).mode
+ target_mode = if Puppet.features.microsoft_windows?
+ mode
+ else
+ "10" + "%04i" % mode.to_i
+ end
+ actual_mode.should == target_mode
+ end
end
diff --git a/spec/lib/puppet_spec/language.rb b/spec/lib/puppet_spec/language.rb
new file mode 100644
index 000000000..197c4b827
--- /dev/null
+++ b/spec/lib/puppet_spec/language.rb
@@ -0,0 +1,74 @@
+require 'puppet_spec/compiler'
+require 'matchers/resource'
+
+module PuppetSpec::Language
+ extend RSpec::Matchers::DSL
+
+ def produces(expectations)
+ calledFrom = caller
+ expectations.each do |manifest, resources|
+ it "evaluates #{manifest} to produce #{resources}" do
+ begin
+ case resources
+ when String
+ node = Puppet::Node.new('specification')
+ Puppet[:code] = manifest
+ compiler = Puppet::Parser::Compiler.new(node)
+ evaluator = Puppet::Pops::Parser::EvaluatingParser.new()
+
+ # see lib/puppet/indirector/catalog/compiler.rb#filter
+ catalog = compiler.compile.filter { |r| r.virtual? }
+
+ compiler.send(:instance_variable_set, :@catalog, catalog)
+
+ expect(evaluator.evaluate_string(compiler.topscope, resources)).to eq(true)
+ when Array
+ catalog = PuppetSpec::Compiler.compile_to_catalog(manifest)
+
+ if resources.empty?
+ base_resources = ["Class[Settings]", "Class[main]", "Stage[main]"]
+ expect(catalog.resources.collect(&:ref) - base_resources).to eq([])
+ else
+ resources.each do |reference|
+ if reference.is_a?(Array)
+ matcher = Matchers::Resource.have_resource(reference[0])
+ reference[1].each do |name, value|
+ matcher = matcher.with_parameter(name, value)
+ end
+ else
+ matcher = Matchers::Resource.have_resource(reference)
+ end
+
+ expect(catalog).to matcher
+ end
+ end
+ else
+ raise "Unsupported creates specification: #{resources.inspect}"
+ end
+ rescue Puppet::Error, RSpec::Expectations::ExpectationNotMetError => e
+ # provide the backtrace from the caller, or it is close to impossible to find some originators
+ e.set_backtrace(calledFrom)
+ raise
+ end
+
+ end
+ end
+ end
+
+ def fails(expectations)
+ calledFrom = caller
+ expectations.each do |manifest, pattern|
+ it "fails to evaluate #{manifest} with message #{pattern}" do
+ begin
+ expect do
+ PuppetSpec::Compiler.compile_to_catalog(manifest)
+ end.to raise_error(Puppet::Error, pattern)
+ rescue RSpec::Expectations::ExpectationNotMetError => e
+ # provide the backgrace from the caller, or it is close to impossible to find some originators
+ e.set_backtrace(calledFrom)
+ raise
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/puppet_spec/matchers.rb b/spec/lib/puppet_spec/matchers.rb
index 448bd1811..c01d8e89d 100644
--- a/spec/lib/puppet_spec/matchers.rb
+++ b/spec/lib/puppet_spec/matchers.rb
@@ -44,63 +44,82 @@ RSpec::Matchers.define :exit_with do |expected|
end
end
-class HavePrintedMatcher
- attr_accessor :expected, :actual
- def initialize(expected)
- case expected
+RSpec::Matchers.define :have_printed do |expected|
+
+ case expected
when String, Regexp
- @expected = expected
+ expected = expected
else
- @expected = expected.to_s
+ expected = expected.to_s
+ end
+
+ chain :and_exit_with do |code|
+ @expected_exit_code = code
+ end
+
+ define_method :matches_exit_code? do |actual|
+ @expected_exit_code.nil? || @expected_exit_code == actual
+ end
+
+ define_method :matches_output? do |actual|
+ return false unless actual
+ case expected
+ when String
+ actual.include?(expected)
+ when Regexp
+ expected.match(actual)
+ else
+ raise ArgumentError, "No idea how to match a #{actual.class.name}"
end
end
- def matches?(block)
+ match do |block|
+ $stderr = $stdout = StringIO.new
+ $stdout.set_encoding('UTF-8') if $stdout.respond_to?(:set_encoding)
+
begin
- $stderr = $stdout = StringIO.new
- $stdout.set_encoding('UTF-8') if $stdout.respond_to?(:set_encoding)
block.call
+ rescue SystemExit => e
+ raise unless @expected_exit_code
+ @actual_exit_code = e.status
+ ensure
$stdout.rewind
@actual = $stdout.read
- ensure
+
$stdout = STDOUT
$stderr = STDERR
end
- if @actual then
- case @expected
- when String
- @actual.include? @expected
- when Regexp
- @expected.match @actual
- end
- else
- false
- end
+ matches_output?(@actual) && matches_exit_code?(@actual_exit_code)
end
- def failure_message_for_should
- if @actual.nil? then
- "expected #{@expected.inspect}, but nothing was printed"
+ failure_message_for_should do |actual|
+ if actual.nil? then
+ "expected #{expected.inspect}, but nothing was printed"
else
- "expected #{@expected.inspect} to be printed; got:\n#{@actual}"
+ if !@expected_exit_code.nil? && matches_output?(actual)
+ "expected exit with code #{@expected_exit_code} but " +
+ (@actual_exit_code.nil? ? " exit was not called" : "exited with #{@actual_exit_code} instead")
+ else
+ "expected #{expected.inspect} to be printed; got:\n#{actual}"
+ end
end
end
- def failure_message_for_should_not
- "expected #{@expected.inspect} to not be printed; got:\n#{@actual}"
+ failure_message_for_should_not do |actual|
+ if @expected_exit_code && matches_exit_code?(@actual_exit_code)
+ "expected exit code to not be #{@actual_exit_code}"
+ else
+ "expected #{expected.inspect} to not be printed; got:\n#{actual}"
+ end
end
- def description
- "expect #{@expected.inspect} to be printed"
+ description do
+ "expect #{expected.inspect} to be printed" + (@expected_exit_code.nil ? '' : " with exit code #{@expected_exit_code}")
end
end
-def have_printed(what)
- HavePrintedMatcher.new(what)
-end
-
RSpec::Matchers.define :equal_attributes_of do |expected|
match do |actual|
actual.instance_variables.all? do |attr|
@@ -109,6 +128,14 @@ RSpec::Matchers.define :equal_attributes_of do |expected|
end
end
+RSpec::Matchers.define :equal_resource_attributes_of do |expected|
+ match do |actual|
+ actual.keys do |attr|
+ actual[attr] == expected[attr]
+ end
+ end
+end
+
RSpec::Matchers.define :be_one_of do |*expected|
match do |actual|
expected.include? actual
diff --git a/spec/lib/puppet_spec/module_tool/stub_source.rb b/spec/lib/puppet_spec/module_tool/stub_source.rb
index 5cee57c10..8b5f8bbe1 100644
--- a/spec/lib/puppet_spec/module_tool/stub_source.rb
+++ b/spec/lib/puppet_spec/module_tool/stub_source.rb
@@ -100,6 +100,9 @@ module PuppetSpec
"0.0.2" => { "pmtacceptance/stdlib" => ">= 2.2.0", "pmtacceptance/mysql" => ">= 0.0.1" },
"0.0.1" => { "pmtacceptance/stdlib" => ">= 2.2.0" },
},
+ 'puppetlabs-oneversion' => {
+ "0.0.1" => {}
+ }
}
@available_releases.each do |name, versions|
diff --git a/spec/shared_behaviours/hiera_indirections.rb b/spec/shared_behaviours/hiera_indirections.rb
new file mode 100644
index 000000000..fe6dc35ad
--- /dev/null
+++ b/spec/shared_behaviours/hiera_indirections.rb
@@ -0,0 +1,99 @@
+shared_examples_for "Hiera indirection" do |test_klass, fixture_dir|
+ include PuppetSpec::Files
+
+ def write_hiera_config(config_file, datadir)
+ File.open(config_file, 'w') do |f|
+ f.write("---
+ :yaml:
+ :datadir: #{datadir}
+ :hierarchy: ['global', 'invalid']
+ :logger: 'noop'
+ :backends: ['yaml']
+ ")
+ end
+ end
+
+ def request(key)
+ Puppet::Indirector::Request.new(:hiera, :find, key, nil)
+ end
+
+ before do
+ hiera_config_file = tmpfile("hiera.yaml")
+ Puppet.settings[:hiera_config] = hiera_config_file
+ write_hiera_config(hiera_config_file, fixture_dir)
+ end
+
+ after do
+ test_klass.instance_variable_set(:@hiera, nil)
+ end
+
+ it "should be the default data_binding terminus" do
+ Puppet.settings[:data_binding_terminus].should == :hiera
+ end
+
+ it "should raise an error if we don't have the hiera feature" do
+ Puppet.features.expects(:hiera?).returns(false)
+ lambda { test_klass.new }.should raise_error RuntimeError,
+ "Hiera terminus not supported without hiera library"
+ end
+
+ describe "the behavior of the hiera_config method", :if => Puppet.features.hiera? do
+ it "should override the logger and set it to puppet" do
+ test_klass.hiera_config[:logger].should == "puppet"
+ end
+
+ context "when the Hiera configuration file does not exist" do
+ let(:path) { File.expand_path('/doesnotexist') }
+
+ before do
+ Puppet.settings[:hiera_config] = path
+ end
+
+ it "should log a warning" do
+ Puppet.expects(:warning).with(
+ "Config file #{path} not found, using Hiera defaults")
+ test_klass.hiera_config
+ end
+
+ it "should only configure the logger and set it to puppet" do
+ Puppet.expects(:warning).with(
+ "Config file #{path} not found, using Hiera defaults")
+ test_klass.hiera_config.should == { :logger => 'puppet' }
+ end
+ end
+ end
+
+ describe "the behavior of the find method", :if => Puppet.features.hiera? do
+
+ let(:data_binder) { test_klass.new }
+
+ it "should support looking up an integer" do
+ data_binder.find(request("integer")).should == 3000
+ end
+
+ it "should support looking up a string" do
+ data_binder.find(request("string")).should == 'apache'
+ end
+
+ it "should support looking up an array" do
+ data_binder.find(request("array")).should == [
+ '0.ntp.puppetlabs.com',
+ '1.ntp.puppetlabs.com',
+ ]
+ end
+
+ it "should support looking up a hash" do
+ data_binder.find(request("hash")).should == {
+ 'user' => 'Hightower',
+ 'group' => 'admin',
+ 'mode' => '0644'
+ }
+ end
+
+ it "raises a data binding error if hiera cannot parse the yaml data" do
+ expect do
+ data_binder.find(request('invalid'))
+ end.to raise_error(Puppet::DataBinding::LookupError)
+ end
+ end
+end
diff --git a/spec/shared_behaviours/iterative_functions.rb b/spec/shared_behaviours/iterative_functions.rb
new file mode 100644
index 000000000..1910a3c80
--- /dev/null
+++ b/spec/shared_behaviours/iterative_functions.rb
@@ -0,0 +1,69 @@
+
+shared_examples_for 'all iterative functions hash handling' do |func|
+ it 'passes a hash entry as an array of the key and value' do
+ catalog = compile_to_catalog(<<-MANIFEST)
+ {a=>1}.#{func} |$v| { notify { "${v[0]} ${v[1]}": } }
+ MANIFEST
+
+ catalog.resource(:notify, "a 1").should_not be_nil
+ end
+end
+
+shared_examples_for 'all iterative functions argument checks' do |func|
+
+ it 'raises an error when used against an unsupported type' do
+ expect do
+ compile_to_catalog(<<-MANIFEST)
+ 3.14.#{func} |$k, $v| { }
+ MANIFEST
+ end.to raise_error(Puppet::Error, /must be something enumerable/)
+ end
+
+ it 'raises an error when called with any parameters besides a block' do
+ expect do
+ compile_to_catalog(<<-MANIFEST)
+ [1].#{func}(1) |$v| { }
+ MANIFEST
+ end.to raise_error(Puppet::Error, /mis-matched arguments.*expected.*arg count \{2\}.*actual.*arg count \{3\}/m)
+ end
+
+ it 'raises an error when called without a block' do
+ expect do
+ compile_to_catalog(<<-MANIFEST)
+ [1].#{func}()
+ MANIFEST
+ end.to raise_error(Puppet::Error, /mis-matched arguments.*expected.*arg count \{2\}.*actual.*arg count \{1\}/m)
+ end
+
+ it 'raises an error when called with something that is not a block' do
+ expect do
+ compile_to_catalog(<<-MANIFEST)
+ [1].#{func}(1)
+ MANIFEST
+ end.to raise_error(Puppet::Error, /mis-matched arguments.*expected.*Callable.*actual(?!Callable\)).*/m)
+ end
+
+ it 'raises an error when called with a block with too many required parameters' do
+ expect do
+ compile_to_catalog(<<-MANIFEST)
+ [1].#{func}() |$v1, $v2, $v3| { }
+ MANIFEST
+ end.to raise_error(Puppet::Error, /mis-matched arguments.*expected.*arg count \{2\}.*actual.*Callable\[Any, Any, Any\]/m)
+ end
+
+ it 'raises an error when called with a block with too few parameters' do
+ expect do
+ compile_to_catalog(<<-MANIFEST)
+ [1].#{func}() | | { }
+ MANIFEST
+ end.to raise_error(Puppet::Error, /mis-matched arguments.*expected.*arg count \{2\}.*actual.*Callable\[0, 0\]/m)
+ end
+
+ it 'does not raise an error when called with a block with too many but optional arguments' do
+ expect do
+ compile_to_catalog(<<-MANIFEST)
+ [1].#{func}() |$v1, $v2, $v3=extra| { }
+ MANIFEST
+ end.to_not raise_error
+ end
+end
diff --git a/spec/unit/application/apply_spec.rb b/spec/unit/application/apply_spec.rb
index de4784fbe..ec6afee66 100755
--- a/spec/unit/application/apply_spec.rb
+++ b/spec/unit/application/apply_spec.rb
@@ -131,7 +131,9 @@ describe Puppet::Application::Apply do
@apply.setup
- expect(Puppet::Util::Profiler.current).to be_a(Puppet::Util::Profiler::WallClock)
+ expect(Puppet::Util::Profiler.current).to satisfy do |ps|
+ ps.any? {|p| p.is_a? Puppet::Util::Profiler::WallClock }
+ end
end
it "does not have a profiler if profiling is disabled" do
@@ -139,7 +141,7 @@ describe Puppet::Application::Apply do
@apply.setup
- expect(Puppet::Util::Profiler.current).to eq(Puppet::Util::Profiler::NONE)
+ expect(Puppet::Util::Profiler.current.length).to be 0
end
it "should set default_file_terminus to `file_server` to be local" do
diff --git a/spec/unit/application/doc_spec.rb b/spec/unit/application/doc_spec.rb
index 8e43907ca..2f8624abd 100755
--- a/spec/unit/application/doc_spec.rb
+++ b/spec/unit/application/doc_spec.rb
@@ -259,6 +259,7 @@ describe Puppet::Application::Doc do
let(:envdir) { tmpdir('env') }
let(:modules) { File.join(envdir, "modules") }
+ let(:modules2) { File.join(envdir, "modules2") }
let(:manifests) { File.join(envdir, "manifests") }
before :each do
@@ -277,8 +278,8 @@ describe Puppet::Application::Doc do
around(:each) do |example|
FileUtils.mkdir_p(modules)
- @env = Puppet::Node::Environment.create(Puppet[:environment].to_sym, [modules], "#{manifests}/site.pp")
- Puppet.override(:environments => Puppet::Environments::Static.new(@env)) do
+ env = Puppet::Node::Environment.create(Puppet[:environment].to_sym, [modules], "#{manifests}/site.pp")
+ Puppet.override({:environments => Puppet::Environments::Static.new(env), :current_environment => env}) do
example.run
end
end
@@ -287,39 +288,42 @@ describe Puppet::Application::Doc do
@doc.options[:all] = true
Puppet.settings.expects(:[]=).with(:document_all, true)
- expect { @doc.rdoc }.to exit_with 0
+ expect { @doc.rdoc }.to exit_with(0)
end
it "should call Puppet::Util::RDoc.rdoc in full mode" do
Puppet::Util::RDoc.expects(:rdoc).with('doc', [modules, manifests], nil)
- expect { @doc.rdoc }.to exit_with 0
+ expect { @doc.rdoc }.to exit_with(0)
end
it "should call Puppet::Util::RDoc.rdoc with a charset if --charset has been provided" do
@doc.options[:charset] = 'utf-8'
Puppet::Util::RDoc.expects(:rdoc).with('doc', [modules, manifests], "utf-8")
- expect { @doc.rdoc }.to exit_with 0
+ expect { @doc.rdoc }.to exit_with(0)
end
it "should call Puppet::Util::RDoc.rdoc in full mode with outputdir set to doc if no --outputdir" do
@doc.options[:outputdir] = false
Puppet::Util::RDoc.expects(:rdoc).with('doc', [modules, manifests], nil)
- expect { @doc.rdoc }.to exit_with 0
+ expect { @doc.rdoc }.to exit_with(0)
end
it "should call Puppet::Util::RDoc.manifestdoc in manifest mode" do
@doc.manifest = true
Puppet::Util::RDoc.expects(:manifestdoc)
- expect { @doc.rdoc }.to exit_with 0
+ expect { @doc.rdoc }.to exit_with(0)
end
it "should get modulepath and manifestdir values from the environment" do
- @env.expects(:modulepath).returns(['envmodules1','envmodules2'])
- @env.expects(:manifest).returns('envmanifests/site.pp')
-
- Puppet::Util::RDoc.expects(:rdoc).with('doc', ['envmodules1','envmodules2','envmanifests'], nil)
-
- expect { @doc.rdoc }.to exit_with 0
+ FileUtils.mkdir_p(modules)
+ FileUtils.mkdir_p(modules2)
+ env = Puppet::Node::Environment.create(Puppet[:environment].to_sym,
+ [modules, modules2],
+ "envmanifests/site.pp")
+ Puppet.override({:environments => Puppet::Environments::Static.new(env), :current_environment => env}) do
+ Puppet::Util::RDoc.stubs(:rdoc).with('doc', [modules.to_s, modules2.to_s, env.manifest.to_s], nil)
+ expect { @doc.rdoc }.to exit_with(0)
+ end
end
end
diff --git a/spec/unit/application/master_spec.rb b/spec/unit/application/master_spec.rb
index 4d3167ce8..ef9c1b174 100755
--- a/spec/unit/application/master_spec.rb
+++ b/spec/unit/application/master_spec.rb
@@ -114,38 +114,46 @@ describe Puppet::Application::Master, :unless => Puppet.features.microsoft_windo
expect { @master.setup }.to raise_error(Puppet::Error, /Puppet master is not supported on Microsoft Windows/)
end
- it "should set log level to debug if --debug was passed" do
- @master.options.stubs(:[]).with(:debug).returns(true)
- @master.setup
- Puppet::Log.level.should == :debug
- end
-
- it "should set log level to info if --verbose was passed" do
- @master.options.stubs(:[]).with(:verbose).returns(true)
- @master.setup
- Puppet::Log.level.should == :info
- end
-
- it "should set console as the log destination if no --logdest and --daemonize" do
- @master.stubs(:[]).with(:daemonize).returns(:false)
-
- Puppet::Log.expects(:newdestination).with(:syslog)
-
- @master.setup
- end
+ describe "setting up logging" do
+ it "sets the log level" do
+ @master.expects(:set_log_level)
+ @master.setup
+ end
- it "should set syslog as the log destination if no --logdest and not --daemonize" do
- Puppet::Log.expects(:newdestination).with(:syslog)
+ describe "when the log destination is not explicitly configured" do
+ before do
+ @master.options.stubs(:[]).with(:setdest).returns false
+ end
- @master.setup
- end
+ it "logs to the console when --compile is given" do
+ @master.options.stubs(:[]).with(:node).returns "default"
+ Puppet::Util::Log.expects(:newdestination).with(:console)
+ @master.setup
+ end
- it "should set syslog as the log destination if --rack" do
- @master.options.stubs(:[]).with(:rack).returns(:true)
+ it "logs to the console when the master is not daemonized or run with rack" do
+ Puppet::Util::Log.expects(:newdestination).with(:console)
+ Puppet[:daemonize] = false
+ @master.options.stubs(:[]).with(:rack).returns(false)
+ @master.setup
+ end
- Puppet::Log.expects(:newdestination).with(:syslog)
+ it "logs to syslog when the master is daemonized" do
+ Puppet::Util::Log.expects(:newdestination).with(:console).never
+ Puppet::Util::Log.expects(:newdestination).with(:syslog)
+ Puppet[:daemonize] = true
+ @master.options.stubs(:[]).with(:rack).returns(false)
+ @master.setup
+ end
- @master.setup
+ it "logs to syslog when the master is run with rack" do
+ Puppet::Util::Log.expects(:newdestination).with(:console).never
+ Puppet::Util::Log.expects(:newdestination).with(:syslog)
+ Puppet[:daemonize] = false
+ @master.options.stubs(:[]).with(:rack).returns(true)
+ @master.setup
+ end
+ end
end
it "should print puppet config if asked to in Puppet config" do
diff --git a/spec/unit/application/resource_spec.rb b/spec/unit/application/resource_spec.rb
index f2e2596ca..029aa466d 100755
--- a/spec/unit/application/resource_spec.rb
+++ b/spec/unit/application/resource_spec.rb
@@ -16,11 +16,6 @@ describe Puppet::Application::Resource do
@resource_app.preinit
@resource_app.extra_params.should == []
end
-
- it "should load Facter facts" do
- Facter.expects(:loadfacts).once
- @resource_app.preinit
- end
end
describe "when handling options" do
diff --git a/spec/unit/configurer/downloader_factory_spec.rb b/spec/unit/configurer/downloader_factory_spec.rb
new file mode 100755
index 000000000..fae919918
--- /dev/null
+++ b/spec/unit/configurer/downloader_factory_spec.rb
@@ -0,0 +1,96 @@
+#! /usr/bin/env ruby
+require 'spec_helper'
+require 'puppet/configurer'
+
+describe Puppet::Configurer::DownloaderFactory do
+ let(:factory) { Puppet::Configurer::DownloaderFactory.new }
+ let(:environment) { Puppet::Node::Environment.create(:myenv, []) }
+
+ let(:plugin_downloader) do
+ factory.create_plugin_downloader(environment)
+ end
+
+ let(:facts_downloader) do
+ factory.create_plugin_facts_downloader(environment)
+ end
+
+ def ignores_source_permissions(downloader)
+ expect(downloader.file[:source_permissions]).to eq(:ignore)
+ end
+
+ def uses_source_permissions(downloader)
+ expect(downloader.file[:source_permissions]).to eq(:use)
+ end
+
+ context "when creating a plugin downloader for modules" do
+ it 'is named "plugin"' do
+ expect(plugin_downloader.name).to eq('plugin')
+ end
+
+ it 'downloads files into Puppet[:plugindest]' do
+ plugindest = File.expand_path("/tmp/pdest")
+ Puppet[:plugindest] = plugindest
+
+ expect(plugin_downloader.file[:path]).to eq(plugindest)
+ end
+
+ it 'downloads files from Puppet[:pluginsource]' do
+ Puppet[:pluginsource] = 'puppet:///myotherplugins'
+
+ expect(plugin_downloader.file[:source]).to eq([Puppet[:pluginsource]])
+ end
+
+ it 'ignores files from Puppet[:pluginsignore]' do
+ Puppet[:pluginsignore] = 'pignore'
+
+ expect(plugin_downloader.file[:ignore]).to eq(['pignore'])
+ end
+
+ it 'splits Puppet[:pluginsignore] on whitespace' do
+ Puppet[:pluginsignore] = ".svn CVS .git"
+
+ expect(plugin_downloader.file[:ignore]).to eq(%w[.svn CVS .git])
+ end
+
+ it "ignores source permissions" do
+ ignores_source_permissions(plugin_downloader)
+ end
+ end
+
+ context "when creating a plugin downloader for external facts" do
+ it 'is named "pluginfacts"' do
+ expect(facts_downloader.name).to eq('pluginfacts')
+ end
+
+ it 'downloads files into Puppet[:pluginfactdest]' do
+ plugindest = File.expand_path("/tmp/pdest")
+ Puppet[:pluginfactdest] = plugindest
+
+ expect(facts_downloader.file[:path]).to eq(plugindest)
+ end
+
+ it 'downloads files from Puppet[:pluginfactsource]' do
+ Puppet[:pluginfactsource] = 'puppet:///myotherfacts'
+
+ expect(facts_downloader.file[:source]).to eq([Puppet[:pluginfactsource]])
+ end
+
+ it 'ignores files from Puppet[:pluginsignore]' do
+ Puppet[:pluginsignore] = 'pignore'
+
+ expect(facts_downloader.file[:ignore]).to eq(['pignore'])
+ end
+
+ context "on POSIX", :if => Puppet.features.posix? do
+ it "uses source permissions" do
+ uses_source_permissions(facts_downloader)
+ end
+ end
+
+ context "on Windows", :if => Puppet.features.microsoft_windows? do
+ it "ignores source permissions during external fact pluginsync" do
+ ignores_source_permissions(facts_downloader)
+ end
+ end
+ end
+end
diff --git a/spec/unit/configurer/downloader_spec.rb b/spec/unit/configurer/downloader_spec.rb
index 9e1d3a29b..71b88bb51 100755
--- a/spec/unit/configurer/downloader_spec.rb
+++ b/spec/unit/configurer/downloader_spec.rb
@@ -6,6 +6,10 @@ require 'puppet/configurer/downloader'
describe Puppet::Configurer::Downloader do
require 'puppet_spec/files'
include PuppetSpec::Files
+
+ let(:path) { Puppet[:plugindest] }
+ let(:source) { 'puppet://puppet/plugins' }
+
it "should require a name" do
lambda { Puppet::Configurer::Downloader.new }.should raise_error(ArgumentError)
end
@@ -21,87 +25,115 @@ describe Puppet::Configurer::Downloader do
dler.source.should == "source"
end
- describe "when creating the file that does the downloading" do
- before do
- @dler = Puppet::Configurer::Downloader.new("foo", "path", "source")
- end
+ def downloader(options = {})
+ options[:name] ||= "facts"
+ options[:path] ||= path
+ options[:source_permissions] ||= :ignore
+ Puppet::Configurer::Downloader.new(options[:name], options[:path], source, options[:ignore], options[:environment], options[:source_permissions])
+ end
+
+ def generate_file_resource(options = {})
+ dler = downloader(options)
+ dler.file
+ end
+ describe "when creating the file that does the downloading" do
it "should create a file instance with the right path and source" do
- Puppet::Type.type(:file).expects(:new).with { |opts| opts[:path] == "path" and opts[:source] == "source" }
- @dler.file
+ file = generate_file_resource(:path => path, :source => source)
+
+ expect(file[:path]).to eq(path)
+ expect(file[:source]).to eq([source])
end
it "should tag the file with the downloader name" do
- Puppet::Type.type(:file).expects(:new).with { |opts| opts[:tag] == "foo" }
- @dler.file
+ name = "mydownloader"
+ file = generate_file_resource(:name => name)
+
+ expect(file[:tag]).to eq([name])
end
it "should always recurse" do
- Puppet::Type.type(:file).expects(:new).with { |opts| opts[:recurse] == true }
- @dler.file
+ file = generate_file_resource
+
+ expect(file[:recurse]).to be_true
end
it "should always purge" do
- Puppet::Type.type(:file).expects(:new).with { |opts| opts[:purge] == true }
- @dler.file
+ file = generate_file_resource
+
+ expect(file[:purge]).to be_true
end
it "should never be in noop" do
- Puppet::Type.type(:file).expects(:new).with { |opts| opts[:noop] == false }
- @dler.file
+ file = generate_file_resource
+
+ expect(file[:noop]).to be_false
+ end
+
+ it "should set source_permissions to ignore by default" do
+ file = generate_file_resource
+
+ expect(file[:source_permissions]).to eq(:ignore)
end
- it "should set source_permissions to ignore" do
- Puppet::Type.type(:file).expects(:new).with { |opts| opts[:source_permissions] == :ignore }
- @dler.file
+ it "should allow source_permissions to be overridden" do
+ file = generate_file_resource(:source_permissions => :use)
+
+ expect(file[:source_permissions]).to eq(:use)
end
describe "on POSIX", :as_platform => :posix do
it "should always set the owner to the current UID" do
Process.expects(:uid).returns 51
- Puppet::Type.type(:file).expects(:new).with { |opts| opts[:owner] == 51 }
- @dler.file
+
+ file = generate_file_resource(:path => '/path')
+ expect(file[:owner]).to eq(51)
end
it "should always set the group to the current GID" do
Process.expects(:gid).returns 61
- Puppet::Type.type(:file).expects(:new).with { |opts| opts[:group] == 61 }
- @dler.file
+
+ file = generate_file_resource(:path => '/path')
+ expect(file[:group]).to eq(61)
end
end
describe "on Windows", :as_platform => :windows do
it "should omit the owner" do
- Puppet::Type.type(:file).expects(:new).with { |opts| opts[:owner] == nil }
- @dler.file
+ file = generate_file_resource(:path => 'C:/path')
+
+ expect(file[:owner]).to be_nil
end
it "should omit the group" do
- Puppet::Type.type(:file).expects(:new).with { |opts| opts[:group] == nil }
- @dler.file
+ file = generate_file_resource(:path => 'C:/path')
+
+ expect(file[:group]).to be_nil
end
end
it "should always force the download" do
- Puppet::Type.type(:file).expects(:new).with { |opts| opts[:force] == true }
- @dler.file
+ file = generate_file_resource
+
+ expect(file[:force]).to be_true
end
it "should never back up when downloading" do
- Puppet::Type.type(:file).expects(:new).with { |opts| opts[:backup] == false }
- @dler.file
+ file = generate_file_resource
+
+ expect(file[:backup]).to be_false
end
it "should support providing an 'ignore' parameter" do
- Puppet::Type.type(:file).expects(:new).with { |opts| opts[:ignore] == [".svn"] }
- @dler = Puppet::Configurer::Downloader.new("foo", "path", "source", ".svn")
- @dler.file
+ file = generate_file_resource(:ignore => '.svn')
+
+ expect(file[:ignore]).to eq(['.svn'])
end
it "should split the 'ignore' parameter on whitespace" do
- Puppet::Type.type(:file).expects(:new).with { |opts| opts[:ignore] == %w{.svn CVS} }
- @dler = Puppet::Configurer::Downloader.new("foo", "path", "source", ".svn CVS")
- @dler.file
+ file = generate_file_resource(:ignore => '.svn CVS')
+
+ expect(file[:ignore]).to eq(['.svn', 'CVS'])
end
end
@@ -142,25 +174,6 @@ describe Puppet::Configurer::Downloader do
it "should log that it is downloading" do
Puppet.expects(:info)
- Timeout.stubs(:timeout)
-
- @dler.evaluate
- end
-
- it "should set a timeout for the download using the `configtimeout` setting" do
- Puppet[:configtimeout] = 50
- Timeout.expects(:timeout).with(50)
-
- @dler.evaluate
- end
-
- it "should apply the catalog within the timeout block" do
- catalog = mock 'catalog'
- @dler.expects(:catalog).returns(catalog)
-
- Timeout.expects(:timeout).yields
-
- catalog.expects(:apply)
@dler.evaluate
end
@@ -172,8 +185,6 @@ describe Puppet::Configurer::Downloader do
@dler.expects(:catalog).returns(catalog)
catalog.expects(:apply).yields(trans)
- Timeout.expects(:timeout).yields
-
resource = mock 'resource'
resource.expects(:[]).with(:path).returns "/changed/file"
@@ -189,8 +200,6 @@ describe Puppet::Configurer::Downloader do
@dler.expects(:catalog).returns(catalog)
catalog.expects(:apply).yields(trans)
- Timeout.expects(:timeout).yields
-
resource = mock 'resource'
resource.expects(:[]).with(:path).returns "/changed/file"
@@ -203,7 +212,9 @@ describe Puppet::Configurer::Downloader do
it "should catch and log exceptions" do
Puppet.expects(:err)
- Timeout.stubs(:timeout).raises(Puppet::Error, "testing")
+ # The downloader creates a new catalog for each apply, and really the only object
+ # that it is possible to stub for the purpose of generating a puppet error
+ Puppet::Resource::Catalog.any_instance.stubs(:apply).raises(Puppet::Error, "testing")
lambda { @dler.evaluate }.should_not raise_error
end
diff --git a/spec/unit/configurer/plugin_handler_spec.rb b/spec/unit/configurer/plugin_handler_spec.rb
index 295629481..a80236888 100755
--- a/spec/unit/configurer/plugin_handler_spec.rb
+++ b/spec/unit/configurer/plugin_handler_spec.rb
@@ -3,37 +3,37 @@ require 'spec_helper'
require 'puppet/configurer'
require 'puppet/configurer/plugin_handler'
-class PluginHandlerTester
- include Puppet::Configurer::PluginHandler
- attr_accessor :environment
-end
-
describe Puppet::Configurer::PluginHandler do
- before do
- @pluginhandler = PluginHandlerTester.new
+ let(:factory) { Puppet::Configurer::DownloaderFactory.new }
+ let(:pluginhandler) { Puppet::Configurer::PluginHandler.new(factory) }
+ let(:environment) { Puppet::Node::Environment.create(:myenv, []) }
+ before :each do
# PluginHandler#load_plugin has an extra-strong rescue clause
# this mock is to make sure that we don't silently ignore errors
Puppet.expects(:err).never
end
- it "should use an Agent Downloader, with the name, source, destination, ignore, and environment set correctly, to download plugins when downloading is enabled" do
- environment = Puppet::Node::Environment.create(:myenv, [])
- Puppet.features.stubs(:external_facts?).returns(:true)
- plugindest = File.expand_path("/tmp/pdest")
- Puppet[:pluginsource] = "psource"
- Puppet[:plugindest] = plugindest
- Puppet[:pluginsignore] = "pignore"
- Puppet[:pluginfactsource] = "psource"
- Puppet[:pluginfactdest] = plugindest
+ it "downloads plugins and facts" do
+ Puppet.features.stubs(:external_facts?).returns(true)
+
+ plugin_downloader = stub('plugin-downloader', :evaluate => [])
+ facts_downloader = stub('facts-downloader', :evaluate => [])
+
+ factory.expects(:create_plugin_downloader).returns(plugin_downloader)
+ factory.expects(:create_plugin_facts_downloader).returns(facts_downloader)
+
+ pluginhandler.download_plugins(environment)
+ end
+
+ it "skips facts if not enabled" do
+ Puppet.features.stubs(:external_facts?).returns(false)
- downloader = mock 'downloader'
- Puppet::Configurer::Downloader.expects(:new).with("pluginfacts", plugindest, "psource", "pignore", environment).returns downloader
- Puppet::Configurer::Downloader.expects(:new).with("plugin", plugindest, "psource", "pignore", environment).returns downloader
+ plugin_downloader = stub('plugin-downloader', :evaluate => [])
- downloader.stubs(:evaluate).returns([])
- downloader.expects(:evaluate).twice
+ factory.expects(:create_plugin_downloader).returns(plugin_downloader)
+ factory.expects(:create_plugin_facts_downloader).never
- @pluginhandler.download_plugins(environment)
+ pluginhandler.download_plugins(environment)
end
end
diff --git a/spec/unit/configurer_spec.rb b/spec/unit/configurer_spec.rb
index ecbedec28..093990591 100755
--- a/spec/unit/configurer_spec.rb
+++ b/spec/unit/configurer_spec.rb
@@ -12,10 +12,6 @@ describe Puppet::Configurer do
Puppet[:report] = true
end
- it "should include the Plugin Handler module" do
- Puppet::Configurer.ancestors.should be_include(Puppet::Configurer::PluginHandler)
- end
-
it "should include the Fact Handler module" do
Puppet::Configurer.ancestors.should be_include(Puppet::Configurer::FactHandler)
end
diff --git a/spec/unit/defaults_spec.rb b/spec/unit/defaults_spec.rb
index f86283a64..0d141d91c 100644
--- a/spec/unit/defaults_spec.rb
+++ b/spec/unit/defaults_spec.rb
@@ -41,4 +41,34 @@ describe "Defaults" do
end
end
end
+
+ describe 'cfacter' do
+
+ before :each do
+ Facter.reset
+ end
+
+ it 'should default to false' do
+ Puppet.settings[:cfacter].should be_false
+ end
+
+ it 'should raise an error if cfacter is not installed' do
+ Puppet.features.stubs(:cfacter?).returns false
+ lambda { Puppet.settings[:cfacter] = true }.should raise_exception ArgumentError, 'cfacter version 0.2.0 or later is not installed.'
+ end
+
+ it 'should raise an error if facter has already evaluated facts' do
+ Facter[:facterversion]
+ Puppet.features.stubs(:cfacter?).returns true
+ lambda { Puppet.settings[:cfacter] = true }.should raise_exception ArgumentError, 'facter has already evaluated facts.'
+ end
+
+ it 'should initialize cfacter when set to true' do
+ Puppet.features.stubs(:cfacter?).returns true
+ CFacter = mock
+ CFacter.stubs(:initialize)
+ Puppet.settings[:cfacter] = true
+ end
+
+ end
end
diff --git a/spec/unit/face/certificate_request_spec.rb b/spec/unit/face/certificate_request_spec.rb
deleted file mode 100755
index 60917254f..000000000
--- a/spec/unit/face/certificate_request_spec.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-#! /usr/bin/env ruby
-require 'spec_helper'
-require 'puppet/face'
-
-describe Puppet::Face[:certificate_request, '0.0.1'] do
- it "should actually have some tests..."
-end
diff --git a/spec/unit/face/certificate_revocation_list_spec.rb b/spec/unit/face/certificate_revocation_list_spec.rb
deleted file mode 100755
index b833fb718..000000000
--- a/spec/unit/face/certificate_revocation_list_spec.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-#! /usr/bin/env ruby
-require 'spec_helper'
-require 'puppet/face'
-
-describe Puppet::Face[:certificate_revocation_list, '0.0.1'] do
- it "should actually have some tests..."
-end
diff --git a/spec/unit/face/config_spec.rb b/spec/unit/face/config_spec.rb
index f42844827..2c43b33b7 100755
--- a/spec/unit/face/config_spec.rb
+++ b/spec/unit/face/config_spec.rb
@@ -92,6 +92,7 @@ basemodulepath = #{File.expand_path("/some/base")}
end
it "prints the default configured env settings for an env that does not exist" do
+ pending "This case no longer exists because Application will through an error before we even get here because of the non-existent environment"
Puppet[:environment] = 'doesnotexist'
FS.overlay(
@@ -125,7 +126,7 @@ basemodulepath = #{File.expand_path("/some/base")}
CONF
end
- it_behaves_like :config_printing_a_section
+ it_behaves_like :config_printing_a_section, nil
end
context "from master section" do
diff --git a/spec/unit/face/key_spec.rb b/spec/unit/face/key_spec.rb
deleted file mode 100755
index 6d9519825..000000000
--- a/spec/unit/face/key_spec.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-#! /usr/bin/env ruby
-require 'spec_helper'
-require 'puppet/face'
-
-describe Puppet::Face[:key, '0.0.1'] do
- it "should actually have some tests..."
-end
diff --git a/spec/unit/face/module/build_spec.rb b/spec/unit/face/module/build_spec.rb
index 02bbb7e9d..72ba5e1f1 100644
--- a/spec/unit/face/module/build_spec.rb
+++ b/spec/unit/face/module/build_spec.rb
@@ -25,7 +25,7 @@ describe "puppet module build" do
it "if current directory or parents contain no module root, should return exception" do
Dir.expects(:pwd).returns('/a/b/c')
Puppet::ModuleTool.expects(:find_module_root).returns(nil)
- expect { subject.build }.to raise_error RuntimeError, "Unable to find module root at /a/b/c or parent directories"
+ expect { subject.build }.to raise_error RuntimeError, "Unable to find metadata.json or Modulefile in module root /a/b/c or parent directories. See <http://links.puppetlabs.com/modulefile> for required file format."
end
end
@@ -39,7 +39,7 @@ describe "puppet module build" do
it "if path is not a module root should raise exception" do
Puppet::ModuleTool.expects(:is_module_root?).with('/a/b/c').returns(false)
- expect { subject.build('/a/b/c') }.to raise_error RuntimeError, "Unable to find module root at /a/b/c"
+ expect { subject.build('/a/b/c') }.to raise_error RuntimeError, "Unable to find metadata.json or Modulefile in module root /a/b/c. See <http://links.puppetlabs.com/modulefile> for required file format."
end
end
diff --git a/spec/unit/face/module/install_spec.rb b/spec/unit/face/module/install_spec.rb
index 528c7822a..3f0b27684 100644
--- a/spec/unit/face/module/install_spec.rb
+++ b/spec/unit/face/module/install_spec.rb
@@ -7,19 +7,19 @@ describe "puppet module install" do
describe "action" do
let(:name) { stub(:name) }
- let(:target_dir) { stub(:target_dir) }
- let(:target_path) { stub(:target_path) }
- let(:install_dir) { stub(:install_dir) }
+ let(:target_dir) { tmpdir('module install face action') }
let(:options) { { :target_dir => target_dir } }
it 'should invoke the Installer app' do
- args = [ name, install_dir, options ]
-
Puppet::ModuleTool.expects(:set_option_defaults).with(options)
+ Puppet::ModuleTool::Applications::Installer.expects(:run).with do |*args|
+ mod, target, opts = args
- Pathname.expects(:new).with(target_dir).returns(target_path)
- Puppet::ModuleTool::InstallDirectory.expects(:new).with(target_path).returns(install_dir)
- Puppet::ModuleTool::Applications::Installer.expects(:run).with(*args)
+ expect(mod).to eql(name)
+ expect(opts).to eql(options)
+ expect(target).to be_a(Puppet::ModuleTool::InstallDirectory)
+ expect(target.target).to eql(Pathname.new(target_dir))
+ end
Puppet::Face[:module, :current].install(name, options)
end
diff --git a/spec/unit/face/parser_spec.rb b/spec/unit/face/parser_spec.rb
index 9b1b07a47..e9ba07a2d 100644
--- a/spec/unit/face/parser_spec.rb
+++ b/spec/unit/face/parser_spec.rb
@@ -8,58 +8,96 @@ describe Puppet::Face[:parser, :current] do
let(:parser) { Puppet::Face[:parser, :current] }
- context "from an interactive terminal" do
- before :each do
- from_an_interactive_terminal
+ context "validate" do
+ context "from an interactive terminal" do
+ before :each do
+ from_an_interactive_terminal
+ end
+
+ it "validates the configured site manifest when no files are given" do
+ manifest = file_containing('site.pp', "{ invalid =>")
+
+ configured_environment = Puppet::Node::Environment.create(:default, [], manifest)
+ Puppet.override(:current_environment => configured_environment) do
+ expect { parser.validate() }.to exit_with(1)
+ end
+ end
+
+ it "validates the given file" do
+ manifest = file_containing('site.pp', "{ invalid =>")
+
+ expect { parser.validate(manifest) }.to exit_with(1)
+ end
+
+ it "runs error free when there are no validation errors" do
+ manifest = file_containing('site.pp', "notify { valid: }")
+
+ parser.validate(manifest)
+ end
+
+ it "reports missing files" do
+ expect do
+ parser.validate("missing.pp")
+ end.to raise_error(Puppet::Error, /One or more file\(s\) specified did not exist.*missing\.pp/m)
+ end
+
+ it "parses supplied manifest files in the context of a directory environment" do
+ manifest = file_containing('test.pp', "{ invalid =>")
+
+ env = Puppet::Node::Environment.create(:special, [])
+ env_loader = Puppet::Environments::Static.new(env)
+ Puppet.override({:environments => env_loader, :current_environment => env}) do
+ expect { parser.validate(manifest) }.to exit_with(1)
+ end
+
+ expect(@logs.join).to match(/environment special.*Syntax error at '\{'/)
+ end
+
end
- it "validates the configured site manifest when no files are given" do
- manifest = file_containing('site.pp', "{ invalid =>")
+ it "validates the contents of STDIN when no files given and STDIN is not a tty" do
+ from_a_piped_input_of("{ invalid =>")
- configured_environment = Puppet::Node::Environment.create(:default, [], manifest)
- Puppet.override(:current_environment => configured_environment) do
+ Puppet.override(:current_environment => Puppet::Node::Environment.create(:special, [])) do
expect { parser.validate() }.to exit_with(1)
end
end
+ end
- it "validates the given file" do
- manifest = file_containing('site.pp', "{ invalid =>")
-
- expect { parser.validate(manifest) }.to exit_with(1)
+ context "dump" do
+ it "prints the AST of the passed expression" do
+ expect(parser.dump({ :e => 'notice hi' })).to eq("(invoke notice hi)\n")
end
- it "runs error free when there are no validation errors" do
- manifest = file_containing('site.pp', "notify { valid: }")
+ it "prints the AST of the code read from the passed files" do
+ first_manifest = file_containing('site.pp', "notice hi")
+ second_manifest = file_containing('site2.pp', "notice bye")
- parser.validate(manifest)
+ output = parser.dump(first_manifest, second_manifest)
+
+ expect(output).to match(/site\.pp.*\(invoke notice hi\)/)
+ expect(output).to match(/site2\.pp.*\(invoke notice bye\)/)
end
- it "reports missing files" do
- expect do
- parser.validate("missing.pp")
- end.to raise_error(Puppet::Error, /One or more file\(s\) specified did not exist.*missing\.pp/m)
+ it "informs the user of files that don't exist" do
+ expect(parser.dump('does_not_exist_here.pp')).to match(/did not exist:\s*does_not_exist_here\.pp/m)
end
- it "parses supplied manifest files in the context of a directory environment" do
- manifest = file_containing('test.pp', "{ invalid =>")
+ it "prints the AST of STDIN when no files given and STDIN is not a tty" do
+ from_a_piped_input_of("notice hi")
- env_loader = Puppet::Environments::Static.new(
- Puppet::Node::Environment.create(:special, [])
- )
- Puppet.override(:environments => env_loader) do
- Puppet[:environment] = 'special'
- expect { parser.validate(manifest) }.to exit_with(1)
+ Puppet.override(:current_environment => Puppet::Node::Environment.create(:special, [])) do
+ expect(parser.dump()).to eq("(invoke notice hi)\n")
end
-
- expect(@logs.join).to match(/environment special.*Syntax error at '\{'/)
end
- end
-
- it "validates the contents of STDIN when no files given and STDIN is not a tty" do
- from_a_piped_input_of("{ invalid =>")
+ it "logs an error if the input cannot be parsed" do
+ output = parser.dump({ :e => '{ invalid =>' })
- expect { parser.validate() }.to exit_with(1)
+ expect(output).to eq("")
+ expect(@logs[0].message).to eq("Syntax error at end of file")
+ expect(@logs[0].level).to eq(:err)
+ end
end
def from_an_interactive_terminal
diff --git a/spec/unit/face/report_spec.rb b/spec/unit/face/report_spec.rb
deleted file mode 100755
index 6fcb49a64..000000000
--- a/spec/unit/face/report_spec.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-#! /usr/bin/env ruby
-require 'spec_helper'
-require 'puppet/face'
-
-describe Puppet::Face[:report, '0.0.1'] do
- it "should actually have some tests..."
-end
diff --git a/spec/unit/face/resource_spec.rb b/spec/unit/face/resource_spec.rb
deleted file mode 100755
index 031d78116..000000000
--- a/spec/unit/face/resource_spec.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-#! /usr/bin/env ruby
-require 'spec_helper'
-require 'puppet/face'
-
-describe Puppet::Face[:resource, '0.0.1'] do
- it "should actually have some tests..."
-end
diff --git a/spec/unit/face/resource_type_spec.rb b/spec/unit/face/resource_type_spec.rb
deleted file mode 100755
index 5713a01fb..000000000
--- a/spec/unit/face/resource_type_spec.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-#! /usr/bin/env ruby
-require 'spec_helper'
-require 'puppet/face'
-
-describe Puppet::Face[:resource_type, '0.0.1'] do
- it "should actually have some tests..."
-end
diff --git a/spec/unit/file_bucket/file_spec.rb b/spec/unit/file_bucket/file_spec.rb
index 370ba1eeb..500e81685 100755
--- a/spec/unit/file_bucket/file_spec.rb
+++ b/spec/unit/file_bucket/file_spec.rb
@@ -14,7 +14,7 @@ describe Puppet::FileBucket::File, :uses_checksums => true do
end
it "accepts s and pson" do
- expect(Puppet::FileBucket::File.supported_formats).to include(:s, :pson)
+ expect(Puppet::FileBucket::File.supported_formats).to include(:s, :pson)
end
describe "making round trips through network formats" do
@@ -34,7 +34,7 @@ describe Puppet::FileBucket::File, :uses_checksums => true do
end
it "should require contents to be a string" do
- expect { Puppet::FileBucket::File.new(5) }.to raise_error(ArgumentError, /contents must be a String, got a Fixnum$/)
+ expect { Puppet::FileBucket::File.new(5) }.to raise_error(ArgumentError, /contents must be a String or Pathname, got a Fixnum$/)
end
it "should complain about options other than :bucket_path" do
diff --git a/spec/unit/file_system/tempfile_spec.rb b/spec/unit/file_system/tempfile_spec.rb
deleted file mode 100644
index eb13b0406..000000000
--- a/spec/unit/file_system/tempfile_spec.rb
+++ /dev/null
@@ -1,48 +0,0 @@
-require 'spec_helper'
-
-describe Puppet::FileSystem::Tempfile do
- it "makes the name of the file available" do
- Puppet::FileSystem::Tempfile.open('foo') do |file|
- expect(file.path).to match(/foo/)
- end
- end
-
- it "provides a writeable file" do
- Puppet::FileSystem::Tempfile.open('foo') do |file|
- file.write("stuff")
- file.flush
-
- expect(Puppet::FileSystem.read(file.path)).to eq("stuff")
- end
- end
-
- it "returns the value of the block" do
- the_value = Puppet::FileSystem::Tempfile.open('foo') do |file|
- "my value"
- end
-
- expect(the_value).to eq("my value")
- end
-
- it "unlinks the temporary file" do
- filename = Puppet::FileSystem::Tempfile.open('foo') do |file|
- file.path
- end
-
- expect(Puppet::FileSystem.exist?(filename)).to be_false
- end
-
- it "unlinks the temporary file even if the block raises an error" do
- filename = nil
-
- begin
- Puppet::FileSystem::Tempfile.open('foo') do |file|
- filename = file.path
- raise "error!"
- end
- rescue
- end
-
- expect(Puppet::FileSystem.exist?(filename)).to be_false
- end
-end
diff --git a/spec/unit/file_system/uniquefile_spec.rb b/spec/unit/file_system/uniquefile_spec.rb
new file mode 100644
index 000000000..1268581fa
--- /dev/null
+++ b/spec/unit/file_system/uniquefile_spec.rb
@@ -0,0 +1,184 @@
+require 'spec_helper'
+
+describe Puppet::FileSystem::Uniquefile do
+ it "makes the name of the file available" do
+ Puppet::FileSystem::Uniquefile.open_tmp('foo') do |file|
+ expect(file.path).to match(/foo/)
+ end
+ end
+
+ it "provides a writeable file" do
+ Puppet::FileSystem::Uniquefile.open_tmp('foo') do |file|
+ file.write("stuff")
+ file.flush
+
+ expect(Puppet::FileSystem.read(file.path)).to eq("stuff")
+ end
+ end
+
+ it "returns the value of the block" do
+ the_value = Puppet::FileSystem::Uniquefile.open_tmp('foo') do |file|
+ "my value"
+ end
+
+ expect(the_value).to eq("my value")
+ end
+
+ it "unlinks the temporary file" do
+ filename = Puppet::FileSystem::Uniquefile.open_tmp('foo') do |file|
+ file.path
+ end
+
+ expect(Puppet::FileSystem.exist?(filename)).to be_false
+ end
+
+ it "unlinks the temporary file even if the block raises an error" do
+ filename = nil
+
+ begin
+ Puppet::FileSystem::Uniquefile.open_tmp('foo') do |file|
+ filename = file.path
+ raise "error!"
+ end
+ rescue
+ end
+
+ expect(Puppet::FileSystem.exist?(filename)).to be_false
+ end
+
+
+ context "Ruby 1.9.3 Tempfile tests" do
+ # the remaining tests in this file are ported directly from the ruby 1.9.3 source,
+ # since most of this file was ported from there
+ # see: https://github.com/ruby/ruby/blob/v1_9_3_547/test/test_tempfile.rb
+
+ def tempfile(*args, &block)
+ t = Puppet::FileSystem::Uniquefile.new(*args, &block)
+ @tempfile = (t unless block)
+ end
+
+ after(:each) do
+ if @tempfile
+ @tempfile.close!
+ end
+ end
+
+ it "creates tempfiles" do
+ t = tempfile("foo")
+ path = t.path
+ t.write("hello world")
+ t.close
+ expect(File.read(path)).to eq("hello world")
+ end
+
+ it "saves in tmpdir by default" do
+ t = tempfile("foo")
+ expect(Dir.tmpdir).to eq(File.dirname(t.path))
+ end
+
+ it "saves in given directory" do
+ subdir = File.join(Dir.tmpdir, "tempfile-test-#{rand}")
+ Dir.mkdir(subdir)
+ begin
+ tempfile = Tempfile.new("foo", subdir)
+ tempfile.close
+ begin
+ expect(subdir).to eq(File.dirname(tempfile.path))
+ ensure
+ tempfile.unlink
+ end
+ ensure
+ Dir.rmdir(subdir)
+ end
+ end
+
+ it "supports basename" do
+ t = tempfile("foo")
+ expect(File.basename(t.path)).to match(/^foo/)
+ end
+
+ it "supports basename with suffix" do
+ t = tempfile(["foo", ".txt"])
+ expect(File.basename(t.path)).to match(/^foo/)
+ expect(File.basename(t.path)).to match(/\.txt$/)
+ end
+
+ it "supports unlink" do
+ t = tempfile("foo")
+ path = t.path
+ t.close
+ expect(File.exist?(path)).to eq(true)
+ t.unlink
+ expect(File.exist?(path)).to eq(false)
+ expect(t.path).to eq(nil)
+ end
+
+ it "supports closing" do
+ t = tempfile("foo")
+ expect(t.closed?).to eq(false)
+ t.close
+ expect(t.closed?).to eq(true)
+ end
+
+ it "supports closing and unlinking via boolean argument" do
+ t = tempfile("foo")
+ path = t.path
+ t.close(true)
+ expect(t.closed?).to eq(true)
+ expect(t.path).to eq(nil)
+ expect(File.exist?(path)).to eq(false)
+ end
+
+ context "on unix platforms", :unless => Puppet.features.microsoft_windows? do
+ it "close doesn't unlink if already unlinked" do
+ t = tempfile("foo")
+ path = t.path
+ t.unlink
+ File.open(path, "w").close
+ begin
+ t.close(true)
+ expect(File.exist?(path)).to eq(true)
+ ensure
+ File.unlink(path) rescue nil
+ end
+ end
+ end
+
+ it "supports close!" do
+ t = tempfile("foo")
+ path = t.path
+ t.close!
+ expect(t.closed?).to eq(true)
+ expect(t.path).to eq(nil)
+ expect(File.exist?(path)).to eq(false)
+ end
+
+ context "on unix platforms", :unless => Puppet.features.microsoft_windows? do
+ it "close! doesn't unlink if already unlinked" do
+ t = tempfile("foo")
+ path = t.path
+ t.unlink
+ File.open(path, "w").close
+ begin
+ t.close!
+ expect(File.exist?(path)).to eq(true)
+ ensure
+ File.unlink(path) rescue nil
+ end
+ end
+ end
+
+ it "close does not make path nil" do
+ t = tempfile("foo")
+ t.close
+ expect(t.path.nil?).to eq(false)
+ end
+
+ it "close flushes buffer" do
+ t = tempfile("foo")
+ t.write("hello")
+ t.close
+ expect(File.size(t.path)).to eq(5)
+ end
+ end
+end
diff --git a/spec/unit/forge/errors_spec.rb b/spec/unit/forge/errors_spec.rb
index 057bf014a..fc1ac1a0e 100644
--- a/spec/unit/forge/errors_spec.rb
+++ b/spec/unit/forge/errors_spec.rb
@@ -47,15 +47,14 @@ Could not connect to http://fake.com:1111
let(:exception) { subject.new(:uri => 'http://fake.com:1111', :response => response, :input => 'user/module') }
it 'should return a valid single line error' do
- exception.message.should == 'Could not execute operation for \'user/module\'. Detail: 404 not found.'
+ exception.message.should == 'Request to Puppet Forge failed. Detail: 404 not found.'
end
it 'should return a valid multiline error' do
exception.multiline.should == <<-eos.chomp
-Could not execute operation for 'user/module'
+Request to Puppet Forge failed.
The server being queried was http://fake.com:1111
The HTTP response we received was '404 not found'
- Check the author and module names are correct.
eos
end
end
@@ -64,16 +63,15 @@ Could not execute operation for 'user/module'
let(:exception) { subject.new(:uri => 'http://fake.com:1111', :response => response, :input => 'user/module', :message => 'no such module') }
it 'should return a valid single line error' do
- exception.message.should == 'Could not execute operation for \'user/module\'. Detail: no such module / 404 not found.'
+ exception.message.should == 'Request to Puppet Forge failed. Detail: no such module / 404 not found.'
end
it 'should return a valid multiline error' do
exception.multiline.should == <<-eos.chomp
-Could not execute operation for 'user/module'
+Request to Puppet Forge failed.
The server being queried was http://fake.com:1111
The HTTP response we received was '404 not found'
The message we received said 'no such module'
- Check the author and module names are correct.
eos
end
end
diff --git a/spec/unit/forge/module_release_spec.rb b/spec/unit/forge/module_release_spec.rb
index fbf5e157a..b763f0833 100644
--- a/spec/unit/forge/module_release_spec.rb
+++ b/spec/unit/forge/module_release_spec.rb
@@ -8,57 +8,14 @@ describe Puppet::Forge::ModuleRelease do
let(:agent) { "Test/1.0" }
let(:repository) { Puppet::Forge::Repository.new('http://fake.com', agent) }
let(:ssl_repository) { Puppet::Forge::Repository.new('https://fake.com', agent) }
-
- let(:release_json) do
- <<-EOF
- {
- "uri": "/v3/releases/puppetlabs-stdlib-4.1.0",
- "module": {
- "uri": "/v3/modules/puppetlabs-stdlib",
- "name": "stdlib",
- "owner": {
- "uri": "/v3/users/puppetlabs",
- "username": "puppetlabs",
- "gravatar_id": "fdd009b7c1ec96e088b389f773e87aec"
- }
- },
- "version": "4.1.0",
- "metadata": {
- "types": [ ],
- "license": "Apache 2.0",
- "checksums": { },
- "version": "4.1.0",
- "description": "Standard Library for Puppet Modules",
- "source": "git://github.com/puppetlabs/puppetlabs-stdlib.git",
- "project_page": "https://github.com/puppetlabs/puppetlabs-stdlib",
- "summary": "Puppet Module Standard Library",
- "dependencies": [
-
- ],
- "author": "puppetlabs",
- "name": "puppetlabs-stdlib"
- },
- "tags": [
- "puppetlabs",
- "library",
- "stdlib",
- "standard",
- "stages"
- ],
- "file_uri": "/v3/files/puppetlabs-stdlib-4.1.0.tar.gz",
- "file_size": 67586,
- "file_md5": "bbf919d7ee9d278d2facf39c25578bf8",
- "downloads": 610751,
- "readme": "",
- "changelog": "",
- "license": "",
- "created_at": "2013-05-13 08:31:19 -0700",
- "updated_at": "2013-05-13 08:31:19 -0700",
- "deleted_at": null
- }
- EOF
- end
-
+ let(:api_version) { "v3" }
+ let(:module_author) { "puppetlabs" }
+ let(:module_name) { "stdlib" }
+ let(:module_version) { "4.1.0" }
+ let(:module_full_name) { "#{module_author}-#{module_name}" }
+ let(:module_full_name_versioned) { "#{module_full_name}-#{module_version}" }
+ let(:module_md5) { "bbf919d7ee9d278d2facf39c25578bf8" }
+ let(:uri) { " "}
let(:release) { Puppet::Forge::ModuleRelease.new(ssl_repository, JSON.parse(release_json)) }
let(:mock_file) {
@@ -69,63 +26,195 @@ describe Puppet::Forge::ModuleRelease do
let(:mock_dir) { '/tmp' }
- def mock_digest_file_with_md5(md5)
- Digest::MD5.stubs(:file).returns(stub(:hexdigest => md5))
- end
-
- describe '#prepare' do
- before :each do
- release.stubs(:tmpfile).returns(mock_file)
- release.stubs(:tmpdir).returns(mock_dir)
+ shared_examples 'a module release' do
+ def mock_digest_file_with_md5(md5)
+ Digest::MD5.stubs(:file).returns(stub(:hexdigest => md5))
end
- it 'should call sub methods with correct params' do
- release.expects(:download).with('/v3/files/puppetlabs-stdlib-4.1.0.tar.gz', mock_file)
- release.expects(:validate_checksum).with(mock_file, 'bbf919d7ee9d278d2facf39c25578bf8')
- release.expects(:unpack).with(mock_file, mock_dir)
+ describe '#prepare' do
+ before :each do
+ release.stubs(:tmpfile).returns(mock_file)
+ release.stubs(:tmpdir).returns(mock_dir)
+ end
+
+ it 'should call sub methods with correct params' do
+ release.expects(:download).with("/#{api_version}/files/#{module_full_name_versioned}.tar.gz", mock_file)
+ release.expects(:validate_checksum).with(mock_file, module_md5)
+ release.expects(:unpack).with(mock_file, mock_dir)
- release.prepare
+ release.prepare
+ end
end
- end
- describe '#tmpfile' do
+ describe '#tmpfile' do
- # This is impossible to test under Ruby 1.8.x, but should also occur there.
- it 'should be opened in binary mode', :unless => RUBY_VERSION >= '1.8.7' do
- Puppet::Forge::Cache.stubs(:base_path).returns(Dir.tmpdir)
- release.send(:tmpfile).binmode?.should be_true
+ # This is impossible to test under Ruby 1.8.x, but should also occur there.
+ it 'should be opened in binary mode', :unless => RUBY_VERSION >= '1.8.7' do
+ Puppet::Forge::Cache.stubs(:base_path).returns(Dir.tmpdir)
+ release.send(:tmpfile).binmode?.should be_true
+ end
end
- end
- describe '#download' do
- it 'should call make_http_request with correct params' do
- # valid URI comes from file_uri in JSON blob above
- ssl_repository.expects(:make_http_request).with('/v3/files/puppetlabs-stdlib-4.1.0.tar.gz', mock_file).returns(mock_file)
+ describe '#download' do
+ it 'should call make_http_request with correct params' do
+ # valid URI comes from file_uri in JSON blob above
+ ssl_repository.expects(:make_http_request).with("/#{api_version}/files/#{module_full_name_versioned}.tar.gz", mock_file).returns(stub(:body => '{}', :code => '200'))
+
+ release.send(:download, "/#{api_version}/files/#{module_full_name_versioned}.tar.gz", mock_file)
+ end
- release.send(:download, '/v3/files/puppetlabs-stdlib-4.1.0.tar.gz', mock_file)
+ it 'should raise a response error when it receives an error from forge' do
+ ssl_repository.stubs(:make_http_request).returns(stub(:body => '{"errors": ["error"]}', :code => '500', :message => 'server error'))
+ expect { release.send(:download, "/some/path", mock_file)}. to raise_error Puppet::Forge::Errors::ResponseError
+ end
end
- end
- describe '#verify_checksum' do
- it 'passes md5 check when valid' do
- # valid hash comes from file_md5 in JSON blob above
- mock_digest_file_with_md5('bbf919d7ee9d278d2facf39c25578bf8')
+ describe '#verify_checksum' do
+ it 'passes md5 check when valid' do
+ # valid hash comes from file_md5 in JSON blob above
+ mock_digest_file_with_md5(module_md5)
- release.send(:validate_checksum, mock_file, 'bbf919d7ee9d278d2facf39c25578bf8')
+ release.send(:validate_checksum, mock_file, module_md5)
+ end
+
+ it 'fails md5 check when invalid' do
+ mock_digest_file_with_md5('ffffffffffffffffffffffffffffffff')
+
+ expect { release.send(:validate_checksum, mock_file, module_md5) }.to raise_error(RuntimeError, /did not match expected checksum/)
+ end
end
- it 'fails md5 check when invalid' do
- mock_digest_file_with_md5('ffffffffffffffffffffffffffffffff')
+ describe '#unpack' do
+ it 'should call unpacker with correct params' do
+ Puppet::ModuleTool::Applications::Unpacker.expects(:unpack).with(mock_file.path, mock_dir).returns(true)
+
+ release.send(:unpack, mock_file, mock_dir)
+ end
+ end
+ end
- expect { release.send(:validate_checksum, mock_file, 'bbf919d7ee9d278d2facf39c25578bf8') }.to raise_error(RuntimeError, /did not match expected checksum/)
+ context 'standard forge module' do
+ let(:release_json) do %Q{
+ {
+ "uri": "/#{api_version}/releases/#{module_full_name_versioned}",
+ "module": {
+ "uri": "/#{api_version}/modules/#{module_full_name}",
+ "name": "#{module_name}",
+ "owner": {
+ "uri": "/#{api_version}/users/#{module_author}",
+ "username": "#{module_author}",
+ "gravatar_id": "fdd009b7c1ec96e088b389f773e87aec"
+ }
+ },
+ "version": "#{module_version}",
+ "metadata": {
+ "types": [ ],
+ "license": "Apache 2.0",
+ "checksums": { },
+ "version": "#{module_version}",
+ "description": "Standard Library for Puppet Modules",
+ "source": "git://github.com/puppetlabs/puppetlabs-stdlib.git",
+ "project_page": "https://github.com/puppetlabs/puppetlabs-stdlib",
+ "summary": "Puppet Module Standard Library",
+ "dependencies": [
+
+ ],
+ "author": "#{module_author}",
+ "name": "#{module_full_name}"
+ },
+ "tags": [
+ "puppetlabs",
+ "library",
+ "stdlib",
+ "standard",
+ "stages"
+ ],
+ "file_uri": "/#{api_version}/files/#{module_full_name_versioned}.tar.gz",
+ "file_size": 67586,
+ "file_md5": "#{module_md5}",
+ "downloads": 610751,
+ "readme": "",
+ "changelog": "",
+ "license": "",
+ "created_at": "2013-05-13 08:31:19 -0700",
+ "updated_at": "2013-05-13 08:31:19 -0700",
+ "deleted_at": null
+ }
+ }
end
+
+ it_behaves_like 'a module release'
end
- describe '#unpack' do
- it 'should call unpacker with correct params' do
- Puppet::ModuleTool::Applications::Unpacker.expects(:unpack).with(mock_file.path, mock_dir).returns(true)
+ context 'forge module with no dependencies field' do
+ let(:release_json) do %Q{
+ {
+ "uri": "/#{api_version}/releases/#{module_full_name_versioned}",
+ "module": {
+ "uri": "/#{api_version}/modules/#{module_full_name}",
+ "name": "#{module_name}",
+ "owner": {
+ "uri": "/#{api_version}/users/#{module_author}",
+ "username": "#{module_author}",
+ "gravatar_id": "fdd009b7c1ec96e088b389f773e87aec"
+ }
+ },
+ "version": "#{module_version}",
+ "metadata": {
+ "types": [ ],
+ "license": "Apache 2.0",
+ "checksums": { },
+ "version": "#{module_version}",
+ "description": "Standard Library for Puppet Modules",
+ "source": "git://github.com/puppetlabs/puppetlabs-stdlib.git",
+ "project_page": "https://github.com/puppetlabs/puppetlabs-stdlib",
+ "summary": "Puppet Module Standard Library",
+ "author": "#{module_author}",
+ "name": "#{module_full_name}"
+ },
+ "tags": [
+ "puppetlabs",
+ "library",
+ "stdlib",
+ "standard",
+ "stages"
+ ],
+ "file_uri": "/#{api_version}/files/#{module_full_name_versioned}.tar.gz",
+ "file_size": 67586,
+ "file_md5": "#{module_md5}",
+ "downloads": 610751,
+ "readme": "",
+ "changelog": "",
+ "license": "",
+ "created_at": "2013-05-13 08:31:19 -0700",
+ "updated_at": "2013-05-13 08:31:19 -0700",
+ "deleted_at": null
+ }
+ }
+ end
+
+ it_behaves_like 'a module release'
+ end
- release.send(:unpack, mock_file, mock_dir)
+ context 'forge module with the minimal set of fields' do
+ let(:release_json) do %Q{
+ {
+ "uri": "/#{api_version}/releases/#{module_full_name_versioned}",
+ "module": {
+ "uri": "/#{api_version}/modules/#{module_full_name}",
+ "name": "#{module_name}"
+ },
+ "metadata": {
+ "version": "#{module_version}",
+ "name": "#{module_full_name}"
+ },
+ "file_uri": "/#{api_version}/files/#{module_full_name_versioned}.tar.gz",
+ "file_size": 67586,
+ "file_md5": "#{module_md5}"
+ }
+ }
end
+
+ it_behaves_like 'a module release'
end
end
diff --git a/spec/unit/forge/repository_spec.rb b/spec/unit/forge/repository_spec.rb
index 04b10b166..4ac77ecb8 100644
--- a/spec/unit/forge/repository_spec.rb
+++ b/spec/unit/forge/repository_spec.rb
@@ -80,6 +80,20 @@ describe Puppet::Forge::Repository do
request['User-Agent'].should =~ /\bRuby\b/
end
+ it "Does not set Authorization header by default" do
+ Puppet.features.stubs(:pe_license?).returns(false)
+ Puppet[:forge_authorization] = nil
+ request = repository.get_request_object("the_path")
+ request['Authorization'].should == nil
+ end
+
+ it "Sets Authorization header from config" do
+ token = 'bearer some token'
+ Puppet[:forge_authorization] = token
+ request = repository.get_request_object("the_path")
+ request['Authorization'].should == token
+ end
+
it "escapes the received URI" do
unescaped_uri = "héllo world !! ç à"
performs_an_http_request do |http|
@@ -97,7 +111,7 @@ describe Puppet::Forge::Repository do
proxy = mock("http proxy")
proxy_class.expects(:new).with("fake.com", 80).returns(proxy)
proxy.expects(:start).yields(http).returns(result)
- Net::HTTP.expects(:Proxy).with("proxy", 1234).returns(proxy_class)
+ Net::HTTP.expects(:Proxy).with("proxy", 1234, nil, nil).returns(proxy_class)
end
def performs_an_https_request(result = nil, &block)
@@ -111,7 +125,94 @@ describe Puppet::Forge::Repository do
proxy.expects(:use_ssl=).with(true)
proxy.expects(:cert_store=)
proxy.expects(:verify_mode=).with(OpenSSL::SSL::VERIFY_PEER)
- Net::HTTP.expects(:Proxy).with("proxy", 1234).returns(proxy_class)
+ Net::HTTP.expects(:Proxy).with("proxy", 1234, nil, nil).returns(proxy_class)
+ end
+ end
+
+ describe "making a request against an authentiated proxy" do
+ before :each do
+ authenticated_proxy_settings_of("proxy", 1234, 'user1', 'password')
+ end
+
+ it "returns the result object from the request" do
+ result = "#{Object.new}"
+
+ performs_an_authenticated_http_request result do |http|
+ http.expects(:request).with(responds_with(:path, "the_path"))
+ end
+
+ repository.make_http_request("the_path").should == result
+ end
+
+ it 'returns the result object from a request with ssl' do
+ result = "#{Object.new}"
+ performs_an_authenticated_https_request result do |http|
+ http.expects(:request).with(responds_with(:path, "the_path"))
+ end
+
+ ssl_repository.make_http_request("the_path").should == result
+ end
+
+ it 'return a valid exception when there is an SSL verification problem' do
+ performs_an_authenticated_https_request "#{Object.new}" do |http|
+ http.expects(:request).with(responds_with(:path, "the_path")).raises OpenSSL::SSL::SSLError.new("certificate verify failed")
+ end
+
+ expect { ssl_repository.make_http_request("the_path") }.to raise_error Puppet::Forge::Errors::SSLVerifyError, 'Unable to verify the SSL certificate at https://fake.com'
+ end
+
+ it 'return a valid exception when there is a communication problem' do
+ performs_an_authenticated_http_request "#{Object.new}" do |http|
+ http.expects(:request).with(responds_with(:path, "the_path")).raises SocketError
+ end
+
+ expect { repository.make_http_request("the_path") }.
+ to raise_error Puppet::Forge::Errors::CommunicationError,
+ 'Unable to connect to the server at http://fake.com. Detail: SocketError.'
+ end
+
+ it "sets the user agent for the request" do
+ path = 'the_path'
+
+ request = repository.get_request_object(path)
+
+ request['User-Agent'].should =~ /\b#{agent}\b/
+ request['User-Agent'].should =~ /\bPuppet\b/
+ request['User-Agent'].should =~ /\bRuby\b/
+ end
+
+ it "escapes the received URI" do
+ unescaped_uri = "héllo world !! ç à"
+ performs_an_authenticated_http_request do |http|
+ http.expects(:request).with(responds_with(:path, URI.escape(unescaped_uri)))
+ end
+
+ repository.make_http_request(unescaped_uri)
+ end
+
+ def performs_an_authenticated_http_request(result = nil, &block)
+ http = mock("http client")
+ yield http
+
+ proxy_class = mock("http proxy class")
+ proxy = mock("http proxy")
+ proxy_class.expects(:new).with("fake.com", 80).returns(proxy)
+ proxy.expects(:start).yields(http).returns(result)
+ Net::HTTP.expects(:Proxy).with("proxy", 1234, "user1", "password").returns(proxy_class)
+ end
+
+ def performs_an_authenticated_https_request(result = nil, &block)
+ http = mock("http client")
+ yield http
+
+ proxy_class = mock("http proxy class")
+ proxy = mock("http proxy")
+ proxy_class.expects(:new).with("fake.com", 443).returns(proxy)
+ proxy.expects(:start).yields(http).returns(result)
+ proxy.expects(:use_ssl=).with(true)
+ proxy.expects(:cert_store=)
+ proxy.expects(:verify_mode=).with(OpenSSL::SSL::VERIFY_PEER)
+ Net::HTTP.expects(:Proxy).with("proxy", 1234, "user1", "password").returns(proxy_class)
end
end
@@ -119,4 +220,11 @@ describe Puppet::Forge::Repository do
Puppet[:http_proxy_host] = host
Puppet[:http_proxy_port] = port
end
+
+ def authenticated_proxy_settings_of(host, port, user, password)
+ Puppet[:http_proxy_host] = host
+ Puppet[:http_proxy_port] = port
+ Puppet[:http_proxy_user] = user
+ Puppet[:http_proxy_password] = password
+ end
end
diff --git a/spec/unit/forge_spec.rb b/spec/unit/forge_spec.rb
index 96bf8d3be..eb7c56a3e 100644
--- a/spec/unit/forge_spec.rb
+++ b/spec/unit/forge_spec.rb
@@ -110,17 +110,38 @@ describe Puppet::Forge do
forge.search('bacula').should == search_results
end
+ context "when module_groups are defined" do
+ let(:release_response) do
+ releases = JSON.parse(http_response)
+ releases['results'] = []
+ JSON.dump(releases)
+ end
+
+ before :each do
+ repository_responds_with(stub(:body => release_response, :code => '200')).with {|uri| uri =~ /module_groups=foo/}
+ Puppet[:module_groups] = "foo"
+ end
+
+ it "passes module_groups with search" do
+ forge.search('bacula')
+ end
+
+ it "passes module_groups with fetch" do
+ forge.fetch('puppetlabs-bacula')
+ end
+ end
+
context "when the connection to the forge fails" do
before :each do
repository_responds_with(stub(:body => '{}', :code => '404', :message => "not found"))
end
it "raises an error for search" do
- expect { forge.search('bacula') }.to raise_error Puppet::Forge::Errors::ResponseError, "Could not execute operation for 'bacula'. Detail: 404 not found."
+ expect { forge.search('bacula') }.to raise_error Puppet::Forge::Errors::ResponseError, "Request to Puppet Forge failed. Detail: 404 not found."
end
it "raises an error for fetch" do
- expect { forge.fetch('puppetlabs/bacula') }.to raise_error Puppet::Forge::Errors::ResponseError, "Could not execute operation for 'puppetlabs/bacula'. Detail: 404 not found."
+ expect { forge.fetch('puppetlabs/bacula') }.to raise_error Puppet::Forge::Errors::ResponseError, "Request to Puppet Forge failed. Detail: 404 not found."
end
end
@@ -130,7 +151,22 @@ describe Puppet::Forge do
end
it "raises an error for fetch" do
- expect { forge.fetch('puppetlabs/bacula') }.to raise_error Puppet::Forge::Errors::ResponseError, "Could not execute operation for 'puppetlabs/bacula'. Detail: 410 Gone."
+ expect { forge.fetch('puppetlabs/bacula') }.to raise_error Puppet::Forge::Errors::ResponseError, "Request to Puppet Forge failed. Detail: 410 Gone."
+ end
+ end
+
+ context "when the forge returns a module with unparseable dependencies" do
+ before :each do
+ response = JSON.parse(http_response)
+ release = response['results'][0]['current_release']
+ release['metadata']['dependencies'] = [{'name' => 'broken-garbage >= 1.0.0', 'version_requirement' => 'banana'}]
+ response['results'] = [release]
+ repository_responds_with(stub(:body => JSON.dump(response), :code => '200'))
+ end
+
+ it "ignores modules with unparseable dependencies" do
+ expect { result = forge.fetch('puppetlabs/bacula') }.to_not raise_error
+ expect { result.to be_empty }
end
end
end
diff --git a/spec/unit/functions/assert_type_spec.rb b/spec/unit/functions/assert_type_spec.rb
index d47f47e30..13b353401 100644
--- a/spec/unit/functions/assert_type_spec.rb
+++ b/spec/unit/functions/assert_type_spec.rb
@@ -37,8 +37,8 @@ describe 'the assert_type function' do
end.to raise_error(ArgumentError, Regexp.new(Regexp.escape(
"function 'assert_type' called with mis-matched arguments
expected one of:
- assert_type(Type type, Optional[Object] value) - arg count {2}
- assert_type(String type_string, Optional[Object] value) - arg count {2}
+ assert_type(Type type, Any value, Callable[Type, Type] block {0,1}) - arg count {2,3}
+ assert_type(String type_string, Any value, Callable[Type, Type] block {0,1}) - arg count {2,3}
actual:
assert_type(Integer, Integer) - arg count {2}")))
end
@@ -46,7 +46,13 @@ actual:
it 'allows the second arg to be undef/nil)' do
expect do
func.call({}, optional(String), nil)
- end.to_not raise_error(ArgumentError)
+ end.to_not raise_error
+ end
+
+ it 'can be called with a callable that receives a specific type' do
+ expected, actual = func.call({}, optional(String), 1, create_callable_2_args_unit)
+ expect(expected.to_s).to eql('Optional[String]')
+ expect(actual.to_s).to eql('Integer[1, 1]')
end
def optional(type_ref)
@@ -56,4 +62,17 @@ actual:
def type(type_ref)
Puppet::Pops::Types::TypeFactory.type_of(type_ref)
end
+
+ def create_callable_2_args_unit()
+ Puppet::Functions.create_function(:func) do
+ dispatch :func do
+ param 'Type', 'expected'
+ param 'Type', 'actual'
+ end
+
+ def func(expected, actual)
+ [expected, actual]
+ end
+ end.new({}, nil)
+ end
end
diff --git a/spec/unit/parser/methods/each_spec.rb b/spec/unit/functions/each_spec.rb
index 5e9ce4e0c..d6651cf5a 100644
--- a/spec/unit/parser/methods/each_spec.rb
+++ b/spec/unit/functions/each_spec.rb
@@ -1,7 +1,8 @@
require 'puppet'
require 'spec_helper'
require 'puppet_spec/compiler'
-require 'rubygems'
+
+require 'shared_behaviours/iterative_functions'
describe 'the each method' do
include PuppetSpec::Compiler
@@ -23,6 +24,7 @@ describe 'the each method' do
catalog.resource(:file, "/file_2")['ensure'].should == 'present'
catalog.resource(:file, "/file_3")['ensure'].should == 'present'
end
+
it 'each on an array selecting each value - function call style' do
catalog = compile_to_catalog(<<-MANIFEST)
$a = [1,2,3]
@@ -61,6 +63,7 @@ describe 'the each method' do
catalog.resource(:file, "/file_b")['ensure'].should == 'absent'
catalog.resource(:file, "/file_c")['ensure'].should == 'present'
end
+
it 'each on a hash selecting key and value' do
catalog = compile_to_catalog(<<-MANIFEST)
$a = {'a'=>present,'b'=>absent,'c'=>present}
@@ -73,7 +76,21 @@ describe 'the each method' do
catalog.resource(:file, "/file_b")['ensure'].should == 'absent'
catalog.resource(:file, "/file_c")['ensure'].should == 'present'
end
+
+ it 'each on a hash selecting key and value (using captures-last parameter)' do
+ catalog = compile_to_catalog(<<-MANIFEST)
+ $a = {'a'=>present,'b'=>absent,'c'=>present}
+ $a.each |*$kv| {
+ file { "/file_${kv[0]}": ensure => $kv[1] }
+ }
+ MANIFEST
+
+ catalog.resource(:file, "/file_a")['ensure'].should == 'present'
+ catalog.resource(:file, "/file_b")['ensure'].should == 'absent'
+ catalog.resource(:file, "/file_c")['ensure'].should == 'present'
+ end
end
+
context "should produce receiver" do
it 'each checking produced value using single expression' do
catalog = compile_to_catalog(<<-MANIFEST)
@@ -88,4 +105,7 @@ describe 'the each method' do
end
end
+ it_should_behave_like 'all iterative functions argument checks', 'each'
+ it_should_behave_like 'all iterative functions hash handling', 'each'
+
end
diff --git a/spec/unit/parser/functions/epp_spec.rb b/spec/unit/functions/epp_spec.rb
index b88d3da8f..382fd9548 100644
--- a/spec/unit/parser/functions/epp_spec.rb
+++ b/spec/unit/functions/epp_spec.rb
@@ -27,7 +27,7 @@ describe "the epp function" do
end
it "get nil accessing a variable that is undef" do
- scope['undef_var'] = :undef
+ scope['undef_var'] = nil
eval_template("<%= $undef_var == undef %>").should == "true"
end
@@ -36,26 +36,74 @@ describe "the epp function" do
eval_template_with_args("<%= $phantom == dragos %>", 'phantom' => 'dragos').should == "true"
end
+ it "can use values from the enclosing scope for defaults" do
+ scope['phantom'] = 'of the opera'
+ eval_template("<%- |$phantom = $phantom| -%><%= $phantom %>").should == "of the opera"
+ end
+
+ it "uses the default value if the given value is undef/nil" do
+ eval_template_with_args("<%- |$phantom = 'inside your mind'| -%><%= $phantom %>", 'phantom' => nil).should == "inside your mind"
+ end
+
it "gets shadowed variable if args are given and parameters are specified" do
scope['x'] = 'wrong one'
- eval_template_with_args("<%-( $x )-%><%= $x == correct %>", 'x' => 'correct').should == "true"
+ eval_template_with_args("<%- |$x| -%><%= $x == correct %>", 'x' => 'correct').should == "true"
end
it "raises an error if required variable is not given" do
scope['x'] = 'wrong one'
- expect {
+ expect do
eval_template_with_args("<%-| $x |-%><%= $x == correct %>", 'y' => 'correct')
- }.to raise_error(/no value given for required parameters x/)
+ end.to raise_error(/no value given for required parameters x/)
end
it "raises an error if too many arguments are given" do
scope['x'] = 'wrong one'
- expect {
+ expect do
eval_template_with_args("<%-| $x |-%><%= $x == correct %>", 'x' => 'correct', 'y' => 'surplus')
- }.to raise_error(/Too many arguments: 2 for 1/)
+ end.to raise_error(/Too many arguments: 2 for 1/)
+ end
+ end
+
+ context "when given an empty template" do
+ it "allows the template file to be empty" do
+ expect(eval_template("")).to eq("")
+ end
+
+ it "allows the template to have empty body after parameters" do
+ expect(eval_template_with_args("<%-|$x|%>", 'x'=>1)).to eq("")
end
end
+ context "when using typed parameters" do
+ it "allows a passed value that matches the parameter's type" do
+ expect(eval_template_with_args("<%-|String $x|-%><%= $x == correct %>", 'x' => 'correct')).to eq("true")
+ end
+
+ it "does not allow slurped parameters" do
+ expect do
+ eval_template_with_args("<%-|*$x|-%><%= $x %>", 'x' => 'incorrect')
+ end.to raise_error(/'captures rest' - not supported in an Epp Template/)
+ end
+
+ it "raises an error when the passed value does not match the parameter's type" do
+ expect do
+ eval_template_with_args("<%-|Integer $x|-%><%= $x %>", 'x' => 'incorrect')
+ end.to raise_error(/expected.*Integer.*actual.*String/m)
+ end
+
+ it "raises an error when the default value does not match the parameter's type" do
+ expect do
+ eval_template("<%-|Integer $x = 'nope'|-%><%= $x %>")
+ end.to raise_error(/expected.*Integer.*actual.*String/m)
+ end
+
+ it "allows an parameter to default to undef" do
+ expect(eval_template("<%-|Optional[Integer] $x = undef|-%><%= $x == undef %>")).to eq("true")
+ end
+ end
+
+
# although never a problem with epp
it "is not interfered with by having a variable named 'string' (#14093)" do
scope['string'] = "this output should not be seen"
@@ -78,7 +126,7 @@ describe "the epp function" do
}})
Puppet.override({:current_environment => (env = Puppet::Node::Environment.create(:testload, [ modules_dir ]))}, "test") do
node.environment = env
- expect(scope.function_epp([ 'testmodule/the_x.epp', { 'x' => '3'} ])).to eql("The x is 3")
+ expect(epp_function.call(scope, 'testmodule/the_x.epp', { 'x' => '3'} )).to eql("The x is 3")
end
end
end
@@ -89,7 +137,7 @@ describe "the epp function" do
File.open(filename, "w+") { |f| f.write(content) }
Puppet::Parser::Files.stubs(:find_template).returns(filename)
- scope.function_epp(['template', args_hash])
+ epp_function.call(scope, 'template', args_hash)
end
def eval_template(content)
@@ -98,6 +146,10 @@ describe "the epp function" do
File.open(filename, "w+") { |f| f.write(content) }
Puppet::Parser::Files.stubs(:find_template).returns(filename)
- scope.function_epp(['template'])
+ epp_function.call(scope, 'template')
+ end
+
+ def epp_function()
+ epp_func = scope.compiler.loaders.public_environment_loader.load(:function, 'epp')
end
end
diff --git a/spec/unit/parser/methods/filter_spec.rb b/spec/unit/functions/filter_spec.rb
index c9fed31d2..e904c6751 100644
--- a/spec/unit/parser/methods/filter_spec.rb
+++ b/spec/unit/functions/filter_spec.rb
@@ -1,11 +1,13 @@
require 'puppet'
require 'spec_helper'
require 'puppet_spec/compiler'
+require 'matchers/resource'
-require 'unit/parser/methods/shared'
+require 'shared_behaviours/iterative_functions'
describe 'the filter method' do
include PuppetSpec::Compiler
+ include Matchers::Resource
before :each do
Puppet[:parser] = 'future'
@@ -19,8 +21,8 @@ describe 'the filter method' do
}
MANIFEST
- catalog.resource(:file, "/file_strawberry")['ensure'].should == 'present'
- catalog.resource(:file, "/file_blueberry")['ensure'].should == 'present'
+ expect(catalog).to have_resource("File[/file_strawberry]").with_parameter(:ensure, 'present')
+ expect(catalog).to have_resource("File[/file_blueberry]").with_parameter(:ensure, 'present')
end
it 'should filter on enumerable type (Integer)' do
@@ -31,9 +33,9 @@ describe 'the filter method' do
}
MANIFEST
- catalog.resource(:file, "/file_3")['ensure'].should == 'present'
- catalog.resource(:file, "/file_6")['ensure'].should == 'present'
- catalog.resource(:file, "/file_9")['ensure'].should == 'present'
+ expect(catalog).to have_resource("File[/file_3]").with_parameter(:ensure, 'present')
+ expect(catalog).to have_resource("File[/file_6]").with_parameter(:ensure, 'present')
+ expect(catalog).to have_resource("File[/file_9]").with_parameter(:ensure, 'present')
end
it 'should filter on enumerable type (Integer) using two args index/value' do
@@ -44,9 +46,9 @@ describe 'the filter method' do
}
MANIFEST
- catalog.resource(:file, "/file_10")['ensure'].should == 'present'
- catalog.resource(:file, "/file_13")['ensure'].should == 'present'
- catalog.resource(:file, "/file_16")['ensure'].should == 'present'
+ expect(catalog).to have_resource("File[/file_10]").with_parameter(:ensure, 'present')
+ expect(catalog).to have_resource("File[/file_13]").with_parameter(:ensure, 'present')
+ expect(catalog).to have_resource("File[/file_16]").with_parameter(:ensure, 'present')
end
it 'should produce an array when acting on an array' do
@@ -57,8 +59,8 @@ describe 'the filter method' do
file { "/file_${b[1]}": ensure => present }
MANIFEST
- catalog.resource(:file, "/file_strawberry")['ensure'].should == 'present'
- catalog.resource(:file, "/file_blueberry")['ensure'].should == 'present'
+ expect(catalog).to have_resource("File[/file_strawberry]").with_parameter(:ensure, 'present')
+ expect(catalog).to have_resource("File[/file_blueberry]").with_parameter(:ensure, 'present')
end
it 'can filter array using index and value' do
@@ -69,8 +71,20 @@ describe 'the filter method' do
file { "/file_${b[1]}": ensure => present }
MANIFEST
- catalog.resource(:file, "/file_strawberry")['ensure'].should == 'present'
- catalog.resource(:file, "/file_orange")['ensure'].should == 'present'
+ expect(catalog).to have_resource("File[/file_strawberry]").with_parameter(:ensure, 'present')
+ expect(catalog).to have_resource("File[/file_orange]").with_parameter(:ensure, 'present')
+ end
+
+ it 'can filter array using index and value (using captures-rest)' do
+ catalog = compile_to_catalog(<<-MANIFEST)
+ $a = ['strawberry','blueberry','orange']
+ $b = $a.filter |*$ix|{ $ix[0] == 0 or $ix[0] ==2}
+ file { "/file_${b[0]}": ensure => present }
+ file { "/file_${b[1]}": ensure => present }
+ MANIFEST
+
+ expect(catalog).to have_resource("File[/file_strawberry]").with_parameter(:ensure, 'present')
+ expect(catalog).to have_resource("File[/file_orange]").with_parameter(:ensure, 'present')
end
it 'filters on a hash (all berries) by key' do
@@ -81,8 +95,8 @@ describe 'the filter method' do
}
MANIFEST
- catalog.resource(:file, "/file_strawberry")['ensure'].should == 'present'
- catalog.resource(:file, "/file_blueberry")['ensure'].should == 'present'
+ expect(catalog).to have_resource("File[/file_strawberry]").with_parameter(:ensure, 'present')
+ expect(catalog).to have_resource("File[/file_blueberry]").with_parameter(:ensure, 'present')
end
it 'should produce a hash when acting on a hash' do
@@ -95,9 +109,9 @@ describe 'the filter method' do
MANIFEST
- catalog.resource(:file, "/file_red")['ensure'].should == 'present'
- catalog.resource(:file, "/file_blue")['ensure'].should == 'present'
- catalog.resource(:file, "/file_")['ensure'].should == 'present'
+ expect(catalog).to have_resource("File[/file_red]").with_parameter(:ensure, 'present')
+ expect(catalog).to have_resource("File[/file_blue]").with_parameter(:ensure, 'present')
+ expect(catalog).to have_resource("File[/file_]").with_parameter(:ensure, 'present')
end
it 'filters on a hash (all berries) by value' do
@@ -108,26 +122,8 @@ describe 'the filter method' do
}
MANIFEST
- catalog.resource(:file, "/file_strawb")['ensure'].should == 'present'
- catalog.resource(:file, "/file_blueb")['ensure'].should == 'present'
- end
-
- context 'filter checks arguments and' do
- it 'raises an error when block has more than 2 argument' do
- expect do
- compile_to_catalog(<<-MANIFEST)
- [1].filter |$indexm, $x, $yikes|{ }
- MANIFEST
- end.to raise_error(Puppet::Error, /block must define at most two parameters/)
- end
-
- it 'raises an error when block has fewer than 1 argument' do
- expect do
- compile_to_catalog(<<-MANIFEST)
- [1].filter || { }
- MANIFEST
- end.to raise_error(Puppet::Error, /block must define at least one parameter/)
- end
+ expect(catalog).to have_resource("File[/file_strawb]").with_parameter(:ensure, 'present')
+ expect(catalog).to have_resource("File[/file_blueb]").with_parameter(:ensure, 'present')
end
it_should_behave_like 'all iterative functions argument checks', 'filter'
diff --git a/spec/unit/parser/functions/inline_epp_spec.rb b/spec/unit/functions/inline_epp_spec.rb
index 44b24528b..36328c2dc 100644
--- a/spec/unit/parser/functions/inline_epp_spec.rb
+++ b/spec/unit/functions/inline_epp_spec.rb
@@ -56,8 +56,18 @@ describe "the inline_epp function" do
end
end
+ context "when given an empty template" do
+ it "allows the template file to be empty" do
+ expect(eval_template("")).to eq("")
+ end
+
+ it "allows the template to have empty body after parameters" do
+ expect(eval_template_with_args("<%-|$x|%>", 'x'=>1)).to eq("")
+ end
+ end
+
it "renders a block expression" do
- eval_template_with_args("<%= {($x) $x + 1} %>", 'x' => 2).should == "3"
+ eval_template_with_args("<%= { $y = $x $x + 1} %>", 'x' => 2).should == "3"
end
# although never a problem with epp
@@ -73,10 +83,15 @@ describe "the inline_epp function" do
def eval_template_with_args(content, args_hash)
- scope.function_inline_epp([content, args_hash])
+ epp_function.call(scope, content, args_hash)
end
def eval_template(content)
- scope.function_inline_epp([content])
+ epp_function.call(scope, content)
end
+
+ def epp_function()
+ epp_func = scope.compiler.loaders.public_environment_loader.load(:function, 'inline_epp')
+ end
+
end
diff --git a/spec/unit/functions/map_spec.rb b/spec/unit/functions/map_spec.rb
new file mode 100644
index 000000000..e1b09cf24
--- /dev/null
+++ b/spec/unit/functions/map_spec.rb
@@ -0,0 +1,169 @@
+require 'puppet'
+require 'spec_helper'
+require 'puppet_spec/compiler'
+require 'matchers/resource'
+
+require 'shared_behaviours/iterative_functions'
+
+describe 'the map method' do
+ include PuppetSpec::Compiler
+ include Matchers::Resource
+
+ before :each do
+ Puppet[:parser] = "future"
+ end
+
+ context "using future parser" do
+ it 'map on an array (multiplying each value by 2)' do
+ catalog = compile_to_catalog(<<-MANIFEST)
+ $a = [1,2,3]
+ $a.map |$x|{ $x*2}.each |$v|{
+ file { "/file_$v": ensure => present }
+ }
+ MANIFEST
+
+ expect(catalog).to have_resource("File[/file_2]").with_parameter(:ensure, 'present')
+ expect(catalog).to have_resource("File[/file_4]").with_parameter(:ensure, 'present')
+ expect(catalog).to have_resource("File[/file_6]").with_parameter(:ensure, 'present')
+ end
+
+ it 'map on an enumerable type (multiplying each value by 2)' do
+ catalog = compile_to_catalog(<<-MANIFEST)
+ $a = Integer[1,3]
+ $a.map |$x|{ $x*2}.each |$v|{
+ file { "/file_$v": ensure => present }
+ }
+ MANIFEST
+
+ expect(catalog).to have_resource("File[/file_2]").with_parameter(:ensure, 'present')
+ expect(catalog).to have_resource("File[/file_4]").with_parameter(:ensure, 'present')
+ expect(catalog).to have_resource("File[/file_6]").with_parameter(:ensure, 'present')
+ end
+
+ it 'map on an integer (multiply each by 3)' do
+ catalog = compile_to_catalog(<<-MANIFEST)
+ 3.map |$x|{ $x*3}.each |$v|{
+ file { "/file_$v": ensure => present }
+ }
+ MANIFEST
+
+ expect(catalog).to have_resource("File[/file_0]").with_parameter(:ensure, 'present')
+ expect(catalog).to have_resource("File[/file_3]").with_parameter(:ensure, 'present')
+ expect(catalog).to have_resource("File[/file_6]").with_parameter(:ensure, 'present')
+ end
+
+ it 'map on a string' do
+ catalog = compile_to_catalog(<<-MANIFEST)
+ $a = {a=>x, b=>y}
+ "ab".map |$x|{$a[$x]}.each |$v|{
+ file { "/file_$v": ensure => present }
+ }
+ MANIFEST
+
+ expect(catalog).to have_resource("File[/file_x]").with_parameter(:ensure, 'present')
+ expect(catalog).to have_resource("File[/file_y]").with_parameter(:ensure, 'present')
+ end
+
+ it 'map on an array (multiplying value by 10 in even index position)' do
+ catalog = compile_to_catalog(<<-MANIFEST)
+ $a = [1,2,3]
+ $a.map |$i, $x|{ if $i % 2 == 0 {$x} else {$x*10}}.each |$v|{
+ file { "/file_$v": ensure => present }
+ }
+ MANIFEST
+
+ expect(catalog).to have_resource("File[/file_1]").with_parameter(:ensure, 'present')
+ expect(catalog).to have_resource("File[/file_20]").with_parameter(:ensure, 'present')
+ expect(catalog).to have_resource("File[/file_3]").with_parameter(:ensure, 'present')
+ end
+
+ it 'map on a hash selecting keys' do
+ catalog = compile_to_catalog(<<-MANIFEST)
+ $a = {'a'=>1,'b'=>2,'c'=>3}
+ $a.map |$x|{ $x[0]}.each |$k|{
+ file { "/file_$k": ensure => present }
+ }
+ MANIFEST
+
+ expect(catalog).to have_resource("File[/file_a]").with_parameter(:ensure, 'present')
+ expect(catalog).to have_resource("File[/file_b]").with_parameter(:ensure, 'present')
+ expect(catalog).to have_resource("File[/file_c]").with_parameter(:ensure, 'present')
+ end
+
+ it 'map on a hash selecting keys - using two block parameters' do
+ catalog = compile_to_catalog(<<-MANIFEST)
+ $a = {'a'=>1,'b'=>2,'c'=>3}
+ $a.map |$k,$v|{ file { "/file_$k": ensure => present }
+ }
+ MANIFEST
+
+ expect(catalog).to have_resource("File[/file_a]").with_parameter(:ensure, 'present')
+ expect(catalog).to have_resource("File[/file_b]").with_parameter(:ensure, 'present')
+ expect(catalog).to have_resource("File[/file_c]").with_parameter(:ensure, 'present')
+ end
+
+ it 'map on a hash using captures-last parameter' do
+ catalog = compile_to_catalog(<<-MANIFEST)
+ $a = {'a'=>present,'b'=>absent,'c'=>present}
+ $a.map |*$kv|{ file { "/file_${kv[0]}": ensure => $kv[1] } }
+ MANIFEST
+
+ expect(catalog).to have_resource("File[/file_a]").with_parameter(:ensure, 'present')
+ expect(catalog).to have_resource("File[/file_b]").with_parameter(:ensure, 'absent')
+ expect(catalog).to have_resource("File[/file_c]").with_parameter(:ensure, 'present')
+ end
+
+ it 'each on a hash selecting value' do
+ catalog = compile_to_catalog(<<-MANIFEST)
+ $a = {'a'=>1,'b'=>2,'c'=>3}
+ $a.map |$x|{ $x[1]}.each |$k|{ file { "/file_$k": ensure => present } }
+ MANIFEST
+
+ expect(catalog).to have_resource("File[/file_1]").with_parameter(:ensure, 'present')
+ expect(catalog).to have_resource("File[/file_2]").with_parameter(:ensure, 'present')
+ expect(catalog).to have_resource("File[/file_3]").with_parameter(:ensure, 'present')
+ end
+
+ it 'each on a hash selecting value - using two block parameters' do
+ catalog = compile_to_catalog(<<-MANIFEST)
+ $a = {'a'=>1,'b'=>2,'c'=>3}
+ $a.map |$k,$v|{ file { "/file_$v": ensure => present } }
+ MANIFEST
+
+ expect(catalog).to have_resource("File[/file_1]").with_parameter(:ensure, 'present')
+ expect(catalog).to have_resource("File[/file_2]").with_parameter(:ensure, 'present')
+ expect(catalog).to have_resource("File[/file_3]").with_parameter(:ensure, 'present')
+ end
+
+ context "handles data type corner cases" do
+ it "map gets values that are false" do
+ catalog = compile_to_catalog(<<-MANIFEST)
+ $a = [false,false]
+ $a.map |$x| { $x }.each |$i, $v| {
+ file { "/file_$i.$v": ensure => present }
+ }
+ MANIFEST
+
+ expect(catalog).to have_resource("File[/file_0.false]").with_parameter(:ensure, 'present')
+ expect(catalog).to have_resource("File[/file_1.false]").with_parameter(:ensure, 'present')
+ end
+
+ it "map gets values that are nil" do
+ Puppet::Parser::Functions.newfunction(:nil_array, :type => :rvalue) do |args|
+ [nil]
+ end
+ catalog = compile_to_catalog(<<-MANIFEST)
+ $a = nil_array()
+ $a.map |$x| { $x }.each |$i, $v| {
+ file { "/file_$i.$v": ensure => present }
+ }
+ MANIFEST
+
+ expect(catalog).to have_resource("File[/file_0.]").with_parameter(:ensure, 'present')
+ end
+ end
+
+ it_should_behave_like 'all iterative functions argument checks', 'map'
+ it_should_behave_like 'all iterative functions hash handling', 'map'
+ end
+end
diff --git a/spec/unit/functions/match_spec.rb b/spec/unit/functions/match_spec.rb
new file mode 100644
index 000000000..f4e2e383b
--- /dev/null
+++ b/spec/unit/functions/match_spec.rb
@@ -0,0 +1,57 @@
+require 'spec_helper'
+require 'puppet/pops'
+require 'puppet/loaders'
+
+describe 'the match function' do
+
+ before(:all) do
+ loaders = Puppet::Pops::Loaders.new(Puppet::Node::Environment.create(:testing, []))
+ Puppet.push_context({:loaders => loaders}, "test-examples")
+ end
+
+ after(:all) do
+ Puppet::Pops::Loaders.clear
+ Puppet::pop_context()
+ end
+
+ let(:func) do
+ Puppet.lookup(:loaders).puppet_system_loader.load(:function, 'match')
+ end
+
+ let(:type_parser) { Puppet::Pops::Types::TypeParser.new }
+
+
+ it 'matches string and regular expression without captures' do
+ expect(func.call({}, 'abc123', /[a-z]+[1-9]+/)).to eql(['abc123'])
+ end
+
+ it 'matches string and regular expression with captures' do
+ expect(func.call({}, 'abc123', /([a-z]+)([1-9]+)/)).to eql(['abc123', 'abc', '123'])
+ end
+
+ it 'produces nil if match is not found' do
+ expect(func.call({}, 'abc123', /([x]+)([6]+)/)).to be_nil
+ end
+
+ [ 'Pattern[/([a-z]+)([1-9]+)/]', # regexp
+ 'Pattern["([a-z]+)([1-9]+)"]', # string
+ 'Regexp[/([a-z]+)([1-9]+)/]', # regexp type
+ 'Pattern[/x9/, /([a-z]+)([1-9]+)/]', # regexp, first found matches
+ ].each do |pattern|
+ it "matches string and type #{pattern} with captures" do
+ expect(func.call({}, 'abc123', type(pattern))).to eql(['abc123', 'abc', '123'])
+ end
+ end
+
+ it 'matches an array of strings and yields a map of the result' do
+ expect(func.call({}, ['abc123', '2a', 'xyz2'], /([a-z]+)[1-9]+/)).to eql([['abc123', 'abc'], nil, ['xyz2', 'xyz']])
+ end
+
+ it 'raises error if Regexp type without regexp is used' do
+ expect{func.call({}, 'abc123', type('Regexp'))}.to raise_error(ArgumentError, /Given Regexp Type has no regular expression/)
+ end
+
+ def type(s)
+ Puppet::Pops::Types::TypeParser.new.parse(s)
+ end
+end
diff --git a/spec/unit/parser/methods/reduce_spec.rb b/spec/unit/functions/reduce_spec.rb
index 4f0c14e5e..032f6ccc4 100644
--- a/spec/unit/parser/methods/reduce_spec.rb
+++ b/spec/unit/functions/reduce_spec.rb
@@ -1,9 +1,12 @@
require 'puppet'
require 'spec_helper'
require 'puppet_spec/compiler'
+require 'matchers/resource'
+require 'shared_behaviours/iterative_functions'
describe 'the reduce method' do
include PuppetSpec::Compiler
+ include Matchers::Resource
before :all do
# enable switching back
@@ -32,7 +35,17 @@ describe 'the reduce method' do
file { "/file_$b": ensure => present }
MANIFEST
- catalog.resource(:file, "/file_6")['ensure'].should == 'present'
+ expect(catalog).to have_resource("File[/file_6]").with_parameter(:ensure, 'present')
+ end
+
+ it 'reduce on an array with captures rest in lambda' do
+ catalog = compile_to_catalog(<<-MANIFEST)
+ $a = [1,2,3]
+ $b = $a.reduce |*$mx| { $mx[0] + $mx[1] }
+ file { "/file_$b": ensure => present }
+ MANIFEST
+
+ expect(catalog).to have_resource("File[/file_6]").with_parameter(:ensure, 'present')
end
it 'reduce on enumerable type' do
@@ -42,7 +55,7 @@ describe 'the reduce method' do
file { "/file_$b": ensure => present }
MANIFEST
- catalog.resource(:file, "/file_6")['ensure'].should == 'present'
+ expect(catalog).to have_resource("File[/file_6]").with_parameter(:ensure, 'present')
end
it 'reduce on an array with start value' do
@@ -52,8 +65,9 @@ describe 'the reduce method' do
file { "/file_$b": ensure => present }
MANIFEST
- catalog.resource(:file, "/file_10")['ensure'].should == 'present'
+ expect(catalog).to have_resource("File[/file_10]").with_parameter(:ensure, 'present')
end
+
it 'reduce on a hash' do
catalog = compile_to_catalog(<<-MANIFEST)
$a = {a=>1, b=>2, c=>3}
@@ -62,8 +76,9 @@ describe 'the reduce method' do
file { "/file_${$b[0]}_${$b[1]}": ensure => present }
MANIFEST
- catalog.resource(:file, "/file_sum_6")['ensure'].should == 'present'
+ expect(catalog).to have_resource("File[/file_sum_6]").with_parameter(:ensure, 'present')
end
+
it 'reduce on a hash with start value' do
catalog = compile_to_catalog(<<-MANIFEST)
$a = {a=>1, b=>2, c=>3}
@@ -72,7 +87,10 @@ describe 'the reduce method' do
file { "/file_${$b[0]}_${$b[1]}": ensure => present }
MANIFEST
- catalog.resource(:file, "/file_sum_10")['ensure'].should == 'present'
+ expect(catalog).to have_resource("File[/file_sum_10]").with_parameter(:ensure, 'present')
end
end
+
+ it_should_behave_like 'all iterative functions argument checks', 'reduce'
+
end
diff --git a/spec/unit/parser/methods/slice_spec.rb b/spec/unit/functions/slice_spec.rb
index 1de1dd0f1..945cae5c7 100644
--- a/spec/unit/parser/methods/slice_spec.rb
+++ b/spec/unit/functions/slice_spec.rb
@@ -1,10 +1,11 @@
require 'puppet'
require 'spec_helper'
require 'puppet_spec/compiler'
-require 'rubygems'
+require 'matchers/resource'
describe 'methods' do
include PuppetSpec::Compiler
+ include Matchers::Resource
before :all do
# enable switching back
@@ -36,9 +37,22 @@ describe 'methods' do
}
MANIFEST
- catalog.resource(:file, "/file_1")['ensure'].should == 'present'
- catalog.resource(:file, "/file_2")['ensure'].should == 'absent'
- catalog.resource(:file, "/file_3")['ensure'].should == 'present'
+ expect(catalog).to have_resource("File[/file_1]").with_parameter(:ensure, 'present')
+ expect(catalog).to have_resource("File[/file_2]").with_parameter(:ensure, 'absent')
+ expect(catalog).to have_resource("File[/file_3]").with_parameter(:ensure, 'present')
+ end
+
+ it 'slice with captures last' do
+ catalog = compile_to_catalog(<<-MANIFEST)
+ $a = [1, present, 2, absent, 3, present]
+ $a.slice(2) |*$kv| {
+ file { "/file_${$kv[0]}": ensure => $kv[1] }
+ }
+ MANIFEST
+
+ expect(catalog).to have_resource("File[/file_1]").with_parameter(:ensure, 'present')
+ expect(catalog).to have_resource("File[/file_2]").with_parameter(:ensure, 'absent')
+ expect(catalog).to have_resource("File[/file_3]").with_parameter(:ensure, 'present')
end
it 'slice with one parameter' do
@@ -49,9 +63,9 @@ describe 'methods' do
}
MANIFEST
- catalog.resource(:file, "/file_1")['ensure'].should == 'present'
- catalog.resource(:file, "/file_2")['ensure'].should == 'absent'
- catalog.resource(:file, "/file_3")['ensure'].should == 'present'
+ expect(catalog).to have_resource("File[/file_1]").with_parameter(:ensure, 'present')
+ expect(catalog).to have_resource("File[/file_2]").with_parameter(:ensure, 'absent')
+ expect(catalog).to have_resource("File[/file_3]").with_parameter(:ensure, 'present')
end
it 'slice with shorter last slice' do
@@ -62,8 +76,8 @@ describe 'methods' do
}
MANIFEST
- catalog.resource(:file, "/file_1.2")['ensure'].should == 'present'
- catalog.resource(:file, "/file_3.")['ensure'].should == 'absent'
+ expect(catalog).to have_resource("File[/file_1.2]").with_parameter(:ensure, 'present')
+ expect(catalog).to have_resource("File[/file_3.]").with_parameter(:ensure, 'absent')
end
end
@@ -76,8 +90,8 @@ describe 'methods' do
}
MANIFEST
- catalog.resource(:file, "/file_1.2")['ensure'].should == 'present'
- catalog.resource(:file, "/file_3.")['ensure'].should == 'absent'
+ expect(catalog).to have_resource("File[/file_1.2]").with_parameter(:ensure, 'present')
+ expect(catalog).to have_resource("File[/file_3.]").with_parameter(:ensure, 'absent')
end
end
@@ -90,8 +104,8 @@ describe 'methods' do
}
MANIFEST
- catalog.resource(:file, "/file_1.2")['ensure'].should == 'present'
- catalog.resource(:file, "/file_3.4")['ensure'].should == 'present'
+ expect(catalog).to have_resource("File[/file_1.2]").with_parameter(:ensure, 'present')
+ expect(catalog).to have_resource("File[/file_3.4]").with_parameter(:ensure, 'present')
end
it 'slice with integer' do
@@ -101,8 +115,8 @@ describe 'methods' do
}
MANIFEST
- catalog.resource(:file, "/file_0.1")['ensure'].should == 'present'
- catalog.resource(:file, "/file_2.3")['ensure'].should == 'present'
+ expect(catalog).to have_resource("File[/file_0.1]").with_parameter(:ensure, 'present')
+ expect(catalog).to have_resource("File[/file_2.3]").with_parameter(:ensure, 'present')
end
it 'slice with string' do
@@ -112,8 +126,8 @@ describe 'methods' do
}
MANIFEST
- catalog.resource(:file, "/file_a.b")['ensure'].should == 'present'
- catalog.resource(:file, "/file_c.d")['ensure'].should == 'present'
+ expect(catalog).to have_resource("File[/file_a.b]").with_parameter(:ensure, 'present')
+ expect(catalog).to have_resource("File[/file_c.d]").with_parameter(:ensure, 'present')
end
end
@@ -126,10 +140,9 @@ describe 'methods' do
}
MANIFEST
- catalog.resource(:file, "/file_1")['ensure'].should == 'present'
- catalog.resource(:file, "/file_2")['ensure'].should == 'absent'
- catalog.resource(:file, "/file_3")['ensure'].should == 'present'
-
+ expect(catalog).to have_resource("File[/file_1]").with_parameter(:ensure, 'present')
+ expect(catalog).to have_resource("File[/file_2]").with_parameter(:ensure, 'absent')
+ expect(catalog).to have_resource("File[/file_3]").with_parameter(:ensure, 'present')
end
end
end
diff --git a/spec/unit/functions/with_spec.rb b/spec/unit/functions/with_spec.rb
new file mode 100644
index 000000000..952b14412
--- /dev/null
+++ b/spec/unit/functions/with_spec.rb
@@ -0,0 +1,35 @@
+require 'spec_helper'
+
+require 'puppet_spec/compiler'
+require 'matchers/resource'
+
+describe 'the with function' do
+ include PuppetSpec::Compiler
+ include Matchers::Resource
+
+ before :each do
+ Puppet[:parser] = 'future'
+ end
+
+ it 'calls a lambda passing no arguments' do
+ expect(compile_to_catalog("with() || { notify { testing: } }")).to have_resource('Notify[testing]')
+ end
+
+ it 'calls a lambda passing a single argument' do
+ expect(compile_to_catalog('with(1) |$x| { notify { "testing$x": } }')).to have_resource('Notify[testing1]')
+ end
+
+ it 'calls a lambda passing more than one argument' do
+ expect(compile_to_catalog('with(1, 2) |*$x| { notify { "testing${x[0]}, ${x[1]}": } }')).to have_resource('Notify[testing1, 2]')
+ end
+
+ it 'passes a type reference to a lambda' do
+ expect(compile_to_catalog('notify { test: message => "data" } with(Notify[test]) |$x| { notify { "${x[message]}": } }')).to have_resource('Notify[data]')
+ end
+
+ it 'errors when not given enough arguments for the lambda' do
+ expect do
+ compile_to_catalog('with(1) |$x, $y| { }')
+ end.to raise_error(/Parameter \$y is required but no value was given/m)
+ end
+end
diff --git a/spec/unit/functions4_spec.rb b/spec/unit/functions4_spec.rb
index a5404cc1e..6c31b75c2 100644
--- a/spec/unit/functions4_spec.rb
+++ b/spec/unit/functions4_spec.rb
@@ -83,9 +83,9 @@ actual:
func = f.new(:closure_scope, :loader)
expect(func.is_a?(Puppet::Functions::Function)).to be_true
signature = if RUBY_VERSION =~ /^1\.8/
- 'Object{2}'
+ 'Any{2}'
else
- 'Object x, Object y'
+ 'Any x, Any y'
end
expect do
func.call({}, 10)
@@ -102,9 +102,9 @@ actual:
func = f.new(:closure_scope, :loader)
expect(func.is_a?(Puppet::Functions::Function)).to be_true
signature = if RUBY_VERSION =~ /^1\.8/
- 'Object{2}'
+ 'Any{2}'
else
- 'Object x, Object y'
+ 'Any x, Any y'
end
expect do
func.call({}, 10, 10, 10)
@@ -162,9 +162,9 @@ actual:
func = f.new(:closure_scope, :loader)
expect(func.is_a?(Puppet::Functions::Function)).to be_true
signature = if RUBY_VERSION =~ /^1\.8/
- 'Object{2,}'
+ 'Any{2,}'
else
- 'Object x, Object y, Object a?, Object b?, Object c{0,}'
+ 'Any x, Any y, Any a?, Any b?, Any c{0,}'
end
expect do
func.call({}, 10)
@@ -439,12 +439,11 @@ actual:
# about selection of parser and evaluator
#
Puppet[:parser] = 'future'
- Puppet[:evaluator] = 'future'
# Puppetx cannot be loaded until the correct parser has been set (injector is turned off otherwise)
require 'puppetx'
end
- let(:parser) { Puppet::Pops::Parser::EvaluatingParser::Transitional.new }
+ let(:parser) { Puppet::Pops::Parser::EvaluatingParser.new }
let(:node) { 'node.example.com' }
let(:scope) { s = create_test_scope_for_node(node); s }
diff --git a/spec/unit/indirector/catalog/compiler_spec.rb b/spec/unit/indirector/catalog/compiler_spec.rb
index 7d92aa80f..4aaecd664 100755
--- a/spec/unit/indirector/catalog/compiler_spec.rb
+++ b/spec/unit/indirector/catalog/compiler_spec.rb
@@ -6,10 +6,8 @@ require 'puppet/rails'
describe Puppet::Resource::Catalog::Compiler do
before do
- require 'puppet/rails'
Puppet::Rails.stubs(:init)
Facter.stubs(:to_hash).returns({})
- Facter.stubs(:value).returns(Facter::Util::Fact.new("something"))
end
describe "when initializing" do
diff --git a/spec/unit/indirector/catalog/static_compiler_spec.rb b/spec/unit/indirector/catalog/static_compiler_spec.rb
index a3d13e804..556bc7943 100644
--- a/spec/unit/indirector/catalog/static_compiler_spec.rb
+++ b/spec/unit/indirector/catalog/static_compiler_spec.rb
@@ -11,10 +11,21 @@ describe Puppet::Resource::Catalog::StaticCompiler do
end
before :each do
+ Facter.stubs(:loadfacts)
Facter.stubs(:to_hash).returns({})
Facter.stubs(:value)
end
+ around(:each) do |example|
+ Puppet.override({
+ :current_environment => Puppet::Node::Environment.create(:app, []),
+ },
+ "Ensure we are using an environment other than root"
+ ) do
+ example.run
+ end
+ end
+
let(:request) do
Puppet::Indirector::Request.new(:the_indirection_named_foo,
:find,
diff --git a/spec/unit/indirector/data_binding/hiera_spec.rb b/spec/unit/indirector/data_binding/hiera_spec.rb
index 7ab3b27fc..12572b174 100644
--- a/spec/unit/indirector/data_binding/hiera_spec.rb
+++ b/spec/unit/indirector/data_binding/hiera_spec.rb
@@ -2,34 +2,6 @@ require 'spec_helper'
require 'puppet/indirector/data_binding/hiera'
describe Puppet::DataBinding::Hiera do
- include PuppetSpec::Files
-
- def write_hiera_config(config_file, datadir)
- File.open(config_file, 'w') do |f|
- f.write("---
- :yaml:
- :datadir: #{datadir}
- :hierarchy: ['global', 'invalid']
- :logger: 'noop'
- :backends: ['yaml']
- ")
- end
- end
-
- def request(key)
- Puppet::Indirector::Request.new(:hiera, :find, key, nil)
- end
-
- before do
- hiera_config_file = tmpfile("hiera.yaml")
- Puppet.settings[:hiera_config] = hiera_config_file
- write_hiera_config(hiera_config_file, my_fixture_dir)
- end
-
- after do
- Puppet::DataBinding::Hiera.instance_variable_set(:@hiera, nil)
- end
-
it "should have documentation" do
Puppet::DataBinding::Hiera.doc.should_not be_nil
end
@@ -42,73 +14,6 @@ describe Puppet::DataBinding::Hiera do
it "should have its name set to :hiera" do
Puppet::DataBinding::Hiera.name.should == :hiera
end
- it "should be the default data_binding terminus" do
- Puppet.settings[:data_binding_terminus].should == :hiera
- end
-
- it "should raise an error if we don't have the hiera feature" do
- Puppet.features.expects(:hiera?).returns(false)
- lambda { Puppet::DataBinding::Hiera.new }.should raise_error RuntimeError,
- "Hiera terminus not supported without hiera library"
- end
-
- describe "the behavior of the hiera_config method", :if => Puppet.features.hiera? do
- it "should override the logger and set it to puppet" do
- Puppet::DataBinding::Hiera.hiera_config[:logger].should == "puppet"
- end
- context "when the Hiera configuration file does not exist" do
- let(:path) { File.expand_path('/doesnotexist') }
-
- before do
- Puppet.settings[:hiera_config] = path
- end
-
- it "should log a warning" do
- Puppet.expects(:warning).with(
- "Config file #{path} not found, using Hiera defaults")
- Puppet::DataBinding::Hiera.hiera_config
- end
-
- it "should only configure the logger and set it to puppet" do
- Puppet.expects(:warning).with(
- "Config file #{path} not found, using Hiera defaults")
- Puppet::DataBinding::Hiera.hiera_config.should == { :logger => 'puppet' }
- end
- end
- end
-
- describe "the behavior of the find method", :if => Puppet.features.hiera? do
-
- let(:data_binder) { Puppet::DataBinding::Hiera.new }
-
- it "should support looking up an integer" do
- data_binder.find(request("integer")).should == 3000
- end
-
- it "should support looking up a string" do
- data_binder.find(request("string")).should == 'apache'
- end
-
- it "should support looking up an array" do
- data_binder.find(request("array")).should == [
- '0.ntp.puppetlabs.com',
- '1.ntp.puppetlabs.com',
- ]
- end
-
- it "should support looking up a hash" do
- data_binder.find(request("hash")).should == {
- 'user' => 'Hightower',
- 'group' => 'admin',
- 'mode' => '0644'
- }
- end
-
- it "raises a data binding error if hiera cannot parse the yaml data" do
- expect do
- data_binder.find(request('invalid'))
- end.to raise_error(Puppet::DataBinding::LookupError)
- end
- end
+ it_should_behave_like "Hiera indirection", Puppet::DataBinding::Hiera, my_fixture_dir
end
diff --git a/spec/unit/indirector/facts/facter_spec.rb b/spec/unit/indirector/facts/facter_spec.rb
index cb90fb7c3..4417ede54 100755
--- a/spec/unit/indirector/facts/facter_spec.rb
+++ b/spec/unit/indirector/facts/facter_spec.rb
@@ -1,9 +1,8 @@
#! /usr/bin/env ruby
require 'spec_helper'
-
require 'puppet/indirector/facts/facter'
-module PuppetNodeFactsFacter
+module NodeFactsFacterSpec
describe Puppet::Node::Facts::Facter do
FS = Puppet::FileSystem
@@ -24,27 +23,6 @@ describe Puppet::Node::Facts::Facter do
Puppet::Node::Facts::Facter.name.should == :facter
end
- describe "when reloading Facter" do
- before do
- @facter_class = Puppet::Node::Facts::Facter
- Facter.stubs(:clear)
- Facter.stubs(:load)
- Facter.stubs(:loadfacts)
- end
-
- it "should clear Facter" do
- Facter.expects(:clear)
- @facter_class.reload_facter
- end
-
- it "should load all Facter facts" do
- Facter.expects(:loadfacts)
- @facter_class.reload_facter
- end
- end
-end
-
-describe Puppet::Node::Facts::Facter do
before :each do
Puppet::Node::Facts::Facter.stubs(:reload_facter)
@facter = Puppet::Node::Facts::Facter.new
@@ -54,22 +32,31 @@ describe Puppet::Node::Facts::Facter do
@environment = stub 'environment'
@request.stubs(:environment).returns(@environment)
@request.environment.stubs(:modules).returns([])
+ @request.environment.stubs(:modulepath).returns([])
end
- describe Puppet::Node::Facts::Facter, " when finding facts" do
- it "should reset and load facts" do
- clear = sequence 'clear'
- Puppet::Node::Facts::Facter.expects(:reload_facter).in_sequence(clear)
- Puppet::Node::Facts::Facter.expects(:load_fact_plugins).in_sequence(clear)
+ describe 'when finding facts' do
+ it 'should reset facts' do
+ reset = sequence 'reset'
+ Facter.expects(:reset).in_sequence(reset)
+ Puppet::Node::Facts::Facter.expects(:setup_search_paths).in_sequence(reset)
+ @facter.find(@request)
+ end
+
+ it 'should include external facts when feature is present' do
+ reset = sequence 'reset'
+ Puppet.features.stubs(:external_facts?).returns true
+ Facter.expects(:reset).in_sequence(reset)
+ Puppet::Node::Facts::Facter.expects(:setup_external_search_paths).in_sequence(reset)
+ Puppet::Node::Facts::Facter.expects(:setup_search_paths).in_sequence(reset)
@facter.find(@request)
end
- it "should include external facts when feature is present" do
- clear = sequence 'clear'
- Puppet.features.stubs(:external_facts?).returns(:true)
- Puppet::Node::Facts::Facter.expects(:setup_external_facts).in_sequence(clear)
- Puppet::Node::Facts::Facter.expects(:reload_facter).in_sequence(clear)
- Puppet::Node::Facts::Facter.expects(:load_fact_plugins).in_sequence(clear)
+ it 'should not include external facts when feature is not present' do
+ reset = sequence 'reset'
+ Puppet.features.stubs(:external_facts?).returns false
+ Facter.expects(:reset).in_sequence(reset)
+ Puppet::Node::Facts::Facter.expects(:setup_search_paths).in_sequence(reset)
@facter.find(@request)
end
@@ -114,89 +101,69 @@ describe Puppet::Node::Facts::Facter do
end
end
- describe Puppet::Node::Facts::Facter, " when saving facts" do
-
- it "should fail" do
- proc { @facter.save(@facts) }.should raise_error(Puppet::DevError)
- end
- end
-
- describe Puppet::Node::Facts::Facter, " when destroying facts" do
-
- it "should fail" do
- proc { @facter.destroy(@facts) }.should raise_error(Puppet::DevError)
- end
+ it 'should fail when saving facts' do
+ proc { @facter.save(@facts) }.should raise_error(Puppet::DevError)
end
- it "should skip files when asked to load a directory" do
- FileTest.expects(:directory?).with("myfile").returns false
-
- Puppet::Node::Facts::Facter.load_facts_in_dir("myfile")
+ it 'should fail when destroying facts' do
+ proc { @facter.destroy(@facts) }.should raise_error(Puppet::DevError)
end
- it "should load each ruby file when asked to load a directory" do
- FileTest.expects(:directory?).with("mydir").returns true
- Dir.expects(:chdir).with("mydir").yields
+ describe 'when setting up search paths' do
+ let(:factpath1) { File.expand_path 'one' }
+ let(:factpath2) { File.expand_path 'two' }
+ let(:factpath) { [factpath1, factpath2].join(File::PATH_SEPARATOR) }
+ let(:modulepath) { File.expand_path 'module/foo' }
+ let(:modulelibfacter) { File.expand_path 'module/foo/lib/facter' }
+ let(:modulepluginsfacter) { File.expand_path 'module/foo/plugins/facter' }
- Dir.expects(:glob).with("*.rb").returns %w{a.rb b.rb}
+ before :each do
+ FileTest.expects(:directory?).with(factpath1).returns true
+ FileTest.expects(:directory?).with(factpath2).returns true
+ @request.environment.stubs(:modulepath).returns [modulepath]
+ Dir.expects(:glob).with("#{modulepath}/*/lib/facter").returns [modulelibfacter]
+ Dir.expects(:glob).with("#{modulepath}/*/plugins/facter").returns [modulepluginsfacter]
- Puppet::Node::Facts::Facter.expects(:load).with("a.rb")
- Puppet::Node::Facts::Facter.expects(:load).with("b.rb")
-
- Puppet::Node::Facts::Facter.load_facts_in_dir("mydir")
- end
+ Puppet[:factpath] = factpath
+ end
- it "should include pluginfactdest when loading external facts",
- :if => (Puppet.features.external_facts? and not Puppet.features.microsoft_windows?) do
- Puppet[:pluginfactdest] = "/plugin/dest"
- @facter.find(@request)
- Facter.search_external_path.include?("/plugin/dest")
- end
+ it 'should skip files' do
+ FileTest.expects(:directory?).with(modulelibfacter).returns false
+ FileTest.expects(:directory?).with(modulepluginsfacter).returns false
+ Facter.expects(:search).with(factpath1, factpath2)
+ Puppet::Node::Facts::Facter.setup_search_paths @request
+ end
- it "should include pluginfactdest when loading external facts",
- :if => (Puppet.features.external_facts? and Puppet.features.microsoft_windows?) do
- Puppet[:pluginfactdest] = "/plugin/dest"
- @facter.find(@request)
- Facter.search_external_path.include?("C:/plugin/dest")
+ it 'should add directories' do
+ FileTest.expects(:directory?).with(modulelibfacter).returns true
+ FileTest.expects(:directory?).with(modulepluginsfacter).returns true
+ Facter.expects(:search).with(modulelibfacter, modulepluginsfacter, factpath1, factpath2)
+ Puppet::Node::Facts::Facter.setup_search_paths @request
+ end
end
- describe "when loading fact plugins from disk" do
- let(:one) { File.expand_path("one") }
- let(:two) { File.expand_path("two") }
-
- it "should load each directory in the Fact path" do
- Puppet[:factpath] = [one, two].join(File::PATH_SEPARATOR)
-
- Puppet::Node::Facts::Facter.expects(:load_facts_in_dir).with(one)
- Puppet::Node::Facts::Facter.expects(:load_facts_in_dir).with(two)
+ describe 'when setting up external search paths', :if => Puppet.features.external_facts? do
+ let(:pluginfactdest) { File.expand_path 'plugin/dest' }
+ let(:modulepath) { File.expand_path 'module/foo' }
+ let(:modulefactsd) { File.expand_path 'module/foo/facts.d' }
- Puppet::Node::Facts::Facter.load_fact_plugins
+ before :each do
+ FileTest.expects(:directory?).with(pluginfactdest).returns true
+ mod = Puppet::Module.new('foo', modulepath, @request.environment)
+ @request.environment.stubs(:modules).returns [mod]
+ Puppet[:pluginfactdest] = pluginfactdest
end
- it "should load all facts from the modules" do
- Puppet::Node::Facts::Facter.stubs(:load_facts_in_dir)
-
- Dir.stubs(:glob).returns []
- Dir.expects(:glob).with("#{one}/*/lib/facter").returns %w{oneA oneB}
- Dir.expects(:glob).with("#{two}/*/lib/facter").returns %w{twoA twoB}
-
- Puppet::Node::Facts::Facter.expects(:load_facts_in_dir).with("oneA")
- Puppet::Node::Facts::Facter.expects(:load_facts_in_dir).with("oneB")
- Puppet::Node::Facts::Facter.expects(:load_facts_in_dir).with("twoA")
- Puppet::Node::Facts::Facter.expects(:load_facts_in_dir).with("twoB")
-
- FS.overlay(FS::MemoryFile.a_directory(one), FS::MemoryFile.a_directory(two)) do
- Puppet.override(:current_environment => Puppet::Node::Environment.create(:testing, [one, two], "")) do
- Puppet::Node::Facts::Facter.load_fact_plugins
- end
- end
+ it 'should skip files' do
+ File.expects(:directory?).with(modulefactsd).returns false
+ Facter.expects(:search_external).with [pluginfactdest]
+ Puppet::Node::Facts::Facter.setup_external_search_paths @request
end
- it "should include module plugin facts when present", :if => Puppet.features.external_facts? do
- mod = Puppet::Module.new("mymodule", "#{one}/mymodule", @request.environment)
- @request.environment.stubs(:modules).returns([mod])
- @facter.find(@request)
- Facter.search_external_path.include?("#{one}/mymodule/facts.d")
+ it 'should add directories' do
+ File.expects(:directory?).with(modulefactsd).returns true
+ Facter.expects(:search_external).with [modulefactsd, pluginfactdest]
+ Puppet::Node::Facts::Facter.setup_external_search_paths @request
end
end
end
diff --git a/spec/unit/indirector/hiera_spec.rb b/spec/unit/indirector/hiera_spec.rb
new file mode 100644
index 000000000..f74fd29aa
--- /dev/null
+++ b/spec/unit/indirector/hiera_spec.rb
@@ -0,0 +1,17 @@
+require 'spec_helper'
+require 'puppet/data_binding'
+require 'puppet/indirector/hiera'
+require 'hiera/backend'
+
+describe Puppet::Indirector::Hiera do
+
+ module Testing
+ module DataBinding
+ class Hiera < Puppet::Indirector::Hiera
+ end
+ end
+ end
+
+ it_should_behave_like "Hiera indirection", Testing::DataBinding::Hiera, my_fixture_dir
+end
+
diff --git a/spec/unit/indirector/request_spec.rb b/spec/unit/indirector/request_spec.rb
index 3c11664d3..e3edfd670 100755
--- a/spec/unit/indirector/request_spec.rb
+++ b/spec/unit/indirector/request_spec.rb
@@ -232,14 +232,12 @@ describe Puppet::Indirector::Request do
Puppet::Indirector::Request.new(:myind, :find, "my key", nil, :environment => env).environment.should equal(env)
end
- it "should use the configured environment when none is provided" do
+ it "should use the current environment when none is provided" do
configured = Puppet::Node::Environment.create(:foo, [])
Puppet[:environment] = "foo"
- Puppet.override(:environments => Puppet::Environments::Static.new(configured)) do
- Puppet::Indirector::Request.new(:myind, :find, "my key", nil).environment.should == configured
- end
+ expect(Puppet::Indirector::Request.new(:myind, :find, "my key", nil).environment).to eq(Puppet.lookup(:current_environment))
end
it "should support converting its options to a hash" do
diff --git a/spec/unit/indirector/resource/ral_spec.rb b/spec/unit/indirector/resource/ral_spec.rb
index 8b42f6881..36ba5f1c2 100755
--- a/spec/unit/indirector/resource/ral_spec.rb
+++ b/spec/unit/indirector/resource/ral_spec.rb
@@ -25,6 +25,11 @@ describe "Puppet::Resource::Ral" do
Puppet::Resource::Ral.new.find(@request).should == my_resource
end
+ it "should produce Puppet::Error instead of ArgumentError" do
+ @bad_request = stub 'thiswillcauseanerror', :key => "thiswill/causeanerror"
+ expect{Puppet::Resource::Ral.new.find(@bad_request)}.to raise_error(Puppet::Error)
+ end
+
it "if there is no instance, it should create one" do
wrong_instance = stub "wrong user", :name => "bob"
root = mock "Root User"
diff --git a/spec/unit/indirector/resource_type/parser_spec.rb b/spec/unit/indirector/resource_type/parser_spec.rb
index cf81428e4..fdbab84e7 100755
--- a/spec/unit/indirector/resource_type/parser_spec.rb
+++ b/spec/unit/indirector/resource_type/parser_spec.rb
@@ -7,9 +7,13 @@ require 'puppet_spec/files'
describe Puppet::Indirector::ResourceType::Parser do
include PuppetSpec::Files
+ let(:environmentpath) { tmpdir("envs") }
+ let(:modulepath) { "#{environmentpath}/test/modules" }
+ let(:environment) { Puppet::Node::Environment.create(:test, [modulepath]) }
before do
@terminus = Puppet::Indirector::ResourceType::Parser.new
@request = Puppet::Indirector::Request.new(:resource_type, :find, "foo", nil)
+ @request.environment = environment
@krt = @request.environment.known_resource_types
end
@@ -26,19 +30,18 @@ describe Puppet::Indirector::ResourceType::Parser do
end
it "should attempt to load the type if none is found in memory" do
- dir = tmpdir("find_a_type")
- FileUtils.mkdir_p(dir)
- Puppet[:modulepath] = dir
+ FileUtils.mkdir_p(modulepath)
# Make a new request, since we've reset the env
- @request = Puppet::Indirector::Request.new(:resource_type, :find, "foo::bar", nil)
+ request = Puppet::Indirector::Request.new(:resource_type, :find, "foo::bar", nil)
+ request.environment = environment
- manifest_path = File.join(dir, "foo", "manifests")
+ manifest_path = File.join(modulepath, "foo", "manifests")
FileUtils.mkdir_p(manifest_path)
File.open(File.join(manifest_path, "bar.pp"), "w") { |f| f.puts "class foo::bar {}" }
- result = @terminus.find(@request)
+ result = @terminus.find(request)
result.should be_instance_of(Puppet::Resource::Type)
result.name.should == "foo::bar"
end
@@ -108,10 +111,11 @@ describe Puppet::Indirector::ResourceType::Parser do
second = File.join(dir, "second")
FileUtils.mkdir_p(first)
FileUtils.mkdir_p(second)
- Puppet[:modulepath] = "#{first}#{File::PATH_SEPARATOR}#{second}"
+ environment = Puppet::Node::Environment.create(:test, [first, second])
# Make a new request, since we've reset the env
- @request = Puppet::Indirector::Request.new(:resource_type, :search, "*", nil)
+ request = Puppet::Indirector::Request.new(:resource_type, :search, "*", nil)
+ request.environment = environment
onepath = File.join(first, "one", "manifests")
FileUtils.mkdir_p(onepath)
@@ -121,7 +125,7 @@ describe Puppet::Indirector::ResourceType::Parser do
File.open(File.join(onepath, "oneklass.pp"), "w") { |f| f.puts "class one::oneklass {}" }
File.open(File.join(twopath, "twoklass.pp"), "w") { |f| f.puts "class two::twoklass {}" }
- result = @terminus.search(@request)
+ result = @terminus.search(request)
result.find { |t| t.name == "one::oneklass" }.should be_instance_of(Puppet::Resource::Type)
result.find { |t| t.name == "two::twoklass" }.should be_instance_of(Puppet::Resource::Type)
end
@@ -228,10 +232,11 @@ describe Puppet::Indirector::ResourceType::Parser do
second = File.join(dir, "second")
FileUtils.mkdir_p(first)
FileUtils.mkdir_p(second)
- Puppet[:modulepath] = "#{first}#{File::PATH_SEPARATOR}#{second}"
+ environment = Puppet::Node::Environment.create(:test, [first,second])
# Make a new request, since we've reset the env
- @request = Puppet::Indirector::Request.new(:resource_type, :search, "*", nil)
+ request = Puppet::Indirector::Request.new(:resource_type, :search, "*", nil)
+ request.environment = environment
onepath = File.join(first, "one", "manifests")
FileUtils.mkdir_p(onepath)
@@ -241,7 +246,7 @@ describe Puppet::Indirector::ResourceType::Parser do
File.open(File.join(onepath, "oneklass.pp"), "w") { |f| f.puts "class one::oneklass {}" }
File.open(File.join(twopath, "twoklass.pp"), "w") { |f| f.puts "class two::twoklass {}" }
- result = @terminus.search(@request)
+ result = @terminus.search(request)
result.find { |t| t.name == "one::oneklass" }.should be_instance_of(Puppet::Resource::Type)
result.find { |t| t.name == "two::twoklass" }.should be_instance_of(Puppet::Resource::Type)
end
diff --git a/spec/unit/indirector/rest_spec.rb b/spec/unit/indirector/rest_spec.rb
index 5c9065fec..7a97708d3 100755
--- a/spec/unit/indirector/rest_spec.rb
+++ b/spec/unit/indirector/rest_spec.rb
@@ -136,6 +136,12 @@ describe Puppet::Indirector::REST do
let(:indirection) { Puppet::TestModel.indirection }
let(:model) { Puppet::TestModel }
+ around(:each) do |example|
+ Puppet.override(:current_environment => Puppet::Node::Environment.create(:production, [])) do
+ example.run
+ end
+ end
+
def mock_response(code, body, content_type='text/plain', encoding=nil)
obj = stub('http 200 ok', :code => code.to_s, :body => body)
obj.stubs(:[]).with('content-type').returns(content_type)
@@ -285,17 +291,41 @@ describe Puppet::Indirector::REST do
end
context 'when fail_on_404 is used in request' do
- let(:request) { find_request('foo', :fail_on_404 => true) }
-
it 'raises an error for a 404 when asked to do so' do
+ request = find_request('foo', :fail_on_404 => true)
response = mock_response('404', 'this is the notfound you are looking for')
connection.expects(:get).returns(response)
- expected_message = [
- 'Find /production/test_model/foo?fail_on_404=true',
- 'resulted in 404 with the message: this is the notfound you are looking for'].join( ' ')
+
+ expect do
+ terminus.find(request)
+ end.to raise_error(
+ Puppet::Error,
+ 'Find /production/test_model/foo?fail_on_404=true resulted in 404 with the message: this is the notfound you are looking for')
+ end
+
+ it 'truncates the URI when it is very long' do
+ request = find_request('foo', :fail_on_404 => true, :long_param => ('A' * 100) + 'B')
+ response = mock_response('404', 'this is the notfound you are looking for')
+ connection.expects(:get).returns(response)
+
+ expect do
+ terminus.find(request)
+ end.to raise_error(
+ Puppet::Error,
+ /\/production\/test_model\/foo.*long_param=A+\.\.\..*resulted in 404 with the message/)
+ end
+
+ it 'does not truncate the URI when logging debug information' do
+ Puppet.debug = true
+ request = find_request('foo', :fail_on_404 => true, :long_param => ('A' * 100) + 'B')
+ response = mock_response('404', 'this is the notfound you are looking for')
+ connection.expects(:get).returns(response)
+
expect do
terminus.find(request)
- end.to raise_error(Puppet::Error, expected_message)
+ end.to raise_error(
+ Puppet::Error,
+ /\/production\/test_model\/foo.*long_param=A+B.*resulted in 404 with the message/)
end
end
diff --git a/spec/unit/interface/face_collection_spec.rb b/spec/unit/interface/face_collection_spec.rb
index 0562c933e..89928b70a 100755
--- a/spec/unit/interface/face_collection_spec.rb
+++ b/spec/unit/interface/face_collection_spec.rb
@@ -11,12 +11,12 @@ describe Puppet::Interface::FaceCollection do
# the 'subject' of the specs will differ.
before :all do
# Save FaceCollection's global state
- faces = subject.instance_variable_get(:@faces)
+ faces = described_class.instance_variable_get(:@faces)
@faces = faces.dup
faces.each do |k, v|
@faces[k] = v.dup
end
- @faces_loaded = subject.instance_variable_get(:@loaded)
+ @faces_loaded = described_class.instance_variable_get(:@loaded)
# Save the already required face files
@required = []
diff --git a/spec/unit/module_tool/applications/builder_spec.rb b/spec/unit/module_tool/applications/builder_spec.rb
index e2b63c192..291473db9 100644
--- a/spec/unit/module_tool/applications/builder_spec.rb
+++ b/spec/unit/module_tool/applications/builder_spec.rb
@@ -1,4 +1,5 @@
require 'spec_helper'
+require 'puppet/file_system'
require 'puppet/module_tool/applications'
require 'puppet_spec/modules'
@@ -12,6 +13,330 @@ describe Puppet::ModuleTool::Applications::Builder do
let(:tarball) { File.join(path, 'pkg', release_name) + ".tar.gz" }
let(:builder) { Puppet::ModuleTool::Applications::Builder.new(path) }
+ shared_examples "a packagable module" do
+ def target_exists?(file)
+ File.exist?(File.join(path, "pkg", "#{module_name}-#{version}", file))
+ end
+
+ def build
+ tarrer = mock('tarrer')
+ Puppet::ModuleTool::Tar.expects(:instance).returns(tarrer)
+ Dir.expects(:chdir).with(File.join(path, 'pkg')).yields
+ tarrer.expects(:pack).with(release_name, tarball)
+
+ builder.run
+ end
+
+ def create_regular_files
+ Puppet::FileSystem.touch(File.join(path, '.dotfile'))
+ Puppet::FileSystem.touch(File.join(path, 'file.foo'))
+ Puppet::FileSystem.touch(File.join(path, 'REVISION'))
+ Puppet::FileSystem.touch(File.join(path, '~file'))
+ Puppet::FileSystem.touch(File.join(path, '#file'))
+ Puppet::FileSystem.mkpath(File.join(path, 'pkg'))
+ Puppet::FileSystem.mkpath(File.join(path, 'coverage'))
+ Puppet::FileSystem.mkpath(File.join(path, 'sub'))
+ Puppet::FileSystem.touch(File.join(path, 'sub/.dotfile'))
+ Puppet::FileSystem.touch(File.join(path, 'sub/file.foo'))
+ Puppet::FileSystem.touch(File.join(path, 'sub/REVISION'))
+ Puppet::FileSystem.touch(File.join(path, 'sub/~file'))
+ Puppet::FileSystem.touch(File.join(path, 'sub/#file'))
+ Puppet::FileSystem.mkpath(File.join(path, 'sub/pkg'))
+ Puppet::FileSystem.mkpath(File.join(path, 'sub/coverage'))
+ end
+
+ def create_symlinks
+ Puppet::FileSystem.touch(File.join(path, 'symlinkedfile'))
+ Puppet::FileSystem.symlink(File.join(path, 'symlinkedfile'), File.join(path, 'symlinkfile'))
+ end
+
+ def create_ignored_files
+ Puppet::FileSystem.touch(File.join(path, 'gitignored.foo'))
+ Puppet::FileSystem.mkpath(File.join(path, 'gitdirectory/sub'))
+ Puppet::FileSystem.touch(File.join(path, 'gitdirectory/gitartifact'))
+ Puppet::FileSystem.touch(File.join(path, 'gitdirectory/gitimportantfile'))
+ Puppet::FileSystem.touch(File.join(path, 'gitdirectory/sub/artifact'))
+ Puppet::FileSystem.touch(File.join(path, 'pmtignored.foo'))
+ Puppet::FileSystem.mkpath(File.join(path, 'pmtdirectory/sub'))
+ Puppet::FileSystem.touch(File.join(path, 'pmtdirectory/pmtimportantfile'))
+ Puppet::FileSystem.touch(File.join(path, 'pmtdirectory/pmtartifact'))
+ Puppet::FileSystem.touch(File.join(path, 'pmtdirectory/sub/artifact'))
+ end
+
+ def create_pmtignore_file
+ Puppet::FileSystem.open(File.join(path, '.pmtignore'), 0600, 'w') do |f|
+ f << <<-PMTIGNORE
+pmtignored.*
+pmtdirectory/sub/**
+pmtdirectory/pmt*
+!pmtimportantfile
+PMTIGNORE
+ end
+ end
+
+ def create_gitignore_file
+ Puppet::FileSystem.open(File.join(path, '.gitignore'), 0600, 'w') do |f|
+ f << <<-GITIGNORE
+gitignored.*
+gitdirectory/sub/**
+gitdirectory/git*
+!gitimportantfile
+GITIGNORE
+ end
+ end
+
+ def create_symlink_gitignore_file
+ Puppet::FileSystem.open(File.join(path, '.gitignore'), 0600, 'w') do |f|
+ f << <<-GITIGNORE
+symlinkfile
+ GITIGNORE
+ end
+ end
+
+ shared_examples "regular files are present" do
+ it "has metadata" do
+ expect(target_exists?('metadata.json')).to eq true
+ end
+
+ it "has checksums" do
+ expect(target_exists?('checksums.json')).to eq true
+ end
+
+ it "copies regular files" do
+ expect(target_exists?('file.foo')).to eq true
+ end
+ end
+
+ shared_examples "default artifacts are removed in module dir but not in subdirs" do
+ it "ignores dotfiles" do
+ expect(target_exists?('.dotfile')).to eq false
+ expect(target_exists?('sub/.dotfile')).to eq true
+ end
+
+ it "does not have .gitignore" do
+ expect(target_exists?('.gitignore')).to eq false
+ end
+
+ it "does not have .pmtignore" do
+ expect(target_exists?('.pmtignore')).to eq false
+ end
+
+ it "does not have pkg" do
+ expect(target_exists?('pkg')).to eq false
+ expect(target_exists?('sub/pkg')).to eq true
+ end
+
+ it "does not have coverage" do
+ expect(target_exists?('coverage')).to eq false
+ expect(target_exists?('sub/coverage')).to eq true
+ end
+
+ it "does not have REVISION" do
+ expect(target_exists?('REVISION')).to eq false
+ expect(target_exists?('sub/REVISION')).to eq true
+ end
+
+ it "does not have ~files" do
+ expect(target_exists?('~file')).to eq false
+ expect(target_exists?('sub/~file')).to eq true
+ end
+
+ it "does not have #files" do
+ expect(target_exists?('#file')).to eq false
+ expect(target_exists?('sub/#file')).to eq true
+ end
+ end
+
+ shared_examples "gitignored files are present" do
+ it "leaves regular files" do
+ expect(target_exists?('gitignored.foo')).to eq true
+ end
+
+ it "leaves directories" do
+ expect(target_exists?('gitdirectory')).to eq true
+ end
+
+ it "leaves files in directories" do
+ expect(target_exists?('gitdirectory/gitartifact')).to eq true
+ end
+
+ it "leaves exceptional files" do
+ expect(target_exists?('gitdirectory/gitimportantfile')).to eq true
+ end
+
+ it "leaves subdirectories" do
+ expect(target_exists?('gitdirectory/sub')).to eq true
+ end
+
+ it "leaves files in subdirectories" do
+ expect(target_exists?('gitdirectory/sub/artifact')).to eq true
+ end
+ end
+
+ shared_examples "gitignored files are not present" do
+ it "ignores regular files" do
+ expect(target_exists?('gitignored.foo')).to eq false
+ end
+
+ it "ignores directories" do
+ expect(target_exists?('gitdirectory')).to eq true
+ end
+
+ it "ignores files in directories" do
+ expect(target_exists?('gitdirectory/gitartifact')).to eq false
+ end
+
+ it "copies exceptional files" do
+ expect(target_exists?('gitdirectory/gitimportantfile')).to eq true
+ end
+
+ it "ignores subdirectories" do
+ expect(target_exists?('gitdirectory/sub')).to eq false
+ end
+
+ it "ignores files in subdirectories" do
+ expect(target_exists?('gitdirectory/sub/artifact')).to eq false
+ end
+ end
+
+ shared_examples "pmtignored files are present" do
+ it "leaves regular files" do
+ expect(target_exists?('pmtignored.foo')).to eq true
+ end
+
+ it "leaves directories" do
+ expect(target_exists?('pmtdirectory')).to eq true
+ end
+
+ it "ignores files in directories" do
+ expect(target_exists?('pmtdirectory/pmtartifact')).to eq true
+ end
+
+ it "leaves exceptional files" do
+ expect(target_exists?('pmtdirectory/pmtimportantfile')).to eq true
+ end
+
+ it "leaves subdirectories" do
+ expect(target_exists?('pmtdirectory/sub')).to eq true
+ end
+
+ it "leaves files in subdirectories" do
+ expect(target_exists?('pmtdirectory/sub/artifact')).to eq true
+ end
+ end
+
+ shared_examples "pmtignored files are not present" do
+ it "ignores regular files" do
+ expect(target_exists?('pmtignored.foo')).to eq false
+ end
+
+ it "ignores directories" do
+ expect(target_exists?('pmtdirectory')).to eq true
+ end
+
+ it "copies exceptional files" do
+ expect(target_exists?('pmtdirectory/pmtimportantfile')).to eq true
+ end
+
+ it "ignores files in directories" do
+ expect(target_exists?('pmtdirectory/pmtartifact')).to eq false
+ end
+
+ it "ignores subdirectories" do
+ expect(target_exists?('pmtdirectory/sub')).to eq false
+ end
+
+ it "ignores files in subdirectories" do
+ expect(target_exists?('pmtdirectory/sub/artifact')).to eq false
+ end
+ end
+
+ context "with no ignore files" do
+ before :each do
+ create_regular_files
+ create_ignored_files
+
+ build
+ end
+
+ it_behaves_like "regular files are present"
+ it_behaves_like "default artifacts are removed in module dir but not in subdirs"
+ it_behaves_like "pmtignored files are present"
+ it_behaves_like "gitignored files are present"
+ end
+
+ context "with .gitignore file" do
+ before :each do
+ create_regular_files
+ create_ignored_files
+ create_gitignore_file
+
+ build
+ end
+
+ it_behaves_like "regular files are present"
+ it_behaves_like "default artifacts are removed in module dir but not in subdirs"
+ it_behaves_like "pmtignored files are present"
+ it_behaves_like "gitignored files are not present"
+ end
+
+ context "with .pmtignore file" do
+ before :each do
+ create_regular_files
+ create_ignored_files
+ create_pmtignore_file
+
+ build
+ end
+
+ it_behaves_like "regular files are present"
+ it_behaves_like "default artifacts are removed in module dir but not in subdirs"
+ it_behaves_like "gitignored files are present"
+ it_behaves_like "pmtignored files are not present"
+ end
+
+ context "with .pmtignore and .gitignore file" do
+ before :each do
+ create_regular_files
+ create_ignored_files
+ create_pmtignore_file
+ create_gitignore_file
+
+ build
+ end
+
+ it_behaves_like "regular files are present"
+ it_behaves_like "default artifacts are removed in module dir but not in subdirs"
+ it_behaves_like "gitignored files are present"
+ it_behaves_like "pmtignored files are not present"
+ end
+
+ context "with unignored symlinks", :if => Puppet.features.manages_symlinks? do
+ before :each do
+ create_regular_files
+ create_symlinks
+ create_ignored_files
+ end
+
+ it "give an error about symlinks" do
+ expect { builder.run }.to raise_error
+ end
+ end
+
+ context "with .gitignore file and ignored symlinks", :if => Puppet.features.manages_symlinks? do
+ before :each do
+ create_regular_files
+ create_symlinks
+ create_ignored_files
+ create_symlink_gitignore_file
+ end
+
+ it "does not give an error about symlinks" do
+ expect { build }.not_to raise_error
+ end
+ end
+ end
+
context 'with metadata.json' do
before :each do
File.open(File.join(path, 'metadata.json'), 'w') do |f|
@@ -28,16 +353,48 @@ describe Puppet::ModuleTool::Applications::Builder do
end
end
- it "packages the module in a tarball named after the module" do
- tarrer = mock('tarrer')
- Puppet::ModuleTool::Tar.expects(:instance).returns(tarrer)
- Dir.expects(:chdir).with(File.join(path, 'pkg')).yields
- tarrer.expects(:pack).with(release_name, tarball)
+ it_behaves_like "a packagable module"
- builder.run
+ it "does not package with a symlink", :if => Puppet.features.manages_symlinks? do
+ FileUtils.touch(File.join(path, 'tempfile'))
+ Puppet::FileSystem.symlink(File.join(path, 'tempfile'), File.join(path, 'tempfile2'))
+
+ expect {
+ builder.run
+ }.to raise_error Puppet::ModuleTool::Errors::ModuleToolError, /symlinks/i
+ end
+
+ it "does not package with a symlink in a subdir", :if => Puppet.features.manages_symlinks? do
+ FileUtils.mkdir(File.join(path, 'manifests'))
+ FileUtils.touch(File.join(path, 'manifests/tempfile.pp'))
+ Puppet::FileSystem.symlink(File.join(path, 'manifests/tempfile.pp'), File.join(path, 'manifests/tempfile2.pp'))
+
+ expect {
+ builder.run
+ }.to raise_error Puppet::ModuleTool::Errors::ModuleToolError, /symlinks/i
end
end
+ context 'with metadata.json containing checksums' do
+ before :each do
+ File.open(File.join(path, 'metadata.json'), 'w') do |f|
+ f.puts({
+ "name" => "#{module_name}",
+ "version" => "#{version}",
+ "source" => "http://github.com/testing/#{module_name}",
+ "author" => "testing",
+ "license" => "Apache License Version 2.0",
+ "summary" => "Puppet testing module",
+ "description" => "This module can be used for basic testing",
+ "project_page" => "http://github.com/testing/#{module_name}",
+ "checksums" => {"README.md" => "deadbeef"}
+ }.to_json)
+ end
+ end
+
+ it_behaves_like "a packagable module"
+ end
+
context 'with Modulefile' do
before :each do
File.open(File.join(path, 'Modulefile'), 'w') do |f|
@@ -54,13 +411,6 @@ MODULEFILE
end
end
- it "packages the module in a tarball named after the module" do
- tarrer = mock('tarrer')
- Puppet::ModuleTool::Tar.expects(:instance).returns(tarrer)
- Dir.expects(:chdir).with(File.join(path, 'pkg')).yields
- tarrer.expects(:pack).with(release_name, tarball)
-
- builder.run
- end
+ it_behaves_like "a packagable module"
end
end
diff --git a/spec/unit/module_tool/applications/uninstaller_spec.rb b/spec/unit/module_tool/applications/uninstaller_spec.rb
index 2a8562ab9..66e71b638 100644
--- a/spec/unit/module_tool/applications/uninstaller_spec.rb
+++ b/spec/unit/module_tool/applications/uninstaller_spec.rb
@@ -113,6 +113,28 @@ describe Puppet::ModuleTool::Applications::Uninstaller do
end
end
+ context 'with --ignore-changes' do
+ def options
+ super.merge(:ignore_changes => true)
+ end
+
+ context 'with local changes' do
+ before do
+ mark_changed(File.join(primary_dir, 'stdlib'))
+ end
+
+ it 'overwrites the installed module with the greatest version matching that range' do
+ subject.should include :result => :success
+ end
+ end
+
+ context 'without local changes' do
+ it 'overwrites the installed module with the greatest version matching that range' do
+ subject.should include :result => :success
+ end
+ end
+ end
+
context "when using the --force flag" do
def options
diff --git a/spec/unit/module_tool/applications/unpacker_spec.rb b/spec/unit/module_tool/applications/unpacker_spec.rb
index 39b0c261f..81557df99 100644
--- a/spec/unit/module_tool/applications/unpacker_spec.rb
+++ b/spec/unit/module_tool/applications/unpacker_spec.rb
@@ -2,6 +2,7 @@ require 'spec_helper'
require 'json'
require 'puppet/module_tool/applications'
+require 'puppet/file_system'
require 'puppet_spec/modules'
describe Puppet::ModuleTool::Applications::Unpacker do
@@ -31,4 +32,43 @@ describe Puppet::ModuleTool::Applications::Unpacker do
Puppet::ModuleTool::Applications::Unpacker.run(filename, :target_dir => target)
File.should be_directory(File.join(target, 'mytarball'))
end
+
+ it "should warn about symlinks", :if => Puppet.features.manages_symlinks? do
+ untar = mock('Tar')
+ untar.expects(:unpack).with(filename, anything()) do |src, dest, _|
+ FileUtils.mkdir(File.join(dest, 'extractedmodule'))
+ File.open(File.join(dest, 'extractedmodule', 'metadata.json'), 'w+') do |file|
+ file.puts JSON.generate('name' => module_name, 'version' => '1.0.0')
+ end
+ FileUtils.touch(File.join(dest, 'extractedmodule/tempfile'))
+ Puppet::FileSystem.symlink(File.join(dest, 'extractedmodule/tempfile'), File.join(dest, 'extractedmodule/tempfile2'))
+ true
+ end
+
+ Puppet::ModuleTool::Tar.expects(:instance).returns(untar)
+ Puppet.expects(:warning).with(regexp_matches(/symlinks/i))
+
+ Puppet::ModuleTool::Applications::Unpacker.run(filename, :target_dir => target)
+ File.should be_directory(File.join(target, 'mytarball'))
+ end
+
+ it "should warn about symlinks in subdirectories", :if => Puppet.features.manages_symlinks? do
+ untar = mock('Tar')
+ untar.expects(:unpack).with(filename, anything()) do |src, dest, _|
+ FileUtils.mkdir(File.join(dest, 'extractedmodule'))
+ File.open(File.join(dest, 'extractedmodule', 'metadata.json'), 'w+') do |file|
+ file.puts JSON.generate('name' => module_name, 'version' => '1.0.0')
+ end
+ FileUtils.mkdir(File.join(dest, 'extractedmodule/manifests'))
+ FileUtils.touch(File.join(dest, 'extractedmodule/manifests/tempfile'))
+ Puppet::FileSystem.symlink(File.join(dest, 'extractedmodule/manifests/tempfile'), File.join(dest, 'extractedmodule/manifests/tempfile2'))
+ true
+ end
+
+ Puppet::ModuleTool::Tar.expects(:instance).returns(untar)
+ Puppet.expects(:warning).with(regexp_matches(/symlinks/i))
+
+ Puppet::ModuleTool::Applications::Unpacker.run(filename, :target_dir => target)
+ File.should be_directory(File.join(target, 'mytarball'))
+ end
end
diff --git a/spec/unit/module_tool/applications/upgrader_spec.rb b/spec/unit/module_tool/applications/upgrader_spec.rb
index 44627f94f..382e45a75 100644
--- a/spec/unit/module_tool/applications/upgrader_spec.rb
+++ b/spec/unit/module_tool/applications/upgrader_spec.rb
@@ -52,6 +52,16 @@ describe Puppet::ModuleTool::Applications::Upgrader do
end
context 'for an installed module' do
+ context 'with only one version' do
+ before { preinstall('puppetlabs-oneversion', '0.0.1') }
+ let(:module) { 'puppetlabs-oneversion' }
+
+ it 'declines to upgrade' do
+ subject.should include :result => :noop
+ subject[:error][:multiline].should =~ /already the latest version/
+ end
+ end
+
context 'without dependencies' do
before { preinstall('pmtacceptance-stdlib', '1.0.0') }
@@ -90,6 +100,7 @@ describe Puppet::ModuleTool::Applications::Upgrader do
context 'without options' do
it 'declines to upgrade' do
subject.should include :result => :noop
+ subject[:error][:multiline].should =~ /already the latest version/
end
end
@@ -165,6 +176,17 @@ describe Puppet::ModuleTool::Applications::Upgrader do
subject.should include :result => :failure
subject[:error].should include :oneline => "Could not upgrade '#{self.module}'; module has had changes made locally"
end
+
+ context 'with --ignore-changes' do
+ def options
+ super.merge(:ignore_changes => true)
+ end
+
+ it 'overwrites the installed module with the greatest version matching that range' do
+ subject.should include :result => :success
+ graph_should_include 'pmtacceptance-stdlib', v('1.0.0') => v('4.1.0')
+ end
+ end
end
context 'with dependencies' do
diff --git a/spec/unit/module_tool/installed_modules_spec.rb b/spec/unit/module_tool/installed_modules_spec.rb
new file mode 100644
index 000000000..b9492d986
--- /dev/null
+++ b/spec/unit/module_tool/installed_modules_spec.rb
@@ -0,0 +1,49 @@
+require 'spec_helper'
+require 'puppet/module_tool/installed_modules'
+require 'puppet_spec/modules'
+
+describe Puppet::ModuleTool::InstalledModules do
+ include PuppetSpec::Files
+
+ around do |example|
+ dir = tmpdir("deep_path")
+
+ FileUtils.mkdir_p(@modpath = File.join(dir, "modpath"))
+
+ @env = Puppet::Node::Environment.create(:env, [@modpath])
+ Puppet.override(:current_environment => @env) do
+ example.run
+ end
+ end
+
+ it 'works when given a semantic version' do
+ mod = PuppetSpec::Modules.create('goodsemver', @modpath, :metadata => {:version => '1.2.3'})
+ installed = described_class.new(@env)
+ expect(installed.modules["puppetlabs-#{mod.name}"].version).to eq(Semantic::Version.parse('1.2.3'))
+ end
+
+ it 'defaults when not given a semantic version' do
+ mod = PuppetSpec::Modules.create('badsemver', @modpath, :metadata => {:version => 'banana'})
+ Puppet.expects(:warning).with(regexp_matches(/Semantic Version/))
+ installed = described_class.new(@env)
+ expect(installed.modules["puppetlabs-#{mod.name}"].version).to eq(Semantic::Version.parse('0.0.0'))
+ end
+
+ it 'defaults when not given a full semantic version' do
+ mod = PuppetSpec::Modules.create('badsemver', @modpath, :metadata => {:version => '1.2'})
+ Puppet.expects(:warning).with(regexp_matches(/Semantic Version/))
+ installed = described_class.new(@env)
+ expect(installed.modules["puppetlabs-#{mod.name}"].version).to eq(Semantic::Version.parse('0.0.0'))
+ end
+
+ it 'still works if there is an invalid version in one of the modules' do
+ mod1 = PuppetSpec::Modules.create('badsemver', @modpath, :metadata => {:version => 'banana'})
+ mod2 = PuppetSpec::Modules.create('goodsemver', @modpath, :metadata => {:version => '1.2.3'})
+ mod3 = PuppetSpec::Modules.create('notquitesemver', @modpath, :metadata => {:version => '1.2'})
+ Puppet.expects(:warning).with(regexp_matches(/Semantic Version/)).twice
+ installed = described_class.new(@env)
+ expect(installed.modules["puppetlabs-#{mod1.name}"].version).to eq(Semantic::Version.parse('0.0.0'))
+ expect(installed.modules["puppetlabs-#{mod2.name}"].version).to eq(Semantic::Version.parse('1.2.3'))
+ expect(installed.modules["puppetlabs-#{mod3.name}"].version).to eq(Semantic::Version.parse('0.0.0'))
+ end
+end
diff --git a/spec/unit/module_tool/metadata_spec.rb b/spec/unit/module_tool/metadata_spec.rb
index 3b925c644..fce9c8f8d 100644
--- a/spec/unit/module_tool/metadata_spec.rb
+++ b/spec/unit/module_tool/metadata_spec.rb
@@ -5,6 +5,19 @@ describe Puppet::ModuleTool::Metadata do
let(:data) { {} }
let(:metadata) { Puppet::ModuleTool::Metadata.new }
+ describe 'property lookups' do
+ subject { metadata }
+
+ %w[ name version author summary license source project_page issues_url
+ dependencies dashed_name release_name description ].each do |prop|
+ describe "##{prop}" do
+ it "responds to the property" do
+ subject.send(prop)
+ end
+ end
+ end
+ end
+
describe "#update" do
subject { metadata.update(data) }
@@ -156,6 +169,61 @@ describe Puppet::ModuleTool::Metadata do
end
+ context "with a valid dependency" do
+ let(:data) { {'dependencies' => [{'name' => 'puppetlabs-goodmodule'}] }}
+
+ it "adds the dependency" do
+ subject.dependencies.size.should == 1
+ end
+ end
+
+ context "with a invalid dependency name" do
+ let(:data) { {'dependencies' => [{'name' => 'puppetlabsbadmodule'}] }}
+
+ it "raises an exception" do
+ expect { subject }.to raise_error(ArgumentError)
+ end
+ end
+
+ context "with a valid dependency version range" do
+ let(:data) { {'dependencies' => [{'name' => 'puppetlabs-badmodule', 'version_requirement' => '>= 2.0.0'}] }}
+
+ it "adds the dependency" do
+ subject.dependencies.size.should == 1
+ end
+ end
+
+ context "with a invalid version range" do
+ let(:data) { {'dependencies' => [{'name' => 'puppetlabsbadmodule', 'version_requirement' => '>= banana'}] }}
+
+ it "raises an exception" do
+ expect { subject }.to raise_error(ArgumentError)
+ end
+ end
+
+ context "with duplicate dependencies" do
+ let(:data) { {'dependencies' => [{'name' => 'puppetlabs-dupmodule', 'version_requirement' => '1.0.0'},
+ {'name' => 'puppetlabs-dupmodule', 'version_requirement' => '0.0.1'}] }
+ }
+
+ it "raises an exception" do
+ expect { subject }.to raise_error(ArgumentError)
+ end
+ end
+
+ context "adding a duplicate dependency" do
+ let(:data) { {'dependencies' => [{'name' => 'puppetlabs-origmodule', 'version_requirement' => '1.0.0'}] }}
+
+ it "with a different version raises an exception" do
+ metadata.add_dependency('puppetlabs-origmodule', '>= 0.0.1')
+ expect { subject }.to raise_error(ArgumentError)
+ end
+
+ it "with the same version does not add another dependency" do
+ metadata.add_dependency('puppetlabs-origmodule', '1.0.0')
+ subject.dependencies.size.should == 1
+ end
+ end
end
describe '#dashed_name' do
@@ -202,8 +270,8 @@ describe Puppet::ModuleTool::Metadata do
describe "#to_hash" do
subject { metadata.to_hash }
- its(:keys) do
- subject.sort.should == %w[ name version author summary license source issues_url project_page dependencies ].sort
+ it "contains the default set of keys" do
+ subject.keys.sort.should == %w[ name version author summary license source issues_url project_page dependencies ].sort
end
describe "['license']" do
@@ -213,8 +281,8 @@ describe Puppet::ModuleTool::Metadata do
end
describe "['dependencies']" do
- it "defaults to an empty Array" do
- subject['dependencies'].should == []
+ it "defaults to an empty set" do
+ subject['dependencies'].should == Set.new
end
end
diff --git a/spec/unit/module_tool/tar/mini_spec.rb b/spec/unit/module_tool/tar/mini_spec.rb
index ef030611b..179952741 100644
--- a/spec/unit/module_tool/tar/mini_spec.rb
+++ b/spec/unit/module_tool/tar/mini_spec.rb
@@ -54,6 +54,7 @@ describe Puppet::ModuleTool::Tar::Mini, :if => (Puppet.features.minitar? and Pup
reader = mock('GzipReader')
Zlib::GzipReader.expects(:open).with(sourcefile).yields(reader)
- Archive::Tar::Minitar.expects(:unpack).with(reader, destdir).yields(type, name, nil)
+ minitar.expects(:find_valid_files).with(reader).returns([name])
+ Archive::Tar::Minitar.expects(:unpack).with(reader, destdir, [name]).yields(type, name, nil)
end
end
diff --git a/spec/unit/network/authentication_spec.rb b/spec/unit/network/authentication_spec.rb
index 8f3653cad..5e2f2de87 100755
--- a/spec/unit/network/authentication_spec.rb
+++ b/spec/unit/network/authentication_spec.rb
@@ -81,6 +81,10 @@ describe Puppet::Network::Authentication do
cert.stubs(:unmunged_name).returns('foo')
end
+ after(:all) do
+ reload_module
+ end
+
it "should log a warning if a certificate's expiration is near" do
logger.expects(:warning)
subject.warn_if_near_expiration(cert)
diff --git a/spec/unit/network/http/api/v2/environments_spec.rb b/spec/unit/network/http/api/v2/environments_spec.rb
index 6c6d7a581..993e55011 100644
--- a/spec/unit/network/http/api/v2/environments_spec.rb
+++ b/spec/unit/network/http/api/v2/environments_spec.rb
@@ -23,20 +23,41 @@ describe Puppet::Network::HTTP::API::V2::Environments do
"production" => {
"settings" => {
"modulepath" => [File.expand_path("/first"), File.expand_path("/second")],
- "manifest" => File.expand_path("/manifests")
+ "manifest" => File.expand_path("/manifests"),
+ "environment_timeout" => 0,
+ "config_version" => ""
}
}
}
})
end
- it "the response conforms to the environments schema" do
+ it "the response conforms to the environments schema for unlimited timeout" do
+ conf_stub = stub 'conf_stub'
+ conf_stub.expects(:environment_timeout).returns(1.0 / 0.0)
environment = Puppet::Node::Environment.create(:production, [])
- handler = Puppet::Network::HTTP::API::V2::Environments.new(Puppet::Environments::Static.new(environment))
+ env_loader = Puppet::Environments::Static.new(environment)
+ env_loader.expects(:get_conf).with(:production).returns(conf_stub)
+ handler = Puppet::Network::HTTP::API::V2::Environments.new(env_loader)
response = Puppet::Network::HTTP::MemoryResponse.new
handler.call(Puppet::Network::HTTP::Request.from_hash(:headers => { 'accept' => 'application/json' }), response)
expect(response.body).to validate_against('api/schemas/environments.json')
end
+
+ it "the response conforms to the environments schema for integer timeout" do
+ conf_stub = stub 'conf_stub'
+ conf_stub.expects(:environment_timeout).returns(1)
+ environment = Puppet::Node::Environment.create(:production, [])
+ env_loader = Puppet::Environments::Static.new(environment)
+ env_loader.expects(:get_conf).with(:production).returns(conf_stub)
+ handler = Puppet::Network::HTTP::API::V2::Environments.new(env_loader)
+ response = Puppet::Network::HTTP::MemoryResponse.new
+
+ handler.call(Puppet::Network::HTTP::Request.from_hash(:headers => { 'accept' => 'application/json' }), response)
+
+ expect(response.body).to validate_against('api/schemas/environments.json')
+ end
+
end
diff --git a/spec/unit/network/http/connection_spec.rb b/spec/unit/network/http/connection_spec.rb
index a5e6f64ae..ef6ca65d6 100644..100755
--- a/spec/unit/network/http/connection_spec.rb
+++ b/spec/unit/network/http/connection_spec.rb
@@ -11,50 +11,30 @@ describe Puppet::Network::HTTP::Connection do
let (:httpok) { Net::HTTPOK.new('1.1', 200, '') }
context "when providing HTTP connections" do
- after do
- Puppet::Network::HTTP::Connection.instance_variable_set("@ssl_host", nil)
- end
-
context "when initializing http instances" do
- before :each do
- # All of the cert stuff is tested elsewhere
- Puppet::Network::HTTP::Connection.stubs(:cert_setup)
- end
-
it "should return an http instance created with the passed host and port" do
- http = subject.send(:connection)
- http.should be_an_instance_of Net::HTTP
- http.address.should == host
- http.port.should == port
+ conn = Puppet::Network::HTTP::Connection.new(host, port, :verify => Puppet::SSL::Validator.no_validator)
+
+ expect(conn.address).to eq(host)
+ expect(conn.port).to eq(port)
end
it "should enable ssl on the http instance by default" do
- http = subject.send(:connection)
- http.should be_use_ssl
- end
+ conn = Puppet::Network::HTTP::Connection.new(host, port, :verify => Puppet::SSL::Validator.no_validator)
- it "can set ssl using an option" do
- Puppet::Network::HTTP::Connection.new(host, port, :use_ssl => false, :verify => Puppet::SSL::Validator.no_validator).send(:connection).should_not be_use_ssl
- Puppet::Network::HTTP::Connection.new(host, port, :use_ssl => true, :verify => Puppet::SSL::Validator.no_validator).send(:connection).should be_use_ssl
+ expect(conn).to be_use_ssl
end
- context "proxy and timeout settings should propagate" do
- subject { Puppet::Network::HTTP::Connection.new(host, port, :verify => Puppet::SSL::Validator.no_validator).send(:connection) }
- before :each do
- Puppet[:http_proxy_host] = "myhost"
- Puppet[:http_proxy_port] = 432
- Puppet[:configtimeout] = 120
- end
+ it "can disable ssl using an option" do
+ conn = Puppet::Network::HTTP::Connection.new(host, port, :use_ssl => false, :verify => Puppet::SSL::Validator.no_validator)
- its(:open_timeout) { should == Puppet[:configtimeout] }
- its(:read_timeout) { should == Puppet[:configtimeout] }
- its(:proxy_address) { should == Puppet[:http_proxy_host] }
- its(:proxy_port) { should == Puppet[:http_proxy_port] }
+ expect(conn).to_not be_use_ssl
end
- it "should not set a proxy if the value is 'none'" do
- Puppet[:http_proxy_host] = 'none'
- subject.send(:connection).proxy_address.should be_nil
+ it "can enable ssl using an option" do
+ conn = Puppet::Network::HTTP::Connection.new(host, port, :use_ssl => true, :verify => Puppet::SSL::Validator.no_validator)
+
+ expect(conn).to be_use_ssl
end
it "should raise Puppet::Error when invalid options are specified" do
@@ -96,7 +76,44 @@ describe Puppet::Network::HTTP::Connection do
end
end
- context "when validating HTTPS requests" do
+ class ConstantErrorValidator
+ def initialize(args)
+ @fails_with = args[:fails_with]
+ @error_string = args[:error_string] || ""
+ @peer_certs = args[:peer_certs] || []
+ end
+
+ def setup_connection(connection)
+ connection.stubs(:start).raises(OpenSSL::SSL::SSLError.new(@fails_with))
+ end
+
+ def peer_certs
+ @peer_certs
+ end
+
+ def verify_errors
+ [@error_string]
+ end
+ end
+
+ class NoProblemsValidator
+ def initialize(cert)
+ @cert = cert
+ end
+
+ def setup_connection(connection)
+ end
+
+ def peer_certs
+ [@cert]
+ end
+
+ def verify_errors
+ []
+ end
+ end
+
+ shared_examples_for 'ssl verifier' do
include PuppetSpec::Files
let (:host) { "my_server" }
@@ -117,11 +134,11 @@ describe Puppet::Network::HTTP::Connection do
Puppet[:confdir] = tmpdir('conf')
connection = Puppet::Network::HTTP::Connection.new(
- host, port,
- :verify => ConstantErrorValidator.new(
- :fails_with => 'hostname was not match with server certificate',
- :peer_certs => [Puppet::SSL::CertificateAuthority.new.generate(
- 'not_my_server', :dns_alt_names => 'foo,bar,baz')]))
+ host, port,
+ :verify => ConstantErrorValidator.new(
+ :fails_with => 'hostname was not match with server certificate',
+ :peer_certs => [Puppet::SSL::CertificateAuthority.new.generate(
+ 'not_my_server', :dns_alt_names => 'foo,bar,baz')]))
expect do
connection.get('request')
@@ -150,86 +167,104 @@ describe Puppet::Network::HTTP::Connection do
host, port,
:verify => NoProblemsValidator.new(cert))
+ Net::HTTP.any_instance.stubs(:start)
Net::HTTP.any_instance.stubs(:request).returns(httpok)
connection.expects(:warn_if_near_expiration).with(cert)
connection.get('request')
end
+ end
- class ConstantErrorValidator
- def initialize(args)
- @fails_with = args[:fails_with]
- @error_string = args[:error_string] || ""
- @peer_certs = args[:peer_certs] || []
- end
+ context "when using single use HTTPS connections" do
+ it_behaves_like 'ssl verifier' do
+ end
+ end
- def setup_connection(connection)
- connection.stubs(:request).with do
- true
- end.raises(OpenSSL::SSL::SSLError.new(@fails_with))
+ context "when using persistent HTTPS connections" do
+ around :each do |example|
+ pool = Puppet::Network::HTTP::Pool.new
+ Puppet.override(:http_pool => pool) do
+ example.run
end
+ pool.close
+ end
- def peer_certs
- @peer_certs
- end
+ it_behaves_like 'ssl verifier' do
+ end
+ end
- def verify_errors
- [@error_string]
- end
+ context "when response is a redirect" do
+ let (:site) { Puppet::Network::HTTP::Site.new('http', 'my_server', 8140) }
+ let (:other_site) { Puppet::Network::HTTP::Site.new('http', 'redirected', 9292) }
+ let (:other_path) { "other-path" }
+ let (:verify) { Puppet::SSL::Validator.no_validator }
+ let (:subject) { Puppet::Network::HTTP::Connection.new(site.host, site.port, :use_ssl => false, :verify => verify) }
+ let (:httpredirection) do
+ response = Net::HTTPFound.new('1.1', 302, 'Moved Temporarily')
+ response['location'] = "#{other_site.addr}/#{other_path}"
+ response.stubs(:read_body).returns("This resource has moved")
+ response
end
- class NoProblemsValidator
- def initialize(cert)
- @cert = cert
- end
+ def create_connection(site, options)
+ options[:use_ssl] = site.use_ssl?
+ Puppet::Network::HTTP::Connection.new(site.host, site.port, options)
+ end
- def setup_connection(connection)
- end
+ it "should redirect to the final resource location" do
+ http = stub('http')
+ http.stubs(:request).returns(httpredirection).then.returns(httpok)
- def peer_certs
- [@cert]
- end
+ seq = sequence('redirection')
+ pool = Puppet.lookup(:http_pool)
+ pool.expects(:with_connection).with(site, anything).yields(http).in_sequence(seq)
+ pool.expects(:with_connection).with(other_site, anything).yields(http).in_sequence(seq)
- def verify_errors
- []
- end
+ conn = create_connection(site, :verify => verify)
+ conn.get('/foo')
end
- end
- context "when response is a redirect" do
- let (:other_host) { "redirected" }
- let (:other_port) { 9292 }
- let (:other_path) { "other-path" }
- let (:subject) { Puppet::Network::HTTP::Connection.new("my_server", 8140, :use_ssl => false, :verify => Puppet::SSL::Validator.no_validator) }
- let (:httpredirection) { Net::HTTPFound.new('1.1', 302, 'Moved Temporarily') }
+ def expects_redirection(conn, &block)
+ http = stub('http')
+ http.stubs(:request).returns(httpredirection)
- before :each do
- httpredirection['location'] = "http://#{other_host}:#{other_port}/#{other_path}"
- httpredirection.stubs(:read_body).returns("This resource has moved")
+ pool = Puppet.lookup(:http_pool)
+ pool.expects(:with_connection).with(site, anything).yields(http)
+ pool
+ end
- socket = stub_everything("socket")
- TCPSocket.stubs(:open).returns(socket)
+ def expects_limit_exceeded(conn)
+ expect {
+ conn.get('/')
+ }.to raise_error(Puppet::Network::HTTP::RedirectionLimitExceededException)
+ end
- Net::HTTP::Get.any_instance.stubs(:exec).returns("")
- Net::HTTP::Post.any_instance.stubs(:exec).returns("")
+ it "should not redirect when the limit is 0" do
+ conn = create_connection(site, :verify => verify, :redirect_limit => 0)
+
+ pool = expects_redirection(conn)
+ pool.expects(:with_connection).with(other_site, anything).never
+
+ expects_limit_exceeded(conn)
end
- it "should redirect to the final resource location" do
- httpok.stubs(:read_body).returns(:body)
- Net::HTTPResponse.stubs(:read_new).returns(httpredirection).then.returns(httpok)
+ it "should redirect only once" do
+ conn = create_connection(site, :verify => verify, :redirect_limit => 1)
+
+ pool = expects_redirection(conn)
+ pool.expects(:with_connection).with(other_site, anything).once
- subject.get("/foo").body.should == :body
- subject.port.should == other_port
- subject.address.should == other_host
+ expects_limit_exceeded(conn)
end
- it "should raise an error after too many redirections" do
- Net::HTTPResponse.stubs(:read_new).returns(httpredirection)
+ it "should raise an exception when the redirect limit is exceeded" do
+ conn = create_connection(site, :verify => verify, :redirect_limit => 3)
- expect {
- subject.get("/foo")
- }.to raise_error(Puppet::Network::HTTP::RedirectionLimitExceededException)
+ pool = expects_redirection(conn)
+ pool.expects(:with_connection).with(other_site, anything).times(3)
+
+ expects_limit_exceeded(conn)
end
end
diff --git a/spec/unit/network/http/factory_spec.rb b/spec/unit/network/http/factory_spec.rb
new file mode 100755
index 000000000..107ededcd
--- /dev/null
+++ b/spec/unit/network/http/factory_spec.rb
@@ -0,0 +1,82 @@
+#! /usr/bin/env ruby
+require 'spec_helper'
+require 'puppet/network/http'
+
+describe Puppet::Network::HTTP::Factory do
+ before :each do
+ Puppet::SSL::Key.indirection.terminus_class = :memory
+ Puppet::SSL::CertificateRequest.indirection.terminus_class = :memory
+ end
+
+ let(:site) { Puppet::Network::HTTP::Site.new('https', 'www.example.com', 443) }
+ def create_connection(site)
+ factory = Puppet::Network::HTTP::Factory.new
+
+ factory.create_connection(site)
+ end
+
+ it 'creates a connection for the site' do
+ conn = create_connection(site)
+
+ expect(conn.use_ssl?).to be_true
+ expect(conn.address).to eq(site.host)
+ expect(conn.port).to eq(site.port)
+ end
+
+ it 'creates a connection that has not yet been started' do
+ conn = create_connection(site)
+
+ expect(conn).to_not be_started
+ end
+
+ it 'creates a connection supporting at least HTTP 1.1' do
+ conn = create_connection(site)
+
+ expect(any_of(conn.class.version_1_1?, conn.class.version_1_1?)).to be_true
+ end
+
+ context "proxy settings" do
+ let(:proxy_host) { 'myhost' }
+ let(:proxy_port) { 432 }
+
+ it "should not set a proxy if the value is 'none'" do
+ Puppet[:http_proxy_host] = 'none'
+ conn = create_connection(site)
+
+ expect(conn.proxy_address).to be_nil
+ end
+
+ it 'sets proxy_address' do
+ Puppet[:http_proxy_host] = proxy_host
+ conn = create_connection(site)
+
+ expect(conn.proxy_address).to eq(proxy_host)
+ end
+
+ it 'sets proxy address and port' do
+ Puppet[:http_proxy_host] = proxy_host
+ Puppet[:http_proxy_port] = proxy_port
+ conn = create_connection(site)
+
+ expect(conn.proxy_port).to eq(proxy_port)
+ end
+
+ context 'socket timeouts' do
+ let(:timeout) { 5 }
+
+ it 'sets open timeout' do
+ Puppet[:configtimeout] = timeout
+ conn = create_connection(site)
+
+ expect(conn.open_timeout).to eq(timeout)
+ end
+
+ it 'sets read timeout' do
+ Puppet[:configtimeout] = timeout
+ conn = create_connection(site)
+
+ expect(conn.read_timeout).to eq(timeout)
+ end
+ end
+ end
+end
diff --git a/spec/unit/network/http/handler_spec.rb b/spec/unit/network/http/handler_spec.rb
index 345818b4a..25df9d270 100755
--- a/spec/unit/network/http/handler_spec.rb
+++ b/spec/unit/network/http/handler_spec.rb
@@ -103,31 +103,30 @@ describe Puppet::Network::HTTP::Handler do
handler.stubs(:warn_if_near_expiration)
end
- it "should check the client certificate for upcoming expiration" do
- request = a_request
- cert = mock 'cert'
- handler.expects(:client_cert).returns(cert).with(request)
- handler.expects(:warn_if_near_expiration).with(cert)
-
- handler.process(request, response)
- end
-
it "should setup a profiler when the puppet-profiling header exists" do
request = a_request
request[:headers][Puppet::Network::HTTP::HEADER_ENABLE_PROFILING.downcase] = "true"
- handler.process(request, response)
+ p = HandlerTestProfiler.new
+
+ Puppet::Util::Profiler.expects(:add_profiler).with { |profiler|
+ profiler.is_a? Puppet::Util::Profiler::WallClock
+ }.returns(p)
- Puppet::Util::Profiler.current.should be_kind_of(Puppet::Util::Profiler::WallClock)
+ Puppet::Util::Profiler.expects(:remove_profiler).with { |profiler|
+ profiler == p
+ }
+
+ handler.process(request, response)
end
it "should not setup profiler when the profile parameter is missing" do
request = a_request
request[:params] = { }
- handler.process(request, response)
+ Puppet::Util::Profiler.expects(:add_profiler).never
- Puppet::Util::Profiler.current.should == Puppet::Util::Profiler::NONE
+ handler.process(request, response)
end
it "should raise an error if the request is formatted in an unknown format" do
@@ -219,4 +218,15 @@ describe Puppet::Network::HTTP::Handler do
request[:headers] || {}
end
end
+
+ class HandlerTestProfiler
+ def start(metric, description)
+ end
+
+ def finish(context, metric, description)
+ end
+
+ def shutdown()
+ end
+ end
end
diff --git a/spec/unit/network/http/nocache_pool_spec.rb b/spec/unit/network/http/nocache_pool_spec.rb
new file mode 100755
index 000000000..69e2d2e9a
--- /dev/null
+++ b/spec/unit/network/http/nocache_pool_spec.rb
@@ -0,0 +1,43 @@
+#! /usr/bin/env ruby
+require 'spec_helper'
+
+require 'puppet/network/http'
+require 'puppet/network/http/connection'
+
+describe Puppet::Network::HTTP::NoCachePool do
+ let(:site) { Puppet::Network::HTTP::Site.new('https', 'rubygems.org', 443) }
+ let(:verify) { stub('verify', :setup_connection => nil) }
+
+ it 'yields a connection' do
+ http = stub('http')
+
+ factory = Puppet::Network::HTTP::Factory.new
+ factory.stubs(:create_connection).returns(http)
+ pool = Puppet::Network::HTTP::NoCachePool.new(factory)
+
+ expect { |b|
+ pool.with_connection(site, verify, &b)
+ }.to yield_with_args(http)
+ end
+
+ it 'yields a new connection each time' do
+ http1 = stub('http1')
+ http2 = stub('http2')
+
+ factory = Puppet::Network::HTTP::Factory.new
+ factory.stubs(:create_connection).returns(http1).then.returns(http2)
+ pool = Puppet::Network::HTTP::NoCachePool.new(factory)
+
+ expect { |b|
+ pool.with_connection(site, verify, &b)
+ }.to yield_with_args(http1)
+
+ expect { |b|
+ pool.with_connection(site, verify, &b)
+ }.to yield_with_args(http2)
+ end
+
+ it 'has a close method' do
+ Puppet::Network::HTTP::NoCachePool.new.close
+ end
+end
diff --git a/spec/unit/network/http/pool_spec.rb b/spec/unit/network/http/pool_spec.rb
new file mode 100755
index 000000000..aef100953
--- /dev/null
+++ b/spec/unit/network/http/pool_spec.rb
@@ -0,0 +1,269 @@
+#! /usr/bin/env ruby
+require 'spec_helper'
+
+require 'openssl'
+require 'puppet/network/http'
+require 'puppet/network/http_pool'
+
+describe Puppet::Network::HTTP::Pool do
+ before :each do
+ Puppet::SSL::Key.indirection.terminus_class = :memory
+ Puppet::SSL::CertificateRequest.indirection.terminus_class = :memory
+ end
+
+ let(:site) do
+ Puppet::Network::HTTP::Site.new('https', 'rubygems.org', 443)
+ end
+
+ let(:different_site) do
+ Puppet::Network::HTTP::Site.new('https', 'github.com', 443)
+ end
+
+ let(:verify) do
+ stub('verify', :setup_connection => nil)
+ end
+
+ def create_pool
+ Puppet::Network::HTTP::Pool.new
+ end
+
+ def create_pool_with_connections(site, *connections)
+ pool = Puppet::Network::HTTP::Pool.new
+ connections.each do |conn|
+ pool.release(site, conn)
+ end
+ pool
+ end
+
+ def create_pool_with_expired_connections(site, *connections)
+ # setting keepalive timeout to -1 ensures any newly added
+ # connections have already expired
+ pool = Puppet::Network::HTTP::Pool.new(-1)
+ connections.each do |conn|
+ pool.release(site, conn)
+ end
+ pool
+ end
+
+ def create_connection(site)
+ stub(site.addr, :started? => false, :start => nil, :finish => nil, :use_ssl? => true, :verify_mode => OpenSSL::SSL::VERIFY_PEER)
+ end
+
+ context 'when yielding a connection' do
+ it 'yields a connection' do
+ conn = create_connection(site)
+ pool = create_pool_with_connections(site, conn)
+
+ expect { |b|
+ pool.with_connection(site, verify, &b)
+ }.to yield_with_args(conn)
+ end
+
+ it 'returns the connection to the pool' do
+ conn = create_connection(site)
+ pool = create_pool
+ pool.release(site, conn)
+
+ pool.with_connection(site, verify) { |c| }
+
+ expect(pool.pool[site].first.connection).to eq(conn)
+ end
+
+ it 'can yield multiple connections to the same site' do
+ lru_conn = create_connection(site)
+ mru_conn = create_connection(site)
+ pool = create_pool_with_connections(site, lru_conn, mru_conn)
+
+ pool.with_connection(site, verify) do |a|
+ expect(a).to eq(mru_conn)
+
+ pool.with_connection(site, verify) do |b|
+ expect(b).to eq(lru_conn)
+ end
+ end
+ end
+
+ it 'propagates exceptions' do
+ conn = create_connection(site)
+ pool = create_pool
+ pool.release(site, conn)
+
+ expect {
+ pool.with_connection(site, verify) do |c|
+ raise IOError, 'connection reset'
+ end
+ }.to raise_error(IOError, 'connection reset')
+ end
+
+ it 'does not re-cache connections when an error occurs' do
+ # we're not distinguishing between network errors that would
+ # suggest we close the socket, and other errors
+ conn = create_connection(site)
+ pool = create_pool
+ pool.release(site, conn)
+
+ pool.expects(:release).with(site, conn).never
+
+ pool.with_connection(site, verify) do |c|
+ raise IOError, 'connection reset'
+ end rescue nil
+ end
+
+ context 'when releasing connections' do
+ it 'releases HTTP connections' do
+ conn = create_connection(site)
+ conn.expects(:use_ssl?).returns(false)
+
+ pool = create_pool_with_connections(site, conn)
+ pool.expects(:release).with(site, conn)
+
+ pool.with_connection(site, verify) {|c| }
+ end
+
+ it 'releases secure HTTPS connections' do
+ conn = create_connection(site)
+ conn.expects(:use_ssl?).returns(true)
+ conn.expects(:verify_mode).returns(OpenSSL::SSL::VERIFY_PEER)
+
+ pool = create_pool_with_connections(site, conn)
+ pool.expects(:release).with(site, conn)
+
+ pool.with_connection(site, verify) {|c| }
+ end
+
+ it 'closes insecure HTTPS connections' do
+ conn = create_connection(site)
+ conn.expects(:use_ssl?).returns(true)
+ conn.expects(:verify_mode).returns(OpenSSL::SSL::VERIFY_NONE)
+
+ pool = create_pool_with_connections(site, conn)
+
+ pool.expects(:release).with(site, conn).never
+
+ pool.with_connection(site, verify) {|c| }
+ end
+ end
+ end
+
+ context 'when borrowing' do
+ it 'returns a new connection if the pool is empty' do
+ conn = create_connection(site)
+ pool = create_pool
+ pool.factory.expects(:create_connection).with(site).returns(conn)
+
+ expect(pool.borrow(site, verify)).to eq(conn)
+ end
+
+ it 'returns a matching connection' do
+ conn = create_connection(site)
+ pool = create_pool_with_connections(site, conn)
+
+ pool.factory.expects(:create_connection).never
+
+ expect(pool.borrow(site, verify)).to eq(conn)
+ end
+
+ it 'returns a new connection if there are no matching sites' do
+ different_conn = create_connection(different_site)
+ pool = create_pool_with_connections(different_site, different_conn)
+
+ conn = create_connection(site)
+ pool.factory.expects(:create_connection).with(site).returns(conn)
+
+ expect(pool.borrow(site, verify)).to eq(conn)
+ end
+
+ it 'returns started connections' do
+ conn = create_connection(site)
+ conn.expects(:start)
+
+ pool = create_pool
+ pool.factory.expects(:create_connection).with(site).returns(conn)
+
+ expect(pool.borrow(site, verify)).to eq(conn)
+ end
+
+ it "doesn't start a cached connection" do
+ conn = create_connection(site)
+ conn.expects(:start).never
+
+ pool = create_pool_with_connections(site, conn)
+ pool.borrow(site, verify)
+ end
+
+ it 'returns the most recently used connection from the pool' do
+ least_recently_used = create_connection(site)
+ most_recently_used = create_connection(site)
+
+ pool = create_pool_with_connections(site, least_recently_used, most_recently_used)
+ expect(pool.borrow(site, verify)).to eq(most_recently_used)
+ end
+
+ it 'finishes expired connections' do
+ conn = create_connection(site)
+ conn.expects(:finish)
+
+ pool = create_pool_with_expired_connections(site, conn)
+ pool.factory.expects(:create_connection => stub('conn', :start => nil))
+
+ pool.borrow(site, verify)
+ end
+
+ it 'logs an exception if it fails to close an expired connection' do
+ Puppet.expects(:log_exception).with(is_a(IOError), "Failed to close connection for #{site}: read timeout")
+
+ conn = create_connection(site)
+ conn.expects(:finish).raises(IOError, 'read timeout')
+
+ pool = create_pool_with_expired_connections(site, conn)
+ pool.factory.expects(:create_connection => stub('open_conn', :start => nil))
+
+ pool.borrow(site, verify)
+ end
+ end
+
+ context 'when releasing a connection' do
+ it 'adds the connection to an empty pool' do
+ conn = create_connection(site)
+
+ pool = create_pool
+ pool.release(site, conn)
+
+ expect(pool.pool[site].first.connection).to eq(conn)
+ end
+
+ it 'adds the connection to a pool with a connection for the same site' do
+ pool = create_pool
+ pool.release(site, create_connection(site))
+ pool.release(site, create_connection(site))
+
+ expect(pool.pool[site].count).to eq(2)
+ end
+
+ it 'adds the connection to a pool with a connection for a different site' do
+ pool = create_pool
+ pool.release(site, create_connection(site))
+ pool.release(different_site, create_connection(different_site))
+
+ expect(pool.pool[site].count).to eq(1)
+ expect(pool.pool[different_site].count).to eq(1)
+ end
+ end
+
+ context 'when closing' do
+ it 'clears the pool' do
+ pool = create_pool
+ pool.close
+
+ expect(pool.pool).to be_empty
+ end
+
+ it 'closes all cached connections' do
+ conn = create_connection(site)
+ conn.expects(:finish)
+
+ pool = create_pool_with_connections(site, conn)
+ pool.close
+ end
+ end
+end
diff --git a/spec/unit/network/http/rack/rest_spec.rb b/spec/unit/network/http/rack/rest_spec.rb
index 165b6ceb9..c35b789a2 100755
--- a/spec/unit/network/http/rack/rest_spec.rb
+++ b/spec/unit/network/http/rack/rest_spec.rb
@@ -12,11 +12,11 @@ describe "Puppet::Network::HTTP::RackREST", :if => Puppet.features.rack? do
before :all do
@model_class = stub('indirected model class')
Puppet::Indirector::Indirection.stubs(:model).with(:foo).returns(@model_class)
- @handler = Puppet::Network::HTTP::RackREST.new(:handler => :foo)
end
before :each do
@response = Rack::Response.new
+ @handler = Puppet::Network::HTTP::RackREST.new(:handler => :foo)
end
def mk_req(uri, opts = {})
diff --git a/spec/unit/network/http/session_spec.rb b/spec/unit/network/http/session_spec.rb
new file mode 100755
index 000000000..4eba67d7d
--- /dev/null
+++ b/spec/unit/network/http/session_spec.rb
@@ -0,0 +1,43 @@
+#! /usr/bin/env ruby
+require 'spec_helper'
+
+require 'puppet/network/http'
+
+describe Puppet::Network::HTTP::Session do
+ let(:connection) { stub('connection') }
+
+ def create_session(connection, expiration_time = nil)
+ expiration_time ||= Time.now + 60 * 60
+
+ Puppet::Network::HTTP::Session.new(connection, expiration_time)
+ end
+
+ it 'provides access to its connection' do
+ session = create_session(connection)
+
+ session.connection.should == connection
+ end
+
+ it 'expires a connection whose expiration time is in the past' do
+ now = Time.now
+ past = now - 1
+
+ session = create_session(connection, past)
+ session.expired?(now).should be_true
+ end
+
+ it 'expires a connection whose expiration time is now' do
+ now = Time.now
+
+ session = create_session(connection, now)
+ session.expired?(now).should be_true
+ end
+
+ it 'does not expire a connection whose expiration time is in the future' do
+ now = Time.now
+ future = now + 1
+
+ session = create_session(connection, future)
+ session.expired?(now).should be_false
+ end
+end
diff --git a/spec/unit/network/http/site_spec.rb b/spec/unit/network/http/site_spec.rb
new file mode 100755
index 000000000..06fcbf83d
--- /dev/null
+++ b/spec/unit/network/http/site_spec.rb
@@ -0,0 +1,90 @@
+#! /usr/bin/env ruby
+require 'spec_helper'
+
+require 'puppet/network/http'
+
+describe Puppet::Network::HTTP::Site do
+ let(:scheme) { 'https' }
+ let(:host) { 'rubygems.org' }
+ let(:port) { 443 }
+
+ def create_site(scheme, host, port)
+ Puppet::Network::HTTP::Site.new(scheme, host, port)
+ end
+
+ it 'accepts scheme, host, and port' do
+ site = create_site(scheme, host, port)
+
+ expect(site.scheme).to eq(scheme)
+ expect(site.host).to eq(host)
+ expect(site.port).to eq(port)
+ end
+
+ it 'generates an external URI string' do
+ site = create_site(scheme, host, port)
+
+ expect(site.addr).to eq("https://rubygems.org:443")
+ end
+
+ it 'considers sites to be different when the scheme is different' do
+ https_site = create_site('https', host, port)
+ http_site = create_site('http', host, port)
+
+ expect(https_site).to_not eq(http_site)
+ end
+
+ it 'considers sites to be different when the host is different' do
+ rubygems_site = create_site(scheme, 'rubygems.org', port)
+ github_site = create_site(scheme, 'github.com', port)
+
+ expect(rubygems_site).to_not eq(github_site)
+ end
+
+ it 'considers sites to be different when the port is different' do
+ site_443 = create_site(scheme, host, 443)
+ site_80 = create_site(scheme, host, 80)
+
+ expect(site_443).to_not eq(site_80)
+ end
+
+ it 'compares values when determining equality' do
+ site = create_site(scheme, host, port)
+
+ sites = {}
+ sites[site] = site
+
+ another_site = create_site(scheme, host, port)
+
+ expect(sites.include?(another_site)).to be_true
+ end
+
+ it 'computes the same hash code for equivalent objects' do
+ site = create_site(scheme, host, port)
+ same_site = create_site(scheme, host, port)
+
+ expect(site.hash).to eq(same_site.hash)
+ end
+
+ it 'uses ssl with https' do
+ site = create_site('https', host, port)
+
+ expect(site).to be_use_ssl
+ end
+
+ it 'does not use ssl with http' do
+ site = create_site('http', host, port)
+
+ expect(site).to_not be_use_ssl
+ end
+
+ it 'moves to a new URI location' do
+ site = create_site('http', 'host1', 80)
+
+ uri = URI.parse('https://host2:443/some/where/else')
+ new_site = site.move_to(uri)
+
+ expect(new_site.scheme).to eq('https')
+ expect(new_site.host).to eq('host2')
+ expect(new_site.port).to eq(443)
+ end
+end
diff --git a/spec/unit/network/http/webrick_spec.rb b/spec/unit/network/http/webrick_spec.rb
index 17f61e339..edeb439a9 100755
--- a/spec/unit/network/http/webrick_spec.rb
+++ b/spec/unit/network/http/webrick_spec.rb
@@ -127,7 +127,7 @@ describe Puppet::Network::HTTP::WEBrick do
server.setup_logger
end
- it "should use the masterlog if the run_mode is master" do
+ it "should use the masterhttplog if the run_mode is master" do
Puppet.run_mode.stubs(:master?).returns(true)
log = make_absolute("/master/log")
Puppet[:masterhttplog] = log
diff --git a/spec/unit/network/http_pool_spec.rb b/spec/unit/network/http_pool_spec.rb
index d8b84232e..a9c5783f2 100755
--- a/spec/unit/network/http_pool_spec.rb
+++ b/spec/unit/network/http_pool_spec.rb
@@ -9,7 +9,6 @@ describe Puppet::Network::HttpPool do
end
describe "when managing http instances" do
-
it "should return an http instance created with the passed host and port" do
http = Puppet::Network::HttpPool.http_instance("me", 54321)
http.should be_an_instance_of Puppet::Network::HTTP::Connection
@@ -47,7 +46,6 @@ describe Puppet::Network::HttpPool do
Puppet::Network::HttpPool.http_instance("me", 54321, true).should be_use_ssl
end
-
describe 'peer verification' do
def setup_standard_ssl_configuration
ca_cert_file = File.expand_path('/path/to/ssl/certs/ca_cert.pem')
@@ -77,12 +75,18 @@ describe Puppet::Network::HttpPool do
setup_standard_ssl_host
end
- it 'can enable peer verification' do
- Puppet::Network::HttpPool.http_instance("me", 54321, true, true).send(:connection).verify_mode.should == OpenSSL::SSL::VERIFY_PEER
+ it 'enables peer verification by default' do
+ response = Net::HTTPOK.new('1.1', 200, 'body')
+ conn = Puppet::Network::HttpPool.http_instance("me", 54321, true)
+ conn.expects(:execute_request).with { |http, request| expect(http.verify_mode).to eq(OpenSSL::SSL::VERIFY_PEER) }.returns(response)
+ conn.get('/')
end
it 'can disable peer verification' do
- Puppet::Network::HttpPool.http_instance("me", 54321, true, false).send(:connection).verify_mode.should == OpenSSL::SSL::VERIFY_NONE
+ response = Net::HTTPOK.new('1.1', 200, 'body')
+ conn = Puppet::Network::HttpPool.http_instance("me", 54321, true, false)
+ conn.expects(:execute_request).with { |http, request| expect(http.verify_mode).to eq(OpenSSL::SSL::VERIFY_NONE) }.returns(response)
+ conn.get('/')
end
end
@@ -91,5 +95,4 @@ describe Puppet::Network::HttpPool do
should_not equal(Puppet::Network::HttpPool.http_instance("me", 54321))
end
end
-
end
diff --git a/spec/unit/network/http_spec.rb b/spec/unit/network/http_spec.rb
new file mode 100755
index 000000000..4a149d3a8
--- /dev/null
+++ b/spec/unit/network/http_spec.rb
@@ -0,0 +1,10 @@
+#! /usr/bin/env ruby
+require 'spec_helper'
+require 'puppet/network/http'
+
+describe Puppet::Network::HTTP do
+ it 'defines an http_pool context' do
+ pool = Puppet.lookup(:http_pool)
+ expect(pool).to be_a(Puppet::Network::HTTP::NoCachePool)
+ end
+end
diff --git a/spec/unit/node/environment_spec.rb b/spec/unit/node/environment_spec.rb
index 0881b8576..3df5d329c 100755
--- a/spec/unit/node/environment_spec.rb
+++ b/spec/unit/node/environment_spec.rb
@@ -43,6 +43,42 @@ describe Puppet::Node::Environment do
Puppet::Node::Environment.new(one).should equal(one)
end
+ describe "equality" do
+ it "works as a hash key" do
+ base = Puppet::Node::Environment.create(:first, ["modules"], "manifests")
+ same = Puppet::Node::Environment.create(:first, ["modules"], "manifests")
+ different = Puppet::Node::Environment.create(:first, ["different"], "manifests")
+ hash = {}
+
+ hash[base] = "base env"
+ hash[same] = "same env"
+ hash[different] = "different env"
+
+ expect(hash[base]).to eq("same env")
+ expect(hash[different]).to eq("different env")
+ expect(hash).to have(2).item
+ end
+
+ it "is equal when name, modules, and manifests are the same" do
+ base = Puppet::Node::Environment.create(:base, ["modules"], "manifests")
+ different_name = Puppet::Node::Environment.create(:different, base.full_modulepath, base.manifest)
+
+ expect(base).to_not eq("not an environment")
+
+ expect(base).to eq(base)
+ expect(base.hash).to eq(base.hash)
+
+ expect(base.override_with(:modulepath => ["different"])).to_not eq(base)
+ expect(base.override_with(:modulepath => ["different"]).hash).to_not eq(base.hash)
+
+ expect(base.override_with(:manifest => "different")).to_not eq(base)
+ expect(base.override_with(:manifest => "different").hash).to_not eq(base.hash)
+
+ expect(different_name).to_not eq(base)
+ expect(different_name.hash).to_not eq(base.hash)
+ end
+ end
+
describe "overriding an existing environment" do
let(:original_path) { [tmpdir('original')] }
let(:new_path) { [tmpdir('new')] }
@@ -151,6 +187,60 @@ describe Puppet::Node::Environment do
end
end
+ it "does not register conflicting_manifest_settings? when not using directory environments" do
+ expect(Puppet::Node::Environment.create(:directory, [], '/some/non/default/manifest.pp').conflicting_manifest_settings?).to be_false
+ end
+
+ describe "when operating in the context of directory environments" do
+ before(:each) do
+ Puppet[:environmentpath] = "$confdir/environments"
+ Puppet[:default_manifest] = "/default/manifests/site.pp"
+ end
+
+ it "has no conflicting_manifest_settings? when disable_per_environment_manifest is false" do
+ expect(Puppet::Node::Environment.create(:directory, [], '/some/non/default/manifest.pp').conflicting_manifest_settings?).to be_false
+ end
+
+ context "when disable_per_environment_manifest is true" do
+ let(:config) { mock('config') }
+ let(:global_modulepath) { ["/global/modulepath"] }
+ let(:envconf) { Puppet::Settings::EnvironmentConf.new("/some/direnv", config, global_modulepath) }
+
+ before(:each) do
+ Puppet[:disable_per_environment_manifest] = true
+ end
+
+ def assert_manifest_conflict(expectation, envconf_manifest_value)
+ config.expects(:setting).with(:manifest).returns(
+ mock('setting', :value => envconf_manifest_value)
+ )
+ environment = Puppet::Node::Environment.create(:directory, [], '/default/manifests/site.pp')
+ loader = Puppet::Environments::Static.new(environment)
+ loader.stubs(:get_conf).returns(envconf)
+
+ Puppet.override(:environments => loader) do
+ expect(environment.conflicting_manifest_settings?).to eq(expectation)
+ end
+ end
+
+ it "has conflicting_manifest_settings when environment.conf manifest was set" do
+ assert_manifest_conflict(true, '/some/envconf/manifest/site.pp')
+ end
+
+ it "does not have conflicting_manifest_settings when environment.conf manifest is empty" do
+ assert_manifest_conflict(false, '')
+ end
+
+ it "does not have conflicting_manifest_settings when environment.conf manifest is nil" do
+ assert_manifest_conflict(false, nil)
+ end
+
+ it "does not have conflicting_manifest_settings when environment.conf manifest is an exact, uninterpolated match of default_manifest" do
+ assert_manifest_conflict(false, '/default/manifests/site.pp')
+ end
+ end
+ end
+
describe "when modeling a specific environment" do
it "should have a method for returning the environment name" do
Puppet::Node::Environment.new("testing").name.should == :testing
diff --git a/spec/unit/node_spec.rb b/spec/unit/node_spec.rb
index 85ec6e127..2de2b8279 100755
--- a/spec/unit/node_spec.rb
+++ b/spec/unit/node_spec.rb
@@ -157,11 +157,9 @@ describe Puppet::Node do
Puppet::Node.should read_json_attribute('parameters').from(@node.to_pson).as({"a" => "b", "c" => "d"})
end
- it "should include the environment" do
- Puppet.override(:environments => env_loader) do
- @node.environment = environment
- Puppet::Node.should read_json_attribute('environment').from(@node.to_pson).as(environment)
- end
+ it "deserializes environment to environment_name as a string" do
+ @node.environment = environment
+ Puppet::Node.should read_json_attribute('environment_name').from(@node.to_pson).as('bar')
end
end
end
diff --git a/spec/unit/parser/compiler_spec.rb b/spec/unit/parser/compiler_spec.rb
index d1e855a20..0ca01f521 100755
--- a/spec/unit/parser/compiler_spec.rb
+++ b/spec/unit/parser/compiler_spec.rb
@@ -97,6 +97,13 @@ describe Puppet::Parser::Compiler do
@compiler.environment.should equal(@node.environment)
end
+ it "fails if the node's environment has conflicting manifest settings" do
+ conflicted_environment = Puppet::Node::Environment.create(:testing, [], '/some/environment.conf/manifest.pp')
+ conflicted_environment.stubs(:conflicting_manifest_settings?).returns(true)
+ @node.environment = conflicted_environment
+ expect { Puppet::Parser::Compiler.compile(@node) }.to raise_error(Puppet::Error, /disable_per_environment_manifest.*true.*environment.conf.*manifest.*conflict/)
+ end
+
it "should include the resource type collection helper" do
Puppet::Parser::Compiler.ancestors.should be_include(Puppet::Resource::TypeCollectionHelper)
end
@@ -681,7 +688,7 @@ describe Puppet::Parser::Compiler do
it "should skip classes that have already been evaluated" do
@compiler.catalog.stubs(:tag)
- @scope.stubs(:class_scope).with(@class).returns("something")
+ @scope.stubs(:class_scope).with(@class).returns(@scope)
@compiler.expects(:add_resource).never
@@ -694,7 +701,7 @@ describe Puppet::Parser::Compiler do
it "should skip classes previously evaluated with different capitalization" do
@compiler.catalog.stubs(:tag)
@scope.stubs(:find_hostclass).with("MyClass",{:assume_fqname => false}).returns(@class)
- @scope.stubs(:class_scope).with(@class).returns("something")
+ @scope.stubs(:class_scope).with(@class).returns(@scope)
@compiler.expects(:add_resource).never
@resource.expects(:evaluate).never
Puppet::Parser::Resource.expects(:new).never
diff --git a/spec/unit/parser/eparser_adapter_spec.rb b/spec/unit/parser/eparser_adapter_spec.rb
deleted file mode 100644
index 173cfb783..000000000
--- a/spec/unit/parser/eparser_adapter_spec.rb
+++ /dev/null
@@ -1,407 +0,0 @@
-#! /usr/bin/env ruby
-require 'spec_helper'
-require 'puppet/parser/e_parser_adapter'
-
-describe Puppet::Parser do
-
- Puppet::Parser::AST
-
- before :each do
- @known_resource_types = Puppet::Resource::TypeCollection.new("development")
- @classic_parser = Puppet::Parser::Parser.new "development"
- @parser = Puppet::Parser::EParserAdapter.new(@classic_parser)
- @classic_parser.stubs(:known_resource_types).returns @known_resource_types
- @true_ast = Puppet::Parser::AST::Boolean.new :value => true
- end
-
- it "should require an environment at initialization" do
- expect {
- Puppet::Parser::EParserAdapter.new
- }.to raise_error(ArgumentError, /wrong number of arguments/)
- end
-
- describe "when parsing append operator" do
-
- it "should not raise syntax errors" do
- expect { @parser.parse("$var += something") }.to_not raise_error
- end
-
- it "should raise syntax error on incomplete syntax " do
- expect {
- @parser.parse("$var += ")
- }.to raise_error(Puppet::ParseError, /Syntax error at end of file/)
- end
-
- it "should create ast::VarDef with append=true" do
- vardef = @parser.parse("$var += 2").code[0]
- vardef.should be_a(Puppet::Parser::AST::VarDef)
- vardef.append.should == true
- end
-
- it "should work with arrays too" do
- vardef = @parser.parse("$var += ['test']").code[0]
- vardef.should be_a(Puppet::Parser::AST::VarDef)
- vardef.append.should == true
- end
-
- end
-
- describe "when parsing selector" do
- it "should support hash access on the left hand side" do
- expect { @parser.parse("$h = { 'a' => 'b' } $a = $h['a'] ? { 'b' => 'd', default => undef }") }.to_not raise_error
- end
- end
-
- describe "parsing 'unless'" do
- it "should create the correct ast objects" do
- Puppet::Parser::AST::Not.expects(:new).with { |h| h[:value].is_a?(Puppet::Parser::AST::Boolean) }
- @parser.parse("unless false { $var = 1 }")
- end
-
- it "should not raise an error with empty statements" do
- expect { @parser.parse("unless false { }") }.to_not raise_error
- end
-
- #test for bug #13296
- it "should not override 'unless' as a parameter inside resources" do
- lambda { @parser.parse("exec {'/bin/echo foo': unless => '/usr/bin/false',}") }.should_not raise_error
- end
- end
-
- describe "when parsing parameter names" do
- Puppet::Parser::Lexer::KEYWORDS.sort_tokens.each do |keyword|
- it "should allow #{keyword} as a keyword" do
- lambda { @parser.parse("exec {'/bin/echo foo': #{keyword} => '/usr/bin/false',}") }.should_not raise_error
- end
- end
- end
-
- describe "when parsing 'if'" do
- it "not, it should create the correct ast objects" do
- Puppet::Parser::AST::Not.expects(:new).with { |h| h[:value].is_a?(Puppet::Parser::AST::Boolean) }
- @parser.parse("if ! true { $var = 1 }")
- end
-
- it "boolean operation, it should create the correct ast objects" do
- Puppet::Parser::AST::BooleanOperator.expects(:new).with {
- |h| h[:rval].is_a?(Puppet::Parser::AST::Boolean) and h[:lval].is_a?(Puppet::Parser::AST::Boolean) and h[:operator]=="or"
- }
- @parser.parse("if true or true { $var = 1 }")
-
- end
-
- it "comparison operation, it should create the correct ast objects" do
- Puppet::Parser::AST::ComparisonOperator.expects(:new).with {
- |h| h[:lval].is_a?(Puppet::Parser::AST::Name) and h[:rval].is_a?(Puppet::Parser::AST::Name) and h[:operator]=="<"
- }
- @parser.parse("if 1 < 2 { $var = 1 }")
-
- end
-
- end
-
- describe "when parsing if complex expressions" do
- it "should create a correct ast tree" do
- aststub = stub_everything 'ast'
- Puppet::Parser::AST::ComparisonOperator.expects(:new).with {
- |h| h[:rval].is_a?(Puppet::Parser::AST::Name) and h[:lval].is_a?(Puppet::Parser::AST::Name) and h[:operator]==">"
- }.returns(aststub)
- Puppet::Parser::AST::ComparisonOperator.expects(:new).with {
- |h| h[:rval].is_a?(Puppet::Parser::AST::Name) and h[:lval].is_a?(Puppet::Parser::AST::Name) and h[:operator]=="=="
- }.returns(aststub)
- Puppet::Parser::AST::BooleanOperator.expects(:new).with {
- |h| h[:rval]==aststub and h[:lval]==aststub and h[:operator]=="and"
- }
- @parser.parse("if (1 > 2) and (1 == 2) { $var = 1 }")
- end
-
- it "should raise an error on incorrect expression" do
- expect {
- @parser.parse("if (1 > 2 > ) or (1 == 2) { $var = 1 }")
- }.to raise_error(Puppet::ParseError, /Syntax error at '\)'/)
- end
-
- end
-
- describe "when parsing resource references" do
-
- it "should not raise syntax errors" do
- expect { @parser.parse('exec { test: param => File["a"] }') }.to_not raise_error
- end
-
- it "should not raise syntax errors with multiple references" do
- expect { @parser.parse('exec { test: param => File["a","b"] }') }.to_not raise_error
- end
-
- it "should create an ast::ResourceReference" do
- # NOTE: In egrammar, type and name are unified immediately to lower case whereas the regular grammar
- # keeps the UC name in some contexts - it gets downcased later as the name of the type is in lower case.
- #
- Puppet::Parser::AST::ResourceReference.expects(:new).with { |arg|
- arg[:line]==1 and arg[:pos] ==25 and arg[:type]=="file" and arg[:title].is_a?(Puppet::Parser::AST::ASTArray)
- }
- @parser.parse('exec { test: command => File["a","b"] }')
- end
- end
-
- describe "when parsing resource overrides" do
-
- it "should not raise syntax errors" do
- expect { @parser.parse('Resource["title"] { param => value }') }.to_not raise_error
- end
-
- it "should not raise syntax errors with multiple overrides" do
- expect { @parser.parse('Resource["title1","title2"] { param => value }') }.to_not raise_error
- end
-
- it "should create an ast::ResourceOverride" do
- ro = @parser.parse('Resource["title1","title2"] { param => value }').code[0]
- ro.should be_a(Puppet::Parser::AST::ResourceOverride)
- ro.line.should == 1
- ro.object.should be_a(Puppet::Parser::AST::ResourceReference)
- ro.parameters[0].should be_a(Puppet::Parser::AST::ResourceParam)
- end
-
- end
-
- describe "when parsing if statements" do
-
- it "should not raise errors with empty if" do
- expect { @parser.parse("if true { }") }.to_not raise_error
- end
-
- it "should not raise errors with empty else" do
- expect { @parser.parse("if false { notice('if') } else { }") }.to_not raise_error
- end
-
- it "should not raise errors with empty if and else" do
- expect { @parser.parse("if false { } else { }") }.to_not raise_error
- end
-
- it "should create a nop node for empty branch" do
- Puppet::Parser::AST::Nop.expects(:new).twice
- @parser.parse("if true { }")
- end
-
- it "should create a nop node for empty else branch" do
- Puppet::Parser::AST::Nop.expects(:new)
- @parser.parse("if true { notice('test') } else { }")
- end
-
- it "should build a chain of 'ifs' if there's an 'elsif'" do
- expect { @parser.parse(<<-PP) }.to_not raise_error
- if true { notice('test') } elsif true {} else { }
- PP
- end
-
- end
-
- describe "when parsing function calls" do
- it "should not raise errors with no arguments" do
- expect { @parser.parse("tag()") }.to_not raise_error
- end
-
- it "should not raise errors with rvalue function with no args" do
- expect { @parser.parse("$a = template()") }.to_not raise_error
- end
-
- it "should not raise errors with arguments" do
- expect { @parser.parse("notice(1)") }.to_not raise_error
- end
-
- it "should not raise errors with multiple arguments" do
- expect { @parser.parse("notice(1,2)") }.to_not raise_error
- end
-
- it "should not raise errors with multiple arguments and a trailing comma" do
- expect { @parser.parse("notice(1,2,)") }.to_not raise_error
- end
-
- end
-
- describe "when parsing arrays" do
- it "should parse an array" do
- expect { @parser.parse("$a = [1,2]") }.to_not raise_error
- end
-
- it "should not raise errors with a trailing comma" do
- expect { @parser.parse("$a = [1,2,]") }.to_not raise_error
- end
-
- it "should accept an empty array" do
- expect { @parser.parse("$var = []\n") }.to_not raise_error
- end
- end
-
- describe "when parsing classes" do
- before :each do
- @krt = Puppet::Resource::TypeCollection.new("development")
- @classic_parser = Puppet::Parser::Parser.new "development"
- @parser = Puppet::Parser::EParserAdapter.new(@classic_parser)
- @classic_parser.stubs(:known_resource_types).returns @krt
- end
-
- it "should not create new classes" do
- @parser.parse("class foobar {}").code[0].should be_a(Puppet::Parser::AST::Hostclass)
- @krt.hostclass("foobar").should be_nil
- end
-
- it "should correctly set the parent class when one is provided" do
- @parser.parse("class foobar inherits yayness {}").code[0].instantiate('')[0].parent.should == "yayness"
- end
-
- it "should correctly set the parent class for multiple classes at a time" do
- statements = @parser.parse("class foobar inherits yayness {}\nclass boo inherits bar {}").code
- statements[0].instantiate('')[0].parent.should == "yayness"
- statements[1].instantiate('')[0].parent.should == "bar"
- end
-
- it "should define the code when some is provided" do
- @parser.parse("class foobar { $var = val }").code[0].code.should_not be_nil
- end
-
- it "should accept parameters with trailing comma" do
- @parser.parse("file { '/example': ensure => file, }").should be
- end
-
- it "should accept parametrized classes with trailing comma" do
- @parser.parse("class foobar ($var1 = 0,) { $var = val }").code[0].code.should_not be_nil
- end
-
- it "should define parameters when provided" do
- foobar = @parser.parse("class foobar($biz,$baz) {}").code[0].instantiate('')[0]
- foobar.arguments.should == {"biz" => nil, "baz" => nil}
- end
- end
-
- describe "when parsing resources" do
- before :each do
- @krt = Puppet::Resource::TypeCollection.new("development")
- @classic_parser = Puppet::Parser::Parser.new "development"
- @parser = Puppet::Parser::EParserAdapter.new(@classic_parser)
- @classic_parser.stubs(:known_resource_types).returns @krt
- end
-
- it "should be able to parse class resources" do
- @krt.add(Puppet::Resource::Type.new(:hostclass, "foobar", :arguments => {"biz" => nil}))
- expect { @parser.parse("class { foobar: biz => stuff }") }.to_not raise_error
- end
-
- it "should correctly mark exported resources as exported" do
- @parser.parse("@@file { '/file': }").code[0].exported.should be_true
- end
-
- it "should correctly mark virtual resources as virtual" do
- @parser.parse("@file { '/file': }").code[0].virtual.should be_true
- end
- end
-
- describe "when parsing nodes" do
- it "should be able to parse a node with a single name" do
- node = @parser.parse("node foo { }").code[0]
- node.should be_a Puppet::Parser::AST::Node
- node.names.length.should == 1
- node.names[0].value.should == "foo"
- end
-
- it "should be able to parse a node with two names" do
- node = @parser.parse("node foo, bar { }").code[0]
- node.should be_a Puppet::Parser::AST::Node
- node.names.length.should == 2
- node.names[0].value.should == "foo"
- node.names[1].value.should == "bar"
- end
-
- it "should be able to parse a node with three names" do
- node = @parser.parse("node foo, bar, baz { }").code[0]
- node.should be_a Puppet::Parser::AST::Node
- node.names.length.should == 3
- node.names[0].value.should == "foo"
- node.names[1].value.should == "bar"
- node.names[2].value.should == "baz"
- end
- end
-
- it "should fail if trying to collect defaults" do
- expect {
- @parser.parse("@Port { protocols => tcp }")
- }.to raise_error(Puppet::ParseError, /Defaults are not virtualizable/)
- end
-
- context "when parsing collections" do
- it "should parse basic collections" do
- @parser.parse("Port <| |>").code.
- should be_all {|x| x.is_a? Puppet::Parser::AST::Collection }
- end
-
- it "should parse fully qualified collections" do
- @parser.parse("Port::Range <| |>").code.
- should be_all {|x| x.is_a? Puppet::Parser::AST::Collection }
- end
- end
-
- it "should not assign to a fully qualified variable" do
- expect {
- @parser.parse("$one::two = yay")
- }.to raise_error(Puppet::ParseError, /Cannot assign to variables in other namespaces/)
- end
-
- it "should parse assignment of undef" do
- tree = @parser.parse("$var = undef")
- tree.code.children[0].should be_an_instance_of Puppet::Parser::AST::VarDef
- tree.code.children[0].value.should be_an_instance_of Puppet::Parser::AST::Undef
- end
-
- it "should treat classes as case insensitive" do
- @classic_parser.known_resource_types.import_ast(@parser.parse("class yayness {}"), '')
- @classic_parser.known_resource_types.hostclass('yayness').
- should == @classic_parser.find_hostclass("", "YayNess")
- end
-
- it "should treat defines as case insensitive" do
- @classic_parser.known_resource_types.import_ast(@parser.parse("define funtest {}"), '')
- @classic_parser.known_resource_types.hostclass('funtest').
- should == @classic_parser.find_hostclass("", "fUntEst")
- end
- context "when parsing method calls" do
- it "should parse method call with one param lambda" do
- expect { @parser.parse("$a.each |$a|{ debug $a }") }.to_not raise_error
- end
- it "should parse method call with two param lambda" do
- expect { @parser.parse("$a.each |$a,$b|{ debug $a }") }.to_not raise_error
- end
- it "should parse method call with two param lambda and default value" do
- expect { @parser.parse("$a.each |$a,$b=1|{ debug $a }") }.to_not raise_error
- end
- it "should parse method call without lambda (statement)" do
- expect { @parser.parse("$a.each") }.to_not raise_error
- end
- it "should parse method call without lambda (expression)" do
- expect { @parser.parse("$x = $a.each + 1") }.to_not raise_error
- end
- context "a receiver expression of type" do
- it "variable should be allowed" do
- expect { @parser.parse("$a.each") }.to_not raise_error
- end
- it "hasharrayaccess should be allowed" do
- expect { @parser.parse("$a[0][1].each") }.to_not raise_error
- end
- it "quoted text should be allowed" do
- expect { @parser.parse("\"monkey\".each") }.to_not raise_error
- expect { @parser.parse("'monkey'.each") }.to_not raise_error
- end
- it "selector text should be allowed" do
- expect { @parser.parse("$a ? { 'banana'=>[1,2,3]}.each") }.to_not raise_error
- end
- it "function call should be allowed" do
- expect { @parser.parse("duh(1,2,3).each") }.to_not raise_error
- end
- it "method call should be allowed" do
- expect { @parser.parse("$a.foo.bar") }.to_not raise_error
- end
- it "chained method calls with lambda should be allowed" do
- expect { @parser.parse("$a.foo||{}.bar||{}") }.to_not raise_error
- end
- end
- end
-end
diff --git a/spec/unit/parser/files_spec.rb b/spec/unit/parser/files_spec.rb
index 3eb43b276..020ce740b 100755
--- a/spec/unit/parser/files_spec.rb
+++ b/spec/unit/parser/files_spec.rb
@@ -12,6 +12,25 @@ describe Puppet::Parser::Files do
@basepath = make_absolute("/somepath")
end
+ describe "when searching for files" do
+ it "should return fully-qualified files directly" do
+ Puppet::Parser::Files.expects(:modulepath).never
+ Puppet::Parser::Files.find_file(@basepath + "/my/file", environment).should == @basepath + "/my/file"
+ end
+
+ it "should return the first found file" do
+ mod = mock 'module'
+ mod.expects(:file).returns("/one/mymod/files/myfile")
+ environment.expects(:module).with("mymod").returns mod
+
+ Puppet::Parser::Files.find_file("mymod/myfile", environment).should == "/one/mymod/files/myfile"
+ end
+
+ it "should return nil if template is not found" do
+ Puppet::Parser::Files.find_file("foomod/myfile", environment).should be_nil
+ end
+ end
+
describe "when searching for templates" do
it "should return fully-qualified templates directly" do
Puppet::Parser::Files.expects(:modulepath).never
diff --git a/spec/unit/parser/functions/contain_spec.rb b/spec/unit/parser/functions/contain_spec.rb
index 3150e0c8e..2a5aa57c7 100644
--- a/spec/unit/parser/functions/contain_spec.rb
+++ b/spec/unit/parser/functions/contain_spec.rb
@@ -3,11 +3,15 @@ require 'spec_helper'
require 'puppet_spec/compiler'
require 'puppet/parser/functions'
require 'matchers/containment_matchers'
+require 'matchers/resource'
require 'matchers/include_in_order'
+require 'unit/parser/functions/shared'
+
describe 'The "contain" function' do
include PuppetSpec::Compiler
include ContainmentMatchers
+ include Matchers::Resource
it "includes the class" do
catalog = compile_to_catalog(<<-MANIFEST)
@@ -25,6 +29,41 @@ describe 'The "contain" function' do
expect(catalog.classes).to include("contained")
end
+ it "includes the class when using a fully qualified anchored name" do
+ catalog = compile_to_catalog(<<-MANIFEST)
+ class contained {
+ notify { "contained": }
+ }
+
+ class container {
+ contain ::contained
+ }
+
+ include container
+ MANIFEST
+
+ expect(catalog.classes).to include("contained")
+ end
+
+ it "ensures that the edge is with the correct class" do
+ catalog = compile_to_catalog(<<-MANIFEST)
+ class outer {
+ class named { }
+ contain named
+ }
+
+ class named { }
+
+ include named
+ include outer
+ MANIFEST
+
+ expect(catalog).to have_resource("Class[Named]")
+ expect(catalog).to have_resource("Class[Outer]")
+ expect(catalog).to have_resource("Class[Outer::Named]")
+ expect(catalog).to contain_class("outer::named").in("outer")
+ end
+
it "makes the class contained in the current class" do
catalog = compile_to_catalog(<<-MANIFEST)
class contained {
@@ -182,4 +221,16 @@ describe 'The "contain" function' do
)
end
end
+
+ describe "When the future parser is in use" do
+ require 'puppet/pops'
+ before(:each) do
+ Puppet[:parser] = 'future'
+ compiler = Puppet::Parser::Compiler.new(Puppet::Node.new("foo"))
+ @scope = Puppet::Parser::Scope.new(compiler)
+ end
+
+ it_should_behave_like 'all functions transforming relative to absolute names', :function_contain
+ it_should_behave_like 'an inclusion function, regardless of the type of class reference,', :contain
+ end
end
diff --git a/spec/unit/parser/functions/create_resources_spec.rb b/spec/unit/parser/functions/create_resources_spec.rb
index 79ed02f22..3e7bd8015 100755
--- a/spec/unit/parser/functions/create_resources_spec.rb
+++ b/spec/unit/parser/functions/create_resources_spec.rb
@@ -49,7 +49,7 @@ describe 'function for dynamically creating resources' do
end
it 'should be able to add exported resources' do
- catalog = compile_to_catalog("create_resources('@@file', {'/etc/foo'=>{'ensure'=>'present'}})")
+ catalog = compile_to_catalog("create_resources('@@file', {'/etc/foo'=>{'ensure'=>'present'}}) realize(File['/etc/foo'])")
catalog.resource(:file, "/etc/foo")['ensure'].should == 'present'
catalog.resource(:file, "/etc/foo").exported.should == true
end
@@ -202,5 +202,12 @@ describe 'function for dynamically creating resources' do
catalog.resource(:notify, "test")['message'].should == 'two'
catalog.resource(:class, "bar").should_not be_nil
end
+
+ it 'should fail with a correct error message if the syntax of an imported file is incorrect' do
+ expect{
+ Puppet[:modulepath] = my_fixture_dir
+ compile_to_catalog('include foo')
+ }.to raise_error(Puppet::Error, /Syntax error at.*/)
+ end
end
end
diff --git a/spec/unit/parser/functions/digest_spec.rb b/spec/unit/parser/functions/digest_spec.rb
new file mode 100755
index 000000000..e3c0762d4
--- /dev/null
+++ b/spec/unit/parser/functions/digest_spec.rb
@@ -0,0 +1,31 @@
+#!/usr/bin/env rspec
+require 'spec_helper'
+
+describe "the digest function", :uses_checksums => true do
+ before :all do
+ Puppet::Parser::Functions.autoloader.loadall
+ end
+
+ before :each do
+ n = Puppet::Node.new('unnamed')
+ c = Puppet::Parser::Compiler.new(n)
+ @scope = Puppet::Parser::Scope.new(c)
+ end
+
+ it "should exist" do
+ Puppet::Parser::Functions.function("digest").should == "function_digest"
+ end
+
+ with_digest_algorithms do
+ it "should use the proper digest function" do
+ result = @scope.function_digest([plaintext])
+ result.should(eql( checksum ))
+ end
+
+ it "should only accept one parameter" do
+ expect do
+ @scope.function_digest(['foo', 'bar'])
+ end.to raise_error(ArgumentError)
+ end
+ end
+end
diff --git a/spec/unit/parser/functions/file_spec.rb b/spec/unit/parser/functions/file_spec.rb
index 34d12e2f2..c5f157300 100755
--- a/spec/unit/parser/functions/file_spec.rb
+++ b/spec/unit/parser/functions/file_spec.rb
@@ -13,10 +13,6 @@ describe "the 'file' function" do
let :compiler do Puppet::Parser::Compiler.new(node) end
let :scope do Puppet::Parser::Scope.new(compiler) end
- it "should exist" do
- Puppet::Parser::Functions.function("file").should == "function_file"
- end
-
def with_file_content(content)
path = tmpfile('file-function')
file = File.new(path, 'w')
@@ -31,7 +27,17 @@ describe "the 'file' function" do
end
end
- it "should return the first file if given two files" do
+ it "should read a file from a module path" do
+ with_file_content('file content') do |name|
+ mod = mock 'module'
+ mod.stubs(:file).with('myfile').returns(name)
+ compiler.environment.stubs(:module).with('mymod').returns(mod)
+
+ scope.function_file(['mymod/myfile']).should == 'file content'
+ end
+ end
+
+ it "should return the first file if given two files with absolute paths" do
with_file_content('one') do |one|
with_file_content('two') do |two|
scope.function_file([one, two]).should == "one"
@@ -39,6 +45,43 @@ describe "the 'file' function" do
end
end
+ it "should return the first file if given two files with module paths" do
+ with_file_content('one') do |one|
+ with_file_content('two') do |two|
+ mod = mock 'module'
+ compiler.environment.expects(:module).with('mymod').returns(mod)
+ mod.expects(:file).with('one').returns(one)
+ mod.stubs(:file).with('two').returns(two)
+
+ scope.function_file(['mymod/one','mymod/two']).should == 'one'
+ end
+ end
+ end
+
+ it "should return the first file if given two files with mixed paths, absolute first" do
+ with_file_content('one') do |one|
+ with_file_content('two') do |two|
+ mod = mock 'module'
+ compiler.environment.stubs(:module).with('mymod').returns(mod)
+ mod.stubs(:file).with('two').returns(two)
+
+ scope.function_file([one,'mymod/two']).should == 'one'
+ end
+ end
+ end
+
+ it "should return the first file if given two files with mixed paths, module first" do
+ with_file_content('one') do |one|
+ with_file_content('two') do |two|
+ mod = mock 'module'
+ compiler.environment.expects(:module).with('mymod').returns(mod)
+ mod.stubs(:file).with('two').returns(two)
+
+ scope.function_file(['mymod/two',one]).should == 'two'
+ end
+ end
+ end
+
it "should not fail when some files are absent" do
expect {
with_file_content('one') do |one|
diff --git a/spec/unit/parser/functions/include_spec.rb b/spec/unit/parser/functions/include_spec.rb
index c1a5cbd5c..3fa0da35d 100755
--- a/spec/unit/parser/functions/include_spec.rb
+++ b/spec/unit/parser/functions/include_spec.rb
@@ -1,5 +1,6 @@
#! /usr/bin/env ruby
require 'spec_helper'
+require 'unit/parser/functions/shared'
describe "the 'include' function" do
before :all do
@@ -46,6 +47,19 @@ describe "the 'include' function" do
it "should raise if the class is not found" do
@scope.stubs(:source).returns(true)
- expect { @scope.function_include(["nosuchclass"]) }.to raise_error Puppet::Error
+ expect { @scope.function_include(["nosuchclass"]) }.to raise_error(Puppet::Error)
+ end
+
+ describe "When the future parser is in use" do
+ require 'puppet/pops'
+ require 'puppet_spec/compiler'
+ include PuppetSpec::Compiler
+
+ before(:each) do
+ Puppet[:parser] = 'future'
+ end
+
+ it_should_behave_like 'all functions transforming relative to absolute names', :function_include
+ it_should_behave_like 'an inclusion function, regardless of the type of class reference,', :include
end
end
diff --git a/spec/unit/parser/functions/realize_spec.rb b/spec/unit/parser/functions/realize_spec.rb
index 9f53f5a76..79e5eb155 100755
--- a/spec/unit/parser/functions/realize_spec.rb
+++ b/spec/unit/parser/functions/realize_spec.rb
@@ -1,53 +1,61 @@
-#! /usr/bin/env ruby
require 'spec_helper'
+require 'matchers/resource'
+require 'puppet_spec/compiler'
describe "the realize function" do
- before :all do
- Puppet::Parser::Functions.autoloader.loadall
- end
+ include Matchers::Resource
+ include PuppetSpec::Compiler
- before :each do
- @collector = stub_everything 'collector'
- node = Puppet::Node.new('localhost')
- @compiler = Puppet::Parser::Compiler.new(node)
- @scope = Puppet::Parser::Scope.new(@compiler)
- @compiler.stubs(:add_collection).with(@collector)
- end
+ it "realizes a single, referenced resource" do
+ catalog = compile_to_catalog(<<-EOM)
+ @notify { testing: }
+ realize(Notify[testing])
+ EOM
- it "should exist" do
- Puppet::Parser::Functions.function("realize").should == "function_realize"
+ expect(catalog).to have_resource("Notify[testing]")
end
- it "should create a Collector when called" do
-
- Puppet::Parser::Collector.expects(:new).returns(@collector)
+ it "realizes multiple resources" do
+ catalog = compile_to_catalog(<<-EOM)
+ @notify { testing: }
+ @notify { other: }
+ realize(Notify[testing], Notify[other])
+ EOM
- @scope.function_realize(["test"])
+ expect(catalog).to have_resource("Notify[testing]")
+ expect(catalog).to have_resource("Notify[other]")
end
- it "should assign the passed-in resources to the collector" do
- Puppet::Parser::Collector.stubs(:new).returns(@collector)
+ it "realizes resources provided in arrays" do
+ catalog = compile_to_catalog(<<-EOM)
+ @notify { testing: }
+ @notify { other: }
+ realize([Notify[testing], [Notify[other]]])
+ EOM
- @collector.expects(:resources=).with(["test"])
-
- @scope.function_realize(["test"])
+ expect(catalog).to have_resource("Notify[testing]")
+ expect(catalog).to have_resource("Notify[other]")
end
- it "should flatten the resources assigned to the collector" do
- Puppet::Parser::Collector.stubs(:new).returns(@collector)
-
- @collector.expects(:resources=).with(["test"])
-
- @scope.function_realize([["test"]])
+ it "fails when the resource does not exist" do
+ expect do
+ compile_to_catalog(<<-EOM)
+ realize(Notify[missing])
+ EOM
+ end.to raise_error(Puppet::Error, /Failed to realize/)
end
- it "should let the compiler know this collector" do
- Puppet::Parser::Collector.stubs(:new).returns(@collector)
- @collector.stubs(:resources=).with(["test"])
-
- @compiler.expects(:add_collection).with(@collector)
-
- @scope.function_realize(["test"])
+ it "fails when no parameters given" do
+ expect do
+ compile_to_catalog(<<-EOM)
+ realize()
+ EOM
+ end.to raise_error(Puppet::Error, /Wrong number of arguments/)
end
+ it "silently does nothing when an empty array of resources is given" do
+ compile_to_catalog(<<-EOM)
+ realize([])
+ EOM
+ end
end
diff --git a/spec/unit/parser/functions/require_spec.rb b/spec/unit/parser/functions/require_spec.rb
index 72c3f9f5f..f0b4fcc28 100755
--- a/spec/unit/parser/functions/require_spec.rb
+++ b/spec/unit/parser/functions/require_spec.rb
@@ -1,5 +1,6 @@
#! /usr/bin/env ruby
require 'spec_helper'
+require 'unit/parser/functions/shared'
describe "the require function" do
before :all do
@@ -26,13 +27,13 @@ describe "the require function" do
end
it "should delegate to the 'include' puppet function" do
- @scope.expects(:function_include).with(["myclass"])
+ @scope.compiler.expects(:evaluate_classes).with(["myclass"], @scope, false)
@scope.function_require(["myclass"])
end
- it "should set the 'require' prarameter on the resource to a resource reference" do
- @scope.stubs(:function_include)
+ it "should set the 'require' parameter on the resource to a resource reference" do
+ @scope.compiler.stubs(:evaluate_classes)
@scope.function_require(["myclass"])
@resource["require"].should be_instance_of(Array)
@@ -40,7 +41,7 @@ describe "the require function" do
end
it "should lookup the absolute class path" do
- @scope.stubs(:function_include)
+ @scope.compiler.stubs(:evaluate_classes)
@scope.expects(:find_hostclass).with("myclass").returns(@klass)
@klass.expects(:name).returns("myclass")
@@ -49,7 +50,7 @@ describe "the require function" do
end
it "should append the required class to the require parameter" do
- @scope.stubs(:function_include)
+ @scope.compiler.stubs(:evaluate_classes)
one = Puppet::Resource.new(:file, "/one")
@resource[:require] = one
@@ -58,4 +59,17 @@ describe "the require function" do
@resource[:require].should be_include(one)
@resource[:require].detect { |r| r.to_s == "Class[Myclass]" }.should be_instance_of(Puppet::Resource)
end
+
+ describe "When the future parser is in use" do
+ require 'puppet/pops'
+ require 'puppet_spec/compiler'
+ include PuppetSpec::Compiler
+
+ before(:each) do
+ Puppet[:parser] = 'future'
+ end
+
+ it_should_behave_like 'all functions transforming relative to absolute names', :function_require
+ it_should_behave_like 'an inclusion function, regardless of the type of class reference,', :require
+ end
end
diff --git a/spec/unit/parser/functions/search_spec.rb b/spec/unit/parser/functions/search_spec.rb
index b2c042b04..54054bd6a 100755
--- a/spec/unit/parser/functions/search_spec.rb
+++ b/spec/unit/parser/functions/search_spec.rb
@@ -20,4 +20,9 @@ describe "the 'search' function" do
scope.expects(:add_namespace).with("who")
scope.function_search(["where", "what", "who"])
end
+
+ it "is deprecated" do
+ Puppet.expects(:deprecation_warning).with("The 'search' function is deprecated. See http://links.puppetlabs.com/search-function-deprecation")
+ scope.function_search(['wat'])
+ end
end
diff --git a/spec/unit/parser/functions/shared.rb b/spec/unit/parser/functions/shared.rb
new file mode 100644
index 000000000..f5adcd811
--- /dev/null
+++ b/spec/unit/parser/functions/shared.rb
@@ -0,0 +1,82 @@
+shared_examples_for 'all functions transforming relative to absolute names' do |func_method|
+
+ it 'transforms relative names to absolute' do
+ @scope.compiler.expects(:evaluate_classes).with(["::myclass"], @scope, false)
+ @scope.send(func_method, ["myclass"])
+ end
+
+ it 'accepts a Class[name] type' do
+ @scope.compiler.expects(:evaluate_classes).with(["::myclass"], @scope, false)
+ @scope.send(func_method, [Puppet::Pops::Types::TypeFactory.host_class('myclass')])
+ end
+
+ it 'accepts a Resource[class, name] type' do
+ @scope.compiler.expects(:evaluate_classes).with(["::myclass"], @scope, false)
+ @scope.send(func_method, [Puppet::Pops::Types::TypeFactory.resource('class', 'myclass')])
+ end
+
+ it 'raises and error for unspecific Class' do
+ expect {
+ @scope.send(func_method, [Puppet::Pops::Types::TypeFactory.host_class()])
+ }.to raise_error(ArgumentError, /Cannot use an unspecific Class\[\] Type/)
+ end
+
+ it 'raises and error for Resource that is not of class type' do
+ expect {
+ @scope.send(func_method, [Puppet::Pops::Types::TypeFactory.resource('file')])
+ }.to raise_error(ArgumentError, /Cannot use a Resource\[file\] where a Resource\['class', name\] is expected/)
+ end
+
+ it 'raises and error for Resource that is unspecific' do
+ expect {
+ @scope.send(func_method, [Puppet::Pops::Types::TypeFactory.resource()])
+ }.to raise_error(ArgumentError, /Cannot use an unspecific Resource\[\] where a Resource\['class', name\] is expected/)
+ end
+
+ it 'raises and error for Resource[class] that is unspecific' do
+ expect {
+ @scope.send(func_method, [Puppet::Pops::Types::TypeFactory.resource('class')])
+ }.to raise_error(ArgumentError, /Cannot use an unspecific Resource\['class'\] where a Resource\['class', name\] is expected/)
+ end
+
+end
+
+shared_examples_for 'an inclusion function, regardless of the type of class reference,' do |function|
+
+ it "and #{function} a class absolutely, even when a relative namespaced class of the same name is present" do
+ catalog = compile_to_catalog(<<-MANIFEST)
+ class foo {
+ class bar { }
+ #{function} bar
+ }
+ class bar { }
+ include foo
+ MANIFEST
+ expect(catalog.classes).to include('foo','bar')
+ end
+
+ it "and #{function} a class absolutely by Class['type'] reference" do
+ catalog = compile_to_catalog(<<-MANIFEST)
+ class foo {
+ class bar { }
+ #{function} Class['bar']
+ }
+ class bar { }
+ include foo
+ MANIFEST
+ expect(catalog.classes).to include('foo','bar')
+ end
+
+ it "and #{function} a class absolutely by Resource['type','title'] reference" do
+ catalog = compile_to_catalog(<<-MANIFEST)
+ class foo {
+ class bar { }
+ #{function} Resource['class','bar']
+ }
+ class bar { }
+ include foo
+ MANIFEST
+ expect(catalog.classes).to include('foo','bar')
+ end
+
+end
diff --git a/spec/unit/parser/functions_spec.rb b/spec/unit/parser/functions_spec.rb
index b3c04d1af..3c6266752 100755
--- a/spec/unit/parser/functions_spec.rb
+++ b/spec/unit/parser/functions_spec.rb
@@ -38,7 +38,7 @@ describe Puppet::Parser::Functions do
it "instruments the function to profile the execution" do
messages = []
- Puppet::Util::Profiler.current = Puppet::Util::Profiler::WallClock.new(proc { |msg| messages << msg }, "id")
+ Puppet::Util::Profiler.add_profiler(Puppet::Util::Profiler::WallClock.new(proc { |msg| messages << msg }, "id"))
Puppet::Parser::Functions.newfunction("name", :type => :rvalue) { |args| }
callable_functions_from(function_module).function_name([])
@@ -85,10 +85,7 @@ describe Puppet::Parser::Functions do
end
describe "when calling function to test arity" do
- let(:function_module) { Module.new }
- before do
- Puppet::Parser::Functions.stubs(:environment_module).returns(function_module)
- end
+ let(:function_module) { Puppet::Parser::Functions.environment_module(Puppet.lookup(:current_environment)) }
it "should raise an error if the function is called with too many arguments" do
Puppet::Parser::Functions.newfunction("name", :arity => 2) { |args| }
diff --git a/spec/unit/parser/lexer_spec.rb b/spec/unit/parser/lexer_spec.rb
index 62234e214..f0f10e9f3 100755
--- a/spec/unit/parser/lexer_spec.rb
+++ b/spec/unit/parser/lexer_spec.rb
@@ -279,7 +279,8 @@ describe Puppet::Parser::Lexer::TOKENS[:NAME] do
it "should return itself and the value if the matched term is not a keyword" do
Puppet::Parser::Lexer::KEYWORDS.expects(:lookup).returns(nil)
- @token.convert(stub("lexer"), "myval").should == [Puppet::Parser::Lexer::TOKENS[:NAME], "myval"]
+ lexer = stub("lexer")
+ @token.convert(lexer, "myval").should == [Puppet::Parser::Lexer::TOKENS[:NAME], "myval"]
end
it "should return the keyword token and the value if the matched term is a keyword" do
@@ -845,6 +846,14 @@ describe "Puppet::Parser::Lexer in the old tests" do
end
end
+describe 'Puppet::Parser::Lexer handles reserved words' do
+ ['function', 'private', 'attr', 'type'].each do |reserved_bare_word|
+ it "by delivering '#{reserved_bare_word}' as a bare word" do
+ expect(tokens_scanned_from(reserved_bare_word)).to eq([[:NAME, {:value=>reserved_bare_word, :line => 1}]])
+ end
+ end
+end
+
describe "Puppet::Parser::Lexer in the old tests when lexing example files" do
my_fixtures('*.pp') do |file|
it "should correctly lex #{file}" do
diff --git a/spec/unit/parser/methods/map_spec.rb b/spec/unit/parser/methods/map_spec.rb
deleted file mode 100644
index 7f8e79789..000000000
--- a/spec/unit/parser/methods/map_spec.rb
+++ /dev/null
@@ -1,184 +0,0 @@
-require 'puppet'
-require 'spec_helper'
-require 'puppet_spec/compiler'
-
-require 'unit/parser/methods/shared'
-
-describe 'the map method' do
- include PuppetSpec::Compiler
-
- before :each do
- Puppet[:parser] = "future"
- end
-
- context "using future parser" do
- it 'map on an array (multiplying each value by 2)' do
- catalog = compile_to_catalog(<<-MANIFEST)
- $a = [1,2,3]
- $a.map |$x|{ $x*2}.each |$v|{
- file { "/file_$v": ensure => present }
- }
- MANIFEST
-
- catalog.resource(:file, "/file_2")['ensure'].should == 'present'
- catalog.resource(:file, "/file_4")['ensure'].should == 'present'
- catalog.resource(:file, "/file_6")['ensure'].should == 'present'
- end
-
- it 'map on an enumerable type (multiplying each value by 2)' do
- catalog = compile_to_catalog(<<-MANIFEST)
- $a = Integer[1,3]
- $a.map |$x|{ $x*2}.each |$v|{
- file { "/file_$v": ensure => present }
- }
- MANIFEST
-
- catalog.resource(:file, "/file_2")['ensure'].should == 'present'
- catalog.resource(:file, "/file_4")['ensure'].should == 'present'
- catalog.resource(:file, "/file_6")['ensure'].should == 'present'
- end
-
- it 'map on an integer (multiply each by 3)' do
- catalog = compile_to_catalog(<<-MANIFEST)
- 3.map |$x|{ $x*3}.each |$v|{
- file { "/file_$v": ensure => present }
- }
- MANIFEST
-
- catalog.resource(:file, "/file_0")['ensure'].should == 'present'
- catalog.resource(:file, "/file_3")['ensure'].should == 'present'
- catalog.resource(:file, "/file_6")['ensure'].should == 'present'
- end
-
- it 'map on a string' do
- catalog = compile_to_catalog(<<-MANIFEST)
- $a = {a=>x, b=>y}
- "ab".map |$x|{$a[$x]}.each |$v|{
- file { "/file_$v": ensure => present }
- }
- MANIFEST
-
- catalog.resource(:file, "/file_x")['ensure'].should == 'present'
- catalog.resource(:file, "/file_y")['ensure'].should == 'present'
- end
-
- it 'map on an array (multiplying value by 10 in even index position)' do
- catalog = compile_to_catalog(<<-MANIFEST)
- $a = [1,2,3]
- $a.map |$i, $x|{ if $i % 2 == 0 {$x} else {$x*10}}.each |$v|{
- file { "/file_$v": ensure => present }
- }
- MANIFEST
-
- catalog.resource(:file, "/file_1")['ensure'].should == 'present'
- catalog.resource(:file, "/file_20")['ensure'].should == 'present'
- catalog.resource(:file, "/file_3")['ensure'].should == 'present'
- end
-
- it 'map on a hash selecting keys' do
- catalog = compile_to_catalog(<<-MANIFEST)
- $a = {'a'=>1,'b'=>2,'c'=>3}
- $a.map |$x|{ $x[0]}.each |$k|{
- file { "/file_$k": ensure => present }
- }
- MANIFEST
-
- catalog.resource(:file, "/file_a")['ensure'].should == 'present'
- catalog.resource(:file, "/file_b")['ensure'].should == 'present'
- catalog.resource(:file, "/file_c")['ensure'].should == 'present'
- end
-
- it 'map on a hash selecting keys - using two block parameters' do
- catalog = compile_to_catalog(<<-MANIFEST)
- $a = {'a'=>1,'b'=>2,'c'=>3}
- $a.map |$k,$v|{ file { "/file_$k": ensure => present }
- }
- MANIFEST
-
- catalog.resource(:file, "/file_a")['ensure'].should == 'present'
- catalog.resource(:file, "/file_b")['ensure'].should == 'present'
- catalog.resource(:file, "/file_c")['ensure'].should == 'present'
- end
-
- it 'each on a hash selecting value' do
- catalog = compile_to_catalog(<<-MANIFEST)
- $a = {'a'=>1,'b'=>2,'c'=>3}
- $a.map |$x|{ $x[1]}.each |$k|{ file { "/file_$k": ensure => present } }
- MANIFEST
-
- catalog.resource(:file, "/file_1")['ensure'].should == 'present'
- catalog.resource(:file, "/file_2")['ensure'].should == 'present'
- catalog.resource(:file, "/file_3")['ensure'].should == 'present'
- end
-
- it 'each on a hash selecting value - using two bloc parameters' do
- catalog = compile_to_catalog(<<-MANIFEST)
- $a = {'a'=>1,'b'=>2,'c'=>3}
- $a.map |$k,$v|{ file { "/file_$v": ensure => present } }
- MANIFEST
-
- catalog.resource(:file, "/file_1")['ensure'].should == 'present'
- catalog.resource(:file, "/file_2")['ensure'].should == 'present'
- catalog.resource(:file, "/file_3")['ensure'].should == 'present'
- end
-
- context "handles data type corner cases" do
- it "map gets values that are false" do
- catalog = compile_to_catalog(<<-MANIFEST)
- $a = [false,false]
- $a.map |$x| { $x }.each |$i, $v| {
- file { "/file_$i.$v": ensure => present }
- }
- MANIFEST
-
- catalog.resource(:file, "/file_0.false")['ensure'].should == 'present'
- catalog.resource(:file, "/file_1.false")['ensure'].should == 'present'
- end
-
- it "map gets values that are nil" do
- Puppet::Parser::Functions.newfunction(:nil_array, :type => :rvalue) do |args|
- [nil]
- end
- catalog = compile_to_catalog(<<-MANIFEST)
- $a = nil_array()
- $a.map |$x| { $x }.each |$i, $v| {
- file { "/file_$i.$v": ensure => present }
- }
- MANIFEST
-
- catalog.resource(:file, "/file_0.")['ensure'].should == 'present'
- end
-
- it "map gets values that are undef" do
- catalog = compile_to_catalog(<<-MANIFEST)
- $a = [$does_not_exist]
- $a.map |$x = "something"| { $x }.each |$i, $v| {
- file { "/file_$i.$v": ensure => present }
- }
- MANIFEST
- catalog.resource(:file, "/file_0.something")['ensure'].should == 'present'
- end
- end
-
- context 'map checks arguments and' do
- it 'raises an error when block has more than 2 argument' do
- expect do
- compile_to_catalog(<<-MANIFEST)
- [1].map |$index, $x, $yikes|{ }
- MANIFEST
- end.to raise_error(Puppet::Error, /block must define at most two parameters/)
- end
-
- it 'raises an error when block has fewer than 1 argument' do
- expect do
- compile_to_catalog(<<-MANIFEST)
- [1].map || { }
- MANIFEST
- end.to raise_error(Puppet::Error, /block must define at least one parameter/)
- end
- end
-
- it_should_behave_like 'all iterative functions argument checks', 'map'
- it_should_behave_like 'all iterative functions hash handling', 'map'
- end
-end
diff --git a/spec/unit/parser/methods/shared.rb b/spec/unit/parser/methods/shared.rb
deleted file mode 100644
index 42cfd2359..000000000
--- a/spec/unit/parser/methods/shared.rb
+++ /dev/null
@@ -1,45 +0,0 @@
-
-shared_examples_for 'all iterative functions hash handling' do |func|
- it 'passes a hash entry as an array of the key and value' do
- catalog = compile_to_catalog(<<-MANIFEST)
- {a=>1}.#{func} |$v| { notify { "${v[0]} ${v[1]}": } }
- MANIFEST
-
- catalog.resource(:notify, "a 1").should_not be_nil
- end
-end
-
-shared_examples_for 'all iterative functions argument checks' do |func|
-
- it 'raises an error when used against an unsupported type' do
- expect do
- compile_to_catalog(<<-MANIFEST)
- 3.14.#{func} |$v| { }
- MANIFEST
- end.to raise_error(Puppet::Error, /must be something enumerable/)
- end
-
- it 'raises an error when called with any parameters besides a block' do
- expect do
- compile_to_catalog(<<-MANIFEST)
- [1].#{func}(1) |$v| { }
- MANIFEST
- end.to raise_error(Puppet::Error, /Wrong number of arguments/)
- end
-
- it 'raises an error when called without a block' do
- expect do
- compile_to_catalog(<<-MANIFEST)
- [1].#{func}()
- MANIFEST
- end.to raise_error(Puppet::Error, /Wrong number of arguments/)
- end
-
- it 'raises an error when called without a block' do
- expect do
- compile_to_catalog(<<-MANIFEST)
- [1].#{func}(1)
- MANIFEST
- end.to raise_error(Puppet::Error, /must be a parameterized block/)
- end
-end
diff --git a/spec/unit/parser/type_loader_spec.rb b/spec/unit/parser/type_loader_spec.rb
index 659ffa942..5454528a7 100755
--- a/spec/unit/parser/type_loader_spec.rb
+++ b/spec/unit/parser/type_loader_spec.rb
@@ -3,7 +3,6 @@ require 'spec_helper'
require 'puppet/parser/type_loader'
require 'puppet/parser/parser_factory'
-require 'puppet/parser/e_parser_adapter'
require 'puppet_spec/modules'
require 'puppet_spec/files'
diff --git a/spec/unit/pops/benchmark_spec.rb b/spec/unit/pops/benchmark_spec.rb
index 03c2e743d..462c03947 100644
--- a/spec/unit/pops/benchmark_spec.rb
+++ b/spec/unit/pops/benchmark_spec.rb
@@ -118,7 +118,7 @@ $a = "interpolate ${foo} and stuff"
end
context "Measure Evaluator" do
- let(:parser) { Puppet::Pops::Parser::EvaluatingParser::Transitional.new }
+ let(:parser) { Puppet::Pops::Parser::EvaluatingParser.new }
let(:node) { 'node.example.com' }
let(:scope) { s = create_test_scope_for_node(node); s }
it "evaluator", :profile => true do
diff --git a/spec/unit/pops/binder/bindings_composer_spec.rb b/spec/unit/pops/binder/bindings_composer_spec.rb
index 93bc44722..d8e4b6f9c 100644
--- a/spec/unit/pops/binder/bindings_composer_spec.rb
+++ b/spec/unit/pops/binder/bindings_composer_spec.rb
@@ -34,31 +34,33 @@ describe 'BinderComposer' do
it 'should load everything without errors' do
Puppet.settings[:confdir] = config_directory
Puppet.settings[:libdir] = File.join(config_directory, 'lib')
- Puppet.settings[:modulepath] = File.join(config_directory, 'modules')
- # this ensure the binder is active at the right time
- # (issues with getting a /dev/null path for "confdir" / "libdir")
- raise "Binder not active" unless scope.compiler.is_binder_active?
- diagnostics = diag
- composer = Puppet::Pops::Binder::BindingsComposer.new()
- the_scope = scope
- the_scope['fqdn'] = 'localhost'
- the_scope['environment'] = 'production'
- layered_bindings = composer.compose(scope)
- # puts Puppet::Pops::Binder::BindingsModelDumper.new().dump(layered_bindings)
- binder = Puppet::Pops::Binder::Binder.new(layered_bindings)
- injector = Puppet::Pops::Binder::Injector.new(binder)
- expect(injector.lookup(scope, 'awesome_x')).to be == 'golden'
- expect(injector.lookup(scope, 'good_x')).to be == 'golden'
- expect(injector.lookup(scope, 'rotten_x')).to be == nil
- expect(injector.lookup(scope, 'the_meaning_of_life')).to be == 42
- expect(injector.lookup(scope, 'has_funny_hat')).to be == 'the pope'
- expect(injector.lookup(scope, 'all your base')).to be == 'are belong to us'
- expect(injector.lookup(scope, 'env_meaning_of_life')).to be == 'production thinks it is 42'
- expect(injector.lookup(scope, '::quick::brown::fox')).to be == 'echo: quick brown fox'
+ Puppet.override(:environments => Puppet::Environments::Static.new(Puppet::Node::Environment.create(:production, [File.join(config_directory, 'modules')]))) do
+ # this ensure the binder is active at the right time
+ # (issues with getting a /dev/null path for "confdir" / "libdir")
+ raise "Binder not active" unless scope.compiler.is_binder_active?
+
+ diagnostics = diag
+ composer = Puppet::Pops::Binder::BindingsComposer.new()
+ the_scope = scope
+ the_scope['fqdn'] = 'localhost'
+ the_scope['environment'] = 'production'
+ layered_bindings = composer.compose(scope)
+ # puts Puppet::Pops::Binder::BindingsModelDumper.new().dump(layered_bindings)
+ binder = Puppet::Pops::Binder::Binder.new(layered_bindings)
+ injector = Puppet::Pops::Binder::Injector.new(binder)
+ expect(injector.lookup(scope, 'awesome_x')).to be == 'golden'
+ expect(injector.lookup(scope, 'good_x')).to be == 'golden'
+ expect(injector.lookup(scope, 'rotten_x')).to be == nil
+ expect(injector.lookup(scope, 'the_meaning_of_life')).to be == 42
+ expect(injector.lookup(scope, 'has_funny_hat')).to be == 'the pope'
+ expect(injector.lookup(scope, 'all your base')).to be == 'are belong to us'
+ expect(injector.lookup(scope, 'env_meaning_of_life')).to be == 'production thinks it is 42'
+ expect(injector.lookup(scope, '::quick::brown::fox')).to be == 'echo: quick brown fox'
+ end
end
end
# TODO: test error conditions (see BinderConfigChecker for what to test)
-end \ No newline at end of file
+end
diff --git a/spec/unit/pops/binder/injector_spec.rb b/spec/unit/pops/binder/injector_spec.rb
index 32eba3260..b62ceaeb9 100644
--- a/spec/unit/pops/binder/injector_spec.rb
+++ b/spec/unit/pops/binder/injector_spec.rb
@@ -101,14 +101,16 @@ describe 'Injector' do
let(:bindings) { factory.named_bindings('test') }
let(:scope) { null_scope()}
- let(:duck_type) { type_factory.ruby(InjectorSpecModule::TestDuck) }
-
let(:binder) { Puppet::Pops::Binder::Binder }
let(:lbinder) do
binder.new(layered_bindings)
end
+ def duck_type
+ # create distinct instances
+ type_factory.ruby(InjectorSpecModule::TestDuck)
+ end
let(:layered_bindings) { factory.layered_bindings(test_layer_with_bindings(bindings.model)) }
@@ -528,7 +530,7 @@ describe 'Injector' do
expect {
the_ducks = injector(lbinder).lookup(scope, hash_of_duck, "donalds_nephews")
- }.to_not raise_error(/Duplicate key/)
+ }.to_not raise_error
end
it "should produce detailed type error message" do
@@ -592,11 +594,11 @@ describe 'Injector' do
it "should fail attempts to append, perform uniq or flatten on type incompatible multibind hash" do
hash_of_integer = type_factory.hash_of(type_factory.integer())
ids = ["ducks1", "ducks2", "ducks3"]
- mb = bindings.multibind(ids[0]).type(hash_of_integer).name('broken_family0')
+ mb = bindings.multibind(ids[0]).type(hash_of_integer.copy).name('broken_family0')
mb.producer_options(:conflict_resolution => :append)
- mb = bindings.multibind(ids[1]).type(hash_of_integer).name('broken_family1')
+ mb = bindings.multibind(ids[1]).type(hash_of_integer.copy).name('broken_family1')
mb.producer_options(:flatten => :true)
- mb = bindings.multibind(ids[2]).type(hash_of_integer).name('broken_family2')
+ mb = bindings.multibind(ids[2]).type(hash_of_integer.copy).name('broken_family2')
mb.producer_options(:uniq => :true)
injector = injector(binder.new(factory.layered_bindings(test_layer_with_bindings(bindings.model))))
diff --git a/spec/unit/pops/evaluator/access_ops_spec.rb b/spec/unit/pops/evaluator/access_ops_spec.rb
index d0965ad5d..0fa4779a0 100644
--- a/spec/unit/pops/evaluator/access_ops_spec.rb
+++ b/spec/unit/pops/evaluator/access_ops_spec.rb
@@ -236,7 +236,7 @@ describe 'Puppet::Pops::Evaluator::EvaluatorImpl/AccessOperator' do
it "Tuple parameterization gives an error if parameter is not a type" do
expr = fqr('Tuple')['String']
- expect { evaluate(expr)}.to raise_error(/Tuple-Type, Cannot use String where Abstract-Type is expected/)
+ expect { evaluate(expr)}.to raise_error(/Tuple-Type, Cannot use String where Any-Type is expected/)
end
it 'produces a varargs Tuple when the last two arguments specify size constraint' do
@@ -415,12 +415,12 @@ describe 'Puppet::Pops::Evaluator::EvaluatorImpl/AccessOperator' do
# Ruby Type
#
it 'creates a Ruby Type instance when applied to a Ruby Type' do
- type_expr = fqr('Ruby')['String']
+ type_expr = fqr('Runtime')['ruby','String']
tf = Puppet::Pops::Types::TypeFactory
expect(evaluate(type_expr)).to eql(tf.ruby_type('String'))
# arguments are flattened
- type_expr = fqr('Ruby')[['String']]
+ type_expr = fqr('Runtime')[['ruby', 'String']]
expect(evaluate(type_expr)).to eql(tf.ruby_type('String'))
end
diff --git a/spec/unit/pops/evaluator/comparison_ops_spec.rb b/spec/unit/pops/evaluator/comparison_ops_spec.rb
index 29938542e..e3564195c 100644
--- a/spec/unit/pops/evaluator/comparison_ops_spec.rb
+++ b/spec/unit/pops/evaluator/comparison_ops_spec.rb
@@ -88,10 +88,13 @@ describe 'Puppet::Pops::Evaluator::EvaluatorImpl' do
end
context "of booleans" do
- it "true == true == true" do; evaluate(literal(true) == literal(true)).should == true ; end;
- it "false == false == true" do; evaluate(literal(false) == literal(false)).should == true ; end;
- it "true == false != true" do; evaluate(literal(true) == literal(false)).should == false ; end;
- it "false == '' == false" do; evaluate(literal(false) == literal('')).should == false ; end;
+ it "true == true == true" do; evaluate(literal(true) == literal(true)).should == true ; end;
+ it "false == false == true" do; evaluate(literal(false) == literal(false)).should == true ; end;
+ it "true == false != true" do; evaluate(literal(true) == literal(false)).should == false ; end;
+ it "false == '' == false" do; evaluate(literal(false) == literal('')).should == false ; end;
+ it "undef == '' == false" do; evaluate(literal(:undef) == literal('')).should == false ; end;
+ it "undef == undef == true" do; evaluate(literal(:undef) == literal(:undef)).should == true ; end;
+ it "nil == undef == true" do; evaluate(literal(nil) == literal(:undef)).should == true ; end;
end
context "of collections" do
diff --git a/spec/unit/pops/evaluator/evaluating_parser_spec.rb b/spec/unit/pops/evaluator/evaluating_parser_spec.rb
index 210b55b20..5e80e4076 100644
--- a/spec/unit/pops/evaluator/evaluating_parser_spec.rb
+++ b/spec/unit/pops/evaluator/evaluating_parser_spec.rb
@@ -2,6 +2,7 @@ require 'spec_helper'
require 'puppet/pops'
require 'puppet/pops/evaluator/evaluator_impl'
+require 'puppet/loaders'
require 'puppet_spec/pops'
require 'puppet_spec/scope'
require 'puppet/parser/e4_parser_adapter'
@@ -16,18 +17,29 @@ describe 'Puppet::Pops::Evaluator::EvaluatorImpl' do
before(:each) do
Puppet[:strict_variables] = true
- # These must be set since the is 3x logic that triggers on these even if the tests are explicit
- # about selection of parser and evaluator
+ # These must be set since the 3x logic switches some behaviors on these even if the tests explicitly
+ # use the 4x parser and evaluator.
#
Puppet[:parser] = 'future'
- Puppet[:evaluator] = 'future'
+
# Puppetx cannot be loaded until the correct parser has been set (injector is turned off otherwise)
require 'puppetx'
+
+ # Tests needs a known configuration of node/scope/compiler since it parses and evaluates
+ # snippets as the compiler will evaluate them, butwithout the overhead of compiling a complete
+ # catalog for each tested expression.
+ #
+ @parser = Puppet::Pops::Parser::EvaluatingParser.new
+ @node = Puppet::Node.new('node.example.com')
+ @node.environment = Puppet::Node::Environment.create(:testing, [])
+ @compiler = Puppet::Parser::Compiler.new(@node)
+ @scope = Puppet::Parser::Scope.new(@compiler)
+ @scope.source = Puppet::Resource::Type.new(:node, 'node.example.com')
+ @scope.parent = @compiler.topscope
end
- let(:parser) { Puppet::Pops::Parser::EvaluatingParser::Transitional.new }
- let(:node) { 'node.example.com' }
- let(:scope) { s = create_test_scope_for_node(node); s }
+ let(:parser) { @parser }
+ let(:scope) { @scope }
types = Puppet::Pops::Types::TypeFactory
context "When evaluator evaluates literals" do
@@ -206,7 +218,7 @@ describe 'Puppet::Pops::Evaluator::EvaluatorImpl' do
"'a' !~ 'b.*'" => true,
'$x = a; a =~ "$x.*"' => true,
"a =~ Pattern['a.*']" => true,
- "a =~ Regexp['a.*']" => true,
+ "a =~ Regexp['a.*']" => false, # String is not subtype of Regexp. PUP-957
"$x = /a.*/ a =~ $x" => true,
"$x = Pattern['a.*'] a =~ $x" => true,
"1 =~ Integer" => true,
@@ -240,6 +252,7 @@ describe 'Puppet::Pops::Evaluator::EvaluatorImpl' do
"/ana/ in bananas" => true,
"/xxx/ in bananas" => false,
"ANA in bananas" => false, # ANA is a type, not a String
+ "String[1] in bananas" => false, # Philosophically true though :-)
"'ANA' in bananas" => true,
"ana in 'BANANAS'" => true,
"/ana/ in 'BANANAS'" => false,
@@ -278,7 +291,20 @@ describe 'Puppet::Pops::Evaluator::EvaluatorImpl' do
end
{
- 'Object' => ['Data', 'Scalar', 'Numeric', 'Integer', 'Float', 'Boolean', 'String', 'Pattern', 'Collection',
+ "if /(ana)/ in bananas {$1}" => 'ana',
+ "if /(xyz)/ in bananas {$1} else {$1}" => nil,
+ "$a = bananas =~ /(ana)/; $b = /(xyz)/ in bananas; $1" => 'ana',
+ "$a = xyz =~ /(xyz)/; $b = /(ana)/ in bananas; $1" => 'ana',
+ "if /p/ in [pineapple, bananas] {$0}" => 'p',
+ "if /b/ in [pineapple, bananas] {$0}" => 'b',
+ }.each do |source, result|
+ it "sets match variables for a regexp search using in such that '#{source}' produces '#{result}'" do
+ parser.evaluate_string(scope, source, __FILE__).should == result
+ end
+ end
+
+ {
+ 'Any' => ['Data', 'Scalar', 'Numeric', 'Integer', 'Float', 'Boolean', 'String', 'Pattern', 'Collection',
'Array', 'Hash', 'CatalogEntry', 'Resource', 'Class', 'Undef', 'File', 'NotYetKnownResourceType'],
# Note, Data > Collection is false (so not included)
@@ -364,11 +390,21 @@ describe 'Puppet::Pops::Evaluator::EvaluatorImpl' do
context "on strings requiring boxing to Numeric" do
{
"'2' + '2'" => 4,
+ "'-2' + '2'" => 0,
+ "'- 2' + '2'" => 0,
+ '"-\t 2" + "2"' => 0,
+ "'+2' + '2'" => 4,
+ "'+ 2' + '2'" => 4,
"'2.2' + '2.2'" => 4.4,
+ "'-2.2' + '2.2'" => 0.0,
"'0xF7' + '010'" => 0xFF,
"'0xF7' + '0x8'" => 0xFF,
"'0367' + '010'" => 0xFF,
"'012.3' + '010'" => 20.3,
+ "'-0x2' + '0x4'" => 2,
+ "'+0x2' + '0x4'" => 6,
+ "'-02' + '04'" => 2,
+ "'+02' + '04'" => 6,
}.each do |source, result|
it "should parse and evaluate the expression '#{source}' to #{result}" do
parser.evaluate_string(scope, source, __FILE__).should == result
@@ -380,6 +416,10 @@ describe 'Puppet::Pops::Evaluator::EvaluatorImpl' do
"'0xWTF' + '010'" => :error,
"'0x12.3' + '010'" => :error,
"'0x12.3' + '010'" => :error,
+ '"-\n 2" + "2"' => :error,
+ '"-\v 2" + "2"' => :error,
+ '"-2\n" + "2"' => :error,
+ '"-2\n " + "2"' => :error,
}.each do |source, result|
it "should parse and raise error for '#{source}'" do
expect { parser.evaluate_string(scope, source, __FILE__) }.to raise_error(Puppet::ParseError)
@@ -395,8 +435,6 @@ describe 'Puppet::Pops::Evaluator::EvaluatorImpl' do
"$a = 5; $a" => 5,
"$a = 5; $b = 6; $a" => 5,
"$a = $b = 5; $a == $b" => true,
- "$a = [1,2,3]; [x].map |$x| { $a += x; $a }" => [[1,2,3,'x']],
- "$a = [a,x,c]; [x].map |$x| { $a -= x; $a }" => [['a','c']],
}.each do |source, result|
it "should parse and evaluate the expression '#{source}' to #{result}" do
parser.evaluate_string(scope, source, __FILE__).should == result
@@ -404,13 +442,11 @@ describe 'Puppet::Pops::Evaluator::EvaluatorImpl' do
end
{
- "[a,b,c] = [1,2,3]; $a == 1 and $b == 2 and $c == 3" => :error,
- "[a,b,c] = {b=>2,c=>3,a=>1}; $a == 1 and $b == 2 and $c == 3" => :error,
- "$a = [1,2,3]; [x].collect |$x| { [a] += x; $a }" => :error,
- "$a = [a,x,c]; [x].collect |$x| { [a] -= x; $a }" => :error,
+ "[a,b,c] = [1,2,3]" => /attempt to assign to 'an Array Expression'/,
+ "[a,b,c] = {b=>2,c=>3,a=>1}" => /attempt to assign to 'an Array Expression'/,
}.each do |source, result|
- it "should parse and evaluate the expression '#{source}' to #{result}" do
- expect { parser.evaluate_string(scope, source, __FILE__)}.to raise_error(Puppet::ParseError)
+ it "should parse and evaluate the expression '#{source}' to error with #{result}" do
+ expect { parser.evaluate_string(scope, source, __FILE__)}.to raise_error(Puppet::ParseError, result)
end
end
end
@@ -458,6 +494,9 @@ describe 'Puppet::Pops::Evaluator::EvaluatorImpl' do
"case Integer {
Integer : { no }
Type[Integer] : { yes } }" => 'yes',
+ # supports unfold
+ "case ringo {
+ *[paul, john, ringo, george] : { 'beatle' } }" => 'beatle',
}.each do |source, result|
it "should parse and evaluate the expression '#{source}' to #{result}" do
@@ -467,16 +506,42 @@ describe 'Puppet::Pops::Evaluator::EvaluatorImpl' do
{
"2 ? { 1 => no, 2 => yes}" => 'yes',
- "3 ? { 1 => no, 2 => no}" => nil,
"3 ? { 1 => no, 2 => no, default => yes }" => 'yes',
- "3 ? { 1 => no, default => yes, 3 => no }" => 'yes',
+ "3 ? { 1 => no, default => yes, 3 => no }" => 'no',
+ "3 ? { 1 => no, 3 => no, default => yes }" => 'no',
+ "4 ? { 1 => no, default => yes, 3 => no }" => 'yes',
+ "4 ? { 1 => no, 3 => no, default => yes }" => 'yes',
"'banana' ? { /.*(ana).*/ => $1 }" => 'ana',
"[2] ? { Array[String] => yes, Array => yes}" => 'yes',
+ "ringo ? *[paul, john, ringo, george] => 'beatle'" => 'beatle',
}.each do |source, result|
it "should parse and evaluate the expression '#{source}' to #{result}" do
parser.evaluate_string(scope, source, __FILE__).should == result
end
end
+
+ it 'fails if a selector does not match' do
+ expect{parser.evaluate_string(scope, "2 ? 3 => 4")}.to raise_error(/No matching entry for selector parameter with value '2'/)
+ end
+ end
+
+ context "When evaluator evaluated unfold" do
+ {
+ "*[1,2,3]" => [1,2,3],
+ "*1" => [1],
+ "*'a'" => ['a']
+ }.each do |source, result|
+ it "should parse and evaluate the expression '#{source}' to #{result}" do
+ parser.evaluate_string(scope, source, __FILE__).should == result
+ end
+ end
+
+ it "should parse and evaluate the expression '*{a=>10, b=>20} to [['a',10],['b',20]]" do
+ result = parser.evaluate_string(scope, '*{a=>10, b=>20}', __FILE__)
+ expect(result).to include(['a', 10])
+ expect(result).to include(['b', 20])
+ end
+
end
context "When evaluator performs [] operations" do
@@ -501,6 +566,8 @@ describe 'Puppet::Pops::Evaluator::EvaluatorImpl' do
"[1,2,3,4][-5,-3]" => [1,2],
"[1,2,3,4][-6,-3]" => [1,2],
"[1,2,3,4][2,-3]" => [],
+ "[1,*[2,3],4]" => [1,2,3,4],
+ "[1,*[2,3],4][1]" => 2,
}.each do |source, result|
it "should parse and evaluate the expression '#{source}' to #{result}" do
parser.evaluate_string(scope, source, __FILE__).should == result
@@ -651,24 +718,82 @@ describe 'Puppet::Pops::Evaluator::EvaluatorImpl' do
adapted_parser = Puppet::Parser::E4ParserAdapter.new
adapted_parser.file = __FILE__
ast = adapted_parser.parse(source)
- scope.known_resource_types.import_ast(ast, '')
- ast.code.safeevaluate(scope).should == 'chocolate'
+ Puppet.override({:global_scope => scope}, "test") do
+ scope.known_resource_types.import_ast(ast, '')
+ ast.code.safeevaluate(scope).should == 'chocolate'
+ end
end
# Resource default and override expressions and resource parameter access with []
{
+ # Properties
"notify { id: message=>explicit} Notify[id][message]" => "explicit",
"Notify { message=>by_default} notify {foo:} Notify[foo][message]" => "by_default",
"notify {foo:} Notify[foo]{message =>by_override} Notify[foo][message]" => "by_override",
+ # Parameters
+ "notify { id: withpath=>explicit} Notify[id][withpath]" => "explicit",
+ "Notify { withpath=>by_default } notify { foo: } Notify[foo][withpath]" => "by_default",
+ "notify {foo:}
+ Notify[foo]{withpath=>by_override}
+ Notify[foo][withpath]" => "by_override",
+ # Metaparameters
"notify { foo: tag => evoe} Notify[foo][tag]" => "evoe",
- # Does not produce the defaults for tag
+ # Does not produce the defaults for tag parameter (title, type or names of scopes)
"notify { foo: } Notify[foo][tag]" => nil,
+ # But a default may be specified on the type
+ "Notify { tag=>by_default } notify { foo: } Notify[foo][tag]" => "by_default",
+ "Notify { tag=>by_default }
+ notify { foo: }
+ Notify[foo]{ tag=>by_override }
+ Notify[foo][tag]" => "by_override",
}.each do |source, result|
it "should parse and evaluate the expression '#{source}' to #{result}" do
parser.evaluate_string(scope, source, __FILE__).should == result
end
end
+ # Virtual and realized resource default and overridden resource parameter access with []
+ {
+ # Properties
+ "@notify { id: message=>explicit } Notify[id][message]" => "explicit",
+ "@notify { id: message=>explicit }
+ realize Notify[id]
+ Notify[id][message]" => "explicit",
+ "Notify { message=>by_default } @notify { id: } Notify[id][message]" => "by_default",
+ "Notify { message=>by_default }
+ @notify { id: tag=>thisone }
+ Notify <| tag == thisone |>;
+ Notify[id][message]" => "by_default",
+ "@notify { id: } Notify[id]{message=>by_override} Notify[id][message]" => "by_override",
+ # Parameters
+ "@notify { id: withpath=>explicit } Notify[id][withpath]" => "explicit",
+ "Notify { withpath=>by_default }
+ @notify { id: }
+ Notify[id][withpath]" => "by_default",
+ "@notify { id: }
+ realize Notify[id]
+ Notify[id]{withpath=>by_override}
+ Notify[id][withpath]" => "by_override",
+ # Metaparameters
+ "@notify { id: tag=>explicit } Notify[id][tag]" => "explicit",
+ }.each do |source, result|
+ it "parses and evaluates virtual and realized resources in the expression '#{source}' to #{result}" do
+ expect(parser.evaluate_string(scope, source, __FILE__)).to eq(result)
+ end
+ end
+
+ # Exported resource attributes
+ {
+ "@@notify { id: message=>explicit } Notify[id][message]" => "explicit",
+ "@@notify { id: message=>explicit, tag=>thisone }
+ Notify <<| tag == thisone |>>
+ Notify[id][message]" => "explicit",
+ }.each do |source, result|
+ it "parses and evaluates exported resources in the expression '#{source}' to #{result}" do
+ expect(parser.evaluate_string(scope, source, __FILE__)).to eq(result)
+ end
+ end
+
# Resource default and override expressions and resource parameter access error conditions
{
"notify { xid: message=>explicit} Notify[id][message]" => /Resource not found/,
@@ -676,6 +801,7 @@ describe 'Puppet::Pops::Evaluator::EvaluatorImpl' do
# NOTE: these meta-esque parameters are not recognized as such
"notify { id: message=>explicit} Notify[id][title]" => /does not have a parameter called 'title'/,
"notify { id: message=>explicit} Notify[id]['type']" => /does not have a parameter called 'type'/,
+ "notify { id: message=>explicit } Notify[id]{message=>override}" => /'message' is already set on Notify\[id\]/
}.each do |source, result|
it "should parse '#{source}' and raise error matching #{result}" do
expect { parser.evaluate_string(scope, source, __FILE__)}.to raise_error(result)
@@ -713,7 +839,7 @@ describe 'Puppet::Pops::Evaluator::EvaluatorImpl' do
"!! true" => true,
"!! false" => false,
"! 'x'" => false,
- "! ''" => true,
+ "! ''" => false,
"! undef" => true,
"! [a]" => false,
"! []" => false,
@@ -744,12 +870,15 @@ describe 'Puppet::Pops::Evaluator::EvaluatorImpl' do
end
context "When evaluator performs calls" do
+
let(:populate) do
parser.evaluate_string(scope, "$a = 10 $b = [1,2,3]")
end
{
'sprintf( "x%iy", $a )' => "x10y",
+ # unfolds
+ 'sprintf( *["x%iy", $a] )' => "x10y",
'"x%iy".sprintf( $a )' => "x10y",
'$b.reduce |$memo,$x| { $memo + $x }' => 6,
'reduce($b) |$memo,$x| { $memo + $x }' => 6,
@@ -771,6 +900,69 @@ describe 'Puppet::Pops::Evaluator::EvaluatorImpl' do
it "provides location information on error in unparenthesized call logic" do
expect{parser.evaluate_string(scope, "include non_existing_class", __FILE__)}.to raise_error(Puppet::ParseError, /line 1\:1/)
end
+
+ it 'defaults can be given in a lambda and used only when arg is missing' do
+ env_loader = @compiler.loaders.public_environment_loader
+ fc = Puppet::Functions.create_function(:test) do
+ dispatch :test do
+ param 'Integer', 'count'
+ required_block_param
+ end
+ def test(count, block)
+ block.call({}, *[].fill(10, 0, count))
+ end
+ end
+ the_func = fc.new({}, env_loader)
+ env_loader.add_entry(:function, 'test', the_func, __FILE__)
+ expect(parser.evaluate_string(scope, "test(1) |$x, $y=20| { $x + $y}")).to eql(30)
+ expect(parser.evaluate_string(scope, "test(2) |$x, $y=20| { $x + $y}")).to eql(20)
+ end
+
+ it 'a given undef does not select the default value' do
+ env_loader = @compiler.loaders.public_environment_loader
+ fc = Puppet::Functions.create_function(:test) do
+ dispatch :test do
+ param 'Any', 'lambda_arg'
+ required_block_param
+ end
+ def test(lambda_arg, block)
+ block.call({}, lambda_arg)
+ end
+ end
+ the_func = fc.new({}, env_loader)
+ env_loader.add_entry(:function, 'test', the_func, __FILE__)
+
+ expect(parser.evaluate_string(scope, "test(undef) |$x=20| { $x == undef}")).to eql(true)
+ end
+
+ it 'a given undef is given as nil' do
+ env_loader = @compiler.loaders.public_environment_loader
+ fc = Puppet::Functions.create_function(:assert_no_undef) do
+ dispatch :assert_no_undef do
+ param 'Any', 'x'
+ end
+
+ def assert_no_undef(x)
+ case x
+ when Array
+ return unless x.include?(:undef)
+ when Hash
+ return unless x.keys.include?(:undef) || x.values.include?(:undef)
+ else
+ return unless x == :undef
+ end
+ raise "contains :undef"
+ end
+ end
+
+ the_func = fc.new({}, env_loader)
+ env_loader.add_entry(:function, 'assert_no_undef', the_func, __FILE__)
+
+ expect{parser.evaluate_string(scope, "assert_no_undef(undef)")}.to_not raise_error()
+ expect{parser.evaluate_string(scope, "assert_no_undef([undef])")}.to_not raise_error()
+ expect{parser.evaluate_string(scope, "assert_no_undef({undef => 1})")}.to_not raise_error()
+ expect{parser.evaluate_string(scope, "assert_no_undef({1 => undef})")}.to_not raise_error()
+ end
end
context "When evaluator performs string interpolation" do
@@ -971,7 +1163,7 @@ describe 'Puppet::Pops::Evaluator::EvaluatorImpl' do
expect { parser.evaluate_string(scope, src)}.to raise_error(/Cannot parse invalid JSON string/)
end
- it "parses interpolated heredoc epression" do
+ it "parses interpolated heredoc expression" do
src = <<-CODE
$name = 'Fjodor'
@("END")
@@ -983,12 +1175,6 @@ describe 'Puppet::Pops::Evaluator::EvaluatorImpl' do
end
context "Handles Deprecations and Discontinuations" do
- around(:each) do |example|
- Puppet.override({:loaders => Puppet::Pops::Loaders.new(Puppet::Node::Environment.create(:testing, []))}, 'test') do
- example.run
- end
- end
-
it 'of import statements' do
source = "\nimport foo"
# Error references position 5 at the opening '{'
@@ -1002,7 +1188,8 @@ describe 'Puppet::Pops::Evaluator::EvaluatorImpl' do
source = '1+1 { "title": }'
# Error references position 5 at the opening '{'
# Set file to nil to make it easier to match with line number (no file name in output)
- expect { parser.parse_string(source, nil) }.to raise_error(/Expression is not valid as a resource.*line 1:5/)
+ expect { parser.evaluate_string(scope, source) }.to raise_error(
+ /Illegal Resource Type expression, expected result to be a type name, or untitled Resource.*line 1:2/)
end
it 'for non r-value producing <| |>' do
@@ -1058,6 +1245,39 @@ describe 'Puppet::Pops::Evaluator::EvaluatorImpl' do
end
+ context 'does not leak variables' do
+ it 'local variables are gone when lambda ends' do
+ source = <<-SOURCE
+ [1,2,3].each |$x| { $y = $x}
+ $a = $y
+ SOURCE
+ expect do
+ parser.evaluate_string(scope, source)
+ end.to raise_error(/Unknown variable: 'y'/)
+ end
+
+ it 'lambda parameters are gone when lambda ends' do
+ source = <<-SOURCE
+ [1,2,3].each |$x| { $y = $x}
+ $a = $x
+ SOURCE
+ expect do
+ parser.evaluate_string(scope, source)
+ end.to raise_error(/Unknown variable: 'x'/)
+ end
+
+ it 'does not leak match variables' do
+ source = <<-SOURCE
+ if 'xyz' =~ /(x)(y)(z)/ { notice $2 }
+ case 'abc' {
+ /(a)(b)(c)/ : { $x = $2 }
+ }
+ "-$x-$2-"
+ SOURCE
+ expect(parser.evaluate_string(scope, source)).to eq('-b--')
+ end
+ end
+
matcher :have_relationship do |expected|
calc = Puppet::Pops::Types::TypeCalculator.new
diff --git a/spec/unit/pops/evaluator/logical_ops_spec.rb b/spec/unit/pops/evaluator/logical_ops_spec.rb
index e5cdd1f93..d6a179e03 100644
--- a/spec/unit/pops/evaluator/logical_ops_spec.rb
+++ b/spec/unit/pops/evaluator/logical_ops_spec.rb
@@ -63,8 +63,8 @@ describe 'Puppet::Pops::Evaluator::EvaluatorImpl' do
evaluate(literal('x').not()).should == false
end
- it "'' == false" do
- evaluate(literal('').not()).should == true
+ it "'' == true" do
+ evaluate(literal('').not()).should == false
end
it ":undef == false" do
diff --git a/spec/unit/pops/evaluator/variables_spec.rb b/spec/unit/pops/evaluator/variables_spec.rb
index fe93842c4..6c1e9f821 100644
--- a/spec/unit/pops/evaluator/variables_spec.rb
+++ b/spec/unit/pops/evaluator/variables_spec.rb
@@ -49,111 +49,6 @@ describe 'Puppet::Pops::Impl::EvaluatorImpl' do
expect { evaluate_l(block(var('a').set(10), var('a').set(20))) }.to raise_error(/Cannot reassign variable a/)
end
- context "-= operations" do
- # Also see collections_ops_spec.rb where delete via - is fully tested, here only the
- # the -= operation itself is tested (there are many combinations)
- #
- it 'deleting from non existing value produces :undef, nil -= ?' do
- top_scope_block = var('b').set([1,2,3])
- local_scope_block = block(var('a').minus_set([4]), fqn('a').var)
- evaluate_l(top_scope_block, local_scope_block).should == :undef
- end
-
- it 'deletes from a list' do
- top_scope_block = var('a').set([1,2,3])
- local_scope_block = block(var('a').minus_set([2]), fqn('a').var())
- evaluate_l(top_scope_block, local_scope_block).should == [1,3]
- end
-
- it 'deletes from a hash' do
- top_scope_block = var('a').set({'a'=>1,'b'=>2,'c'=>3})
- local_scope_block = block(var('a').minus_set('b'), fqn('a').var())
- evaluate_l(top_scope_block, local_scope_block).should == {'a'=>1,'c'=>3}
- end
- end
-
- context "+= operations" do
- # Also see collections_ops_spec.rb where concatenation via + is fully tested
- it "appending to non existing value, nil += []" do
- top_scope_block = var('b').set([1,2,3])
- local_scope_block = var('a').plus_set([4])
- evaluate_l(top_scope_block, local_scope_block).should == [4]
- end
-
- context "appending to list" do
- it "from list, [] += []" do
- top_scope_block = var('a').set([1,2,3])
- local_scope_block = block(var('a').plus_set([4]), fqn('a').var())
- evaluate_l(top_scope_block, local_scope_block).should == [1,2,3,4]
- end
-
- it "from hash, [] += {a=>b}" do
- top_scope_block = var('a').set([1,2,3])
- local_scope_block = block(var('a').plus_set({'a' => 1, 'b'=>2}), fqn('a').var())
- evaluate_l(top_scope_block, local_scope_block).should satisfy {|result|
- # hash in 1.8.7 is not insertion order preserving, hence this hoop
- result == [1,2,3,['a',1],['b',2]] || result == [1,2,3,['b',2],['a',1]]
- }
- end
-
- it "from single value, [] += x" do
- top_scope_block = var('a').set([1,2,3])
- local_scope_block = block(var('a').plus_set(4), fqn('a').var())
- evaluate_l(top_scope_block, local_scope_block).should == [1,2,3,4]
- end
-
- it "from embedded list, [] += [[x]]" do
- top_scope_block = var('a').set([1,2,3])
- local_scope_block = block(var('a').plus_set([[4,5]]), fqn('a').var())
- evaluate_l(top_scope_block, local_scope_block).should == [1,2,3,[4,5]]
- end
- end
-
- context "appending to hash" do
- it "from hash, {a=>b} += {x=>y}" do
- top_scope_block = var('a').set({'a' => 1, 'b' => 2})
- local_scope_block = block(var('a').plus_set({'c' => 3}), fqn('a').var())
- evaluate_l(top_scope_block, local_scope_block) do |scope|
- # Assert no change to top scope hash
- scope['a'].should == {'a' =>1, 'b'=> 2}
- end.should == {'a' => 1, 'b' => 2, 'c' => 3}
- end
-
- it "from list, {a=>b} += ['x', y]" do
- top_scope_block = var('a').set({'a' => 1, 'b' => 2})
- local_scope_block = block(var('a').plus_set(['c', 3]), fqn('a').var())
- evaluate_l(top_scope_block, local_scope_block) do |scope|
- # Assert no change to top scope hash
- scope['a'].should == {'a' =>1, 'b'=> 2}
- end.should == {'a' => 1, 'b' => 2, 'c' => 3}
- end
-
- it "with overwrite from hash, {a=>b} += {a=>c}" do
- top_scope_block = var('a').set({'a' => 1, 'b' => 2})
- local_scope_block = block(var('a').plus_set({'b' => 4, 'c' => 3}),fqn('a').var())
- evaluate_l(top_scope_block, local_scope_block) do |scope|
- # Assert no change to top scope hash
- scope['a'].should == {'a' =>1, 'b'=> 2}
- end.should == {'a' => 1, 'b' => 4, 'c' => 3}
- end
-
- it "with overwrite from list, {a=>b} += ['a', c]" do
- top_scope_block = var('a').set({'a' => 1, 'b' => 2})
- local_scope_block = block(var('a').plus_set(['b', 4, 'c', 3]), fqn('a').var())
- evaluate_l(top_scope_block, local_scope_block) do |scope|
- # Assert no change to topscope hash
- scope['a'].should == {'a' =>1, 'b'=> 2}
- end.should == {'a' => 1, 'b' => 4, 'c' => 3}
- end
-
- it "from odd length array - error" do
- top_scope_block = var('a').set({'a' => 1, 'b' => 2})
- local_scope_block = var('a').plus_set(['b', 4, 'c'])
- expect { evaluate_l(top_scope_block, local_scope_block) }.to raise_error(/Append assignment \+= failed with error: odd number of arguments for Hash/)
- end
- end
- end
-
context "access to numeric variables" do
it "without a match" do
evaluate_l(block(literal(2) + literal(2),
diff --git a/spec/unit/pops/issues_spec.rb b/spec/unit/pops/issues_spec.rb
index 93f093d61..82dce1b44 100644
--- a/spec/unit/pops/issues_spec.rb
+++ b/spec/unit/pops/issues_spec.rb
@@ -23,4 +23,174 @@ describe "Puppet::Pops::Issues" do
x = Puppet::Pops::Issues::NOT_TOP_LEVEL
x.format().should == "Classes, definitions, and nodes may only appear at toplevel or inside other classes"
end
+
+end
+
+describe "Puppet::Pops::IssueReporter" do
+
+ let(:acceptor) { Puppet::Pops::Validation::Acceptor.new }
+
+ def fake_positioned(number)
+ stub("positioned_#{number}", :line => number, :pos => number)
+ end
+
+ def diagnostic(severity, number)
+ Puppet::Pops::Validation::Diagnostic.new(
+ severity,
+ Puppet::Pops::Issues::Issue.new(number) { "#{severity}#{number}" },
+ "#{severity}file",
+ fake_positioned(number))
+ end
+
+ def warning(number)
+ diagnostic(:warning, number)
+ end
+
+ def deprecation(number)
+ diagnostic(:deprecation, number)
+ end
+
+ def error(number)
+ diagnostic(:error, number)
+ end
+
+ context "given warnings" do
+
+ before(:each) do
+ acceptor.accept( warning(1) )
+ acceptor.accept( deprecation(1) )
+ end
+
+ it "emits warnings if told to emit them" do
+ Puppet.expects(:warning).twice.with(regexp_matches(/warning1|deprecation1/))
+ Puppet::Pops::IssueReporter.assert_and_report(acceptor, { :emit_warnings => true })
+ end
+
+ it "does not emit warnings if not told to emit them" do
+ Puppet.expects(:warning).never
+ Puppet::Pops::IssueReporter.assert_and_report(acceptor, {})
+ end
+
+ it "emits no warnings if :max_warnings is 0" do
+ acceptor.accept( warning(2) )
+ Puppet[:max_warnings] = 0
+ Puppet.expects(:warning).once.with(regexp_matches(/deprecation1/))
+ Puppet::Pops::IssueReporter.assert_and_report(acceptor, { :emit_warnings => true })
+ end
+
+ it "emits no more than 1 warning if :max_warnings is 1" do
+ acceptor.accept( warning(2) )
+ acceptor.accept( warning(3) )
+ Puppet[:max_warnings] = 1
+ Puppet.expects(:warning).twice.with(regexp_matches(/warning1|deprecation1/))
+ Puppet::Pops::IssueReporter.assert_and_report(acceptor, { :emit_warnings => true })
+ end
+
+ it "does not emit more deprecations warnings than the max deprecation warnings" do
+ acceptor.accept( deprecation(2) )
+ Puppet[:max_deprecations] = 0
+ Puppet.expects(:warning).once.with(regexp_matches(/warning1/))
+ Puppet::Pops::IssueReporter.assert_and_report(acceptor, { :emit_warnings => true })
+ end
+
+ it "does not emit deprecation warnings, but does emit regular warnings if disable_warnings includes deprecations" do
+ Puppet[:disable_warnings] = 'deprecations'
+ Puppet.expects(:warning).once.with(regexp_matches(/warning1/))
+ Puppet::Pops::IssueReporter.assert_and_report(acceptor, { :emit_warnings => true })
+ end
+ end
+
+ context "given errors" do
+ it "logs nothing, but raises the given :message if :emit_errors is repressing error logging" do
+ acceptor.accept( error(1) )
+ Puppet.expects(:err).never
+ expect do
+ Puppet::Pops::IssueReporter.assert_and_report(acceptor, { :emit_errors => false, :message => 'special'})
+ end.to raise_error(Puppet::ParseError, 'special')
+ end
+
+ it "prefixes :message if a single error is raised" do
+ acceptor.accept( error(1) )
+ Puppet.expects(:err).never
+ expect do
+ Puppet::Pops::IssueReporter.assert_and_report(acceptor, { :message => 'special'})
+ end.to raise_error(Puppet::ParseError, /special error1/)
+ end
+
+ it "logs nothing and raises immediately if there is only one error" do
+ acceptor.accept( error(1) )
+ Puppet.expects(:err).never
+ expect do
+ Puppet::Pops::IssueReporter.assert_and_report(acceptor, { })
+ end.to raise_error(Puppet::ParseError, /error1/)
+ end
+
+ it "logs nothing and raises immediately if there are multiple errors but max_errors is 0" do
+ acceptor.accept( error(1) )
+ acceptor.accept( error(2) )
+ Puppet[:max_errors] = 0
+ Puppet.expects(:err).never
+ expect do
+ Puppet::Pops::IssueReporter.assert_and_report(acceptor, { })
+ end.to raise_error(Puppet::ParseError, /error1/)
+ end
+
+ it "logs the :message if there is more than one allowed error" do
+ acceptor.accept( error(1) )
+ acceptor.accept( error(2) )
+ Puppet.expects(:err).times(3).with(regexp_matches(/error1|error2|special/))
+ expect do
+ Puppet::Pops::IssueReporter.assert_and_report(acceptor, { :message => 'special'})
+ end.to raise_error(Puppet::ParseError, /Giving up/)
+ end
+
+ it "emits accumulated errors before raising a 'giving up' message if there are more errors than allowed" do
+ acceptor.accept( error(1) )
+ acceptor.accept( error(2) )
+ acceptor.accept( error(3) )
+ Puppet[:max_errors] = 2
+ Puppet.expects(:err).times(2).with(regexp_matches(/error1|error2/))
+ expect do
+ Puppet::Pops::IssueReporter.assert_and_report(acceptor, { })
+ end.to raise_error(Puppet::ParseError, /3 errors.*Giving up/)
+ end
+
+ it "emits accumulated errors before raising a 'giving up' message if there are multiple errors but fewer than limits" do
+ acceptor.accept( error(1) )
+ acceptor.accept( error(2) )
+ acceptor.accept( error(3) )
+ Puppet[:max_errors] = 4
+ Puppet.expects(:err).times(3).with(regexp_matches(/error[123]/))
+ expect do
+ Puppet::Pops::IssueReporter.assert_and_report(acceptor, { })
+ end.to raise_error(Puppet::ParseError, /3 errors.*Giving up/)
+ end
+
+ it "emits errors regardless of disable_warnings setting" do
+ acceptor.accept( error(1) )
+ acceptor.accept( error(2) )
+ Puppet[:disable_warnings] = 'deprecations'
+ Puppet.expects(:err).times(2).with(regexp_matches(/error1|error2/))
+ expect do
+ Puppet::Pops::IssueReporter.assert_and_report(acceptor, { })
+ end.to raise_error(Puppet::ParseError, /Giving up/)
+ end
+ end
+
+ context "given both" do
+
+ it "logs warnings and errors" do
+ acceptor.accept( warning(1) )
+ acceptor.accept( error(1) )
+ acceptor.accept( error(2) )
+ acceptor.accept( error(3) )
+ acceptor.accept( deprecation(1) )
+ Puppet[:max_errors] = 2
+ Puppet.expects(:warning).twice.with(regexp_matches(/warning1|deprecation1/))
+ Puppet.expects(:err).times(2).with(regexp_matches(/error[123]/))
+ expect do
+ Puppet::Pops::IssueReporter.assert_and_report(acceptor, { :emit_warnings => true })
+ end.to raise_error(Puppet::ParseError, /3 errors.*2 warnings.*Giving up/)
+ end
+ end
end
diff --git a/spec/unit/pops/loaders/dependency_loader_spec.rb b/spec/unit/pops/loaders/dependency_loader_spec.rb
index dbea5b208..cbdefe897 100644
--- a/spec/unit/pops/loaders/dependency_loader_spec.rb
+++ b/spec/unit/pops/loaders/dependency_loader_spec.rb
@@ -36,6 +36,23 @@ describe 'dependency loader' do
expect(function.class.name).to eq('testmodule::foo')
expect(function.is_a?(Puppet::Functions::Function)).to eq(true)
end
+
+ it 'can load something in a qualified name space more than once' do
+ module_dir = dir_containing('testmodule', {
+ 'lib' => { 'puppet' => { 'functions' => { 'testmodule' => {
+ 'foo.rb' => 'Puppet::Functions.create_function("testmodule::foo") { def foo; end; }'
+ }}}}})
+ module_loader = Puppet::Pops::Loader::ModuleLoaders::FileBased.new(static_loader, loaders, 'testmodule', module_dir, 'test1')
+ dep_loader = Puppet::Pops::Loader::DependencyLoader.new(static_loader, 'test-dep', [module_loader])
+
+ function = dep_loader.load_typed(typed_name(:function, 'testmodule::foo')).value
+ expect(function.class.name).to eq('testmodule::foo')
+ expect(function.is_a?(Puppet::Functions::Function)).to eq(true)
+
+ function = dep_loader.load_typed(typed_name(:function, 'testmodule::foo')).value
+ expect(function.class.name).to eq('testmodule::foo')
+ expect(function.is_a?(Puppet::Functions::Function)).to eq(true)
+ end
end
def typed_name(type, name)
diff --git a/spec/unit/pops/loaders/loader_paths_spec.rb b/spec/unit/pops/loaders/loader_paths_spec.rb
index 2929fe7f8..2cd565a8c 100644
--- a/spec/unit/pops/loaders/loader_paths_spec.rb
+++ b/spec/unit/pops/loaders/loader_paths_spec.rb
@@ -5,7 +5,6 @@ require 'puppet/loaders'
describe 'loader paths' do
include PuppetSpec::Files
- before(:each) { Puppet[:biff] = true }
let(:static_loader) { Puppet::Pops::Loader::StaticLoader.new() }
let(:unused_loaders) { nil }
@@ -17,17 +16,13 @@ describe 'loader paths' do
'lib' => {
'puppet' => {
'functions' => {},
- 'parser' => {
- 'functions' => {},
- }
}}})
module_loader = Puppet::Pops::Loader::ModuleLoaders::FileBased.new(static_loader, unused_loaders, 'testmodule', module_dir, 'test1')
effective_paths = Puppet::Pops::Loader::LoaderPaths.relative_paths_for_type(:function, module_loader)
expect(effective_paths.collect(&:generic_path)).to eq([
- File.join(module_dir, 'lib', 'puppet', 'functions'), # 4x functions
- File.join(module_dir, 'lib', 'puppet','parser', 'functions') # 3x functions
+ File.join(module_dir, 'lib', 'puppet', 'functions')
])
end
@@ -39,8 +34,6 @@ describe 'loader paths' do
expect(effective_paths.size).to be_eql(1)
expect(effective_paths[0].generic_path).to be_eql(File.join(module_dir, 'lib', 'puppet', 'functions'))
- expect(module_loader.path_index.size).to be_eql(1)
- expect(module_loader.path_index.include?(File.join(module_dir, 'lib', 'puppet', 'functions', 'foo.rb'))).to be(true)
end
it 'all function smart-paths produces entries if they exist' do
@@ -48,19 +41,15 @@ describe 'loader paths' do
'lib' => {
'puppet' => {
'functions' => {'foo4x.rb' => 'ignored in this test'},
- 'parser' => {
- 'functions' => {'foo3x.rb' => 'ignored in this test'},
- }
}}})
module_loader = Puppet::Pops::Loader::ModuleLoaders::FileBased.new(static_loader, unused_loaders, 'testmodule', module_dir, 'test1')
effective_paths = module_loader.smart_paths.effective_paths(:function)
- expect(effective_paths.size).to eq(2)
- expect(module_loader.path_index.size).to eq(2)
+ expect(effective_paths.size).to eq(1)
+ expect(module_loader.path_index.size).to eq(1)
path_index = module_loader.path_index
- expect(path_index.include?(File.join(module_dir, 'lib', 'puppet', 'functions', 'foo4x.rb'))).to eq(true)
- expect(path_index.include?(File.join(module_dir, 'lib', 'puppet', 'parser', 'functions', 'foo3x.rb'))).to eq(true)
+ expect(path_index).to include(File.join(module_dir, 'lib', 'puppet', 'functions', 'foo4x.rb'))
end
end
end
diff --git a/spec/unit/pops/loaders/loaders_spec.rb b/spec/unit/pops/loaders/loaders_spec.rb
index daa9c716c..831236698 100644
--- a/spec/unit/pops/loaders/loaders_spec.rb
+++ b/spec/unit/pops/loaders/loaders_spec.rb
@@ -4,6 +4,25 @@ require 'puppet_spec/files'
require 'puppet/pops'
require 'puppet/loaders'
+describe 'loader helper classes' do
+ it 'NamedEntry holds values and is frozen' do
+ ne = Puppet::Pops::Loader::Loader::NamedEntry.new('name', 'value', 'origin')
+ expect(ne.frozen?).to be_true
+ expect(ne.typed_name).to eql('name')
+ expect(ne.origin).to eq('origin')
+ expect(ne.value).to eq('value')
+ end
+
+ it 'TypedName holds values and is frozen' do
+ tn = Puppet::Pops::Loader::Loader::TypedName.new(:function, '::foo::bar')
+ expect(tn.frozen?).to be_true
+ expect(tn.type).to eq(:function)
+ expect(tn.name_parts).to eq(['foo', 'bar'])
+ expect(tn.name).to eq('foo::bar')
+ expect(tn.qualified).to be_true
+ end
+end
+
describe 'loaders' do
include PuppetSpec::Files
@@ -29,17 +48,6 @@ describe 'loaders' do
expect(loaders.private_environment_loader().to_s).to eql("(DependencyLoader 'environment' [])")
end
- it 'can load 3x system functions' do
- Puppet[:biff] = true
- loaders = Puppet::Pops::Loaders.new(empty_test_env)
- puppet_loader = loaders.puppet_system_loader()
-
- function = puppet_loader.load_typed(typed_name(:function, 'sprintf')).value
-
- expect(function.class.name).to eq('sprintf')
- expect(function).to be_a(Puppet::Functions::Function)
- end
-
it 'can load a function using a qualified or unqualified name from a module with metadata' do
loaders = Puppet::Pops::Loaders.new(environment_for(module_with_metadata))
modulea_loader = loaders.public_loader_for_module('modulea')
@@ -91,6 +99,18 @@ describe 'loaders' do
expect(function.call({})).to eql("usee::callee() was told 'passed value' + I am user::caller()")
end
+ it 'can load a function more than once from modules' do
+ env = environment_for(dependent_modules_with_metadata)
+ loaders = Puppet::Pops::Loaders.new(env)
+
+ moduleb_loader = loaders.private_loader_for_module('user')
+ function = moduleb_loader.load_typed(typed_name(:function, 'user::caller')).value
+ expect(function.call({})).to eql("usee::callee() was told 'passed value' + I am user::caller()")
+
+ function = moduleb_loader.load_typed(typed_name(:function, 'user::caller')).value
+ expect(function.call({})).to eql("usee::callee() was told 'passed value' + I am user::caller()")
+ end
+
def environment_for(*module_paths)
Puppet::Node::Environment.create(:'*test*', module_paths, '')
end
diff --git a/spec/unit/pops/loaders/module_loaders_spec.rb b/spec/unit/pops/loaders/module_loaders_spec.rb
index 9d0c1a86d..058e765de 100644
--- a/spec/unit/pops/loaders/module_loaders_spec.rb
+++ b/spec/unit/pops/loaders/module_loaders_spec.rb
@@ -84,35 +84,6 @@ describe 'FileBased module loader' do
expect(function.is_a?(Puppet::Functions::Function)).to eq(true)
end
- context 'when delegating 3x to 4x' do
- before(:each) { Puppet[:biff] = true }
-
- it 'can load a 3x function API ruby function in global name space' do
- module_dir = dir_containing('testmodule', {
- 'lib' => {
- 'puppet' => {
- 'parser' => {
- 'functions' => {
- 'foo3x.rb' => <<-CODE
- Puppet::Parser::Functions::newfunction(
- :foo3x, :type => :rvalue,
- :arity => 1
- ) do |args|
- args[0]
- end
- CODE
- }
- }
- }
- }})
-
- module_loader = Puppet::Pops::Loader::ModuleLoaders::FileBased.new(static_loader, loaders, 'testmodule', module_dir, 'test1')
- function = module_loader.load_typed(typed_name(:function, 'foo3x')).value
- expect(function.class.name).to eq('foo3x')
- expect(function.is_a?(Puppet::Functions::Function)).to eq(true)
- end
- end
-
def typed_name(type, name)
Puppet::Pops::Loader::Loader::TypedName.new(type, name)
end
diff --git a/spec/unit/pops/loaders/static_loader_spec.rb b/spec/unit/pops/loaders/static_loader_spec.rb
index e1a73273e..3d85f4522 100644
--- a/spec/unit/pops/loaders/static_loader_spec.rb
+++ b/spec/unit/pops/loaders/static_loader_spec.rb
@@ -37,6 +37,12 @@ describe 'the static loader' do
it "uses the evaluator to format output" do
expect(loader.load(:function, level).call({}, ['yay', 'surprise']).to_s).to eql('[yay, surprise]')
end
+
+ it 'outputs name of source (scope) by passing it to the Log utility' do
+ the_scope = {}
+ Puppet::Util::Log.any_instance.expects(:source=).with(the_scope)
+ loader.load(:function, level).call(the_scope, 'x')
+ end
end
end
diff --git a/spec/unit/pops/parser/epp_parser_spec.rb b/spec/unit/pops/parser/epp_parser_spec.rb
index 0db4ba7d9..fb32b9ba4 100644
--- a/spec/unit/pops/parser/epp_parser_spec.rb
+++ b/spec/unit/pops/parser/epp_parser_spec.rb
@@ -51,36 +51,65 @@ describe "epp parser" do
context "handles parsing of" do
it "text (and nothing else)" do
- dump(parse("Hello World")).should == "(lambda (epp (block (render-s 'Hello World'))))"
+ dump(parse("Hello World")).should == [
+ "(lambda (epp (block",
+ " (render-s 'Hello World')",
+ ")))"].join("\n")
end
it "template parameters" do
- dump(parse("<%|$x|%>Hello World")).should == "(lambda (parameters x) (epp (block (render-s 'Hello World'))))"
+ dump(parse("<%|$x|%>Hello World")).should == [
+ "(lambda (parameters x) (epp (block",
+ " (render-s 'Hello World')",
+ ")))"].join("\n")
end
it "template parameters with default" do
- dump(parse("<%|$x='cigar'|%>Hello World")).should == "(lambda (parameters (= x 'cigar')) (epp (block (render-s 'Hello World'))))"
+ dump(parse("<%|$x='cigar'|%>Hello World")).should == [
+ "(lambda (parameters (= x 'cigar')) (epp (block",
+ " (render-s 'Hello World')",
+ ")))"].join("\n")
end
it "template parameters with and without default" do
- dump(parse("<%|$x='cigar', $y|%>Hello World")).should == "(lambda (parameters (= x 'cigar') y) (epp (block (render-s 'Hello World'))))"
+ dump(parse("<%|$x='cigar', $y|%>Hello World")).should == [
+ "(lambda (parameters (= x 'cigar') y) (epp (block",
+ " (render-s 'Hello World')",
+ ")))"].join("\n")
end
it "template parameters + additional setup" do
- dump(parse("<%|$x| $y = 10 %>Hello World")).should == "(lambda (parameters x) (epp (block (= $y 10) (render-s 'Hello World'))))"
+ dump(parse("<%|$x| $y = 10 %>Hello World")).should == [
+ "(lambda (parameters x) (epp (block",
+ " (= $y 10)",
+ " (render-s 'Hello World')",
+ ")))"].join("\n")
end
it "comments" do
- dump(parse("<%#($x='cigar', $y)%>Hello World")).should == "(lambda (epp (block (render-s 'Hello World'))))"
+ dump(parse("<%#($x='cigar', $y)%>Hello World")).should == [
+ "(lambda (epp (block",
+ " (render-s 'Hello World')",
+ ")))"
+ ].join("\n")
end
it "verbatim epp tags" do
- dump(parse("<%% contemplating %%>Hello World")).should == "(lambda (epp (block (render-s '<% contemplating %>Hello World'))))"
+ dump(parse("<%% contemplating %%>Hello World")).should == [
+ "(lambda (epp (block",
+ " (render-s '<% contemplating %>Hello World')",
+ ")))"
+ ].join("\n")
end
it "expressions" do
- dump(parse("We all live in <%= 3.14 - 2.14 %> world")).should ==
- "(lambda (epp (block (render-s 'We all live in ') (render (- 3.14 2.14)) (render-s ' world'))))"
+ dump(parse("We all live in <%= 3.14 - 2.14 %> world")).should == [
+ "(lambda (epp (block",
+ " (render-s 'We all live in ')",
+ " (render (- 3.14 2.14))",
+ " (render-s ' world')",
+ ")))"
+ ].join("\n")
end
end
end
diff --git a/spec/unit/pops/parser/evaluating_parser_spec.rb b/spec/unit/pops/parser/evaluating_parser_spec.rb
index 8448a5af3..7645b9bc2 100644
--- a/spec/unit/pops/parser/evaluating_parser_spec.rb
+++ b/spec/unit/pops/parser/evaluating_parser_spec.rb
@@ -9,7 +9,6 @@ describe 'The Evaluating Parser' do
include PuppetSpec::Scope
let(:acceptor) { Puppet::Pops::Validation::Acceptor.new() }
- let(:diag) { Puppet::Pops::Binder::Hiera2::DiagnosticProducer.new(acceptor) }
let(:scope) { s = create_test_scope_for_node(node); s }
let(:node) { 'node.example.com' }
diff --git a/spec/unit/pops/parser/lexer2_spec.rb b/spec/unit/pops/parser/lexer2_spec.rb
index 8ccdc2630..b9a0a916b 100644
--- a/spec/unit/pops/parser/lexer2_spec.rb
+++ b/spec/unit/pops/parser/lexer2_spec.rb
@@ -21,7 +21,7 @@ describe 'Lexer2' do
include EgrammarLexer2Spec
{
- :LBRACK => '[',
+ :LISTSTART => '[',
:RBRACK => ']',
:LBRACE => '{',
:RBRACE => '}',
@@ -69,6 +69,10 @@ describe 'Lexer2' do
end
end
+ it "should lex [ in position after non whitespace as LBRACK" do
+ tokens_scanned_from("a[").should match_tokens2(:NAME, :LBRACK)
+ end
+
{
"case" => :CASE,
"class" => :CLASS,
@@ -186,6 +190,16 @@ describe 'Lexer2' do
end
end
+ { "''" => [2, ""],
+ "'a'" => [3, "a"],
+ "'a\\'b'" => [6, "a'b"],
+ }.each do |source, expected|
+ it "should lex a single quoted STRING on the form #{source} as having length #{expected[0]}" do
+ length, value = expected
+ tokens_scanned_from(source).should match_tokens2([:STRING, value, {:line => 1, :pos=>1, :length=> length}])
+ end
+ end
+
{ '""' => '',
'"a"' => 'a',
'"a\'b"' => "a'b",
@@ -229,7 +243,7 @@ describe 'Lexer2' do
it "differentiates between foo[x] and foo [x] (whitespace)" do
tokens_scanned_from("$a[1]").should match_tokens2(:VARIABLE, :LBRACK, :NUMBER, :RBRACK)
- tokens_scanned_from("$a [1]").should match_tokens2(:VARIABLE, :LBRACK, :NUMBER, :RBRACK)
+ tokens_scanned_from("$a [1]").should match_tokens2(:VARIABLE, :LISTSTART, :NUMBER, :RBRACK)
tokens_scanned_from("a[1]").should match_tokens2(:NAME, :LBRACK, :NUMBER, :RBRACK)
tokens_scanned_from("a [1]").should match_tokens2(:NAME, :LISTSTART, :NUMBER, :RBRACK)
tokens_scanned_from(" if \n\r\t\nif if ").should match_tokens2(:IF, :IF, :IF)
@@ -257,7 +271,9 @@ describe 'Lexer2' do
"!~" => [:NOMATCH, "!~ /./"],
"," => [:COMMA, ", /./"],
"(" => [:LPAREN, "( /./"],
- "[" => [:LBRACK, "[ /./"],
+ "[" => [:LISTSTART, "[ /./"],
+ "[" => [[:NAME, :LBRACK], "a[ /./"],
+ "[" => [[:NAME, :LISTSTART], "a [ /./"],
"{" => [:LBRACE, "{ /./"],
"+" => [:PLUS, "+ /./"],
"-" => [:MINUS, "- /./"],
@@ -265,7 +281,8 @@ describe 'Lexer2' do
";" => [:SEMIC, "; /./"],
}.each do |token, entry|
it "should lex regexp after '#{token}'" do
- tokens_scanned_from(entry[1]).should match_tokens2(entry[0], :REGEX)
+ expected = [entry[0], :REGEX].flatten
+ tokens_scanned_from(entry[1]).should match_tokens2(*expected)
end
end
diff --git a/spec/unit/pops/parser/lexer_spec.rb b/spec/unit/pops/parser/lexer_spec.rb
deleted file mode 100755
index 40d9b3e51..000000000
--- a/spec/unit/pops/parser/lexer_spec.rb
+++ /dev/null
@@ -1,840 +0,0 @@
-#! /usr/bin/env ruby
-require 'spec_helper'
-
-require 'puppet/pops'
-
-# This is a special matcher to match easily lexer output
-RSpec::Matchers.define :be_like do |*expected|
- match do |actual|
- diffable
- expected.zip(actual).all? { |e,a| !e or a[0] == e or (e.is_a? Array and a[0] == e[0] and (a[1] == e[1] or (a[1].is_a?(Hash) and a[1][:value] == e[1]))) }
- end
-end
-__ = nil
-
-module EgrammarLexerSpec
- def self.tokens_scanned_from(s)
- lexer = Puppet::Pops::Parser::Lexer.new
- lexer.string = s
- tokens = lexer.fullscan[0..-2]
- tokens.map do |t|
- key = t[0]
- options = t[1]
- if options[:locator]
- # unresolved locations needs to be resolved for tests that check positioning
- [key,
- options[:locator].to_location_hash(
- options[:offset],
- options[:end_offset]).merge({:value => options[:value]}) ]
- else
- t
- end
- end
- end
-end
-
-describe Puppet::Pops::Parser::Lexer do
- include EgrammarLexerSpec
-
- describe "when reading strings" do
- before { @lexer = Puppet::Pops::Parser::Lexer.new }
-
- it "should increment the line count for every carriage return in the string" do
- @lexer.string = "'this\nis\natest'"
- @lexer.fullscan[0..-2]
-
- line = @lexer.line
- line.should == 3
- end
-
- it "should not increment the line count for escapes in the string" do
- @lexer.string = "'this\\nis\\natest'"
- @lexer.fullscan[0..-2]
-
- @lexer.line.should == 1
- end
-
- it "should not think the terminator is escaped, when preceeded by an even number of backslashes" do
- @lexer.string = "'here\nis\nthe\nstring\\\\'with\nextra\njunk"
- @lexer.fullscan[0..-2]
-
- @lexer.line.should == 6
- end
-
- {
- 'r' => "\r",
- 'n' => "\n",
- 't' => "\t",
- 's' => " "
- }.each do |esc, expected_result|
- it "should recognize \\#{esc} sequence" do
- @lexer.string = "\\#{esc}'"
- @lexer.slurpstring("'")[0].should == expected_result
- end
- end
- end
-end
-
-describe Puppet::Pops::Parser::Lexer::Token, "when initializing" do
- it "should create a regex if the first argument is a string" do
- Puppet::Pops::Parser::Lexer::Token.new("something", :NAME).regex.should == %r{something}
- end
-
- it "should set the string if the first argument is one" do
- Puppet::Pops::Parser::Lexer::Token.new("something", :NAME).string.should == "something"
- end
-
- it "should set the regex if the first argument is one" do
- Puppet::Pops::Parser::Lexer::Token.new(%r{something}, :NAME).regex.should == %r{something}
- end
-end
-
-describe Puppet::Pops::Parser::Lexer::TokenList do
- before do
- @list = Puppet::Pops::Parser::Lexer::TokenList.new
- end
-
- it "should have a method for retrieving tokens by the name" do
- token = @list.add_token :name, "whatever"
- @list[:name].should equal(token)
- end
-
- it "should have a method for retrieving string tokens by the string" do
- token = @list.add_token :name, "whatever"
- @list.lookup("whatever").should equal(token)
- end
-
- it "should add tokens to the list when directed" do
- token = @list.add_token :name, "whatever"
- @list[:name].should equal(token)
- end
-
- it "should have a method for adding multiple tokens at once" do
- @list.add_tokens "whatever" => :name, "foo" => :bar
- @list[:name].should_not be_nil
- @list[:bar].should_not be_nil
- end
-
- it "should fail to add tokens sharing a name with an existing token" do
- @list.add_token :name, "whatever"
- expect { @list.add_token :name, "whatever" }.to raise_error(ArgumentError)
- end
-
- it "should set provided options on tokens being added" do
- token = @list.add_token :name, "whatever", :skip_text => true
- token.skip_text.should == true
- end
-
- it "should define any provided blocks as a :convert method" do
- token = @list.add_token(:name, "whatever") do "foo" end
- token.convert.should == "foo"
- end
-
- it "should store all string tokens in the :string_tokens list" do
- one = @list.add_token(:name, "1")
- @list.string_tokens.should be_include(one)
- end
-
- it "should store all regex tokens in the :regex_tokens list" do
- one = @list.add_token(:name, %r{one})
- @list.regex_tokens.should be_include(one)
- end
-
- it "should not store string tokens in the :regex_tokens list" do
- one = @list.add_token(:name, "1")
- @list.regex_tokens.should_not be_include(one)
- end
-
- it "should not store regex tokens in the :string_tokens list" do
- one = @list.add_token(:name, %r{one})
- @list.string_tokens.should_not be_include(one)
- end
-
- it "should sort the string tokens inversely by length when asked" do
- one = @list.add_token(:name, "1")
- two = @list.add_token(:other, "12")
- @list.sort_tokens
- @list.string_tokens.should == [two, one]
- end
-end
-
-describe Puppet::Pops::Parser::Lexer::TOKENS do
- before do
- @lexer = Puppet::Pops::Parser::Lexer.new
- end
-
- {
- :LBRACK => '[',
- :RBRACK => ']',
-# :LBRACE => '{',
-# :RBRACE => '}',
- :LPAREN => '(',
- :RPAREN => ')',
- :EQUALS => '=',
- :ISEQUAL => '==',
- :GREATEREQUAL => '>=',
- :GREATERTHAN => '>',
- :LESSTHAN => '<',
- :LESSEQUAL => '<=',
- :NOTEQUAL => '!=',
- :NOT => '!',
- :COMMA => ',',
- :DOT => '.',
- :COLON => ':',
- :AT => '@',
- :LLCOLLECT => '<<|',
- :RRCOLLECT => '|>>',
- :LCOLLECT => '<|',
- :RCOLLECT => '|>',
- :SEMIC => ';',
- :QMARK => '?',
- :BACKSLASH => '\\',
- :FARROW => '=>',
- :PARROW => '+>',
- :APPENDS => '+=',
- :DELETES => '-=',
- :PLUS => '+',
- :MINUS => '-',
- :DIV => '/',
- :TIMES => '*',
- :LSHIFT => '<<',
- :RSHIFT => '>>',
- :MATCH => '=~',
- :NOMATCH => '!~',
- :IN_EDGE => '->',
- :OUT_EDGE => '<-',
- :IN_EDGE_SUB => '~>',
- :OUT_EDGE_SUB => '<~',
- :PIPE => '|',
- }.each do |name, string|
- it "should have a token named #{name.to_s}" do
- Puppet::Pops::Parser::Lexer::TOKENS[name].should_not be_nil
- end
-
- it "should match '#{string}' for the token #{name.to_s}" do
- Puppet::Pops::Parser::Lexer::TOKENS[name].string.should == string
- end
- end
-
- {
- "case" => :CASE,
- "class" => :CLASS,
- "default" => :DEFAULT,
- "define" => :DEFINE,
-# "import" => :IMPORT, # done as a function in egrammar
- "if" => :IF,
- "elsif" => :ELSIF,
- "else" => :ELSE,
- "inherits" => :INHERITS,
- "node" => :NODE,
- "and" => :AND,
- "or" => :OR,
- "undef" => :UNDEF,
- "false" => :FALSE,
- "true" => :TRUE,
- "in" => :IN,
- "unless" => :UNLESS,
- }.each do |string, name|
- it "should have a keyword named #{name.to_s}" do
- Puppet::Pops::Parser::Lexer::KEYWORDS[name].should_not be_nil
- end
-
- it "should have the keyword for #{name.to_s} set to #{string}" do
- Puppet::Pops::Parser::Lexer::KEYWORDS[name].string.should == string
- end
- end
-
- # These tokens' strings don't matter, just that the tokens exist.
- [:STRING, :DQPRE, :DQMID, :DQPOST, :BOOLEAN, :NAME, :NUMBER, :COMMENT, :MLCOMMENT,
- :LBRACE, :RBRACE,
- :RETURN, :SQUOTE, :DQUOTE, :VARIABLE].each do |name|
- it "should have a token named #{name.to_s}" do
- Puppet::Pops::Parser::Lexer::TOKENS[name].should_not be_nil
- end
- end
-end
-
-describe Puppet::Pops::Parser::Lexer::TOKENS[:CLASSREF] do
- before { @token = Puppet::Pops::Parser::Lexer::TOKENS[:CLASSREF] }
-
- it "should match against single upper-case alpha-numeric terms" do
- @token.regex.should =~ "One"
- end
-
- it "should match against upper-case alpha-numeric terms separated by double colons" do
- @token.regex.should =~ "One::Two"
- end
-
- it "should match against many upper-case alpha-numeric terms separated by double colons" do
- @token.regex.should =~ "One::Two::Three::Four::Five"
- end
-
- it "should match against upper-case alpha-numeric terms prefixed by double colons" do
- @token.regex.should =~ "::One"
- end
-end
-
-describe Puppet::Pops::Parser::Lexer::TOKENS[:NAME] do
- before { @token = Puppet::Pops::Parser::Lexer::TOKENS[:NAME] }
-
- it "should match against lower-case alpha-numeric terms" do
- @token.regex.should =~ "one-two"
- end
-
- it "should return itself and the value if the matched term is not a keyword" do
- Puppet::Pops::Parser::Lexer::KEYWORDS.expects(:lookup).returns(nil)
- @token.convert(stub("lexer"), "myval").should == [Puppet::Pops::Parser::Lexer::TOKENS[:NAME], "myval"]
- end
-
- it "should return the keyword token and the value if the matched term is a keyword" do
- keyword = stub 'keyword', :name => :testing
- Puppet::Pops::Parser::Lexer::KEYWORDS.expects(:lookup).returns(keyword)
- @token.convert(stub("lexer"), "myval").should == [keyword, "myval"]
- end
-
- it "should return the BOOLEAN token and 'true' if the matched term is the string 'true'" do
- keyword = stub 'keyword', :name => :TRUE
- Puppet::Pops::Parser::Lexer::KEYWORDS.expects(:lookup).returns(keyword)
- @token.convert(stub('lexer'), "true").should == [Puppet::Pops::Parser::Lexer::TOKENS[:BOOLEAN], true]
- end
-
- it "should return the BOOLEAN token and 'false' if the matched term is the string 'false'" do
- keyword = stub 'keyword', :name => :FALSE
- Puppet::Pops::Parser::Lexer::KEYWORDS.expects(:lookup).returns(keyword)
- @token.convert(stub('lexer'), "false").should == [Puppet::Pops::Parser::Lexer::TOKENS[:BOOLEAN], false]
- end
-
- it "should match against lower-case alpha-numeric terms separated by double colons" do
- @token.regex.should =~ "one::two"
- end
-
- it "should match against many lower-case alpha-numeric terms separated by double colons" do
- @token.regex.should =~ "one::two::three::four::five"
- end
-
- it "should match against lower-case alpha-numeric terms prefixed by double colons" do
- @token.regex.should =~ "::one"
- end
-
- it "should match against nested terms starting with numbers" do
- @token.regex.should =~ "::1one::2two::3three"
- end
-end
-
-describe Puppet::Pops::Parser::Lexer::TOKENS[:NUMBER] do
- before do
- @token = Puppet::Pops::Parser::Lexer::TOKENS[:NUMBER]
- @regex = @token.regex
- end
-
- it "should match against numeric terms" do
- @regex.should =~ "2982383139"
- end
-
- it "should match against float terms" do
- @regex.should =~ "29823.235"
- end
-
- it "should match against hexadecimal terms" do
- @regex.should =~ "0xBEEF0023"
- end
-
- it "should match against float with exponent terms" do
- @regex.should =~ "10e23"
- end
-
- it "should match against float terms with negative exponents" do
- @regex.should =~ "10e-23"
- end
-
- it "should match against float terms with fractional parts and exponent" do
- @regex.should =~ "1.234e23"
- end
-end
-
-describe Puppet::Pops::Parser::Lexer::TOKENS[:COMMENT] do
- before { @token = Puppet::Pops::Parser::Lexer::TOKENS[:COMMENT] }
-
- it "should match against lines starting with '#'" do
- @token.regex.should =~ "# this is a comment"
- end
-
- it "should be marked to get skipped" do
- @token.skip?.should be_true
- end
-
- it "'s block should return the comment without any text" do
- # This is a silly test, the original tested that the comments was processed, but
- # all comments are skipped anyway, and never collected for documentation.
- #
- @token.convert(@lexer,"# this is a comment")[1].should == ""
- end
-end
-
-describe Puppet::Pops::Parser::Lexer::TOKENS[:MLCOMMENT] do
- before do
- @token = Puppet::Pops::Parser::Lexer::TOKENS[:MLCOMMENT]
- @lexer = stub 'lexer', :line => 0
- end
-
- it "should match against lines enclosed with '/*' and '*/'" do
- @token.regex.should =~ "/* this is a comment */"
- end
-
- it "should match multiple lines enclosed with '/*' and '*/'" do
- @token.regex.should =~ """/*
- this is a comment
- */"""
- end
-
-# # TODO: REWRITE THIS TEST TO NOT BE BASED ON INTERNALS
-# it "should increase the lexer current line number by the amount of lines spanned by the comment" do
-# @lexer.expects(:line=).with(2)
-# @token.convert(@lexer, "1\n2\n3")
-# end
-
- it "should not greedily match comments" do
- match = @token.regex.match("/* first */ word /* second */")
- match[1].should == " first "
- end
-
- it "'s block should return the comment without the comment marks" do
- # This is a silly test, the original tested that the comments was processed, but
- # all comments are skipped anyway, and never collected for documentation.
- #
- @lexer.stubs(:line=).with(0)
-
- @token.convert(@lexer,"/* this is a comment */")[1].should == ""
- end
-end
-
-describe Puppet::Pops::Parser::Lexer::TOKENS[:RETURN] do
- before { @token = Puppet::Pops::Parser::Lexer::TOKENS[:RETURN] }
-
- it "should match against carriage returns" do
- @token.regex.should =~ "\n"
- end
-
- it "should be marked to initiate text skipping" do
- @token.skip_text.should be_true
- end
-end
-
-shared_examples_for "handling `-` in standard variable names for egrammar" do |prefix|
- # Watch out - a regex might match a *prefix* on these, not just the whole
- # word, so make sure you don't have false positive or negative results based
- # on that.
- legal = %w{f foo f::b foo::b f::bar foo::bar 3 foo3 3foo}
- illegal = %w{f- f-o -f f::-o f::o- f::o-o}
-
- ["", "::"].each do |global_scope|
- legal.each do |name|
- var = prefix + global_scope + name
- it "should accept #{var.inspect} as a valid variable name" do
- (subject.regex.match(var) || [])[0].should == var
- end
- end
-
- illegal.each do |name|
- var = prefix + global_scope + name
- it "when `variable_with_dash` is disabled it should NOT accept #{var.inspect} as a valid variable name" do
- Puppet[:allow_variables_with_dashes] = false
- (subject.regex.match(var) || [])[0].should_not == var
- end
-
- it "when `variable_with_dash` is enabled it should NOT accept #{var.inspect} as a valid variable name" do
- Puppet[:allow_variables_with_dashes] = true
- (subject.regex.match(var) || [])[0].should_not == var
- end
- end
- end
-end
-
-describe Puppet::Pops::Parser::Lexer::TOKENS[:DOLLAR_VAR] do
- its(:skip_text) { should be_false }
-
- it_should_behave_like "handling `-` in standard variable names for egrammar", '$'
-end
-
-describe Puppet::Pops::Parser::Lexer::TOKENS[:VARIABLE] do
- its(:skip_text) { should be_false }
-
- it_should_behave_like "handling `-` in standard variable names for egrammar", ''
-end
-
-describe "the horrible deprecation / compatibility variables with dashes" do
-
- context "deprecation warnings" do
- before :each do Puppet[:allow_variables_with_dashes] = true end
-
- it "does not warn about a variable without a dash" do
- Puppet.expects(:deprecation_warning).never
-
- EgrammarLexerSpec.tokens_scanned_from('$c').should == [
- [:VARIABLE, {:value=>"c", :line=>1, :pos=>1, :offset=>0, :length=>2}]
- ]
- end
-
- it "does not warn about referencing a class name that contains a dash" do
- Puppet.expects(:deprecation_warning).never
-
- EgrammarLexerSpec.tokens_scanned_from('foo-bar').should == [
- [:NAME, {:value=>"foo-bar", :line=>1, :pos=>1, :offset=>0, :length=>7}]
- ]
- end
- end
-end
-
-
-describe Puppet::Pops::Parser::Lexer,"when lexing strings" do
- {
- %q{'single quoted string')} => [[:STRING,'single quoted string']],
- %q{"double quoted string"} => [[:STRING,'double quoted string']],
- %q{'single quoted string with an escaped "\\'"'} => [[:STRING,'single quoted string with an escaped "\'"']],
- %q{'single quoted string with an escaped "\$"'} => [[:STRING,'single quoted string with an escaped "\$"']],
- %q{'single quoted string with an escaped "\."'} => [[:STRING,'single quoted string with an escaped "\."']],
- %q{'single quoted string with an escaped "\r\n"'} => [[:STRING,'single quoted string with an escaped "\r\n"']],
- %q{'single quoted string with an escaped "\n"'} => [[:STRING,'single quoted string with an escaped "\n"']],
- %q{'single quoted string with an escaped "\\\\"'} => [[:STRING,'single quoted string with an escaped "\\\\"']],
- %q{"string with an escaped '\\"'"} => [[:STRING,"string with an escaped '\"'"]],
- %q{"string with an escaped '\\$'"} => [[:STRING,"string with an escaped '$'"]],
- %Q{"string with a line ending with a backslash: \\\nfoo"} => [[:STRING,"string with a line ending with a backslash: foo"]],
- %q{"string with $v (but no braces)"} => [[:DQPRE,"string with "],[:VARIABLE,'v'],[:DQPOST,' (but no braces)']],
- %q["string with ${v} in braces"] => [[:DQPRE,"string with "],[:VARIABLE,'v'],[:DQPOST,' in braces']],
- %q["string with ${qualified::var} in braces"] => [[:DQPRE,"string with "],[:VARIABLE,'qualified::var'],[:DQPOST,' in braces']],
- %q{"string with $v and $v (but no braces)"} => [[:DQPRE,"string with "],[:VARIABLE,"v"],[:DQMID," and "],[:VARIABLE,"v"],[:DQPOST," (but no braces)"]],
- %q["string with ${v} and ${v} in braces"] => [[:DQPRE,"string with "],[:VARIABLE,"v"],[:DQMID," and "],[:VARIABLE,"v"],[:DQPOST," in braces"]],
- %q["string with ${'a nested single quoted string'} inside it."] => [[:DQPRE,"string with "],[:STRING,'a nested single quoted string'],[:DQPOST,' inside it.']],
- %q["string with ${['an array ',$v2]} in it."] => [[:DQPRE,"string with "],:LBRACK,[:STRING,"an array "],:COMMA,[:VARIABLE,"v2"],:RBRACK,[:DQPOST," in it."]],
- %q{a simple "scanner" test} => [[:NAME,"a"],[:NAME,"simple"], [:STRING,"scanner"],[:NAME,"test"]],
- %q{a simple 'single quote scanner' test} => [[:NAME,"a"],[:NAME,"simple"], [:STRING,"single quote scanner"],[:NAME,"test"]],
- %q{a harder 'a $b \c"'} => [[:NAME,"a"],[:NAME,"harder"], [:STRING,'a $b \c"']],
- %q{a harder "scanner test"} => [[:NAME,"a"],[:NAME,"harder"], [:STRING,"scanner test"]],
- %q{a hardest "scanner \"test\""} => [[:NAME,"a"],[:NAME,"hardest"],[:STRING,'scanner "test"']],
- %Q{a hardestest "scanner \\"test\\"\n"} => [[:NAME,"a"],[:NAME,"hardestest"],[:STRING,%Q{scanner "test"\n}]],
- %q{function("call")} => [[:NAME,"function"],[:LPAREN,"("],[:STRING,'call'],[:RPAREN,")"]],
- %q["string with ${(3+5)/4} nested math."] => [[:DQPRE,"string with "],:LPAREN,[:NAME,"3"],:PLUS,[:NAME,"5"],:RPAREN,:DIV,[:NAME,"4"],[:DQPOST," nested math."]],
- %q["$$$$"] => [[:STRING,"$$$$"]],
- %q["$variable"] => [[:DQPRE,""],[:VARIABLE,"variable"],[:DQPOST,""]],
- %q["$var$other"] => [[:DQPRE,""],[:VARIABLE,"var"],[:DQMID,""],[:VARIABLE,"other"],[:DQPOST,""]],
- %q["foo$bar$"] => [[:DQPRE,"foo"],[:VARIABLE,"bar"],[:DQPOST,"$"]],
- %q["foo$$bar"] => [[:DQPRE,"foo$"],[:VARIABLE,"bar"],[:DQPOST,""]],
- %q[""] => [[:STRING,""]],
- %q["123 456 789 0"] => [[:STRING,"123 456 789 0"]],
- %q["${123} 456 $0"] => [[:DQPRE,""],[:VARIABLE,"123"],[:DQMID," 456 "],[:VARIABLE,"0"],[:DQPOST,""]],
- %q["$foo::::bar"] => [[:DQPRE,""],[:VARIABLE,"foo"],[:DQPOST,"::::bar"]],
- # Keyword variables
- %q["$true"] => [[:DQPRE,""],[:VARIABLE, "true"],[:DQPOST,""]],
- %q["$false"] => [[:DQPRE,""],[:VARIABLE, "false"],[:DQPOST,""]],
- %q["$if"] => [[:DQPRE,""],[:VARIABLE, "if"],[:DQPOST,""]],
- %q["$case"] => [[:DQPRE,""],[:VARIABLE, "case"],[:DQPOST,""]],
- %q["$unless"] => [[:DQPRE,""],[:VARIABLE, "unless"],[:DQPOST,""]],
- %q["$undef"] => [[:DQPRE,""],[:VARIABLE, "undef"],[:DQPOST,""]],
- # Expressions
- %q["${true}"] => [[:DQPRE,""],[:BOOLEAN, true],[:DQPOST,""]],
- %q["${false}"] => [[:DQPRE,""],[:BOOLEAN, false],[:DQPOST,""]],
- %q["${undef}"] => [[:DQPRE,""],:UNDEF,[:DQPOST,""]],
- %q["${if true {false}}"] => [[:DQPRE,""],:IF,[:BOOLEAN, true], :LBRACE, [:BOOLEAN, false], :RBRACE, [:DQPOST,""]],
- %q["${unless true {false}}"] => [[:DQPRE,""],:UNLESS,[:BOOLEAN, true], :LBRACE, [:BOOLEAN, false], :RBRACE, [:DQPOST,""]],
- %q["${case true {true:{false}}}"] => [
- [:DQPRE,""],:CASE,[:BOOLEAN, true], :LBRACE, [:BOOLEAN, true], :COLON, :LBRACE, [:BOOLEAN, false],
- :RBRACE, :RBRACE, [:DQPOST,""]],
- %q[{ "${a}" => 1 }] => [ :LBRACE, [:DQPRE,""], [:VARIABLE,"a"], [:DQPOST,""], :FARROW, [:NAME,"1"], :RBRACE ],
- }.each { |src,expected_result|
- it "should handle #{src} correctly" do
- EgrammarLexerSpec.tokens_scanned_from(src).should be_like(*expected_result)
- end
- }
-end
-
-describe Puppet::Pops::Parser::Lexer::TOKENS[:DOLLAR_VAR] do
- before { @token = Puppet::Pops::Parser::Lexer::TOKENS[:DOLLAR_VAR] }
-
- it "should match against alpha words prefixed with '$'" do
- @token.regex.should =~ '$this_var'
- end
-
- it "should return the VARIABLE token and the variable name stripped of the '$'" do
- @token.convert(stub("lexer"), "$myval").should == [Puppet::Pops::Parser::Lexer::TOKENS[:VARIABLE], "myval"]
- end
-end
-
-describe Puppet::Pops::Parser::Lexer::TOKENS[:REGEX] do
- before { @token = Puppet::Pops::Parser::Lexer::TOKENS[:REGEX] }
-
- it "should match against any expression enclosed in //" do
- @token.regex.should =~ '/this is a regex/'
- end
-
- it 'should not match if there is \n in the regex' do
- @token.regex.should_not =~ "/this is \n a regex/"
- end
-
- describe "when scanning" do
- it "should not consider escaped slashes to be the end of a regex" do
- EgrammarLexerSpec.tokens_scanned_from("$x =~ /this \\/ foo/").should be_like(__,__,[:REGEX,%r{this / foo}])
- end
-
- it "should not lex chained division as a regex" do
- EgrammarLexerSpec.tokens_scanned_from("$x = $a/$b/$c").collect { |name, data| name }.should_not be_include( :REGEX )
- end
-
- it "should accept a regular expression after NODE" do
- EgrammarLexerSpec.tokens_scanned_from("node /www.*\.mysite\.org/").should be_like(__,[:REGEX,Regexp.new("www.*\.mysite\.org")])
- end
-
- it "should accept regular expressions in a CASE" do
- s = %q{case $variable {
- "something": {$othervar = 4096 / 2}
- /regex/: {notice("this notably sucks")}
- }
- }
- EgrammarLexerSpec.tokens_scanned_from(s).should be_like(
- :CASE,:VARIABLE,:LBRACE,:STRING,:COLON,:LBRACE,:VARIABLE,:EQUALS,:NAME,:DIV,:NAME,:RBRACE,[:REGEX,/regex/],:COLON,:LBRACE,:NAME,:LPAREN,:STRING,:RPAREN,:RBRACE,:RBRACE
- )
- end
- end
-
- it "should return the REGEX token and a Regexp" do
- @token.convert(stub("lexer"), "/myregex/").should == [Puppet::Pops::Parser::Lexer::TOKENS[:REGEX], Regexp.new(/myregex/)]
- end
-end
-
-describe Puppet::Pops::Parser::Lexer, "when lexing comments" do
- before { @lexer = Puppet::Pops::Parser::Lexer.new }
-
- it "should skip whitespace before lexing the next token after a non-token" do
- EgrammarLexerSpec.tokens_scanned_from("/* 1\n\n */ \ntest").should be_like([:NAME, "test"])
- end
-end
-
-# FIXME: We need to rewrite all of these tests, but I just don't want to take the time right now.
-describe "Puppet::Pops::Parser::Lexer in the old tests" do
- before { @lexer = Puppet::Pops::Parser::Lexer.new }
-
- it "should do simple lexing" do
- {
- %q{\\} => [[:BACKSLASH,"\\"]],
- %q{simplest scanner test} => [[:NAME,"simplest"],[:NAME,"scanner"],[:NAME,"test"]],
- %Q{returned scanner test\n} => [[:NAME,"returned"],[:NAME,"scanner"],[:NAME,"test"]]
- }.each { |source,expected|
- EgrammarLexerSpec.tokens_scanned_from(source).should be_like(*expected)
- }
- end
-
- it "should fail usefully" do
- expect { EgrammarLexerSpec.tokens_scanned_from('^') }.to raise_error(RuntimeError)
- end
-
- it "should fail if the string is not set" do
- expect { @lexer.fullscan }.to raise_error(Puppet::LexError)
- end
-
- it "should correctly identify keywords" do
- EgrammarLexerSpec.tokens_scanned_from("case").should be_like([:CASE, "case"])
- end
-
- it "should correctly parse class references" do
- %w{Many Different Words A Word}.each { |t| EgrammarLexerSpec.tokens_scanned_from(t).should be_like([:CLASSREF,t])}
- end
-
- # #774
- it "should correctly parse namespaced class refernces token" do
- %w{Foo ::Foo Foo::Bar ::Foo::Bar}.each { |t| EgrammarLexerSpec.tokens_scanned_from(t).should be_like([:CLASSREF, t]) }
- end
-
- it "should correctly parse names" do
- %w{this is a bunch of names}.each { |t| EgrammarLexerSpec.tokens_scanned_from(t).should be_like([:NAME,t]) }
- end
-
- it "should correctly parse names with numerals" do
- %w{1name name1 11names names11}.each { |t| EgrammarLexerSpec.tokens_scanned_from(t).should be_like([:NAME,t]) }
- end
-
- it "should correctly parse empty strings" do
- expect { EgrammarLexerSpec.tokens_scanned_from('$var = ""') }.to_not raise_error
- end
-
- it "should correctly parse virtual resources" do
- EgrammarLexerSpec.tokens_scanned_from("@type {").should be_like([:AT, "@"], [:NAME, "type"], [:LBRACE, "{"])
- end
-
- it "should correctly deal with namespaces" do
- @lexer.string = %{class myclass}
- @lexer.fullscan
- @lexer.namespace.should == "myclass"
-
- @lexer.namepop
- @lexer.namespace.should == ""
-
- @lexer.string = "class base { class sub { class more"
- @lexer.fullscan
- @lexer.namespace.should == "base::sub::more"
-
- @lexer.namepop
- @lexer.namespace.should == "base::sub"
- end
-
- it "should not put class instantiation on the namespace" do
- @lexer.string = "class base { class sub { class { mode"
- @lexer.fullscan
- @lexer.namespace.should == "base::sub"
- end
-
- it "should correctly handle fully qualified names" do
- @lexer.string = "class base { class sub::more {"
- @lexer.fullscan
- @lexer.namespace.should == "base::sub::more"
-
- @lexer.namepop
- @lexer.namespace.should == "base"
- end
-
- it "should correctly lex variables" do
- ["$variable", "$::variable", "$qualified::variable", "$further::qualified::variable"].each do |string|
- EgrammarLexerSpec.tokens_scanned_from(string).should be_like([:VARIABLE,string.sub(/^\$/,'')])
- end
- end
-
- it "should end variables at `-`" do
- EgrammarLexerSpec.tokens_scanned_from('$hyphenated-variable').
- should be_like([:VARIABLE, "hyphenated"], [:MINUS, '-'], [:NAME, 'variable'])
- end
-
- it "should not include whitespace in a variable" do
- EgrammarLexerSpec.tokens_scanned_from("$foo bar").should_not be_like([:VARIABLE, "foo bar"])
- end
- it "should not include excess colons in a variable" do
- EgrammarLexerSpec.tokens_scanned_from("$foo::::bar").should_not be_like([:VARIABLE, "foo::::bar"])
- end
-end
-
-describe "Puppet::Pops::Parser::Lexer in the old tests when lexing example files" do
- my_fixtures('*.pp') do |file|
- it "should correctly lex #{file}" do
- lexer = Puppet::Pops::Parser::Lexer.new
- lexer.file = file
- expect { lexer.fullscan }.to_not raise_error
- end
- end
-end
-
-describe "when trying to lex a non-existent file" do
- include PuppetSpec::Files
-
- it "should return an empty list of tokens" do
- lexer = Puppet::Pops::Parser::Lexer.new
- lexer.file = nofile = tmpfile('lexer')
- Puppet::FileSystem.exist?(nofile).should == false
-
- lexer.fullscan.should == [[false,false]]
- end
-end
-
-describe "when string quotes are not closed" do
- it "should report with message including an \" opening quote" do
- expect { EgrammarLexerSpec.tokens_scanned_from('$var = "') }.to raise_error(/after '"'/)
- end
-
- it "should report with message including an \' opening quote" do
- expect { EgrammarLexerSpec.tokens_scanned_from('$var = \'') }.to raise_error(/after "'"/)
- end
-
- it "should report <eof> if immediately followed by eof" do
- expect { EgrammarLexerSpec.tokens_scanned_from('$var = "') }.to raise_error(/followed by '<eof>'/)
- end
-
- it "should report max 5 chars following quote" do
- expect { EgrammarLexerSpec.tokens_scanned_from('$var = "123456') }.to raise_error(/followed by '12345...'/)
- end
-
- it "should escape control chars" do
- expect { EgrammarLexerSpec.tokens_scanned_from('$var = "12\n3456') }.to raise_error(/followed by '12\\n3...'/)
- end
-
- it "should resport position of opening quote" do
- expect { EgrammarLexerSpec.tokens_scanned_from('$var = "123456') }.to raise_error(/at line 1:8/)
- expect { EgrammarLexerSpec.tokens_scanned_from('$var = "123456') }.to raise_error(/at line 1:9/)
- end
-end
-
-describe "when lexing number, bad input should not go unpunished" do
- it "should slap bad octal as such" do
- expect { EgrammarLexerSpec.tokens_scanned_from('$var = 0778') }.to raise_error(/Not a valid octal/)
- end
-
- it "should slap bad hex as such" do
- expect { EgrammarLexerSpec.tokens_scanned_from('$var = 0xFG') }.to raise_error(/Not a valid hex/)
- expect { EgrammarLexerSpec.tokens_scanned_from('$var = 0xfg') }.to raise_error(/Not a valid hex/)
- end
- # Note, bad decimals are probably impossible to enter, as they are not recognized as complete numbers, instead,
- # the error will be something else, depending on what follows some initial digit.
- #
-end
-
-describe "when lexing interpolation detailed positioning should be correct" do
- it "should correctly position a string without interpolation" do
- EgrammarLexerSpec.tokens_scanned_from('"not interpolated"').should be_like(
- [:STRING, {:value=>"not interpolated", :line=>1, :offset=>0, :pos=>1, :length=>18}])
- end
-
- it "should correctly position a string with false start in interpolation" do
- EgrammarLexerSpec.tokens_scanned_from('"not $$$ rpolated"').should be_like(
- [:STRING, {:value=>"not $$$ rpolated", :line=>1, :offset=>0, :pos=>1, :length=>18}])
- end
-
- it "should correctly position pre-mid-end interpolation " do
- EgrammarLexerSpec.tokens_scanned_from('"pre $x mid $y end"').should be_like(
- [:DQPRE, {:value=>"pre ", :line=>1, :offset=>0, :pos=>1, :length=>6}],
- [:VARIABLE, {:value=>"x", :line=>1, :offset=>6, :pos=>7, :length=>1}],
- [:DQMID, {:value=>" mid ", :line=>1, :offset=>7, :pos=>8, :length=>6}],
- [:VARIABLE, {:value=>"y", :line=>1, :offset=>13, :pos=>14, :length=>1}],
- [:DQPOST, {:value=>" end", :line=>1, :offset=>14, :pos=>15, :length=>5}]
- )
- end
-
- it "should correctly position pre-mid-end interpolation using ${} " do
- EgrammarLexerSpec.tokens_scanned_from('"pre ${x} mid ${y} end"').should be_like(
- [:DQPRE, {:value=>"pre ", :line=>1, :offset=>0, :pos=>1, :length=>7}],
- [:VARIABLE, {:value=>"x", :line=>1, :offset=>7, :pos=>8, :length=>1}],
- [:DQMID, {:value=>" mid ", :line=>1, :offset=>8, :pos=>9, :length=>8}],
- [:VARIABLE, {:value=>"y", :line=>1, :offset=>16, :pos=>17, :length=>1}],
- [:DQPOST, {:value=>" end", :line=>1, :offset=>17, :pos=>18, :length=>6}]
- )
- end
-
- it "should correctly position pre-end interpolation using ${} with f call" do
- EgrammarLexerSpec.tokens_scanned_from('"pre ${x()} end"').should be_like(
- [:DQPRE, {:value=>"pre ", :line=>1, :offset=>0, :pos=>1, :length=>7}],
- [:NAME, {:value=>"x", :line=>1, :offset=>7, :pos=>8, :length=>1}],
- [:LPAREN, {:value=>"(", :line=>1, :offset=>8, :pos=>9, :length=>1}],
- [:RPAREN, {:value=>")", :line=>1, :offset=>9, :pos=>10, :length=>1}],
- [:DQPOST, {:value=>" end", :line=>1, :offset=>10, :pos=>11, :length=>6}]
- )
- end
-
- it "should correctly position pre-end interpolation using ${} with $x" do
- EgrammarLexerSpec.tokens_scanned_from('"pre ${$x} end"').should be_like(
- [:DQPRE, {:value=>"pre ", :line=>1, :offset=>0, :pos=>1, :length=>7}],
- [:VARIABLE, {:value=>"x", :line=>1, :offset=>7, :pos=>8, :length=>2}],
- [:DQPOST, {:value=>" end", :line=>1, :offset=>9, :pos=>10, :length=>6}]
- )
- end
-
- it "should correctly position pre-end interpolation across lines" do
- EgrammarLexerSpec.tokens_scanned_from(%Q["pre ${\n$x} end"]).should be_like(
- [:DQPRE, {:value=>"pre ", :line=>1, :offset=>0, :pos=>1, :length=>7}],
- [:VARIABLE, {:value=>"x", :line=>2, :offset=>8, :pos=>1, :length=>2}],
- [:DQPOST, {:value=>" end", :line=>2, :offset=>10, :pos=>3, :length=>6}]
- )
- end
-
- it "should correctly position interpolation across lines when strings have embedded newlines" do
- EgrammarLexerSpec.tokens_scanned_from(%Q["pre \n\n${$x}\n mid$y"]).should be_like(
- [:DQPRE, {:value=>"pre \n\n", :line=>1, :offset=>0, :pos=>1, :length=>9}],
- [:VARIABLE, {:value=>"x", :line=>3, :offset=>9, :pos=>3, :length=>2}],
- [:DQMID, {:value=>"\n mid", :line=>3, :offset=>11, :pos=>5, :length=>7}],
- [:VARIABLE, {:value=>"y", :line=>4, :offset=>18, :pos=>6, :length=>1}]
- )
- end
-end
diff --git a/spec/unit/pops/parser/parse_basic_expressions_spec.rb b/spec/unit/pops/parser/parse_basic_expressions_spec.rb
index f5aeb2a29..2190e54bb 100644
--- a/spec/unit/pops/parser/parse_basic_expressions_spec.rb
+++ b/spec/unit/pops/parser/parse_basic_expressions_spec.rb
@@ -134,6 +134,11 @@ describe "egrammar parsing basic expressions" do
it "$a = 'a' !~ 'b.*'" do; dump(parse("$a = 'a' !~ 'b.*'")).should == "(= $a (!~ 'a' 'b.*'))" ; end
end
+ context "When parsing unfold" do
+ it "$a = *[1,2]" do; dump(parse("$a = *[1,2]")).should == "(= $a (unfold ([] 1 2)))" ; end
+ it "$a = *1" do; dump(parse("$a = *1")).should == "(= $a (unfold 1))" ; end
+ end
+
context "When parsing Lists" do
it "$a = []" do
dump(parse("$a = []")).should == "(= $a ([]))"
diff --git a/spec/unit/pops/parser/parse_calls_spec.rb b/spec/unit/pops/parser/parse_calls_spec.rb
index 115c160d6..ee80544f5 100644
--- a/spec/unit/pops/parser/parse_calls_spec.rb
+++ b/spec/unit/pops/parser/parse_calls_spec.rb
@@ -66,7 +66,7 @@ describe "egrammar parsing function calls" do
# For egrammar where a bare word can be a "statement"
it "$a = foo bar # illegal, must have parentheses" do
- dump(parse("$a = foo bar")).should == "(block (= $a foo) bar)"
+ dump(parse("$a = foo bar")).should == "(block\n (= $a foo)\n bar\n)"
end
context "in nested scopes" do
@@ -94,8 +94,11 @@ describe "egrammar parsing function calls" do
end
it "$a.foo |$x|{ }" do
- dump(parse("$a.foo |$x|{ $b = $x}")).should ==
- "(call-method (. $a foo) (lambda (parameters x) (block (= $b $x))))"
+ dump(parse("$a.foo |$x|{ $b = $x}")).should == [
+ "(call-method (. $a foo) (lambda (parameters x) (block",
+ " (= $b $x)",
+ ")))"
+ ].join("\n")
end
end
end
diff --git a/spec/unit/pops/parser/parse_conditionals_spec.rb b/spec/unit/pops/parser/parse_conditionals_spec.rb
index b8b8d9c8e..591b20e97 100644
--- a/spec/unit/pops/parser/parse_conditionals_spec.rb
+++ b/spec/unit/pops/parser/parse_conditionals_spec.rb
@@ -30,10 +30,14 @@ describe "egrammar parsing conditionals" do
end
it "if true { $a = 10 $b = 10 } else {$a = 20}" do
- dump(parse("if true { $a = 10 $b = 20} else {$a = 20}")).should ==
- ["(if true",
- " (then (block (= $a 10) (= $b 20)))",
- " (else (= $a 20)))"].join("\n")
+ dump(parse("if true { $a = 10 $b = 20} else {$a = 20}")).should == [
+ "(if true",
+ " (then (block",
+ " (= $a 10)",
+ " (= $b 20)",
+ " ))",
+ " (else (= $a 20)))"
+ ].join("\n")
end
it "allows a parenthesized conditional expression" do
@@ -142,7 +146,10 @@ describe "egrammar parsing conditionals" do
it "case $a { a : {$b = 10 $c = 20}}" do
dump(parse("case $a { a : {$b = 10 $c = 20}}")).should ==
["(case $a",
- " (when (a) (then (block (= $b 10) (= $c 20)))))"
+ " (when (a) (then (block",
+ " (= $b 10)",
+ " (= $c 20)",
+ " ))))"
].join("\n")
end
end
diff --git a/spec/unit/pops/parser/parse_containers_spec.rb b/spec/unit/pops/parser/parse_containers_spec.rb
index 57d6efee9..a05c5975d 100644
--- a/spec/unit/pops/parser/parse_containers_spec.rb
+++ b/spec/unit/pops/parser/parse_containers_spec.rb
@@ -10,7 +10,12 @@ describe "egrammar parsing containers" do
context "When parsing file scope" do
it "$a = 10 $b = 20" do
- dump(parse("$a = 10 $b = 20")).should == "(block (= $a 10) (= $b 20))"
+ dump(parse("$a = 10 $b = 20")).should == [
+ "(block",
+ " (= $a 10)",
+ " (= $b 20)",
+ ")"
+ ].join("\n")
end
it "$a = 10" do
@@ -24,7 +29,11 @@ describe "egrammar parsing containers" do
end
it "class foo { class bar {} }" do
- dump(parse("class foo { class bar {}}")).should == "(class foo (block (class foo::bar ())))"
+ dump(parse("class foo { class bar {}}")).should == [
+ "(class foo (block",
+ " (class foo::bar ())",
+ "))"
+ ].join("\n")
end
it "class foo::bar {}" do
@@ -52,7 +61,12 @@ describe "egrammar parsing containers" do
end
it "class foo {$a = 10 $b = 20}" do
- dump(parse("class foo {$a = 10 $b = 20}")).should == "(class foo (block (= $a 10) (= $b 20)))"
+ dump(parse("class foo {$a = 10 $b = 20}")).should == [
+ "(class foo (block",
+ " (= $a 10)",
+ " (= $b 20)",
+ "))"
+ ].join("\n")
end
context "it should handle '3x weirdness'" do
@@ -100,6 +114,16 @@ describe "egrammar parsing containers" do
}.to raise_error(/not a valid classname/)
end
end
+
+ context 'it should allow keywords as attribute names' do
+ ['and', 'case', 'class', 'default', 'define', 'else', 'elsif', 'if', 'in', 'inherits', 'node', 'or',
+ 'undef', 'unless', 'type', 'attr', 'function', 'private'].each do |keyword|
+ it "such as #{keyword}" do
+ expect {parse("class x ($#{keyword}){} class { x: #{keyword} => 1 }")}.to_not raise_error
+ end
+ end
+ end
+
end
context "When the parser parses define" do
@@ -108,12 +132,20 @@ describe "egrammar parsing containers" do
end
it "class foo { define bar {}}" do
- dump(parse("class foo {define bar {}}")).should == "(class foo (block (define foo::bar ())))"
+ dump(parse("class foo {define bar {}}")).should == [
+ "(class foo (block",
+ " (define foo::bar ())",
+ "))"
+ ].join("\n")
end
it "define foo { define bar {}}" do
# This is illegal, but handled as part of validation
- dump(parse("define foo { define bar {}}")).should == "(define foo (block (define bar ())))"
+ dump(parse("define foo { define bar {}}")).should == [
+ "(define foo (block",
+ " (define bar ())",
+ "))"
+ ].join("\n")
end
it "define foo::bar {}" do
@@ -133,7 +165,12 @@ describe "egrammar parsing containers" do
end
it "define foo {$a = 10 $b = 20}" do
- dump(parse("define foo {$a = 10 $b = 20}")).should == "(define foo (block (= $a 10) (= $b 20)))"
+ dump(parse("define foo {$a = 10 $b = 20}")).should == [
+ "(define foo (block",
+ " (= $a 10)",
+ " (= $b 20)",
+ "))"
+ ].join("\n")
end
context "it should handle '3x weirdness'" do
@@ -152,6 +189,15 @@ describe "egrammar parsing containers" do
expect { dump(parse("define default {}")).should == "(define default ())"}.to raise_error(Puppet::ParseError)
end
end
+
+ context 'it should allow keywords as attribute names' do
+ ['and', 'case', 'class', 'default', 'define', 'else', 'elsif', 'if', 'in', 'inherits', 'node', 'or',
+ 'undef', 'unless', 'type', 'attr', 'function', 'private'].each do |keyword|
+ it "such as #{keyword}" do
+ expect {parse("define x ($#{keyword}){} x { y: #{keyword} => 1 }")}.to_not raise_error
+ end
+ end
+ end
end
context "When parsing node" do
@@ -159,6 +205,10 @@ describe "egrammar parsing containers" do
dump(parse("node foo {}")).should == "(node (matches 'foo') ())"
end
+ it "node foo, {} # trailing comma" do
+ dump(parse("node foo, {}")).should == "(node (matches 'foo') ())"
+ end
+
it "node kermit.example.com {}" do
dump(parse("node kermit.example.com {}")).should == "(node (matches 'kermit.example.com') ())"
end
@@ -200,7 +250,12 @@ describe "egrammar parsing containers" do
end
it "node foo inherits bar {$a = 10 $b = 20}" do
- dump(parse("node foo inherits bar {$a = 10 $b = 20}")).should == "(node (matches 'foo') (parent 'bar') (block (= $a 10) (= $b 20)))"
+ dump(parse("node foo inherits bar {$a = 10 $b = 20}")).should == [
+ "(node (matches 'foo') (parent 'bar') (block",
+ " (= $a 10)",
+ " (= $b 20)",
+ "))"
+ ].join("\n")
end
end
end
diff --git a/spec/unit/pops/parser/parse_resource_spec.rb b/spec/unit/pops/parser/parse_resource_spec.rb
index ee7e13445..cf7e7cb01 100644
--- a/spec/unit/pops/parser/parse_resource_spec.rb
+++ b/spec/unit/pops/parser/parse_resource_spec.rb
@@ -9,72 +9,98 @@ describe "egrammar parsing resource declarations" do
include ParserRspecHelper
context "When parsing regular resource" do
- it "file { 'title': }" do
- dump(parse("file { 'title': }")).should == [
- "(resource file",
- " ('title'))"
- ].join("\n")
- end
+ ["File", "file"].each do |word|
+ it "#{word} { 'title': }" do
+ dump(parse("#{word} { 'title': }")).should == [
+ "(resource file",
+ " ('title'))"
+ ].join("\n")
+ end
- it "file { 'title': path => '/somewhere', mode => 0777}" do
- dump(parse("file { 'title': path => '/somewhere', mode => 0777}")).should == [
- "(resource file",
- " ('title'",
- " (path => '/somewhere')",
- " (mode => 0777)))"
- ].join("\n")
- end
+ it "#{word} { 'title': path => '/somewhere', mode => '0777'}" do
+ dump(parse("#{word} { 'title': path => '/somewhere', mode => '0777'}")).should == [
+ "(resource file",
+ " ('title'",
+ " (path => '/somewhere')",
+ " (mode => '0777')))"
+ ].join("\n")
+ end
- it "file { 'title': path => '/somewhere', }" do
- dump(parse("file { 'title': path => '/somewhere', }")).should == [
- "(resource file",
- " ('title'",
- " (path => '/somewhere')))"
- ].join("\n")
- end
+ it "#{word} { 'title': path => '/somewhere', }" do
+ dump(parse("#{word} { 'title': path => '/somewhere', }")).should == [
+ "(resource file",
+ " ('title'",
+ " (path => '/somewhere')))"
+ ].join("\n")
+ end
- it "file { 'title': , }" do
- dump(parse("file { 'title': , }")).should == [
- "(resource file",
- " ('title'))"
- ].join("\n")
- end
+ it "#{word} { 'title': , }" do
+ dump(parse("#{word} { 'title': , }")).should == [
+ "(resource file",
+ " ('title'))"
+ ].join("\n")
+ end
- it "file { 'title': ; }" do
- dump(parse("file { 'title': ; }")).should == [
- "(resource file",
- " ('title'))"
- ].join("\n")
- end
+ it "#{word} { 'title': ; }" do
+ dump(parse("#{word} { 'title': ; }")).should == [
+ "(resource file",
+ " ('title'))"
+ ].join("\n")
+ end
- it "file { 'title': ; 'other_title': }" do
- dump(parse("file { 'title': ; 'other_title': }")).should == [
- "(resource file",
- " ('title')",
- " ('other_title'))"
- ].join("\n")
- end
+ it "#{word} { 'title': ; 'other_title': }" do
+ dump(parse("#{word} { 'title': ; 'other_title': }")).should == [
+ "(resource file",
+ " ('title')",
+ " ('other_title'))"
+ ].join("\n")
+ end
- it "file { 'title1': path => 'x'; 'title2': path => 'y'}" do
- dump(parse("file { 'title1': path => 'x'; 'title2': path => 'y'}")).should == [
- "(resource file",
- " ('title1'",
- " (path => 'x'))",
- " ('title2'",
- " (path => 'y')))",
- ].join("\n")
+ # PUP-2898, trailing ';'
+ it "#{word} { 'title': ; 'other_title': ; }" do
+ dump(parse("#{word} { 'title': ; 'other_title': ; }")).should == [
+ "(resource file",
+ " ('title')",
+ " ('other_title'))"
+ ].join("\n")
+ end
+
+ it "#{word} { 'title1': path => 'x'; 'title2': path => 'y'}" do
+ dump(parse("#{word} { 'title1': path => 'x'; 'title2': path => 'y'}")).should == [
+ "(resource file",
+ " ('title1'",
+ " (path => 'x'))",
+ " ('title2'",
+ " (path => 'y')))",
+ ].join("\n")
+ end
+
+ it "#{word} { title: * => {mode => '0777'} }" do
+ dump(parse("#{word} { title: * => {mode => '0777'}}")).should == [
+ "(resource file",
+ " (title",
+ " (* => ({} (mode '0777')))))"
+ ].join("\n")
+ end
end
end
- context "When parsing resource defaults" do
+ context "When parsing (type based) resource defaults" do
it "File { }" do
dump(parse("File { }")).should == "(resource-defaults file)"
end
- it "File { mode => 0777 }" do
- dump(parse("File { mode => 0777}")).should == [
+ it "File { mode => '0777' }" do
+ dump(parse("File { mode => '0777'}")).should == [
"(resource-defaults file",
- " (mode => 0777))"
+ " (mode => '0777'))"
+ ].join("\n")
+ end
+
+ it "File { * => {mode => '0777'} } (even if validated to be illegal)" do
+ dump(parse("File { * => {mode => '0777'}}")).should == [
+ "(resource-defaults file",
+ " (* => ({} (mode '0777'))))"
].join("\n")
end
end
@@ -85,36 +111,92 @@ describe "egrammar parsing resource declarations" do
end
it "File['x'] { x => 1 }" do
- dump(parse("File['x'] { x => 1}")).should == "(override (slice file 'x')\n (x => 1))"
+ dump(parse("File['x'] { x => 1}")).should == [
+ "(override (slice file 'x')",
+ " (x => 1))"
+ ].join("\n")
end
+
it "File['x', 'y'] { x => 1 }" do
- dump(parse("File['x', 'y'] { x => 1}")).should == "(override (slice file ('x' 'y'))\n (x => 1))"
+ dump(parse("File['x', 'y'] { x => 1}")).should == [
+ "(override (slice file ('x' 'y'))",
+ " (x => 1))"
+ ].join("\n")
end
it "File['x'] { x => 1, y => 2 }" do
- dump(parse("File['x'] { x => 1, y=> 2}")).should == "(override (slice file 'x')\n (x => 1)\n (y => 2))"
+ dump(parse("File['x'] { x => 1, y=> 2}")).should == [
+ "(override (slice file 'x')",
+ " (x => 1)",
+ " (y => 2))"
+ ].join("\n")
end
it "File['x'] { x +> 1 }" do
- dump(parse("File['x'] { x +> 1}")).should == "(override (slice file 'x')\n (x +> 1))"
+ dump(parse("File['x'] { x +> 1}")).should == [
+ "(override (slice file 'x')",
+ " (x +> 1))"
+ ].join("\n")
+ end
+
+ it "File['x'] { * => {mode => '0777'} } (even if validated to be illegal)" do
+ dump(parse("File['x'] { * => {mode => '0777'}}")).should == [
+ "(override (slice file 'x')",
+ " (* => ({} (mode '0777'))))"
+ ].join("\n")
end
end
context "When parsing virtual and exported resources" do
- it "@@file { 'title': }" do
+ it "parses exported @@file { 'title': }" do
dump(parse("@@file { 'title': }")).should == "(exported-resource file\n ('title'))"
end
- it "@file { 'title': }" do
+ it "parses virtual @file { 'title': }" do
dump(parse("@file { 'title': }")).should == "(virtual-resource file\n ('title'))"
end
- it "@file { mode => 0777 }" do
- # Defaults are not virtualizeable
- expect {
- dump(parse("@file { mode => 0777 }")).should == ""
- }.to raise_error(Puppet::ParseError, /Defaults are not virtualizable/)
+ it "nothing before the title colon is a syntax error" do
+ expect do
+ parse("@file {: mode => '0777' }")
+ end.to raise_error(/Syntax error/)
+ end
+
+ it "raises error for user error; not a resource" do
+ # The expression results in VIRTUAL, CALL FUNCTION('file', HASH) since the resource body has
+ # no title.
+ expect do
+ parse("@file { mode => '0777' }")
+ end.to raise_error(/Virtual \(@\) can only be applied to a Resource Expression/)
+ end
+
+ it "parses global defaults with @ (even if validated to be illegal)" do
+ dump(parse("@File { mode => '0777' }")).should == [
+ "(virtual-resource-defaults file",
+ " (mode => '0777'))"
+ ].join("\n")
+ end
+
+ it "parses global defaults with @@ (even if validated to be illegal)" do
+ dump(parse("@@File { mode => '0777' }")).should == [
+ "(exported-resource-defaults file",
+ " (mode => '0777'))"
+ ].join("\n")
+ end
+
+ it "parses override with @ (even if validated to be illegal)" do
+ dump(parse("@File[foo] { mode => '0777' }")).should == [
+ "(virtual-override (slice file foo)",
+ " (mode => '0777'))"
+ ].join("\n")
+ end
+
+ it "parses override combined with @@ (even if validated to be illegal)" do
+ dump(parse("@@File[foo] { mode => '0777' }")).should == [
+ "(exported-override (slice file foo)",
+ " (mode => '0777'))"
+ ].join("\n")
end
end
@@ -220,22 +302,22 @@ describe "egrammar parsing resource declarations" do
dump(parse("File <| tag == 'foo' |>")).should == "(collect file\n (<| |> (== tag 'foo')))"
end
- it "File <| tag == 'foo' and mode != 0777 |>" do
- dump(parse("File <| tag == 'foo' and mode != 0777 |>")).should == "(collect file\n (<| |> (&& (== tag 'foo') (!= mode 0777))))"
+ it "File <| tag == 'foo' and mode != '0777' |>" do
+ dump(parse("File <| tag == 'foo' and mode != '0777' |>")).should == "(collect file\n (<| |> (&& (== tag 'foo') (!= mode '0777'))))"
end
- it "File <| tag == 'foo' or mode != 0777 |>" do
- dump(parse("File <| tag == 'foo' or mode != 0777 |>")).should == "(collect file\n (<| |> (|| (== tag 'foo') (!= mode 0777))))"
+ it "File <| tag == 'foo' or mode != '0777' |>" do
+ dump(parse("File <| tag == 'foo' or mode != '0777' |>")).should == "(collect file\n (<| |> (|| (== tag 'foo') (!= mode '0777'))))"
end
- it "File <| tag == 'foo' or tag == 'bar' and mode != 0777 |>" do
- dump(parse("File <| tag == 'foo' or tag == 'bar' and mode != 0777 |>")).should ==
- "(collect file\n (<| |> (|| (== tag 'foo') (&& (== tag 'bar') (!= mode 0777)))))"
+ it "File <| tag == 'foo' or tag == 'bar' and mode != '0777' |>" do
+ dump(parse("File <| tag == 'foo' or tag == 'bar' and mode != '0777' |>")).should ==
+ "(collect file\n (<| |> (|| (== tag 'foo') (&& (== tag 'bar') (!= mode '0777')))))"
end
- it "File <| (tag == 'foo' or tag == 'bar') and mode != 0777 |>" do
- dump(parse("File <| (tag == 'foo' or tag == 'bar') and mode != 0777 |>")).should ==
- "(collect file\n (<| |> (&& (|| (== tag 'foo') (== tag 'bar')) (!= mode 0777))))"
+ it "File <| (tag == 'foo' or tag == 'bar') and mode != '0777' |>" do
+ dump(parse("File <| (tag == 'foo' or tag == 'bar') and mode != '0777' |>")).should ==
+ "(collect file\n (<| |> (&& (|| (== tag 'foo') (== tag 'bar')) (!= mode '0777'))))"
end
end
end
diff --git a/spec/unit/pops/parser/parser_spec.rb b/spec/unit/pops/parser/parser_spec.rb
index fb44a08c9..23f537eeb 100644
--- a/spec/unit/pops/parser/parser_spec.rb
+++ b/spec/unit/pops/parser/parser_spec.rb
@@ -14,4 +14,20 @@ describe Puppet::Pops::Parser::Parser do
model.body.class.should == Puppet::Pops::Model::AssignmentExpression
end
+ it "should accept empty input and return a model" do
+ parser = Puppet::Pops::Parser::Parser.new()
+ model = parser.parse_string("").current
+ model.class.should == Puppet::Pops::Model::Program
+ model.body.class.should == Puppet::Pops::Model::Nop
+ end
+
+ it "should accept empty input containing only comments and report location at end of input" do
+ parser = Puppet::Pops::Parser::Parser.new()
+ model = parser.parse_string("# comment\n").current
+ model.class.should == Puppet::Pops::Model::Program
+ model.body.class.should == Puppet::Pops::Model::Nop
+ adapter = Puppet::Pops::Adapters::SourcePosAdapter.adapt(model.body)
+ expect(adapter.offset).to eq(10)
+ expect(adapter.length).to eq(0)
+ end
end
diff --git a/spec/unit/pops/parser/parsing_typed_parameters_spec.rb b/spec/unit/pops/parser/parsing_typed_parameters_spec.rb
new file mode 100644
index 000000000..678f6523c
--- /dev/null
+++ b/spec/unit/pops/parser/parsing_typed_parameters_spec.rb
@@ -0,0 +1,72 @@
+require 'spec_helper'
+
+require 'puppet/pops'
+require 'puppet/pops/evaluator/evaluator_impl'
+require 'puppet_spec/pops'
+require 'puppet_spec/scope'
+require 'puppet/parser/e4_parser_adapter'
+
+
+# relative to this spec file (./) does not work as this file is loaded by rspec
+#require File.join(File.dirname(__FILE__), '/evaluator_rspec_helper')
+
+describe 'Puppet::Pops::Evaluator::EvaluatorImpl' do
+ include PuppetSpec::Pops
+ include PuppetSpec::Scope
+ before(:each) do
+
+ # These must be set since the is 3x logic that triggers on these even if the tests are explicit
+ # about selection of parser and evaluator
+ #
+ Puppet[:parser] = 'future'
+ end
+
+ let(:parser) { Puppet::Pops::Parser::EvaluatingParser.new }
+
+ context "captures-rest parameter" do
+ it 'is allowed in lambda when placed last' do
+ source = <<-CODE
+ foo() |$a, *$b| { $a + $b[0] }
+ CODE
+ expect do
+ parser.parse_string(source, __FILE__)
+ end.to_not raise_error()
+ end
+
+ it 'allows a type annotation' do
+ source = <<-CODE
+ foo() |$a, Integer *$b| { $a + $b[0] }
+ CODE
+ expect do
+ parser.parse_string(source, __FILE__)
+ end.to_not raise_error()
+ end
+
+ it 'is not allowed in lambda except last' do
+ source = <<-CODE
+ foo() |*$a, $b| { $a + $b[0] }
+ CODE
+ expect do
+ parser.parse_string(source, __FILE__)
+ end.to raise_error(Puppet::ParseError, /Parameter \$a is not last, and has 'captures rest'/)
+ end
+
+ it 'is not allowed in define' do
+ source = <<-CODE
+ define foo(*$a) { }
+ CODE
+ expect do
+ parser.parse_string(source, __FILE__)
+ end.to raise_error(Puppet::ParseError, /Parameter \$a has 'captures rest' - not supported in a 'define'/)
+ end
+
+ it 'is not allowed in class' do
+ source = <<-CODE
+ class foo(*$a) { }
+ CODE
+ expect do
+ parser.parse_string(source, __FILE__)
+ end.to raise_error(Puppet::ParseError, /Parameter \$a has 'captures rest' - not supported in a Host Class Definition/)
+ end
+ end
+end
diff --git a/spec/unit/pops/transformer/transform_calls_spec.rb b/spec/unit/pops/transformer/transform_calls_spec.rb
index f58c79e1e..dba0d560f 100644
--- a/spec/unit/pops/transformer/transform_calls_spec.rb
+++ b/spec/unit/pops/transformer/transform_calls_spec.rb
@@ -34,6 +34,7 @@ describe "transformation to Puppet AST for function calls" do
"realize bar" => '(invoke realize bar)',
"contain bar" => '(invoke contain bar)',
"include bar" => '(invoke include bar)',
+ "tag bar" => '(invoke tag bar)',
"info bar" => '(invoke info bar)',
"notice bar" => '(invoke notice bar)',
@@ -54,7 +55,6 @@ describe "transformation to Puppet AST for function calls" do
{
"foo bar" => '(block foo bar)',
- "tag bar" => '(block tag bar)',
"tag" => 'tag',
}.each do |source, result|
it "should not transform #{source}, and instead produce #{result}" do
diff --git a/spec/unit/pops/transformer/transform_resource_spec.rb b/spec/unit/pops/transformer/transform_resource_spec.rb
deleted file mode 100644
index 251ef2f75..000000000
--- a/spec/unit/pops/transformer/transform_resource_spec.rb
+++ /dev/null
@@ -1,185 +0,0 @@
-#! /usr/bin/env ruby
-require 'spec_helper'
-require 'puppet/pops'
-
-# relative to this spec file (./) does not work as this file is loaded by rspec
-require File.join(File.dirname(__FILE__), '/transformer_rspec_helper')
-
-describe "transformation to Puppet AST for resource declarations" do
- include TransformerRspecHelper
-
- context "When transforming regular resource" do
- it "file { 'title': }" do
- astdump(parse("file { 'title': }")).should == [
- "(resource file",
- " ('title'))"
- ].join("\n")
- end
-
- it "file { 'title': ; 'other_title': }" do
- astdump(parse("file { 'title': ; 'other_title': }")).should == [
- "(resource file",
- " ('title')",
- " ('other_title'))"
- ].join("\n")
- end
-
- it "file { 'title': path => '/somewhere', mode => 0777}" do
- astdump(parse("file { 'title': path => '/somewhere', mode => 0777}")).should == [
- "(resource file",
- " ('title'",
- " (path => '/somewhere')",
- " (mode => 0777)))"
- ].join("\n")
- end
-
- it "file { 'title1': path => 'x'; 'title2': path => 'y'}" do
- astdump(parse("file { 'title1': path => 'x'; 'title2': path => 'y'}")).should == [
- "(resource file",
- " ('title1'",
- " (path => 'x'))",
- " ('title2'",
- " (path => 'y')))",
- ].join("\n")
- end
- end
-
- context "When transforming resource defaults" do
- it "File { }" do
- astdump(parse("File { }")).should == "(resource-defaults file)"
- end
-
- it "File { mode => 0777 }" do
- astdump(parse("File { mode => 0777}")).should == [
- "(resource-defaults file",
- " (mode => 0777))"
- ].join("\n")
- end
- end
-
- context "When transforming resource override" do
- it "File['x'] { }" do
- astdump(parse("File['x'] { }")).should == "(override (slice file 'x'))"
- end
-
- it "File['x'] { x => 1 }" do
- astdump(parse("File['x'] { x => 1}")).should == "(override (slice file 'x')\n (x => 1))"
- end
-
- it "File['x', 'y'] { x => 1 }" do
- astdump(parse("File['x', 'y'] { x => 1}")).should == "(override (slice file ('x' 'y'))\n (x => 1))"
- end
-
- it "File['x'] { x => 1, y => 2 }" do
- astdump(parse("File['x'] { x => 1, y=> 2}")).should == "(override (slice file 'x')\n (x => 1)\n (y => 2))"
- end
-
- it "File['x'] { x +> 1 }" do
- astdump(parse("File['x'] { x +> 1}")).should == "(override (slice file 'x')\n (x +> 1))"
- end
- end
-
- context "When transforming virtual and exported resources" do
- it "@@file { 'title': }" do
- astdump(parse("@@file { 'title': }")).should == "(exported-resource file\n ('title'))"
- end
-
- it "@file { 'title': }" do
- astdump(parse("@file { 'title': }")).should == "(virtual-resource file\n ('title'))"
- end
- end
-
- context "When transforming class resource" do
- it "class { 'cname': }" do
- astdump(parse("class { 'cname': }")).should == [
- "(resource class",
- " ('cname'))"
- ].join("\n")
- end
-
- it "class { 'cname': x => 1, y => 2}" do
- astdump(parse("class { 'cname': x => 1, y => 2}")).should == [
- "(resource class",
- " ('cname'",
- " (x => 1)",
- " (y => 2)))"
- ].join("\n")
- end
-
- it "class { 'cname1': x => 1; 'cname2': y => 2}" do
- astdump(parse("class { 'cname1': x => 1; 'cname2': y => 2}")).should == [
- "(resource class",
- " ('cname1'",
- " (x => 1))",
- " ('cname2'",
- " (y => 2)))",
- ].join("\n")
- end
- end
-
- context "When transforming Relationships" do
- it "File[a] -> File[b]" do
- astdump(parse("File[a] -> File[b]")).should == "(-> (slice file a) (slice file b))"
- end
-
- it "File[a] <- File[b]" do
- astdump(parse("File[a] <- File[b]")).should == "(<- (slice file a) (slice file b))"
- end
-
- it "File[a] ~> File[b]" do
- astdump(parse("File[a] ~> File[b]")).should == "(~> (slice file a) (slice file b))"
- end
-
- it "File[a] <~ File[b]" do
- astdump(parse("File[a] <~ File[b]")).should == "(<~ (slice file a) (slice file b))"
- end
-
- it "Should chain relationships" do
- astdump(parse("a -> b -> c")).should ==
- "(-> (-> a b) c)"
- end
-
- it "Should chain relationships" do
- astdump(parse("File[a] -> File[b] ~> File[c] <- File[d] <~ File[e]")).should ==
- "(<~ (<- (~> (-> (slice file a) (slice file b)) (slice file c)) (slice file d)) (slice file e))"
- end
- end
-
- context "When transforming collection" do
- context "of virtual resources" do
- it "File <| |>" do
- astdump(parse("File <| |>")).should == "(collect file\n (<| |>))"
- end
- end
-
- context "of exported resources" do
- it "File <<| |>>" do
- astdump(parse("File <<| |>>")).should == "(collect file\n (<<| |>>))"
- end
- end
-
- context "queries are parsed with correct precedence" do
- it "File <| tag == 'foo' |>" do
- astdump(parse("File <| tag == 'foo' |>")).should == "(collect file\n (<| |> (== tag 'foo')))"
- end
-
- it "File <| tag == 'foo' and mode != 0777 |>" do
- astdump(parse("File <| tag == 'foo' and mode != 0777 |>")).should == "(collect file\n (<| |> (&& (== tag 'foo') (!= mode 0777))))"
- end
-
- it "File <| tag == 'foo' or mode != 0777 |>" do
- astdump(parse("File <| tag == 'foo' or mode != 0777 |>")).should == "(collect file\n (<| |> (|| (== tag 'foo') (!= mode 0777))))"
- end
-
- it "File <| tag == 'foo' or tag == 'bar' and mode != 0777 |>" do
- astdump(parse("File <| tag == 'foo' or tag == 'bar' and mode != 0777 |>")).should ==
- "(collect file\n (<| |> (|| (== tag 'foo') (&& (== tag 'bar') (!= mode 0777)))))"
- end
-
- it "File <| (tag == 'foo' or tag == 'bar') and mode != 0777 |>" do
- astdump(parse("File <| (tag == 'foo' or tag == 'bar') and mode != 0777 |>")).should ==
- "(collect file\n (<| |> (&& (|| (== tag 'foo') (== tag 'bar')) (!= mode 0777))))"
- end
- end
- end
-end
diff --git a/spec/unit/pops/types/type_calculator_spec.rb b/spec/unit/pops/types/type_calculator_spec.rb
index 3c4ea77ca..0bd475263 100644
--- a/spec/unit/pops/types/type_calculator_spec.rb
+++ b/spec/unit/pops/types/type_calculator_spec.rb
@@ -78,8 +78,13 @@ describe 'The type calculator' do
Puppet::Pops::Types::TypeFactory.struct(type_hash)
end
- def optional_object_t
- Puppet::Pops::Types::TypeFactory.optional_object()
+ def object_t
+ Puppet::Pops::Types::TypeFactory.any()
+ end
+
+ def unit_t
+ # Cannot be created via factory, the type is private to the type system
+ Puppet::Pops::Types::PUnitType.new
end
def types
@@ -88,8 +93,9 @@ describe 'The type calculator' do
shared_context "types_setup" do
+ # Do not include the special type Unit in this list
def all_types
- [ Puppet::Pops::Types::PObjectType,
+ [ Puppet::Pops::Types::PAnyType,
Puppet::Pops::Types::PNilType,
Puppet::Pops::Types::PDataType,
Puppet::Pops::Types::PScalarType,
@@ -102,7 +108,7 @@ describe 'The type calculator' do
Puppet::Pops::Types::PCollectionType,
Puppet::Pops::Types::PArrayType,
Puppet::Pops::Types::PHashType,
- Puppet::Pops::Types::PRubyType,
+ Puppet::Pops::Types::PRuntimeType,
Puppet::Pops::Types::PHostClassType,
Puppet::Pops::Types::PResourceType,
Puppet::Pops::Types::PPatternType,
@@ -111,6 +117,9 @@ describe 'The type calculator' do
Puppet::Pops::Types::PStructType,
Puppet::Pops::Types::PTupleType,
Puppet::Pops::Types::PCallableType,
+ Puppet::Pops::Types::PType,
+ Puppet::Pops::Types::POptionalType,
+ Puppet::Pops::Types::PDefaultType,
]
end
@@ -215,17 +224,18 @@ describe 'The type calculator' do
calculator.infer(nil).class.should == Puppet::Pops::Types::PNilType
end
- it ':undef translates to PNilType' do
- calculator.infer(:undef).class.should == Puppet::Pops::Types::PNilType
+ it ':undef translates to PRuntimeType' do
+ calculator.infer(:undef).class.should == Puppet::Pops::Types::PRuntimeType
end
- it 'an instance of class Foo translates to PRubyType[Foo]' do
+ it 'an instance of class Foo translates to PRuntimeType[ruby, Foo]' do
class Foo
end
t = calculator.infer(Foo.new)
- t.class.should == Puppet::Pops::Types::PRubyType
- t.ruby_class.should == 'Foo'
+ t.class.should == Puppet::Pops::Types::PRuntimeType
+ t.runtime.should == :ruby
+ t.runtime_type_name.should == 'Foo'
end
context 'array' do
@@ -278,8 +288,8 @@ describe 'The type calculator' do
calculator.infer(['one', /two/]).element_type.class.should == Puppet::Pops::Types::PScalarType
end
- it 'with string and symbol values translates to PArrayType[PObjectType]' do
- calculator.infer(['one', :two]).element_type.class.should == Puppet::Pops::Types::PObjectType
+ it 'with string and symbol values translates to PArrayType[PAnyType]' do
+ calculator.infer(['one', :two]).element_type.class.should == Puppet::Pops::Types::PAnyType
end
it 'with fixnum and nil values translates to PArrayType[PIntegerType]' do
@@ -328,17 +338,18 @@ describe 'The type calculator' do
calculator.infer({:first => 1, :second => 2}).class.should == Puppet::Pops::Types::PHashType
end
- it 'with symbolic keys translates to PHashType[PRubyType[Symbol],value]' do
+ it 'with symbolic keys translates to PHashType[PRuntimeType[ruby, Symbol], value]' do
k = calculator.infer({:first => 1, :second => 2}).key_type
- k.class.should == Puppet::Pops::Types::PRubyType
- k.ruby_class.should == 'Symbol'
+ k.class.should == Puppet::Pops::Types::PRuntimeType
+ k.runtime.should == :ruby
+ k.runtime_type_name.should == 'Symbol'
end
- it 'with string keys translates to PHashType[PStringType,value]' do
+ it 'with string keys translates to PHashType[PStringType, value]' do
calculator.infer({'first' => 1, 'second' => 2}).key_type.class.should == Puppet::Pops::Types::PStringType
end
- it 'with fixnum values translates to PHashType[key,PIntegerType]' do
+ it 'with fixnum values translates to PHashType[key, PIntegerType]' do
calculator.infer({:first => 1, :second => 2}).element_type.class.should == Puppet::Pops::Types::PIntegerType
end
end
@@ -457,14 +468,14 @@ describe 'The type calculator' do
expect(common_t.block_type).to be_nil
end
- it 'compatible instances => the least specific' do
+ it 'compatible instances => the most specific' do
t1 = callable_t(String)
scalar_t = Puppet::Pops::Types::PScalarType.new
t2 = callable_t(scalar_t)
common_t = calculator.common_type(t1, t2)
expect(common_t.class).to be(Puppet::Pops::Types::PCallableType)
expect(common_t.param_types.class).to be(Puppet::Pops::Types::PTupleType)
- expect(common_t.param_types.types).to eql([scalar_t])
+ expect(common_t.param_types.types).to eql([string_t])
expect(common_t.block_type).to be_nil
end
@@ -491,15 +502,33 @@ describe 'The type calculator' do
context 'computes assignability' do
include_context "types_setup"
- context "for Object, such that" do
- it 'all types are assignable to Object' do
- t = Puppet::Pops::Types::PObjectType.new()
+ context 'for Unit, such that' do
+ it 'all types are assignable to Unit' do
+ t = Puppet::Pops::Types::PUnitType.new()
+ all_types.each { |t2| t2.new.should be_assignable_to(t) }
+ end
+
+ it 'Unit is assignable to all other types' do
+ t = Puppet::Pops::Types::PUnitType.new()
+ all_types.each { |t2| t.should be_assignable_to(t2.new) }
+ end
+
+ it 'Unit is assignable to Unit' do
+ t = Puppet::Pops::Types::PUnitType.new()
+ t2 = Puppet::Pops::Types::PUnitType.new()
+ t.should be_assignable_to(t2)
+ end
+ end
+
+ context "for Any, such that" do
+ it 'all types are assignable to Any' do
+ t = Puppet::Pops::Types::PAnyType.new()
all_types.each { |t2| t2.new.should be_assignable_to(t) }
end
- it 'Object is not assignable to anything but Object' do
- tested_types = all_types() - [Puppet::Pops::Types::PObjectType]
- t = Puppet::Pops::Types::PObjectType.new()
+ it 'Any is not assignable to anything but Any' do
+ tested_types = all_types() - [Puppet::Pops::Types::PAnyType]
+ t = Puppet::Pops::Types::PAnyType.new()
tested_types.each { |t2| t.should_not be_assignable_to(t2.new) }
end
end
@@ -530,7 +559,7 @@ describe 'The type calculator' do
end
it 'Data is not assignable to any disjunct type' do
- tested_types = all_types - [Puppet::Pops::Types::PObjectType, Puppet::Pops::Types::PDataType] - scalar_types
+ tested_types = all_types - [Puppet::Pops::Types::PAnyType, Puppet::Pops::Types::PDataType] - scalar_types
t = Puppet::Pops::Types::PDataType.new()
tested_types.each {|t2| t.should_not be_assignable_to(t2.new) }
end
@@ -549,7 +578,7 @@ describe 'The type calculator' do
end
it 'Scalar is not assignable to any disjunct type' do
- tested_types = all_types - [Puppet::Pops::Types::PObjectType, Puppet::Pops::Types::PDataType] - scalar_types
+ tested_types = all_types - [Puppet::Pops::Types::PAnyType, Puppet::Pops::Types::PDataType] - scalar_types
t = Puppet::Pops::Types::PScalarType.new()
tested_types.each {|t2| t.should_not be_assignable_to(t2.new) }
end
@@ -569,7 +598,7 @@ describe 'The type calculator' do
it 'Numeric is not assignable to any disjunct type' do
tested_types = all_types - [
- Puppet::Pops::Types::PObjectType,
+ Puppet::Pops::Types::PAnyType,
Puppet::Pops::Types::PDataType,
Puppet::Pops::Types::PScalarType,
] - numeric_types
@@ -591,7 +620,7 @@ describe 'The type calculator' do
end
it 'Collection is not assignable to any disjunct type' do
- tested_types = all_types - [Puppet::Pops::Types::PObjectType] - collection_types
+ tested_types = all_types - [Puppet::Pops::Types::PAnyType] - collection_types
t = Puppet::Pops::Types::PCollectionType.new()
tested_types.each {|t2| t.should_not be_assignable_to(t2.new) }
end
@@ -609,7 +638,7 @@ describe 'The type calculator' do
it 'Array is not assignable to any disjunct type' do
tested_types = all_types - [
- Puppet::Pops::Types::PObjectType,
+ Puppet::Pops::Types::PAnyType,
Puppet::Pops::Types::PDataType] - collection_types
t = Puppet::Pops::Types::PArrayType.new()
tested_types.each {|t2| t.should_not be_assignable_to(t2.new) }
@@ -628,7 +657,7 @@ describe 'The type calculator' do
it 'Hash is not assignable to any disjunct type' do
tested_types = all_types - [
- Puppet::Pops::Types::PObjectType,
+ Puppet::Pops::Types::PAnyType,
Puppet::Pops::Types::PDataType] - collection_types
t = Puppet::Pops::Types::PHashType.new()
tested_types.each {|t2| t.should_not be_assignable_to(t2.new) }
@@ -647,7 +676,7 @@ describe 'The type calculator' do
it 'Tuple is not assignable to any disjunct type' do
tested_types = all_types - [
- Puppet::Pops::Types::PObjectType,
+ Puppet::Pops::Types::PAnyType,
Puppet::Pops::Types::PDataType] - collection_types
t = Puppet::Pops::Types::PTupleType.new()
tested_types.each {|t2| t.should_not be_assignable_to(t2.new) }
@@ -666,7 +695,7 @@ describe 'The type calculator' do
it 'Struct is not assignable to any disjunct type' do
tested_types = all_types - [
- Puppet::Pops::Types::PObjectType,
+ Puppet::Pops::Types::PAnyType,
Puppet::Pops::Types::PDataType] - collection_types
t = Puppet::Pops::Types::PStructType.new()
tested_types.each {|t2| t.should_not be_assignable_to(t2.new) }
@@ -678,7 +707,7 @@ describe 'The type calculator' do
t = Puppet::Pops::Types::PCallableType.new()
tested_types = all_types - [
Puppet::Pops::Types::PCallableType,
- Puppet::Pops::Types::PObjectType]
+ Puppet::Pops::Types::PAnyType]
tested_types.each {|t2| t.should_not be_assignable_to(t2.new) }
end
end
@@ -793,9 +822,50 @@ describe 'The type calculator' do
calculator.assignable?(pattern, enum).should == true
end
+ it 'pattern should accept a variant where all variants are acceptable' do
+ pattern = pattern_t(/^\w+$/)
+ calculator.assignable?(pattern, variant_t(string_t('a'), string_t('b'))).should == true
+ end
+
+ end
+
+ context 'when dealing with enums' do
+ it 'should accept a string with matching content' do
+ calculator.assignable?(enum_t('a', 'b'), string_t('a')).should == true
+ calculator.assignable?(enum_t('a', 'b'), string_t('b')).should == true
+ calculator.assignable?(enum_t('a', 'b'), string_t('c')).should == false
+ end
+
+ it 'should accept an enum with matching enum' do
+ calculator.assignable?(enum_t('a', 'b'), enum_t('a', 'b')).should == true
+ calculator.assignable?(enum_t('a', 'b'), enum_t('a')).should == true
+ calculator.assignable?(enum_t('a', 'b'), enum_t('c')).should == false
+ end
+
+ it 'enum should accept a variant where all variants are acceptable' do
+ enum = enum_t('a', 'b')
+ calculator.assignable?(enum, variant_t(string_t('a'), string_t('b'))).should == true
+ end
end
context 'when dealing with tuples' do
+ it 'matches empty tuples' do
+ tuple1 = tuple_t()
+ tuple2 = tuple_t()
+
+ calculator.assignable?(tuple1, tuple2).should == true
+ calculator.assignable?(tuple2, tuple1).should == true
+ end
+
+ it 'accepts an empty tuple as assignable to a tuple with a min size of 0' do
+ tuple1 = tuple_t(Object)
+ factory.constrain_size(tuple1, 0, :default)
+ tuple2 = tuple_t()
+
+ calculator.assignable?(tuple1, tuple2).should == true
+ calculator.assignable?(tuple2, tuple1).should == false
+ end
+
it 'should accept matching tuples' do
tuple1 = tuple_t(1,2)
tuple2 = tuple_t(Integer,Integer)
@@ -859,6 +929,17 @@ describe 'The type calculator' do
calculator.assignable?(tuple1, array).should == true
calculator.assignable?(array, tuple1).should == true
end
+
+ it 'should accept empty array when tuple allows min of 0' do
+ tuple1 = tuple_t(Integer)
+ factory.constrain_size(tuple1, 0, 1)
+
+ array = array_t(Integer)
+ factory.constrain_size(array, 0, 0)
+
+ calculator.assignable?(tuple1, array).should == true
+ calculator.assignable?(array, tuple1).should == false
+ end
end
context 'when dealing with structs' do
@@ -960,21 +1041,48 @@ describe 'The type calculator' do
context 'when testing if x is instance of type t' do
include_context "types_setup"
- it 'should consider undef to be instance of Object and NilType' do
+ it 'should consider undef to be instance of Any, NilType, and optional' do
calculator.instance?(Puppet::Pops::Types::PNilType.new(), nil).should == true
- calculator.instance?(Puppet::Pops::Types::PObjectType.new(), nil).should == true
+ calculator.instance?(Puppet::Pops::Types::PAnyType.new(), nil).should == true
+ calculator.instance?(Puppet::Pops::Types::POptionalType.new(), nil).should == true
end
- it 'should not consider undef to be an instance of any other type than Object and NilType and Data' do
- types_to_test = all_types - [
- Puppet::Pops::Types::PObjectType,
+ it 'all types should be (ruby) instance of PAnyType' do
+ all_types.each do |t|
+ t.new.is_a?(Puppet::Pops::Types::PAnyType).should == true
+ end
+ end
+
+ it "should consider :undef to be instance of Runtime['ruby', 'Symbol]" do
+ calculator.instance?(Puppet::Pops::Types::PRuntimeType.new(:runtime => :ruby, :runtime_type_name => 'Symbol'), :undef).should == true
+ end
+
+ it 'should not consider undef to be an instance of any other type than Any, NilType and Data' do
+ types_to_test = all_types - [
+ Puppet::Pops::Types::PAnyType,
Puppet::Pops::Types::PNilType,
- Puppet::Pops::Types::PDataType]
+ Puppet::Pops::Types::PDataType,
+ Puppet::Pops::Types::POptionalType,
+ ]
types_to_test.each {|t| calculator.instance?(t.new, nil).should == false }
types_to_test.each {|t| calculator.instance?(t.new, :undef).should == false }
end
+ it 'should consider default to be instance of Default and Any' do
+ calculator.instance?(Puppet::Pops::Types::PDefaultType.new(), :default).should == true
+ calculator.instance?(Puppet::Pops::Types::PAnyType.new(), :default).should == true
+ end
+
+ it 'should not consider "default" to be an instance of anything but Default, and Any' do
+ types_to_test = all_types - [
+ Puppet::Pops::Types::PAnyType,
+ Puppet::Pops::Types::PDefaultType,
+ ]
+
+ types_to_test.each {|t| calculator.instance?(t.new, :default).should == false }
+ end
+
it 'should consider fixnum instanceof PIntegerType' do
calculator.instance?(Puppet::Pops::Types::PIntegerType.new(), 1).should == true
end
@@ -1075,7 +1183,7 @@ describe 'The type calculator' do
context 'and t is Data' do
it 'undef should be considered instance of Data' do
- calculator.instance?(data_t, :undef).should == true
+ calculator.instance?(data_t, nil).should == true
end
it 'other symbols should not be considered instance of Data' do
@@ -1092,21 +1200,18 @@ describe 'The type calculator' do
it 'a hash with nil/undef data should be considered instance of Data' do
calculator.instance?(data_t, {'a' => nil}).should == true
- calculator.instance?(data_t, {'a' => :undef}).should == true
end
- it 'a hash with nil/undef key should not considered instance of Data' do
+ it 'a hash with nil/default key should not considered instance of Data' do
calculator.instance?(data_t, {nil => 10}).should == false
- calculator.instance?(data_t, {:undef => 10}).should == false
+ calculator.instance?(data_t, {:default => 10}).should == false
end
- it 'an array with undef entries should be considered instance of Data' do
- calculator.instance?(data_t, [:undef]).should == true
+ it 'an array with nil entries should be considered instance of Data' do
calculator.instance?(data_t, [nil]).should == true
end
- it 'an array with undef / data entries should be considered instance of Data' do
- calculator.instance?(data_t, [1, :undef, 'a']).should == true
+ it 'an array with nil + data entries should be considered instance of Data' do
calculator.instance?(data_t, [1, nil, 'a']).should == true
end
end
@@ -1119,10 +1224,8 @@ describe 'The type calculator' do
the_block = factory.LAMBDA(params,factory.literal(42))
the_closure = Puppet::Pops::Evaluator::Closure.new(:fake_evaluator, the_block, :fake_scope)
expect(calculator.instance?(all_callables_t, the_closure)).to be_true
- # TODO: lambdas are currently unttypes, anything can be given if arg count is correct
- expect(calculator.instance?(callable_t(optional_object_t), the_closure)).to be_true
- # Arg count is wrong
- expect(calculator.instance?(callable_t(optional_object_t, optional_object_t), the_closure)).to be_false
+ expect(calculator.instance?(callable_t(object_t), the_closure)).to be_true
+ expect(calculator.instance?(callable_t(object_t, object_t), the_closure)).to be_false
end
it 'a Function instance should be considered a Callable' do
@@ -1192,8 +1295,8 @@ describe 'The type calculator' do
calculator.string(Puppet::Pops::Types::PType.new()).should == 'Type'
end
- it 'should yield \'Object\' for PObjectType' do
- calculator.string(Puppet::Pops::Types::PObjectType.new()).should == 'Object'
+ it 'should yield \'Object\' for PAnyType' do
+ calculator.string(Puppet::Pops::Types::PAnyType.new()).should == 'Any'
end
it 'should yield \'Scalar\' for PScalarType' do
@@ -1397,20 +1500,29 @@ describe 'The type calculator' do
expect(calculator.string(callable_t(String, Integer))).to eql("Callable[String, Integer]")
end
- it "should yield 'Callable[t,min.max]' for callable with size constraint (infinite max)" do
+ it "should yield 'Callable[t,min,max]' for callable with size constraint (infinite max)" do
expect(calculator.string(callable_t(String, 0))).to eql("Callable[String, 0, default]")
end
- it "should yield 'Callable[t,min.max]' for callable with size constraint (capped max)" do
+ it "should yield 'Callable[t,min,max]' for callable with size constraint (capped max)" do
expect(calculator.string(callable_t(String, 0, 3))).to eql("Callable[String, 0, 3]")
end
+ it "should yield 'Callable[min,max]' callable with size > 0" do
+ expect(calculator.string(callable_t(0, 0))).to eql("Callable[0, 0]")
+ expect(calculator.string(callable_t(0, 1))).to eql("Callable[0, 1]")
+ expect(calculator.string(callable_t(0, :default))).to eql("Callable[0, default]")
+ end
+
it "should yield 'Callable[Callable]' for callable with block" do
expect(calculator.string(callable_t(all_callables_t))).to eql("Callable[0, 0, Callable]")
expect(calculator.string(callable_t(string_t, all_callables_t))).to eql("Callable[String, Callable]")
expect(calculator.string(callable_t(string_t, 1,1, all_callables_t))).to eql("Callable[String, 1, 1, Callable]")
end
+ it "should yield Unit for a Unit type" do
+ expect(calculator.string(unit_t)).to eql('Unit')
+ end
end
context 'when processing meta type' do
@@ -1428,7 +1540,7 @@ describe 'The type calculator' do
calculator.infer(Puppet::Pops::Types::PCollectionType.new()).is_a?(ptype).should() == true
calculator.infer(Puppet::Pops::Types::PArrayType.new() ).is_a?(ptype).should() == true
calculator.infer(Puppet::Pops::Types::PHashType.new() ).is_a?(ptype).should() == true
- calculator.infer(Puppet::Pops::Types::PRubyType.new() ).is_a?(ptype).should() == true
+ calculator.infer(Puppet::Pops::Types::PRuntimeType.new() ).is_a?(ptype).should() == true
calculator.infer(Puppet::Pops::Types::PHostClassType.new() ).is_a?(ptype).should() == true
calculator.infer(Puppet::Pops::Types::PResourceType.new() ).is_a?(ptype).should() == true
calculator.infer(Puppet::Pops::Types::PEnumType.new() ).is_a?(ptype).should() == true
@@ -1453,7 +1565,7 @@ describe 'The type calculator' do
calculator.string(calculator.infer(Puppet::Pops::Types::PCollectionType.new())).should == "Type[Collection]"
calculator.string(calculator.infer(Puppet::Pops::Types::PArrayType.new() )).should == "Type[Array[?]]"
calculator.string(calculator.infer(Puppet::Pops::Types::PHashType.new() )).should == "Type[Hash[?, ?]]"
- calculator.string(calculator.infer(Puppet::Pops::Types::PRubyType.new() )).should == "Type[Ruby[?]]"
+ calculator.string(calculator.infer(Puppet::Pops::Types::PRuntimeType.new() )).should == "Type[Runtime[?, ?]]"
calculator.string(calculator.infer(Puppet::Pops::Types::PHostClassType.new() )).should == "Type[Class]"
calculator.string(calculator.infer(Puppet::Pops::Types::PResourceType.new() )).should == "Type[Resource]"
calculator.string(calculator.infer(Puppet::Pops::Types::PEnumType.new() )).should == "Type[Enum]"
@@ -1462,6 +1574,10 @@ describe 'The type calculator' do
calculator.string(calculator.infer(Puppet::Pops::Types::PTupleType.new() )).should == "Type[Tuple]"
calculator.string(calculator.infer(Puppet::Pops::Types::POptionalType.new() )).should == "Type[Optional]"
calculator.string(calculator.infer(Puppet::Pops::Types::PCallableType.new() )).should == "Type[Callable]"
+
+ calculator.infer(Puppet::Pops::Types::PResourceType.new(:type_name => 'foo::fee::fum')).to_s.should == "Type[Foo::Fee::Fum]"
+ calculator.string(calculator.infer(Puppet::Pops::Types::PResourceType.new(:type_name => 'foo::fee::fum'))).should == "Type[Foo::Fee::Fum]"
+ calculator.infer(Puppet::Pops::Types::PResourceType.new(:type_name => 'Foo::Fee::Fum')).to_s.should == "Type[Foo::Fee::Fum]"
end
it "computes the common type of PType's type parameter" do
@@ -1584,6 +1700,87 @@ describe 'The type calculator' do
end
end
+ context 'when determening callability' do
+ context 'and given is exact' do
+ it 'with callable' do
+ required = callable_t(string_t)
+ given = callable_t(string_t)
+ calculator.callable?(required, given).should == true
+ end
+
+ it 'with args tuple' do
+ required = callable_t(string_t)
+ given = tuple_t(string_t)
+ calculator.callable?(required, given).should == true
+ end
+
+ it 'with args tuple having a block' do
+ required = callable_t(string_t, callable_t(string_t))
+ given = tuple_t(string_t, callable_t(string_t))
+ calculator.callable?(required, given).should == true
+ end
+
+ it 'with args array' do
+ required = callable_t(string_t)
+ given = array_t(string_t)
+ factory.constrain_size(given, 1, 1)
+ calculator.callable?(required, given).should == true
+ end
+ end
+
+ context 'and given is more generic' do
+ it 'with callable' do
+ required = callable_t(string_t)
+ given = callable_t(object_t)
+ calculator.callable?(required, given).should == true
+ end
+
+ it 'with args tuple' do
+ required = callable_t(string_t)
+ given = tuple_t(object_t)
+ calculator.callable?(required, given).should == false
+ end
+
+ it 'with args tuple having a block' do
+ required = callable_t(string_t, callable_t(string_t))
+ given = tuple_t(string_t, callable_t(object_t))
+ calculator.callable?(required, given).should == true
+ end
+
+ it 'with args tuple having a block with captures rest' do
+ required = callable_t(string_t, callable_t(string_t))
+ given = tuple_t(string_t, callable_t(object_t, 0, :default))
+ calculator.callable?(required, given).should == true
+ end
+ end
+
+ context 'and given is more specific' do
+ it 'with callable' do
+ required = callable_t(object_t)
+ given = callable_t(string_t)
+ calculator.callable?(required, given).should == false
+ end
+
+ it 'with args tuple' do
+ required = callable_t(object_t)
+ given = tuple_t(string_t)
+ calculator.callable?(required, given).should == true
+ end
+
+ it 'with args tuple having a block' do
+ required = callable_t(string_t, callable_t(object_t))
+ given = tuple_t(string_t, callable_t(string_t))
+ calculator.callable?(required, given).should == false
+ end
+
+ it 'with args tuple having a block with captures rest' do
+ required = callable_t(string_t, callable_t(object_t))
+ given = tuple_t(string_t, callable_t(string_t, 0, :default))
+ calculator.callable?(required, given).should == false
+ end
+ end
+ end
+
matcher :be_assignable_to do |type|
calc = Puppet::Pops::Types::TypeCalculator.new
diff --git a/spec/unit/pops/types/type_factory_spec.rb b/spec/unit/pops/types/type_factory_spec.rb
index a5b949640..cca19a75c 100644
--- a/spec/unit/pops/types/type_factory_spec.rb
+++ b/spec/unit/pops/types/type_factory_spec.rb
@@ -67,6 +67,10 @@ describe 'The type factory' do
Puppet::Pops::Types::TypeFactory.undef().class().should == Puppet::Pops::Types::PNilType
end
+ it 'default() returns PDefaultType' do
+ Puppet::Pops::Types::TypeFactory.default().class().should == Puppet::Pops::Types::PDefaultType
+ end
+
it 'range(to, from) returns PIntegerType' do
t = Puppet::Pops::Types::TypeFactory.range(1,2)
t.class().should == Puppet::Pops::Types::PIntegerType
@@ -150,10 +154,11 @@ describe 'The type factory' do
ht.element_type.class.should == Puppet::Pops::Types::PDataType
end
- it 'ruby(1) returns PRubyType[\'Fixnum\']' do
+ it 'ruby(1) returns PRuntimeType[ruby, \'Fixnum\']' do
ht = Puppet::Pops::Types::TypeFactory.ruby(1)
- ht.class().should == Puppet::Pops::Types::PRubyType
- ht.ruby_class.should == 'Fixnum'
+ ht.class().should == Puppet::Pops::Types::PRuntimeType
+ ht.runtime.should == :ruby
+ ht.runtime_type_name.should == 'Fixnum'
end
it 'a size constrained collection can be created from array' do
diff --git a/spec/unit/pops/types/type_parser_spec.rb b/spec/unit/pops/types/type_parser_spec.rb
index 95595e55d..b67b7b6cd 100644
--- a/spec/unit/pops/types/type_parser_spec.rb
+++ b/spec/unit/pops/types/type_parser_spec.rb
@@ -5,7 +5,8 @@ describe Puppet::Pops::Types::TypeParser do
extend RSpec::Matchers::DSL
let(:parser) { Puppet::Pops::Types::TypeParser.new }
- let(:types) { Puppet::Pops::Types::TypeFactory }
+ let(:types) { Puppet::Pops::Types::TypeFactory }
+
it "rejects a puppet expression" do
expect { parser.parse("1 + 1") }.to raise_error(Puppet::ParseError, /The expression <1 \+ 1> is not a valid type specification/)
@@ -30,7 +31,7 @@ describe Puppet::Pops::Types::TypeParser do
end
[
- 'Object', 'Data', 'CatalogEntry', 'Boolean', 'Scalar', 'Undef', 'Numeric',
+ 'Any', 'Data', 'CatalogEntry', 'Boolean', 'Scalar', 'Undef', 'Numeric', 'Default'
].each do |name|
it "does not support parameterizing unparameterized type <#{name}>" do
expect { parser.parse("#{name}[Integer]") }.to raise_unparameterized_error_for(name)
@@ -38,7 +39,7 @@ describe Puppet::Pops::Types::TypeParser do
end
it "parses a simple, unparameterized type into the type object" do
- expect(the_type_parsed_from(types.object)).to be_the_type(types.object)
+ expect(the_type_parsed_from(types.any)).to be_the_type(types.any)
expect(the_type_parsed_from(types.integer)).to be_the_type(types.integer)
expect(the_type_parsed_from(types.float)).to be_the_type(types.float)
expect(the_type_parsed_from(types.string)).to be_the_type(types.string)
@@ -50,6 +51,7 @@ describe Puppet::Pops::Types::TypeParser do
expect(the_type_parsed_from(types.tuple)).to be_the_type(types.tuple)
expect(the_type_parsed_from(types.struct)).to be_the_type(types.struct)
expect(the_type_parsed_from(types.optional)).to be_the_type(types.optional)
+ expect(the_type_parsed_from(types.default)).to be_the_type(types.default)
end
it "interprets an unparameterized Array as an Array of Data" do
@@ -113,6 +115,18 @@ describe Puppet::Pops::Types::TypeParser do
expect(the_type_parsed_from(struct_t)).to be_the_type(struct_t)
end
+ describe "handles parsing of patterns and regexp" do
+ { 'Pattern[/([a-z]+)([1-9]+)/]' => [:pattern, [/([a-z]+)([1-9]+)/]],
+ 'Pattern["([a-z]+)([1-9]+)"]' => [:pattern, [/([a-z]+)([1-9]+)/]],
+ 'Regexp[/([a-z]+)([1-9]+)/]' => [:regexp, [/([a-z]+)([1-9]+)/]],
+ 'Pattern[/x9/, /([a-z]+)([1-9]+)/]' => [:pattern, [/x9/, /([a-z]+)([1-9]+)/]],
+ }.each do |source, type|
+ it "such that the source '#{source}' yields the type #{type.to_s}" do
+ expect(parser.parse(source)).to be_the_type(Puppet::Pops::Types::TypeFactory.send(type[0], *type[1]))
+ end
+ end
+ end
+
it "rejects an collection spec with the wrong number of parameters" do
expect { parser.parse("Array[Integer, 1,2,3]") }.to raise_the_parameter_error("Array", "1 to 3", 4)
expect { parser.parse("Hash[Integer, Integer, 1,2,3]") }.to raise_the_parameter_error("Hash", "1 to 4", 5)
@@ -159,7 +173,7 @@ describe Puppet::Pops::Types::TypeParser do
end
it 'parses a ruby type' do
- expect(parser.parse("Ruby['Integer']")).to be_the_type(types.ruby_type('Integer'))
+ expect(parser.parse("Runtime[ruby, 'Integer']")).to be_the_type(types.ruby_type('Integer'))
end
it 'parses a callable type' do
@@ -178,12 +192,19 @@ describe Puppet::Pops::Types::TypeParser do
expect(parser.parse("Callable[String, Callable[Boolean]]")).to be_the_type(types.callable(String, types.callable(true)))
end
- it 'parses a parameterized callable type with only min/max' do
+ it 'parses a parameterized callable type with 0 min/max' do
t = parser.parse("Callable[0,0]")
expect(t).to be_the_type(types.callable())
expect(t.param_types.types).to be_empty
end
+ it 'parses a parameterized callable type with >0 min/max' do
+ t = parser.parse("Callable[0,1]")
+ expect(t).to be_the_type(types.callable(0,1))
+ # Contains a Unit type to indicate "called with what you accept"
+ expect(t.param_types.types[0]).to be_the_type(Puppet::Pops::Types::PUnitType.new())
+ end
+
matcher :be_the_type do |type|
calc = Puppet::Pops::Types::TypeCalculator.new
diff --git a/spec/unit/pops/validator/validator_spec.rb b/spec/unit/pops/validator/validator_spec.rb
index 1d865de5f..31defdd6b 100644
--- a/spec/unit/pops/validator/validator_spec.rb
+++ b/spec/unit/pops/validator/validator_spec.rb
@@ -6,12 +6,12 @@ require 'puppet_spec/pops'
# relative to this spec file (./) does not work as this file is loaded by rspec
require File.join(File.dirname(__FILE__), '../parser/parser_rspec_helper')
-describe "validating 3x" do
+describe "validating 4x" do
include ParserRspecHelper
include PuppetSpec::Pops
let(:acceptor) { Puppet::Pops::Validation::Acceptor.new() }
- let(:validator) { Puppet::Pops::Validation::ValidatorFactory_3_1.new().validator(acceptor) }
+ let(:validator) { Puppet::Pops::Validation::ValidatorFactory_4_0.new().validator(acceptor) }
def validate(model)
validator.validate(model)
@@ -25,44 +25,160 @@ describe "validating 3x" do
end
it 'should raise error for illegal variable names' do
- pending "validation was too strict, now too relaxed - validation missing"
- expect(validate(fqn('Aaa').var())).to have_issue(Puppet::Pops::Issues::ILLEGAL_NAME)
- expect(validate(fqn('AAA').var())).to have_issue(Puppet::Pops::Issues::ILLEGAL_NAME)
+ expect(validate(fqn('Aaa').var())).to have_issue(Puppet::Pops::Issues::ILLEGAL_VAR_NAME)
+ expect(validate(fqn('AAA').var())).to have_issue(Puppet::Pops::Issues::ILLEGAL_VAR_NAME)
+ expect(validate(fqn('aaa::_aaa').var())).to have_issue(Puppet::Pops::Issues::ILLEGAL_VAR_NAME)
end
- it 'should raise error for -= assignment' do
- expect(validate(fqn('aaa').minus_set(2))).to have_issue(Puppet::Pops::Issues::UNSUPPORTED_OPERATOR)
+ it 'should not raise error for variable name with underscore first in first name segment' do
+ expect(validate(fqn('_aa').var())).to_not have_issue(Puppet::Pops::Issues::ILLEGAL_VAR_NAME)
+ expect(validate(fqn('::_aa').var())).to_not have_issue(Puppet::Pops::Issues::ILLEGAL_VAR_NAME)
end
-end
+ context 'for non productive expressions' do
+ [ '1',
+ '3.14',
+ "'a'",
+ '"a"',
+ '"${$a=10}"', # interpolation with side effect
+ 'false',
+ 'true',
+ 'default',
+ 'undef',
+ '[1,2,3]',
+ '{a=>10}',
+ 'if 1 {2}',
+ 'if 1 {2} else {3}',
+ 'if 1 {2} elsif 3 {4}',
+ 'unless 1 {2}',
+ 'unless 1 {2} else {3}',
+ '1 ? 2 => 3',
+ '1 ? { 2 => 3}',
+ '-1',
+ '-foo()', # unary minus on productive
+ '1+2',
+ '1<2',
+ '(1<2)',
+ '!true',
+ '!foo()', # not on productive
+ '$a',
+ '$a[1]',
+ 'name',
+ 'Type',
+ 'Type[foo]'
+ ].each do |expr|
+ it "produces error for non productive: #{expr}" do
+ source = "#{expr}; $a = 10"
+ expect(validate(parse(source))).to have_issue(Puppet::Pops::Issues::IDEM_EXPRESSION_NOT_LAST)
+ end
-describe "validating 4x" do
- include ParserRspecHelper
- include PuppetSpec::Pops
+ it "does not produce error when last for non productive: #{expr}" do
+ source = " $a = 10; #{expr}"
+ expect(validate(parse(source))).to_not have_issue(Puppet::Pops::Issues::IDEM_EXPRESSION_NOT_LAST)
+ end
+ end
- let(:acceptor) { Puppet::Pops::Validation::Acceptor.new() }
- let(:validator) { Puppet::Pops::Validation::ValidatorFactory_4_0.new().validator(acceptor) }
+ [
+ 'if 1 {$a = 1}',
+ 'if 1 {2} else {$a=1}',
+ 'if 1 {2} elsif 3 {$a=1}',
+ 'unless 1 {$a=1}',
+ 'unless 1 {2} else {$a=1}',
+ '$a = 1 ? 2 => 3',
+ '$a = 1 ? { 2 => 3}',
+ 'Foo[a] -> Foo[b]',
+ '($a=1)',
+ 'foo()',
+ '$a.foo()'
+ ].each do |expr|
- def validate(model)
- validator.validate(model)
- acceptor
+ it "does not produce error when for productive: #{expr}" do
+ source = "#{expr}; $x = 1"
+ expect(validate(parse(source))).to_not have_issue(Puppet::Pops::Issues::IDEM_EXPRESSION_NOT_LAST)
+ end
+ end
+
+ ['class', 'define', 'node'].each do |type|
+ it "flags non productive expression last in #{type}" do
+ source = <<-SOURCE
+ #{type} nope {
+ 1
+ }
+ end
+ SOURCE
+ expect(validate(parse(source))).to have_issue(Puppet::Pops::Issues::IDEM_NOT_ALLOWED_LAST)
+ end
+ end
end
- it 'should raise error for illegal names' do
- pending "validation was too strict, now too relaxed - validation missing"
- expect(validate(fqn('Aaa'))).to have_issue(Puppet::Pops::Issues::ILLEGAL_NAME)
- expect(validate(fqn('AAA'))).to have_issue(Puppet::Pops::Issues::ILLEGAL_NAME)
+ context 'for reserved words' do
+ ['function', 'private', 'type', 'attr'].each do |word|
+ it "produces an error for the word '#{word}'" do
+ source = "$a = #{word}"
+ expect(validate(parse(source))).to have_issue(Puppet::Pops::Issues::RESERVED_WORD)
+ end
+ end
end
- it 'should raise error for illegal variable names' do
- expect(validate(fqn('Aaa').var())).to have_issue(Puppet::Pops::Issues::ILLEGAL_VAR_NAME)
- expect(validate(fqn('AAA').var())).to have_issue(Puppet::Pops::Issues::ILLEGAL_VAR_NAME)
- expect(validate(fqn('aaa::_aaa').var())).to have_issue(Puppet::Pops::Issues::ILLEGAL_VAR_NAME)
+ context 'for reserved type names' do
+ [# type/Type, is a reserved name but results in syntax error because it is a keyword in lower case form
+ 'any',
+ 'unit',
+ 'scalar',
+ 'boolean',
+ 'numeric',
+ 'integer',
+ 'float',
+ 'collection',
+ 'array',
+ 'hash',
+ 'tuple',
+ 'struct',
+ 'variant',
+ 'optional',
+ 'enum',
+ 'regexp',
+ 'pattern',
+ 'runtime',
+ ].each do |name|
+
+ it "produces an error for 'class #{name}'" do
+ source = "class #{name} {}"
+ expect(validate(parse(source))).to have_issue(Puppet::Pops::Issues::RESERVED_TYPE_NAME)
+ end
+
+ it "produces an error for 'define #{name}'" do
+ source = "define #{name} {}"
+ expect(validate(parse(source))).to have_issue(Puppet::Pops::Issues::RESERVED_TYPE_NAME)
+ end
+ end
end
- it 'should not raise error for variable name with underscore first in first name segment' do
- expect(validate(fqn('_aa').var())).to_not have_issue(Puppet::Pops::Issues::ILLEGAL_VAR_NAME)
- expect(validate(fqn('::_aa').var())).to_not have_issue(Puppet::Pops::Issues::ILLEGAL_VAR_NAME)
+ context 'for reserved parameter names' do
+ ['name', 'title'].each do |word|
+ it "produces an error when $#{word} is used as a parameter in a class" do
+ source = "class x ($#{word}) {}"
+ expect(validate(parse(source))).to have_issue(Puppet::Pops::Issues::RESERVED_PARAMETER)
+ end
+
+ it "produces an error when $#{word} is used as a parameter in a define" do
+ source = "define x ($#{word}) {}"
+ expect(validate(parse(source))).to have_issue(Puppet::Pops::Issues::RESERVED_PARAMETER)
+ end
+ end
+
+ end
+
+ context 'for numeric parameter names' do
+ ['1', '0x2', '03'].each do |word|
+ it "produces an error when $#{word} is used as a parameter in a class" do
+ source = "class x ($#{word}) {}"
+ expect(validate(parse(source))).to have_issue(Puppet::Pops::Issues::ILLEGAL_NUMERIC_PARAMETER)
+ end
+ end
end
+ def parse(source)
+ Puppet::Pops::Parser::Parser.new().parse_string(source)
+ end
end
diff --git a/spec/unit/provider/exec/posix_spec.rb b/spec/unit/provider/exec/posix_spec.rb
index 7c4982fcc..02c338b69 100755
--- a/spec/unit/provider/exec/posix_spec.rb
+++ b/spec/unit/provider/exec/posix_spec.rb
@@ -1,19 +1,18 @@
#! /usr/bin/env ruby
require 'spec_helper'
-describe Puppet::Type.type(:exec).provider(:posix) do
+describe Puppet::Type.type(:exec).provider(:posix), :if => Puppet.features.posix? do
include PuppetSpec::Files
def make_exe
cmdpath = tmpdir('cmdpath')
exepath = tmpfile('my_command', cmdpath)
- exepath = exepath + ".exe" if Puppet.features.microsoft_windows?
FileUtils.touch(exepath)
File.chmod(0755, exepath)
exepath
end
- let(:resource) { Puppet::Type.type(:exec).new(:title => File.expand_path('/foo'), :provider => :posix) }
+ let(:resource) { Puppet::Type.type(:exec).new(:title => '/foo', :provider => :posix) }
let(:provider) { described_class.new(resource) }
describe "#validatecmd" do
@@ -31,7 +30,7 @@ describe Puppet::Type.type(:exec).provider(:posix) do
it "should pass if command is fully qualifed" do
provider.resource[:path] = ['/bogus/bin']
- provider.validatecmd(File.expand_path("/bin/blah/foo"))
+ provider.validatecmd("/bin/blah/foo")
end
end
@@ -64,7 +63,7 @@ describe Puppet::Type.type(:exec).provider(:posix) do
provider.resource[:path] = [File.dirname(command)]
filename = File.basename(command)
- Puppet::Util::Execution.expects(:execute).with { |cmdline, arguments| (cmdline == filename) && (arguments.is_a? Hash) }.returns(Puppet::Util::Execution::ProcessOutput.new('', 0))
+ Puppet::Util::Execution.expects(:execute).with(filename, instance_of(Hash)).returns(Puppet::Util::Execution::ProcessOutput.new('', 0))
provider.run(filename)
end
@@ -91,17 +90,28 @@ describe Puppet::Type.type(:exec).provider(:posix) do
expect { provider.run("cd ..") }.to raise_error(ArgumentError, "Could not find command 'cd'")
end
+ it "does not override the user when it is already the requested user" do
+ Etc.stubs(:getpwuid).returns(Struct::Passwd.new('testing'))
+ provider.resource[:user] = 'testing'
+ command = make_exe
+
+ Puppet::Util::Execution.expects(:execute).with(anything(), has_entry(:uid, nil)).returns(Puppet::Util::Execution::ProcessOutput.new('', 0))
+
+ provider.run(command)
+ end
+
it "should execute the command if the command given includes arguments or subcommands" do
provider.resource[:path] = ['/bogus/bin']
command = make_exe
- Puppet::Util::Execution.expects(:execute).with { |cmdline, arguments| (cmdline == "#{command} bar --sillyarg=true --blah") && (arguments.is_a? Hash) }.returns(Puppet::Util::Execution::ProcessOutput.new('', 0))
+ Puppet::Util::Execution.expects(:execute).with("#{command} bar --sillyarg=true --blah", instance_of(Hash)).returns(Puppet::Util::Execution::ProcessOutput.new('', 0))
+
provider.run("#{command} bar --sillyarg=true --blah")
end
it "should fail if quoted command doesn't exist" do
provider.resource[:path] = ['/bogus/bin']
- command = "#{File.expand_path('/foo')} bar --sillyarg=true --blah"
+ command = "/foo bar --sillyarg=true --blah"
expect { provider.run(%Q["#{command}"]) }.to raise_error(ArgumentError, "Could not find command '#{command}'")
end
@@ -110,8 +120,10 @@ describe Puppet::Type.type(:exec).provider(:posix) do
provider.resource[:environment] = ['WHATEVER=/something/else', 'WHATEVER=/foo']
command = make_exe
- Puppet::Util::Execution.expects(:execute).with { |cmdline, arguments| (cmdline == command) && (arguments.is_a? Hash) }.returns(Puppet::Util::Execution::ProcessOutput.new('', 0))
+ Puppet::Util::Execution.expects(:execute).with(command, instance_of(Hash)).returns(Puppet::Util::Execution::ProcessOutput.new('', 0))
+
provider.run(command)
+
@logs.map {|l| "#{l.level}: #{l.message}" }.should == ["warning: Overriding environment setting 'WHATEVER' with '/foo'"]
end
@@ -121,7 +133,7 @@ describe Puppet::Type.type(:exec).provider(:posix) do
provider.run(provider.resource[:command])
end
- describe "posix locale settings", :unless => Puppet.features.microsoft_windows? do
+ describe "posix locale settings" do
# a sentinel value that we can use to emulate what locale environment variables might be set to on an international
# system.
lang_sentinel_value = "en_US.UTF-8"
@@ -160,7 +172,7 @@ describe Puppet::Type.type(:exec).provider(:posix) do
end
end
- describe "posix user-related environment vars", :unless => Puppet.features.microsoft_windows? do
+ describe "posix user-related environment vars" do
# a temporary hash that contains sentinel values for each of the user-related environment variables that we
# are expected to unset during an "exec"
user_sentinel_env = {}
@@ -202,10 +214,6 @@ describe Puppet::Type.type(:exec).provider(:posix) do
output.strip.should == sentinel_value
end
end
-
-
end
-
-
end
end
diff --git a/spec/unit/provider/exec/shell_spec.rb b/spec/unit/provider/exec/shell_spec.rb
index 0f0faa594..932f46b6a 100755
--- a/spec/unit/provider/exec/shell_spec.rb
+++ b/spec/unit/provider/exec/shell_spec.rb
@@ -2,8 +2,8 @@
require 'spec_helper'
describe Puppet::Type.type(:exec).provider(:shell), :unless => Puppet.features.microsoft_windows? do
- let :resource do Puppet::Resource.new(:exec, 'foo') end
- let :provider do described_class.new(resource) end
+ let(:resource) { Puppet::Type.type(:exec).new(:title => 'foo', :provider => 'shell') }
+ let(:provider) { described_class.new(resource) }
describe "#run" do
it "should be able to run builtin shell commands" do
diff --git a/spec/unit/provider/file/windows_spec.rb b/spec/unit/provider/file/windows_spec.rb
index a0e9d3a4e..f6d7ef0e7 100755
--- a/spec/unit/provider/file/windows_spec.rb
+++ b/spec/unit/provider/file/windows_spec.rb
@@ -49,22 +49,22 @@ describe Puppet::Type.type(:file).provider(:windows), :if => Puppet.features.mic
describe "#id2name" do
it "should return the name of the user identified by the sid" do
- Puppet::Util::Windows::Security.expects(:valid_sid?).with(sid).returns(true)
- Puppet::Util::Windows::Security.expects(:sid_to_name).with(sid).returns(account)
+ Puppet::Util::Windows::SID.expects(:valid_sid?).with(sid).returns(true)
+ Puppet::Util::Windows::SID.expects(:sid_to_name).with(sid).returns(account)
provider.id2name(sid).should == account
end
it "should return the argument if it's already a name" do
- Puppet::Util::Windows::Security.expects(:valid_sid?).with(account).returns(false)
- Puppet::Util::Windows::Security.expects(:sid_to_name).never
+ Puppet::Util::Windows::SID.expects(:valid_sid?).with(account).returns(false)
+ Puppet::Util::Windows::SID.expects(:sid_to_name).never
provider.id2name(account).should == account
end
it "should return nil if the user doesn't exist" do
- Puppet::Util::Windows::Security.expects(:valid_sid?).with(sid).returns(true)
- Puppet::Util::Windows::Security.expects(:sid_to_name).with(sid).returns(nil)
+ Puppet::Util::Windows::SID.expects(:valid_sid?).with(sid).returns(true)
+ Puppet::Util::Windows::SID.expects(:sid_to_name).with(sid).returns(nil)
provider.id2name(sid).should == nil
end
@@ -72,7 +72,7 @@ describe Puppet::Type.type(:file).provider(:windows), :if => Puppet.features.mic
describe "#name2id" do
it "should delegate to name_to_sid" do
- Puppet::Util::Windows::Security.expects(:name_to_sid).with(account).returns(sid)
+ Puppet::Util::Windows::SID.expects(:name_to_sid).with(account).returns(sid)
provider.name2id(account).should == sid
end
diff --git a/spec/unit/provider/group/windows_adsi_spec.rb b/spec/unit/provider/group/windows_adsi_spec.rb
index a7de859da..7c9366f72 100644
--- a/spec/unit/provider/group/windows_adsi_spec.rb
+++ b/spec/unit/provider/group/windows_adsi_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Puppet::Type.type(:group).provider(:windows_adsi) do
+describe Puppet::Type.type(:group).provider(:windows_adsi), :if => Puppet.features.microsoft_windows? do
let(:resource) do
Puppet::Type.type(:group).new(
:title => 'testers',
@@ -15,8 +15,8 @@ describe Puppet::Type.type(:group).provider(:windows_adsi) do
let(:connection) { stub 'connection' }
before :each do
- Puppet::Util::ADSI.stubs(:computer_name).returns('testcomputername')
- Puppet::Util::ADSI.stubs(:connect).returns connection
+ Puppet::Util::Windows::ADSI.stubs(:computer_name).returns('testcomputername')
+ Puppet::Util::Windows::ADSI.stubs(:connect).returns connection
end
describe ".instances" do
@@ -30,14 +30,14 @@ describe Puppet::Type.type(:group).provider(:windows_adsi) do
end
end
- describe "group type :members property helpers", :if => Puppet.features.microsoft_windows? do
+ describe "group type :members property helpers" do
let(:user1) { stub(:account => 'user1', :domain => '.', :to_s => 'user1sid') }
let(:user2) { stub(:account => 'user2', :domain => '.', :to_s => 'user2sid') }
before :each do
- Puppet::Util::Windows::Security.stubs(:name_to_sid_object).with('user1').returns(user1)
- Puppet::Util::Windows::Security.stubs(:name_to_sid_object).with('user2').returns(user2)
+ Puppet::Util::Windows::SID.stubs(:name_to_sid_object).with('user1').returns(user1)
+ Puppet::Util::Windows::SID.stubs(:name_to_sid_object).with('user2').returns(user2)
end
describe "#members_insync?" do
@@ -89,7 +89,7 @@ describe Puppet::Type.type(:group).provider(:windows_adsi) do
provider.members.should =~ ['user1', 'user2', 'user3']
end
- it "should be able to set group members", :if => Puppet.features.microsoft_windows? do
+ it "should be able to set group members" do
provider.group.stubs(:members).returns ['user1', 'user2']
member_sids = [
@@ -100,8 +100,8 @@ describe Puppet::Type.type(:group).provider(:windows_adsi) do
provider.group.stubs(:member_sids).returns(member_sids[0..1])
- Puppet::Util::Windows::Security.expects(:name_to_sid_object).with('user2').returns(member_sids[1])
- Puppet::Util::Windows::Security.expects(:name_to_sid_object).with('user3').returns(member_sids[2])
+ Puppet::Util::Windows::SID.expects(:name_to_sid_object).with('user2').returns(member_sids[1])
+ Puppet::Util::Windows::SID.expects(:name_to_sid_object).with('user3').returns(member_sids[2])
provider.group.expects(:remove_member_sids).with(member_sids[0])
provider.group.expects(:add_member_sids).with(member_sids[2])
@@ -115,7 +115,7 @@ describe Puppet::Type.type(:group).provider(:windows_adsi) do
resource[:members] = ['user1', 'user2']
group = stub 'group'
- Puppet::Util::ADSI::Group.expects(:create).with('testers').returns group
+ Puppet::Util::Windows::ADSI::Group.expects(:create).with('testers').returns group
create = sequence('create')
group.expects(:commit).in_sequence(create)
@@ -125,7 +125,7 @@ describe Puppet::Type.type(:group).provider(:windows_adsi) do
end
it 'should not create a group if a user by the same name exists' do
- Puppet::Util::ADSI::Group.expects(:create).with('testers').raises( Puppet::Error.new("Cannot create group if user 'testers' exists.") )
+ Puppet::Util::Windows::ADSI::Group.expects(:create).with('testers').raises( Puppet::Error.new("Cannot create group if user 'testers' exists.") )
expect{ provider.create }.to raise_error( Puppet::Error,
/Cannot create group if user 'testers' exists./ )
end
@@ -138,11 +138,11 @@ describe Puppet::Type.type(:group).provider(:windows_adsi) do
end
it "should be able to test whether a group exists" do
- Puppet::Util::ADSI.stubs(:sid_uri_safe).returns(nil)
- Puppet::Util::ADSI.stubs(:connect).returns stub('connection')
+ Puppet::Util::Windows::ADSI.stubs(:sid_uri_safe).returns(nil)
+ Puppet::Util::Windows::ADSI.stubs(:connect).returns stub('connection')
provider.should be_exists
- Puppet::Util::ADSI.stubs(:connect).returns nil
+ Puppet::Util::Windows::ADSI.stubs(:connect).returns nil
provider.should_not be_exists
end
@@ -152,8 +152,8 @@ describe Puppet::Type.type(:group).provider(:windows_adsi) do
provider.delete
end
- it "should report the group's SID as gid", :if => Puppet.features.microsoft_windows? do
- Puppet::Util::Windows::Security.expects(:name_to_sid).with('testers').returns('S-1-5-32-547')
+ it "should report the group's SID as gid" do
+ Puppet::Util::Windows::SID.expects(:name_to_sid).with('testers').returns('S-1-5-32-547')
provider.gid.should == 'S-1-5-32-547'
end
@@ -162,7 +162,7 @@ describe Puppet::Type.type(:group).provider(:windows_adsi) do
provider.send(:gid=, 500)
end
- it "should prefer the domain component from the resolved SID", :if => Puppet.features.microsoft_windows? do
+ it "should prefer the domain component from the resolved SID" do
provider.members_to_s(['.\Administrators']).should == 'BUILTIN\Administrators'
end
end
diff --git a/spec/unit/provider/package/gem_spec.rb b/spec/unit/provider/package/gem_spec.rb
index f382335cd..23aa2798d 100755
--- a/spec/unit/provider/package/gem_spec.rb
+++ b/spec/unit/provider/package/gem_spec.rb
@@ -174,4 +174,14 @@ describe provider_class do
{:provider=>:gem, :ensure=>["1.11.3.3"], :name=>"rvm"}]
end
end
+
+ describe "listing gems" do
+ describe "searching for a single package" do
+ it "searches for an exact match" do
+ provider_class.expects(:execute).with(includes('^bundler$')).returns(File.read(my_fixture('gem-list-single-package')))
+ expected = {:name => 'bundler', :ensure => %w[1.6.2], :provider => :gem}
+ expect(provider_class.gemlist({:justme => 'bundler'})).to eq(expected)
+ end
+ end
+ end
end
diff --git a/spec/unit/provider/package/openbsd_spec.rb b/spec/unit/provider/package/openbsd_spec.rb
index 8d4f079fe..712f9cfda 100755
--- a/spec/unit/provider/package/openbsd_spec.rb
+++ b/spec/unit/provider/package/openbsd_spec.rb
@@ -17,7 +17,7 @@ describe provider_class do
def expect_pkgadd_with_source(source)
provider.expects(:pkgadd).with do |fullname|
ENV.should_not be_key('PKG_PATH')
- fullname.should == source
+ fullname.should == [source]
end
end
@@ -28,7 +28,7 @@ describe provider_class do
ENV.should be_key('PKG_PATH')
ENV['PKG_PATH'].should == source
- fullname.should == provider.resource[:name]
+ fullname.should == [provider.resource[:name]]
end
provider.expects(:execpipe).with(['/bin/pkg_info', '-I', provider.resource[:name]]).yields('')
@@ -37,6 +37,15 @@ describe provider_class do
ENV.should_not be_key('PKG_PATH')
end
+ describe 'provider features' do
+ it { should be_installable }
+ it { should be_install_options }
+ it { should be_uninstallable }
+ it { should be_uninstall_options }
+ it { should be_upgradeable }
+ it { should be_versionable }
+ end
+
before :each do
# Stub some provider methods to avoid needing the actual software
# installed, so we can test on whatever platform we want.
@@ -45,7 +54,7 @@ describe provider_class do
provider_class.stubs(:command).with(:pkgdelete).returns('/bin/pkg_delete')
end
- context "::instances" do
+ context "#instances" do
it "should return nil if execution failed" do
provider_class.expects(:execpipe).raises(Puppet::ExecutionFailure, 'wawawa')
provider_class.instances.should be_nil
@@ -69,7 +78,7 @@ describe provider_class do
instances = provider_class.instances.map {|p| {:name => p.get(:name),
:ensure => p.get(:ensure), :flavor => p.get(:flavor)}}
instances.size.should == 2
- instances[0].should == {:name => 'bash', :ensure => '3.1.17', :flavor => 'static'}
+ instances[0].should == {:name => 'bash', :ensure => '3.1.17', :flavor => 'static'}
instances[1].should == {:name => 'vim', :ensure => '7.0.42', :flavor => 'no_x11'}
end
end
@@ -100,7 +109,7 @@ describe provider_class do
end
it "should install correctly when given a directory-unlike source" do
- source = '/whatever.pkg'
+ source = '/whatever.tgz'
provider.resource[:source] = source
expect_pkgadd_with_source(source)
@@ -224,6 +233,46 @@ describe provider_class do
}.to raise_error(Puppet::Error, /No valid installpath found in \/etc\/pkg\.conf and no source was set/)
end
end
+
+ it 'should use install_options as Array' do
+ provider.resource[:source] = '/tma1/'
+ provider.resource[:install_options] = ['-r', '-z']
+ provider.expects(:pkgadd).with(['-r', '-z', 'bash'])
+ provider.install
+ end
+ end
+
+ context "#latest" do
+ before do
+ provider.resource[:source] = '/tmp/tcsh.tgz'
+ provider.resource[:name] = 'tcsh'
+ provider.stubs(:pkginfo).with('tcsh')
+ end
+
+ it "should return the ensure value if the package is already installed" do
+ provider.stubs(:properties).returns({:ensure => '4.2.45'})
+ provider.stubs(:pkginfo).with('-Q', 'tcsh')
+ provider.latest.should == '4.2.45'
+ end
+
+ it "should recognize a new version" do
+ pkginfo_query = 'tcsh-6.18.01p1'
+ provider.stubs(:pkginfo).with('-Q', 'tcsh').returns(pkginfo_query)
+ provider.latest.should == '6.18.01p1'
+ end
+
+ it "should recognize a newer version" do
+ provider.stubs(:properties).returns({:ensure => '1.6.8'})
+ pkginfo_query = 'tcsh-1.6.10'
+ provider.stubs(:pkginfo).with('-Q', 'tcsh').returns(pkginfo_query)
+ provider.latest.should == '1.6.10'
+ end
+
+ it "should recognize a package that is already the newest" do
+ pkginfo_query = 'tcsh-6.18.01p0 (installed)'
+ provider.stubs(:pkginfo).with('-Q', 'tcsh').returns(pkginfo_query)
+ provider.latest.should == '6.18.01p0'
+ end
end
context "#get_version" do
@@ -233,8 +282,8 @@ describe provider_class do
end
it "should return the package version if in the output" do
- fixture = File.read(my_fixture('pkginfo.list'))
- provider.expects(:execpipe).with(%w{/bin/pkg_info -I bash}).yields(fixture)
+ output = 'bash-3.1.17 GNU Bourne Again Shell'
+ provider.expects(:execpipe).with(%w{/bin/pkg_info -I bash}).yields(output)
provider.get_version.should == '3.1.17'
end
@@ -279,7 +328,7 @@ describe provider_class do
provider.install_options.should == ['-Darch=vax']
end
end
-
+
context "#uninstall_options" do
it "should return nill by default" do
provider.uninstall_options.should be_nil
@@ -300,7 +349,7 @@ describe provider_class do
provider.uninstall_options.should == ['-Dbaddepend=1']
end
end
-
+
context "#uninstall" do
describe 'when uninstalling' do
it 'should use erase to purge' do
@@ -308,5 +357,13 @@ describe provider_class do
provider.purge
end
end
+
+ describe 'with uninstall_options' do
+ it 'should use uninstall_options as Array' do
+ provider.resource[:uninstall_options] = ['-q', '-c']
+ provider.expects(:pkgdelete).with(['-q', '-c'], 'bash')
+ provider.uninstall
+ end
+ end
end
end
diff --git a/spec/unit/provider/package/pacman_spec.rb b/spec/unit/provider/package/pacman_spec.rb
index 789fd88fb..aca7c5d64 100755
--- a/spec/unit/provider/package/pacman_spec.rb
+++ b/spec/unit/provider/package/pacman_spec.rb
@@ -2,58 +2,73 @@
require 'spec_helper'
require 'stringio'
-provider = Puppet::Type.type(:package).provider(:pacman)
-describe provider do
+describe Puppet::Type.type(:package).provider(:pacman) do
let(:no_extra_options) { { :failonfail => true, :combine => true, :custom_environment => {} } }
let(:executor) { Puppet::Util::Execution }
let(:resolver) { Puppet::Util }
+ let(:resource) { Puppet::Type.type(:package).new(:name => 'package', :provider => 'pacman') }
+ let(:provider) { described_class.new(resource) }
+
before do
resolver.stubs(:which).with('/usr/bin/pacman').returns('/usr/bin/pacman')
- provider.stubs(:which).with('/usr/bin/pacman').returns('/usr/bin/pacman')
+ described_class.stubs(:which).with('/usr/bin/pacman').returns('/usr/bin/pacman')
resolver.stubs(:which).with('/usr/bin/yaourt').returns('/usr/bin/yaourt')
- provider.stubs(:which).with('/usr/bin/yaourt').returns('/usr/bin/yaourt')
- @resource = Puppet::Type.type(:package).new(:name => 'package')
- @provider = provider.new(@resource)
+ described_class.stubs(:which).with('/usr/bin/yaourt').returns('/usr/bin/yaourt')
end
describe "when installing" do
before do
- @provider.stubs(:query).returns({
+ provider.stubs(:query).returns({
:ensure => '1.0'
})
end
- it "should call pacman to install the right package quietly" do
-
- if @provider.yaourt?
- args = ['/usr/bin/yaourt', '--noconfirm', '-S', @resource[:name]]
- else
- args = ['/usr/bin/pacman', '--noconfirm', '--noprogressbar', '-Sy', @resource[:name]]
- end
-
- executor.
- expects(:execute).
- at_least_once.
- with(args, no_extra_options).
- returns ''
+ it "should call pacman to install the right package quietly when yaourt is not installed" do
+ provider.stubs(:yaourt?).returns(false)
+ args = ['--noconfirm', '--noprogressbar', '-Sy', resource[:name]]
+ provider.expects(:pacman).at_least_once.with(*args).returns ''
+ provider.install
+ end
- @provider.install
+ it "should call yaourt to install the right package quietly when yaourt is installed" do
+ provider.stubs(:yaourt?).returns(true)
+ args = ['--noconfirm', '-S', resource[:name]]
+ provider.expects(:yaourt).at_least_once.with(*args).returns ''
+ provider.install
end
it "should raise an ExecutionFailure if the installation failed" do
executor.stubs(:execute).returns("")
- @provider.expects(:query).returns(nil)
+ provider.expects(:query).returns(nil)
- lambda { @provider.install }.should raise_exception(Puppet::ExecutionFailure)
+ lambda { provider.install }.should raise_exception(Puppet::ExecutionFailure)
end
- context "when :source is specified" do
- before :each do
- @install = sequence("install")
+ describe "and install_options are given" do
+ before do
+ resource[:install_options] = ['-x', {'--arg' => 'value'}]
+ end
+
+ it "should call pacman to install the right package quietly when yaourt is not installed" do
+ provider.stubs(:yaourt?).returns(false)
+ args = ['--noconfirm', '--noprogressbar', '-x', '--arg=value', '-Sy', resource[:name]]
+ provider.expects(:pacman).at_least_once.with(*args).returns ''
+ provider.install
end
+ it "should call yaourt to install the right package quietly when yaourt is installed" do
+ provider.stubs(:yaourt?).returns(true)
+ args = ['--noconfirm', '-x', '--arg=value', '-S', resource[:name]]
+ provider.expects(:yaourt).at_least_once.with(*args).returns ''
+ provider.install
+ end
+ end
+
+ context "when :source is specified" do
+ let(:install_seq) { sequence("install") }
+
context "recognizable by pacman" do
%w{
/some/package/file
@@ -61,28 +76,28 @@ describe provider do
ftp://some.package.in/the/air
}.each do |source|
it "should install #{source} directly" do
- @resource[:source] = source
+ resource[:source] = source
executor.expects(:execute).
with(all_of(includes("-Sy"), includes("--noprogressbar")), no_extra_options).
- in_sequence(@install).
+ in_sequence(install_seq).
returns("")
executor.expects(:execute).
with(all_of(includes("-U"), includes(source)), no_extra_options).
- in_sequence(@install).
+ in_sequence(install_seq).
returns("")
- @provider.install
+ provider.install
end
end
end
context "as a file:// URL" do
+ let(:actual_file_path) { "/some/package/file" }
+
before do
- @package_file = "file:///some/package/file"
- @actual_file_path = "/some/package/file"
- @resource[:source] = @package_file
+ resource[:source] = "file:///some/package/file"
end
it "should install from the path segment of the URL" do
@@ -91,35 +106,35 @@ describe provider do
includes("--noprogressbar"),
includes("--noconfirm")),
no_extra_options).
- in_sequence(@install).
+ in_sequence(install_seq).
returns("")
executor.expects(:execute).
- with(all_of(includes("-U"), includes(@actual_file_path)), no_extra_options).
- in_sequence(@install).
+ with(all_of(includes("-U"), includes(actual_file_path)), no_extra_options).
+ in_sequence(install_seq).
returns("")
- @provider.install
+ provider.install
end
end
context "as a puppet URL" do
before do
- @resource[:source] = "puppet://server/whatever"
+ resource[:source] = "puppet://server/whatever"
end
it "should fail" do
- lambda { @provider.install }.should raise_error(Puppet::Error)
+ lambda { provider.install }.should raise_error(Puppet::Error)
end
end
context "as a malformed URL" do
before do
- @resource[:source] = "blah://"
+ resource[:source] = "blah://"
end
it "should fail" do
- lambda { @provider.install }.should raise_error(Puppet::Error)
+ lambda { provider.install }.should raise_error(Puppet::Error)
end
end
end
@@ -127,19 +142,23 @@ describe provider do
describe "when updating" do
it "should call install" do
- @provider.expects(:install).returns("install return value")
- @provider.update.should == "install return value"
+ provider.expects(:install).returns("install return value")
+ provider.update.should == "install return value"
end
end
describe "when uninstalling" do
it "should call pacman to remove the right package quietly" do
- executor.
- expects(:execute).
- with(["/usr/bin/pacman", "--noconfirm", "--noprogressbar", "-R", @resource[:name]], no_extra_options).
- returns ""
+ args = ["/usr/bin/pacman", "--noconfirm", "--noprogressbar", "-R", resource[:name]]
+ executor.expects(:execute).with(args, no_extra_options).returns ""
+ provider.uninstall
+ end
- @provider.uninstall
+ it "adds any uninstall_options" do
+ resource[:uninstall_options] = ['-x', {'--arg' => 'value'}]
+ args = ["/usr/bin/pacman", "--noconfirm", "--noprogressbar", "-x", "--arg=value", "-R", resource[:name]]
+ executor.expects(:execute).with(args, no_extra_options).returns ""
+ provider.uninstall
end
end
@@ -147,8 +166,8 @@ describe provider do
it "should query pacman" do
executor.
expects(:execute).
- with(["/usr/bin/pacman", "-Qi", @resource[:name]], no_extra_options)
- @provider.query
+ with(["/usr/bin/pacman", "-Qi", resource[:name]], no_extra_options)
+ provider.query
end
it "should return the version" do
@@ -176,20 +195,20 @@ Description : A library-based package manager with dependency support
EOF
executor.expects(:execute).returns(query_output)
- @provider.query.should == {:ensure => "1.01.3-2"}
+ provider.query.should == {:ensure => "1.01.3-2"}
end
it "should return a nil if the package isn't found" do
executor.expects(:execute).returns("")
- @provider.query.should be_nil
+ provider.query.should be_nil
end
it "should return a hash indicating that the package is missing on error" do
executor.expects(:execute).raises(Puppet::ExecutionFailure.new("ERROR!"))
- @provider.query.should == {
+ provider.query.should == {
:ensure => :purged,
:status => 'missing',
- :name => @resource[:name],
+ :name => resource[:name],
:error => 'ok',
}
end
@@ -199,18 +218,18 @@ EOF
describe "when fetching a package list" do
it "should retrieve installed packages" do
- provider.expects(:execpipe).with(["/usr/bin/pacman", '-Q'])
- provider.installedpkgs
+ described_class.expects(:execpipe).with(["/usr/bin/pacman", '-Q'])
+ described_class.installedpkgs
end
it "should retrieve installed package groups" do
- provider.expects(:execpipe).with(["/usr/bin/pacman", '-Qg'])
- provider.installedgroups
+ described_class.expects(:execpipe).with(["/usr/bin/pacman", '-Qg'])
+ described_class.installedgroups
end
it "should return installed packages with their versions" do
- provider.expects(:execpipe).yields(StringIO.new("package1 1.23-4\npackage2 2.00\n"))
- packages = provider.installedpkgs
+ described_class.expects(:execpipe).yields(StringIO.new("package1 1.23-4\npackage2 2.00\n"))
+ packages = described_class.installedpkgs
packages.length.should == 2
@@ -228,8 +247,8 @@ EOF
end
it "should return installed groups with a dummy version" do
- provider.expects(:execpipe).yields(StringIO.new("group1 pkg1\ngroup1 pkg2"))
- groups = provider.installedgroups
+ described_class.expects(:execpipe).yields(StringIO.new("group1 pkg1\ngroup1 pkg2"))
+ groups = described_class.installedgroups
groups.length.should == 1
@@ -241,14 +260,14 @@ EOF
end
it "should return nil on error" do
- provider.expects(:execpipe).twice.raises(Puppet::ExecutionFailure.new("ERROR!"))
- provider.instances.should be_nil
+ described_class.expects(:execpipe).twice.raises(Puppet::ExecutionFailure.new("ERROR!"))
+ described_class.instances.should be_nil
end
it "should warn on invalid input" do
- provider.expects(:execpipe).yields(StringIO.new("blah"))
- provider.expects(:warning).with("Failed to match line blah")
- provider.installedpkgs == []
+ described_class.expects(:execpipe).yields(StringIO.new("blah"))
+ described_class.expects(:warning).with("Failed to match line blah")
+ described_class.installedpkgs == []
end
end
@@ -265,7 +284,7 @@ EOF
in_sequence(get_latest_version).
returns("")
- @provider.latest
+ provider.latest
end
it "should get query pacman for the latest version" do
@@ -277,10 +296,10 @@ EOF
executor.
expects(:execute).
in_sequence(get_latest_version).
- with(['/usr/bin/pacman', '-Sp', '--print-format', '%v', @resource[:name]], no_extra_options).
+ with(['/usr/bin/pacman', '-Sp', '--print-format', '%v', resource[:name]], no_extra_options).
returns("")
- @provider.latest
+ provider.latest
end
it "should return the version number from pacman" do
@@ -289,7 +308,7 @@ EOF
at_least_once().
returns("1.00.2-3\n")
- @provider.latest.should == "1.00.2-3"
+ provider.latest.should == "1.00.2-3"
end
end
end
diff --git a/spec/unit/provider/package/windows/package_spec.rb b/spec/unit/provider/package/windows/package_spec.rb
index 7466be1e9..632fa13a6 100755
--- a/spec/unit/provider/package/windows/package_spec.rb
+++ b/spec/unit/provider/package/windows/package_spec.rb
@@ -32,7 +32,7 @@ describe Puppet::Provider::Package::Windows::Package do
end
end
- context '::with_key' do
+ context '::with_key', :if => Puppet.features.microsoft_windows? do
it 'should search HKLM (64 & 32) and HKCU (64 & 32)' do
seq = sequence('reg')
@@ -44,8 +44,8 @@ describe Puppet::Provider::Package::Windows::Package do
subject.with_key { |key, values| }
end
- it 'should ignore file not found exceptions', :if => Puppet.features.microsoft_windows? do
- ex = Puppet::Util::Windows::Error.new('Failed to open registry key', Windows::Error::ERROR_FILE_NOT_FOUND)
+ it 'should ignore file not found exceptions' do
+ ex = Puppet::Util::Windows::Error.new('Failed to open registry key', Puppet::Util::Windows::Error::ERROR_FILE_NOT_FOUND)
# make sure we don't stop after the first exception
subject.expects(:open).times(4).raises(ex)
@@ -55,13 +55,13 @@ describe Puppet::Provider::Package::Windows::Package do
keys.should be_empty
end
- it 'should raise other types of exceptions', :if => Puppet.features.microsoft_windows? do
- ex = Puppet::Util::Windows::Error.new('Failed to open registry key', Windows::Error::ERROR_ACCESS_DENIED)
+ it 'should raise other types of exceptions' do
+ ex = Puppet::Util::Windows::Error.new('Failed to open registry key', Puppet::Util::Windows::Error::ERROR_ACCESS_DENIED)
subject.expects(:open).raises(ex)
expect {
subject.with_key{ |key, values| }
- }.to raise_error(Puppet::Error, /Access is denied/)
+ }.to raise_error(Puppet::Util::Windows::Error, /Access is denied/)
end
end
@@ -103,6 +103,21 @@ describe Puppet::Provider::Package::Windows::Package do
end
end
+ context '::munge' do
+ it 'should shell quote strings with spaces and fix forward slashes' do
+ subject.munge('c:/windows/the thing').should == '"c:\windows\the thing"'
+ end
+ it 'should leave properly formatted paths alone' do
+ subject.munge('c:\windows\thething').should == 'c:\windows\thething'
+ end
+ end
+
+ context '::replace_forward_slashes' do
+ it 'should replace forward with back slashes' do
+ subject.replace_forward_slashes('c:/windows/thing/stuff').should == 'c:\windows\thing\stuff'
+ end
+ end
+
context '::quote' do
it 'should shell quote strings with spaces' do
subject.quote('foo bar').should == '"foo bar"'
diff --git a/spec/unit/provider/package/yum_spec.rb b/spec/unit/provider/package/yum_spec.rb
index d7130ee77..20a523be9 100755
--- a/spec/unit/provider/package/yum_spec.rb
+++ b/spec/unit/provider/package/yum_spec.rb
@@ -81,6 +81,7 @@ describe provider_class do
resource[:ensure] = :installed
resource[:install_options] = ['-t', {'-x' => 'expackage'}]
+ provider.expects(:yum).with('-d', '0', '-e', '0', '-y', ['-t', '-x=expackage'], :list, name)
provider.expects(:yum).with('-d', '0', '-e', '0', '-y', ['-t', '-x=expackage'], :install, name)
provider.install
end
diff --git a/spec/unit/provider/parsedfile_spec.rb b/spec/unit/provider/parsedfile_spec.rb
index f8a1773de..b814bc7ee 100755
--- a/spec/unit/provider/parsedfile_spec.rb
+++ b/spec/unit/provider/parsedfile_spec.rb
@@ -108,7 +108,7 @@ describe Puppet::Provider::ParsedFile do
provider.prefetch
@filetype = Puppet::Util::FileType.filetype(:flat).new("/my/file")
- Puppet::Util::FileType.filetype(:flat).stubs(:new).with("/my/file").returns @filetype
+ Puppet::Util::FileType.filetype(:flat).stubs(:new).with("/my/file",nil).returns @filetype
@filetype.stubs(:write)
end
diff --git a/spec/unit/provider/scheduled_task/win32_taskscheduler_spec.rb b/spec/unit/provider/scheduled_task/win32_taskscheduler_spec.rb
index 4a950dad3..3d37956c5 100644
--- a/spec/unit/provider/scheduled_task/win32_taskscheduler_spec.rb
+++ b/spec/unit/provider/scheduled_task/win32_taskscheduler_spec.rb
@@ -1,7 +1,7 @@
#! /usr/bin/env ruby
require 'spec_helper'
-require 'win32/taskscheduler' if Puppet.features.microsoft_windows?
+require 'puppet/util/windows/taskscheduler' if Puppet.features.microsoft_windows?
shared_examples_for "a trigger that handles start_date and start_time" do
let(:trigger) do
@@ -569,27 +569,27 @@ describe Puppet::Type.type(:scheduled_task).provider(:win32_taskscheduler), :if
let(:resource) { described_class.new(:name => 'foobar', :command => 'C:\Windows\System32\notepad.exe') }
it 'should consider the user as in sync if the name matches' do
- Puppet::Util::Windows::Security.expects(:name_to_sid).with('joe').twice.returns('SID A')
+ Puppet::Util::Windows::SID.expects(:name_to_sid).with('joe').twice.returns('SID A')
resource.should be_user_insync('joe', ['joe'])
end
it 'should consider the user as in sync if the current user is fully qualified' do
- Puppet::Util::Windows::Security.expects(:name_to_sid).with('joe').returns('SID A')
- Puppet::Util::Windows::Security.expects(:name_to_sid).with('MACHINE\joe').returns('SID A')
+ Puppet::Util::Windows::SID.expects(:name_to_sid).with('joe').returns('SID A')
+ Puppet::Util::Windows::SID.expects(:name_to_sid).with('MACHINE\joe').returns('SID A')
resource.should be_user_insync('MACHINE\joe', ['joe'])
end
it 'should consider a current user of the empty string to be the same as the system user' do
- Puppet::Util::Windows::Security.expects(:name_to_sid).with('system').twice.returns('SYSTEM SID')
+ Puppet::Util::Windows::SID.expects(:name_to_sid).with('system').twice.returns('SYSTEM SID')
resource.should be_user_insync('', ['system'])
end
it 'should consider different users as being different' do
- Puppet::Util::Windows::Security.expects(:name_to_sid).with('joe').returns('SID A')
- Puppet::Util::Windows::Security.expects(:name_to_sid).with('bob').returns('SID B')
+ Puppet::Util::Windows::SID.expects(:name_to_sid).with('joe').returns('SID A')
+ Puppet::Util::Windows::SID.expects(:name_to_sid).with('bob').returns('SID B')
resource.should_not be_user_insync('joe', ['bob'])
end
@@ -1469,7 +1469,7 @@ describe Puppet::Type.type(:scheduled_task).provider(:win32_taskscheduler), :if
end
it 'should use nil for user and password when setting the user to the SYSTEM account' do
- Puppet::Util::Windows::Security.stubs(:name_to_sid).with('system').returns('SYSTEM SID')
+ Puppet::Util::Windows::SID.stubs(:name_to_sid).with('system').returns('SYSTEM SID')
resource = Puppet::Type.type(:scheduled_task).new(
:name => 'Test Task',
@@ -1483,7 +1483,7 @@ describe Puppet::Type.type(:scheduled_task).provider(:win32_taskscheduler), :if
end
it 'should use the specified user and password when setting the user to anything other than SYSTEM' do
- Puppet::Util::Windows::Security.stubs(:name_to_sid).with('my_user_name').returns('SID A')
+ Puppet::Util::Windows::SID.stubs(:name_to_sid).with('my_user_name').returns('SID A')
resource = Puppet::Type.type(:scheduled_task).new(
:name => 'Test Task',
diff --git a/spec/unit/provider/service/openbsd_spec.rb b/spec/unit/provider/service/openbsd_spec.rb
index e7ba4a4db..ad1e50d18 100644..100755
--- a/spec/unit/provider/service/openbsd_spec.rb
+++ b/spec/unit/provider/service/openbsd_spec.rb
@@ -173,13 +173,13 @@ describe provider_class do
it "can append to the package_scripts array and return the result" do
provider = described_class.new(Puppet::Type.type(:service).new(:name => 'cupsd'))
provider.expects(:load_rcconf_local_array).returns ['pkg_scripts="dbus_daemon"']
- expect(provider.pkg_scripts_append).to match_array(['dbus_daemon', 'cupsd'])
+ provider.pkg_scripts_append.should === ['dbus_daemon', 'cupsd']
end
it "should not duplicate the script name" do
provider = described_class.new(Puppet::Type.type(:service).new(:name => 'cupsd'))
provider.expects(:load_rcconf_local_array).returns ['pkg_scripts="cupsd dbus_daemon"']
- expect(provider.pkg_scripts_append).to match_array(['dbus_daemon', 'cupsd'])
+ provider.pkg_scripts_append.should === ['cupsd', 'dbus_daemon']
end
end
@@ -210,6 +210,22 @@ describe provider_class do
output = provider.set_content_flags(content,"-d")
output.should match_array(['cupsd_flags="-d"'])
end
+
+ it "does not set empty flags for package scripts" do
+ content = []
+ provider = described_class.new(Puppet::Type.type(:service).new(:name => 'cupsd'))
+ provider.expects(:in_base?).returns(false)
+ output = provider.set_content_flags(content,'')
+ output.should match_array([nil])
+ end
+
+ it "does set empty flags for base scripts" do
+ content = []
+ provider = described_class.new(Puppet::Type.type(:service).new(:name => 'ntpd'))
+ provider.expects(:in_base?).returns(true)
+ output = provider.set_content_flags(content,'')
+ output.should match_array(['ntpd_flags=""'])
+ end
end
describe "#remove_content_flags" do
@@ -229,4 +245,12 @@ describe provider_class do
provider.set_content_scripts(content,scripts).should match_array(['pkg_scripts="dbus_daemon cupsd"'])
end
end
+
+ describe "#in_base?" do
+ it "should true if in base" do
+ File.stubs(:readlines).with('/etc/rc.conf').returns(['sshd_flags=""'])
+ provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd'))
+ provider.in_base?.should be_true
+ end
+ end
end
diff --git a/spec/unit/provider/service/upstart_spec.rb b/spec/unit/provider/service/upstart_spec.rb
index 06be70acb..d31511626 100755
--- a/spec/unit/provider/service/upstart_spec.rb
+++ b/spec/unit/provider/service/upstart_spec.rb
@@ -22,6 +22,11 @@ describe Puppet::Type.type(:service).provider(:upstart) do
provider_class.stubs(:which).with("/sbin/initctl").returns("/sbin/initctl")
end
+ it "should be the default provider on Ubuntu" do
+ Facter.expects(:value).with(:operatingsystem).returns("Ubuntu")
+ described_class.default?.should be_true
+ end
+
describe "excluding services" do
it "ignores tty and serial on Redhat systems" do
Facter.stubs(:value).with(:osfamily).returns('RedHat')
@@ -50,7 +55,13 @@ describe Puppet::Type.type(:service).provider(:upstart) do
end
it "should not find excluded services" do
- processes = "wait-for-state stop/waiting\nportmap-wait start/running\nidmapd-mounting stop/waiting\nstartpar-bridge start/running"
+ processes = "wait-for-state stop/waiting"
+ processes += "\nportmap-wait start/running"
+ processes += "\nidmapd-mounting stop/waiting"
+ processes += "\nstartpar-bridge start/running"
+ processes += "\ncryptdisks-udev stop/waiting"
+ processes += "\nstatd-mounting stop/waiting"
+ processes += "\ngssd-mounting stop/waiting"
provider_class.stubs(:execpipe).yields(processes)
provider_class.instances.should be_empty
end
diff --git a/spec/unit/provider/ssh_authorized_key/parsed_spec.rb b/spec/unit/provider/ssh_authorized_key/parsed_spec.rb
index d0cd4e850..2e88c57df 100755
--- a/spec/unit/provider/ssh_authorized_key/parsed_spec.rb
+++ b/spec/unit/provider/ssh_authorized_key/parsed_spec.rb
@@ -99,6 +99,12 @@ describe provider_class, :unless => Puppet.features.microsoft_windows? do
@provider_class.parse_options(optionstr).should == options
end
+ it "should parse quoted options" do
+ line = 'command="/usr/local/bin/mybin \"$SSH_ORIGINAL_COMMAND\"" ssh-rsa xxx mykey'
+
+ @provider_class.parse(line)[0][:options][0].should == 'command="/usr/local/bin/mybin \"$SSH_ORIGINAL_COMMAND\""'
+ end
+
it "should use '' as name for entries that lack a comment" do
line = "ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAut8aOSxenjOqF527dlsdHWV4MNoAsX14l9M297+SQXaQ5Z3BedIxZaoQthkDALlV/25A1COELrg9J2MqJNQc8Xe9XQOIkBQWWinUlD/BXwoOTWEy8C8zSZPHZ3getMMNhGTBO+q/O+qiJx3y5cA4MTbw2zSxukfWC87qWwcZ64UUlegIM056vPsdZWFclS9hsROVEa57YUMrehQ1EGxT4Z5j6zIopufGFiAPjZigq/vqgcAqhAKP6yu4/gwO6S9tatBeEjZ8fafvj1pmvvIplZeMr96gHE7xS3pEEQqnB3nd4RY7AF6j9kFixnsytAUO7STPh/M3pLiVQBN89TvWPQ=="
diff --git a/spec/unit/provider/user/user_role_add_spec.rb b/spec/unit/provider/user/user_role_add_spec.rb
index 8dd48e767..42cc4995e 100755
--- a/spec/unit/provider/user/user_role_add_spec.rb
+++ b/spec/unit/provider/user/user_role_add_spec.rb
@@ -317,7 +317,7 @@ EOT
describe "#shadow_entry" do
it "should return the line for the right user" do
File.stubs(:readlines).returns(["someuser:!:10:5:20:7:1::\n", "fakeval:*:20:10:30:7:2::\n", "testuser:*:30:15:40:7:3::\n"])
- provider.shadow_entry.should == ["fakeval", "*", "20", "10", "30", "7", "2"]
+ provider.shadow_entry.should == ["fakeval", "*", "20", "10", "30", "7", "2", "", ""]
end
end
@@ -331,5 +331,27 @@ EOT
File.stubs(:readlines).returns(["fakeval:NP:12345::::::\n"])
provider.password_max_age.should == -1
end
+
+ it "should return -1 for no maximum when failed attempts are present" do
+ File.stubs(:readlines).returns(["fakeval:NP:12345::::::3\n"])
+ provider.password_max_age.should == -1
+ end
+ end
+
+ describe "#password_min_age" do
+ it "should return a minimum age number" do
+ File.stubs(:readlines).returns(["fakeval:NP:12345:10:50::::\n"])
+ provider.password_min_age.should == "10"
+ end
+
+ it "should return -1 for no minimum" do
+ File.stubs(:readlines).returns(["fakeval:NP:12345::::::\n"])
+ provider.password_min_age.should == -1
+ end
+
+ it "should return -1 for no minimum when failed attempts are present" do
+ File.stubs(:readlines).returns(["fakeval:NP:12345::::::3\n"])
+ provider.password_min_age.should == -1
+ end
end
end
diff --git a/spec/unit/provider/user/windows_adsi_spec.rb b/spec/unit/provider/user/windows_adsi_spec.rb
index 8d3ed1d0a..84aa8a74c 100755
--- a/spec/unit/provider/user/windows_adsi_spec.rb
+++ b/spec/unit/provider/user/windows_adsi_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Puppet::Type.type(:user).provider(:windows_adsi) do
+describe Puppet::Type.type(:user).provider(:windows_adsi), :if => Puppet.features.microsoft_windows? do
let(:resource) do
Puppet::Type.type(:user).new(
:title => 'testuser',
@@ -16,8 +16,8 @@ describe Puppet::Type.type(:user).provider(:windows_adsi) do
let(:connection) { stub 'connection' }
before :each do
- Puppet::Util::ADSI.stubs(:computer_name).returns('testcomputername')
- Puppet::Util::ADSI.stubs(:connect).returns connection
+ Puppet::Util::Windows::ADSI.stubs(:computer_name).returns('testcomputername')
+ Puppet::Util::Windows::ADSI.stubs(:connect).returns connection
end
describe ".instances" do
@@ -30,8 +30,8 @@ describe Puppet::Type.type(:user).provider(:windows_adsi) do
end
end
- it "should provide access to a Puppet::Util::ADSI::User object" do
- provider.user.should be_a(Puppet::Util::ADSI::User)
+ it "should provide access to a Puppet::Util::Windows::ADSI::User object" do
+ provider.user.should be_a(Puppet::Util::Windows::ADSI::User)
end
describe "when managing groups" do
@@ -68,7 +68,7 @@ describe Puppet::Type.type(:user).provider(:windows_adsi) do
resource[:home] = 'C:\Users\testuser'
user = stub 'user'
- Puppet::Util::ADSI::User.expects(:create).with('testuser').returns user
+ Puppet::Util::Windows::ADSI::User.expects(:create).with('testuser').returns user
user.stubs(:groups).returns(['group2', 'group3'])
@@ -82,12 +82,12 @@ describe Puppet::Type.type(:user).provider(:windows_adsi) do
provider.create
end
- it "should load the profile if managehome is set", :if => Puppet.features.microsoft_windows? do
+ it "should load the profile if managehome is set" do
resource[:password] = '0xDeadBeef'
resource[:managehome] = true
user = stub_everything 'user'
- Puppet::Util::ADSI::User.expects(:create).with('testuser').returns user
+ Puppet::Util::Windows::ADSI::User.expects(:create).with('testuser').returns user
Puppet::Util::Windows::User.expects(:load_profile).with('testuser', '0xDeadBeef')
provider.create
@@ -115,18 +115,18 @@ describe Puppet::Type.type(:user).provider(:windows_adsi) do
end
it 'should not create a user if a group by the same name exists' do
- Puppet::Util::ADSI::User.expects(:create).with('testuser').raises( Puppet::Error.new("Cannot create user if group 'testuser' exists.") )
+ Puppet::Util::Windows::ADSI::User.expects(:create).with('testuser').raises( Puppet::Error.new("Cannot create user if group 'testuser' exists.") )
expect{ provider.create }.to raise_error( Puppet::Error,
/Cannot create user if group 'testuser' exists./ )
end
end
it 'should be able to test whether a user exists' do
- Puppet::Util::ADSI.stubs(:sid_uri_safe).returns(nil)
- Puppet::Util::ADSI.stubs(:connect).returns stub('connection')
+ Puppet::Util::Windows::ADSI.stubs(:sid_uri_safe).returns(nil)
+ Puppet::Util::Windows::ADSI.stubs(:connect).returns stub('connection')
provider.should be_exists
- Puppet::Util::ADSI.stubs(:connect).returns nil
+ Puppet::Util::Windows::ADSI.stubs(:connect).returns nil
provider.should_not be_exists
end
@@ -136,12 +136,12 @@ describe Puppet::Type.type(:user).provider(:windows_adsi) do
provider.delete
end
- it 'should delete the profile if managehome is set', :if => Puppet.features.microsoft_windows? do
+ it 'should delete the profile if managehome is set' do
resource[:managehome] = true
sid = 'S-A-B-C'
- Puppet::Util::Windows::Security.expects(:name_to_sid).with('testuser').returns(sid)
- Puppet::Util::ADSI::UserProfile.expects(:delete).with(sid)
+ Puppet::Util::Windows::SID.expects(:name_to_sid).with('testuser').returns(sid)
+ Puppet::Util::Windows::ADSI::UserProfile.expects(:delete).with(sid)
connection.expects(:Delete).with('user', 'testuser')
provider.delete
@@ -153,8 +153,8 @@ describe Puppet::Type.type(:user).provider(:windows_adsi) do
provider.flush
end
- it "should return the user's SID as uid", :if => Puppet.features.microsoft_windows? do
- Puppet::Util::Windows::Security.expects(:name_to_sid).with('testuser').returns('S-1-5-21-1362942247-2130103807-3279964888-1111')
+ it "should return the user's SID as uid" do
+ Puppet::Util::Windows::SID.expects(:name_to_sid).with('testuser').returns('S-1-5-21-1362942247-2130103807-3279964888-1111')
provider.uid.should == 'S-1-5-21-1362942247-2130103807-3279964888-1111'
end
diff --git a/spec/unit/reports/store_spec.rb b/spec/unit/reports/store_spec.rb
index 7866571a1..7f94f7d1b 100755
--- a/spec/unit/reports/store_spec.rb
+++ b/spec/unit/reports/store_spec.rb
@@ -30,25 +30,9 @@ describe processor do
File.read(File.join(Puppet[:reportdir], @report.host, "201101061200.yaml")).should == @report.to_yaml
end
- it "should write to the report directory in the correct sequence" do
- # By doing things in this sequence we should protect against race
- # conditions
- Time.stubs(:now).returns(Time.parse("2011-01-06 12:00:00 UTC"))
- writeseq = sequence("write")
- file = mock "file"
- Tempfile.expects(:new).in_sequence(writeseq).returns(file)
- file.expects(:chmod).in_sequence(writeseq).with(0640)
- file.expects(:print).with(@report.to_yaml).in_sequence(writeseq)
- file.expects(:close).in_sequence(writeseq)
- file.stubs(:path).returns(File.join(Dir.tmpdir, "foo123"))
- FileUtils.expects(:mv).in_sequence(writeseq).with(File.join(Dir.tmpdir, "foo123"), File.join(Puppet[:reportdir], @report.host, "201101061200.yaml"))
- @report.process
- end
-
it "rejects invalid hostnames" do
@report.host = ".."
Puppet::FileSystem.expects(:exist?).never
- Tempfile.expects(:new).never
expect { @report.process }.to raise_error(ArgumentError, /Invalid node/)
end
end
diff --git a/spec/unit/resource/catalog_spec.rb b/spec/unit/resource/catalog_spec.rb
index 741f60ef3..80933dc41 100755
--- a/spec/unit/resource/catalog_spec.rb
+++ b/spec/unit/resource/catalog_spec.rb
@@ -606,7 +606,6 @@ describe Puppet::Resource::Catalog, "when compiling" do
@catalog.apply
end
- after { Puppet.settings.clear }
end
describe "non-host catalogs" do
@@ -627,7 +626,6 @@ describe Puppet::Resource::Catalog, "when compiling" do
@catalog.apply
end
- after { Puppet.settings.clear }
end
end
@@ -659,9 +657,6 @@ describe Puppet::Resource::Catalog, "when compiling" do
@catalog.write_graph(@name)
end
- after do
- Puppet.settings.clear
- end
end
describe "when indirecting" do
diff --git a/spec/unit/resource_spec.rb b/spec/unit/resource_spec.rb
index 73c27c1fe..e954a2179 100755
--- a/spec/unit/resource_spec.rb
+++ b/spec/unit/resource_spec.rb
@@ -592,7 +592,7 @@ describe Puppet::Resource do
text = @resource.render('yaml')
newresource = Puppet::Resource.convert_from('yaml', text)
- newresource.should equal_attributes_of @resource
+ newresource.should equal_resource_attributes_of @resource
end
end
@@ -615,7 +615,7 @@ describe Puppet::Resource do
text = @resource.render('yaml')
newresource = Puppet::Resource.convert_from('yaml', text)
- newresource.should equal_attributes_of @resource
+ newresource.should equal_resource_attributes_of @resource
end
end
diff --git a/spec/unit/settings/array_setting_spec.rb b/spec/unit/settings/array_setting_spec.rb
new file mode 100644
index 000000000..05cc28d6a
--- /dev/null
+++ b/spec/unit/settings/array_setting_spec.rb
@@ -0,0 +1,39 @@
+require 'spec_helper'
+
+require 'puppet/settings'
+require 'puppet/settings/array_setting'
+
+describe Puppet::Settings::ArraySetting do
+ subject { described_class.new(:settings => stub('settings'), :desc => "test") }
+
+ it "is of type :array" do
+ expect(subject.type).to eq :array
+ end
+
+ describe "munging the value" do
+ describe "when given a string" do
+ it "splits multiple values into an array" do
+ expect(subject.munge("foo,bar")).to eq %w[foo bar]
+ end
+ it "strips whitespace between elements" do
+ expect(subject.munge("foo , bar")).to eq %w[foo bar]
+ end
+
+ it "creates an array when one item is given" do
+ expect(subject.munge("foo")).to eq %w[foo]
+ end
+ end
+
+ describe "when given an array" do
+ it "returns the array" do
+ expect(subject.munge(%w[foo])).to eq %w[foo]
+ end
+ end
+
+ it "raises an error when given an unexpected object type" do
+ expect {
+ subject.munge({:foo => 'bar'})
+ }.to raise_error(ArgumentError, "Expected an Array or String, got a Hash")
+ end
+ end
+end
diff --git a/spec/unit/settings/autosign_setting_spec.rb b/spec/unit/settings/autosign_setting_spec.rb
index 0c8184c8a..0dbfe4ecb 100644
--- a/spec/unit/settings/autosign_setting_spec.rb
+++ b/spec/unit/settings/autosign_setting_spec.rb
@@ -73,7 +73,7 @@ describe Puppet::Settings::AutosignSetting do
describe "converting the setting to a resource" do
it "converts the file path to a file resource" do
path = File.expand_path('/path/to/autosign.conf')
- settings.stubs(:value).with('autosign').returns(path)
+ settings.stubs(:value).with('autosign', nil, false).returns(path)
Puppet::FileSystem.stubs(:exist?).with(path).returns true
Puppet.stubs(:features).returns(stub(:root? => true, :microsoft_windows? => false))
@@ -91,7 +91,7 @@ describe Puppet::Settings::AutosignSetting do
end
it "returns nil when the setting is a boolean" do
- settings.stubs(:value).with('autosign').returns 'true'
+ settings.stubs(:value).with('autosign', nil, false).returns 'true'
setting.mode = '0664'
setting.owner = 'service'
diff --git a/spec/unit/settings/environment_conf_spec.rb b/spec/unit/settings/environment_conf_spec.rb
index 6a8a6689e..e4a492ae8 100644
--- a/spec/unit/settings/environment_conf_spec.rb
+++ b/spec/unit/settings/environment_conf_spec.rb
@@ -3,31 +3,47 @@ require 'puppet/settings/environment_conf.rb'
describe Puppet::Settings::EnvironmentConf do
+ def setup_environment_conf(config, conf_hash)
+ conf_hash.each do |setting,value|
+ config.expects(:setting).with(setting).returns(
+ mock('setting', :value => value)
+ )
+ end
+ end
+
context "with config" do
- let(:config) { stub(:config) }
+ let(:config) { stub('config') }
let(:envconf) { Puppet::Settings::EnvironmentConf.new("/some/direnv", config, ["/global/modulepath"]) }
it "reads a modulepath from config and does not include global_module_path" do
- config.expects(:setting).with(:modulepath).returns(
- mock('setting', :value => '/some/modulepath')
- )
+ setup_environment_conf(config, :modulepath => '/some/modulepath')
+
expect(envconf.modulepath).to eq(File.expand_path('/some/modulepath'))
end
it "reads a manifest from config" do
- config.expects(:setting).with(:manifest).returns(
- mock('setting', :value => '/some/manifest')
- )
+ setup_environment_conf(config, :manifest => '/some/manifest')
+
expect(envconf.manifest).to eq(File.expand_path('/some/manifest'))
end
it "reads a config_version from config" do
- config.expects(:setting).with(:config_version).returns(
- mock('setting', :value => '/some/version.sh')
- )
+ setup_environment_conf(config, :config_version => '/some/version.sh')
+
expect(envconf.config_version).to eq(File.expand_path('/some/version.sh'))
end
+ it "read an environment_timeout from config" do
+ setup_environment_conf(config, :environment_timeout => '3m')
+
+ expect(envconf.environment_timeout).to eq(180)
+ end
+
+ it "can retrieve raw settings" do
+ setup_environment_conf(config, :manifest => 'manifest.pp')
+
+ expect(envconf.raw_setting(:manifest)).to eq('manifest.pp')
+ end
end
context "without config" do
@@ -47,5 +63,56 @@ describe Puppet::Settings::EnvironmentConf do
it "returns nothing for config_version when config has none" do
expect(envconf.config_version).to be_nil
end
+
+ it "returns a defult of 0 for environment_timeout when config has none" do
+ expect(envconf.environment_timeout).to eq(0)
+ end
+
+ it "can still retrieve raw setting" do
+ expect(envconf.raw_setting(:manifest)).to be_nil
+ end
+ end
+
+ describe "with disable_per_environment_manifest" do
+
+ let(:config) { stub('config') }
+ let(:envconf) { Puppet::Settings::EnvironmentConf.new("/some/direnv", config, ["/global/modulepath"]) }
+
+ context "set true" do
+
+ before(:each) do
+ Puppet[:default_manifest] = File.expand_path('/default/manifest')
+ Puppet[:disable_per_environment_manifest] = true
+ end
+
+ it "ignores environment.conf manifest" do
+ setup_environment_conf(config, :manifest => '/some/manifest.pp')
+
+ expect(envconf.manifest).to eq(File.expand_path('/default/manifest'))
+ end
+
+ it "logs error when environment.conf has manifest set" do
+ setup_environment_conf(config, :manifest => '/some/manifest.pp')
+
+ envconf.manifest
+ expect(@logs.first.to_s).to match(/disable_per_environment_manifest.*true.*environment.conf.*does not match the default_manifest/)
+ end
+
+ it "does not log an error when environment.conf does not have a manifest set" do
+ setup_environment_conf(config, :manifest => nil)
+
+ expect(envconf.manifest).to eq(File.expand_path('/default/manifest'))
+ expect(@logs).to be_empty
+ end
+ end
+
+ it "uses environment.conf when false" do
+ setup_environment_conf(config, :manifest => '/some/manifest.pp')
+
+ Puppet[:default_manifest] = File.expand_path('/default/manifest')
+ Puppet[:disable_per_environment_manifest] = false
+
+ expect(envconf.manifest).to eq(File.expand_path('/some/manifest.pp'))
+ end
end
end
diff --git a/spec/unit/settings/file_setting_spec.rb b/spec/unit/settings/file_setting_spec.rb
index b31d0ccb3..c77cc5981 100755
--- a/spec/unit/settings/file_setting_spec.rb
+++ b/spec/unit/settings/file_setting_spec.rb
@@ -129,7 +129,7 @@ describe Puppet::Settings::FileSetting do
@settings = mock 'settings'
@file = Puppet::Settings::FileSetting.new(:settings => @settings, :desc => "eh", :name => :myfile, :section => "mysect")
@file.stubs(:create_files?).returns true
- @settings.stubs(:value).with(:myfile).returns @basepath
+ @settings.stubs(:value).with(:myfile, nil, false).returns @basepath
end
it "should return :file as its type" do
@@ -146,19 +146,20 @@ describe Puppet::Settings::FileSetting do
it "should manage existent files even if 'create_files' is not enabled" do
@file.expects(:create_files?).returns false
@file.expects(:type).returns :file
+ Puppet::FileSystem.stubs(:exist?)
Puppet::FileSystem.expects(:exist?).with(@basepath).returns true
@file.to_resource.should be_instance_of(Puppet::Resource)
end
describe "on POSIX systems", :if => Puppet.features.posix? do
it "should skip files in /dev" do
- @settings.stubs(:value).with(:myfile).returns "/dev/file"
+ @settings.stubs(:value).with(:myfile, nil, false).returns "/dev/file"
@file.to_resource.should be_nil
end
end
it "should skip files whose paths are not strings" do
- @settings.stubs(:value).with(:myfile).returns :foo
+ @settings.stubs(:value).with(:myfile, nil, false).returns :foo
@file.to_resource.should be_nil
end
@@ -169,7 +170,7 @@ describe Puppet::Settings::FileSetting do
end
it "should fully qualified returned files if necessary (#795)" do
- @settings.stubs(:value).with(:myfile).returns "myfile"
+ @settings.stubs(:value).with(:myfile, nil, false).returns "myfile"
path = File.expand_path('myfile')
@file.to_resource.title.should == path
end
diff --git a/spec/unit/settings/priority_setting_spec.rb b/spec/unit/settings/priority_setting_spec.rb
index d51e39dc4..62cad5def 100755
--- a/spec/unit/settings/priority_setting_spec.rb
+++ b/spec/unit/settings/priority_setting_spec.rb
@@ -53,10 +53,10 @@ describe Puppet::Settings::PrioritySetting do
describe "on a Windows-like platform it", :if => Puppet::Util::Platform.windows? do
it "parses high, normal, low, and idle priorities" do
{
- '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
}.each do |value, converted_value|
setting.munge(value).should == converted_value
end
diff --git a/spec/unit/settings_spec.rb b/spec/unit/settings_spec.rb
index 66ec67607..0a1cee2bd 100755
--- a/spec/unit/settings_spec.rb
+++ b/spec/unit/settings_spec.rb
@@ -674,7 +674,7 @@ describe Puppet::Settings do
@settings.send(:parse_config_files)
expect(@settings.value(:manifestdir)).to eq("/somewhere/production/manifests")
end
-
+
it "interpolates the set environment when no environment specified" do
text = <<-EOF
[main]
@@ -1003,102 +1003,95 @@ describe Puppet::Settings do
end
describe "deprecations" do
- context "in puppet.conf" do
-
- def assert_puppet_conf_deprecation(setting, matches)
- Puppet.expects(:deprecation_warning).with(regexp_matches(matches), anything)
-
- val = "/you/can/set/this/but/will/get/warning"
- text = "[main]
- #{setting}=#{val}
- "
- Puppet.settings.parse_config(text)
- end
-
- it "warns when manifest is set" do
- assert_puppet_conf_deprecation('manifest', /manifest.*puppet.conf/)
- end
-
- it "warns when modulepath is set" do
- assert_puppet_conf_deprecation('modulepath', /modulepath.*puppet.conf/)
- end
-
- it "warns when config_version is set" do
- assert_puppet_conf_deprecation('config_version', /config_version.*puppet.conf/)
- end
-
- it "warns when manifestdir is set" do
- assert_puppet_conf_deprecation('manifestdir', /Setting manifestdir.*is.*deprecated/)
- end
-
- it "warns when templatedir is set" do
- assert_puppet_conf_deprecation('templatedir', /Setting templatedir.*is.*deprecated/)
- end
+ let(:settings) { Puppet::Settings.new }
+ let(:app_defaults) {
+ {
+ :logdir => "/dev/null",
+ :confdir => "/dev/null",
+ :vardir => "/dev/null",
+ }
+ }
+
+ def assert_accessing_setting_is_deprecated(settings, setting)
+ Puppet.expects(:deprecation_warning).with("Accessing '#{setting}' as a setting is deprecated. See http://links.puppetlabs.com/env-settings-deprecations")
+ Puppet.expects(:deprecation_warning).with("Modifying '#{setting}' as a setting is deprecated. See http://links.puppetlabs.com/env-settings-deprecations")
+ settings[setting.intern] = apath = File.expand_path('foo')
+ expect(settings[setting.intern]).to eq(apath)
end
- context "on the command line" do
- def assert_command_line_deprecation(setting, message)
- Puppet.expects(:deprecation_warning).with(message, anything)
-
- args = ["--#{setting}", "/some/value"]
- Puppet.settings.send(:parse_global_options, args)
- end
-
- def assert_command_line_not_deprecated(setting)
- Puppet.expects(:deprecation_warning).never
-
- args = ["--#{setting}", "/some/value"]
- Puppet.settings.send(:parse_global_options, args)
+ before(:each) do
+ settings.define_settings(:main, {
+ :logdir => { :default => 'a', :desc => 'a' },
+ :confdir => { :default => 'b', :desc => 'b' },
+ :vardir => { :default => 'c', :desc => 'c' },
+ })
+ end
+
+ context "complete" do
+ let(:completely_deprecated_settings) do
+ settings.define_settings(:main, {
+ :manifestdir => {
+ :default => 'foo',
+ :desc => 'a deprecated setting',
+ :deprecated => :completely,
+ }
+ })
+ settings
end
- it "does not warn when manifest is set on command line" do
- assert_command_line_not_deprecated('manifest')
- end
+ it "warns when set in puppet.conf" do
+ Puppet.expects(:deprecation_warning).with(regexp_matches(/manifestdir is deprecated\./), 'setting-manifestdir')
- it "does not warn when modulepath is set on command line" do
- assert_command_line_not_deprecated('modulepath')
+ completely_deprecated_settings.parse_config(<<-CONF)
+ manifestdir='should warn'
+ CONF
+ completely_deprecated_settings.initialize_app_defaults(app_defaults)
end
- it "does not warn when config_version is set on command line" do
- assert_command_line_not_deprecated('config_version')
- end
+ it "warns when set on the commandline" do
+ Puppet.expects(:deprecation_warning).with(regexp_matches(/manifestdir is deprecated\./), 'setting-manifestdir')
- it "warns when manifestdir is set on command line" do
- assert_command_line_deprecation('manifestdir', "Setting manifestdir is deprecated. See http://links.puppetlabs.com/env-settings-deprecations")
+ args = ["--manifestdir", "/some/value"]
+ completely_deprecated_settings.send(:parse_global_options, args)
+ completely_deprecated_settings.initialize_app_defaults(app_defaults)
end
- it "warns when templatedir is set on command line" do
- assert_command_line_deprecation('templatedir', "Setting templatedir is deprecated. See http://links.puppetlabs.com/env-settings-deprecations")
+ it "warns when set in code" do
+ assert_accessing_setting_is_deprecated(completely_deprecated_settings, 'manifestdir')
end
end
- context "as settings in the code base" do
- def assert_accessing_setting_is_deprecated(setting)
- Puppet.expects(:deprecation_warning).with("Accessing '#{setting}' as a setting is deprecated. See http://links.puppetlabs.com/env-settings-deprecations")
- Puppet.expects(:deprecation_warning).with("Modifying '#{setting}' as a setting is deprecated. See http://links.puppetlabs.com/env-settings-deprecations")
- Puppet[setting.intern] = apath = File.expand_path('foo')
- expect(Puppet[setting.intern]).to eq(apath)
+ context "partial" do
+ let(:partially_deprecated_settings) do
+ settings.define_settings(:main, {
+ :modulepath => {
+ :default => 'foo',
+ :desc => 'a partially deprecated setting',
+ :deprecated => :allowed_on_commandline,
+ }
+ })
+ settings
end
- it "warns when attempt to access a 'manifest' setting" do
- assert_accessing_setting_is_deprecated('manifest')
+ it "warns for a deprecated setting allowed on the command line set in puppet.conf" do
+ Puppet.expects(:deprecation_warning).with(regexp_matches(/modulepath is deprecated in puppet\.conf/), 'puppet-conf-setting-modulepath')
+ partially_deprecated_settings.parse_config(<<-CONF)
+ modulepath='should warn'
+ CONF
+ partially_deprecated_settings.initialize_app_defaults(app_defaults)
end
- it "warns when attempt to access a 'modulepath' setting" do
- assert_accessing_setting_is_deprecated('modulepath')
- end
- it "warns when attempt to access a 'config_version' setting" do
- assert_accessing_setting_is_deprecated('config_version')
- end
+ it "does not warn when manifest is set on command line" do
+ Puppet.expects(:deprecation_warning).never
- it "warns when attempt to access a 'manifestdir' setting" do
- assert_accessing_setting_is_deprecated('manifestdir')
+ args = ["--modulepath", "/some/value"]
+ partially_deprecated_settings.send(:parse_global_options, args)
+ partially_deprecated_settings.initialize_app_defaults(app_defaults)
end
- it "warns when attempt to access a 'templatedir' setting" do
- assert_accessing_setting_is_deprecated('templatedir')
+ it "warns when set in code" do
+ assert_accessing_setting_is_deprecated(partially_deprecated_settings, 'modulepath')
end
-
end
end
end
@@ -1366,6 +1359,44 @@ describe Puppet::Settings do
end
end
+ describe "adding default directory environment to the catalog" do
+ let(:tmpenv) { tmpdir("envs") }
+ let(:default_path) { "#{tmpenv}/environments" }
+ before(:each) do
+ @settings.define_settings :main,
+ :environment => { :default => "production", :desc => "env"},
+ :environmentpath => { :type => :path, :default => default_path, :desc => "envpath"}
+ end
+
+ it "adds if environmentpath exists" do
+ envpath = "#{tmpenv}/custom_envpath"
+ @settings[:environmentpath] = envpath
+ Dir.mkdir(envpath)
+ catalog = @settings.to_catalog
+ expect(catalog.resource_keys).to include(["File", "#{envpath}/production"])
+ end
+
+ it "adds the first directory of environmentpath" do
+ envdir = "#{tmpenv}/custom_envpath"
+ envpath = "#{envdir}#{File::PATH_SEPARATOR}/some/other/envdir"
+ @settings[:environmentpath] = envpath
+ Dir.mkdir(envdir)
+ catalog = @settings.to_catalog
+ expect(catalog.resource_keys).to include(["File", "#{envdir}/production"])
+ end
+
+ it "handles a non-existent environmentpath" do
+ catalog = @settings.to_catalog
+ expect(catalog.resource_keys).to be_empty
+ end
+
+ it "handles a default environmentpath" do
+ Dir.mkdir(default_path)
+ catalog = @settings.to_catalog
+ expect(catalog.resource_keys).to include(["File", "#{default_path}/production"])
+ end
+ end
+
describe "when adding users and groups to the catalog" do
before do
Puppet.features.stubs(:root?).returns true
@@ -1475,14 +1506,14 @@ describe Puppet::Settings do
:maindir => { :type => :directory, :default => make_absolute("/maindir"), :desc => "a" },
:seconddir => { :type => :directory, :default => make_absolute("/seconddir"), :desc => "a"}
@settings.define_settings :main, :user => { :default => "suser", :desc => "doc" }, :group => { :default => "sgroup", :desc => "doc" }
- @settings.define_settings :other, :otherdir => {:type => :directory, :default => make_absolute("/otherdir"), :desc => "a", :owner => "service", :group => "service", :mode => 0755}
+ @settings.define_settings :other, :otherdir => {:type => :directory, :default => make_absolute("/otherdir"), :desc => "a", :owner => "service", :group => "service", :mode => '0755'}
@settings.define_settings :third, :thirddir => { :type => :directory, :default => make_absolute("/thirddir"), :desc => "b"}
- @settings.define_settings :files, :myfile => {:type => :file, :default => make_absolute("/myfile"), :desc => "a", :mode => 0755}
+ @settings.define_settings :files, :myfile => {:type => :file, :default => make_absolute("/myfile"), :desc => "a", :mode => '0755'}
end
it "should provide a method that creates directories with the correct modes" do
Puppet::Util::SUIDManager.expects(:asuser).with("suser", "sgroup").yields
- Dir.expects(:mkdir).with(make_absolute("/otherdir"), 0755)
+ Dir.expects(:mkdir).with(make_absolute("/otherdir"), '0755')
@settings.mkdir(:otherdir)
end
diff --git a/spec/unit/ssl/certificate_authority_spec.rb b/spec/unit/ssl/certificate_authority_spec.rb
index ef5a86862..2881b0a1e 100755
--- a/spec/unit/ssl/certificate_authority_spec.rb
+++ b/spec/unit/ssl/certificate_authority_spec.rb
@@ -7,7 +7,6 @@ require 'puppet/ssl/certificate_authority'
describe Puppet::SSL::CertificateAuthority do
after do
Puppet::SSL::CertificateAuthority.instance_variable_set(:@singleton_instance, nil)
- Puppet.settings.clearused
end
def stub_ca_host
@@ -937,12 +936,36 @@ describe Puppet::SSL::CertificateAuthority do
cert = stub 'cert', :content => real_cert
Puppet::SSL::Certificate.indirection.expects(:find).with("host").returns nil
- @ca.inventory.expects(:serial).with("host").returns 16
+ @ca.inventory.expects(:serials).with("host").returns [16]
@ca.crl.expects(:revoke).with { |serial, key| serial == 16 }
@ca.revoke('host')
end
+ it "should revoke all serials matching a name" do
+ real_cert = stub 'real_cert', :serial => 15
+ cert = stub 'cert', :content => real_cert
+ Puppet::SSL::Certificate.indirection.expects(:find).with("host").returns nil
+
+ @ca.inventory.expects(:serials).with("host").returns [16, 20, 25]
+
+ @ca.crl.expects(:revoke).with { |serial, key| serial == 16 }
+ @ca.crl.expects(:revoke).with { |serial, key| serial == 20 }
+ @ca.crl.expects(:revoke).with { |serial, key| serial == 25 }
+ @ca.revoke('host')
+ end
+
+ it "should raise an error if no certificate match" do
+ real_cert = stub 'real_cert', :serial => 15
+ cert = stub 'cert', :content => real_cert
+ Puppet::SSL::Certificate.indirection.expects(:find).with("host").returns nil
+
+ @ca.inventory.expects(:serials).with("host").returns []
+
+ @ca.crl.expects(:revoke).never
+ expect { @ca.revoke('host') }.to raise_error
+ end
+
context "revocation by serial number (#16798)" do
it "revokes when given a lower case hexadecimal formatted string" do
@ca.crl.expects(:revoke).with { |serial, key| serial == 15 }
diff --git a/spec/unit/ssl/inventory_spec.rb b/spec/unit/ssl/inventory_spec.rb
index 6e4fbd340..879fd90d1 100755
--- a/spec/unit/ssl/inventory_spec.rb
+++ b/spec/unit/ssl/inventory_spec.rb
@@ -133,5 +133,18 @@ describe Puppet::SSL::Inventory, :unless => Puppet.features.microsoft_windows? d
@inventory.serial("me").should == 15
end
end
+
+ describe "and finding all serial numbers" do
+ it "should return nil if the inventory file is missing" do
+ Puppet::FileSystem.expects(:exist?).with(cert_inventory).returns false
+ @inventory.serials(:whatever).should be_empty
+ end
+
+ it "should return the all the serial numbers from the lines matching the provided name" do
+ File.expects(:readlines).with(cert_inventory).returns ["0x00f blah blah /CN=me\n", "0x001 blah blah /CN=you\n", "0x002 blah blah /CN=me\n"]
+
+ @inventory.serials("me").should == [15, 2]
+ end
+ end
end
end
diff --git a/spec/unit/ssl/validator_spec.rb b/spec/unit/ssl/validator_spec.rb
index 2b8cfb0f9..ade1575dc 100644
--- a/spec/unit/ssl/validator_spec.rb
+++ b/spec/unit/ssl/validator_spec.rb
@@ -1,6 +1,5 @@
require 'spec_helper'
require 'puppet/ssl'
-require 'puppet/ssl/configuration'
describe Puppet::SSL::Validator::DefaultValidator do
let(:ssl_context) do
diff --git a/spec/unit/transaction/resource_harness_spec.rb b/spec/unit/transaction/resource_harness_spec.rb
index 5eeaf0ba4..7d9c6f439 100755
--- a/spec/unit/transaction/resource_harness_spec.rb
+++ b/spec/unit/transaction/resource_harness_spec.rb
@@ -352,6 +352,70 @@ describe Puppet::Transaction::ResourceHarness do
event.status.should != 'failure'
end
end
+
+ it "should not ignore microseconds when auditing a file's mtime" do
+ test_file = tmpfile('foo')
+ File.open(test_file, 'w').close
+ resource = Puppet::Type.type(:file).new :path => test_file, :audit => ['mtime'], :backup => false
+
+ # construct a property hash with nanosecond resolution as would be
+ # found on an ext4 file system
+ time_with_nsec_resolution = Time.at(1000, 123456.999)
+ current_from_filesystem = {:mtime => time_with_nsec_resolution}
+
+ # construct a property hash with a 1 microsecond difference from above
+ time_with_usec_resolution = Time.at(1000, 123457.000)
+ historical_from_state_yaml = {:mtime => time_with_usec_resolution}
+
+ # set up the sequence of stubs; yeah, this is pretty
+ # brittle, so this might need to be adjusted if the
+ # resource_harness logic changes
+ resource.expects(:retrieve).returns(current_from_filesystem)
+ Puppet::Util::Storage.stubs(:cache).with(resource).
+ returns(historical_from_state_yaml).then.
+ returns(current_from_filesystem).then.
+ returns(current_from_filesystem)
+
+ # there should be an audit change recorded, since the two
+ # timestamps differ by at least 1 microsecond
+ status = @harness.evaluate(resource)
+ status.events.should_not be_empty
+ status.events.each do |event|
+ event.message.should =~ /audit change: previously recorded/
+ end
+ end
+
+ it "should ignore nanoseconds when auditing a file's mtime" do
+ test_file = tmpfile('foo')
+ File.open(test_file, 'w').close
+ resource = Puppet::Type.type(:file).new :path => test_file, :audit => ['mtime'], :backup => false
+
+ # construct a property hash with nanosecond resolution as would be
+ # found on an ext4 file system
+ time_with_nsec_resolution = Time.at(1000, 123456.789)
+ current_from_filesystem = {:mtime => time_with_nsec_resolution}
+
+ # construct a property hash with the same timestamp as above,
+ # truncated to microseconds, as would be read back from state.yaml
+ time_with_usec_resolution = Time.at(1000, 123456.000)
+ historical_from_state_yaml = {:mtime => time_with_usec_resolution}
+
+ # set up the sequence of stubs; yeah, this is pretty
+ # brittle, so this might need to be adjusted if the
+ # resource_harness logic changes
+ resource.expects(:retrieve).returns(current_from_filesystem)
+ Puppet::Util::Storage.stubs(:cache).with(resource).
+ returns(historical_from_state_yaml).then.
+ returns(current_from_filesystem).then.
+ returns(current_from_filesystem)
+
+ # there should be no audit change recorded, despite the
+ # slight difference in the two timestamps
+ status = @harness.evaluate(resource)
+ status.events.each do |event|
+ event.message.should_not =~ /audit change: previously recorded/
+ end
+ end
end
describe "when applying changes" do
diff --git a/spec/unit/transaction_spec.rb b/spec/unit/transaction_spec.rb
index 04ce08c4f..bf7820227 100755
--- a/spec/unit/transaction_spec.rb
+++ b/spec/unit/transaction_spec.rb
@@ -37,7 +37,8 @@ describe Puppet::Transaction do
# This will basically only ever be used during testing.
it "should automatically create resource statuses if asked for a non-existent status" do
resource = Puppet::Type.type(:notify).new :title => "foobar"
- @transaction.resource_status(resource).should be_instance_of(Puppet::Resource::Status)
+ transaction = transaction_with_resource(resource)
+ transaction.resource_status(resource).should be_instance_of(Puppet::Resource::Status)
end
it "should add provided resource statuses to its report" do
@@ -72,15 +73,15 @@ describe Puppet::Transaction do
describe "when initializing" do
it "should create an event manager" do
- @transaction = Puppet::Transaction.new(Puppet::Resource::Catalog.new, nil, nil)
- @transaction.event_manager.should be_instance_of(Puppet::Transaction::EventManager)
- @transaction.event_manager.transaction.should equal(@transaction)
+ transaction = Puppet::Transaction.new(Puppet::Resource::Catalog.new, nil, nil)
+ transaction.event_manager.should be_instance_of(Puppet::Transaction::EventManager)
+ transaction.event_manager.transaction.should equal(transaction)
end
it "should create a resource harness" do
- @transaction = Puppet::Transaction.new(Puppet::Resource::Catalog.new, nil, nil)
- @transaction.resource_harness.should be_instance_of(Puppet::Transaction::ResourceHarness)
- @transaction.resource_harness.transaction.should equal(@transaction)
+ transaction = Puppet::Transaction.new(Puppet::Resource::Catalog.new, nil, nil)
+ transaction.resource_harness.should be_instance_of(Puppet::Transaction::ResourceHarness)
+ transaction.resource_harness.transaction.should equal(transaction)
end
it "should set retrieval time on the report" do
@@ -95,29 +96,25 @@ describe Puppet::Transaction do
end
describe "when evaluating a resource" do
- before do
- @catalog = Puppet::Resource::Catalog.new
- @resource = Puppet::Type.type(:file).new :path => @basepath
- @catalog.add_resource(@resource)
-
- @transaction = Puppet::Transaction.new(@catalog, nil, Puppet::Graph::RandomPrioritizer.new)
- @transaction.stubs(:skip?).returns false
- end
+ let(:resource) { Puppet::Type.type(:file).new :path => @basepath }
it "should process events" do
- @transaction.event_manager.expects(:process_events).with(@resource)
+ transaction = transaction_with_resource(resource)
- @transaction.evaluate
+ transaction.expects(:skip?).with(resource).returns false
+ transaction.event_manager.expects(:process_events).with(resource)
+
+ transaction.evaluate
end
describe "and the resource should be skipped" do
- before do
- @transaction.expects(:skip?).with(@resource).returns true
- end
-
it "should mark the resource's status as skipped" do
- @transaction.evaluate
- @transaction.resource_status(@resource).should be_skipped
+ transaction = transaction_with_resource(resource)
+
+ transaction.expects(:skip?).with(resource).returns true
+
+ transaction.evaluate
+ transaction.resource_status(resource).should be_skipped
end
end
end
@@ -288,6 +285,9 @@ describe Puppet::Transaction do
before :each do
catalog.add_resource generator
generator.stubs(:generate).returns generated
+ # avoid crude failures because of nil resources that result
+ # from implicit containment and lacking containers
+ catalog.stubs(:container_of).returns generator
end
it "should call 'generate' on all created resources" do
@@ -313,6 +313,31 @@ describe Puppet::Transaction do
end
end
+ describe "when performing pre-run checks" do
+ let(:resource) { Puppet::Type.type(:notify).new(:title => "spec") }
+ let(:transaction) { transaction_with_resource(resource) }
+ let(:spec_exception) { 'spec-exception' }
+
+ it "should invoke each resource's hook and apply the catalog after no failures" do
+ resource.expects(:pre_run_check)
+
+ transaction.evaluate
+ end
+
+ it "should abort the transaction on failure" do
+ resource.expects(:pre_run_check).raises(Puppet::Error, spec_exception)
+
+ expect { transaction.evaluate }.to raise_error(Puppet::Error, /Some pre-run checks failed/)
+ end
+
+ it "should log the resource-specific exception" do
+ resource.expects(:pre_run_check).raises(Puppet::Error, spec_exception)
+ resource.expects(:log_exception).with(responds_with(:message, spec_exception))
+
+ expect { transaction.evaluate }.to raise_error(Puppet::Error)
+ end
+ end
+
describe "when skipping a resource" do
before :each do
@resource = Puppet::Type.type(:notify).new :name => "foo"
@@ -478,44 +503,70 @@ describe Puppet::Transaction do
end
describe "during teardown" do
+ let(:catalog) { Puppet::Resource::Catalog.new }
+ let(:transaction) do
+ Puppet::Transaction.new(catalog, nil, Puppet::Graph::RandomPrioritizer.new)
+ end
+
+ let(:teardown_type) do
+ Puppet::Type.newtype(:teardown_test) do
+ newparam(:name) {}
+ end
+ end
+
before :each do
- @catalog = Puppet::Resource::Catalog.new
- @transaction = Puppet::Transaction.new(@catalog, nil, Puppet::Graph::RandomPrioritizer.new)
+ teardown_type.provide(:teardown_provider) do
+ class << self
+ attr_reader :result
+
+ def post_resource_eval
+ @result = 'passed'
+ end
+ end
+ end
end
it "should call ::post_resource_eval on provider classes that support it" do
- @resource = Puppet::Type.type(:notify).new :title => "foo"
- @catalog.add_resource @resource
+ resource = teardown_type.new(:title => "foo", :provider => :teardown_provider)
- # 'expects' will cause 'respond_to?(:post_resource_eval)' to return true
- @resource.provider.class.expects(:post_resource_eval)
- @transaction.evaluate
+ transaction = transaction_with_resource(resource)
+ transaction.evaluate
+
+ expect(resource.provider.class.result).to eq('passed')
end
it "should call ::post_resource_eval even if other providers' ::post_resource_eval fails" do
- @resource3 = Puppet::Type.type(:user).new :title => "bloo"
- @resource3.provider.class.stubs(:post_resource_eval).raises
- @resource4 = Puppet::Type.type(:notify).new :title => "blob"
- @resource4.provider.class.stubs(:post_resource_eval).raises
- @catalog.add_resource @resource3
- @catalog.add_resource @resource4
-
- # ruby's Set does not guarantee ordering, so both resource3 and resource4
- # need to expect post_resource_eval, rather than just the 'first' one.
- @resource3.provider.class.expects(:post_resource_eval)
- @resource4.provider.class.expects(:post_resource_eval)
+ teardown_type.provide(:always_fails) do
+ class << self
+ attr_reader :result
+
+ def post_resource_eval
+ @result = 'failed'
+ raise Puppet::Error, "This provider always fails"
+ end
+ end
+ end
- @transaction.evaluate
+ good_resource = teardown_type.new(:title => "bloo", :provider => :teardown_provider)
+ bad_resource = teardown_type.new(:title => "blob", :provider => :always_fails)
+
+ catalog.add_resource(bad_resource)
+ catalog.add_resource(good_resource)
+
+ transaction.evaluate
+
+ expect(good_resource.provider.class.result).to eq('passed')
+ expect(bad_resource.provider.class.result).to eq('failed')
end
it "should call ::post_resource_eval even if one of the resources fails" do
- @resource3 = Puppet::Type.type(:notify).new :title => "bloo"
- @resource3.stubs(:retrieve_resource).raises
- @catalog.add_resource @resource3
+ resource = teardown_type.new(:title => "foo", :provider => :teardown_provider)
+ resource.stubs(:retrieve_resource).raises
+ catalog.add_resource resource
- @resource3.provider.class.expects(:post_resource_eval)
+ resource.provider.class.expects(:post_resource_eval)
- @transaction.evaluate
+ transaction.evaluate
end
end
diff --git a/spec/unit/type/cron_spec.rb b/spec/unit/type/cron_spec.rb
index b4a853173..82f646290 100755
--- a/spec/unit/type/cron_spec.rb
+++ b/spec/unit/type/cron_spec.rb
@@ -452,7 +452,7 @@ describe Puppet::Type.type(:cron), :unless => Puppet.features.microsoft_windows?
describe "special" do
%w(reboot yearly annually monthly weekly daily midnight hourly).each do |value|
it "should support the value '#{value}'" do
- expect { described_class.new(:name => 'foo', :special => value ) }.to_not raise_error(Puppet::Error, /cannot specify both a special schedule and a value/)
+ expect { described_class.new(:name => 'foo', :special => value ) }.to_not raise_error
end
end
@@ -462,7 +462,7 @@ describe Puppet::Type.type(:cron), :unless => Puppet.features.microsoft_windows?
it "should accept the value '#{value}' for special" do
expect {
described_class.new(:name => 'foo', :minute => :absent, :special => value )
- }.to_not raise_error(Puppet::Error, /cannot specify both a special schedule and a value/)
+ }.to_not raise_error
end
}
end
@@ -477,7 +477,7 @@ describe Puppet::Type.type(:cron), :unless => Puppet.features.microsoft_windows?
it "should accept the 'absent' value for special" do
expect {
described_class.new(:name => 'foo', :minute => "1", :special => :absent )
- }.to_not raise_error(Puppet::Error, /cannot specify both a special schedule and a value/)
+ }.to_not raise_error
end
end
end
diff --git a/spec/unit/type/exec_spec.rb b/spec/unit/type/exec_spec.rb
index 6d780b3ff..ec9847e91 100755
--- a/spec/unit/type/exec_spec.rb
+++ b/spec/unit/type/exec_spec.rb
@@ -204,6 +204,15 @@ describe Puppet::Type.type(:exec) do
}.to raise_error Puppet::Error, /Parameter user failed/
end
+ it "accepts the current user" do
+ Puppet.features.stubs(:root?).returns(false)
+ Etc.stubs(:getpwuid).returns(Struct::Passwd.new('input'))
+
+ type = Puppet::Type.type(:exec).new(:name => '/bin/true whatever', :user => 'input')
+
+ expect(type[:user]).to eq('input')
+ end
+
['one', 2, 'root', 4294967295, 4294967296].each do |value|
it "should accept '#{value}' as user if we are root" do
Puppet.features.stubs(:root?).returns(true)
diff --git a/spec/unit/type/file/content_spec.rb b/spec/unit/type/file/content_spec.rb
index 33e995416..bf438d680 100755
--- a/spec/unit/type/file/content_spec.rb
+++ b/spec/unit/type/file/content_spec.rb
@@ -332,7 +332,7 @@ describe Puppet::Type.type(:file).attrclass(:content), :uses_checksums => true d
end
describe "from local source" do
- let(:source_content) { "source file content\r\n"*10000 }
+ let(:source_content) { "source file content\r\n"*10 }
before(:each) do
sourcename = tmpfile('source')
resource[:backup] = false
@@ -362,104 +362,87 @@ describe Puppet::Type.type(:file).attrclass(:content), :uses_checksums => true d
end
end
- describe "from an explicit fileserver" do
- let(:source_content) { "source file content\n"*10000 }
- let(:response) { stub_everything 'response' }
+ describe 'from remote source' do
+ let(:source_content) { "source file content\n"*10 }
let(:source) { resource.newattr(:source) }
+ let(:response) { stub_everything('response') }
+ let(:conn) { mock('connection') }
before(:each) do
resource[:backup] = false
- response.stubs(:read_body).multiple_yields(*(["source file content\n"]*10000))
-
- conn = mock('connection')
- conn.stubs(:request_get).yields response
-
- Puppet::Network::HttpPool.expects(:http_instance).with('somehostname',any_parameters).returns(conn).at_least_once
-
# This needs to be invoked to properly initialize the content property,
# or attempting to write a file will fail.
resource.newattr(:content)
- source.stubs(:metadata).returns stub_everything('metadata', :source => "puppet://somehostname/test/foo", :ftype => 'file')
+ response.stubs(:read_body).multiple_yields(*source_content.lines)
+ conn.stubs(:request_get).yields(response)
end
- describe "and the request was successful" do
- before { response.stubs(:code).returns '200' }
-
- it "should write the contents to the file" do
- resource.write(source)
- Puppet::FileSystem.binread(filename).should == source_content
- end
+ it 'should use an explicit fileserver if source starts with puppet://' do
+ response.stubs(:code).returns('200')
+ source.stubs(:metadata).returns stub_everything('metadata', :source => 'puppet://somehostname/test/foo', :ftype => 'file')
+ Puppet::Network::HttpPool.expects(:http_instance).with('somehostname', anything).returns(conn)
- with_digest_algorithms do
- it "should return the checksum computed" do
- File.open(filename, 'w') do |file|
- resource[:checksum] = digest_algorithm
- content.write(file).should == "{#{digest_algorithm}}#{digest(source_content)}"
- end
- end
- end
+ resource.write(source)
end
- it "should not write anything if source is not found" do
- response.stubs(:code).returns("404")
- expect { resource.write(source) }.to raise_error(Net::HTTPError, /404/)
- File.read(filename).should == "initial file content"
- end
+ it 'should use the default fileserver if source starts with puppet:///' do
+ response.stubs(:code).returns('200')
+ source.stubs(:metadata).returns stub_everything('metadata', :source => 'puppet:///test/foo', :ftype => 'file')
+ Puppet::Network::HttpPool.expects(:http_instance).with(Puppet.settings[:server], anything).returns(conn)
- it "should raise an HTTP error in case of server error" do
- response.stubs(:code).returns("500")
- expect { content.write(fh) }.to raise_error(Net::HTTPError, /500/)
+ resource.write(source)
end
- end
+ it 'should percent encode reserved characters' do
+ response.stubs(:code).returns('200')
+ Puppet::Network::HttpPool.stubs(:http_instance).returns(conn)
+ source.stubs(:metadata).returns stub_everything('metadata', :source => 'puppet:///test/foo bar', :ftype => 'file')
- describe "from remote source" do
- let(:source_content) { "source file content\n"*10000 }
- let(:response) { stub_everything 'response' }
- let(:source) { resource.newattr(:source) }
+ conn.unstub(:request_get)
+ conn.expects(:request_get).with('/none/file_content/test/foo%20bar', anything).yields(response)
- before(:each) do
- resource[:backup] = false
- response.stubs(:read_body).multiple_yields(*(["source file content\n"]*10000))
+ resource.write(source)
+ end
- conn = stub_everything 'connection'
- conn.stubs(:request_get).yields response
- Puppet::Network::HttpPool.stubs(:http_instance).returns conn
+ describe 'when handling file_content responses' do
+ before(:each) do
+ Puppet::Network::HttpPool.stubs(:http_instance).returns(conn)
+ source.stubs(:metadata).returns stub_everything('metadata', :source => 'puppet:///test/foo', :ftype => 'file')
+ end
- # This needs to be invoked to properly initialize the content property,
- # or attempting to write a file will fail.
- resource.newattr(:content)
- source.stubs(:metadata).returns stub_everything('metadata', :source => "puppet://somehostname/test/foo", :ftype => 'file')
- end
+ it 'should not write anything if source is not found' do
+ response.stubs(:code).returns('404')
+
+ expect { resource.write(source) }.to raise_error(Net::HTTPError, /404/)
+ expect(File.read(filename)).to eq('initial file content')
+ end
- describe "and the request was successful" do
- before { response.stubs(:code).returns '200' }
+ it 'should raise an HTTP error in case of server error' do
+ response.stubs(:code).returns('500')
- it "should write the contents to the file" do
- resource.write(source)
- Puppet::FileSystem.binread(filename).should == source_content
+ expect { resource.write(source) }.to raise_error(Net::HTTPError, /500/)
end
- with_digest_algorithms do
- it "should return the checksum computed" do
- File.open(filename, 'w') do |file|
- resource[:checksum] = digest_algorithm
- content.write(file).should == "{#{digest_algorithm}}#{digest(source_content)}"
+ context 'and the request was successful' do
+ before(:each) { response.stubs(:code).returns '200' }
+
+ it 'should write the contents to the file' do
+ resource.write(source)
+ expect(Puppet::FileSystem.binread(filename)).to eq(source_content)
+ end
+
+ with_digest_algorithms do
+ it 'should return the checksum computed' do
+ File.open(filename, 'w') do |file|
+ resource[:checksum] = digest_algorithm
+ expect(content.write(file)).to eq("{#{digest_algorithm}}#{digest(source_content)}")
+ end
end
end
- end
- end
- it "should not write anything if source is not found" do
- response.stubs(:code).returns("404")
- expect {resource.write(source)}.to raise_error(Net::HTTPError, /404/)
- File.read(filename).should == "initial file content"
- end
+ end
- it "should raise an HTTP error in case of server error" do
- response.stubs(:code).returns("500")
- expect { content.write(fh) }.to raise_error(Net::HTTPError, /500/)
end
end
diff --git a/spec/unit/type/file/mode_spec.rb b/spec/unit/type/file/mode_spec.rb
index 9936ebdbc..82bd5a09f 100755
--- a/spec/unit/type/file/mode_spec.rb
+++ b/spec/unit/type/file/mode_spec.rb
@@ -6,7 +6,7 @@ describe Puppet::Type.type(:file).attrclass(:mode) do
include PuppetSpec::Files
let(:path) { tmpfile('mode_spec') }
- let(:resource) { Puppet::Type.type(:file).new :path => path, :mode => 0644 }
+ let(:resource) { Puppet::Type.type(:file).new :path => path, :mode => '0644' }
let(:mode) { resource.property(:mode) }
describe "#validate" do
@@ -192,4 +192,29 @@ describe Puppet::Type.type(:file).attrclass(:mode) do
(stat.mode & 0777).to_s(8).should == "644"
end
end
+
+ describe '#sync with a symbolic mode of +X for a file' do
+ let(:resource_sym) { Puppet::Type.type(:file).new :path => path, :mode => 'g+wX' }
+ let(:mode_sym) { resource_sym.property(:mode) }
+
+ before { FileUtils.touch(path) }
+
+ it 'does not change executable bit if no executable bit is set' do
+ Puppet::FileSystem.chmod(0644, path)
+
+ mode_sym.sync
+
+ stat = Puppet::FileSystem.stat(path)
+ (stat.mode & 0777).to_s(8).should == '664'
+ end
+
+ it 'does change executable bit if an executable bit is set' do
+ Puppet::FileSystem.chmod(0744, path)
+
+ mode_sym.sync
+
+ stat = Puppet::FileSystem.stat(path)
+ (stat.mode & 0777).to_s(8).should == '774'
+ end
+ end
end
diff --git a/spec/unit/type/file/source_spec.rb b/spec/unit/type/file/source_spec.rb
index b6e97cd7a..ff192a5f4 100755
--- a/spec/unit/type/file/source_spec.rb
+++ b/spec/unit/type/file/source_spec.rb
@@ -176,7 +176,7 @@ describe Puppet::Type.type(:file).attrclass(:source) do
end
describe "when copying the source values" do
- before do
+ before :each do
@resource = Puppet::Type.type(:file).new :path => @foobar
@source = source.new(:resource => @resource)
@@ -186,6 +186,28 @@ describe Puppet::Type.type(:file).attrclass(:source) do
Puppet.features.stubs(:root?).returns true
end
+ it "should not issue a deprecation warning if the source mode value is a Numeric" do
+ @metadata.stubs(:mode).returns 0173
+ if Puppet::Util::Platform.windows?
+ Puppet.expects(:deprecation_warning).with(regexp_matches(/Copying owner\/mode\/group from the source file on Windows is deprecated/)).at_least_once
+ else
+ Puppet.expects(:deprecation_warning).never
+ end
+
+ @source.copy_source_values
+ end
+
+ it "should not issue a deprecation warning if the source mode value is a String" do
+ @metadata.stubs(:mode).returns "173"
+ if Puppet::Util::Platform.windows?
+ Puppet.expects(:deprecation_warning).with(regexp_matches(/Copying owner\/mode\/group from the source file on Windows is deprecated/)).at_least_once
+ else
+ Puppet.expects(:deprecation_warning).never
+ end
+
+ @source.copy_source_values
+ end
+
it "should fail if there is no metadata" do
@source.stubs(:metadata).returns nil
@source.expects(:devfail).raises ArgumentError
@@ -409,7 +431,7 @@ describe Puppet::Type.type(:file).attrclass(:source) do
@source.stubs(:local?).returns false
Puppet.expects(:deprecation_warning).with(deprecation_message).at_least_once
@resource[:group] = 2
- @resource[:mode] = 3
+ @resource[:mode] = "0003"
@source.copy_source_values
end
@@ -418,7 +440,7 @@ describe Puppet::Type.type(:file).attrclass(:source) do
@source.stubs(:local?).returns false
Puppet.expects(:deprecation_warning).with(deprecation_message).at_least_once
@resource[:owner] = 1
- @resource[:mode] = 3
+ @resource[:mode] = "0003"
@source.copy_source_values
end
@@ -437,7 +459,7 @@ describe Puppet::Type.type(:file).attrclass(:source) do
Puppet.expects(:deprecation_warning).with(deprecation_message).never
@resource[:owner] = 1
@resource[:group] = 2
- @resource[:mode] = 3
+ @resource[:mode] = "0003"
@source.copy_source_values
end
diff --git a/spec/unit/type/file_spec.rb b/spec/unit/type/file_spec.rb
index 00b027ed7..7a4d02553 100755
--- a/spec/unit/type/file_spec.rb
+++ b/spec/unit/type/file_spec.rb
@@ -418,7 +418,7 @@ describe Puppet::Type.type(:file) do
it "should not copy values to the child which were set by the source" do
source = File.expand_path(__FILE__)
file[:source] = source
- metadata = stub 'metadata', :owner => "root", :group => "root", :mode => 0755, :ftype => "file", :checksum => "{md5}whatever", :source => source
+ metadata = stub 'metadata', :owner => "root", :group => "root", :mode => '0755', :ftype => "file", :checksum => "{md5}whatever", :source => source
file.parameter(:source).stubs(:metadata).returns metadata
file.parameter(:source).copy_source_values
@@ -1357,13 +1357,13 @@ describe Puppet::Type.type(:file) do
target = described_class.new(
:ensure => :file, :path => @target,
:catalog => catalog, :content => 'yayness',
- :mode => 0644)
+ :mode => '0644')
catalog.add_resource target
@link_resource = described_class.new(
:ensure => :link, :path => @link,
:target => @target, :catalog => catalog,
- :mode => 0755)
+ :mode => '0755')
catalog.add_resource @link_resource
# to prevent the catalog from trying to write state.yaml
diff --git a/spec/unit/type/nagios_spec.rb b/spec/unit/type/nagios_spec.rb
index bc96c26d4..4bd1271c2 100755
--- a/spec/unit/type/nagios_spec.rb
+++ b/spec/unit/type/nagios_spec.rb
@@ -125,7 +125,7 @@ EOL
parser = Nagios::Parser.new
expect {
results = parser.parse(ESCAPED_SEMICOLON)
- }.to_not raise_error Nagios::Parser::SyntaxError
+ }.to_not raise_error
end
it "should ignore it if it is a comment" do
@@ -147,7 +147,7 @@ EOL
parser = Nagios::Parser.new
expect {
results = parser.parse(POUND_SIGN_HASH_SYMBOL_NOT_IN_FIRST_COLUMN)
- }.to_not raise_error Nagios::Parser::SyntaxError
+ }.to_not raise_error
end
@@ -170,7 +170,7 @@ EOL
parser = Nagios::Parser.new
expect {
results = parser.parse(ANOTHER_ESCAPED_SEMICOLON)
- }.to_not raise_error Nagios::Parser::SyntaxError
+ }.to_not raise_error
end
it "should parse correctly" do
@@ -217,6 +217,15 @@ describe "Nagios generator" do
results = parser.parse(nagios_type.to_s)
results[0].command_line.should eql(param)
end
+
+ it "should accept FixNum params and convert to string" do
+ param = 1
+ nagios_type = Nagios::Base.create(:serviceescalation)
+ nagios_type.first_notification = param
+ parser = Nagios::Parser.new
+ results = parser.parse(nagios_type.to_s)
+ results[0].first_notification.should eql(param.to_s)
+ end
end
describe "Nagios resource types" do
diff --git a/spec/unit/type/resources_spec.rb b/spec/unit/type/resources_spec.rb
index f08afd7ae..e985b9752 100755
--- a/spec/unit/type/resources_spec.rb
+++ b/spec/unit/type/resources_spec.rb
@@ -5,6 +5,11 @@ resources = Puppet::Type.type(:resources)
# There are still plenty of tests to port over from test/.
describe resources do
+
+ before :each do
+ described_class.reset_system_users_max_uid!
+ end
+
describe "when initializing" do
it "should fail if the specified resource type does not exist" do
Puppet::Type.stubs(:type).with { |x| x.to_s.downcase == "resources"}.returns resources
@@ -47,7 +52,7 @@ describe resources do
it "can be set to true for a resource type that has instances and can accept ensure" do
instance.resource_type.stubs(:respond_to?).returns true
instance.resource_type.stubs(:validproperty?).returns true
- expect { instance[:purge] = 'yes' }.not_to raise_error Puppet::Error
+ expect { instance[:purge] = 'yes' }.to_not raise_error
end
end
@@ -56,6 +61,7 @@ describe resources do
before do
@res = Puppet::Type.type(:resources).new :name => :user, :purge => true, :unless_system_user => true
@res.catalog = Puppet::Resource::Catalog.new
+ Puppet::FileSystem.stubs(:exist?).with('/etc/login.defs').returns false
end
it "should never purge hardcoded system users" do
@@ -71,60 +77,89 @@ describe resources do
@res.user_check(user).should be_false
end
- it "should purge manual users if unless_system_user => true" do
- user_hash = {:name => 'system_user', :uid => 525, :system => true}
+ it "should purge non-system users if unless_system_user => true" do
+ user_hash = {:name => 'system_user', :uid => described_class.system_users_max_uid + 1, :system => true}
user = Puppet::Type.type(:user).new(user_hash)
user.stubs(:retrieve_resource).returns Puppet::Resource.new("user", user_hash[:name], :parameters => user_hash)
@res.user_check(user).should be_true
end
- it "should purge system users over 500 if unless_system_user => 600" do
+ it "should not purge system users under 600 if unless_system_user => 600" do
res = Puppet::Type.type(:resources).new :name => :user, :purge => true, :unless_system_user => 600
res.catalog = Puppet::Resource::Catalog.new
- user_hash = {:name => 'system_user', :uid => 525, :system => true}
+ user_hash = {:name => 'system_user', :uid => 500, :system => true}
user = Puppet::Type.type(:user).new(user_hash)
user.stubs(:retrieve_resource).returns Puppet::Resource.new("user", user_hash[:name], :parameters => user_hash)
res.user_check(user).should be_false
end
end
- describe "with unless_uid" do
- describe "with a uid range" do
- before do
- @res = Puppet::Type.type(:resources).new :name => :user, :purge => true, :unless_uid => 10_000..20_000
+ %w(FreeBSD OpenBSD).each do |os|
+ describe "on #{os}" do
+ before :each do
+ Facter.stubs(:value).with(:kernel).returns(os)
+ Facter.stubs(:value).with(:operatingsystem).returns(os)
+ Facter.stubs(:value).with(:osfamily).returns(os)
+ Puppet::FileSystem.stubs(:exist?).with('/etc/login.defs').returns false
+ @res = Puppet::Type.type(:resources).new :name => :user, :purge => true, :unless_system_user => true
@res.catalog = Puppet::Resource::Catalog.new
end
- it "should purge uids that are not in a specified range" do
- user_hash = {:name => 'special_user', :uid => 25_000}
+ it "should not purge system users under 1000" do
+ user_hash = {:name => 'system_user', :uid => 999}
user = Puppet::Type.type(:user).new(user_hash)
user.stubs(:retrieve_resource).returns Puppet::Resource.new("user", user_hash[:name], :parameters => user_hash)
- @res.user_check(user).should be_true
+ @res.user_check(user).should be_false
end
- it "should not purge uids that are in a specified range" do
- user_hash = {:name => 'special_user', :uid => 15_000}
+ it "should purge users over 999" do
+ user_hash = {:name => 'system_user', :uid => 1000}
user = Puppet::Type.type(:user).new(user_hash)
user.stubs(:retrieve_resource).returns Puppet::Resource.new("user", user_hash[:name], :parameters => user_hash)
- @res.user_check(user).should be_false
+ @res.user_check(user).should be_true
end
end
+ end
+
+ describe 'with login.defs present' do
+ before :each do
+ Puppet::FileSystem.expects(:exist?).with('/etc/login.defs').returns true
+ Puppet::FileSystem.expects(:each_line).with('/etc/login.defs').yields(' UID_MIN 1234 # UID_MIN comment ')
+ @res = Puppet::Type.type(:resources).new :name => :user, :purge => true, :unless_system_user => true
+ @res.catalog = Puppet::Resource::Catalog.new
+ end
+
+ it 'should not purge a system user' do
+ user_hash = {:name => 'system_user', :uid => 1233}
+ user = Puppet::Type.type(:user).new(user_hash)
+ user.stubs(:retrieve_resource).returns Puppet::Resource.new("user", user_hash[:name], :parameters => user_hash)
+ @res.user_check(user).should be_false
+ end
+
+ it 'should purge a non-system user' do
+ user_hash = {:name => 'system_user', :uid => 1234}
+ user = Puppet::Type.type(:user).new(user_hash)
+ user.stubs(:retrieve_resource).returns Puppet::Resource.new("user", user_hash[:name], :parameters => user_hash)
+ @res.user_check(user).should be_true
+ end
+ end
- describe "with a uid range array" do
+ describe "with unless_uid" do
+ describe "with a uid array" do
before do
- @res = Puppet::Type.type(:resources).new :name => :user, :purge => true, :unless_uid => [10_000..15_000, 15_000..20_000]
+ @res = Puppet::Type.type(:resources).new :name => :user, :purge => true, :unless_uid => [15_000, 15_001, 15_002]
@res.catalog = Puppet::Resource::Catalog.new
end
- it "should purge uids that are not in a specified range array" do
+ it "should purge uids that are not in a specified array" do
user_hash = {:name => 'special_user', :uid => 25_000}
user = Puppet::Type.type(:user).new(user_hash)
user.stubs(:retrieve_resource).returns Puppet::Resource.new("user", user_hash[:name], :parameters => user_hash)
@res.user_check(user).should be_true
end
- it "should not purge uids that are in a specified range array" do
- user_hash = {:name => 'special_user', :uid => 15_000}
+ it "should not purge uids that are in a specified array" do
+ user_hash = {:name => 'special_user', :uid => 15000}
user = Puppet::Type.type(:user).new(user_hash)
user.stubs(:retrieve_resource).returns Puppet::Resource.new("user", user_hash[:name], :parameters => user_hash)
@res.user_check(user).should be_false
@@ -132,31 +167,30 @@ describe resources do
end
- describe "with a uid array" do
+ describe "with a single integer uid" do
before do
- @res = Puppet::Type.type(:resources).new :name => :user, :purge => true, :unless_uid => [15_000, 15_001, 15_002]
+ @res = Puppet::Type.type(:resources).new :name => :user, :purge => true, :unless_uid => 15_000
@res.catalog = Puppet::Resource::Catalog.new
end
- it "should purge uids that are not in a specified array" do
+ it "should purge uids that are not specified" do
user_hash = {:name => 'special_user', :uid => 25_000}
user = Puppet::Type.type(:user).new(user_hash)
user.stubs(:retrieve_resource).returns Puppet::Resource.new("user", user_hash[:name], :parameters => user_hash)
@res.user_check(user).should be_true
end
- it "should not purge uids that are in a specified array" do
- user_hash = {:name => 'special_user', :uid => 15000}
+ it "should not purge uids that are specified" do
+ user_hash = {:name => 'special_user', :uid => 15_000}
user = Puppet::Type.type(:user).new(user_hash)
user.stubs(:retrieve_resource).returns Puppet::Resource.new("user", user_hash[:name], :parameters => user_hash)
@res.user_check(user).should be_false
end
-
end
- describe "with a single uid" do
+ describe "with a single string uid" do
before do
- @res = Puppet::Type.type(:resources).new :name => :user, :purge => true, :unless_uid => 15_000
+ @res = Puppet::Type.type(:resources).new :name => :user, :purge => true, :unless_uid => '15000'
@res.catalog = Puppet::Resource::Catalog.new
end
@@ -177,7 +211,7 @@ describe resources do
describe "with a mixed uid array" do
before do
- @res = Puppet::Type.type(:resources).new :name => :user, :purge => true, :unless_uid => [10_000..15_000, 16_666]
+ @res = Puppet::Type.type(:resources).new :name => :user, :purge => true, :unless_uid => ['15000', 16_666]
@res.catalog = Puppet::Resource::Catalog.new
end
@@ -202,7 +236,7 @@ describe resources do
@res.user_check(user).should be_true
end
end
-
+
end
end
diff --git a/spec/unit/type/user_spec.rb b/spec/unit/type/user_spec.rb
index d9468357b..f5a351752 100755
--- a/spec/unit/type/user_spec.rb
+++ b/spec/unit/type/user_spec.rb
@@ -467,40 +467,47 @@ describe Puppet::Type.type(:user) do
res.catalog = Puppet::Resource::Catalog.new
res
end
- it "should not just return from eval_generate" do
+ it "should not just return from generate" do
subject.expects :find_unmanaged_keys
- subject.eval_generate
+ subject.generate
end
it "should check each keyfile for readability" do
paths.each do |path|
File.expects(:readable?).with(path)
end
- subject.eval_generate
+ subject.generate
end
end
describe "generated keys" do
subject do
- res = described_class.new(:name => "test", :purge_ssh_keys => purge_param)
+ res = described_class.new(:name => "test_user_name", :purge_ssh_keys => purge_param)
res.catalog = Puppet::Resource::Catalog.new
res
end
context "when purging is disabled" do
let(:purge_param) { false }
- its(:eval_generate) { should be_empty }
+ its(:generate) { should be_empty }
end
context "when purging is enabled" do
let(:purge_param) { my_fixture('authorized_keys') }
- let(:resources) { subject.eval_generate }
+ let(:resources) { subject.generate }
it "should contain a resource for each key" do
names = resources.collect { |res| res.name }
- names.should include("keyname1")
+ names.should include("key1 name")
names.should include("keyname2")
end
it "should not include keys in comment lines" do
names = resources.collect { |res| res.name }
names.should_not include("keyname3")
end
+ it "should each have a value for the user property" do
+ resources.map { |res|
+ res[:user]
+ }.reject { |user_name|
+ user_name == "test_user_name"
+ }.should be_empty
+ end
end
end
end
diff --git a/spec/unit/type/yumrepo_spec.rb b/spec/unit/type/yumrepo_spec.rb
index b97c60666..2246b7274 100644..100755
--- a/spec/unit/type/yumrepo_spec.rb
+++ b/spec/unit/type/yumrepo_spec.rb
@@ -10,6 +10,37 @@ shared_examples_for "a yumrepo parameter that can be absent" do |param|
end
end
+shared_examples_for "a yumrepo parameter that expects a natural value" do |param|
+ it "accepts a valid positive integer" do
+ instance = described_class.new(:name => 'puppetlabs', param => '12')
+ expect(instance[param]).to eq '12'
+ end
+ it "rejects invalid negative integer" do
+ expect {
+ described_class.new(
+ :name => 'puppetlabs',
+ param => '-12'
+ )
+ }.to raise_error(Puppet::ResourceError, /Parameter #{param} failed/)
+ end
+ it "rejects invalid non-integer" do
+ expect {
+ described_class.new(
+ :name => 'puppetlabs',
+ param => 'I\'m a six'
+ )
+ }.to raise_error(Puppet::ResourceError, /Parameter #{param} failed/)
+ end
+ it "rejects invalid string with integers inside" do
+ expect {
+ described_class.new(
+ :name => 'puppetlabs',
+ param => 'I\'m a 6'
+ )
+ }.to raise_error(Puppet::ResourceError, /Parameter #{param} failed/)
+ end
+end
+
shared_examples_for "a yumrepo parameter that expects a boolean parameter" do |param|
valid_values = %w[True False 0 1 No Yes]
@@ -22,6 +53,14 @@ shared_examples_for "a yumrepo parameter that expects a boolean parameter" do |p
instance = described_class.new(:name => 'puppetlabs', param => value.downcase)
expect(instance[param]).to eq value.downcase
end
+ it "fails on valid value #{value} contained in another value" do
+ expect {
+ described_class.new(
+ :name => 'puppetlabs',
+ param => "bla#{value}bla"
+ )
+ }.to raise_error(Puppet::ResourceError, /Parameter #{param} failed/)
+ end
end
it "rejects invalid boolean values" do
@@ -76,6 +115,26 @@ shared_examples_for "a yumrepo parameter that accepts multiple URLs" do |param|
end
end
+shared_examples_for "a yumrepo parameter that accepts kMG units" do |param|
+ %w[k M G].each do |unit|
+ it "can accept an integer with #{unit} units" do
+ described_class.new(
+ :name => 'puppetlabs',
+ param => "123#{unit}"
+ )
+ end
+ end
+
+ it "fails if wrong unit passed" do
+ expect {
+ described_class.new(
+ :name => 'puppetlabs',
+ param => '123J'
+ )
+ }.to raise_error(Puppet::ResourceError, /Parameter #{param} failed/)
+ end
+end
+
describe Puppet::Type.type(:yumrepo) do
it "has :name as its namevar" do
expect(described_class.key_attributes).to eq [:name]
@@ -154,6 +213,14 @@ describe Puppet::Type.type(:yumrepo) do
it "accepts a value of #{value}" do
described_class.new(:name => "puppetlabs", :failovermethod => value)
end
+ it "fails on valid value #{value} contained in another value" do
+ expect {
+ described_class.new(
+ :name => 'puppetlabs',
+ :failovermethod => "bla#{value}bla"
+ )
+ }.to raise_error(Puppet::ResourceError, /Parameter failovermethod failed/)
+ end
end
it "raises an error if an invalid value is given" do
@@ -175,6 +242,14 @@ describe Puppet::Type.type(:yumrepo) do
it "accepts a valid value of #{value}" do
described_class.new(:name => 'puppetlabs', :http_caching => value)
end
+ it "fails on valid value #{value} contained in another value" do
+ expect {
+ described_class.new(
+ :name => 'puppetlabs',
+ :http_caching => "bla#{value}bla"
+ )
+ }.to raise_error(Puppet::ResourceError, /Parameter http_caching failed/)
+ end
end
it "rejects invalid values" do
@@ -188,10 +263,25 @@ describe Puppet::Type.type(:yumrepo) do
describe "timeout" do
it_behaves_like "a yumrepo parameter that can be absent", :timeout
+ it_behaves_like "a yumrepo parameter that expects a natural value", :timeout
end
describe "metadata_expire" do
it_behaves_like "a yumrepo parameter that can be absent", :metadata_expire
+ it_behaves_like "a yumrepo parameter that expects a natural value", :metadata_expire
+
+ it "accepts dhm units" do
+ %W[d h m].each do |unit|
+ described_class.new(
+ :name => 'puppetlabs',
+ :metadata_expire => "123#{unit}"
+ )
+ end
+ end
+
+ it "accepts never as value" do
+ described_class.new(:name => 'puppetlabs', :metadata_expire => 'never')
+ end
end
describe "protect" do
@@ -205,6 +295,12 @@ describe Puppet::Type.type(:yumrepo) do
describe "proxy" do
it_behaves_like "a yumrepo parameter that can be absent", :proxy
+ it "accepts _none_" do
+ described_class.new(
+ :name => 'puppetlabs',
+ :proxy => "_none_"
+ )
+ end
it_behaves_like "a yumrepo parameter that accepts a single URL", :proxy
end
@@ -247,5 +343,45 @@ describe Puppet::Type.type(:yumrepo) do
it_behaves_like "a yumrepo parameter that can be absent", :metalink
it_behaves_like "a yumrepo parameter that accepts a single URL", :metalink
end
+
+
+ describe "cost" do
+ it_behaves_like "a yumrepo parameter that can be absent", :cost
+ it_behaves_like "a yumrepo parameter that expects a natural value", :cost
+ end
+
+ describe "throttle" do
+ it_behaves_like "a yumrepo parameter that can be absent", :throttle
+ it_behaves_like "a yumrepo parameter that expects a natural value", :throttle
+ it_behaves_like "a yumrepo parameter that accepts kMG units", :throttle
+
+ it "accepts percentage as unit" do
+ described_class.new(
+ :name => 'puppetlabs',
+ :throttle => '123%'
+ )
+ end
+ end
+
+ describe "bandwidth" do
+ it_behaves_like "a yumrepo parameter that can be absent", :bandwidth
+ it_behaves_like "a yumrepo parameter that expects a natural value", :bandwidth
+ it_behaves_like "a yumrepo parameter that accepts kMG units", :bandwidth
+ end
+
+ describe "gpgcakey" do
+ it_behaves_like "a yumrepo parameter that can be absent", :gpgcakey
+ it_behaves_like "a yumrepo parameter that accepts a single URL", :gpgcakey
+ end
+
+ describe "retries" do
+ it_behaves_like "a yumrepo parameter that can be absent", :retries
+ it_behaves_like "a yumrepo parameter that expects a natural value", :retries
+ end
+
+ describe "mirrorlist_expire" do
+ it_behaves_like "a yumrepo parameter that can be absent", :mirrorlist_expire
+ it_behaves_like "a yumrepo parameter that expects a natural value", :mirrorlist_expire
+ end
end
end
diff --git a/spec/unit/type/zone_spec.rb b/spec/unit/type/zone_spec.rb
index e54902627..3497ae3a7 100755
--- a/spec/unit/type/zone_spec.rb
+++ b/spec/unit/type/zone_spec.rb
@@ -2,8 +2,11 @@
require 'spec_helper'
describe Puppet::Type.type(:zone) do
- let(:zone) { described_class.new(:name => 'dummy', :path => '/dummy', :provider => :solaris) }
+ let(:zone) { described_class.new(:name => 'dummy', :path => '/dummy', :provider => :solaris, :ip=>'if:1.2.3.4:2.3.4.5', :inherit=>'/', :dataset=>'tank') }
let(:provider) { zone.provider }
+ let(:ip) { zone.property(:ip) }
+ let(:inherit) { zone.property(:inherit) }
+ let(:dataset) { zone.property(:dataset) }
parameters = [:create_args, :install_args, :sysidcfg, :realhostname]
@@ -21,6 +24,46 @@ describe Puppet::Type.type(:zone) do
end
end
+ describe "when trying to set a property that is empty" do
+ it "should verify that property.insync? of nil or :absent is true" do
+ [inherit, ip, dataset].each do |prop|
+ prop.stubs(:should).returns []
+ end
+ [inherit, ip, dataset].each do |prop|
+ prop.insync?(nil).should be_true
+ end
+ [inherit, ip, dataset].each do |prop|
+ prop.insync?(:absent).should be_true
+ end
+ end
+ end
+ describe "when trying to set a property that is non empty" do
+ it "should verify that property.insync? of nil or :absent is false" do
+ [inherit, ip, dataset].each do |prop|
+ prop.stubs(:should).returns ['a','b']
+ end
+ [inherit, ip, dataset].each do |prop|
+ prop.insync?(nil).should be_false
+ end
+ [inherit, ip, dataset].each do |prop|
+ prop.insync?(:absent).should be_false
+ end
+ end
+ end
+ describe "when trying to set a property that is non empty" do
+ it "insync? should return true or false depending on the current value, and new value" do
+ [inherit, ip, dataset].each do |prop|
+ prop.stubs(:should).returns ['a','b']
+ end
+ [inherit, ip, dataset].each do |prop|
+ prop.insync?(['b', 'a']).should be_true
+ end
+ [inherit, ip, dataset].each do |prop|
+ prop.insync?(['a']).should be_false
+ end
+ end
+ end
+
it "should be valid when only :path is given" do
described_class.new(:name => "dummy", :path => '/dummy', :provider => :solaris)
end
diff --git a/spec/unit/type_spec.rb b/spec/unit/type_spec.rb
index 75a5a0768..8c250ee34 100755
--- a/spec/unit/type_spec.rb
+++ b/spec/unit/type_spec.rb
@@ -328,6 +328,26 @@ describe Puppet::Type, :unless => Puppet.features.microsoft_windows? do
provider.ancestors.should include(Puppet::Provider)
provider.should == @type.provider(:test_provider)
end
+
+ describe "with a parent class from another type" do
+ before :each do
+ @parent_type = Puppet::Type.newtype(:provider_parent_type) do
+ newparam(:name) { isnamevar }
+ end
+ @parent_provider = @parent_type.provide(:parent_provider)
+ end
+
+ it "should be created successfully" do
+ child_provider = @type.provide(:child_provider, :parent => @parent_provider)
+ child_provider.ancestors.should include(@parent_provider)
+ end
+
+ it "should be registered as a provider of the child type" do
+ child_provider = @type.provide(:child_provider, :parent => @parent_provider)
+ @type.providers.should include(:child_provider)
+ @parent_type.providers.should_not include(:child_provider)
+ end
+ end
end
describe "when choosing a default provider" do
diff --git a/spec/unit/util/colors_spec.rb b/spec/unit/util/colors_spec.rb
index 7407b628b..b0f791f82 100755
--- a/spec/unit/util/colors_spec.rb
+++ b/spec/unit/util/colors_spec.rb
@@ -67,17 +67,23 @@ describe Puppet::Util::Colors do
end
end
- describe "on Windows", :if => Puppet.features.microsoft_windows? do
- it "expects a trailing embedded NULL character in the wide string" do
- message = "hello"
+ context "on Windows in Ruby 1.x", :if => Puppet.features.microsoft_windows? && RUBY_VERSION =~ /^1./ do
+ it "should define WideConsole" do
+ expect(defined?(Puppet::Util::Colors::WideConsole)).to be_true
+ end
- console = Puppet::Util::Colors::WideConsole.new
- wstr, nchars = console.string_encode(message)
+ it "should define WideIO" do
+ expect(defined?(Puppet::Util::Colors::WideIO)).to be_true
+ end
+ end
- expect(nchars).to eq(message.length)
+ context "on Windows in Ruby 2.x", :if => Puppet.features.microsoft_windows? && RUBY_VERSION =~ /^2./ do
+ it "should not define WideConsole" do
+ expect(defined?(Puppet::Util::Colors::WideConsole)).to be_false
+ end
- expect(wstr.length).to eq(nchars + 1)
- expect(wstr[-1].ord).to be_zero
+ it "should not define WideIO" do
+ expect(defined?(Puppet::Util::Colors::WideIO)).to be_false
end
end
end
diff --git a/spec/unit/util/command_line_spec.rb b/spec/unit/util/command_line_spec.rb
index 6ba8077c2..9eb61b077 100755
--- a/spec/unit/util/command_line_spec.rb
+++ b/spec/unit/util/command_line_spec.rb
@@ -70,7 +70,7 @@ describe Puppet::Util::CommandLine do
it "should print the version and exit if #{arg} is given" do
expect do
described_class.new("puppet", [arg]).execute
- end.to have_printed(/^#{Puppet.version}$/)
+ end.to have_printed(/^#{Regexp.escape(Puppet.version)}$/)
end
end
end
@@ -93,35 +93,39 @@ describe Puppet::Util::CommandLine do
end
describe "and an external implementation cannot be found" do
+ before :each do
+ Puppet::Util::CommandLine::UnknownSubcommand.any_instance.stubs(:console_has_color?).returns false
+ end
+
it "should abort and show the usage message" do
- commandline = Puppet::Util::CommandLine.new("puppet", ['whatever', 'argument'])
Puppet::Util.expects(:which).with('puppet-whatever').returns(nil)
+ commandline = Puppet::Util::CommandLine.new("puppet", ['whatever', 'argument'])
commandline.expects(:exec).never
expect {
commandline.execute
- }.to have_printed(/Unknown Puppet subcommand 'whatever'/)
+ }.to have_printed(/Unknown Puppet subcommand 'whatever'/).and_exit_with(1)
end
it "should abort and show the help message" do
- commandline = Puppet::Util::CommandLine.new("puppet", ['whatever', 'argument'])
Puppet::Util.expects(:which).with('puppet-whatever').returns(nil)
+ commandline = Puppet::Util::CommandLine.new("puppet", ['whatever', 'argument'])
commandline.expects(:exec).never
expect {
commandline.execute
- }.to have_printed(/See 'puppet help' for help on available puppet subcommands/)
+ }.to have_printed(/See 'puppet help' for help on available puppet subcommands/).and_exit_with(1)
end
%w{--version -V}.each do |arg|
it "should abort and display #{arg} information" do
- commandline = Puppet::Util::CommandLine.new("puppet", ['whatever', arg])
Puppet::Util.expects(:which).with('puppet-whatever').returns(nil)
+ commandline = Puppet::Util::CommandLine.new("puppet", ['whatever', arg])
commandline.expects(:exec).never
expect {
commandline.execute
- }.to have_printed(/^#{Puppet.version}$/)
+ }.to have_printed(%r[^#{Regexp.escape(Puppet.version)}$]).and_exit_with(1)
end
end
end
diff --git a/spec/unit/util/execution_spec.rb b/spec/unit/util/execution_spec.rb
index 7c6238f9f..6a4bee490 100755
--- a/spec/unit/util/execution_spec.rb
+++ b/spec/unit/util/execution_spec.rb
@@ -1,15 +1,9 @@
#! /usr/bin/env ruby
require 'spec_helper'
+require 'puppet/file_system/uniquefile'
describe Puppet::Util::Execution do
include Puppet::Util::Execution
- # utility method to help deal with some windows vs. unix differences
- def process_status(exitstatus)
- return exitstatus if Puppet.features.microsoft_windows?
-
- stub('child_status', :exitstatus => exitstatus)
- end
-
# utility methods to help us test some private methods without being quite so verbose
def call_exec_posix(command, arguments, stdin, stdout, stderr)
Puppet::Util::Execution.send(:execute_posix, command, arguments, stdin, stdout, stderr)
@@ -28,8 +22,8 @@ describe Puppet::Util::Execution do
def stub_process_wait(exitstatus)
if Puppet.features.microsoft_windows?
Puppet::Util::Windows::Process.stubs(:wait_process).with(process_handle).returns(exitstatus)
- Process.stubs(:CloseHandle).with(process_handle)
- Process.stubs(:CloseHandle).with(thread_handle)
+ FFI::WIN32.stubs(:CloseHandle).with(process_handle)
+ FFI::WIN32.stubs(:CloseHandle).with(thread_handle)
else
Process.stubs(:waitpid2).with(pid).returns([pid, stub('child_status', :exitstatus => exitstatus)])
end
@@ -52,7 +46,7 @@ describe Puppet::Util::Execution do
$stderr.stubs(:reopen)
@stdin = File.open(null_file, 'r')
- @stdout = Tempfile.new('stdout')
+ @stdout = Puppet::FileSystem::Uniquefile.new('stdout')
@stderr = File.open(null_file, 'w')
# there is a danger here that ENV will be modified by exec_posix. Normally it would only affect the ENV
@@ -132,7 +126,7 @@ describe Puppet::Util::Execution do
stub_process_wait(0)
@stdin = File.open(null_file, 'r')
- @stdout = Tempfile.new('stdout')
+ @stdout = Puppet::FileSystem::Uniquefile.new('stdout')
@stderr = File.open(null_file, 'w')
end
@@ -223,8 +217,8 @@ describe Puppet::Util::Execution do
describe "when squelch is not set" do
it "should set stdout to a temporary output file" do
- outfile = Tempfile.new('stdout')
- Tempfile.stubs(:new).returns(outfile)
+ outfile = Puppet::FileSystem::Uniquefile.new('stdout')
+ Puppet::FileSystem::Uniquefile.stubs(:new).returns(outfile)
Puppet::Util::Execution.expects(executor).with do |_,_,_,stdout,_|
stdout.path == outfile.path
@@ -234,8 +228,8 @@ describe Puppet::Util::Execution do
end
it "should set stderr to the same file as stdout if combine is true" do
- outfile = Tempfile.new('stdout')
- Tempfile.stubs(:new).returns(outfile)
+ outfile = Puppet::FileSystem::Uniquefile.new('stdout')
+ Puppet::FileSystem::Uniquefile.stubs(:new).returns(outfile)
Puppet::Util::Execution.expects(executor).with do |_,_,_,stdout,stderr|
stdout.path == outfile.path and stderr.path == outfile.path
@@ -245,8 +239,8 @@ describe Puppet::Util::Execution do
end
it "should set stderr to the null device if combine is false" do
- outfile = Tempfile.new('stdout')
- Tempfile.stubs(:new).returns(outfile)
+ outfile = Puppet::FileSystem::Uniquefile.new('stdout')
+ Puppet::FileSystem::Uniquefile.stubs(:new).returns(outfile)
Puppet::Util::Execution.expects(executor).with do |_,_,_,stdout,stderr|
stdout.path == outfile.path and stderr.path == null_file
@@ -256,8 +250,8 @@ describe Puppet::Util::Execution do
end
it "should combine stdout and stderr if combine is true" do
- outfile = Tempfile.new('stdout')
- Tempfile.stubs(:new).returns(outfile)
+ outfile = Puppet::FileSystem::Uniquefile.new('stdout')
+ Puppet::FileSystem::Uniquefile.stubs(:new).returns(outfile)
Puppet::Util::Execution.expects(executor).with do |_,_,_,stdout,stderr|
stdout.path == outfile.path and stderr.path == outfile.path
@@ -267,8 +261,8 @@ describe Puppet::Util::Execution do
end
it "should default combine to true when no options are specified" do
- outfile = Tempfile.new('stdout')
- Tempfile.stubs(:new).returns(outfile)
+ outfile = Puppet::FileSystem::Uniquefile.new('stdout')
+ Puppet::FileSystem::Uniquefile.stubs(:new).returns(outfile)
Puppet::Util::Execution.expects(executor).with do |_,_,_,stdout,stderr|
stdout.path == outfile.path and stderr.path == outfile.path
@@ -278,8 +272,8 @@ describe Puppet::Util::Execution do
end
it "should default combine to false when options are specified, but combine is not" do
- outfile = Tempfile.new('stdout')
- Tempfile.stubs(:new).returns(outfile)
+ outfile = Puppet::FileSystem::Uniquefile.new('stdout')
+ Puppet::FileSystem::Uniquefile.stubs(:new).returns(outfile)
Puppet::Util::Execution.expects(executor).with do |_,_,_,stdout,stderr|
stdout.path == outfile.path and stderr.path == null_file
@@ -289,8 +283,8 @@ describe Puppet::Util::Execution do
end
it "should default combine to false when an empty hash of options is specified" do
- outfile = Tempfile.new('stdout')
- Tempfile.stubs(:new).returns(outfile)
+ outfile = Puppet::FileSystem::Uniquefile.new('stdout')
+ Puppet::FileSystem::Uniquefile.stubs(:new).returns(outfile)
Puppet::Util::Execution.expects(executor).with do |_,_,_,stdout,stderr|
stdout.path == outfile.path and stderr.path == null_file
@@ -306,8 +300,8 @@ describe Puppet::Util::Execution do
Puppet::Util::Execution.stubs(:execute_windows).returns(proc_info_stub)
Puppet::Util::Windows::Process.expects(:wait_process).with(process_handle).raises('whatever')
- Puppet::Util::Windows::Process.expects(:CloseHandle).with(thread_handle)
- Puppet::Util::Windows::Process.expects(:CloseHandle).with(process_handle)
+ FFI::WIN32.expects(:CloseHandle).with(thread_handle)
+ FFI::WIN32.expects(:CloseHandle).with(process_handle)
expect { Puppet::Util::Execution.execute('test command') }.to raise_error(RuntimeError)
end
@@ -507,25 +501,25 @@ describe Puppet::Util::Execution do
end
it "should read and return the output if squelch is false" do
- stdout = Tempfile.new('test')
- Tempfile.stubs(:new).returns(stdout)
+ stdout = Puppet::FileSystem::Uniquefile.new('test')
+ Puppet::FileSystem::Uniquefile.stubs(:new).returns(stdout)
stdout.write("My expected command output")
Puppet::Util::Execution.execute('test command').should == "My expected command output"
end
it "should not read the output if squelch is true" do
- stdout = Tempfile.new('test')
- Tempfile.stubs(:new).returns(stdout)
+ stdout = Puppet::FileSystem::Uniquefile.new('test')
+ Puppet::FileSystem::Uniquefile.stubs(:new).returns(stdout)
stdout.write("My expected command output")
Puppet::Util::Execution.execute('test command', :squelch => true).should == ''
end
it "should delete the file used for output if squelch is false" do
- stdout = Tempfile.new('test')
+ stdout = Puppet::FileSystem::Uniquefile.new('test')
path = stdout.path
- Tempfile.stubs(:new).returns(stdout)
+ Puppet::FileSystem::Uniquefile.stubs(:new).returns(stdout)
Puppet::Util::Execution.execute('test command')
@@ -533,8 +527,8 @@ describe Puppet::Util::Execution do
end
it "should not raise an error if the file is open" do
- stdout = Tempfile.new('test')
- Tempfile.stubs(:new).returns(stdout)
+ stdout = Puppet::FileSystem::Uniquefile.new('test')
+ Puppet::FileSystem::Uniquefile.stubs(:new).returns(stdout)
file = File.new(stdout.path, 'r')
Puppet::Util.execute('test command')
@@ -597,40 +591,39 @@ describe Puppet::Util::Execution do
describe "#execpipe" do
it "should execute a string as a string" do
Puppet::Util::Execution.expects(:open).with('| echo hello 2>&1').returns('hello')
- $CHILD_STATUS.expects(:==).with(0).returns(true)
+ Puppet::Util::Execution.expects(:exitstatus).returns(0)
Puppet::Util::Execution.execpipe('echo hello').should == 'hello'
end
it "should print meaningful debug message for string argument" do
Puppet::Util::Execution.expects(:debug).with("Executing 'echo hello'")
Puppet::Util::Execution.expects(:open).with('| echo hello 2>&1').returns('hello')
- $CHILD_STATUS.expects(:==).with(0).returns(true)
+ Puppet::Util::Execution.expects(:exitstatus).returns(0)
Puppet::Util::Execution.execpipe('echo hello')
end
it "should print meaningful debug message for array argument" do
Puppet::Util::Execution.expects(:debug).with("Executing 'echo hello'")
Puppet::Util::Execution.expects(:open).with('| echo hello 2>&1').returns('hello')
- $CHILD_STATUS.expects(:==).with(0).returns(true)
+ Puppet::Util::Execution.expects(:exitstatus).returns(0)
Puppet::Util::Execution.execpipe(['echo','hello'])
end
it "should execute an array by pasting together with spaces" do
Puppet::Util::Execution.expects(:open).with('| echo hello 2>&1').returns('hello')
- $CHILD_STATUS.expects(:==).with(0).returns(true)
+ Puppet::Util::Execution.expects(:exitstatus).returns(0)
Puppet::Util::Execution.execpipe(['echo', 'hello']).should == 'hello'
end
it "should fail if asked to fail, and the child does" do
- Puppet::Util::Execution.stubs(:open).returns('error message')
- $CHILD_STATUS.expects(:==).with(0).returns(false)
+ Puppet::Util::Execution.stubs(:open).with('| echo hello 2>&1').returns('error message')
+ Puppet::Util::Execution.expects(:exitstatus).returns(1)
expect { Puppet::Util::Execution.execpipe('echo hello') }.
to raise_error Puppet::ExecutionFailure, /error message/
end
it "should not fail if asked not to fail, and the child does" do
Puppet::Util::Execution.stubs(:open).returns('error message')
- $CHILD_STATUS.stubs(:==).with(0).returns(false)
Puppet::Util::Execution.execpipe('echo hello', false).should == 'error message'
end
end
diff --git a/spec/unit/util/feature_spec.rb b/spec/unit/util/feature_spec.rb
index aa8afbba6..e6d844533 100755
--- a/spec/unit/util/feature_spec.rb
+++ b/spec/unit/util/feature_spec.rb
@@ -91,4 +91,16 @@ describe Puppet::Util::Feature do
@features.should_not be_myfeature
@features.should be_myfeature
end
+
+ it "should cache load failures when configured to do so" do
+ Puppet[:always_cache_features] = true
+
+ @features.add(:myfeature, :libs => %w{foo bar})
+ @features.expects(:require).with("foo").raises(LoadError)
+
+ @features.should_not be_myfeature
+ # second call would cause an expectation exception if 'require' was
+ # called a second time
+ @features.should_not be_myfeature
+ end
end
diff --git a/spec/unit/util/http_proxy_spec.rb b/spec/unit/util/http_proxy_spec.rb
index bc6b4d2b7..59f39c511 100644
--- a/spec/unit/util/http_proxy_spec.rb
+++ b/spec/unit/util/http_proxy_spec.rb
@@ -4,7 +4,7 @@ require 'puppet/util/http_proxy'
describe Puppet::Util::HttpProxy do
- host, port = 'some.host', 1234
+ host, port, user, password = 'some.host', 1234, 'user1', 'pAssw0rd'
describe ".http_proxy_env" do
it "should return nil if no environment variables" do
@@ -80,4 +80,46 @@ describe Puppet::Util::HttpProxy do
end
+ describe ".http_proxy_user" do
+ it "should return a proxy user if set in environment" do
+ Puppet::Util.withenv('HTTP_PROXY' => "http://#{user}:#{password}@#{host}:#{port}") do
+ subject.http_proxy_user.should == user
+ end
+ end
+
+ it "should return a proxy user if set in config" do
+ Puppet.settings[:http_proxy_user] = user
+ subject.http_proxy_user.should == user
+ end
+
+ it "should use environment variable before puppet settings" do
+ Puppet::Util.withenv('HTTP_PROXY' => "http://#{user}:#{password}@#{host}:#{port}") do
+ Puppet.settings[:http_proxy_user] = 'clownpants'
+ subject.http_proxy_user.should == user
+ end
+ end
+
+ end
+
+ describe ".http_proxy_password" do
+ it "should return a proxy password if set in environment" do
+ Puppet::Util.withenv('HTTP_PROXY' => "http://#{user}:#{password}@#{host}:#{port}") do
+ subject.http_proxy_password.should == password
+ end
+ end
+
+ it "should return a proxy password if set in config" do
+ Puppet.settings[:http_proxy_user] = user
+ Puppet.settings[:http_proxy_password] = password
+ subject.http_proxy_password.should == password
+ end
+
+ it "should use environment variable before puppet settings" do
+ Puppet::Util.withenv('HTTP_PROXY' => "http://#{user}:#{password}@#{host}:#{port}") do
+ Puppet.settings[:http_proxy_password] = 'clownpants'
+ subject.http_proxy_password.should == password
+ end
+ end
+
+ end
end
diff --git a/spec/unit/util/log/destinations_spec.rb b/spec/unit/util/log/destinations_spec.rb
index a91236dba..a81eac631 100755
--- a/spec/unit/util/log/destinations_spec.rb
+++ b/spec/unit/util/log/destinations_spec.rb
@@ -29,7 +29,7 @@ describe Puppet::Util::Log.desttypes[:file] do
before do
File.stubs(:open) # prevent actually creating the file
- File.stubs(:chown) # prevent chown on non existing file from failing
+ File.stubs(:chown) # prevent chown on non existing file from failing
@class = Puppet::Util::Log.desttypes[:file]
end
@@ -181,3 +181,47 @@ describe Puppet::Util::Log.desttypes[:console] do
end
end
end
+
+
+describe ":eventlog", :if => Puppet::Util::Platform.windows? do
+ before do
+ if Facter.value(:kernelmajversion).to_f < 6.0
+ pending("requires win32-eventlog gem upgrade to 0.6.2 on Windows 2003")
+ end
+ end
+
+ let(:klass) { Puppet::Util::Log.desttypes[:eventlog] }
+
+ def expects_message_with_type(klass, level, eventlog_type, eventlog_id)
+ eventlog = stub('eventlog')
+ eventlog.expects(:report_event).with(has_entries(:source => "Puppet", :event_type => eventlog_type, :event_id => eventlog_id, :data => "a hitchhiker: don't panic"))
+ Win32::EventLog.stubs(:open).returns(eventlog)
+
+ msg = Puppet::Util::Log.new(:level => level, :message => "don't panic", :source => "a hitchhiker")
+ dest = klass.new
+ dest.handle(msg)
+ end
+
+ it "supports the eventlog feature" do
+ expect(Puppet.features.eventlog?).to be_true
+ end
+
+ it "logs to the Application event log" do
+ eventlog = stub('eventlog')
+ Win32::EventLog.expects(:open).with('Application').returns(stub('eventlog'))
+
+ klass.new
+ end
+
+ it "logs :debug level as an information type event" do
+ expects_message_with_type(klass, :debug, klass::EVENTLOG_INFORMATION_TYPE, 0x1)
+ end
+
+ it "logs :warning level as an warning type event" do
+ expects_message_with_type(klass, :warning, klass::EVENTLOG_WARNING_TYPE, 0x2)
+ end
+
+ it "logs :err level as an error type event" do
+ expects_message_with_type(klass, :err, klass::EVENTLOG_ERROR_TYPE, 0x3)
+ end
+end
diff --git a/spec/unit/util/logging_spec.rb b/spec/unit/util/logging_spec.rb
index 0858f7857..abdae9189 100755
--- a/spec/unit/util/logging_spec.rb
+++ b/spec/unit/util/logging_spec.rb
@@ -93,6 +93,12 @@ describe Puppet::Util::Logging do
end
describe "when sending a deprecation warning" do
+ it "does not log a message when deprecation warnings are disabled" do
+ Puppet.expects(:[]).with(:disable_warnings).returns %w[deprecations]
+ @logger.expects(:warning).never
+ @logger.deprecation_warning 'foo'
+ end
+
it "logs the message with warn" do
@logger.expects(:warning).with do |msg|
msg =~ /^foo\n/
@@ -133,6 +139,44 @@ describe Puppet::Util::Logging do
end
end
+ describe "when sending a puppet_deprecation_warning" do
+ it "requires file and line or key options" do
+ expect do
+ @logger.puppet_deprecation_warning("foo")
+ end.to raise_error(Puppet::DevError, /Need either :file and :line, or :key/)
+ expect do
+ @logger.puppet_deprecation_warning("foo", :file => 'bar')
+ end.to raise_error(Puppet::DevError, /Need either :file and :line, or :key/)
+ expect do
+ @logger.puppet_deprecation_warning("foo", :key => 'akey')
+ @logger.puppet_deprecation_warning("foo", :file => 'afile', :line => 1)
+ end.to_not raise_error
+ end
+
+ it "warns with file and line" do
+ @logger.expects(:warning).with(regexp_matches(/deprecated foo.*afile:5/m))
+ @logger.puppet_deprecation_warning("deprecated foo", :file => 'afile', :line => 5)
+ end
+
+ it "warns keyed from file and line" do
+ @logger.expects(:warning).with(regexp_matches(/deprecated foo.*afile:5/m)).once
+ 5.times do
+ @logger.puppet_deprecation_warning("deprecated foo", :file => 'afile', :line => 5)
+ end
+ end
+
+ it "warns with separate key only once regardless of file and line" do
+ @logger.expects(:warning).with(regexp_matches(/deprecated foo.*afile:5/m)).once
+ @logger.puppet_deprecation_warning("deprecated foo", :key => 'some_key', :file => 'afile', :line => 5)
+ @logger.puppet_deprecation_warning("deprecated foo", :key => 'some_key', :file => 'bfile', :line => 3)
+ end
+
+ it "warns with key but no file and line" do
+ @logger.expects(:warning).with(regexp_matches(/deprecated foo.*unknown:unknown/m))
+ @logger.puppet_deprecation_warning("deprecated foo", :key => 'some_key')
+ end
+ end
+
describe "when formatting exceptions" do
it "should be able to format a chain of exceptions" do
exc3 = Puppet::Error.new("original")
diff --git a/spec/unit/util/pidlock_spec.rb b/spec/unit/util/pidlock_spec.rb
index 2ebe7dec8..fcef7aa31 100644
--- a/spec/unit/util/pidlock_spec.rb
+++ b/spec/unit/util/pidlock_spec.rb
@@ -50,6 +50,26 @@ describe Puppet::Util::Pidlock do
Puppet::FileSystem.exist?(@lockfile).should be_true
end
+ it 'should create an empty lock file even when pid is missing' do
+ Process.stubs(:pid).returns('')
+ @lock.lock
+ Puppet::FileSystem.exist?(@lock.file_path).should be_true
+ Puppet::FileSystem.read(@lock.file_path).should be_empty
+ end
+
+ it 'should replace an existing empty lockfile with a pid, given a subsequent lock call made against a valid pid' do
+ # empty pid results in empty lockfile
+ Process.stubs(:pid).returns('')
+ @lock.lock
+ Puppet::FileSystem.exist?(@lock.file_path).should be_true
+
+ # next lock call with valid pid kills existing empty lockfile
+ Process.stubs(:pid).returns(1234)
+ @lock.lock
+ Puppet::FileSystem.exist?(@lock.file_path).should be_true
+ Puppet::FileSystem.read(@lock.file_path).should == '1234'
+ end
+
it "should expose the lock file_path" do
@lock.file_path.should == @lockfile
end
@@ -83,6 +103,22 @@ describe Puppet::Util::Pidlock do
@lock.lock
@lock.should be_locked
end
+
+ it "should remove the lockfile when pid is missing" do
+ Process.stubs(:pid).returns('')
+ @lock.lock
+ @lock.locked?.should be_false
+ Puppet::FileSystem.exist?(@lock.file_path).should be_false
+ end
+ end
+
+ describe '#lock_pid' do
+ it 'should return nil if the pid is empty' do
+ # fake pid to get empty lockfile
+ Process.stubs(:pid).returns('')
+ @lock.lock
+ @lock.lock_pid.should == nil
+ end
end
describe "with a stale lock" do
@@ -105,7 +141,7 @@ describe Puppet::Util::Pidlock do
describe "#lock" do
it "should clear stale locks" do
- @lock.locked?
+ @lock.locked?.should be_false
Puppet::FileSystem.exist?(@lockfile).should be_false
end
diff --git a/spec/unit/util/profiler/aggregate_spec.rb b/spec/unit/util/profiler/aggregate_spec.rb
new file mode 100644
index 000000000..8d38d4673
--- /dev/null
+++ b/spec/unit/util/profiler/aggregate_spec.rb
@@ -0,0 +1,59 @@
+require 'spec_helper'
+require 'puppet/util/profiler'
+require 'puppet/util/profiler/around_profiler'
+require 'puppet/util/profiler/aggregate'
+
+describe Puppet::Util::Profiler::Aggregate do
+ let(:logger) { AggregateSimpleLog.new }
+ let(:profiler) { Puppet::Util::Profiler::Aggregate.new(logger, nil) }
+ let(:profiler_mgr) do
+ p = Puppet::Util::Profiler::AroundProfiler.new
+ p.add_profiler(profiler)
+ p
+ end
+
+ it "tracks the aggregate counts and time for the hierarchy of metrics" do
+ profiler_mgr.profile("Looking up hiera data in production environment", ["function", "hiera_lookup", "production"]) { sleep 0.01 }
+ profiler_mgr.profile("Looking up hiera data in test environment", ["function", "hiera_lookup", "test"]) {}
+ profiler_mgr.profile("looking up stuff for compilation", ["compiler", "lookup"]) { sleep 0.01 }
+ profiler_mgr.profile("COMPILING ALL OF THE THINGS!", ["compiler", "compiling"]) {}
+
+ profiler.values["function"].count.should == 2
+ profiler.values["function"].time.should be > 0
+ profiler.values["function"]["hiera_lookup"].count.should == 2
+ profiler.values["function"]["hiera_lookup"]["production"].count.should == 1
+ profiler.values["function"]["hiera_lookup"]["test"].count.should == 1
+ profiler.values["function"].time.should be >= profiler.values["function"]["hiera_lookup"]["test"].time
+
+ profiler.values["compiler"].count.should == 2
+ profiler.values["compiler"].time.should be > 0
+ profiler.values["compiler"]["lookup"].count.should == 1
+ profiler.values["compiler"]["compiling"].count.should == 1
+ profiler.values["compiler"].time.should be >= profiler.values["compiler"]["lookup"].time
+
+ profiler.shutdown
+
+ logger.output.should =~ /function -> hiera_lookup: .*\(2 calls\)\nfunction -> hiera_lookup ->.*\(1 calls\)/
+ logger.output.should =~ /compiler: .*\(2 calls\)\ncompiler ->.*\(1 calls\)/
+ end
+
+ it "tolerates calls to `profile` that don't include a metric id" do
+ profiler_mgr.profile("yo") {}
+ end
+
+ it "supports both symbols and strings as components of a metric id" do
+ profiler_mgr.profile("yo", [:foo, "bar"]) {}
+ end
+
+ class AggregateSimpleLog
+ attr_reader :output
+
+ def initialize
+ @output = ""
+ end
+
+ def call(msg)
+ @output << msg << "\n"
+ end
+ end
+end
diff --git a/spec/unit/util/profiler/around_profiler_spec.rb b/spec/unit/util/profiler/around_profiler_spec.rb
new file mode 100644
index 000000000..0837395b5
--- /dev/null
+++ b/spec/unit/util/profiler/around_profiler_spec.rb
@@ -0,0 +1,61 @@
+require 'spec_helper'
+require 'puppet/util/profiler'
+
+describe Puppet::Util::Profiler::AroundProfiler do
+ let(:child) { TestAroundProfiler.new() }
+ let(:profiler) { Puppet::Util::Profiler::AroundProfiler.new }
+
+ before :each do
+ profiler.add_profiler(child)
+ end
+
+ it "returns the value of the profiled segment" do
+ retval = profiler.profile("Testing", ["testing"]) { "the return value" }
+
+ retval.should == "the return value"
+ end
+
+ it "propagates any errors raised in the profiled segment" do
+ expect do
+ profiler.profile("Testing", ["testing"]) { raise "a problem" }
+ end.to raise_error("a problem")
+ end
+
+ it "makes the description and the context available to the `start` and `finish` methods" do
+ profiler.profile("Testing", ["testing"]) { }
+
+ child.context.should == "Testing"
+ child.description.should == "Testing"
+ end
+
+ it "calls finish even when an error is raised" do
+ begin
+ profiler.profile("Testing", ["testing"]) { raise "a problem" }
+ rescue
+ child.context.should == "Testing"
+ end
+ end
+
+ it "supports multiple profilers" do
+ profiler2 = TestAroundProfiler.new
+ profiler.add_profiler(profiler2)
+ profiler.profile("Testing", ["testing"]) {}
+
+ child.context.should == "Testing"
+ profiler2.context.should == "Testing"
+ end
+
+ class TestAroundProfiler
+ attr_accessor :context, :description
+
+ def start(description, metric_id)
+ description
+ end
+
+ def finish(context, description, metric_id)
+ @context = context
+ @description = description
+ end
+ end
+end
+
diff --git a/spec/unit/util/profiler/logging_spec.rb b/spec/unit/util/profiler/logging_spec.rb
index 5316e5ae9..3f6a728dd 100644
--- a/spec/unit/util/profiler/logging_spec.rb
+++ b/spec/unit/util/profiler/logging_spec.rb
@@ -4,51 +4,40 @@ require 'puppet/util/profiler'
describe Puppet::Util::Profiler::Logging do
let(:logger) { SimpleLog.new }
let(:identifier) { "Profiling ID" }
- let(:profiler) { TestLoggingProfiler.new(logger, identifier) }
-
- it "returns the value of the profiled segment" do
- retval = profiler.profile("Testing") { "the return value" }
-
- retval.should == "the return value"
- end
-
- it "propogates any errors raised in the profiled segment" do
- expect do
- profiler.profile("Testing") { raise "a problem" }
- end.to raise_error("a problem")
+ let(:logging_profiler) { TestLoggingProfiler.new(logger, identifier) }
+ let(:profiler) do
+ p = Puppet::Util::Profiler::AroundProfiler.new
+ p.add_profiler(logging_profiler)
+ p
end
it "logs the explanation of the profile results" do
- profiler.profile("Testing") { }
+ profiler.profile("Testing", ["test"]) { }
logger.messages.first.should =~ /the explanation/
end
- it "logs results even when an error is raised" do
- begin
- profiler.profile("Testing") { raise "a problem" }
- rescue
- logger.messages.first.should =~ /the explanation/
- end
- end
-
it "describes the profiled segment" do
- profiler.profile("Tested measurement") { }
+ profiler.profile("Tested measurement", ["test"]) { }
logger.messages.first.should =~ /PROFILE \[#{identifier}\] \d Tested measurement/
end
it "indicates the order in which segments are profiled" do
- profiler.profile("Measurement") { }
- profiler.profile("Another measurement") { }
+ profiler.profile("Measurement", ["measurement"]) { }
+ profiler.profile("Another measurement", ["measurement"]) { }
logger.messages[0].should =~ /1 Measurement/
logger.messages[1].should =~ /2 Another measurement/
end
it "indicates the nesting of profiled segments" do
- profiler.profile("Measurement") { profiler.profile("Nested measurement") { } }
- profiler.profile("Another measurement") { profiler.profile("Another nested measurement") { } }
+ profiler.profile("Measurement", ["measurement1"]) do
+ profiler.profile("Nested measurement", ["measurement2"]) { }
+ end
+ profiler.profile("Another measurement", ["measurement1"]) do
+ profiler.profile("Another nested measurement", ["measurement2"]) { }
+ end
logger.messages[0].should =~ /1.1 Nested measurement/
logger.messages[1].should =~ /1 Measurement/
@@ -57,12 +46,12 @@ describe Puppet::Util::Profiler::Logging do
end
class TestLoggingProfiler < Puppet::Util::Profiler::Logging
- def start
+ def do_start(metric, description)
"the start"
end
- def finish(context)
- "the explanation of #{context}"
+ def do_finish(context, metric, description)
+ {:msg => "the explanation of #{context}"}
end
end
diff --git a/spec/unit/util/profiler/none_spec.rb b/spec/unit/util/profiler/none_spec.rb
deleted file mode 100644
index 0cabfef6f..000000000
--- a/spec/unit/util/profiler/none_spec.rb
+++ /dev/null
@@ -1,12 +0,0 @@
-require 'spec_helper'
-require 'puppet/util/profiler'
-
-describe Puppet::Util::Profiler::None do
- let(:profiler) { Puppet::Util::Profiler::None.new }
-
- it "returns the value of the profiled block" do
- retval = profiler.profile("Testing") { "the return value" }
-
- retval.should == "the return value"
- end
-end
diff --git a/spec/unit/util/profiler/wall_clock_spec.rb b/spec/unit/util/profiler/wall_clock_spec.rb
index 668f63221..1adcf0d61 100644
--- a/spec/unit/util/profiler/wall_clock_spec.rb
+++ b/spec/unit/util/profiler/wall_clock_spec.rb
@@ -6,7 +6,7 @@ describe Puppet::Util::Profiler::WallClock do
it "logs the number of seconds it took to execute the segment" do
profiler = Puppet::Util::Profiler::WallClock.new(nil, nil)
- message = profiler.finish(profiler.start)
+ message = profiler.do_finish(profiler.start(["foo", "bar"], "Testing"), ["foo", "bar"], "Testing")[:msg]
message.should =~ /took \d\.\d{4} seconds/
end
diff --git a/spec/unit/util/profiler_spec.rb b/spec/unit/util/profiler_spec.rb
new file mode 100644
index 000000000..c7fc48cb9
--- /dev/null
+++ b/spec/unit/util/profiler_spec.rb
@@ -0,0 +1,55 @@
+require 'spec_helper'
+require 'puppet/util/profiler'
+
+describe Puppet::Util::Profiler do
+ let(:profiler) { TestProfiler.new() }
+
+ it "supports adding profilers" do
+ subject.add_profiler(profiler)
+ subject.current[0].should == profiler
+ end
+
+ it "supports removing profilers" do
+ subject.add_profiler(profiler)
+ subject.remove_profiler(profiler)
+ subject.current.length.should == 0
+ end
+
+ it "supports clearing profiler list" do
+ subject.add_profiler(profiler)
+ subject.clear
+ subject.current.length.should == 0
+ end
+
+ it "supports profiling" do
+ subject.add_profiler(profiler)
+ subject.profile("hi", ["mymetric"]) {}
+ profiler.context[:metric_id].should == ["mymetric"]
+ profiler.context[:description].should == "hi"
+ profiler.description.should == "hi"
+ end
+
+ it "supports profiling without a metric id" do
+ subject.add_profiler(profiler)
+ subject.profile("hi") {}
+ profiler.context[:metric_id].should == nil
+ profiler.context[:description].should == "hi"
+ profiler.description.should == "hi"
+ end
+
+ class TestProfiler
+ attr_accessor :context, :metric, :description
+
+ def start(description, metric_id)
+ {:metric_id => metric_id,
+ :description => description}
+ end
+
+ def finish(context, description, metric_id)
+ @context = context
+ @metric_id = metric_id
+ @description = description
+ end
+ end
+end
+
diff --git a/spec/unit/util/queue_spec.rb b/spec/unit/util/queue_spec.rb
index d7ba57f85..48b98e8e3 100755
--- a/spec/unit/util/queue_spec.rb
+++ b/spec/unit/util/queue_spec.rb
@@ -1,7 +1,6 @@
#! /usr/bin/env ruby
require 'spec_helper'
require 'puppet/util/queue'
-require 'spec/mocks'
def make_test_client_class(n)
c = Class.new do
diff --git a/spec/unit/util/rdoc/parser_spec.rb b/spec/unit/util/rdoc/parser_spec.rb
index acc606b76..7ed2cffcc 100755
--- a/spec/unit/util/rdoc/parser_spec.rb
+++ b/spec/unit/util/rdoc/parser_spec.rb
@@ -21,6 +21,17 @@ describe "RDoc::Parser", :if => Puppet.features.rdoc1? do
end
describe "when scanning files" do
+ around(:each) do |example|
+ Puppet.override({
+ :current_environment => Puppet::Node::Environment.create(:doc, [], '/somewhere/etc/manifests/site.pp')
+ },
+ "A fake current environment that the application would have established by now"
+ ) do
+
+ example.run
+ end
+ end
+
it "should parse puppet files with the puppet parser" do
@parser.stubs(:scan_top_level)
parser = stub 'parser'
@@ -56,15 +67,12 @@ describe "RDoc::Parser", :if => Puppet.features.rdoc1? do
it "should scan the top level even if the file has already parsed" do
known_type = stub 'known_types'
- env = Puppet::Node::Environment.create(Puppet[:environment].to_sym, [])
+ env = Puppet.lookup(:current_environment)
env.stubs(:known_resource_types).returns(known_type)
known_type.expects(:watching_file?).with("module/manifests/init.pp").returns(true)
- Puppet.override(:environments => Puppet::Environments::Static.new(env)) do
-
- @parser.expects(:scan_top_level)
+ @parser.expects(:scan_top_level)
- @parser.scan
- end
+ @parser.scan
end
end
diff --git a/spec/unit/util/tagging_spec.rb b/spec/unit/util/tagging_spec.rb
index 248e915e9..53bb39d7a 100755
--- a/spec/unit/util/tagging_spec.rb
+++ b/spec/unit/util/tagging_spec.rb
@@ -41,7 +41,7 @@ describe Puppet::Util::Tagging do
end
it "should allow tags containing '.' characters" do
- expect { tagger.tag("good.tag") }.to_not raise_error(Puppet::ParseError)
+ expect { tagger.tag("good.tag") }.to_not raise_error
end
it "should add qualified classes as tags" do
@@ -127,5 +127,36 @@ describe Puppet::Util::Tagging do
expect(tagger).to be_tagged("two")
expect(tagger).to be_tagged("three")
end
+
+ it "protects against empty tags" do
+ expect { tagger.tags = "one,,two"}.to raise_error(/Invalid tag ''/)
+ end
+
+ it "takes an array of tags" do
+ tagger.tags = ["one", "two"]
+
+ expect(tagger).to be_tagged("one")
+ expect(tagger).to be_tagged("two")
+ end
+
+ it "removes any existing tags when reassigning" do
+ tagger.tags = "one, two"
+
+ tagger.tags = "three, four"
+
+ expect(tagger).to_not be_tagged("one")
+ expect(tagger).to_not be_tagged("two")
+ expect(tagger).to be_tagged("three")
+ expect(tagger).to be_tagged("four")
+ end
+
+ it "allows empty tags that are generated from :: separated tags" do
+ tagger.tags = "one::::two::three"
+
+ expect(tagger).to be_tagged("one")
+ expect(tagger).to be_tagged("")
+ expect(tagger).to be_tagged("two")
+ expect(tagger).to be_tagged("three")
+ end
end
end
diff --git a/spec/unit/util/windows/access_control_entry_spec.rb b/spec/unit/util/windows/access_control_entry_spec.rb
index b139b0d42..8d3f51c8a 100644
--- a/spec/unit/util/windows/access_control_entry_spec.rb
+++ b/spec/unit/util/windows/access_control_entry_spec.rb
@@ -5,7 +5,7 @@ require 'puppet/util/windows'
describe "Puppet::Util::Windows::AccessControlEntry", :if => Puppet.features.microsoft_windows? do
let(:klass) { Puppet::Util::Windows::AccessControlEntry }
let(:sid) { 'S-1-5-18' }
- let(:mask) { Windows::File::FILE_ALL_ACCESS }
+ let(:mask) { Puppet::Util::Windows::File::FILE_ALL_ACCESS }
it "creates an access allowed ace" do
ace = klass.new(sid, mask)
diff --git a/spec/unit/util/adsi_spec.rb b/spec/unit/util/windows/adsi_spec.rb
index 491c4374b..f569d91e9 100755
--- a/spec/unit/util/adsi_spec.rb
+++ b/spec/unit/util/windows/adsi_spec.rb
@@ -2,97 +2,104 @@
require 'spec_helper'
-require 'puppet/util/adsi'
+require 'puppet/util/windows'
-describe Puppet::Util::ADSI do
+describe Puppet::Util::Windows::ADSI, :if => Puppet.features.microsoft_windows? do
let(:connection) { stub 'connection' }
before(:each) do
- Puppet::Util::ADSI.instance_variable_set(:@computer_name, 'testcomputername')
- Puppet::Util::ADSI.stubs(:connect).returns connection
+ Puppet::Util::Windows::ADSI.instance_variable_set(:@computer_name, 'testcomputername')
+ Puppet::Util::Windows::ADSI.stubs(:connect).returns connection
end
after(:each) do
- Puppet::Util::ADSI.instance_variable_set(:@computer_name, nil)
+ Puppet::Util::Windows::ADSI.instance_variable_set(:@computer_name, nil)
end
it "should generate the correct URI for a resource" do
- Puppet::Util::ADSI.uri('test', 'user').should == "WinNT://./test,user"
+ Puppet::Util::Windows::ADSI.uri('test', 'user').should == "WinNT://./test,user"
end
it "should be able to get the name of the computer" do
- Puppet::Util::ADSI.computer_name.should == 'testcomputername'
+ Puppet::Util::Windows::ADSI.computer_name.should == 'testcomputername'
end
it "should be able to provide the correct WinNT base URI for the computer" do
- Puppet::Util::ADSI.computer_uri.should == "WinNT://."
+ Puppet::Util::Windows::ADSI.computer_uri.should == "WinNT://."
end
it "should generate a fully qualified WinNT URI" do
- Puppet::Util::ADSI.computer_uri('testcomputername').should == "WinNT://testcomputername"
+ Puppet::Util::Windows::ADSI.computer_uri('testcomputername').should == "WinNT://testcomputername"
end
- describe ".sid_for_account", :if => Puppet.features.microsoft_windows? do
+ describe ".sid_for_account" do
it "should return nil if the account does not exist" do
- Puppet::Util::Windows::Security.expects(:name_to_sid).with('foobar').returns nil
+ Puppet::Util::Windows::SID.expects(:name_to_sid).with('foobar').returns nil
- Puppet::Util::ADSI.sid_for_account('foobar').should be_nil
+ Puppet::Util::Windows::ADSI.sid_for_account('foobar').should be_nil
end
it "should return a SID for a passed user or group name" do
- Puppet::Util::Windows::Security.expects(:name_to_sid).with('testers').returns 'S-1-5-32-547'
+ Puppet::Util::Windows::SID.expects(:name_to_sid).with('testers').returns 'S-1-5-32-547'
- Puppet::Util::ADSI.sid_for_account('testers').should == 'S-1-5-32-547'
+ Puppet::Util::Windows::ADSI.sid_for_account('testers').should == 'S-1-5-32-547'
end
it "should return a SID for a passed fully-qualified user or group name" do
- Puppet::Util::Windows::Security.expects(:name_to_sid).with('MACHINE\testers').returns 'S-1-5-32-547'
+ Puppet::Util::Windows::SID.expects(:name_to_sid).with('MACHINE\testers').returns 'S-1-5-32-547'
- Puppet::Util::ADSI.sid_for_account('MACHINE\testers').should == 'S-1-5-32-547'
+ Puppet::Util::Windows::ADSI.sid_for_account('MACHINE\testers').should == 'S-1-5-32-547'
end
end
- describe ".sid_uri", :if => Puppet.features.microsoft_windows? do
+ describe ".computer_name" do
+ it "should return a non-empty ComputerName string" do
+ Puppet::Util::Windows::ADSI.instance_variable_set(:@computer_name, nil)
+ Puppet::Util::Windows::ADSI.computer_name.should_not be_empty
+ end
+ end
+
+ describe ".sid_uri" do
it "should raise an error when the input is not a SID object" do
[Object.new, {}, 1, :symbol, '', nil].each do |input|
expect {
- Puppet::Util::ADSI.sid_uri(input)
+ Puppet::Util::Windows::ADSI.sid_uri(input)
}.to raise_error(Puppet::Error, /Must use a valid SID object/)
end
end
it "should return a SID uri for a well-known SID (SYSTEM)" do
sid = Win32::Security::SID.new('SYSTEM')
- Puppet::Util::ADSI.sid_uri(sid).should == 'WinNT://S-1-5-18'
+ Puppet::Util::Windows::ADSI.sid_uri(sid).should == 'WinNT://S-1-5-18'
end
end
- describe Puppet::Util::ADSI::User do
+ describe Puppet::Util::Windows::ADSI::User do
let(:username) { 'testuser' }
let(:domain) { 'DOMAIN' }
let(:domain_username) { "#{domain}\\#{username}"}
it "should generate the correct URI" do
- Puppet::Util::ADSI.stubs(:sid_uri_safe).returns(nil)
- Puppet::Util::ADSI::User.uri(username).should == "WinNT://./#{username},user"
+ Puppet::Util::Windows::ADSI.stubs(:sid_uri_safe).returns(nil)
+ Puppet::Util::Windows::ADSI::User.uri(username).should == "WinNT://./#{username},user"
end
it "should generate the correct URI for a user with a domain" do
- Puppet::Util::ADSI.stubs(:sid_uri_safe).returns(nil)
- Puppet::Util::ADSI::User.uri(username, domain).should == "WinNT://#{domain}/#{username},user"
+ Puppet::Util::Windows::ADSI.stubs(:sid_uri_safe).returns(nil)
+ Puppet::Util::Windows::ADSI::User.uri(username, domain).should == "WinNT://#{domain}/#{username},user"
end
it "should be able to parse a username without a domain" do
- Puppet::Util::ADSI::User.parse_name(username).should == [username, '.']
+ Puppet::Util::Windows::ADSI::User.parse_name(username).should == [username, '.']
end
it "should be able to parse a username with a domain" do
- Puppet::Util::ADSI::User.parse_name(domain_username).should == [username, domain]
+ Puppet::Util::Windows::ADSI::User.parse_name(domain_username).should == [username, domain]
end
it "should raise an error with a username that contains a /" do
expect {
- Puppet::Util::ADSI::User.parse_name("#{domain}/#{username}")
+ Puppet::Util::Windows::ADSI::User.parse_name("#{domain}/#{username}")
}.to raise_error(Puppet::Error, /Value must be in DOMAIN\\user style syntax/)
end
@@ -100,63 +107,61 @@ describe Puppet::Util::ADSI do
adsi_user = stub('adsi')
connection.expects(:Create).with('user', username).returns(adsi_user)
- Puppet::Util::ADSI::Group.expects(:exists?).with(username).returns(false)
+ Puppet::Util::Windows::ADSI::Group.expects(:exists?).with(username).returns(false)
- user = Puppet::Util::ADSI::User.create(username)
+ user = Puppet::Util::Windows::ADSI::User.create(username)
- user.should be_a(Puppet::Util::ADSI::User)
+ user.should be_a(Puppet::Util::Windows::ADSI::User)
user.native_user.should == adsi_user
end
it "should be able to check the existence of a user" do
- Puppet::Util::ADSI.stubs(:sid_uri_safe).returns(nil)
- Puppet::Util::ADSI.expects(:connect).with("WinNT://./#{username},user").returns connection
- Puppet::Util::ADSI::User.exists?(username).should be_true
+ Puppet::Util::Windows::ADSI.stubs(:sid_uri_safe).returns(nil)
+ Puppet::Util::Windows::ADSI.expects(:connect).with("WinNT://./#{username},user").returns connection
+ Puppet::Util::Windows::ADSI::User.exists?(username).should be_true
end
it "should be able to check the existence of a domain user" do
- Puppet::Util::ADSI.stubs(:sid_uri_safe).returns(nil)
- Puppet::Util::ADSI.expects(:connect).with("WinNT://#{domain}/#{username},user").returns connection
- Puppet::Util::ADSI::User.exists?(domain_username).should be_true
+ Puppet::Util::Windows::ADSI.stubs(:sid_uri_safe).returns(nil)
+ Puppet::Util::Windows::ADSI.expects(:connect).with("WinNT://#{domain}/#{username},user").returns connection
+ Puppet::Util::Windows::ADSI::User.exists?(domain_username).should be_true
end
- it "should be able to confirm the existence of a user with a well-known SID",
- :if => Puppet.features.microsoft_windows? do
+ it "should be able to confirm the existence of a user with a well-known SID" do
system_user = Win32::Security::SID::LocalSystem
# ensure that the underlying OS is queried here
- Puppet::Util::ADSI.unstub(:connect)
- Puppet::Util::ADSI::User.exists?(system_user).should be_true
+ Puppet::Util::Windows::ADSI.unstub(:connect)
+ Puppet::Util::Windows::ADSI::User.exists?(system_user).should be_true
end
- it "should return nil with an unknown SID",
- :if => Puppet.features.microsoft_windows? do
+ it "should return nil with an unknown SID" do
bogus_sid = 'S-1-2-3-4'
# ensure that the underlying OS is queried here
- Puppet::Util::ADSI.unstub(:connect)
- Puppet::Util::ADSI::User.exists?(bogus_sid).should be_false
+ Puppet::Util::Windows::ADSI.unstub(:connect)
+ Puppet::Util::Windows::ADSI::User.exists?(bogus_sid).should be_false
end
it "should be able to delete a user" do
connection.expects(:Delete).with('user', username)
- Puppet::Util::ADSI::User.delete(username)
+ Puppet::Util::Windows::ADSI::User.delete(username)
end
it "should return an enumeration of IADsUser wrapped objects" do
- Puppet::Util::ADSI.stubs(:sid_uri_safe).returns(nil)
+ Puppet::Util::Windows::ADSI.stubs(:sid_uri_safe).returns(nil)
name = 'Administrator'
wmi_users = [stub('WMI', :name => name)]
- Puppet::Util::ADSI.expects(:execquery).with('select name from win32_useraccount where localaccount = "TRUE"').returns(wmi_users)
+ Puppet::Util::Windows::ADSI.expects(:execquery).with('select name from win32_useraccount where localaccount = "TRUE"').returns(wmi_users)
native_user = stub('IADsUser')
homedir = "C:\\Users\\#{name}"
native_user.expects(:Get).with('HomeDirectory').returns(homedir)
- Puppet::Util::ADSI.expects(:connect).with("WinNT://./#{name},user").returns(native_user)
+ Puppet::Util::Windows::ADSI.expects(:connect).with("WinNT://./#{name},user").returns(native_user)
- users = Puppet::Util::ADSI::User.to_a
+ users = Puppet::Util::Windows::ADSI::User.to_a
users.length.should == 1
users[0].name.should == name
users[0]['HomeDirectory'].should == homedir
@@ -165,7 +170,7 @@ describe Puppet::Util::ADSI do
describe "an instance" do
let(:adsi_user) { stub('user', :objectSID => []) }
let(:sid) { stub(:account => username, :domain => 'testcomputername') }
- let(:user) { Puppet::Util::ADSI::User.new(username, adsi_user) }
+ let(:user) { Puppet::Util::Windows::ADSI::User.new(username, adsi_user) }
it "should provide its groups as a list of names" do
names = ["group1", "group2"]
@@ -178,8 +183,8 @@ describe Puppet::Util::ADSI do
end
it "should be able to test whether a given password is correct" do
- Puppet::Util::ADSI::User.expects(:logon).with(username, 'pwdwrong').returns(false)
- Puppet::Util::ADSI::User.expects(:logon).with(username, 'pwdright').returns(true)
+ Puppet::Util::Windows::ADSI::User.expects(:logon).with(username, 'pwdwrong').returns(false)
+ Puppet::Util::Windows::ADSI::User.expects(:logon).with(username, 'pwdright').returns(true)
user.password_is?('pwdwrong').should be_false
user.password_is?('pwdright').should be_true
@@ -198,16 +203,16 @@ describe Puppet::Util::ADSI do
user.password = 'pwd'
end
- it "should generate the correct URI", :if => Puppet.features.microsoft_windows? do
- Puppet::Util::Windows::Security.stubs(:octet_string_to_sid_object).returns(sid)
+ it "should generate the correct URI" do
+ Puppet::Util::Windows::SID.stubs(:octet_string_to_sid_object).returns(sid)
user.uri.should == "WinNT://testcomputername/#{username},user"
end
- describe "when given a set of groups to which to add the user", :if => Puppet.features.microsoft_windows? do
+ describe "when given a set of groups to which to add the user" do
let(:groups_to_set) { 'group1,group2' }
before(:each) do
- Puppet::Util::Windows::Security.stubs(:octet_string_to_sid_object).returns(sid)
+ Puppet::Util::Windows::SID.stubs(:octet_string_to_sid_object).returns(sid)
user.expects(:groups).returns ['group2', 'group3']
end
@@ -219,9 +224,9 @@ describe Puppet::Util::ADSI do
group3 = stub 'group1'
group3.expects(:Remove).with("WinNT://testcomputername/#{username},user")
- Puppet::Util::ADSI.expects(:sid_uri).with(sid).returns("WinNT://testcomputername/#{username},user").twice
- Puppet::Util::ADSI.expects(:connect).with('WinNT://./group1,group').returns group1
- Puppet::Util::ADSI.expects(:connect).with('WinNT://./group3,group').returns group3
+ Puppet::Util::Windows::ADSI.expects(:sid_uri).with(sid).returns("WinNT://testcomputername/#{username},user").twice
+ Puppet::Util::Windows::ADSI.expects(:connect).with('WinNT://./group1,group').returns group1
+ Puppet::Util::Windows::ADSI.expects(:connect).with('WinNT://./group3,group').returns group3
user.set_groups(groups_to_set, false)
end
@@ -232,8 +237,8 @@ describe Puppet::Util::ADSI do
group1 = stub 'group1'
group1.expects(:Add).with("WinNT://testcomputername/#{username},user")
- Puppet::Util::ADSI.expects(:sid_uri).with(sid).returns("WinNT://testcomputername/#{username},user")
- Puppet::Util::ADSI.expects(:connect).with('WinNT://./group1,group').returns group1
+ Puppet::Util::Windows::ADSI.expects(:sid_uri).with(sid).returns("WinNT://testcomputername/#{username},user")
+ Puppet::Util::Windows::ADSI.expects(:connect).with('WinNT://./group1,group').returns group1
user.set_groups(groups_to_set, true)
end
@@ -242,50 +247,50 @@ describe Puppet::Util::ADSI do
end
end
- describe Puppet::Util::ADSI::Group do
+ describe Puppet::Util::Windows::ADSI::Group do
let(:groupname) { 'testgroup' }
describe "an instance" do
let(:adsi_group) { stub 'group' }
- let(:group) { Puppet::Util::ADSI::Group.new(groupname, adsi_group) }
+ let(:group) { Puppet::Util::Windows::ADSI::Group.new(groupname, adsi_group) }
let(:someone_sid){ stub(:account => 'someone', :domain => 'testcomputername')}
- it "should be able to add a member (deprecated)", :if => Puppet.features.microsoft_windows? do
- Puppet.expects(:deprecation_warning).with('Puppet::Util::ADSI::Group#add_members is deprecated; please use Puppet::Util::ADSI::Group#add_member_sids')
+ it "should be able to add a member (deprecated)" do
+ Puppet.expects(:deprecation_warning).with('Puppet::Util::Windows::ADSI::Group#add_members is deprecated; please use Puppet::Util::Windows::ADSI::Group#add_member_sids')
- Puppet::Util::Windows::Security.expects(:name_to_sid_object).with('someone').returns(someone_sid)
- Puppet::Util::ADSI.expects(:sid_uri).with(someone_sid).returns("WinNT://testcomputername/someone,user")
+ Puppet::Util::Windows::SID.expects(:name_to_sid_object).with('someone').returns(someone_sid)
+ Puppet::Util::Windows::ADSI.expects(:sid_uri).with(someone_sid).returns("WinNT://testcomputername/someone,user")
adsi_group.expects(:Add).with("WinNT://testcomputername/someone,user")
group.add_member('someone')
end
- it "should raise when adding a member that can't resolve to a SID (deprecated)", :if => Puppet.features.microsoft_windows? do
+ it "should raise when adding a member that can't resolve to a SID (deprecated)" do
expect {
group.add_member('foobar')
}.to raise_error(Puppet::Error, /Could not resolve username: foobar/)
end
- it "should be able to remove a member (deprecated)", :if => Puppet.features.microsoft_windows? do
- Puppet.expects(:deprecation_warning).with('Puppet::Util::ADSI::Group#remove_members is deprecated; please use Puppet::Util::ADSI::Group#remove_member_sids')
+ it "should be able to remove a member (deprecated)" do
+ Puppet.expects(:deprecation_warning).with('Puppet::Util::Windows::ADSI::Group#remove_members is deprecated; please use Puppet::Util::Windows::ADSI::Group#remove_member_sids')
- Puppet::Util::Windows::Security.expects(:name_to_sid_object).with('someone').returns(someone_sid)
- Puppet::Util::ADSI.expects(:sid_uri).with(someone_sid).returns("WinNT://testcomputername/someone,user")
+ Puppet::Util::Windows::SID.expects(:name_to_sid_object).with('someone').returns(someone_sid)
+ Puppet::Util::Windows::ADSI.expects(:sid_uri).with(someone_sid).returns("WinNT://testcomputername/someone,user")
adsi_group.expects(:Remove).with("WinNT://testcomputername/someone,user")
group.remove_member('someone')
end
- it "should raise when removing a member that can't resolve to a SID (deprecated)", :if => Puppet.features.microsoft_windows? do
+ it "should raise when removing a member that can't resolve to a SID (deprecated)" do
expect {
group.remove_member('foobar')
}.to raise_error(Puppet::Error, /Could not resolve username: foobar/)
end
- describe "should be able to use SID objects", :if => Puppet.features.microsoft_windows? do
- let(:system) { Puppet::Util::Windows::Security.name_to_sid_object('SYSTEM') }
+ describe "should be able to use SID objects" do
+ let(:system) { Puppet::Util::Windows::SID.name_to_sid_object('SYSTEM') }
it "to add a member" do
adsi_group.expects(:Add).with("WinNT://S-1-5-18")
@@ -310,7 +315,7 @@ describe Puppet::Util::ADSI do
group.members.should =~ names
end
- it "should be able to add a list of users to a group", :if => Puppet.features.microsoft_windows? do
+ it "should be able to add a list of users to a group" do
names = ['DOMAIN\user1', 'user2']
sids = [
stub(:account => 'user1', :domain => 'DOMAIN'),
@@ -319,14 +324,14 @@ describe Puppet::Util::ADSI do
]
# use stubbed objectSid on member to return stubbed SID
- Puppet::Util::Windows::Security.expects(:octet_string_to_sid_object).with([0]).returns(sids[0])
- Puppet::Util::Windows::Security.expects(:octet_string_to_sid_object).with([1]).returns(sids[1])
+ Puppet::Util::Windows::SID.expects(:octet_string_to_sid_object).with([0]).returns(sids[0])
+ Puppet::Util::Windows::SID.expects(:octet_string_to_sid_object).with([1]).returns(sids[1])
- Puppet::Util::Windows::Security.expects(:name_to_sid_object).with('user2').returns(sids[1])
- Puppet::Util::Windows::Security.expects(:name_to_sid_object).with('DOMAIN2\user3').returns(sids[2])
+ Puppet::Util::Windows::SID.expects(:name_to_sid_object).with('user2').returns(sids[1])
+ Puppet::Util::Windows::SID.expects(:name_to_sid_object).with('DOMAIN2\user3').returns(sids[2])
- Puppet::Util::ADSI.expects(:sid_uri).with(sids[0]).returns("WinNT://DOMAIN/user1,user")
- Puppet::Util::ADSI.expects(:sid_uri).with(sids[2]).returns("WinNT://DOMAIN2/user3,user")
+ Puppet::Util::Windows::ADSI.expects(:sid_uri).with(sids[0]).returns("WinNT://DOMAIN/user1,user")
+ Puppet::Util::Windows::ADSI.expects(:sid_uri).with(sids[2]).returns("WinNT://DOMAIN2/user3,user")
members = names.each_with_index.map{|n,i| stub(:Name => n, :objectSID => [i])}
adsi_group.expects(:Members).returns members
@@ -337,7 +342,7 @@ describe Puppet::Util::ADSI do
group.set_members(['user2', 'DOMAIN2\user3'])
end
- it "should raise an error when a username does not resolve to a SID", :if => Puppet.features.microsoft_windows? do
+ it "should raise an error when a username does not resolve to a SID" do
expect {
adsi_group.expects(:Members).returns []
group.set_members(['foobar'])
@@ -345,81 +350,79 @@ describe Puppet::Util::ADSI do
end
it "should generate the correct URI" do
- Puppet::Util::ADSI.stubs(:sid_uri_safe).returns(nil)
+ Puppet::Util::Windows::ADSI.stubs(:sid_uri_safe).returns(nil)
group.uri.should == "WinNT://./#{groupname},group"
end
end
it "should generate the correct URI" do
- Puppet::Util::ADSI.stubs(:sid_uri_safe).returns(nil)
- Puppet::Util::ADSI::Group.uri("people").should == "WinNT://./people,group"
+ Puppet::Util::Windows::ADSI.stubs(:sid_uri_safe).returns(nil)
+ Puppet::Util::Windows::ADSI::Group.uri("people").should == "WinNT://./people,group"
end
it "should be able to create a group" do
adsi_group = stub("adsi")
connection.expects(:Create).with('group', groupname).returns(adsi_group)
- Puppet::Util::ADSI::User.expects(:exists?).with(groupname).returns(false)
+ Puppet::Util::Windows::ADSI::User.expects(:exists?).with(groupname).returns(false)
- group = Puppet::Util::ADSI::Group.create(groupname)
+ group = Puppet::Util::Windows::ADSI::Group.create(groupname)
- group.should be_a(Puppet::Util::ADSI::Group)
+ group.should be_a(Puppet::Util::Windows::ADSI::Group)
group.native_group.should == adsi_group
end
it "should be able to confirm the existence of a group" do
- Puppet::Util::ADSI.stubs(:sid_uri_safe).returns(nil)
- Puppet::Util::ADSI.expects(:connect).with("WinNT://./#{groupname},group").returns connection
+ Puppet::Util::Windows::ADSI.stubs(:sid_uri_safe).returns(nil)
+ Puppet::Util::Windows::ADSI.expects(:connect).with("WinNT://./#{groupname},group").returns connection
- Puppet::Util::ADSI::Group.exists?(groupname).should be_true
+ Puppet::Util::Windows::ADSI::Group.exists?(groupname).should be_true
end
- it "should be able to confirm the existence of a group with a well-known SID",
- :if => Puppet.features.microsoft_windows? do
+ it "should be able to confirm the existence of a group with a well-known SID" do
service_group = Win32::Security::SID::Service
# ensure that the underlying OS is queried here
- Puppet::Util::ADSI.unstub(:connect)
- Puppet::Util::ADSI::Group.exists?(service_group).should be_true
+ Puppet::Util::Windows::ADSI.unstub(:connect)
+ Puppet::Util::Windows::ADSI::Group.exists?(service_group).should be_true
end
- it "should return nil with an unknown SID",
- :if => Puppet.features.microsoft_windows? do
+ it "should return nil with an unknown SID" do
bogus_sid = 'S-1-2-3-4'
# ensure that the underlying OS is queried here
- Puppet::Util::ADSI.unstub(:connect)
- Puppet::Util::ADSI::Group.exists?(bogus_sid).should be_false
+ Puppet::Util::Windows::ADSI.unstub(:connect)
+ Puppet::Util::Windows::ADSI::Group.exists?(bogus_sid).should be_false
end
it "should be able to delete a group" do
connection.expects(:Delete).with('group', groupname)
- Puppet::Util::ADSI::Group.delete(groupname)
+ Puppet::Util::Windows::ADSI::Group.delete(groupname)
end
it "should return an enumeration of IADsGroup wrapped objects" do
- Puppet::Util::ADSI.stubs(:sid_uri_safe).returns(nil)
+ Puppet::Util::Windows::ADSI.stubs(:sid_uri_safe).returns(nil)
name = 'Administrators'
wmi_groups = [stub('WMI', :name => name)]
- Puppet::Util::ADSI.expects(:execquery).with('select name from win32_group where localaccount = "TRUE"').returns(wmi_groups)
+ Puppet::Util::Windows::ADSI.expects(:execquery).with('select name from win32_group where localaccount = "TRUE"').returns(wmi_groups)
native_group = stub('IADsGroup')
native_group.expects(:Members).returns([stub(:Name => 'Administrator')])
- Puppet::Util::ADSI.expects(:connect).with("WinNT://./#{name},group").returns(native_group)
+ Puppet::Util::Windows::ADSI.expects(:connect).with("WinNT://./#{name},group").returns(native_group)
- groups = Puppet::Util::ADSI::Group.to_a
+ groups = Puppet::Util::Windows::ADSI::Group.to_a
groups.length.should == 1
groups[0].name.should == name
groups[0].members.should == ['Administrator']
end
end
- describe Puppet::Util::ADSI::UserProfile do
+ describe Puppet::Util::Windows::ADSI::UserProfile do
it "should be able to delete a user profile" do
connection.expects(:Delete).with("Win32_UserProfile.SID='S-A-B-C'")
- Puppet::Util::ADSI::UserProfile.delete('S-A-B-C')
+ Puppet::Util::Windows::ADSI::UserProfile.delete('S-A-B-C')
end
it "should warn on 2003" do
@@ -431,7 +434,7 @@ describe Puppet::Util::ADSI do
Exception occurred.")
Puppet.expects(:warning).with("Cannot delete user profile for 'S-A-B-C' prior to Vista SP1")
- Puppet::Util::ADSI::UserProfile.delete('S-A-B-C')
+ Puppet::Util::Windows::ADSI::UserProfile.delete('S-A-B-C')
end
end
end
diff --git a/spec/unit/util/windows/api_types_spec.rb b/spec/unit/util/windows/api_types_spec.rb
new file mode 100644
index 000000000..a1e1c76c9
--- /dev/null
+++ b/spec/unit/util/windows/api_types_spec.rb
@@ -0,0 +1,28 @@
+# encoding: UTF-8
+#!/usr/bin/env ruby
+
+require 'spec_helper'
+
+describe "FFI::MemoryPointer", :if => Puppet.features.microsoft_windows? do
+ context "read_wide_string" do
+ let (:string) { "foo_bar" }
+
+ it "should properly roundtrip a given string" do
+ read_string = nil
+ FFI::MemoryPointer.from_string_to_wide_string(string) do |ptr|
+ read_string = ptr.read_wide_string(string.length)
+ end
+
+ read_string.should == string
+ end
+
+ it "should return a given string in the default encoding" do
+ read_string = nil
+ FFI::MemoryPointer.from_string_to_wide_string(string) do |ptr|
+ read_string = ptr.read_wide_string(string.length)
+ end
+
+ read_string.encoding.should == Encoding.default_external
+ end
+ end
+end
diff --git a/spec/unit/util/windows/registry_spec.rb b/spec/unit/util/windows/registry_spec.rb
index 636ba0c93..ed52539a5 100755
--- a/spec/unit/util/windows/registry_spec.rb
+++ b/spec/unit/util/windows/registry_spec.rb
@@ -1,7 +1,6 @@
#! /usr/bin/env ruby
require 'spec_helper'
require 'puppet/util/windows'
-require 'puppet/util/windows/registry'
describe Puppet::Util::Windows::Registry, :if => Puppet::Util::Platform.windows? do
subject do
@@ -43,12 +42,14 @@ describe Puppet::Util::Windows::Registry, :if => Puppet::Util::Platform.windows?
yielded.should == subkey
end
- [described_class::KEY64, described_class::KEY32].each do |access|
- it "should open the key for read access 0x#{access.to_s(16)}" do
- mode = described_class::KEY_READ | access
- hkey.expects(:open).with(path, mode)
+ if Puppet::Util::Platform.windows?
+ [described_class::KEY64, described_class::KEY32].each do |access|
+ it "should open the key for read access 0x#{access.to_s(16)}" do
+ mode = described_class::KEY_READ | access
+ hkey.expects(:open).with(path, mode)
- subject.open(name, path, mode) {|reg| }
+ subject.open(name, path, mode) {|reg| }
+ end
end
end
diff --git a/spec/unit/util/windows/sid_spec.rb b/spec/unit/util/windows/sid_spec.rb
index 770512188..2748f13c6 100755
--- a/spec/unit/util/windows/sid_spec.rb
+++ b/spec/unit/util/windows/sid_spec.rb
@@ -4,12 +4,9 @@ require 'spec_helper'
describe "Puppet::Util::Windows::SID", :if => Puppet.features.microsoft_windows? do
if Puppet.features.microsoft_windows?
require 'puppet/util/windows'
- class SIDTester
- include Puppet::Util::Windows::SID
- end
end
- let(:subject) { SIDTester.new }
+ let(:subject) { Puppet::Util::Windows::SID }
let(:sid) { Win32::Security::SID::LocalSystem }
let(:invalid_sid) { 'bogus' }
let(:unknown_sid) { 'S-0-0-0' }
@@ -50,7 +47,7 @@ describe "Puppet::Util::Windows::SID", :if => Puppet.features.microsoft_windows?
expect {
invalid_octet = [1]
subject.octet_string_to_sid_object(invalid_octet)
- }.to raise_error(Win32::Security::SID::Error, /No mapping between account names and security IDs was done./)
+ }.to raise_error(SystemCallError, /No mapping between account names and security IDs was done./)
end
end
@@ -159,7 +156,7 @@ describe "Puppet::Util::Windows::SID", :if => Puppet.features.microsoft_windows?
it "should raise if the conversion fails" do
subject.expects(:string_to_sid_ptr).with(sid).
- raises(Puppet::Util::Windows::Error.new("Failed to convert string SID: #{sid}", Windows::Error::ERROR_ACCESS_DENIED))
+ raises(Puppet::Util::Windows::Error.new("Failed to convert string SID: #{sid}", Puppet::Util::Windows::Error::ERROR_ACCESS_DENIED))
expect {
subject.string_to_sid_ptr(sid) {|ptr| }
diff --git a/spec/unit/util/windows/string_spec.rb b/spec/unit/util/windows/string_spec.rb
index 60f7e6449..5c6473e70 100644
--- a/spec/unit/util/windows/string_spec.rb
+++ b/spec/unit/util/windows/string_spec.rb
@@ -50,5 +50,9 @@ describe "Puppet::Util::Windows::String", :if => Puppet.features.microsoft_windo
it "should convert an UTF-32BE string" do
converts_to_wide_string("bob\u00E8".encode(Encoding::UTF_32BE))
end
+
+ it "should return a nil when given a nil" do
+ wide_string(nil).should == nil
+ end
end
end
diff --git a/spec/unit/util/zaml_spec.rb b/spec/unit/util/zaml_spec.rb
index 56c5cb719..b239b4a84 100755
--- a/spec/unit/util/zaml_spec.rb
+++ b/spec/unit/util/zaml_spec.rb
@@ -69,7 +69,11 @@ describe "Pure ruby yaml implementation" do
end
it "serializes a time in UTC" do
- pending("not supported on Windows", :if => Puppet.features.microsoft_windows? && RUBY_VERSION[0,3] == '1.8') do
+ bad_rubies =
+ RUBY_VERSION[0,3] == '1.8' ||
+ RUBY_VERSION[0,3] == '2.0' && RUBY_PLATFORM == 'i386-mingw32'
+
+ pending("not supported on Windows", :if => Puppet.features.microsoft_windows? && bad_rubies) do
the_time_in("Europe/London").should be_equivalent_to(the_time_in_yaml_offset_by("+00:00"))
end
end
diff --git a/tasks/benchmark.rake b/tasks/benchmark.rake
index 7456c5c0a..69c0fc27c 100644
--- a/tasks/benchmark.rake
+++ b/tasks/benchmark.rake
@@ -6,6 +6,14 @@ namespace :benchmark do
def generate_scenario_tasks(location, name)
desc File.read(File.join(location, 'description'))
task name => "#{name}:run"
+ # Load a BenchmarkerTask to handle config of the benchmark
+ task_handler_file = File.expand_path(File.join(location, 'benchmarker_task.rb'))
+ if File.exist?(task_handler_file)
+ require task_handler_file
+ run_args = BenchmarkerTask.run_args
+ else
+ run_args = []
+ end
namespace name do
task :setup do
@@ -27,20 +35,20 @@ namespace :benchmark do
end
desc "Run the #{name} scenario."
- task :run => :generate do
+ task :run, [*run_args] => :generate do |_, args|
format = if RUBY_VERSION =~ /^1\.8/
Benchmark::FMTSTR
else
Benchmark::FORMAT
end
-
report = []
+ details = []
Benchmark.benchmark(Benchmark::CAPTION, 10, format, "> total:", "> avg:") do |b|
times = []
ENV['ITERATIONS'].to_i.times do |i|
start_time = Time.now.to_i
times << b.report("Run #{i + 1}") do
- @benchmark.run
+ details << @benchmark.run(args)
end
report << [to_millis(start_time), to_millis(times.last.real), 200, true, name]
end
@@ -53,14 +61,41 @@ namespace :benchmark do
write_csv("#{name}.samples",
%w{timestamp elapsed responsecode success name},
report)
+
+ # report details, if any were produced
+ if details[0].is_a?(Array) && details[0][0].is_a?(Benchmark::Tms)
+ # assume all entries are Tms if the first is
+ # turn each into a hash of label => tms (since labels are lost when doing arithmetic on Tms)
+ hashed = details.reduce([]) do |memo, measures|
+ memo << measures.reduce({}) {|memo2, measure| memo2[measure.label] = measure; memo2}
+ memo
+ end
+ # sum across all hashes
+ result = {}
+
+ hashed_totals = hashed.reduce {|memo, h| memo.merge(h) {|k, old, new| old + new }}
+ # average the totals
+ hashed_totals.keys.each {|k| hashed_totals[k] /= details.length }
+ min_width = 14
+ max_width = (hashed_totals.keys.map(&:length) << min_width).max
+ puts "\n"
+ puts sprintf("%2$*1$s %3$s", -max_width, 'Details (avg)', " user system total real")
+ puts "-" * (46 + max_width)
+ hashed_totals.sort.each {|k,v| puts sprintf("%2$*1$s %3$s", -max_width, k, v.format) }
+ end
end
desc "Profile a single run of the #{name} scenario."
- task :profile => :generate do
+ task :profile, [:warm_up_runs, *run_args] => :generate do |_, args|
+ warm_up_runs = (args[:warm_up_runs] || '0').to_i
+ warm_up_runs.times do
+ @benchmark.run(args)
+ end
+
require 'ruby-prof'
result = RubyProf.profile do
- @benchmark.run
+ @benchmark.run(args)
end
printer = RubyProf::CallTreePrinter.new(result)
diff --git a/tasks/parser.rake b/tasks/parser.rake
index 73167f685..c6993d602 100644
--- a/tasks/parser.rake
+++ b/tasks/parser.rake
@@ -1,5 +1,19 @@
-
-desc "Generate the parser"
+desc "Generate the 3.x 'current' parser"
task :gen_parser do
%x{racc -olib/puppet/parser/parser.rb lib/puppet/parser/grammar.ra}
end
+
+desc "Generate the 4.x 'future' parser"
+task :gen_eparser do
+ %x{racc -olib/puppet/pops/parser/eparser.rb lib/puppet/pops/parser/egrammar.ra}
+end
+
+desc "Generate the 4.x 'future' parser with egrammar.output"
+task :gen_eparser_output do
+ %x{racc -v -olib/puppet/pops/parser/eparser.rb lib/puppet/pops/parser/egrammar.ra}
+end
+
+desc "Generate the 4.x 'future' parser with debugging output"
+task :gen_eparser_debug do
+ %x{racc -t -olib/puppet/pops/parser/eparser.rb lib/puppet/pops/parser/egrammar.ra}
+end
diff --git a/tasks/yard.rake b/tasks/yard.rake
index d513be771..ad5ece076 100644
--- a/tasks/yard.rake
+++ b/tasks/yard.rake
@@ -54,6 +54,6 @@ begin
end
rescue LoadError => e
if verbose
- puts "Document generation not available without yard. #{e.message}"
+ STDERR.puts "Document generation not available without yard. #{e.message}"
end
end