showcase-rails 0.2.1 → 0.2.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a546309faa4499b63781df854aa283c3d3a382f91f0c9be8e00a37a5dcbd0b04
4
- data.tar.gz: 1a7b269de49a2015c7ad9bc037de1e68aed4b33217771322f8345e859bdc5bc0
3
+ metadata.gz: '081d707dee9578e786c17cb621826f685bf2a4f9eab5221037b2394ac3b6805b'
4
+ data.tar.gz: 46fad2bf9b84aba2bf22bf0a627eb225ee904cd67da55cda4c0690b34d8d5a07
5
5
  SHA512:
6
- metadata.gz: 2779fe6f14b9a07db6a161dc4915a90aa2c1419f19dede0822d8009730b06a3b3006ec0eabcf8d008000d48a4997bdd4342b49dd59b35d4dfb205b520493ffcd
7
- data.tar.gz: f1bd8f9effd8ee3af53ab9bddf3456c3bff072e4cfb75d4e6b110a0925f35d996b2dce12f6adeb9e236ae5d5aa8f93eb3c93900b2d53f17c0930b44117b5e043
6
+ metadata.gz: 480d2d5a3c891703603caec5131d3990e19d31c97e815c622d8c6152ec2c1519a430e55416fc63caab1d7f136dd0fe96bf9e45a39bbf854e39134339822f6157
7
+ data.tar.gz: d35a92bd276b05225cdb827a9d3ec832665e1b0978b2ec02294ecc3d0810fee926fbb5f58d989612177ebe879d833ea8a476057efa04ca845d5e3e2b1808694a
data/README.md CHANGED
@@ -29,7 +29,32 @@ Which will then render the following:
29
29
 
30
30
  ![](/readme/example.png?raw=true "Showcase showing a button component")
31
31
 
32
- ## Automatic smokescreen testing
32
+ ## Using options contexts
33
+
34
+ Showcase also supports custom options contexts. They're useful for cases where the options have a very specific format and it would be nice to keep them standardized.
35
+
36
+ By default, Showcase ships Nice Partials and Stimulus contexts out of the box. Here's a sample of the Stimulus one:
37
+
38
+ ```erb
39
+ <% showcase.options.stimulus controller: :welcome do |o| %>
40
+ <% o.optional.targets :greeter, "If the id of the target element must be printed" %>
41
+ <% end %>
42
+ ```
43
+
44
+ In case Showcase didn't ship with a Stimulus context, here's how you could add it:
45
+
46
+ ```ruby
47
+ # config/initializers/showcase.rb
48
+ if defined?(Showcase)
49
+ Showcase.options.define :stimulus do
50
+ def targets(name, ...)
51
+ option(%(data-#{@controller}-target="#{name}"), ...)
52
+ end
53
+ end
54
+ end
55
+ ```
56
+
57
+ ## Automatic integration testing
33
58
 
34
59
  Showcase automatically runs integration tests for all your Showcases by rendering them and asserting they respond with `200 OK`. As long as `gem "showcase-rails"` is in the `:test` group you're set.
35
60
 
@@ -1,5 +1,5 @@
1
1
  /*
2
- ! tailwindcss v3.2.4 | MIT License | https://tailwindcss.com
2
+ ! tailwindcss v3.2.7 | MIT License | https://tailwindcss.com
3
3
  */
4
4
 
5
5
  /*
@@ -691,6 +691,10 @@ select {
691
691
  grid-column: span 9 / span 9;
692
692
  }
693
693
 
694
+ .sc-m-2 {
695
+ margin: 0.5rem;
696
+ }
697
+
694
698
  .sc-mb-2 {
695
699
  margin-bottom: 0.5rem;
696
700
  }
@@ -699,6 +703,10 @@ select {
699
703
  margin-bottom: 1rem;
700
704
  }
701
705
 
706
+ .sc-mt-1 {
707
+ margin-top: 0.25rem;
708
+ }
709
+
702
710
  .sc-inline-block {
703
711
  display: inline-block;
704
712
  }
@@ -765,10 +773,13 @@ select {
765
773
  align-items: center;
766
774
  }
767
775
 
768
- .sc-space-y-8 > :not([hidden]) ~ :not([hidden]) {
769
- --tw-space-y-reverse: 0;
770
- margin-top: calc(2rem * calc(1 - var(--tw-space-y-reverse)));
771
- margin-bottom: calc(2rem * var(--tw-space-y-reverse));
776
+ .sc-justify-between {
777
+ justify-content: space-between;
778
+ }
779
+
780
+ .sc-gap-x-2 {
781
+ -moz-column-gap: 0.5rem;
782
+ column-gap: 0.5rem;
772
783
  }
773
784
 
774
785
  .sc-space-x-2 > :not([hidden]) ~ :not([hidden]) {
@@ -783,6 +794,12 @@ select {
783
794
  margin-bottom: calc(1rem * var(--tw-space-y-reverse));
784
795
  }
785
796
 
797
+ .sc-space-y-8 > :not([hidden]) ~ :not([hidden]) {
798
+ --tw-space-y-reverse: 0;
799
+ margin-top: calc(2rem * calc(1 - var(--tw-space-y-reverse)));
800
+ margin-bottom: calc(2rem * var(--tw-space-y-reverse));
801
+ }
802
+
786
803
  .sc-overflow-scroll {
787
804
  overflow: scroll;
788
805
  }
@@ -801,6 +818,10 @@ select {
801
818
  white-space: nowrap;
802
819
  }
803
820
 
821
+ .sc-rounded-full {
822
+ border-radius: 9999px;
823
+ }
824
+
804
825
  .sc-rounded-md {
805
826
  border-radius: 0.375rem;
806
827
  }
@@ -813,16 +834,20 @@ select {
813
834
  border-width: 0px;
814
835
  }
815
836
 
816
- .sc-border-t {
817
- border-top-width: 1px;
837
+ .sc-border-2 {
838
+ border-width: 2px;
839
+ }
840
+
841
+ .sc-border-b {
842
+ border-bottom-width: 1px;
818
843
  }
819
844
 
820
845
  .sc-border-r {
821
846
  border-right-width: 1px;
822
847
  }
823
848
 
824
- .sc-border-b {
825
- border-bottom-width: 1px;
849
+ .sc-border-t {
850
+ border-top-width: 1px;
826
851
  }
827
852
 
828
853
  .sc-border-gray-200 {
@@ -830,26 +855,36 @@ select {
830
855
  border-color: rgb(229 231 235 / var(--tw-border-opacity));
831
856
  }
832
857
 
833
- .sc-bg-slate-50 {
858
+ .sc-border-indigo-300 {
859
+ --tw-border-opacity: 1;
860
+ border-color: rgb(165 180 252 / var(--tw-border-opacity));
861
+ }
862
+
863
+ .sc-bg-indigo-50 {
834
864
  --tw-bg-opacity: 1;
835
- background-color: rgb(248 250 252 / var(--tw-bg-opacity));
865
+ background-color: rgb(238 242 255 / var(--tw-bg-opacity));
836
866
  }
837
867
 
838
868
  .sc-bg-slate-100\/50 {
839
869
  background-color: rgb(241 245 249 / 0.5);
840
870
  }
841
871
 
842
- .sc-bg-indigo-50 {
872
+ .sc-bg-slate-50 {
843
873
  --tw-bg-opacity: 1;
844
- background-color: rgb(238 242 255 / var(--tw-bg-opacity));
874
+ background-color: rgb(248 250 252 / var(--tw-bg-opacity));
875
+ }
876
+
877
+ .sc-p-12 {
878
+ padding: 3rem;
845
879
  }
846
880
 
847
881
  .sc-p-4 {
848
882
  padding: 1rem;
849
883
  }
850
884
 
851
- .sc-p-12 {
852
- padding: 3rem;
885
+ .sc-px-2 {
886
+ padding-left: 0.5rem;
887
+ padding-right: 0.5rem;
853
888
  }
854
889
 
855
890
  .sc-px-4 {
@@ -857,6 +892,11 @@ select {
857
892
  padding-right: 1rem;
858
893
  }
859
894
 
895
+ .sc-px-8 {
896
+ padding-left: 2rem;
897
+ padding-right: 2rem;
898
+ }
899
+
860
900
  .sc-py-2 {
861
901
  padding-top: 0.5rem;
862
902
  padding-bottom: 0.5rem;
@@ -867,11 +907,6 @@ select {
867
907
  padding-bottom: 1.25rem;
868
908
  }
869
909
 
870
- .sc-px-8 {
871
- padding-left: 2rem;
872
- padding-right: 2rem;
873
- }
874
-
875
910
  .sc-pl-4 {
876
911
  padding-left: 1rem;
877
912
  }
@@ -880,9 +915,9 @@ select {
880
915
  padding-top: 1.75rem;
881
916
  }
882
917
 
883
- .sc-text-xl {
884
- font-size: 1.25rem;
885
- line-height: 1.75rem;
918
+ .sc-text-2xl {
919
+ font-size: 1.5rem;
920
+ line-height: 2rem;
886
921
  }
887
922
 
888
923
  .sc-text-3xl {
@@ -895,17 +930,14 @@ select {
895
930
  line-height: 1.5rem;
896
931
  }
897
932
 
898
- .sc-text-2xl {
899
- font-size: 1.5rem;
900
- line-height: 2rem;
901
- }
902
-
903
- .sc-font-semibold {
904
- font-weight: 600;
933
+ .sc-text-xl {
934
+ font-size: 1.25rem;
935
+ line-height: 1.75rem;
905
936
  }
906
937
 
907
- .sc-font-normal {
908
- font-weight: 400;
938
+ .sc-text-xs {
939
+ font-size: 0.75rem;
940
+ line-height: 1rem;
909
941
  }
910
942
 
911
943
  .sc-font-black {
@@ -916,6 +948,14 @@ select {
916
948
  font-weight: 500;
917
949
  }
918
950
 
951
+ .sc-font-normal {
952
+ font-weight: 400;
953
+ }
954
+
955
+ .sc-font-semibold {
956
+ font-weight: 600;
957
+ }
958
+
919
959
  .sc-italic {
920
960
  font-style: italic;
921
961
  }
@@ -929,6 +969,11 @@ select {
929
969
  color: rgb(0 0 0 / var(--tw-text-opacity));
930
970
  }
931
971
 
972
+ .sc-text-slate-500 {
973
+ --tw-text-opacity: 1;
974
+ color: rgb(100 116 139 / var(--tw-text-opacity));
975
+ }
976
+
932
977
  .hover\:sc-select-all:hover {
933
978
  -webkit-user-select: all;
934
979
  -moz-user-select: all;
@@ -948,11 +993,11 @@ select {
948
993
  }
949
994
 
950
995
  @media (min-width: 1280px) {
951
- .xl\:sc-col-span-2 {
952
- grid-column: span 2 / span 2;
953
- }
954
-
955
996
  .xl\:sc-col-span-10 {
956
997
  grid-column: span 10 / span 10;
957
998
  }
999
+
1000
+ .xl\:sc-col-span-2 {
1001
+ grid-column: span 2 / span 2;
1002
+ }
958
1003
  }
@@ -1,19 +1,16 @@
1
1
  class Showcase::Path
2
- class Tree < Struct.new(:id, :children)
2
+ class Tree < Struct.new(:id, :children, :root)
3
3
  def initialize(id, children = [])
4
- super
4
+ super(id, children, false)
5
5
  end
6
+ alias_method :root?, :root
6
7
  delegate :<<, to: :children
7
8
 
8
9
  cached_partial_path = "showcase/engine/path/tree"
9
10
  define_method(:to_partial_path) { cached_partial_path }
10
11
 
11
12
  def name
12
- root? ? "Previews" : id
13
- end
14
-
15
- def root?
16
- id == "."
13
+ id == "." ? "Previews" : id
17
14
  end
18
15
 
19
16
  def ordered_children
@@ -24,15 +21,10 @@ class Showcase::Path
24
21
  children.flat_map { _1.is_a?(Tree) ? _1.ordered_paths : _1 }
25
22
  end
26
23
 
27
- def self.index(...)
28
- new(:discardable_root).tap { _1.index(...) }.ordered_children
29
- end
30
-
31
- def index(paths)
32
- paths.each do |path|
33
- ids = yield path
34
- ids.inject(self, :edge_for) << path
35
- end
24
+ def self.index(paths)
25
+ paths.each_with_object new(:discardable_root) do |path, root|
26
+ yield(path).reduce(root, :edge_for) << path
27
+ end.children.sort_by(&:id).each { _1.root = true }
36
28
  end
37
29
 
38
30
  def edge_for(id)
@@ -1,6 +1,6 @@
1
1
  class Showcase::Sample
2
2
  attr_reader :name, :id, :events, :details
3
- attr_reader :source
3
+ attr_reader :source, :instrumented
4
4
 
5
5
  def initialize(view_context, name, description: nil, id: name.parameterize, events: nil, **details)
6
6
  @view_context = view_context
@@ -24,7 +24,13 @@ class Showcase::Sample
24
24
  end
25
25
 
26
26
  def preview(&block)
27
- block_given? ? @preview = @view_context.capture(&block) : @preview
27
+ return @preview unless block_given?
28
+
29
+ # TODO: Remove `is_a?` check when Rails 6.1 support is dropped.
30
+ assigns = proc { @instrumented = _1 if _1.is_a?(ActiveSupport::Notifications::Event) }
31
+ ActiveSupport::Notifications.subscribed(assigns, "render_partial.action_view") do
32
+ @preview = @view_context.capture(&block)
33
+ end
28
34
  end
29
35
 
30
36
  def extract(&block)
@@ -4,8 +4,8 @@
4
4
  <title>Showcase</title>
5
5
  <meta name="viewport" content="width=device-width,initial-scale=1">
6
6
 
7
- <%= render "stylesheets" %>
8
- <%= render "javascripts" %>
7
+ <%= render "showcase/engine/stylesheets" %>
8
+ <%= render "showcase/engine/javascripts" %>
9
9
  </head>
10
10
 
11
11
  <body>
@@ -4,7 +4,9 @@
4
4
  <div class="sc-flex sc-items-center sc-space-x-2 sc-mb-2">
5
5
  <h2 class="sc-font-semibold sc-text-3xl"><%= preview.title %></h2>
6
6
 
7
- <%# TODO: Insert default badge support here. %>
7
+ <% preview.badges.each do |badge| %>
8
+ <span class="sc-border-2 sc-border-indigo-300 sc-rounded-full sc-px-2 sc-mt-1"><%= badge.to_s.titleize %></span>
9
+ <% end %>
8
10
  </div>
9
11
  <% end %>
10
12
 
@@ -1,7 +1,16 @@
1
1
  <section class="sc-mb-4 sc-border sc-border-gray-200 sc-rounded-md" aria-labelledby="showcase_<%= sample.id %>_title">
2
2
  <showcase-sample id="<%= sample.id %>" events="<%= sample.events %>">
3
3
  <header class="sc-bg-slate-100/50">
4
- <h3 id="showcase_<%= sample.id %>_title" class="sc-px-4 sc-py-2 sc-font-medium sc-text-base md:sc-text-lg sc-leading-snug sc-truncate"><%= link_to sample.name, "##{sample.id}" %></h3>
4
+ <div class="sc-flex sc-justify-between">
5
+ <h3 id="showcase_<%= sample.id %>_title" class="sc-px-4 sc-py-2 sc-font-medium sc-text-base md:sc-text-lg sc-leading-snug sc-truncate"><%= link_to sample.name, "##{sample.id}" %></h3>
6
+
7
+ <% if event = sample.instrumented %>
8
+ <div class="sc-text-xs sc-grid sc-gap-x-2 sc-m-2 sc-italic sc-text-slate-500">
9
+ <span><%= event.duration.round(1) %>ms</span>
10
+ <span><%= event.allocations %> allocs</span>
11
+ </div>
12
+ <% end %>
13
+ </div>
5
14
 
6
15
  <% if sample.description %>
7
16
  <p class="sc-px-4 sc-py-2 sc-text-base"><%= sample.description %></p>
@@ -0,0 +1,89 @@
1
+ require "active_support/option_merger"
2
+
3
+ class Showcase::Options
4
+ include Enumerable
5
+
6
+ def initialize(view_context)
7
+ @view_context = view_context
8
+ @options = []
9
+ @order = [:name, :required, :type, :default, :description]
10
+ end
11
+ delegate :empty?, to: :@options
12
+
13
+ # Showcase.options.define :stimulus do
14
+ # def value(name, ...)
15
+ # option("data-#{@controller}-#{name}-value", ...)
16
+ # end
17
+ # end
18
+ singleton_class.attr_reader :contexts
19
+ @contexts = Hash.new { |h,k| h[k] = Class.new Context }
20
+
21
+ def self.define(key, &block)
22
+ contexts[key].class_eval(&block) # Lets users reopen an already defined context class.
23
+ end
24
+
25
+ # showcase.options.stimulus controller: :welcome do |o|
26
+ # o.value :greeting, default: "Hello"
27
+ # end
28
+ def context(key, **options, &block)
29
+ context = self.class.contexts.fetch(key)
30
+ context.new(@view_context, @options, **options).tap { yield _1 if block_given? }
31
+ end
32
+
33
+ def required(*arguments, **keywords, &block)
34
+ if arguments.none?
35
+ ActiveSupport::OptionMerger.new(self, required: true)
36
+ else
37
+ option(*arguments, **keywords, required: true, &block)
38
+ end
39
+ end
40
+
41
+ def optional(*arguments, **keywords, &block)
42
+ if arguments.none?
43
+ ActiveSupport::OptionMerger.new(self, required: false)
44
+ else
45
+ option(*arguments, **keywords, required: false, &block)
46
+ end
47
+ end
48
+
49
+ DEFAULT_OMITTED = Object.new
50
+
51
+ def option(name, description = nil, required: false, type: nil, default: DEFAULT_OMITTED, **options, &block)
52
+ description ||= @view_context.capture(&block).remove(/^\s+/).html_safe if block
53
+
54
+ type ||= type_from_default(default)
55
+ default = default == DEFAULT_OMITTED ? nil : default.inspect
56
+
57
+ @options << options.with_defaults(name: name, default: default, type: type, description: description, required: required)
58
+ end
59
+
60
+ def headers
61
+ @headers ||= @order | @options.flat_map(&:keys).uniq.sort
62
+ end
63
+
64
+ def each(&block)
65
+ @options.each do |option|
66
+ yield headers.index_with { option[_1] }
67
+ end
68
+ end
69
+
70
+ private
71
+
72
+ class Context < Showcase::Options
73
+ def initialize(view_context, options, **kwargs)
74
+ super(view_context)
75
+ @options = options
76
+ kwargs.each { instance_variable_set(:"@#{_1}", _2) }
77
+ end
78
+ end
79
+
80
+ def type_from_default(default)
81
+ case default
82
+ when DEFAULT_OMITTED then String
83
+ when true, false then "Boolean"
84
+ when nil then "nil"
85
+ else
86
+ default.class
87
+ end
88
+ end
89
+ end
@@ -1,3 +1,3 @@
1
1
  module Showcase
2
- VERSION = "0.2.1"
2
+ VERSION = "0.2.3"
3
3
  end
data/lib/showcase.rb CHANGED
@@ -3,6 +3,7 @@ require_relative "showcase/version"
3
3
  module Showcase
4
4
  autoload :IntegrationTest, "showcase/integration_test"
5
5
  autoload :RouteHelper, "showcase/route_helper"
6
+ autoload :Options, "showcase/options"
6
7
 
7
8
  singleton_class.attr_accessor :sample_renderer
8
9
  @sample_renderer = ->(lines) { tag.pre lines.join.strip_heredoc }
@@ -15,6 +16,38 @@ module Showcase
15
16
  Dir.glob("**/*.*", base: File.join(root, previews_path))
16
17
  end.uniq
17
18
  end
19
+
20
+ def self.options
21
+ Options
22
+ end
23
+
24
+ options.define :stimulus do
25
+ def targets(name, ...)
26
+ option(%(data-#{@controller}-target="#{name}"), ...)
27
+ end
28
+
29
+ def values(name, ...)
30
+ option("data-#{@controller}-#{name}-value", ...)
31
+ end
32
+
33
+ def classes(name, ...)
34
+ option("data-#{@controller}-#{name}-class", ...)
35
+ end
36
+
37
+ def outlet(name, ...)
38
+ option("data-#{@controller}-#{name}-outlet", ...)
39
+ end
40
+
41
+ def action(name, ...)
42
+ option(%(data-action="#{name}"), ...)
43
+ end
44
+ end
45
+
46
+ options.define :nice_partials do
47
+ def content_block(*arguments, **options, &block)
48
+ option(*arguments, **options, type: "Content Block", &block)
49
+ end
50
+ end
18
51
  end
19
52
 
20
53
  require "showcase/engine" if defined?(Rails::Engine)
@@ -2,9 +2,9 @@ namespace :showcase do
2
2
  namespace :install do
3
3
  INTEGRATION_TEST_PATH = "test/integration/showcase_test.rb"
4
4
 
5
- desc "Install Showcase smokescreen testing in #{INTEGRATION_TEST_PATH}"
6
- task :smokescreen_test do
7
- mkdir_p INTEGRATION_TEST_PATH
5
+ desc "Install Showcase integration testing in #{INTEGRATION_TEST_PATH}"
6
+ task :integration_test do
7
+ mkdir_p File.dirname(INTEGRATION_TEST_PATH)
8
8
  File.write INTEGRATION_TEST_PATH, <<~RUBY
9
9
  require "test_helper"
10
10
 
@@ -16,15 +16,4 @@ namespace :showcase do
16
16
  RUBY
17
17
  end
18
18
  end
19
-
20
- # desc "Pass a directory relative to app/views to copy over"
21
- # task :copy do |t, directory|
22
- # prefix = "app/views/#{directory}"
23
- #
24
- # Dir.glob(File.join(Dir.pwd, prefix, "**/*.*")).each do |filename|
25
- # new_filename = filename.sub(directory, Showcase.templates_path).sub(/\/_/, "/")
26
- # mkdir_p File.dirname(new_filename)
27
- # copy_file filename, new_filename
28
- # end
29
- # end
30
19
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: showcase-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.2.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel Pence
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2023-02-07 00:00:00.000000000 Z
12
+ date: 2023-02-20 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rails
@@ -53,9 +53,7 @@ files:
53
53
  - app/assets/config/showcase_manifest.js
54
54
  - app/assets/javascripts/showcase.js
55
55
  - app/controllers/showcase/engine_controller.rb
56
- - app/controllers/showcase/pages_controller.rb
57
56
  - app/controllers/showcase/previews_controller.rb
58
- - app/models/showcase/options.rb
59
57
  - app/models/showcase/path.rb
60
58
  - app/models/showcase/preview.rb
61
59
  - app/models/showcase/sample.rb
@@ -76,6 +74,7 @@ files:
76
74
  - lib/showcase.rb
77
75
  - lib/showcase/engine.rb
78
76
  - lib/showcase/integration_test.rb
77
+ - lib/showcase/options.rb
79
78
  - lib/showcase/route_helper.rb
80
79
  - lib/showcase/version.rb
81
80
  - lib/tasks/showcase_tasks.rake
@@ -101,7 +100,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
101
100
  - !ruby/object:Gem::Version
102
101
  version: '0'
103
102
  requirements: []
104
- rubygems_version: 3.4.1
103
+ rubygems_version: 3.4.7
105
104
  signing_key:
106
105
  specification_version: 4
107
106
  summary: Showcase helps you show off and document your partials, components, view
@@ -1,5 +0,0 @@
1
- class Showcase::PagesController < Showcase::EngineController
2
- def show
3
- @page = Showcase::Path.new(params[:id]).page_for view_context
4
- end
5
- end
@@ -1,51 +0,0 @@
1
- class Showcase::Options
2
- include Enumerable
3
-
4
- def initialize(view_context)
5
- @view_context = view_context
6
- @options = []
7
- @order = [:name, :required, :type, :default, :description]
8
- end
9
- delegate :empty?, to: :@options
10
-
11
- def required(*arguments, **keywords, &block)
12
- option(*arguments, **keywords, required: true, &block)
13
- end
14
-
15
- def optional(*arguments, **keywords, &block)
16
- option(*arguments, **keywords, required: false, &block)
17
- end
18
-
19
- DEFAULT_OMITTED = Object.new
20
-
21
- def option(name, description = nil, required: false, type: nil, default: DEFAULT_OMITTED, **options, &block)
22
- description ||= @view_context.capture(&block).remove(/^\s+/).html_safe if block
23
-
24
- type ||= type_from_default(default)
25
- default = default == DEFAULT_OMITTED ? nil : default.inspect
26
-
27
- @options << options.with_defaults(name: name, default: default, type: type, description: description, required: required)
28
- end
29
-
30
- def headers
31
- @headers ||= @order | @options.flat_map(&:keys).uniq.sort
32
- end
33
-
34
- def each(&block)
35
- @options.each do |option|
36
- yield headers.index_with { option[_1] }
37
- end
38
- end
39
-
40
- private
41
-
42
- def type_from_default(default)
43
- case default
44
- when DEFAULT_OMITTED then String
45
- when true, false then "Boolean"
46
- when nil then "nil"
47
- else
48
- default.class
49
- end
50
- end
51
- end