shopify-cli 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|