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,36 @@
|
|
|
1
|
+
require 'cli/ui'
|
|
2
|
+
require 'io/console'
|
|
3
|
+
|
|
4
|
+
module CLI
|
|
5
|
+
module UI
|
|
6
|
+
module Terminal
|
|
7
|
+
DEFAULT_WIDTH = 80
|
|
8
|
+
DEFAULT_HEIGHT = 24
|
|
9
|
+
|
|
10
|
+
# Returns the width of the terminal, if possible
|
|
11
|
+
# Otherwise will return 80
|
|
12
|
+
#
|
|
13
|
+
def self.width
|
|
14
|
+
if console = IO.respond_to?(:console) && IO.console
|
|
15
|
+
width = console.winsize[1]
|
|
16
|
+
width.zero? ? DEFAULT_WIDTH : width
|
|
17
|
+
else
|
|
18
|
+
DEFAULT_WIDTH
|
|
19
|
+
end
|
|
20
|
+
rescue Errno::EIO
|
|
21
|
+
DEFAULT_WIDTH
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def self.height
|
|
25
|
+
if console = IO.respond_to?(:console) && IO.console
|
|
26
|
+
height = console.winsize[0]
|
|
27
|
+
height.zero? ? DEFAULT_HEIGHT : height
|
|
28
|
+
else
|
|
29
|
+
DEFAULT_HEIGHT
|
|
30
|
+
end
|
|
31
|
+
rescue Errno::EIO
|
|
32
|
+
DEFAULT_HEIGHT
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'cli/ui'
|
|
4
|
+
|
|
5
|
+
module CLI
|
|
6
|
+
module UI
|
|
7
|
+
# Truncater truncates a string to a provided printable width.
|
|
8
|
+
module Truncater
|
|
9
|
+
PARSE_ROOT = :root
|
|
10
|
+
PARSE_ANSI = :ansi
|
|
11
|
+
PARSE_ESC = :esc
|
|
12
|
+
PARSE_ZWJ = :zwj
|
|
13
|
+
|
|
14
|
+
ESC = 0x1b
|
|
15
|
+
LEFT_SQUARE_BRACKET = 0x5b
|
|
16
|
+
ZWJ = 0x200d # emojipedia.org/emoji-zwj-sequences
|
|
17
|
+
SEMICOLON = 0x3b
|
|
18
|
+
|
|
19
|
+
# EMOJI_RANGE in particular is super inaccurate. This is best-effort.
|
|
20
|
+
# If you need this to be more accurate, we'll almost certainly accept a
|
|
21
|
+
# PR improving it.
|
|
22
|
+
EMOJI_RANGE = 0x1f300..0x1f5ff
|
|
23
|
+
NUMERIC_RANGE = 0x30..0x39
|
|
24
|
+
LC_ALPHA_RANGE = 0x40..0x5a
|
|
25
|
+
UC_ALPHA_RANGE = 0x60..0x71
|
|
26
|
+
|
|
27
|
+
TRUNCATED = "\x1b[0m…"
|
|
28
|
+
|
|
29
|
+
class << self
|
|
30
|
+
def call(text, printing_width)
|
|
31
|
+
return text if text.size <= printing_width
|
|
32
|
+
|
|
33
|
+
width = 0
|
|
34
|
+
mode = PARSE_ROOT
|
|
35
|
+
truncation_index = nil
|
|
36
|
+
|
|
37
|
+
codepoints = text.codepoints
|
|
38
|
+
codepoints.each.with_index do |cp, index|
|
|
39
|
+
case mode
|
|
40
|
+
when PARSE_ROOT
|
|
41
|
+
case cp
|
|
42
|
+
when ESC # non-printable, followed by some more non-printables.
|
|
43
|
+
mode = PARSE_ESC
|
|
44
|
+
when ZWJ # non-printable, followed by another non-printable.
|
|
45
|
+
mode = PARSE_ZWJ
|
|
46
|
+
else
|
|
47
|
+
width += width(cp)
|
|
48
|
+
if width >= printing_width
|
|
49
|
+
truncation_index ||= index
|
|
50
|
+
# it looks like we could break here but we still want the
|
|
51
|
+
# width calculation for the rest of the characters.
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
when PARSE_ESC
|
|
55
|
+
case cp
|
|
56
|
+
when LEFT_SQUARE_BRACKET
|
|
57
|
+
mode = PARSE_ANSI
|
|
58
|
+
else
|
|
59
|
+
mode = PARSE_ROOT
|
|
60
|
+
end
|
|
61
|
+
when PARSE_ANSI
|
|
62
|
+
# ANSI escape codes preeeetty much have the format of:
|
|
63
|
+
# \x1b[0-9;]+[A-Za-z]
|
|
64
|
+
case cp
|
|
65
|
+
when NUMERIC_RANGE, SEMICOLON
|
|
66
|
+
when LC_ALPHA_RANGE, UC_ALPHA_RANGE
|
|
67
|
+
mode = PARSE_ROOT
|
|
68
|
+
else
|
|
69
|
+
# unexpected. let's just go back to the root state I guess?
|
|
70
|
+
mode = PARSE_ROOT
|
|
71
|
+
end
|
|
72
|
+
when PARSE_ZWJ
|
|
73
|
+
# consume any character and consider it as having no width
|
|
74
|
+
# width(x+ZWJ+y) = width(x).
|
|
75
|
+
mode = PARSE_ROOT
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Without the `width <= printing_width` check, we truncate
|
|
80
|
+
# "foo\x1b[0m" for a width of 3, but it should not be truncated.
|
|
81
|
+
# It's specifically for the case where we decided "Yes, this is the
|
|
82
|
+
# point at which we'd have to add a truncation!" but it's actually
|
|
83
|
+
# the end of the string.
|
|
84
|
+
return text if !truncation_index || width <= printing_width
|
|
85
|
+
|
|
86
|
+
codepoints[0...truncation_index].pack("U*") + TRUNCATED
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
private
|
|
90
|
+
|
|
91
|
+
def width(printable_codepoint)
|
|
92
|
+
case printable_codepoint
|
|
93
|
+
when EMOJI_RANGE
|
|
94
|
+
2
|
|
95
|
+
else
|
|
96
|
+
1
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
691028e47d022a29405b7f0ccc8683c9d062c502
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
##
|
|
2
|
+
# {SmartProperties} can be used to easily build more full-fledged accessors
|
|
3
|
+
# for standard Ruby classes. In contrast to regular accessors,
|
|
4
|
+
# {SmartProperties} support validation and conversion of input data, as well
|
|
5
|
+
# as, the specification of default values. Additionally, individual
|
|
6
|
+
# {SmartProperties} can be marked as required. This causes the runtime to
|
|
7
|
+
# throw an +ArgumentError+ whenever a required property has not been
|
|
8
|
+
# specified.
|
|
9
|
+
#
|
|
10
|
+
# In order to use {SmartProperties}, simply include the {SmartProperties}
|
|
11
|
+
# module and use the {ClassMethods#property} method to define properties.
|
|
12
|
+
#
|
|
13
|
+
# @see ClassMethods#property
|
|
14
|
+
# More information on how to configure properties
|
|
15
|
+
#
|
|
16
|
+
# @example Definition of a property that makes use of all {SmartProperties} features.
|
|
17
|
+
#
|
|
18
|
+
# property :language_code, :accepts => [:de, :en],
|
|
19
|
+
# :converts => :to_sym,
|
|
20
|
+
# :default => :de,
|
|
21
|
+
# :required => true
|
|
22
|
+
#
|
|
23
|
+
module SmartProperties
|
|
24
|
+
module ClassMethods
|
|
25
|
+
##
|
|
26
|
+
# Returns a class's smart properties. This includes the properties that
|
|
27
|
+
# have been defined in the parent classes.
|
|
28
|
+
#
|
|
29
|
+
# @return [Hash<String, Property>] A map of property names to property instances.
|
|
30
|
+
#
|
|
31
|
+
def properties
|
|
32
|
+
@_smart_properties ||= PropertyCollection.for(self)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
##
|
|
36
|
+
# Defines a new property from a name and a set of options. This results
|
|
37
|
+
# results in creating an accessor that has additional features:
|
|
38
|
+
#
|
|
39
|
+
# 1. Validation of input data by specifiying the +:accepts+ option:
|
|
40
|
+
# If you use a class as value for this option, the setter will check
|
|
41
|
+
# if the value it is about to assign is of this type. If you use an
|
|
42
|
+
# array, the setter will check if the value it is about to assign is
|
|
43
|
+
# included in this array. Finally, if you specify a block, it will
|
|
44
|
+
# invoke the block with the value it is about to assign and check if
|
|
45
|
+
# the block returns a thruthy value, meaning anything but +false+ and
|
|
46
|
+
# +nil+.
|
|
47
|
+
#
|
|
48
|
+
# 2. Conversion of input data by specifiying the +:converts+ option:
|
|
49
|
+
# If you use provide a symbol as value for this option, the setter will
|
|
50
|
+
# invoke this method on the object it is about to assign and take the
|
|
51
|
+
# result of this call instead. If you provide a block, it will invoke
|
|
52
|
+
# the block with the value it is about to assign and take the result
|
|
53
|
+
# of the block instead.
|
|
54
|
+
#
|
|
55
|
+
# 3. Providing a default value by specifiying the +:default+ option.
|
|
56
|
+
#
|
|
57
|
+
# 4. Forcing a property to be present by setting the +:required+ option
|
|
58
|
+
# to true.
|
|
59
|
+
#
|
|
60
|
+
#
|
|
61
|
+
# @param [Symbol] name the name of the property
|
|
62
|
+
#
|
|
63
|
+
# @param [Hash] options the list of options used to configure the property
|
|
64
|
+
# @option options [Array, Class, Proc] :accepts
|
|
65
|
+
# specifies how the validation is done
|
|
66
|
+
# @option options [Proc, Symbol] :converts
|
|
67
|
+
# specifies how the conversion is done
|
|
68
|
+
# @option options :default
|
|
69
|
+
# specifies the default value of the property
|
|
70
|
+
# @option options [true, false] :required
|
|
71
|
+
# specifies whether or not this property is required
|
|
72
|
+
#
|
|
73
|
+
# @return [Property] The defined property.
|
|
74
|
+
#
|
|
75
|
+
# @example Definition of a property that makes use of all {SmartProperties} features.
|
|
76
|
+
#
|
|
77
|
+
# property :language_code, :accepts => [:de, :en],
|
|
78
|
+
# :converts => :to_sym,
|
|
79
|
+
# :default => :de,
|
|
80
|
+
# :required => true
|
|
81
|
+
#
|
|
82
|
+
def property(name, options = {})
|
|
83
|
+
properties[name] = Property.define(self, name, options)
|
|
84
|
+
end
|
|
85
|
+
protected :property
|
|
86
|
+
|
|
87
|
+
def property!(name, options = {})
|
|
88
|
+
options[:required] = true
|
|
89
|
+
property(name, options)
|
|
90
|
+
end
|
|
91
|
+
protected :property!
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
class << self
|
|
95
|
+
private
|
|
96
|
+
|
|
97
|
+
##
|
|
98
|
+
# Extends the class, which this module is included in, with a property
|
|
99
|
+
# method to define properties.
|
|
100
|
+
#
|
|
101
|
+
# @param [Class] base the class this module is included in
|
|
102
|
+
#
|
|
103
|
+
def included(base)
|
|
104
|
+
base.extend(ClassMethods)
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
##
|
|
109
|
+
# Implements a key-value enabled constructor that acts as default
|
|
110
|
+
# constructor for all {SmartProperties}-enabled classes. Positional arguments
|
|
111
|
+
# or keyword arguments that do not correspond to a property are forwarded to
|
|
112
|
+
# the super class constructor.
|
|
113
|
+
#
|
|
114
|
+
# @param [Hash] attrs the set of attributes that is used for initialization
|
|
115
|
+
#
|
|
116
|
+
# @raise [SmartProperties::ConstructorArgumentForwardingError] when unknown arguments were supplied that could not be processed by the super class initializer either.
|
|
117
|
+
# @raise [SmartProperties::InitializationError] when incorrect values were supplied or required values weren't been supplied.
|
|
118
|
+
#
|
|
119
|
+
def initialize(*args, &block)
|
|
120
|
+
attrs = args.last.is_a?(Hash) ? args.pop.dup : {}
|
|
121
|
+
properties = self.class.properties
|
|
122
|
+
|
|
123
|
+
# Track missing properties
|
|
124
|
+
missing_properties = []
|
|
125
|
+
|
|
126
|
+
# Set values
|
|
127
|
+
properties.each do |name, property|
|
|
128
|
+
if attrs.key?(name)
|
|
129
|
+
property.set(self, attrs.delete(name))
|
|
130
|
+
elsif attrs.key?(name.to_s)
|
|
131
|
+
property.set(self, attrs.delete(name.to_s))
|
|
132
|
+
else
|
|
133
|
+
missing_properties.push(property)
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Call the super constructor and forward unprocessed arguments
|
|
138
|
+
begin
|
|
139
|
+
attrs.empty? ? super(*args) : super(*args.dup.push(attrs))
|
|
140
|
+
rescue ArgumentError
|
|
141
|
+
raise SmartProperties::ConstructorArgumentForwardingError.new(args, attrs)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Execute configuration block
|
|
145
|
+
block.call(self) if block
|
|
146
|
+
|
|
147
|
+
# Set default values for missing properties
|
|
148
|
+
missing_properties.delete_if { |property| property.set_default(self) }
|
|
149
|
+
|
|
150
|
+
# Recheck - cannot be done while assigning default values because
|
|
151
|
+
# one property might depend on the default value of another property
|
|
152
|
+
missing_properties.delete_if { |property| property.present?(self) || property.optional?(self) }
|
|
153
|
+
|
|
154
|
+
raise SmartProperties::InitializationError.new(self, missing_properties) unless missing_properties.empty?
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def [](name)
|
|
158
|
+
return if name.nil?
|
|
159
|
+
name = name.to_sym
|
|
160
|
+
reader = self.class.properties[name].reader
|
|
161
|
+
public_send(reader) if self.class.properties.key?(name)
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def []=(name, value)
|
|
165
|
+
return if name.nil?
|
|
166
|
+
public_send(:"#{name.to_sym}=", value) if self.class.properties.key?(name)
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
require_relative 'smart_properties/property_collection'
|
|
171
|
+
require_relative 'smart_properties/property'
|
|
172
|
+
require_relative 'smart_properties/errors'
|
|
173
|
+
require_relative 'smart_properties/version'
|
|
174
|
+
require_relative 'smart_properties/validations'
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
module SmartProperties
|
|
2
|
+
class Error < ::ArgumentError; end
|
|
3
|
+
class ConfigurationError < Error; end
|
|
4
|
+
|
|
5
|
+
class AssignmentError < Error
|
|
6
|
+
attr_accessor :sender
|
|
7
|
+
attr_accessor :property
|
|
8
|
+
|
|
9
|
+
def initialize(sender, property, message)
|
|
10
|
+
@sender = sender
|
|
11
|
+
@property = property
|
|
12
|
+
super(message)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
class ConstructorArgumentForwardingError < Error
|
|
17
|
+
def initialize(positional_arguments, keyword_arguments)
|
|
18
|
+
argument_description = [
|
|
19
|
+
generate_description("positional", positional_arguments.count),
|
|
20
|
+
generate_description("keyword", keyword_arguments.count)
|
|
21
|
+
].compact
|
|
22
|
+
|
|
23
|
+
arguments = positional_arguments + keyword_arguments.map { |name, value| "#{name}: #{value}" }
|
|
24
|
+
|
|
25
|
+
super "Forwarding the following %s failed: %s" % [
|
|
26
|
+
argument_description.join(" and "),
|
|
27
|
+
arguments.join(", ")
|
|
28
|
+
]
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def generate_description(argument_type, argument_number)
|
|
34
|
+
case argument_number
|
|
35
|
+
when 0
|
|
36
|
+
nil
|
|
37
|
+
when 1
|
|
38
|
+
"#{argument_type} argument"
|
|
39
|
+
else
|
|
40
|
+
"#{argument_number} #{argument_type} arguments"
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
class MissingValueError < AssignmentError
|
|
46
|
+
def initialize(sender, property)
|
|
47
|
+
super(
|
|
48
|
+
sender,
|
|
49
|
+
property,
|
|
50
|
+
"%s requires the property %s to be set" % [
|
|
51
|
+
sender.class.name,
|
|
52
|
+
property.name
|
|
53
|
+
]
|
|
54
|
+
)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def to_hash
|
|
58
|
+
Hash[property.name, "must be set"]
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
class InvalidValueError < AssignmentError
|
|
63
|
+
attr_accessor :value
|
|
64
|
+
|
|
65
|
+
def initialize(sender, property, value)
|
|
66
|
+
@value = value
|
|
67
|
+
|
|
68
|
+
super(
|
|
69
|
+
sender,
|
|
70
|
+
property,
|
|
71
|
+
"%s does not accept %s as value for the property %s. Only accepts: %s" % [
|
|
72
|
+
sender.class.name,
|
|
73
|
+
value.inspect,
|
|
74
|
+
property.name,
|
|
75
|
+
accepter_message(sender, property)
|
|
76
|
+
]
|
|
77
|
+
)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def to_hash
|
|
81
|
+
Hash[property.name, "does not accept %s as value" % value.inspect]
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
private
|
|
85
|
+
|
|
86
|
+
def accepter_message(sender, property)
|
|
87
|
+
accepter = property.accepter
|
|
88
|
+
if accepter.is_a?(Proc)
|
|
89
|
+
return "Values passing lambda defined in #{accepter.source_location.join(' at line ')}"
|
|
90
|
+
end
|
|
91
|
+
accepter
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
class InitializationError < Error
|
|
96
|
+
attr_accessor :sender
|
|
97
|
+
attr_accessor :properties
|
|
98
|
+
|
|
99
|
+
def initialize(sender, properties)
|
|
100
|
+
@sender = sender
|
|
101
|
+
@properties = properties
|
|
102
|
+
super(
|
|
103
|
+
"%s requires the following properties to be set: %s" % [
|
|
104
|
+
sender.class.name,
|
|
105
|
+
properties.map(&:name).sort.join(', ')
|
|
106
|
+
]
|
|
107
|
+
)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def to_hash
|
|
111
|
+
properties.each_with_object({}) { |property, errors| errors[property.name] = "must be set" }
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
module SmartProperties
|
|
2
|
+
class Property
|
|
3
|
+
MODULE_REFERENCE = :"@_smart_properties_method_scope"
|
|
4
|
+
|
|
5
|
+
attr_reader :name
|
|
6
|
+
attr_reader :converter
|
|
7
|
+
attr_reader :accepter
|
|
8
|
+
attr_reader :reader
|
|
9
|
+
attr_reader :instance_variable_name
|
|
10
|
+
attr_reader :writable
|
|
11
|
+
|
|
12
|
+
def self.define(scope, name, options = {})
|
|
13
|
+
new(name, options).tap { |p| p.define(scope) }
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def initialize(name, attrs = {})
|
|
17
|
+
attrs = attrs.dup
|
|
18
|
+
|
|
19
|
+
@name = name.to_sym
|
|
20
|
+
@default = attrs.delete(:default)
|
|
21
|
+
@converter = attrs.delete(:converts)
|
|
22
|
+
@accepter = attrs.delete(:accepts)
|
|
23
|
+
@required = attrs.delete(:required)
|
|
24
|
+
@reader = attrs.delete(:reader)
|
|
25
|
+
@writable = attrs.delete(:writable)
|
|
26
|
+
@reader ||= @name
|
|
27
|
+
|
|
28
|
+
@instance_variable_name = :"@#{name}"
|
|
29
|
+
|
|
30
|
+
unless attrs.empty?
|
|
31
|
+
raise ConfigurationError, "SmartProperties do not support the following configuration options: #{attrs.keys.map { |m| m.to_s }.sort.join(', ')}."
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def required?(scope)
|
|
36
|
+
@required.kind_of?(Proc) ? scope.instance_exec(&@required) : !!@required
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def optional?(scope)
|
|
40
|
+
!required?(scope)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def missing?(scope)
|
|
44
|
+
required?(scope) && !present?(scope)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def present?(scope)
|
|
48
|
+
!null_object?(get(scope))
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def writable?
|
|
52
|
+
return true if @writable.nil?
|
|
53
|
+
@writable
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def convert(scope, value)
|
|
57
|
+
return value unless converter
|
|
58
|
+
return value if null_object?(value)
|
|
59
|
+
|
|
60
|
+
case converter
|
|
61
|
+
when Symbol
|
|
62
|
+
converter.to_proc.call(value)
|
|
63
|
+
else
|
|
64
|
+
scope.instance_exec(value, &converter)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def default(scope)
|
|
69
|
+
@default.kind_of?(Proc) ? scope.instance_exec(&@default) : @default
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def accepts?(value, scope)
|
|
73
|
+
return true unless accepter
|
|
74
|
+
return true if null_object?(value)
|
|
75
|
+
|
|
76
|
+
if accepter.respond_to?(:to_proc)
|
|
77
|
+
!!scope.instance_exec(value, &accepter)
|
|
78
|
+
else
|
|
79
|
+
Array(accepter).any? { |accepter| accepter === value }
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def prepare(scope, value)
|
|
84
|
+
required = required?(scope)
|
|
85
|
+
raise MissingValueError.new(scope, self) if required && null_object?(value)
|
|
86
|
+
value = convert(scope, value)
|
|
87
|
+
raise MissingValueError.new(scope, self) if required && null_object?(value)
|
|
88
|
+
raise InvalidValueError.new(scope, self, value) unless accepts?(value, scope)
|
|
89
|
+
value
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def define(klass)
|
|
93
|
+
property = self
|
|
94
|
+
|
|
95
|
+
scope =
|
|
96
|
+
if klass.instance_variable_defined?(MODULE_REFERENCE)
|
|
97
|
+
klass.instance_variable_get(MODULE_REFERENCE)
|
|
98
|
+
else
|
|
99
|
+
m = Module.new
|
|
100
|
+
klass.send(:include, m)
|
|
101
|
+
klass.instance_variable_set(MODULE_REFERENCE, m)
|
|
102
|
+
m
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
scope.send(:define_method, reader) do
|
|
106
|
+
property.get(self)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
if writable?
|
|
110
|
+
scope.send(:define_method, :"#{name}=") do |value|
|
|
111
|
+
property.set(self, value)
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def set(scope, value)
|
|
117
|
+
scope.instance_variable_set(instance_variable_name, prepare(scope, value))
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def set_default(scope)
|
|
121
|
+
return false if present?(scope)
|
|
122
|
+
|
|
123
|
+
default_value = default(scope)
|
|
124
|
+
return false if null_object?(default_value)
|
|
125
|
+
|
|
126
|
+
set(scope, default_value)
|
|
127
|
+
true
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def get(scope)
|
|
131
|
+
return nil unless scope.instance_variable_defined?(instance_variable_name)
|
|
132
|
+
scope.instance_variable_get(instance_variable_name)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def to_h
|
|
136
|
+
{
|
|
137
|
+
accepter: @accepter,
|
|
138
|
+
converter: @converter,
|
|
139
|
+
default: @default,
|
|
140
|
+
instance_variable_name: @instance_variable_name,
|
|
141
|
+
name: @name,
|
|
142
|
+
reader: @reader,
|
|
143
|
+
required: @required
|
|
144
|
+
}
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
private
|
|
148
|
+
|
|
149
|
+
def null_object?(object)
|
|
150
|
+
object.nil?
|
|
151
|
+
rescue NoMethodError => error
|
|
152
|
+
# BasicObject does not respond to #nil? by default, so we need to double
|
|
153
|
+
# check if somebody implemented it and it fails internally or if the
|
|
154
|
+
# error occured because the method is actually not present. In the former
|
|
155
|
+
# case, we want to raise the exception because there is something wrong
|
|
156
|
+
# with the implementation of object#nil?. In the latter case we treat the
|
|
157
|
+
# object as truthy because we don't know better.
|
|
158
|
+
raise error if (class << object; self; end).public_instance_methods.include?(:nil?)
|
|
159
|
+
false
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|