shopify-cli 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.github/CODEOWNERS +1 -0
- data/.github/CODE_OF_CONDUCT.md +73 -0
- data/.github/CONTRIBUTING.md +51 -0
- data/.github/DESIGN.md +153 -0
- data/.github/ISSUE_TEMPLATE.md +38 -0
- data/.github/PULL_REQUEST_TEMPLATE.md +22 -0
- data/.github/probots.yml +3 -0
- data/.gitignore +19 -0
- data/.rubocop.yml +47 -0
- data/.ruby-version +1 -0
- data/.travis.yml +12 -0
- data/Gemfile +22 -0
- data/Gemfile.lock +77 -0
- data/LICENSE.md +7 -0
- data/README.md +13 -0
- data/Rakefile +101 -0
- data/SECURITY.md +59 -0
- data/Vagrantfile +17 -0
- data/bin/load_shopify.rb +20 -0
- data/bin/shopify +32 -0
- data/dev.yml +17 -0
- data/docs/Gemfile +5 -0
- data/docs/Gemfile.lock +248 -0
- data/docs/_config.yml +16 -0
- data/docs/_data/nav.yml +26 -0
- data/docs/_includes/footer.html +15 -0
- data/docs/_includes/head.html +19 -0
- data/docs/_includes/sidebar_nav.html +22 -0
- data/docs/_includes/toc.html +112 -0
- data/docs/_layouts/default.html +79 -0
- data/docs/app/node/commands/index.md +82 -0
- data/docs/app/node/index.md +35 -0
- data/docs/app/rails/commands/index.md +80 -0
- data/docs/app/rails/index.md +36 -0
- data/docs/core/index.md +70 -0
- data/docs/css/docs.css +157 -0
- data/docs/getting-started/index.md +61 -0
- data/docs/help/start-app/index.md +6 -0
- data/docs/images/header.png +0 -0
- data/docs/index.md +27 -0
- data/docs/installing-ruby.md +28 -0
- data/ext/shopify-cli/extconf.rb +27 -0
- data/install.sh +7 -0
- data/lib/docgen/class_template.md.erb +81 -0
- data/lib/docgen/index_template.md.erb +5 -0
- data/lib/docgen/markdown.rb +101 -0
- data/lib/graphql/admin_introspection.graphql +87 -0
- data/lib/graphql/all_organizations.graphql +19 -0
- data/lib/graphql/all_orgs_with_apps.graphql +30 -0
- data/lib/graphql/api_versions.graphql +6 -0
- data/lib/graphql/convert_dev_to_test_store.graphql +10 -0
- data/lib/graphql/create_app.graphql +20 -0
- data/lib/graphql/create_customer.graphql +9 -0
- data/lib/graphql/create_draft_order.graphql +8 -0
- data/lib/graphql/create_product.graphql +9 -0
- data/lib/graphql/extension_create.graphql +21 -0
- data/lib/graphql/extension_update_draft.graphql +18 -0
- data/lib/graphql/find_organization.graphql +17 -0
- data/lib/graphql/get_app_urls.graphql +6 -0
- data/lib/graphql/update_dashboard_urls.graphql +8 -0
- data/lib/project_types/extension/cli.rb +71 -0
- data/lib/project_types/extension/commands/build.rb +29 -0
- data/lib/project_types/extension/commands/create.rb +49 -0
- data/lib/project_types/extension/commands/extension_command.rb +22 -0
- data/lib/project_types/extension/commands/push.rb +69 -0
- data/lib/project_types/extension/commands/register.rb +78 -0
- data/lib/project_types/extension/commands/serve.rb +24 -0
- data/lib/project_types/extension/commands/tunnel.rb +69 -0
- data/lib/project_types/extension/extension_project.rb +85 -0
- data/lib/project_types/extension/extension_project_keys.rb +10 -0
- data/lib/project_types/extension/features/argo.rb +48 -0
- data/lib/project_types/extension/features/argo_dependencies.rb +28 -0
- data/lib/project_types/extension/features/argo_setup.rb +54 -0
- data/lib/project_types/extension/features/argo_setup_step.rb +31 -0
- data/lib/project_types/extension/features/argo_setup_steps.rb +53 -0
- data/lib/project_types/extension/features/tunnel_url.rb +20 -0
- data/lib/project_types/extension/forms/create.rb +52 -0
- data/lib/project_types/extension/forms/register.rb +48 -0
- data/lib/project_types/extension/messages/message_loading.rb +37 -0
- data/lib/project_types/extension/messages/messages.rb +126 -0
- data/lib/project_types/extension/models/app.rb +14 -0
- data/lib/project_types/extension/models/registration.rb +19 -0
- data/lib/project_types/extension/models/type.rb +76 -0
- data/lib/project_types/extension/models/types/checkout_post_purchase.rb +20 -0
- data/lib/project_types/extension/models/types/subscription_management.rb +20 -0
- data/lib/project_types/extension/models/validation_error.rb +17 -0
- data/lib/project_types/extension/models/version.rb +15 -0
- data/lib/project_types/extension/tasks/converters/registration_converter.rb +26 -0
- data/lib/project_types/extension/tasks/converters/validation_error_converter.rb +25 -0
- data/lib/project_types/extension/tasks/converters/version_converter.rb +28 -0
- data/lib/project_types/extension/tasks/create_extension.rb +31 -0
- data/lib/project_types/extension/tasks/get_apps.rb +34 -0
- data/lib/project_types/extension/tasks/update_draft.rb +29 -0
- data/lib/project_types/extension/tasks/user_errors.rb +45 -0
- data/lib/project_types/node/cli.rb +37 -0
- data/lib/project_types/node/commands/create.rb +117 -0
- data/lib/project_types/node/commands/deploy.rb +22 -0
- data/lib/project_types/node/commands/deploy/heroku.rb +91 -0
- data/lib/project_types/node/commands/generate.rb +51 -0
- data/lib/project_types/node/commands/generate/billing.rb +37 -0
- data/lib/project_types/node/commands/generate/page.rb +55 -0
- data/lib/project_types/node/commands/generate/webhook.rb +33 -0
- data/lib/project_types/node/commands/open.rb +16 -0
- data/lib/project_types/node/commands/populate.rb +23 -0
- data/lib/project_types/node/commands/populate/customer.rb +31 -0
- data/lib/project_types/node/commands/populate/draft_order.rb +28 -0
- data/lib/project_types/node/commands/populate/product.rb +30 -0
- data/lib/project_types/node/commands/serve.rb +45 -0
- data/lib/project_types/node/commands/tunnel.rb +39 -0
- data/lib/project_types/node/forms/create.rb +87 -0
- data/lib/project_types/node/messages/messages.rb +260 -0
- data/lib/project_types/rails/cli.rb +41 -0
- data/lib/project_types/rails/commands/create.rb +126 -0
- data/lib/project_types/rails/commands/deploy.rb +22 -0
- data/lib/project_types/rails/commands/deploy/heroku.rb +113 -0
- data/lib/project_types/rails/commands/generate.rb +49 -0
- data/lib/project_types/rails/commands/generate/webhook.rb +39 -0
- data/lib/project_types/rails/commands/open.rb +16 -0
- data/lib/project_types/rails/commands/populate.rb +23 -0
- data/lib/project_types/rails/commands/populate/customer.rb +31 -0
- data/lib/project_types/rails/commands/populate/draft_order.rb +28 -0
- data/lib/project_types/rails/commands/populate/product.rb +30 -0
- data/lib/project_types/rails/commands/serve.rb +47 -0
- data/lib/project_types/rails/commands/tunnel.rb +39 -0
- data/lib/project_types/rails/forms/create.rb +116 -0
- data/lib/project_types/rails/gem.rb +56 -0
- data/lib/project_types/rails/messages/messages.rb +283 -0
- data/lib/project_types/rails/ruby.rb +17 -0
- data/lib/project_types/script/cli.rb +76 -0
- data/lib/project_types/script/commands/create.rb +45 -0
- data/lib/project_types/script/commands/disable.rb +36 -0
- data/lib/project_types/script/commands/enable.rb +46 -0
- data/lib/project_types/script/commands/push.rb +39 -0
- data/lib/project_types/script/config/extension_points.yml +18 -0
- data/lib/project_types/script/errors.rb +16 -0
- data/lib/project_types/script/forms/create.rb +29 -0
- data/lib/project_types/script/forms/enable.rb +24 -0
- data/lib/project_types/script/forms/push.rb +19 -0
- data/lib/project_types/script/forms/script_form.rb +66 -0
- data/lib/project_types/script/graphql/app_script_update_or_create.graphql +27 -0
- data/lib/project_types/script/graphql/script_service_proxy.graphql +8 -0
- data/lib/project_types/script/graphql/shop_script_delete.graphql +14 -0
- data/lib/project_types/script/graphql/shop_script_update_or_create.graphql +28 -0
- data/lib/project_types/script/layers/application/build_script.rb +43 -0
- data/lib/project_types/script/layers/application/create_script.rb +47 -0
- data/lib/project_types/script/layers/application/disable_script.rb +19 -0
- data/lib/project_types/script/layers/application/enable_script.rb +21 -0
- data/lib/project_types/script/layers/application/extension_points.rb +17 -0
- data/lib/project_types/script/layers/application/project_dependencies.rb +34 -0
- data/lib/project_types/script/layers/application/push_script.rb +30 -0
- data/lib/project_types/script/layers/domain/errors.rb +25 -0
- data/lib/project_types/script/layers/domain/extension_point.rb +29 -0
- data/lib/project_types/script/layers/domain/push_package.rb +29 -0
- data/lib/project_types/script/layers/domain/script.rb +18 -0
- data/lib/project_types/script/layers/infrastructure/assemblyscript_dependency_manager.rb +73 -0
- data/lib/project_types/script/layers/infrastructure/assemblyscript_tsconfig.rb +38 -0
- data/lib/project_types/script/layers/infrastructure/assemblyscript_wasm_builder.rb +39 -0
- data/lib/project_types/script/layers/infrastructure/dependency_manager.rb +36 -0
- data/lib/project_types/script/layers/infrastructure/errors.rb +38 -0
- data/lib/project_types/script/layers/infrastructure/extension_point_repository.rb +31 -0
- data/lib/project_types/script/layers/infrastructure/push_package_repository.rb +47 -0
- data/lib/project_types/script/layers/infrastructure/script_builder.rb +34 -0
- data/lib/project_types/script/layers/infrastructure/script_repository.rb +89 -0
- data/lib/project_types/script/layers/infrastructure/script_service.rb +165 -0
- data/lib/project_types/script/layers/infrastructure/test_suite_repository.rb +59 -0
- data/lib/project_types/script/messages/messages.rb +204 -0
- data/lib/project_types/script/script_project.rb +37 -0
- data/lib/project_types/script/templates/ts/as-pect.config.js +21 -0
- data/lib/project_types/script/ui/error_handler.rb +136 -0
- data/lib/project_types/script/ui/strict_spinner.rb +22 -0
- data/lib/rubygems_plugin.rb +18 -0
- data/lib/shopify-cli/admin_api.rb +99 -0
- data/lib/shopify-cli/admin_api/populate_resource_command.rb +165 -0
- data/lib/shopify-cli/admin_api/schema.rb +32 -0
- data/lib/shopify-cli/api.rb +104 -0
- data/lib/shopify-cli/command.rb +67 -0
- data/lib/shopify-cli/commands.rb +28 -0
- data/lib/shopify-cli/commands/connect.rb +108 -0
- data/lib/shopify-cli/commands/create.rb +50 -0
- data/lib/shopify-cli/commands/help.rb +79 -0
- data/lib/shopify-cli/commands/logout.rb +23 -0
- data/lib/shopify-cli/commands/system.rb +135 -0
- data/lib/shopify-cli/commands/version.rb +15 -0
- data/lib/shopify-cli/context.rb +372 -0
- data/lib/shopify-cli/core.rb +9 -0
- data/lib/shopify-cli/core/entry_point.rb +40 -0
- data/lib/shopify-cli/core/executor.rb +21 -0
- data/lib/shopify-cli/core/help_resolver.rb +20 -0
- data/lib/shopify-cli/core/monorail.rb +118 -0
- data/lib/shopify-cli/db.rb +114 -0
- data/lib/shopify-cli/form.rb +40 -0
- data/lib/shopify-cli/git.rb +141 -0
- data/lib/shopify-cli/helpers.rb +5 -0
- data/lib/shopify-cli/helpers/haikunator.rb +92 -0
- data/lib/shopify-cli/heroku.rb +97 -0
- data/lib/shopify-cli/js_deps.rb +110 -0
- data/lib/shopify-cli/js_system.rb +98 -0
- data/lib/shopify-cli/messages/messages.rb +287 -0
- data/lib/shopify-cli/oauth.rb +192 -0
- data/lib/shopify-cli/oauth/servlet.rb +61 -0
- data/lib/shopify-cli/options.rb +40 -0
- data/lib/shopify-cli/packager.rb +116 -0
- data/lib/shopify-cli/partners_api.rb +114 -0
- data/lib/shopify-cli/partners_api/organizations.rb +32 -0
- data/lib/shopify-cli/process_supervision.rb +187 -0
- data/lib/shopify-cli/project.rb +191 -0
- data/lib/shopify-cli/project_type.rb +83 -0
- data/lib/shopify-cli/resources.rb +5 -0
- data/lib/shopify-cli/resources/env_file.rb +96 -0
- data/lib/shopify-cli/sub_command.rb +15 -0
- data/lib/shopify-cli/task.rb +10 -0
- data/lib/shopify-cli/tasks.rb +32 -0
- data/lib/shopify-cli/tasks/create_api_client.rb +29 -0
- data/lib/shopify-cli/tasks/ensure_dev_store.rb +41 -0
- data/lib/shopify-cli/tasks/ensure_env.rb +31 -0
- data/lib/shopify-cli/tasks/ensure_loopback_url.rb +20 -0
- data/lib/shopify-cli/tasks/update_dashboard_urls.rb +44 -0
- data/lib/shopify-cli/tunnel.rb +154 -0
- data/lib/shopify-cli/version.rb +3 -0
- data/lib/shopify_cli.rb +132 -0
- data/shopify-cli.gemspec +40 -0
- data/shopify.fish +12 -0
- data/shopify.sh +11 -0
- data/vendor/deps/cli-kit/REVISION +1 -0
- data/vendor/deps/cli-kit/lib/cli/kit.rb +60 -0
- data/vendor/deps/cli-kit/lib/cli/kit/autocall.rb +21 -0
- data/vendor/deps/cli-kit/lib/cli/kit/base_command.rb +49 -0
- data/vendor/deps/cli-kit/lib/cli/kit/command_registry.rb +94 -0
- data/vendor/deps/cli-kit/lib/cli/kit/config.rb +133 -0
- data/vendor/deps/cli-kit/lib/cli/kit/error_handler.rb +115 -0
- data/vendor/deps/cli-kit/lib/cli/kit/executor.rb +81 -0
- data/vendor/deps/cli-kit/lib/cli/kit/ini.rb +102 -0
- data/vendor/deps/cli-kit/lib/cli/kit/levenshtein.rb +82 -0
- data/vendor/deps/cli-kit/lib/cli/kit/logger.rb +76 -0
- data/vendor/deps/cli-kit/lib/cli/kit/resolver.rb +60 -0
- data/vendor/deps/cli-kit/lib/cli/kit/ruby_backports/enumerable.rb +6 -0
- data/vendor/deps/cli-kit/lib/cli/kit/support.rb +9 -0
- data/vendor/deps/cli-kit/lib/cli/kit/support/test_helper.rb +244 -0
- data/vendor/deps/cli-kit/lib/cli/kit/system.rb +207 -0
- data/vendor/deps/cli-kit/lib/cli/kit/util.rb +189 -0
- data/vendor/deps/cli-kit/lib/cli/kit/version.rb +5 -0
- data/vendor/deps/cli-ui/REVISION +1 -0
- data/vendor/deps/cli-ui/lib/cli/ui.rb +187 -0
- data/vendor/deps/cli-ui/lib/cli/ui/ansi.rb +153 -0
- data/vendor/deps/cli-ui/lib/cli/ui/box.rb +15 -0
- data/vendor/deps/cli-ui/lib/cli/ui/color.rb +79 -0
- data/vendor/deps/cli-ui/lib/cli/ui/formatter.rb +179 -0
- data/vendor/deps/cli-ui/lib/cli/ui/frame.rb +310 -0
- data/vendor/deps/cli-ui/lib/cli/ui/glyph.rb +78 -0
- data/vendor/deps/cli-ui/lib/cli/ui/progress.rb +88 -0
- data/vendor/deps/cli-ui/lib/cli/ui/prompt.rb +248 -0
- data/vendor/deps/cli-ui/lib/cli/ui/prompt/interactive_options.rb +472 -0
- data/vendor/deps/cli-ui/lib/cli/ui/prompt/options_handler.rb +24 -0
- data/vendor/deps/cli-ui/lib/cli/ui/spinner.rb +48 -0
- data/vendor/deps/cli-ui/lib/cli/ui/spinner/async.rb +40 -0
- data/vendor/deps/cli-ui/lib/cli/ui/spinner/spin_group.rb +241 -0
- data/vendor/deps/cli-ui/lib/cli/ui/stdout_router.rb +227 -0
- data/vendor/deps/cli-ui/lib/cli/ui/terminal.rb +36 -0
- data/vendor/deps/cli-ui/lib/cli/ui/truncater.rb +102 -0
- data/vendor/deps/cli-ui/lib/cli/ui/version.rb +5 -0
- data/vendor/deps/smart_properties/REVISION +1 -0
- data/vendor/deps/smart_properties/lib/smart_properties.rb +174 -0
- data/vendor/deps/smart_properties/lib/smart_properties/errors.rb +114 -0
- data/vendor/deps/smart_properties/lib/smart_properties/property.rb +162 -0
- data/vendor/deps/smart_properties/lib/smart_properties/property_collection.rb +83 -0
- data/vendor/deps/smart_properties/lib/smart_properties/validations.rb +8 -0
- data/vendor/deps/smart_properties/lib/smart_properties/validations/ancestor.rb +27 -0
- data/vendor/deps/smart_properties/lib/smart_properties/version.rb +3 -0
- data/vendor/lib/semantic/LICENSE +20 -0
- data/vendor/lib/semantic/semantic.rb +4 -0
- data/vendor/lib/semantic/version.rb +180 -0
- metadata +374 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
require 'cli/ui'
|
|
2
|
+
|
|
3
|
+
module CLI
|
|
4
|
+
module UI
|
|
5
|
+
class Color
|
|
6
|
+
attr_reader :sgr, :name, :code
|
|
7
|
+
|
|
8
|
+
# Creates a new color mapping
|
|
9
|
+
# Signatures can be found here:
|
|
10
|
+
# https://en.wikipedia.org/wiki/ANSI_escape_code#Colors
|
|
11
|
+
#
|
|
12
|
+
# ==== Attributes
|
|
13
|
+
#
|
|
14
|
+
# * +sgr+ - The color signature
|
|
15
|
+
# * +name+ - The name of the color
|
|
16
|
+
#
|
|
17
|
+
def initialize(sgr, name)
|
|
18
|
+
@sgr = sgr
|
|
19
|
+
@code = CLI::UI::ANSI.sgr(sgr)
|
|
20
|
+
@name = name
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
RED = new('31', :red)
|
|
24
|
+
GREEN = new('32', :green)
|
|
25
|
+
YELLOW = new('33', :yellow)
|
|
26
|
+
# default blue is low-contrast against black in some default terminal color scheme
|
|
27
|
+
BLUE = new('94', :blue) # 9x = high-intensity fg color x
|
|
28
|
+
MAGENTA = new('35', :magenta)
|
|
29
|
+
CYAN = new('36', :cyan)
|
|
30
|
+
RESET = new('0', :reset)
|
|
31
|
+
BOLD = new('1', :bold)
|
|
32
|
+
WHITE = new('97', :white)
|
|
33
|
+
|
|
34
|
+
MAP = {
|
|
35
|
+
red: RED,
|
|
36
|
+
green: GREEN,
|
|
37
|
+
yellow: YELLOW,
|
|
38
|
+
blue: BLUE,
|
|
39
|
+
magenta: MAGENTA,
|
|
40
|
+
cyan: CYAN,
|
|
41
|
+
reset: RESET,
|
|
42
|
+
bold: BOLD,
|
|
43
|
+
}.freeze
|
|
44
|
+
|
|
45
|
+
class InvalidColorName < ArgumentError
|
|
46
|
+
def initialize(name)
|
|
47
|
+
@name = name
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def message
|
|
51
|
+
keys = Color.available.map(&:inspect).join(',')
|
|
52
|
+
"invalid color: #{@name.inspect} " \
|
|
53
|
+
"-- must be one of CLI::UI::Color.available (#{keys})"
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Looks up a color code by name
|
|
58
|
+
#
|
|
59
|
+
# ==== Raises
|
|
60
|
+
# Raises a InvalidColorName if the color is not available
|
|
61
|
+
# You likely need to add it to the +MAP+ or you made a typo
|
|
62
|
+
#
|
|
63
|
+
# ==== Returns
|
|
64
|
+
# Returns a color code
|
|
65
|
+
#
|
|
66
|
+
def self.lookup(name)
|
|
67
|
+
MAP.fetch(name)
|
|
68
|
+
rescue KeyError
|
|
69
|
+
raise InvalidColorName, name
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# All available colors by name
|
|
73
|
+
#
|
|
74
|
+
def self.available
|
|
75
|
+
MAP.keys
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'cli/ui'
|
|
4
|
+
require 'strscan'
|
|
5
|
+
|
|
6
|
+
module CLI
|
|
7
|
+
module UI
|
|
8
|
+
class Formatter
|
|
9
|
+
# Available mappings of formattings
|
|
10
|
+
# To use any of them, you can use {{<key>:<string>}}
|
|
11
|
+
# There are presentational (colours and formatters)
|
|
12
|
+
# and semantic (error, info, command) formatters available
|
|
13
|
+
#
|
|
14
|
+
SGR_MAP = {
|
|
15
|
+
# presentational
|
|
16
|
+
'red' => '31',
|
|
17
|
+
'green' => '32',
|
|
18
|
+
'yellow' => '33',
|
|
19
|
+
# default blue is low-contrast against black in some default terminal color scheme
|
|
20
|
+
'blue' => '94', # 9x = high-intensity fg color x
|
|
21
|
+
'magenta' => '35',
|
|
22
|
+
'cyan' => '36',
|
|
23
|
+
'bold' => '1',
|
|
24
|
+
'italic' => '3',
|
|
25
|
+
'underline' => '4',
|
|
26
|
+
'reset' => '0',
|
|
27
|
+
|
|
28
|
+
# semantic
|
|
29
|
+
'error' => '31', # red
|
|
30
|
+
'success' => '32', # success
|
|
31
|
+
'warning' => '33', # yellow
|
|
32
|
+
'info' => '94', # bright blue
|
|
33
|
+
'command' => '36', # cyan
|
|
34
|
+
}.freeze
|
|
35
|
+
|
|
36
|
+
BEGIN_EXPR = '{{'
|
|
37
|
+
END_EXPR = '}}'
|
|
38
|
+
|
|
39
|
+
SCAN_FUNCNAME = /\w+:/
|
|
40
|
+
SCAN_GLYPH = /.}}/
|
|
41
|
+
SCAN_BODY = /
|
|
42
|
+
.*?
|
|
43
|
+
(
|
|
44
|
+
#{BEGIN_EXPR} |
|
|
45
|
+
#{END_EXPR} |
|
|
46
|
+
\z
|
|
47
|
+
)
|
|
48
|
+
/mx
|
|
49
|
+
|
|
50
|
+
DISCARD_BRACES = 0..-3
|
|
51
|
+
|
|
52
|
+
LITERAL_BRACES = :__literal_braces__
|
|
53
|
+
|
|
54
|
+
class FormatError < StandardError
|
|
55
|
+
attr_accessor :input, :index
|
|
56
|
+
|
|
57
|
+
def initialize(message = nil, input = nil, index = nil)
|
|
58
|
+
super(message)
|
|
59
|
+
@input = input
|
|
60
|
+
@index = index
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Initialize a formatter with text.
|
|
65
|
+
#
|
|
66
|
+
# ===== Attributes
|
|
67
|
+
#
|
|
68
|
+
# * +text+ - the text to format
|
|
69
|
+
#
|
|
70
|
+
def initialize(text)
|
|
71
|
+
@text = text
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Format the text using a map.
|
|
75
|
+
#
|
|
76
|
+
# ===== Attributes
|
|
77
|
+
#
|
|
78
|
+
# * +sgr_map+ - the mapping of the formattings. Defaults to +SGR_MAP+
|
|
79
|
+
#
|
|
80
|
+
# ===== Options
|
|
81
|
+
#
|
|
82
|
+
# * +:enable_color+ - enable color output? Default is true unless output is redirected
|
|
83
|
+
#
|
|
84
|
+
def format(sgr_map = SGR_MAP, enable_color: CLI::UI.enable_color?)
|
|
85
|
+
@nodes = []
|
|
86
|
+
stack = parse_body(StringScanner.new(@text))
|
|
87
|
+
prev_fmt = nil
|
|
88
|
+
content = @nodes.each_with_object(+'') do |(text, fmt), str|
|
|
89
|
+
if prev_fmt != fmt && enable_color
|
|
90
|
+
text = apply_format(text, fmt, sgr_map)
|
|
91
|
+
end
|
|
92
|
+
str << text
|
|
93
|
+
prev_fmt = fmt
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
stack.reject! { |e| e == LITERAL_BRACES }
|
|
97
|
+
|
|
98
|
+
return content unless enable_color
|
|
99
|
+
return content if stack == prev_fmt
|
|
100
|
+
|
|
101
|
+
unless stack.empty? && (@nodes.size.zero? || @nodes.last[1].empty?)
|
|
102
|
+
content << apply_format('', stack, sgr_map)
|
|
103
|
+
end
|
|
104
|
+
content
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
private
|
|
108
|
+
|
|
109
|
+
def apply_format(text, fmt, sgr_map)
|
|
110
|
+
sgr = fmt.each_with_object(+'0') do |name, str|
|
|
111
|
+
next if name == LITERAL_BRACES
|
|
112
|
+
begin
|
|
113
|
+
str << ';' << sgr_map.fetch(name)
|
|
114
|
+
rescue KeyError
|
|
115
|
+
raise FormatError.new(
|
|
116
|
+
"invalid format specifier: #{name}",
|
|
117
|
+
@text,
|
|
118
|
+
-1
|
|
119
|
+
)
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
CLI::UI::ANSI.sgr(sgr) + text
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def parse_expr(sc, stack)
|
|
126
|
+
if match = sc.scan(SCAN_GLYPH)
|
|
127
|
+
glyph_handle = match[0]
|
|
128
|
+
begin
|
|
129
|
+
glyph = Glyph.lookup(glyph_handle)
|
|
130
|
+
emit(glyph.char, [glyph.color.name.to_s])
|
|
131
|
+
rescue Glyph::InvalidGlyphHandle
|
|
132
|
+
index = sc.pos - 2 # rewind past '}}'
|
|
133
|
+
raise FormatError.new(
|
|
134
|
+
"invalid glyph handle at index #{index}: '#{glyph_handle}'",
|
|
135
|
+
@text,
|
|
136
|
+
index
|
|
137
|
+
)
|
|
138
|
+
end
|
|
139
|
+
elsif match = sc.scan(SCAN_FUNCNAME)
|
|
140
|
+
funcname = match.chop
|
|
141
|
+
stack.push(funcname)
|
|
142
|
+
else
|
|
143
|
+
# We read a {{ but it's not apparently Formatter syntax.
|
|
144
|
+
# We could error, but it's nicer to just pass through as text.
|
|
145
|
+
# We do kind of assume that the text will probably have balanced
|
|
146
|
+
# pairs of {{ }} at least.
|
|
147
|
+
emit('{{', stack)
|
|
148
|
+
stack.push(LITERAL_BRACES)
|
|
149
|
+
end
|
|
150
|
+
parse_body(sc, stack)
|
|
151
|
+
stack
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def parse_body(sc, stack = [])
|
|
155
|
+
match = sc.scan(SCAN_BODY)
|
|
156
|
+
if match && match.end_with?(BEGIN_EXPR)
|
|
157
|
+
emit(match[DISCARD_BRACES], stack)
|
|
158
|
+
parse_expr(sc, stack)
|
|
159
|
+
elsif match && match.end_with?(END_EXPR)
|
|
160
|
+
emit(match[DISCARD_BRACES], stack)
|
|
161
|
+
if stack.pop == LITERAL_BRACES
|
|
162
|
+
emit('}}', stack)
|
|
163
|
+
end
|
|
164
|
+
parse_body(sc, stack)
|
|
165
|
+
elsif match
|
|
166
|
+
emit(match, stack)
|
|
167
|
+
else
|
|
168
|
+
emit(sc.rest, stack)
|
|
169
|
+
end
|
|
170
|
+
stack
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def emit(text, stack)
|
|
174
|
+
return if text.nil? || text.empty?
|
|
175
|
+
@nodes << [text, stack.reject { |n| n == LITERAL_BRACES }]
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
end
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
require 'cli/ui'
|
|
2
|
+
|
|
3
|
+
module CLI
|
|
4
|
+
module UI
|
|
5
|
+
module Frame
|
|
6
|
+
class UnnestedFrameException < StandardError; end
|
|
7
|
+
class << self
|
|
8
|
+
DEFAULT_FRAME_COLOR = CLI::UI.resolve_color(:cyan)
|
|
9
|
+
|
|
10
|
+
# Opens a new frame. Can be nested
|
|
11
|
+
# Can be invoked in two ways: block and blockless
|
|
12
|
+
# * In block form, the frame is closed automatically when the block returns
|
|
13
|
+
# * In blockless form, caller MUST call +Frame.close+ when the frame is logically done
|
|
14
|
+
# * Blockless form is strongly discouraged in cases where block form can be made to work
|
|
15
|
+
#
|
|
16
|
+
# https://user-images.githubusercontent.com/3074765/33799861-cb5dcb5c-dd01-11e7-977e-6fad38cee08c.png
|
|
17
|
+
#
|
|
18
|
+
# The return value of the block determines if the block is a "success" or a "failure"
|
|
19
|
+
#
|
|
20
|
+
# ==== Attributes
|
|
21
|
+
#
|
|
22
|
+
# * +text+ - (required) the text/title to output in the frame
|
|
23
|
+
#
|
|
24
|
+
# ==== Options
|
|
25
|
+
#
|
|
26
|
+
# * +:color+ - The color of the frame. Defaults to +DEFAULT_FRAME_COLOR+
|
|
27
|
+
# * +:failure_text+ - If the block failed, what do we output? Defaults to nil
|
|
28
|
+
# * +:success_text+ - If the block succeeds, what do we output? Defaults to nil
|
|
29
|
+
# * +:timing+ - How long did the frame content take? Invalid for blockless. Defaults to true for the block form
|
|
30
|
+
#
|
|
31
|
+
# ==== Example
|
|
32
|
+
#
|
|
33
|
+
# ===== Block Form (Assumes +CLI::UI::StdoutRouter.enable+ has been called)
|
|
34
|
+
#
|
|
35
|
+
# CLI::UI::Frame.open('Open') { puts 'hi' }
|
|
36
|
+
#
|
|
37
|
+
# Output:
|
|
38
|
+
# ┏━━ Open ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
39
|
+
# ┃ hi
|
|
40
|
+
# ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ (0.0s) ━━
|
|
41
|
+
#
|
|
42
|
+
# ===== Blockless Form
|
|
43
|
+
#
|
|
44
|
+
# CLI::UI::Frame.open('Open')
|
|
45
|
+
#
|
|
46
|
+
# Output:
|
|
47
|
+
# ┏━━ Open ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
48
|
+
#
|
|
49
|
+
#
|
|
50
|
+
def open(
|
|
51
|
+
text,
|
|
52
|
+
color: DEFAULT_FRAME_COLOR,
|
|
53
|
+
failure_text: nil,
|
|
54
|
+
success_text: nil,
|
|
55
|
+
timing: nil
|
|
56
|
+
)
|
|
57
|
+
color = CLI::UI.resolve_color(color)
|
|
58
|
+
|
|
59
|
+
unless block_given?
|
|
60
|
+
if failure_text
|
|
61
|
+
raise ArgumentError, "failure_text is not compatible with blockless invocation"
|
|
62
|
+
elsif success_text
|
|
63
|
+
raise ArgumentError, "success_text is not compatible with blockless invocation"
|
|
64
|
+
elsif !timing.nil?
|
|
65
|
+
raise ArgumentError, "timing is not compatible with blockless invocation"
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
timing = true if timing.nil?
|
|
70
|
+
|
|
71
|
+
t_start = Time.now.to_f
|
|
72
|
+
CLI::UI.raw do
|
|
73
|
+
puts edge(text, color: color, first: CLI::UI::Box::Heavy::TL)
|
|
74
|
+
end
|
|
75
|
+
FrameStack.push(color)
|
|
76
|
+
|
|
77
|
+
return unless block_given?
|
|
78
|
+
|
|
79
|
+
closed = false
|
|
80
|
+
begin
|
|
81
|
+
success = false
|
|
82
|
+
success = yield
|
|
83
|
+
rescue
|
|
84
|
+
closed = true
|
|
85
|
+
t_diff = timing ? (Time.now.to_f - t_start) : nil
|
|
86
|
+
close(failure_text, color: :red, elapsed: t_diff)
|
|
87
|
+
raise
|
|
88
|
+
else
|
|
89
|
+
success
|
|
90
|
+
ensure
|
|
91
|
+
unless closed
|
|
92
|
+
t_diff = timing ? (Time.now.to_f - t_start) : nil
|
|
93
|
+
if success != false
|
|
94
|
+
close(success_text, color: color, elapsed: t_diff)
|
|
95
|
+
else
|
|
96
|
+
close(failure_text, color: :red, elapsed: t_diff)
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Closes a frame
|
|
103
|
+
# Automatically called for a block-form +open+
|
|
104
|
+
#
|
|
105
|
+
# ==== Attributes
|
|
106
|
+
#
|
|
107
|
+
# * +text+ - (required) the text/title to output in the frame
|
|
108
|
+
#
|
|
109
|
+
# ==== Options
|
|
110
|
+
#
|
|
111
|
+
# * +:color+ - The color of the frame. Defaults to +DEFAULT_FRAME_COLOR+
|
|
112
|
+
# * +:elapsed+ - How long did the frame take? Defaults to nil
|
|
113
|
+
#
|
|
114
|
+
# ==== Example
|
|
115
|
+
#
|
|
116
|
+
# CLI::UI::Frame.close('Close')
|
|
117
|
+
#
|
|
118
|
+
# Output:
|
|
119
|
+
# ┗━━ Close ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
120
|
+
#
|
|
121
|
+
#
|
|
122
|
+
def close(text, color: DEFAULT_FRAME_COLOR, elapsed: nil)
|
|
123
|
+
color = CLI::UI.resolve_color(color)
|
|
124
|
+
|
|
125
|
+
FrameStack.pop
|
|
126
|
+
kwargs = {}
|
|
127
|
+
if elapsed
|
|
128
|
+
kwargs[:right_text] = "(#{elapsed.round(2)}s)"
|
|
129
|
+
end
|
|
130
|
+
CLI::UI.raw do
|
|
131
|
+
puts edge(text, color: color, first: CLI::UI::Box::Heavy::BL, **kwargs)
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# Adds a divider in a frame
|
|
136
|
+
# Used to separate information within a single frame
|
|
137
|
+
#
|
|
138
|
+
# ==== Attributes
|
|
139
|
+
#
|
|
140
|
+
# * +text+ - (required) the text/title to output in the frame
|
|
141
|
+
#
|
|
142
|
+
# ==== Options
|
|
143
|
+
#
|
|
144
|
+
# * +:color+ - The color of the frame. Defaults to +DEFAULT_FRAME_COLOR+
|
|
145
|
+
#
|
|
146
|
+
# ==== Example
|
|
147
|
+
#
|
|
148
|
+
# CLI::UI::Frame.open('Open') { CLI::UI::Frame.divider('Divider') }
|
|
149
|
+
#
|
|
150
|
+
# Output:
|
|
151
|
+
# ┏━━ Open ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
152
|
+
# ┣━━ Divider ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
153
|
+
# ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
154
|
+
#
|
|
155
|
+
# ==== Raises
|
|
156
|
+
#
|
|
157
|
+
# MUST be inside an open frame or it raises a +UnnestedFrameException+
|
|
158
|
+
#
|
|
159
|
+
def divider(text, color: nil)
|
|
160
|
+
fs_item = FrameStack.pop
|
|
161
|
+
raise UnnestedFrameException, "no frame nesting to unnest" unless fs_item
|
|
162
|
+
color = CLI::UI.resolve_color(color)
|
|
163
|
+
item = CLI::UI.resolve_color(fs_item)
|
|
164
|
+
|
|
165
|
+
CLI::UI.raw do
|
|
166
|
+
puts edge(text, color: (color || item), first: CLI::UI::Box::Heavy::DIV)
|
|
167
|
+
end
|
|
168
|
+
FrameStack.push(item)
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# Determines the prefix of a frame entry taking multi-nested frames into account
|
|
172
|
+
#
|
|
173
|
+
# ==== Options
|
|
174
|
+
#
|
|
175
|
+
# * +:color+ - The color of the prefix. Defaults to +Thread.current[:cliui_frame_color_override]+ or nil
|
|
176
|
+
#
|
|
177
|
+
def prefix(color: nil)
|
|
178
|
+
pfx = +''
|
|
179
|
+
items = FrameStack.items
|
|
180
|
+
items[0..-2].each do |item|
|
|
181
|
+
pfx << CLI::UI.resolve_color(item).code << CLI::UI::Box::Heavy::VERT
|
|
182
|
+
end
|
|
183
|
+
if item = items.last
|
|
184
|
+
c = Thread.current[:cliui_frame_color_override] || color || item
|
|
185
|
+
pfx << CLI::UI.resolve_color(c).code \
|
|
186
|
+
<< CLI::UI::Box::Heavy::VERT << ' ' << CLI::UI::Color::RESET.code
|
|
187
|
+
end
|
|
188
|
+
pfx
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# Override a color for a given thread.
|
|
192
|
+
#
|
|
193
|
+
# ==== Attributes
|
|
194
|
+
#
|
|
195
|
+
# * +color+ - The color to override to
|
|
196
|
+
#
|
|
197
|
+
def with_frame_color_override(color)
|
|
198
|
+
prev = Thread.current[:cliui_frame_color_override]
|
|
199
|
+
Thread.current[:cliui_frame_color_override] = color
|
|
200
|
+
yield
|
|
201
|
+
ensure
|
|
202
|
+
Thread.current[:cliui_frame_color_override] = prev
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# The width of a prefix given the number of Frames in the stack
|
|
206
|
+
#
|
|
207
|
+
def prefix_width
|
|
208
|
+
w = FrameStack.items.size
|
|
209
|
+
w.zero? ? 0 : w + 1
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
private
|
|
213
|
+
|
|
214
|
+
def edge(text, color: raise, first: raise, right_text: nil)
|
|
215
|
+
color = CLI::UI.resolve_color(color)
|
|
216
|
+
text = CLI::UI.resolve_text("{{#{color.name}:#{text}}}")
|
|
217
|
+
|
|
218
|
+
prefix = +''
|
|
219
|
+
FrameStack.items.each do |item|
|
|
220
|
+
prefix << CLI::UI.resolve_color(item).code << CLI::UI::Box::Heavy::VERT
|
|
221
|
+
end
|
|
222
|
+
prefix << color.code << first << (CLI::UI::Box::Heavy::HORZ * 2)
|
|
223
|
+
text ||= ''
|
|
224
|
+
unless text.empty?
|
|
225
|
+
prefix << ' ' << text << ' '
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
termwidth = CLI::UI::Terminal.width
|
|
229
|
+
|
|
230
|
+
suffix = +''
|
|
231
|
+
if right_text
|
|
232
|
+
suffix << ' ' << right_text << ' '
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
suffix_width = CLI::UI::ANSI.printing_width(suffix)
|
|
236
|
+
suffix_end = termwidth - 2
|
|
237
|
+
suffix_start = suffix_end - suffix_width
|
|
238
|
+
|
|
239
|
+
prefix_width = CLI::UI::ANSI.printing_width(prefix)
|
|
240
|
+
prefix_start = 0
|
|
241
|
+
prefix_end = prefix_start + prefix_width
|
|
242
|
+
|
|
243
|
+
if prefix_end > suffix_start
|
|
244
|
+
suffix = ''
|
|
245
|
+
# if prefix_end > termwidth
|
|
246
|
+
# we *could* truncate it, but let's just let it overflow to the
|
|
247
|
+
# next line and call it poor usage of this API.
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
o = +''
|
|
251
|
+
|
|
252
|
+
is_ci = ![0, '', nil].include?(ENV['CI'])
|
|
253
|
+
|
|
254
|
+
# Jumping around the line can cause some unwanted flashes
|
|
255
|
+
o << CLI::UI::ANSI.hide_cursor
|
|
256
|
+
|
|
257
|
+
o << if is_ci
|
|
258
|
+
# In CI, we can't use absolute horizontal positions because of timestamps.
|
|
259
|
+
# So we move around the line by offset from this cursor position.
|
|
260
|
+
CLI::UI::ANSI.cursor_save
|
|
261
|
+
else
|
|
262
|
+
# Outside of CI, we reset to column 1 so that things like ^C don't
|
|
263
|
+
# cause output misformatting.
|
|
264
|
+
"\r"
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
o << color.code
|
|
268
|
+
o << CLI::UI::Box::Heavy::HORZ * termwidth # draw a full line
|
|
269
|
+
o << print_at_x(prefix_start, prefix, is_ci)
|
|
270
|
+
o << color.code
|
|
271
|
+
o << print_at_x(suffix_start, suffix, is_ci)
|
|
272
|
+
o << CLI::UI::Color::RESET.code
|
|
273
|
+
o << CLI::UI::ANSI.show_cursor
|
|
274
|
+
o << "\n"
|
|
275
|
+
|
|
276
|
+
o
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
def print_at_x(x, str, is_ci)
|
|
280
|
+
if is_ci
|
|
281
|
+
CLI::UI::ANSI.cursor_restore + CLI::UI::ANSI.cursor_forward(x) + str
|
|
282
|
+
else
|
|
283
|
+
CLI::UI::ANSI.cursor_horizontal_absolute(1 + x) + str
|
|
284
|
+
end
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
module FrameStack
|
|
288
|
+
ENVVAR = 'CLI_FRAME_STACK'
|
|
289
|
+
|
|
290
|
+
def self.items
|
|
291
|
+
ENV.fetch(ENVVAR, '').split(':').map(&:to_sym)
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
def self.push(item)
|
|
295
|
+
curr = items
|
|
296
|
+
curr << item.name
|
|
297
|
+
ENV[ENVVAR] = curr.join(':')
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
def self.pop
|
|
301
|
+
curr = items
|
|
302
|
+
ret = curr.pop
|
|
303
|
+
ENV[ENVVAR] = curr.join(':')
|
|
304
|
+
ret.nil? ? nil : ret.to_sym
|
|
305
|
+
end
|
|
306
|
+
end
|
|
307
|
+
end
|
|
308
|
+
end
|
|
309
|
+
end
|
|
310
|
+
end
|