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.
Files changed (273) hide show
  1. checksums.yaml +7 -0
  2. data/.github/CODEOWNERS +1 -0
  3. data/.github/CODE_OF_CONDUCT.md +73 -0
  4. data/.github/CONTRIBUTING.md +51 -0
  5. data/.github/DESIGN.md +153 -0
  6. data/.github/ISSUE_TEMPLATE.md +38 -0
  7. data/.github/PULL_REQUEST_TEMPLATE.md +22 -0
  8. data/.github/probots.yml +3 -0
  9. data/.gitignore +19 -0
  10. data/.rubocop.yml +47 -0
  11. data/.ruby-version +1 -0
  12. data/.travis.yml +12 -0
  13. data/Gemfile +22 -0
  14. data/Gemfile.lock +77 -0
  15. data/LICENSE.md +7 -0
  16. data/README.md +13 -0
  17. data/Rakefile +101 -0
  18. data/SECURITY.md +59 -0
  19. data/Vagrantfile +17 -0
  20. data/bin/load_shopify.rb +20 -0
  21. data/bin/shopify +32 -0
  22. data/dev.yml +17 -0
  23. data/docs/Gemfile +5 -0
  24. data/docs/Gemfile.lock +248 -0
  25. data/docs/_config.yml +16 -0
  26. data/docs/_data/nav.yml +26 -0
  27. data/docs/_includes/footer.html +15 -0
  28. data/docs/_includes/head.html +19 -0
  29. data/docs/_includes/sidebar_nav.html +22 -0
  30. data/docs/_includes/toc.html +112 -0
  31. data/docs/_layouts/default.html +79 -0
  32. data/docs/app/node/commands/index.md +82 -0
  33. data/docs/app/node/index.md +35 -0
  34. data/docs/app/rails/commands/index.md +80 -0
  35. data/docs/app/rails/index.md +36 -0
  36. data/docs/core/index.md +70 -0
  37. data/docs/css/docs.css +157 -0
  38. data/docs/getting-started/index.md +61 -0
  39. data/docs/help/start-app/index.md +6 -0
  40. data/docs/images/header.png +0 -0
  41. data/docs/index.md +27 -0
  42. data/docs/installing-ruby.md +28 -0
  43. data/ext/shopify-cli/extconf.rb +27 -0
  44. data/install.sh +7 -0
  45. data/lib/docgen/class_template.md.erb +81 -0
  46. data/lib/docgen/index_template.md.erb +5 -0
  47. data/lib/docgen/markdown.rb +101 -0
  48. data/lib/graphql/admin_introspection.graphql +87 -0
  49. data/lib/graphql/all_organizations.graphql +19 -0
  50. data/lib/graphql/all_orgs_with_apps.graphql +30 -0
  51. data/lib/graphql/api_versions.graphql +6 -0
  52. data/lib/graphql/convert_dev_to_test_store.graphql +10 -0
  53. data/lib/graphql/create_app.graphql +20 -0
  54. data/lib/graphql/create_customer.graphql +9 -0
  55. data/lib/graphql/create_draft_order.graphql +8 -0
  56. data/lib/graphql/create_product.graphql +9 -0
  57. data/lib/graphql/extension_create.graphql +21 -0
  58. data/lib/graphql/extension_update_draft.graphql +18 -0
  59. data/lib/graphql/find_organization.graphql +17 -0
  60. data/lib/graphql/get_app_urls.graphql +6 -0
  61. data/lib/graphql/update_dashboard_urls.graphql +8 -0
  62. data/lib/project_types/extension/cli.rb +71 -0
  63. data/lib/project_types/extension/commands/build.rb +29 -0
  64. data/lib/project_types/extension/commands/create.rb +49 -0
  65. data/lib/project_types/extension/commands/extension_command.rb +22 -0
  66. data/lib/project_types/extension/commands/push.rb +69 -0
  67. data/lib/project_types/extension/commands/register.rb +78 -0
  68. data/lib/project_types/extension/commands/serve.rb +24 -0
  69. data/lib/project_types/extension/commands/tunnel.rb +69 -0
  70. data/lib/project_types/extension/extension_project.rb +85 -0
  71. data/lib/project_types/extension/extension_project_keys.rb +10 -0
  72. data/lib/project_types/extension/features/argo.rb +48 -0
  73. data/lib/project_types/extension/features/argo_dependencies.rb +28 -0
  74. data/lib/project_types/extension/features/argo_setup.rb +54 -0
  75. data/lib/project_types/extension/features/argo_setup_step.rb +31 -0
  76. data/lib/project_types/extension/features/argo_setup_steps.rb +53 -0
  77. data/lib/project_types/extension/features/tunnel_url.rb +20 -0
  78. data/lib/project_types/extension/forms/create.rb +52 -0
  79. data/lib/project_types/extension/forms/register.rb +48 -0
  80. data/lib/project_types/extension/messages/message_loading.rb +37 -0
  81. data/lib/project_types/extension/messages/messages.rb +126 -0
  82. data/lib/project_types/extension/models/app.rb +14 -0
  83. data/lib/project_types/extension/models/registration.rb +19 -0
  84. data/lib/project_types/extension/models/type.rb +76 -0
  85. data/lib/project_types/extension/models/types/checkout_post_purchase.rb +20 -0
  86. data/lib/project_types/extension/models/types/subscription_management.rb +20 -0
  87. data/lib/project_types/extension/models/validation_error.rb +17 -0
  88. data/lib/project_types/extension/models/version.rb +15 -0
  89. data/lib/project_types/extension/tasks/converters/registration_converter.rb +26 -0
  90. data/lib/project_types/extension/tasks/converters/validation_error_converter.rb +25 -0
  91. data/lib/project_types/extension/tasks/converters/version_converter.rb +28 -0
  92. data/lib/project_types/extension/tasks/create_extension.rb +31 -0
  93. data/lib/project_types/extension/tasks/get_apps.rb +34 -0
  94. data/lib/project_types/extension/tasks/update_draft.rb +29 -0
  95. data/lib/project_types/extension/tasks/user_errors.rb +45 -0
  96. data/lib/project_types/node/cli.rb +37 -0
  97. data/lib/project_types/node/commands/create.rb +117 -0
  98. data/lib/project_types/node/commands/deploy.rb +22 -0
  99. data/lib/project_types/node/commands/deploy/heroku.rb +91 -0
  100. data/lib/project_types/node/commands/generate.rb +51 -0
  101. data/lib/project_types/node/commands/generate/billing.rb +37 -0
  102. data/lib/project_types/node/commands/generate/page.rb +55 -0
  103. data/lib/project_types/node/commands/generate/webhook.rb +33 -0
  104. data/lib/project_types/node/commands/open.rb +16 -0
  105. data/lib/project_types/node/commands/populate.rb +23 -0
  106. data/lib/project_types/node/commands/populate/customer.rb +31 -0
  107. data/lib/project_types/node/commands/populate/draft_order.rb +28 -0
  108. data/lib/project_types/node/commands/populate/product.rb +30 -0
  109. data/lib/project_types/node/commands/serve.rb +45 -0
  110. data/lib/project_types/node/commands/tunnel.rb +39 -0
  111. data/lib/project_types/node/forms/create.rb +87 -0
  112. data/lib/project_types/node/messages/messages.rb +260 -0
  113. data/lib/project_types/rails/cli.rb +41 -0
  114. data/lib/project_types/rails/commands/create.rb +126 -0
  115. data/lib/project_types/rails/commands/deploy.rb +22 -0
  116. data/lib/project_types/rails/commands/deploy/heroku.rb +113 -0
  117. data/lib/project_types/rails/commands/generate.rb +49 -0
  118. data/lib/project_types/rails/commands/generate/webhook.rb +39 -0
  119. data/lib/project_types/rails/commands/open.rb +16 -0
  120. data/lib/project_types/rails/commands/populate.rb +23 -0
  121. data/lib/project_types/rails/commands/populate/customer.rb +31 -0
  122. data/lib/project_types/rails/commands/populate/draft_order.rb +28 -0
  123. data/lib/project_types/rails/commands/populate/product.rb +30 -0
  124. data/lib/project_types/rails/commands/serve.rb +47 -0
  125. data/lib/project_types/rails/commands/tunnel.rb +39 -0
  126. data/lib/project_types/rails/forms/create.rb +116 -0
  127. data/lib/project_types/rails/gem.rb +56 -0
  128. data/lib/project_types/rails/messages/messages.rb +283 -0
  129. data/lib/project_types/rails/ruby.rb +17 -0
  130. data/lib/project_types/script/cli.rb +76 -0
  131. data/lib/project_types/script/commands/create.rb +45 -0
  132. data/lib/project_types/script/commands/disable.rb +36 -0
  133. data/lib/project_types/script/commands/enable.rb +46 -0
  134. data/lib/project_types/script/commands/push.rb +39 -0
  135. data/lib/project_types/script/config/extension_points.yml +18 -0
  136. data/lib/project_types/script/errors.rb +16 -0
  137. data/lib/project_types/script/forms/create.rb +29 -0
  138. data/lib/project_types/script/forms/enable.rb +24 -0
  139. data/lib/project_types/script/forms/push.rb +19 -0
  140. data/lib/project_types/script/forms/script_form.rb +66 -0
  141. data/lib/project_types/script/graphql/app_script_update_or_create.graphql +27 -0
  142. data/lib/project_types/script/graphql/script_service_proxy.graphql +8 -0
  143. data/lib/project_types/script/graphql/shop_script_delete.graphql +14 -0
  144. data/lib/project_types/script/graphql/shop_script_update_or_create.graphql +28 -0
  145. data/lib/project_types/script/layers/application/build_script.rb +43 -0
  146. data/lib/project_types/script/layers/application/create_script.rb +47 -0
  147. data/lib/project_types/script/layers/application/disable_script.rb +19 -0
  148. data/lib/project_types/script/layers/application/enable_script.rb +21 -0
  149. data/lib/project_types/script/layers/application/extension_points.rb +17 -0
  150. data/lib/project_types/script/layers/application/project_dependencies.rb +34 -0
  151. data/lib/project_types/script/layers/application/push_script.rb +30 -0
  152. data/lib/project_types/script/layers/domain/errors.rb +25 -0
  153. data/lib/project_types/script/layers/domain/extension_point.rb +29 -0
  154. data/lib/project_types/script/layers/domain/push_package.rb +29 -0
  155. data/lib/project_types/script/layers/domain/script.rb +18 -0
  156. data/lib/project_types/script/layers/infrastructure/assemblyscript_dependency_manager.rb +73 -0
  157. data/lib/project_types/script/layers/infrastructure/assemblyscript_tsconfig.rb +38 -0
  158. data/lib/project_types/script/layers/infrastructure/assemblyscript_wasm_builder.rb +39 -0
  159. data/lib/project_types/script/layers/infrastructure/dependency_manager.rb +36 -0
  160. data/lib/project_types/script/layers/infrastructure/errors.rb +38 -0
  161. data/lib/project_types/script/layers/infrastructure/extension_point_repository.rb +31 -0
  162. data/lib/project_types/script/layers/infrastructure/push_package_repository.rb +47 -0
  163. data/lib/project_types/script/layers/infrastructure/script_builder.rb +34 -0
  164. data/lib/project_types/script/layers/infrastructure/script_repository.rb +89 -0
  165. data/lib/project_types/script/layers/infrastructure/script_service.rb +165 -0
  166. data/lib/project_types/script/layers/infrastructure/test_suite_repository.rb +59 -0
  167. data/lib/project_types/script/messages/messages.rb +204 -0
  168. data/lib/project_types/script/script_project.rb +37 -0
  169. data/lib/project_types/script/templates/ts/as-pect.config.js +21 -0
  170. data/lib/project_types/script/ui/error_handler.rb +136 -0
  171. data/lib/project_types/script/ui/strict_spinner.rb +22 -0
  172. data/lib/rubygems_plugin.rb +18 -0
  173. data/lib/shopify-cli/admin_api.rb +99 -0
  174. data/lib/shopify-cli/admin_api/populate_resource_command.rb +165 -0
  175. data/lib/shopify-cli/admin_api/schema.rb +32 -0
  176. data/lib/shopify-cli/api.rb +104 -0
  177. data/lib/shopify-cli/command.rb +67 -0
  178. data/lib/shopify-cli/commands.rb +28 -0
  179. data/lib/shopify-cli/commands/connect.rb +108 -0
  180. data/lib/shopify-cli/commands/create.rb +50 -0
  181. data/lib/shopify-cli/commands/help.rb +79 -0
  182. data/lib/shopify-cli/commands/logout.rb +23 -0
  183. data/lib/shopify-cli/commands/system.rb +135 -0
  184. data/lib/shopify-cli/commands/version.rb +15 -0
  185. data/lib/shopify-cli/context.rb +372 -0
  186. data/lib/shopify-cli/core.rb +9 -0
  187. data/lib/shopify-cli/core/entry_point.rb +40 -0
  188. data/lib/shopify-cli/core/executor.rb +21 -0
  189. data/lib/shopify-cli/core/help_resolver.rb +20 -0
  190. data/lib/shopify-cli/core/monorail.rb +118 -0
  191. data/lib/shopify-cli/db.rb +114 -0
  192. data/lib/shopify-cli/form.rb +40 -0
  193. data/lib/shopify-cli/git.rb +141 -0
  194. data/lib/shopify-cli/helpers.rb +5 -0
  195. data/lib/shopify-cli/helpers/haikunator.rb +92 -0
  196. data/lib/shopify-cli/heroku.rb +97 -0
  197. data/lib/shopify-cli/js_deps.rb +110 -0
  198. data/lib/shopify-cli/js_system.rb +98 -0
  199. data/lib/shopify-cli/messages/messages.rb +287 -0
  200. data/lib/shopify-cli/oauth.rb +192 -0
  201. data/lib/shopify-cli/oauth/servlet.rb +61 -0
  202. data/lib/shopify-cli/options.rb +40 -0
  203. data/lib/shopify-cli/packager.rb +116 -0
  204. data/lib/shopify-cli/partners_api.rb +114 -0
  205. data/lib/shopify-cli/partners_api/organizations.rb +32 -0
  206. data/lib/shopify-cli/process_supervision.rb +187 -0
  207. data/lib/shopify-cli/project.rb +191 -0
  208. data/lib/shopify-cli/project_type.rb +83 -0
  209. data/lib/shopify-cli/resources.rb +5 -0
  210. data/lib/shopify-cli/resources/env_file.rb +96 -0
  211. data/lib/shopify-cli/sub_command.rb +15 -0
  212. data/lib/shopify-cli/task.rb +10 -0
  213. data/lib/shopify-cli/tasks.rb +32 -0
  214. data/lib/shopify-cli/tasks/create_api_client.rb +29 -0
  215. data/lib/shopify-cli/tasks/ensure_dev_store.rb +41 -0
  216. data/lib/shopify-cli/tasks/ensure_env.rb +31 -0
  217. data/lib/shopify-cli/tasks/ensure_loopback_url.rb +20 -0
  218. data/lib/shopify-cli/tasks/update_dashboard_urls.rb +44 -0
  219. data/lib/shopify-cli/tunnel.rb +154 -0
  220. data/lib/shopify-cli/version.rb +3 -0
  221. data/lib/shopify_cli.rb +132 -0
  222. data/shopify-cli.gemspec +40 -0
  223. data/shopify.fish +12 -0
  224. data/shopify.sh +11 -0
  225. data/vendor/deps/cli-kit/REVISION +1 -0
  226. data/vendor/deps/cli-kit/lib/cli/kit.rb +60 -0
  227. data/vendor/deps/cli-kit/lib/cli/kit/autocall.rb +21 -0
  228. data/vendor/deps/cli-kit/lib/cli/kit/base_command.rb +49 -0
  229. data/vendor/deps/cli-kit/lib/cli/kit/command_registry.rb +94 -0
  230. data/vendor/deps/cli-kit/lib/cli/kit/config.rb +133 -0
  231. data/vendor/deps/cli-kit/lib/cli/kit/error_handler.rb +115 -0
  232. data/vendor/deps/cli-kit/lib/cli/kit/executor.rb +81 -0
  233. data/vendor/deps/cli-kit/lib/cli/kit/ini.rb +102 -0
  234. data/vendor/deps/cli-kit/lib/cli/kit/levenshtein.rb +82 -0
  235. data/vendor/deps/cli-kit/lib/cli/kit/logger.rb +76 -0
  236. data/vendor/deps/cli-kit/lib/cli/kit/resolver.rb +60 -0
  237. data/vendor/deps/cli-kit/lib/cli/kit/ruby_backports/enumerable.rb +6 -0
  238. data/vendor/deps/cli-kit/lib/cli/kit/support.rb +9 -0
  239. data/vendor/deps/cli-kit/lib/cli/kit/support/test_helper.rb +244 -0
  240. data/vendor/deps/cli-kit/lib/cli/kit/system.rb +207 -0
  241. data/vendor/deps/cli-kit/lib/cli/kit/util.rb +189 -0
  242. data/vendor/deps/cli-kit/lib/cli/kit/version.rb +5 -0
  243. data/vendor/deps/cli-ui/REVISION +1 -0
  244. data/vendor/deps/cli-ui/lib/cli/ui.rb +187 -0
  245. data/vendor/deps/cli-ui/lib/cli/ui/ansi.rb +153 -0
  246. data/vendor/deps/cli-ui/lib/cli/ui/box.rb +15 -0
  247. data/vendor/deps/cli-ui/lib/cli/ui/color.rb +79 -0
  248. data/vendor/deps/cli-ui/lib/cli/ui/formatter.rb +179 -0
  249. data/vendor/deps/cli-ui/lib/cli/ui/frame.rb +310 -0
  250. data/vendor/deps/cli-ui/lib/cli/ui/glyph.rb +78 -0
  251. data/vendor/deps/cli-ui/lib/cli/ui/progress.rb +88 -0
  252. data/vendor/deps/cli-ui/lib/cli/ui/prompt.rb +248 -0
  253. data/vendor/deps/cli-ui/lib/cli/ui/prompt/interactive_options.rb +472 -0
  254. data/vendor/deps/cli-ui/lib/cli/ui/prompt/options_handler.rb +24 -0
  255. data/vendor/deps/cli-ui/lib/cli/ui/spinner.rb +48 -0
  256. data/vendor/deps/cli-ui/lib/cli/ui/spinner/async.rb +40 -0
  257. data/vendor/deps/cli-ui/lib/cli/ui/spinner/spin_group.rb +241 -0
  258. data/vendor/deps/cli-ui/lib/cli/ui/stdout_router.rb +227 -0
  259. data/vendor/deps/cli-ui/lib/cli/ui/terminal.rb +36 -0
  260. data/vendor/deps/cli-ui/lib/cli/ui/truncater.rb +102 -0
  261. data/vendor/deps/cli-ui/lib/cli/ui/version.rb +5 -0
  262. data/vendor/deps/smart_properties/REVISION +1 -0
  263. data/vendor/deps/smart_properties/lib/smart_properties.rb +174 -0
  264. data/vendor/deps/smart_properties/lib/smart_properties/errors.rb +114 -0
  265. data/vendor/deps/smart_properties/lib/smart_properties/property.rb +162 -0
  266. data/vendor/deps/smart_properties/lib/smart_properties/property_collection.rb +83 -0
  267. data/vendor/deps/smart_properties/lib/smart_properties/validations.rb +8 -0
  268. data/vendor/deps/smart_properties/lib/smart_properties/validations/ancestor.rb +27 -0
  269. data/vendor/deps/smart_properties/lib/smart_properties/version.rb +3 -0
  270. data/vendor/lib/semantic/LICENSE +20 -0
  271. data/vendor/lib/semantic/semantic.rb +4 -0
  272. data/vendor/lib/semantic/version.rb +180 -0
  273. metadata +374 -0
@@ -0,0 +1,15 @@
1
+ require 'cli/ui'
2
+
3
+ module CLI
4
+ module UI
5
+ module Box
6
+ module Heavy
7
+ VERT = '┃'
8
+ HORZ = '━'
9
+ DIV = "┣"
10
+ TL = '┏'
11
+ BL = '┗'
12
+ end
13
+ end
14
+ end
15
+ end
@@ -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