showcase-rails 0.2.1 → 0.2.3

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 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