pax_global_header00006660000000000000000000000064135775510730014527gustar00rootroot0000000000000052 comment=de5381eecab895b3a2a5c8f7a211e928300f018c mixlib-cli-2.1.6/000077500000000000000000000000001357755107300135665ustar00rootroot00000000000000mixlib-cli-2.1.6/.expeditor/000077500000000000000000000000001357755107300156475ustar00rootroot00000000000000mixlib-cli-2.1.6/.expeditor/config.yml000066400000000000000000000026741357755107300176500ustar00rootroot00000000000000# Documentation available at https://expeditor.chef.io/docs/getting-started/ --- # Slack channel in Chef Software slack to send notifications about build failures, etc slack: notify_channel: chef-found-notify # This publish is triggered by the `built_in:publish_rubygems` artifact_action. rubygems: - mixlib-cli github: # This deletes the GitHub PR branch after successfully merged into the release branch delete_branch_on_merge: true # The tag format to use (e.g. v1.0.0) version_tag_format: "v{{version}}" # allow bumping the minor release via label minor_bump_labels: - "Expeditor: Bump Version Minor" # allow bumping the major release via label major_bump_labels: - "Expeditor: Bump Version Major" changelog: rollup_header: Changes not yet released to rubygems.org # These actions are taken, in order they are specified, anytime a Pull Request is merged. merge_actions: - built_in:bump_version: ignore_labels: - "Expeditor: Skip Version Bump" - "Expeditor: Skip All" - bash:.expeditor/update_version.sh: only_if: built_in:bump_version - built_in:update_changelog: ignore_labels: - "Expeditor: Skip Changelog" - "Expeditor: Skip All" - built_in:build_gem: only_if: built_in:bump_version promote: actions: - built_in:rollover_changelog - built_in:publish_rubygems pipelines: - verify: description: Pull Request validation tests public: true mixlib-cli-2.1.6/.expeditor/run_linux_tests.sh000077500000000000000000000023451357755107300214570ustar00rootroot00000000000000#!/bin/bash # # This script runs a passed in command, but first setups up the bundler caching on the repo set -ue export USER="root" echo "--- dependencies" export LANG=C.UTF-8 LANGUAGE=C.UTF-8 S3_URL="s3://public-cd-buildkite-cache/${BUILDKITE_PIPELINE_SLUG}/${BUILDKITE_LABEL}" pull_s3_file() { aws s3 cp "${S3_URL}/$1" "$1" || echo "Could not pull $1 from S3" } push_s3_file() { if [ -f "$1" ]; then aws s3 cp "$1" "${S3_URL}/$1" || echo "Could not push $1 to S3 for caching." fi } apt-get update -y apt-get install awscli -y echo "--- bundle install" pull_s3_file "bundle.tar.gz" pull_s3_file "bundle.sha256" if [ -f bundle.tar.gz ]; then tar -xzf bundle.tar.gz fi if [ -n "${RESET_BUNDLE_CACHE:-}" ]; then rm bundle.sha256 fi bundle config --local path vendor/bundle bundle install --jobs=7 --retry=3 echo "--- bundle cache" if test -f bundle.sha256 && shasum --check bundle.sha256 --status; then echo "Bundled gems have not changed. Skipping upload to s3" else echo "Bundled gems have changed. Uploading to s3" shasum -a 256 Gemfile.lock > bundle.sha256 tar -czf bundle.tar.gz vendor/ push_s3_file bundle.tar.gz push_s3_file bundle.sha256 fi echo "+++ bundle exec task" bundle exec $1 mixlib-cli-2.1.6/.expeditor/update_version.sh000077500000000000000000000007301357755107300212350ustar00rootroot00000000000000#!/bin/sh # # After a PR merge, Chef Expeditor will bump the PATCH version in the VERSION file. # It then executes this file to update any other files/components with that new version. # set -evx VERSION=$(cat VERSION) sed -i -r "s/^(\\s*)VERSION = \".+\"/\\1VERSION = \"$VERSION\"/" lib/mixlib/cli/version.rb # Once Expeditor finshes executing this script, it will commit the changes and push # the commit as a new tag corresponding to the value in the VERSION file. mixlib-cli-2.1.6/.expeditor/verify.pipeline.yml000066400000000000000000000016301357755107300215020ustar00rootroot00000000000000--- expeditor: defaults: buildkite: timeout_in_minutes: 30 steps: - label: run-lint-and-specs-ruby-2.4 command: - .expeditor/run_linux_tests.sh rake expeditor: executor: docker: image: ruby:2.4-buster - label: run-lint-and-specs-ruby-2.5 command: - .expeditor/run_linux_tests.sh rake expeditor: executor: docker: image: ruby:2.5-buster - label: run-lint-and-specs-ruby-2.6 command: - .expeditor/run_linux_tests.sh rake expeditor: executor: docker: image: ruby:2.6-buster - label: run-lint-and-specs-ruby-2.7rc command: - .expeditor/run_linux_tests.sh rake expeditor: executor: docker: image: ruby:2.7-rc-buster - label: run-specs-windows command: - bundle install --jobs=7 --retry=3 --without docs debug - bundle exec rake expeditor: executor: docker: host_os: windows mixlib-cli-2.1.6/.github/000077500000000000000000000000001357755107300151265ustar00rootroot00000000000000mixlib-cli-2.1.6/.github/CODEOWNERS000066400000000000000000000003741357755107300165250ustar00rootroot00000000000000# Order is important. The last matching pattern has the most precedence. * @chef/chef-foundation-owners @chef/chef-foundation-approvers @chef/chef-foundation-reviewers .expeditor/ @chef/jex-team *.md @chef/docs-team mixlib-cli-2.1.6/.github/ISSUE_TEMPLATE/000077500000000000000000000000001357755107300173115ustar00rootroot00000000000000mixlib-cli-2.1.6/.github/ISSUE_TEMPLATE/BUG_TEMPLATE.md000066400000000000000000000011651357755107300215460ustar00rootroot00000000000000--- name: � Bug Report about: If something isn't working as expected �. labels: "Status: Untriaged, Type: Bug" --- # Version: [Version of the project installed] # Environment: [Details about the environment such as the Operating System, cookbook details, etc...] # Scenario: [What you are trying to achieve and you can't?] # Steps to Reproduce: [If you are filing an issue what are the things we need to do in order to repro your problem?] # Expected Result: [What are you expecting to happen as the consequence of above reproduction steps?] # Actual Result: [What actually happens after the reproduction steps?] mixlib-cli-2.1.6/.github/ISSUE_TEMPLATE/DESIGN_PROPOSAL.md000066400000000000000000000023721357755107300221270ustar00rootroot00000000000000--- name: Design Proposal about: I have a significant change I would like to propose and discuss before starting labels: "Status: Untriaged, Type: Design Proposal" --- ### When a Change Needs a Design Proposal A design proposal should be opened any time a change meets one of the following qualifications: - Significantly changes the user experience of a project in a way that impacts users. - Significantly changes the underlying architecture of the project in a way that impacts other developers. - Changes the development or testing process of the project such as a change of CI systems or test frameworks. ### Why We Use This Process - Allows all interested parties (including any community member) to discuss large impact changes to a project. - Serves as a durable paper trail for discussions regarding project architecture. - Forces design discussions to occur before PRs are created. - Reduces PR refactoring and rejected PRs. --- ## Motivation ## Specification ## Downstream Impact mixlib-cli-2.1.6/.github/ISSUE_TEMPLATE/ENHANCEMENT_REQUEST_TEMPLATE.md000066400000000000000000000014071357755107300240650ustar00rootroot00000000000000--- name: 🚀 Enhancement Request about: I have a suggestion (and may want to implement it 🙂)! labels: "Status: Untriaged" --- ### Describe the Enhancement: ### Describe the Need: ### Current Alternative ### Can We Help You Implement This?: mixlib-cli-2.1.6/.github/ISSUE_TEMPLATE/SUPPORT_QUESTION.md000066400000000000000000000006601357755107300223400ustar00rootroot00000000000000--- name: 🤗 Support Question about: If you have a question 💬, please check out our Slack! --- We use GitHub issues to track bugs and feature requests. If you need help please post to our Mailing List or join the Chef Community Slack. * Chef Community Slack at http://community-slack.chef.io/. * Chef Mailing List https://discourse.chef.io/ Support issues opened here will be closed and redirected to Slack or Discourse. mixlib-cli-2.1.6/.github/lock.yml000066400000000000000000000000221357755107300165730ustar00rootroot00000000000000daysUntilLock: 60 mixlib-cli-2.1.6/.gitignore000066400000000000000000000003671357755107300155640ustar00rootroot00000000000000_yardoc .bundle .config .DS_Store .idea .rake_tasks~ .rspec .ruby-version .rvmrc .yardoc .yardopts *.gem *.rbc *.sw? bin/ coverage doc Gemfile.local Gemfile.lock InstalledFiles lib/bundler/man pkg spec/reports test/tmp test/version_tmp tmp vendor mixlib-cli-2.1.6/CHANGELOG.md000066400000000000000000000132411357755107300154000ustar00rootroot00000000000000# mixlib-cli Changelog ## [v2.1.6](https://github.com/chef/mixlib-cli/tree/v2.1.6) (2019-12-22) #### Merged Pull Requests - Substitute require for require_relative [#76](https://github.com/chef/mixlib-cli/pull/76) ([tas50](https://github.com/tas50)) ### Changes not yet released to rubygems.org #### Merged Pull Requests - Substitute require for require_relative [#76](https://github.com/chef/mixlib-cli/pull/76) ([tas50](https://github.com/tas50)) - Test on Ruby 2.7 + random testing improvements [#75](https://github.com/chef/mixlib-cli/pull/75) ([tas50](https://github.com/tas50)) - Add windows PR testing with Buildkite [#73](https://github.com/chef/mixlib-cli/pull/73) ([tas50](https://github.com/tas50)) - Fix chef-style [#71](https://github.com/chef/mixlib-cli/pull/71) ([vsingh-msys](https://github.com/vsingh-msys)) - Use our standard rakefile [#68](https://github.com/chef/mixlib-cli/pull/68) ([tas50](https://github.com/tas50)) ## [2.1.1](https://github.com/chef/mixlib-cli/tree/2.1.1) (2019-06-10) #### Merged Pull Requests - Don't explode when there are unknown keys in 'config' [#66](https://github.com/chef/mixlib-cli/pull/66) ([marcparadise](https://github.com/marcparadise)) ## [2.1.0](https://github.com/chef/mixlib-cli/tree/2.1.0) (2019-06-07) #### Merged Pull Requests - Setup BuildKite for PR testing [#61](https://github.com/chef/mixlib-cli/pull/61) ([tas50](https://github.com/tas50)) - Disable Travis testing & Update codeowners [#62](https://github.com/chef/mixlib-cli/pull/62) ([tas50](https://github.com/tas50)) - Fix gem homepage url [#64](https://github.com/chef/mixlib-cli/pull/64) ([tsub](https://github.com/tsub)) - [MIXLIB-CLI-63] Add deprecated_option support [#65](https://github.com/chef/mixlib-cli/pull/65) ([marcparadise](https://github.com/marcparadise)) ## [v2.0.6](https://github.com/chef/mixlib-cli/tree/v2.0.6) (2019-05-14) #### Merged Pull Requests - Add additional github templates and update codeowners [#58](https://github.com/chef/mixlib-cli/pull/58) ([tas50](https://github.com/tas50)) - Improve the --help text output of 'in:' [#59](https://github.com/chef/mixlib-cli/pull/59) ([btm](https://github.com/btm)) - Print out human readable lists of allowed CLI options [#60](https://github.com/chef/mixlib-cli/pull/60) ([tas50](https://github.com/tas50)) ## [v2.0.3](https://github.com/chef/mixlib-cli/tree/v2.0.3) (2019-03-20) #### Merged Pull Requests - fix global state pollution issues across examples [#54](https://github.com/chef/mixlib-cli/pull/54) ([lamont-granquist](https://github.com/lamont-granquist)) - Add back support for Ruby 2.4 [#56](https://github.com/chef/mixlib-cli/pull/56) ([tas50](https://github.com/tas50)) ## [v2.0.1](https://github.com/chef/mixlib-cli/tree/v2.0.1) (2019-01-04) #### Merged Pull Requests - Don't ship the test files in the gem artifact [#51](https://github.com/chef/mixlib-cli/pull/51) ([tas50](https://github.com/tas50)) ## [v2.0.0](https://github.com/chef/mixlib-cli/tree/v2.0.0) (2019-01-04) #### Merged Pull Requests - remove hashrockets syntax [#43](https://github.com/chef/mixlib-cli/pull/43) ([lamont-granquist](https://github.com/lamont-granquist)) - Remove require rubygems [#44](https://github.com/chef/mixlib-cli/pull/44) ([tas50](https://github.com/tas50)) - Update testing and contributing boilerplate [#45](https://github.com/chef/mixlib-cli/pull/45) ([tas50](https://github.com/tas50)) - More testing / release boilerplate [#46](https://github.com/chef/mixlib-cli/pull/46) ([tas50](https://github.com/tas50)) - Update codeowners and add github PR template [#47](https://github.com/chef/mixlib-cli/pull/47) ([tas50](https://github.com/tas50)) - Lint the example code [#49](https://github.com/chef/mixlib-cli/pull/49) ([tas50](https://github.com/tas50)) - update travis, drop ruby < 2.5, major version bump [#52](https://github.com/chef/mixlib-cli/pull/52) ([lamont-granquist](https://github.com/lamont-granquist)) - actually do the major version bump [#53](https://github.com/chef/mixlib-cli/pull/53) ([lamont-granquist](https://github.com/lamont-granquist)) ## 1.7.0 - Support two-argument procs for reducer style ## 1.6.0 - Properly pass options during inheritance - Added option key ':in' to specify that option value should be included in the given list - Fixed ruby-warning "instance variable @{ivar} not initialized". - [Kenichi Kamiya](https://github.com/kachick) - Documented CLI arguments. - [C Chamblin](https://github.com/chamblin) - Added rake, rdoc, and rspec and development dependencies - Removed the contributions.md document and merged it with the changelog - Updated code to comply with chefstyle style guidelines - Fixed a missing comma from an example in the readme - Ship the Gemfile so that tests can run from the Gem ## 1.5.0 - Added an API to access option parser without parsing options - Added this changelog and a contributions document - Documented how to use cli_arguments ## 1.4.0 - Added cli_arguments--remaining arguments after stripping CLI options - Add Travis and Bundler support ## 1.3.0 - Added the ability to optionally store default values separately - Added comments documenting the primary interfaces - Fix mixlib-cli to work with bundler in Ruby 1.9.2 ## 1.2.2 - :required works, and we now support Ruby-style boolean option negation (e.g. '--no-cookie' will set 'cookie' to false if the option is boolean) - The repo now includes a Gemspec file - Jeweler is no longer a dependency ## 1.2.0 We no longer destructively manipulate ARGV.mixlib-cli-2.1.6/CODE_OF_CONDUCT.md000066400000000000000000000001331357755107300163620ustar00rootroot00000000000000Please refer to the Chef Community Code of Conduct at https://www.chef.io/code-of-conduct/ mixlib-cli-2.1.6/CONTRIBUTING.md000066400000000000000000000001111357755107300160100ustar00rootroot00000000000000Please refer to https://github.com/chef/chef/blob/master/CONTRIBUTING.md mixlib-cli-2.1.6/Gemfile000066400000000000000000000004361357755107300150640ustar00rootroot00000000000000source "https://rubygems.org" gemspec group :docs do gem "github-markup" gem "redcarpet" gem "yard" end group :test do gem "chefstyle" gem "rspec", "~> 3.0" gem "rake" end group :debug do gem "pry" gem "pry-byebug" gem "pry-stack_explorer" gem "rb-readline" end mixlib-cli-2.1.6/LICENSE000066400000000000000000000251421357755107300145770ustar00rootroot00000000000000 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. mixlib-cli-2.1.6/NOTICE000066400000000000000000000014701357755107300144740ustar00rootroot00000000000000Mixin::CLI NOTICE ================= Developed at Chef (http://www.chef.io). * Copyright 2009-2018, Chef Software, Inc. Mixin::CLI incorporates code from Chef. The Chef notice file follows: Chef NOTICE =========== Developed at Chef (http://www.chef.io). Contributors and Copyright holders: * Copyright 2008, Adam Jacob * Copyright 2008, Arjuna Christensen * Copyright 2008, Bryan McLellan * Copyright 2008, Ezra Zygmuntowicz * Copyright 2009, Sean Cribbs * Copyright 2009, Christopher Brown * Copyright 2009, Thom May Chef incorporates code modified from Open4 (http://www.codeforpeople.com/lib/ruby/open4/), which was written by Ara T. Howard. mixlib-cli-2.1.6/README.md000066400000000000000000000161421357755107300150510ustar00rootroot00000000000000# Mixlib::CLI [![Build Status](https://badge.buildkite.com/5b595abc5c79a69fa4da5aeb14efd8e9104ec3a4ca53fc904a.svg?branch=master)](https://buildkite.com/chef-oss/chef-mixlib-cli-master-verify) [![Gem Version](https://badge.fury.io/rb/mixlib-cli.svg)](https://badge.fury.io/rb/mixlib-cli) **Umbrella Project**: [Chef Foundation](https://github.com/chef/chef-oss-practices/blob/master/projects/chef-foundation.md) **Project State**: [Active](https://github.com/chef/chef-oss-practices/blob/master/repo-management/repo-states.md#active) **Issues [Response Time Maximum](https://github.com/chef/chef-oss-practices/blob/master/repo-management/repo-states.md)**: 14 days **Pull Request [Response Time Maximum](https://github.com/chef/chef-oss-practices/blob/master/repo-management/repo-states.md)**: 14 days Mixlib::CLI provides a class-based command line option parsing object, like the one used in Chef, Ohai and Relish. To use in your project: ```ruby require "mixlib/cli" class MyCLI include Mixlib::CLI option :config_file, short: "-c CONFIG", long: "--config CONFIG", default: "config.rb", description: "The configuration file to use" option :log_level, short: "-l LEVEL", long: "--log_level LEVEL", description: "Set the log level (debug, info, warn, error, fatal)", required: true, in: [:debug, :info, :warn, :error, :fatal], proc: Proc.new { |l| l.to_sym } option :help, short: "-h", long: "--help", description: "Show this message", on: :tail, boolean: true, show_options: true, exit: 0 end # ARGV = [ '-c', 'foo.rb', '-l', 'debug' ] cli = MyCLI.new cli.parse_options cli.config[:config_file] # 'foo.rb' cli.config[:log_level] # :debug ``` If you are using this in conjunction with Mixlib::Config, you can do something like this (building on the above definition): ```ruby class MyConfig extend(Mixlib::Config) log_level :info config_file "default.rb" end class MyCLI def run(argv = ARGV) parse_options(argv) MyConfig.merge!(config) end end c = MyCLI.new # ARGV = [ '-l', 'debug' ] c.run MyConfig[:log_level] # :debug ``` For supported arguments to `option`, see the function documentation in [lib/mixlib/cli.rb](lib/mixlib/cli.rb). If you need access to the leftover options that aren't captured in the config, you can get at them through +cli_arguments+ (referring to the above definition of MyCLI). ```ruby # ARGV = [ '-c', 'foo.rb', '-l', 'debug', 'file1', 'file2', 'file3' ] cli = MyCLI.new cli.parse_options cli.cli_arguments # [ 'file1', 'file2', 'file3' ] ``` ## Deprecating CLI Options mixlib-cli 2.1.0 and later supports declaring options as deprecated. Using a deprecated option will result in a warning message being displayed. If a deprecated flag is supplied, its value is assigned to the replacement flag. You can control this assignment by specifying a `value_mapper` function in the arguments (see example below, and function docs) Usage notes (see docs for arguments to `Mixlib::CLI::ClassMethods#deprecated_option` for more): * Define deprecated items last, after all non-deprecated items have been defined. You will see errors if your deprecated item references a `replacement` that hasn't been defined yet. * deprecated\_option only takes a subset of 'option' configuration. You can only specify `short`, `long` - these should map to the short/long values of the option from before its deprecation. * item description will automatically be generated along the lines of "-f/--flag is deprecated. Use -n/--new-flag instead." * if the `replacement` argument is not given, item description will look like "-f/--flag is deprecated and will be removed in a future release" ### Example Given the following example: ```ruby # mycli.rb class MyCLI include Mixlib::CLI option :arg_not_required, description: "This takes no argument.", long: "--arg-not-required", short: "-n" option :arg_required, description: "This takes an argument.", long: "--arg-required ARG", short: "-a", in: ["a", "b", "c"] deprecated_option :dep_one, short: "-1", long: "--dep-one", # this deprecated option maps to the '--arg-not-required' option: replacement: :arg_not_required, # Do not keep 'dep_one' in `config` after parsing. keep: false deprecated_option :dep_two, short: "-2", long: "--dep-two ARG", replacement: :arg_required, # will map the given value to a valid value for `--arg-required`. value_mapper: Proc.new { |arg| case arg when "q"; "invalid" # We'll use this to show validation still works when "z"; "a" when "x"; "b" else "c" end } end c = MyCLI.new() c.parse_options puts "arg_required: #{c.config[:arg_required]}" if c.config.key? :arg_required puts "arg_not_required: #{c.config[:arg_not_required]}" if c.config.key? :arg_not_required puts "dep_one: #{c.config[:dep_one]}" if c.config.key? :dep_one puts "dep_two: #{c.config[:dep_two]}" if c.config.key? :dep_two ``` In this example, --dep-one will be used. Note that dep_one will not have a value of its own in `options` because `keep: false` was given to the deprecated option. ```bash $ ruby mycli.rb --dep-one -1/--dep-one: This flag is deprecated. Use -n/--arg-not-required instead arg_not_required: true ``` In this example, the value provided to dep-two will be converted to a value that --arg-required will accept,a nd populate `:arg\_required` with ```bash $ ruby mycli.rb --dep-two z # 'q' maps to 'invalid' in the value_mapper proc above -2/--dep-two: This flag is deprecated. Use -a/--arg-required instead. arg_required: a # The value is mapped to its replacement using the function provided. dep_two: z # the deprected value is kept by default ``` In this example, the value provided to dep-two will be converted to a value that --arg-required will reject, showing how content rules are applied even when the input is coming from a deprecated option: ```bash $ ruby mycli.rb --dep-two q -2/--dep-two: This flag is deprecated. Use -a/--arg-required instead. -a/--arg-required: invalid is not one of the allowed values: 'a', 'b', or 'c' ``` ## Documentation Class and module documentation is maintained using YARD. You can generate it by running: ``` rake docs ``` You can serve them locally with live refresh using: ``` bundle exec yard server --reload ``` ## Contributing For information on contributing to this project please see our [Contributing Documentation](https://github.com/chef/chef/blob/master/CONTRIBUTING.md) ## License & Copyright - Copyright:: Copyright (c) 2008-2018 Chef Software, Inc. - License:: Apache License, Version 2.0 ```text 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. ``` mixlib-cli-2.1.6/Rakefile000066400000000000000000000017351357755107300152410ustar00rootroot00000000000000require "bundler/gem_tasks" begin require "rspec/core/rake_task" RSpec::Core::RakeTask.new do |t| t.pattern = "spec/**/*_spec.rb" end rescue LoadError desc "rspec is not installed, this task is disabled" task :spec do abort "rspec is not installed. bundle install first to make sure all dependencies are installed." end end begin require "chefstyle" require "rubocop/rake_task" desc "Run Chefstyle tests" RuboCop::RakeTask.new(:style) do |task| task.options += ["--display-cop-names", "--no-color"] end rescue LoadError puts "chefstyle gem is not installed. bundle install first to make sure all dependencies are installed." end begin require "yard" YARD::Rake::YardocTask.new(:docs) rescue LoadError puts "yard is not available. bundle install first to make sure all dependencies are installed." end task :console do require "irb" require "irb/completion" require "mixlib/cli" ARGV.clear IRB.start end task default: %i{spec style} mixlib-cli-2.1.6/VERSION000066400000000000000000000000051357755107300146310ustar00rootroot000000000000002.1.6mixlib-cli-2.1.6/lib/000077500000000000000000000000001357755107300143345ustar00rootroot00000000000000mixlib-cli-2.1.6/lib/mixlib/000077500000000000000000000000001357755107300156205ustar00rootroot00000000000000mixlib-cli-2.1.6/lib/mixlib/cli.rb000066400000000000000000000433411357755107300167210ustar00rootroot00000000000000# # Author:: Adam Jacob () # Copyright:: Copyright (c) 2008-2019 Chef Software, Inc. # License:: Apache License, Version 2.0 # # 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. # require "optparse" require_relative "cli/formatter" module Mixlib # == Mixlib::CLI # Adds a DSL for defining command line options and methods for parsing those # options to the including class. # # Mixlib::CLI does some setup in #initialize, so the including class must # call `super()` if it defines a custom initializer. # # === DSL # When included, Mixlib::CLI also extends the including class with its # ClassMethods, which define the DSL. The primary methods of the DSL are # ClassMethods#option, which defines a command line option; # ClassMethods#banner, which defines the "usage" banner; # and ClassMethods#deprecated_option, which defines a deprecated command-line option. # # === Parsing # Command line options are parsed by calling the instance method # #parse_options. After calling this method, the attribute #config will # contain a hash of `:option_name => value` pairs. module CLI module InheritMethods def inherited(receiver) receiver.options = deep_dup(options) receiver.extend(Mixlib::CLI::InheritMethods) end # object:: Instance to clone # This method will return a "deep clone" of the provided # `object`. If the provided `object` is an enumerable type the # contents will be iterated and cloned as well. def deep_dup(object) cloned_object = object.respond_to?(:dup) ? object.dup : object if cloned_object.is_a?(Enumerable) if cloned_object.is_a?(Hash) new_hash = cloned_object.class.new cloned_object.each do |key, value| cloned_key = deep_dup(key) cloned_value = deep_dup(value) new_hash[cloned_key] = cloned_value end cloned_object.replace(new_hash) else cloned_object.map! do |shallow_instance| deep_dup(shallow_instance) end end end cloned_object rescue TypeError # Symbol will happily provide a `#dup` method even though # attempts to clone it will result in an exception (atoms!). # So if we run into an issue of TypeErrors, just return the # original object as we gave our "best effort" object end end module ClassMethods # When this setting is set to +true+, default values supplied to the # mixlib-cli DSL will be stored in a separate Hash def use_separate_default_options(true_or_false) @separate_default_options = true_or_false end def use_separate_defaults? @separate_default_options ||= false end # Add a command line option. # # === Parameters # name:: The name of the option to add # args:: A hash of arguments for the option, specifying how it should be parsed. # Supported arguments: # :short - The short option, just like from optparse. Example: "-l LEVEL" # :long - The long option, just like from optparse. Example: "--level LEVEL" # :description - The description for this item, just like from optparse. # :default - A default value for this option. Default values will be populated # on parse into `config` or `default_default`, depending `use_separate_defaults` # :boolean - indicates the flag is a boolean. You can use this if the flag takes no arguments # The config value will be set to 'true' if the flag is provided on the CLI and this # argument is set to true. The config value will be set to false only # if it has a default value of false # :required - When set, the option is required. If the command is run without this option, # it will print a message informing the user of the missing requirement, and exit. Default is false. # :proc - Proc that will be invoked if the human has specified this option. # Two forms are supported: # Proc/1 - provided value is passed in. # Proc/2 - first argument is provided value. Second is the cli flag option hash. # Both versions return the value to be assigned to the option. # :show_options - this option is designated as one that shows all supported options/help when invoked. # :exit - exit your program with the exit code when this option is given. Example: 0 # :in - array containing a list of valid values. The value provided at run-time for the option is # validated against this. If it is not in the list, it will print a message and exit. # :on :head OR :tail - force this option to display at the beginning or end of the # option list, respectively # = # @return :: the config hash for the created option # i def option(name, args) @options ||= {} raise(ArgumentError, "Option name must be a symbol") unless name.is_a?(Symbol) @options[name.to_sym] = args end # Declare a deprecated option # # Add a deprecated command line option. # # name :: The name of the deprecated option # replacement :: The name of the option that replaces this option. # long :: The original long flag name, or flag name with argument, eg "--user USER" # short :: The original short-form flag name, eg "-u USER" # boolean :: true if this is a boolean flag, eg "--[no-]option". # value_mapper :: a block that accepts the original value from the deprecated option, # and converts it to a value suitable for the new option. # If not provided, the value provided to the deprecated option will be # assigned directly to the converted option. # keep :: Defaults to true, this ensure sthat `options[:deprecated_flag]` is # populated when the deprecated flag is used. If set to false, # only the value in `replacement` will be set. Results undefined # if no replacement is provided. You can use this to enforce the transition # to non-deprecated keys in your code. # # === Returns # :: The config hash for the created option. def deprecated_option(name, replacement: nil, long: nil, short: nil, boolean: false, value_mapper: nil, keep: true) description = if replacement replacement_cfg = options[replacement] display_name = CLI::Formatter.combined_option_display_name(replacement_cfg[:short], replacement_cfg[:long]) "This flag is deprecated. Use #{display_name} instead." else "This flag is deprecated and will be removed in a future release." end value_mapper ||= Proc.new { |v| v } option(name, long: long, short: short, boolean: boolean, description: description, on: :tail, deprecated: true, keep: keep, replacement: replacement, value_mapper: value_mapper) end # Get the hash of current options. # # === Returns # @options:: The current options hash. def options @options ||= {} @options end # Set the current options hash # # === Parameters # val:: The hash to set the options to # # === Returns # @options:: The current options hash. def options=(val) raise(ArgumentError, "Options must recieve a hash") unless val.is_a?(Hash) @options = val end # Change the banner. Defaults to: # Usage: #{0} (options) # # === Parameters # bstring:: The string to set the banner to # # === Returns # @banner:: The current banner def banner(bstring = nil) if bstring @banner = bstring else @banner ||= "Usage: #{$0} (options)" @banner end end end # Gives the command line options definition as configured in the DSL. These # are used by #parse_options to generate the option parsing code. To get # the values supplied by the user, see #config. attr_accessor :options # A Hash containing the values supplied by command line options. # # The behavior and contents of this Hash vary depending on whether # ClassMethods#use_separate_default_options is enabled. # ==== use_separate_default_options *disabled* # After initialization, +config+ will contain any default values defined # via the mixlib-config DSL. When #parse_options is called, user-supplied # values (from ARGV) will be merged in. # ==== use_separate_default_options *enabled* # After initialization, this will be an empty hash. When #parse_options is # called, +config+ is populated *only* with user-supplied values. attr_accessor :config # If ClassMethods#use_separate_default_options is enabled, this will be a # Hash containing key value pairs of `:option_name => default_value` # (populated during object initialization). # # If use_separate_default_options is disabled, it will always be an empty # hash. attr_accessor :default_config # Any arguments which were not parsed and placed in "config"--the leftovers. attr_accessor :cli_arguments # Banner for the option parser. If the option parser is printed, e.g., by # `puts opt_parser`, this string will be used as the first line. attr_accessor :banner # Create a new Mixlib::CLI class. If you override this, make sure you call super! # # === Parameters # *args:: The array of arguments passed to the initializer # # === Returns # object:: Returns an instance of whatever you wanted :) def initialize(*args) @options = {} @config = {} @default_config = {} @opt_parser = nil # Set the banner @banner = self.class.banner # Dupe the class options for this instance klass_options = self.class.options klass_options.keys.inject(@options) { |memo, key| memo[key] = klass_options[key].dup; memo } # If use_separate_defaults? is on, default values go in @default_config defaults_container = if self.class.use_separate_defaults? @default_config else @config end # Set the default configuration values for this instance @options.each do |config_key, config_opts| config_opts[:on] ||= :on config_opts[:boolean] ||= false config_opts[:required] ||= false config_opts[:proc] ||= nil config_opts[:show_options] ||= false config_opts[:exit] ||= nil config_opts[:in] ||= nil if config_opts.key?(:default) defaults_container[config_key] = config_opts[:default] end end super(*args) end # Parses an array, by default ARGV, for command line options (as configured at # the class level). # === Parameters # argv:: The array of arguments to parse; defaults to ARGV # # === Returns # argv:: Returns any un-parsed elements. def parse_options(argv = ARGV, show_deprecations: true) argv = argv.dup opt_parser.parse!(argv) # Do this before our custom validations, so that custom # validations apply to any converted deprecation values; # but after parse! so that everything is populated. handle_deprecated_options(show_deprecations) # Deal with any required values options.each do |opt_key, opt_config| if opt_config[:required] && !config.key?(opt_key) reqarg = opt_config[:short] || opt_config[:long] puts "You must supply #{reqarg}!" puts @opt_parser exit 2 end if opt_config[:in] unless opt_config[:in].is_a?(Array) raise(ArgumentError, "Options config key :in must receive an Array") end if config[opt_key] && !opt_config[:in].include?(config[opt_key]) reqarg = Formatter.combined_option_display_name(opt_config[:short], opt_config[:long]) puts "#{reqarg}: #{config[opt_key]} is not one of the allowed values: #{Formatter.friendly_opt_list(opt_config[:in])}" # TODO - get rid of this. nobody wants to be spammed with a ton of information, particularly since we just told them the exact problem and how to fix it. puts @opt_parser exit 2 end end end @cli_arguments = argv argv end # The option parser generated from the mixlib-cli DSL. +opt_parser+ can be # used to print a help message including the banner and any CLI options via # `puts opt_parser`. # === Returns # opt_parser:: The option parser object. def opt_parser @opt_parser ||= OptionParser.new do |opts| # Set the banner opts.banner = banner # Create new options options.sort { |a, b| a[0].to_s <=> b[0].to_s }.each do |opt_key, opt_val| opt_args = build_option_arguments(opt_val) opt_method = case opt_val[:on] when :on :on when :tail :on_tail when :head :on_head else raise ArgumentError, "You must pass :on, :tail, or :head to :on" end parse_block = Proc.new do |c| config[opt_key] = if opt_val[:proc] if opt_val[:proc].arity == 2 # New hotness to allow for reducer-style procs. opt_val[:proc].call(c, config[opt_key]) else # Older single-argument proc. opt_val[:proc].call(c) end else # No proc. c end puts opts if opt_val[:show_options] exit opt_val[:exit] if opt_val[:exit] end full_opt = [ opt_method ] opt_args.inject(full_opt) { |memo, arg| memo << arg; memo } full_opt << parse_block opts.send(*full_opt) end end end # Iterates through options declared as deprecated, # maps values to their replacement options, # and prints deprecation warnings. # # @return NilClass def handle_deprecated_options(show_deprecations) merge_in_values = {} config.each_key do |opt_key| opt_cfg = options[opt_key] # Deprecated entries do not have defaults so no matter what # separate_default_options are set, if we see a 'config' # entry that contains a deprecated indicator, then the option was # explicitly provided by the caller. # # opt_cfg may not exist if an inheriting application # has directly inserted values info config. next unless opt_cfg && opt_cfg[:deprecated] replacement_key = opt_cfg[:replacement] if replacement_key # This is the value passed into the deprecated flag. We'll use # the declared value mapper (defaults to return the same value if caller hasn't # provided a mapper). deprecated_val = config[opt_key] # We can't modify 'config' since we're iterating it, apply updates # at the end. merge_in_values[replacement_key] = opt_cfg[:value_mapper].call(deprecated_val) config.delete(opt_key) unless opt_cfg[:keep] end # Warn about the deprecation. if show_deprecations # Description is also the deprecation message. display_name = CLI::Formatter.combined_option_display_name(opt_cfg[:short], opt_cfg[:long]) puts "#{display_name}: #{opt_cfg[:description]}" end end config.merge!(merge_in_values) nil end def build_option_arguments(opt_setting) arguments = [] arguments << opt_setting[:short] if opt_setting[:short] arguments << opt_setting[:long] if opt_setting[:long] if opt_setting.key?(:description) description = opt_setting[:description].dup description << " (required)" if opt_setting[:required] description << " (valid options: #{Formatter.friendly_opt_list(opt_setting[:in])})" if opt_setting[:in] opt_setting[:description] = description arguments << description end arguments end def self.included(receiver) receiver.extend(Mixlib::CLI::ClassMethods) receiver.extend(Mixlib::CLI::InheritMethods) end end end mixlib-cli-2.1.6/lib/mixlib/cli/000077500000000000000000000000001357755107300163675ustar00rootroot00000000000000mixlib-cli-2.1.6/lib/mixlib/cli/formatter.rb000066400000000000000000000022231357755107300207160ustar00rootroot00000000000000 module Mixlib module CLI class Formatter # Create a string that includes both versions (short/long) of a flag name # based on on whether short/long/both/neither are provided # # @param short [String] the short name of the option. Can be nil. # @param long [String] the long name of the option. Can be nil. # @return [String] the formatted flag name as described above def self.combined_option_display_name(short, long) usage = "" # short/long may have an argument (--long ARG) # splitting on " " and taking first ensures that we get just # the flag name without the argument if one is present. usage << short.split(" ").first if short usage << "/" if long && short usage << long.split(" ").first if long usage end # @param opt_arry [Array] # # @return [String] a friendly quoted list of items complete with "or" def self.friendly_opt_list(opt_array) opts = opt_array.map { |x| "'#{x}'" } return opts.join(" or ") if opts.size < 3 opts[0..-2].join(", ") + ", or " + opts[-1] end end end end mixlib-cli-2.1.6/lib/mixlib/cli/version.rb000066400000000000000000000001021357755107300203720ustar00rootroot00000000000000module Mixlib module CLI VERSION = "2.1.6".freeze end end mixlib-cli-2.1.6/mixlib-cli.gemspec000066400000000000000000000011071357755107300171630ustar00rootroot00000000000000$:.unshift(File.dirname(__FILE__) + "/lib") require "mixlib/cli/version" Gem::Specification.new do |s| s.name = "mixlib-cli" s.version = Mixlib::CLI::VERSION s.summary = "A simple mixin for CLI interfaces, including option parsing" s.description = s.summary s.author = "Chef Software, Inc." s.email = "info@chef.io" s.homepage = "https://github.com/chef/mixlib-cli" s.license = "Apache-2.0" s.required_ruby_version = ">= 2.4" s.require_path = "lib" s.files = %w{LICENSE NOTICE} + Dir.glob("lib/**/*", File::FNM_DOTMATCH).reject { |f| File.directory?(f) } end mixlib-cli-2.1.6/spec/000077500000000000000000000000001357755107300145205ustar00rootroot00000000000000mixlib-cli-2.1.6/spec/mixlib/000077500000000000000000000000001357755107300160045ustar00rootroot00000000000000mixlib-cli-2.1.6/spec/mixlib/cli/000077500000000000000000000000001357755107300165535ustar00rootroot00000000000000mixlib-cli-2.1.6/spec/mixlib/cli/formatter_spec.rb000066400000000000000000000032021357755107300221120ustar00rootroot00000000000000 require "mixlib/cli/formatter" describe Mixlib::CLI::Formatter do Formatter = Mixlib::CLI::Formatter context "combined_option_display_name" do it "converts --option with short -o to '-s/--option'" do expect(Formatter.combined_option_display_name("-o", "--option")).to eql "-o/--option" end it "converts --option with no short to '--option'" do expect(Formatter.combined_option_display_name(nil, "--option")).to eql "--option" end it "converts short -o with no long option to '-o'" do expect(Formatter.combined_option_display_name("-o", nil)).to eql"-o" end it "converts options the same way even with an argument present" do expect(Formatter.combined_option_display_name("-o arg1", "--option arg1")).to eql "-o/--option" end it "converts options to a blank string if neither short nor long are present" do expect(Formatter.combined_option_display_name(nil, nil)).to eql "" end end context "friendly_opt_list" do it "for a single item it quotes it and returns it as a string" do expect(Formatter.friendly_opt_list(%w{hello})).to eql "'hello'" end it "for two items returns ..." do expect(Formatter.friendly_opt_list(%w{hello world})).to eql "'hello' or 'world'" end it "for three items returns..." do expect(Formatter.friendly_opt_list(%w{hello green world})).to eql "'hello', 'green', or 'world'" end it "for more than three items creates a list in the same was as three items" do expect(Formatter.friendly_opt_list(%w{hello green world good morning})).to eql "'hello', 'green', 'world', 'good', or 'morning'" end end end mixlib-cli-2.1.6/spec/mixlib/cli_spec.rb000066400000000000000000000444761357755107300201310ustar00rootroot00000000000000# # Author:: Adam Jacob () # Copyright:: Copyright (c) 2008-2019, Chef Software Inc. # License:: Apache License, Version 2.0 # # 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. # require File.expand_path(File.join(File.dirname(__FILE__), "..", "spec_helper")) describe Mixlib::CLI do after(:each) do TestCLI.options = {} TestCLI.banner("Usage: #{$0} (options)") end describe "class method" do describe "option" do it "allows you to set a config option with a hash" do expect(TestCLI.option(:config_file, short: "-c CONFIG")).to eql({ short: "-c CONFIG" }) end end describe "deprecated_option" do it "makes a deprecated option when you declare one" do TestCLI.deprecated_option(:option_d, short: "-d") expect(TestCLI.options[:option_d]).to include(deprecated: true) end end describe "options" do it "returns the current options hash" do TestCLI.option(:config_file, short: "-c CONFIG") expect(TestCLI.options).to eql({ config_file: { short: "-c CONFIG" } }) end it "includes deprecated options and their generated descriptions" do TestCLI.option(:config_file, short: "-c CONFIG") TestCLI.deprecated_option(:blah, short: "-b BLAH") TestCLI.deprecated_option(:blah2, long: "--blah2 BLAH", replacement: :config_file) opts = TestCLI.options expect(opts[:config_file][:short]).to eql("-c CONFIG") expect(opts[:config_file].key?(:deprecated)).to eql(false) expect(opts[:blah][:description]).to eql("This flag is deprecated and will be removed in a future release.") expect(opts[:blah][:deprecated]).to eql(true) expect(opts[:blah2][:description]).to eql("This flag is deprecated. Use -c instead.") expect(opts[:blah2][:deprecated]).to eql(true) end end describe "options=" do it "allows you to set the full options with a single hash" do TestCLI.options = { config_file: { short: "-c CONFIG" } } expect(TestCLI.options).to eql({ config_file: { short: "-c CONFIG" } }) end end describe "banner" do it "has a default value" do expect(TestCLI.banner).to match(/^Usage: (.+) \(options\)$/) end it "allows you to set the banner" do TestCLI.banner("Usage: foo") expect(TestCLI.banner).to eql("Usage: foo") end end end context "when configured with default single-config-hash behavior" do before(:each) do @cli = TestCLI.new end describe "initialize" do it "sets the banner to the class defined banner" do expect(@cli.banner).to eql(TestCLI.banner) end it "sets the options to the class defined options and deprecated options, with defaults" do TestCLI.option(:config_file, short: "-l FILE") TestCLI.deprecated_option(:option_file, short: "-o FILE", replacement: :config_file) cli = TestCLI.new expect(cli.options[:config_file]).to eql({ short: "-l FILE", on: :on, boolean: false, required: false, proc: nil, show_options: false, exit: nil, in: nil, }) expect(cli.options[:option_file]).to include( boolean: false, deprecated: true, description: "This flag is deprecated. Use -l instead.", exit: nil, in: nil, long: nil, keep: true, proc: nil, replacement: :config_file, required: false, short: "-o FILE", on: :tail, show_options: false ) expect(cli.options[:option_file][:value_mapper].class).to eql(Proc) end it "sets the default config value for any options that include it" do TestCLI.option(:config_file, short: "-l LOG", default: :debug) @cli = TestCLI.new expect(@cli.config[:config_file]).to eql(:debug) end end describe "opt_parser" do it "sets the banner in opt_parse" do expect(@cli.opt_parser.banner).to eql(@cli.banner) end it "presents the arguments in the banner" do TestCLI.option(:config_file, short: "-l LOG") @cli = TestCLI.new expect(@cli.opt_parser.to_s).to match(/-l LOG/) end it "honors :on => :tail options in the banner" do TestCLI.option(:config_file, short: "-l LOG") TestCLI.option(:help, short: "-h", boolean: true, on: :tail) @cli = TestCLI.new expect(@cli.opt_parser.to_s.split("\n").last).to match(/-h/) end it "honors :on => :head options in the banner" do TestCLI.option(:config_file, short: "-l LOG") TestCLI.option(:help, short: "-h", boolean: true, on: :head) @cli = TestCLI.new expect(@cli.opt_parser.to_s.split("\n")[1]).to match(/-h/) end it "presents the arguments in alphabetical order in the banner" do TestCLI.option(:alpha, short: "-a ALPHA") TestCLI.option(:beta, short: "-b BETA") TestCLI.option(:zeta, short: "-z ZETA") @cli = TestCLI.new output_lines = @cli.opt_parser.to_s.split("\n") expect(output_lines[1]).to match(/-a ALPHA/) expect(output_lines[2]).to match(/-b BETA/) expect(output_lines[3]).to match(/-z ZETA/) end end describe "parse_options" do it "sets the corresponding config value for non-boolean arguments" do TestCLI.option(:config_file, short: "-c CONFIG") @cli = TestCLI.new @cli.parse_options([ "-c", "foo.rb" ]) expect(@cli.config[:config_file]).to eql("foo.rb") end it "sets the corresponding config value according to a supplied proc" do TestCLI.option(:number, short: "-n NUMBER", proc: Proc.new { |config| config.to_i + 2 }) @cli = TestCLI.new @cli.parse_options([ "-n", "2" ]) expect(@cli.config[:number]).to eql(4) end it "passes the existing value to two-argument procs" do TestCLI.option(:number, short: "-n NUMBER", proc: Proc.new { |value, existing| existing ||= []; existing << value; existing }) @cli = TestCLI.new @cli.parse_options([ "-n", "2", "-n", "3" ]) expect(@cli.config[:number]).to eql(%w{2 3}) end it "sets the corresponding config value to true for boolean arguments" do TestCLI.option(:i_am_boolean, short: "-i", boolean: true) @cli = TestCLI.new @cli.parse_options([ "-i" ]) expect(@cli.config[:i_am_boolean]).to be true end it "sets the corresponding config value to false when a boolean is prefixed with --no" do TestCLI.option(:i_am_boolean, long: "--[no-]bool", boolean: true) @cli = TestCLI.new @cli.parse_options([ "--no-bool" ]) expect(@cli.config[:i_am_boolean]).to be false end it "exits if a config option has :exit set" do TestCLI.option(:i_am_exit, short: "-x", boolean: true, exit: 0) @cli = TestCLI.new expect(lambda { @cli.parse_options(["-x"]) }).to raise_error(SystemExit) end it "exits if a required option is missing" do TestCLI.option(:require_me, short: "-r", boolean: true, required: true) @cli = TestCLI.new expect(lambda { @cli.parse_options([]) }).to raise_error(SystemExit) end it "exits if option is not included in the list and required" do TestCLI.option(:inclusion, short: "-i val", in: %w{one two}, required: true) @cli = TestCLI.new expect(lambda { @cli.parse_options(["-i", "three"]) }).to raise_error(SystemExit) end it "exits if option is not included in the list and not required" do TestCLI.option(:inclusion, short: "-i val", in: %w{one two}, required: false) @cli = TestCLI.new expect(lambda { @cli.parse_options(["-i", "three"]) }).to raise_error(SystemExit) end it "doesn't exit if option is nil and not required" do TestCLI.option(:inclusion, short: "-i val", in: %w{one two}, required: false) @cli = TestCLI.new expect do expect(@cli.parse_options([])).to eql [] end.to_not raise_error end it "exit if option is nil and required" do TestCLI.option(:inclusion, short: "-i val", in: %w{one two}, required: true) @cli = TestCLI.new expect(lambda { @cli.parse_options([]) }).to raise_error(SystemExit) end it "raises ArgumentError if options key :in is not an array" do TestCLI.option(:inclusion, short: "-i val", in: "foo", required: true) @cli = TestCLI.new expect(lambda { @cli.parse_options(["-i", "three"]) }).to raise_error(ArgumentError) end it "doesn't exit if option is included in the list" do TestCLI.option(:inclusion, short: "-i val", in: %w{one two}, required: true) @cli = TestCLI.new @cli.parse_options(["-i", "one"]) expect(@cli.config[:inclusion]).to eql("one") end it "changes description if :in key is specified with a single value" do TestCLI.option(:inclusion, short: "-i val", in: %w{one}, description: "desc", required: false) @cli = TestCLI.new @cli.parse_options(["-i", "one"]) expect(@cli.options[:inclusion][:description]).to eql("desc (valid options: 'one')") end it "changes description if :in key is specified with 2 values" do TestCLI.option(:inclusion, short: "-i val", in: %w{one two}, description: "desc", required: false) @cli = TestCLI.new @cli.parse_options(["-i", "one"]) expect(@cli.options[:inclusion][:description]).to eql("desc (valid options: 'one' or 'two')") end it "changes description if :in key is specified with 3 values" do TestCLI.option(:inclusion, short: "-i val", in: %w{one two three}, description: "desc", required: false) @cli = TestCLI.new @cli.parse_options(["-i", "one"]) expect(@cli.options[:inclusion][:description]).to eql("desc (valid options: 'one', 'two', or 'three')") end it "doesn't exit if a required option is specified" do TestCLI.option(:require_me, short: "-r", boolean: true, required: true) @cli = TestCLI.new @cli.parse_options(["-r"]) expect(@cli.config[:require_me]).to be true end it "doesn't exit if a required boolean option is specified and false" do TestCLI.option(:require_me, long: "--[no-]req", boolean: true, required: true) @cli = TestCLI.new @cli.parse_options(["--no-req"]) expect(@cli.config[:require_me]).to be false end it "doesn't exit if a required option is specified and empty" do TestCLI.option(:require_me, short: "-r VALUE", required: true) @cli = TestCLI.new @cli.parse_options(["-r", ""]) expect(@cli.config[:require_me]).to eql("") end it "preserves all of the commandline arguments, ARGV" do TestCLI.option(:config_file, short: "-c CONFIG") @cli = TestCLI.new argv_old = ARGV.dup ARGV.replace ["-c", "foo.rb"] @cli.parse_options expect(ARGV).to eql(["-c", "foo.rb"]) ARGV.replace argv_old end it "preserves and return any un-parsed elements" do TestCLI.option(:party, short: "-p LOCATION") @cli = TestCLI.new expect(@cli.parse_options([ "easy", "-p", "opscode", "hard" ])).to eql(%w{easy hard}) expect(@cli.cli_arguments).to eql(%w{easy hard}) end describe "with non-deprecated and deprecated options" do let(:cli) { TestCLI.new } before do TestCLI.option(:option_a, long: "--[no-]option-a", boolean: true) TestCLI.option(:option_b, short: "-b ARG", in: %w{a b c}) TestCLI.option(:option_c, short: "-c ARG") end context "when someone injects an unexpected value into 'config'" do before do cli.config[:surprise] = true end it "parses and preserves both known and unknown config values" do cli.parse_options(%w{--option-a}) expect(cli.config[:surprise]).to eql true expect(cli.config[:option_a]).to eql true end end context "when the deprecated option has a replacement" do context "and a value_mapper is provided" do before do TestCLI.deprecated_option(:option_x, long: "--option-x ARG", replacement: :option_b, value_mapper: Proc.new { |val| val == "valid" ? "a" : "xxx" } ) end it "still checks the replacement's 'in' validation list" do expect { cli.parse_options(%w{--option-x invalid}) }.to raise_error SystemExit end it "sets the mapped value in the replacement option and the deprecated value in the deprecated option" do cli.parse_options(%w{--option-x valid}) expect(cli.config[:option_x]).to eql("valid") expect(cli.config[:option_b]).to eql("a") end end context "and a value_mapper is not provided" do context "and keep is set to false in the deprecated option" do before do TestCLI.deprecated_option(:option_x, long: "--option-x ARG", replacement: :option_c, keep: false) end it "captures the replacement value, but does not set the deprecated value" do cli.parse_options %w{--option-x blah} expect(cli.config.key?(:option_x)).to eql false expect(cli.config[:option_c]).to eql "blah" end end context "and the replacement and deprecated are both boolean" do before do TestCLI.deprecated_option(:option_x, boolean: true, long: "--[no-]option-x", replacement: :option_a) end it "sets original and replacement to true when the deprecated flag is used" do cli.parse_options(%w{--option-x}) expect(cli.config[:option_x]).to eql true expect(cli.config[:option_a]).to eql true end it "sets the original and replacement to false when the negative deprecated flag is used" do cli.parse_options(%w{--no-option-x}) expect(cli.config[:option_x]).to eql false expect(cli.config[:option_a]).to eql false end end context "when the replacement does not accept a value" do before do TestCLI.deprecated_option(:option_x, long: "--option-x ARG", replacement: :option_c) end it "will still set the value because you haven't given a custom value mapper to set a true/false value" do cli.parse_options(%w{--option-x BLAH}) expect(cli.config[:option_c]).to eql("BLAH") end end end end context "when the deprecated option does not have a replacement" do before do TestCLI.deprecated_option(:option_x, short: "-x") end it "warns about the deprecated option being removed" do expect { TestCLI.new.parse_options(%w{-x}) }.to output(/removed in a future release/).to_stdout end end end end end context "when configured to separate default options" do before do TestCLI.use_separate_default_options true TestCLI.option(:defaulter, short: "-D SOMETHING", default: "this is the default") @cli = TestCLI.new end it "sets default values on the `default` hash" do @cli.parse_options([]) expect(@cli.default_config[:defaulter]).to eql("this is the default") expect(@cli.config[:defaulter]).to be_nil end it "sets parsed values on the `config` hash" do @cli.parse_options(%w{-D not-default}) expect(@cli.default_config[:defaulter]).to eql("this is the default") expect(@cli.config[:defaulter]).to eql("not-default") end end context "when subclassed" do before do TestCLI.options = { arg1: { boolean: true } } end it "retains previously defined options from parent" do class T1 < TestCLI option :arg2, boolean: true end expect(T1.options[:arg1]).to be_a(Hash) expect(T1.options[:arg2]).to be_a(Hash) expect(TestCLI.options[:arg2]).to be_nil end it "isn't able to modify parent classes options" do class T2 < TestCLI option :arg2, boolean: true end T2.options[:arg1][:boolean] = false expect(T2.options[:arg1][:boolean]).to be false expect(TestCLI.options[:arg1][:boolean]).to be true end it "passes its options onto child" do class T3 < TestCLI option :arg2, boolean: true end class T4 < T3 option :arg3, boolean: true end 3.times do |i| expect(T4.options["arg#{i + 1}".to_sym]).to be_a(Hash) end end it "also works with an option that's an array" do class T5 < TestCLI option :arg2, default: [] end class T6 < T5 end expect(T6.options[:arg2]).to be_a(Hash) end end end # option :config_file, # :short => "-c CONFIG", # :long => "--config CONFIG", # :default => 'config.rb', # :description => "The configuration file to use" # # option :log_level, # :short => "-l LEVEL", # :long => "--log_level LEVEL", # :description => "Set the log level (debug, info, warn, error, fatal)", # :required => true, # :proc => nil # # option :help, # :short => "-h", # :long => "--help", # :description => "Show this message", # :on => :tail, # :boolean => true, # :show_options => true, # :exit => 0 mixlib-cli-2.1.6/spec/spec_helper.rb000066400000000000000000000014251357755107300173400ustar00rootroot00000000000000$TESTING = true $:.push File.join(File.dirname(__FILE__), "..", "lib") require "mixlib/cli" RSpec.configure do |config| # Use documentation format config.formatter = "doc" # Use color in STDOUT config.color = true # Use color not only in STDOUT but also in pagers and files config.tty = true # run the examples in random order config.order = :rand config.filter_run focus: true config.run_all_when_everything_filtered = true config.warnings = true config.before(:each) do # create a fresh TestCLI class on every example, so that examples never # pollute global variables and create ordering issues Object.send(:remove_const, "TestCLI") if Object.const_defined?("TestCLI") TestCLI = Class.new TestCLI.send(:include, Mixlib::CLI) end end