line-message-builder 0.9.0 → 0.10.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 (43) hide show
  1. checksums.yaml +4 -4
  2. data/.release-please-manifest.json +1 -1
  3. data/.rubocop.yml +5 -0
  4. data/CHANGELOG.md +20 -0
  5. data/CLAUDE.md +124 -0
  6. data/README.md +24 -16
  7. data/claudekit.json +11 -0
  8. data/docs/rubrics/rdoc.md +169 -0
  9. data/lib/line/message/builder/actions/message.rb +47 -26
  10. data/lib/line/message/builder/actions/postback.rb +56 -26
  11. data/lib/line/message/builder/actions/uri.rb +139 -0
  12. data/lib/line/message/builder/actions.rb +6 -3
  13. data/lib/line/message/builder/base.rb +92 -67
  14. data/lib/line/message/builder/container.rb +55 -60
  15. data/lib/line/message/builder/context.rb +51 -72
  16. data/lib/line/message/builder/flex/actionable.rb +53 -32
  17. data/lib/line/message/builder/flex/box.rb +342 -79
  18. data/lib/line/message/builder/flex/bubble.rb +73 -46
  19. data/lib/line/message/builder/flex/builder.rb +39 -28
  20. data/lib/line/message/builder/flex/button.rb +90 -42
  21. data/lib/line/message/builder/flex/carousel.rb +48 -20
  22. data/lib/line/message/builder/flex/icon.rb +152 -0
  23. data/lib/line/message/builder/flex/image.rb +95 -33
  24. data/lib/line/message/builder/flex/partial.rb +47 -49
  25. data/lib/line/message/builder/flex/position.rb +187 -100
  26. data/lib/line/message/builder/flex/separator.rb +49 -6
  27. data/lib/line/message/builder/flex/size.rb +67 -47
  28. data/lib/line/message/builder/flex/span.rb +106 -32
  29. data/lib/line/message/builder/flex/text.rb +166 -54
  30. data/lib/line/message/builder/flex.rb +26 -23
  31. data/lib/line/message/builder/quick_reply.rb +174 -4
  32. data/lib/line/message/builder/text.rb +70 -3
  33. data/lib/line/message/builder/version.rb +1 -1
  34. data/lib/line/message/builder.rb +14 -11
  35. data/lib/line/message/rspec/matchers/have_flex_bubble.rb +1 -1
  36. data/lib/line/message/rspec/matchers/have_flex_component.rb +16 -6
  37. data/lib/line/message/rspec/matchers/have_flex_message.rb +1 -1
  38. data/lib/line/message/rspec/matchers/have_flex_separator.rb +1 -1
  39. data/lib/line/message/rspec/matchers/have_quick_reply.rb +1 -1
  40. data/lib/line/message/rspec/matchers/have_text_message.rb +1 -1
  41. data/llm.txt +276 -35
  42. data/release-please-config.json +3 -1
  43. metadata +9 -3
@@ -3,19 +3,20 @@
3
3
  module Line
4
4
  module Message
5
5
  module Builder
6
- # The `Container` class is the top-level entry point for constructing a batch
6
+ # The +Container+ class is the top-level entry point for constructing a batch
7
7
  # of LINE messages using the builder DSL. It acts as a holder for one or
8
- # more individual message objects (such as {Text} or {Flex} messages).
8
+ # more individual message objects (such as Text or Flex messages).
9
9
  #
10
- # When you use `Line::Message::Builder.with {}`, you are operating within
11
- # the context of a `Container` instance. This container allows you to define
10
+ # When you use <code>Line::Message::Builder.with {}</code>, you are operating within
11
+ # the context of a +Container+ instance. This container allows you to define
12
12
  # multiple messages that can be sent together in a single API call to LINE,
13
13
  # although the LINE API typically expects an array of message objects,
14
14
  # which this container helps to build.
15
15
  #
16
16
  # Each message added to the container can also have its own quick reply.
17
17
  #
18
- # @example Building multiple messages
18
+ # == Example
19
+ #
19
20
  # message_payload = Line::Message::Builder.with do
20
21
  # text "Hello, this is the first message!"
21
22
  # flex alt_text: "This is a Flex Message" do
@@ -27,29 +28,33 @@ module Line
27
28
  # end
28
29
  # end.build # => Returns an array of message hashes
29
30
  #
30
- # @see Base
31
- # @see Text
32
- # @see Flex::Builder
33
- # @see QuickReply
31
+ # See also:
32
+ # - Base
33
+ # - Text
34
+ # - Flex::Builder
35
+ # - QuickReply
34
36
  class Container
35
- # @!attribute [r] context
36
- # @return [Context] The context object, which can hold external data or
37
- # helper methods accessible within the builder blocks.
37
+ # [return]
38
+ # The context object, which can hold external data or
39
+ # helper methods accessible within the builder blocks.
38
40
  attr_reader :context
39
41
 
40
42
  # Initializes a new message container.
41
- # This is typically not called directly but through `Line::Message::Builder.with`.
43
+ # This is typically not called directly but through <code>Line::Message::Builder.with</code>.
42
44
  # The provided block is instance-eval'd, allowing DSL methods like
43
- # {#text} and {#flex} to be called directly on the container instance.
45
+ # #text and #flex to be called directly on the container instance.
44
46
  #
45
- # @param context [Object, nil] An optional context object that can be used
47
+ # [context]
48
+ # An optional context object that can be used
46
49
  # to share data or helper methods within the builder block. It's wrapped
47
- # in a {Context} object.
48
- # @param mode [Symbol] The mode to use for building messages. Can be either
49
- # `:api` (default) for direct LINE Messaging API format or `:sdkv2` for
50
+ # in a Context object.
51
+ # [mode]
52
+ # The mode to use for building messages. Can be either
53
+ # <code>:api</code> (default) for direct LINE Messaging API format or <code>:sdkv2</code> for
50
54
  # LINE Bot SDK v2 compatible format.
51
- # @param block [Proc] A block containing DSL calls to define messages
52
- # (e.g., `text "Hello"`, `flex { ... }`).
55
+ # [block]
56
+ # A block containing DSL calls to define messages
57
+ # (e.g., <code>text "Hello"</code>, <code>flex { ... }</code>).
53
58
  def initialize(context: nil, mode: :api, &block)
54
59
  @messages = [] # Initializes an empty array to store message objects
55
60
  @context = Context.new(context, mode:)
@@ -57,17 +62,20 @@ module Line
57
62
  instance_eval(&block) if ::Kernel.block_given?
58
63
  end
59
64
 
60
- # Creates a new {Text} message and adds it to this container.
65
+ # Creates a new Text message and adds it to this container.
61
66
  #
62
- # @param text [String] The text content of the message.
63
- # @param options [Hash] Additional options for the text message,
64
- # such as `:quick_reply`. See {Text#initialize}.
65
- # @param block [Proc, nil] An optional block that will be instance-eval'd
66
- # in the context of the new {Text} message instance. This can be used
67
+ # [text]
68
+ # The text content of the message.
69
+ # [option]
70
+ # Additional options for the text message,
71
+ # such as <code>:quick_reply</code>. See Text#initialize.
72
+ # [block]
73
+ # An optional block that will be instance-eval'd
74
+ # in the context of the new Text message instance. This can be used
67
75
  # to add a quick reply to the text message.
68
- # @return [Text] The newly created {Text} message object.
69
76
  #
70
- # @example
77
+ # == Example
78
+ #
71
79
  # root.text "Hello, world!" do
72
80
  # quick_reply do
73
81
  # button action: :message, label: "Hi!", text: "Hi!"
@@ -77,20 +85,23 @@ module Line
77
85
  @messages << Text.new(text, context: context, **options, &)
78
86
  end
79
87
 
80
- # Creates a new {Flex::Builder} for constructing a Flex Message and adds it
88
+ # Creates a new Flex::Builder for constructing a Flex Message and adds it
81
89
  # to this container. The block is mandatory and is used to define the
82
90
  # content of the Flex Message using the Flex Message DSL.
83
91
  #
84
- # @param options [Hash] Options for the Flex Message, primarily `:alt_text`.
85
- # See {Flex::Builder#initialize}. It's important to provide `alt_text`.
86
- # @param block [Proc] A block that will be instance-eval'd in the context
87
- # of the new {Flex::Builder} instance. This block is used to define the
92
+ # [option]
93
+ # Options for the Flex Message, primarily <code>:alt_text</code>.
94
+ # See Flex::Builder#initialize. It's important to provide +alt_text+.
95
+ # [block]
96
+ # A block that will be instance-eval'd in the context
97
+ # of the new Flex::Builder instance. This block is used to define the
88
98
  # structure and content of the Flex Message (e.g., bubbles, carousels).
89
- # @return [Flex::Builder] The newly created {Flex::Builder} object.
90
- # @raise [ArgumentError] if `alt_text` is not provided in options (validation
91
- # is typically within Flex::Builder).
92
99
  #
93
- # @example
100
+ # Raises ArgumentError if +alt_text+ is not provided in options (validation
101
+ # is typically within Flex::Builder).
102
+ #
103
+ # == Example
104
+ #
94
105
  # flex alt_text: "Important information" do
95
106
  # bubble do
96
107
  # header { text "Header" }
@@ -102,47 +113,31 @@ module Line
102
113
  end
103
114
 
104
115
  # Converts all messages held by this container into their hash representations.
105
- # This method iterates over each message object (e.g., {Text}, {Flex::Builder})
106
- # stored in the container and calls its `to_h` method. The result is an
116
+ # This method iterates over each message object (e.g., Text, Flex::Builder)
117
+ # stored in the container and calls its +to_h+ method. The result is an
107
118
  # array of hashes, where each hash represents a single LINE message object
108
119
  # ready for JSON serialization and sending to the LINE Messaging API.
109
- #
110
- # @return [Array<Hash>] An array of message objects, each represented as a hash.
111
- # This is the format expected by the LINE API for the `messages` field
112
- # in a request body.
113
120
  def build
114
121
  @messages.map(&:to_h)
115
122
  end
116
123
 
117
- # Converts the array of message hashes (obtained from {#build}) into a
124
+ # Converts the array of message hashes (obtained from #build) into a
118
125
  # JSON string. This is a convenience method for serializing the messages
119
126
  # payload.
120
127
  #
121
- # @param args [Object] Optional arguments that are passed along to `to_json`
128
+ # [arg]
129
+ # Optional arguments that are passed along to +to_json+
122
130
  # method of the underlying array.
123
- # @return [String] A JSON string representing the array of message objects.
124
131
  def to_json(*args)
125
132
  build.to_json(*args)
126
133
  end
127
134
 
128
- # Checks if a method is defined in the context object.
129
- # This is part of Ruby's method_missing mechanism.
130
- #
131
- # @param method_name [Symbol] The name of the method being checked
132
- # @param include_private [Boolean] Whether to include private methods
133
- # @return [Boolean] True if the method exists in the context, false otherwise
135
+ # :nodoc:
134
136
  def respond_to_missing?(method_name, include_private = false)
135
137
  context.respond_to?(method_name, include_private) || super
136
138
  end
137
139
 
138
- # Delegates method calls to the context object if they exist there.
139
- # This allows helper methods defined in the context to be called directly
140
- # from within the builder DSL.
141
- #
142
- # @param method_name [Symbol] The name of the method being called
143
- # @param args [Array] The arguments passed to the method
144
- # @return [Object] The result of calling the method on the context
145
- # @raise [NoMethodError] If the method doesn't exist in the context
140
+ # :nodoc:
146
141
  def method_missing(method_name, ...)
147
142
  return context.send(method_name, ...) if context.respond_to?(method_name)
148
143
 
@@ -3,33 +3,33 @@
3
3
  module Line
4
4
  module Message
5
5
  module Builder
6
- # The `Context` class is a crucial component of the `Line::Message::Builder`
6
+ # The Context class is a crucial component of the Line::Message::Builder
7
7
  # DSL, enabling a flexible and dynamic environment for message construction.
8
8
  # It acts as a wrapper around an optional user-provided context object and
9
- # manages a separate hash of "assigns" (local variables for the DSL).
9
+ # manages a separate hash of +assigns+ (local variables for the DSL).
10
10
  #
11
- # The primary purposes of the `Context` are:
12
- # 1. **To provide access to helper methods**: If a user passes a context object
11
+ # The primary purposes of the Context are:
12
+ # 1. To provide access to helper methods: If a user passes a context object
13
13
  # (e.g., a Rails view context, a presenter, or any custom object) during
14
- # builder initialization (`Line::Message::Builder.with(my_helper_object)`),
15
- # methods defined on `my_helper_object` become directly callable within
16
- # the DSL blocks.
17
- # 2. **To allow local data storage (`assigns`)**: The `assigns` hash allows
18
- # for setting and retrieving temporary data that can be shared across
19
- # different parts of a complex message construction block, without needing
20
- # to pass it explicitly or pollute the user-provided context.
14
+ # builder initialization, methods defined on that object become directly
15
+ # callable within the DSL blocks.
16
+ # 2. To allow local data storage: The +assigns+ hash allows for setting and
17
+ # retrieving temporary data that can be shared across different parts of
18
+ # a complex message construction block, without needing to pass it
19
+ # explicitly or pollute the user-provided context.
21
20
  #
22
21
  # Method calls within the DSL that are not defined on the builder objects
23
- # themselves are resolved by `Context` in the following order:
24
- # - First, it checks if the method name corresponds to a key in the `assigns` hash.
25
- # - If not found in `assigns`, it checks if the wrapped user-context object
22
+ # themselves are resolved by Context in the following order:
23
+ # - First, it checks if the method name corresponds to a key in the +assigns+ hash.
24
+ # - If not found in +assigns+, it checks if the wrapped user-context object
26
25
  # responds to the method.
27
26
  # - If neither, the call proceeds up the normal Ruby method lookup chain.
28
27
  #
29
28
  # This mechanism allows for a clean and powerful way to integrate external logic
30
29
  # and data into the message building process.
31
30
  #
32
- # @example Using a custom context
31
+ # == Example: Using a custom context
32
+ #
33
33
  # class MyContext
34
34
  # def current_user_name
35
35
  # "Alice"
@@ -38,81 +38,58 @@ module Line
38
38
  #
39
39
  # context = MyContext.new
40
40
  # Line::Message::Builder.with(context) do
41
- # # `current_user_name` is resolved from `context` by Context
41
+ # # current_user_name is resolved from context by Context
42
42
  # text "Hello, #{current_user_name}!"
43
43
  # end
44
44
  class Context
45
- # @!attribute assigns
46
- # A hash for storing arbitrary data that can be accessed within the
47
- # builder DSL. This is useful for temporary variables or shared state
48
- # during message construction.
49
- # @return [Hash] The hash of assigned values.
50
- # @example
51
- # context.assigns[:user_id] = 123
52
- # puts context.assigns[:user_id] # => 123
45
+ # A hash for storing arbitrary data that can be accessed within the
46
+ # builder DSL. This is useful for temporary variables or shared state
47
+ # during message construction.
48
+ #
49
+ # == Example
50
+ #
51
+ # context.assigns[:user_id] = 123
52
+ # puts context.assigns[:user_id] # => 123
53
53
  attr_accessor :assigns
54
54
 
55
- # @!attribute [r] mode
56
- # The mode in which the builder is operating. This affects how messages
57
- # are formatted in the final output.
58
- # @return [Symbol] Either `:api` for direct LINE Messaging API format
59
- # or `:sdkv2` for LINE Bot SDK v2 compatible format.
55
+ # The mode in which the builder is operating. This affects how messages
56
+ # are formatted in the final output. Either +:api+ for direct LINE
57
+ # Messaging API format or +:sdkv2+ for LINE Bot SDK v2 compatible format.
60
58
  attr_reader :mode
61
59
 
62
60
  # Initializes a new Context object.
63
61
  #
64
- # @param context [Object, nil] An optional object whose methods will be made
65
- # available within the DSL. If `nil`, only `assigns` and standard
66
- # builder methods will be available.
67
- # @param mode [Symbol] The mode of the context, which can be `:api` (default)
68
- # for direct LINE Messaging API format or `:sdkv2` for LINE Bot SDK v2
69
- # compatible format.
62
+ # [context]
63
+ # An optional object whose methods will be made available within the DSL.
64
+ # If +nil+, only +assigns+ and standard builder methods will be available.
65
+ # [mode]
66
+ # The mode of the context, which can be +:api+ (default) for direct LINE
67
+ # Messaging API format or +:sdkv2+ for LINE Bot SDK v2 compatible format.
68
+ #
69
+ # == Example
70
+ #
71
+ # # With a custom context object
72
+ # my_context = MyHelpers.new
73
+ # context = Line::Message::Builder::Context.new(my_context, mode: :api)
74
+ #
75
+ # === Example: Without context
76
+ #
77
+ # # Without context, using only assigns
78
+ # context = Line::Message::Builder::Context.new(nil)
79
+ # context.assigns[:user_name] = "Alice"
70
80
  def initialize(context, mode: :api)
71
81
  @context = context
72
82
  @assigns = {}
73
83
  @mode = mode
74
84
  end
75
85
 
76
- # Part of Ruby's dynamic method dispatch. It's overridden here to declare
77
- # that instances of `Context` can respond to methods that are either:
78
- # 1. Keys in the `@assigns` hash.
79
- # 2. Methods to which the wrapped `@context` object responds.
80
- #
81
- # This ensures that `respond_to?` behaves consistently with how
82
- # `method_missing` resolves method calls.
83
- #
84
- # @param method_name [Symbol] The name of the method being queried.
85
- # @param include_private [Boolean] Whether to include private methods in
86
- # the check.
87
- # @return [Boolean] `true` if the context can handle the method,
88
- # `false` otherwise.
89
- # @!visibility private
90
- def respond_to_missing?(method_name, include_private = false)
86
+ def respond_to_missing?(method_name, include_private = false) # :nodoc:
91
87
  @assigns.key?(method_name) ||
92
88
  @context.respond_to?(method_name, include_private) || # Check @context directly
93
89
  super
94
90
  end
95
91
 
96
- # Handles calls to methods not explicitly defined on the `Context` class.
97
- # The resolution order is:
98
- # 1. If `method_name` is a key in the `@assigns` hash, its value is returned.
99
- # 2. If the wrapped `@context` object responds to `method_name`, the call
100
- # is delegated to `@context`.
101
- # 3. Otherwise, `super` is called, allowing the standard Ruby method
102
- # lookup to continue (which will likely result in a `NoMethodError`
103
- # if the method is truly undefined).
104
- #
105
- # This is the core mechanism that allows DSL blocks to seamlessly access
106
- # data from `assigns` or methods from the user-provided context.
107
- #
108
- # @param method_name [Symbol] The name of the invoked method.
109
- # @param ... [Object] Arguments passed to the method.
110
- # @return [Object, nil] The value from `@assigns`, the result of the
111
- # delegated call to `@context`, or raises `NoMethodError` via `super`.
112
- # @raise [NoMethodError] If the method is not found in `assigns` or
113
- # on the wrapped context.
114
- # @!visibility private
115
- def method_missing(method_name, ...)
92
+ def method_missing(method_name, ...) # :nodoc:
116
93
  return @assigns[method_name] if @assigns.key?(method_name)
117
94
  # Check @context directly
118
95
  return @context.public_send(method_name, ...) if @context.respond_to?(method_name)
@@ -122,8 +99,10 @@ module Line
122
99
 
123
100
  # Checks if the current mode is set to SDK v2 compatibility.
124
101
  #
125
- # @return [Boolean] `true` if the mode is `:sdkv2`, `false` otherwise.
126
- # @example
102
+ # Returns +true+ if the mode is +:sdkv2+, +false+ otherwise.
103
+ #
104
+ # == Example
105
+ #
127
106
  # if context.sdkv2?
128
107
  # # Format message for LINE Bot SDK v2
129
108
  # else
@@ -4,64 +4,85 @@ module Line
4
4
  module Message
5
5
  module Builder
6
6
  module Flex
7
- # The `Actionable` module provides a DSL for defining an action that can be
7
+ # The Actionable module provides a DSL for defining an action that can be
8
8
  # triggered when a user interacts with certain Flex Message components
9
- # (e.g., a {Button} component, or an entire {Bubble} or {Box} component
9
+ # (e.g., a Button component, or an entire Bubble or Box component
10
10
  # if it's made tappable).
11
11
  #
12
- # When a component includes this module, it gains methods like {#message}
13
- # and {#postback} to associate a specific LINE action with itself. The
14
- # chosen action is stored in the `action` attribute.
12
+ # When a component includes this module, it gains methods like +message+,
13
+ # +postback+ and +uri+ to associate a specific LINE action with itself.
14
+ # The chosen action is stored in the +action+ attribute.
15
15
  #
16
- # @!attribute [r] action
17
- # @return [Actions::Message, Actions::Postback, nil] The action object
18
- # associated with this component. `nil` if no action is defined.
16
+ # == Attributes
19
17
  #
20
- # @see Line::Message::Builder::Actions::Message
21
- # @see Line::Message::Builder::Actions::Postback
22
- # @see https://developers.line.biz/en/reference/messaging-api/#action-objects
18
+ # The following attribute is automatically added to classes that include
19
+ # this module:
20
+ #
21
+ # [action]
22
+ # The action object associated with this component. Returns an
23
+ # Actions::Message, Actions::Postback, or +nil+ if no action is defined.
24
+ #
25
+ # See also:
26
+ # - Line::Message::Builder::Actions::Message
27
+ # - Line::Message::Builder::Actions::Postback
28
+ # - Line::Message::Builder::Actions::Uri
29
+ # - https://developers.line.biz/en/reference/messaging-api/#action-objects
23
30
  module Actionable
24
- # @!visibility private
25
- # Automatically adds an `attr_reader :action` to the class that includes
26
- # this module.
27
- # @param base [Class] The class including this module.
28
- def self.included(base)
31
+ def self.included(base) # :nodoc:
29
32
  base.attr_reader :action
30
33
  end
31
34
 
32
35
  # Defines a message action for the component.
33
- # When the component is tapped, a message with the given `text` is sent
36
+ # When the component is tapped, a message with the given text is sent
34
37
  # from the user to the chat.
35
38
  #
36
- # @param text [String] The text of the message to send.
37
- # @param options [Hash] Additional options for the message action,
38
- # such as `:label`. See {Actions::Message#initialize}.
39
- # @param block [Proc, nil] An optional block, though not typically used
40
- # directly for message actions here.
41
- # @return [Actions::Message] The created message action object.
39
+ # [text]
40
+ # The text of the message to send
41
+ # [options]
42
+ # Additional options for the message action, such as +:label+.
43
+ # See Actions::Message
44
+ #
45
+ # == Example
42
46
  #
43
- # @example Setting a message action on a button
44
47
  # button_component.message "Hello User!", label: "Send Greeting"
45
48
  def message(text, **options, &)
46
49
  @action = Actions::Message.new(text, context: context, **options, &)
47
50
  end
48
51
 
49
52
  # Defines a postback action for the component.
50
- # When the component is tapped, a postback event with the given `data`
53
+ # When the component is tapped, a postback event with the given data
51
54
  # is sent to the bot's webhook.
52
55
  #
53
- # @param data [String] The data payload for the postback event.
54
- # @param options [Hash] Additional options for the postback action,
55
- # such as `:label` or `:display_text`. See {Actions::Postback#initialize}.
56
- # @param block [Proc, nil] An optional block, though not typically used
57
- # directly for postback actions here.
58
- # @return [Actions::Postback] The created postback action object.
56
+ # [data]
57
+ # The data payload for the postback event
58
+ # [options]
59
+ # Additional options for the postback action, such as +:label+ or
60
+ # +:display_text+. See Actions::Postback
61
+ #
62
+ # == Example
59
63
  #
60
- # @example Setting a postback action on a button
61
64
  # button_component.postback "action=buy&item_id=123", label: "Buy Item"
62
65
  def postback(data, **options, &)
63
66
  @action = Actions::Postback.new(data, context: context, **options, &)
64
67
  end
68
+
69
+ # Defines a URI action for the component.
70
+ # When the component is tapped, the given URI is opened. No webhook
71
+ # handling is needed, which makes this the action to use for linking
72
+ # a component to a web page.
73
+ #
74
+ # [uri]
75
+ # The URI to open. Schemes +http+, +https+, +line+ and +tel+
76
+ # [options]
77
+ # Additional options for the URI action, such as +:label+ or
78
+ # +:alt_uri_desktop+. See Actions::Uri
79
+ #
80
+ # == Example
81
+ #
82
+ # button_component.uri "https://example.com", label: "Learn more"
83
+ def uri(uri, **options, &)
84
+ @action = Actions::Uri.new(uri, context: context, **options, &)
85
+ end
65
86
  end
66
87
  end
67
88
  end