File Coverage

blib/lib/Slack/BlockKit/Sugar.pm
Criterion Covered Total %
statement 38 161 23.6
branch 4 62 6.4
condition 4 13 30.7
subroutine 9 35 25.7
pod 26 27 96.3
total 81 298 27.1


line stmt bran cond sub pod time code
1             package Slack::BlockKit::Sugar 0.005;
2             # ABSTRACT: sugar for building Block Kit structures easily (start here!)
3              
4 3     3   404930 use v5.36.0;
  3         10  
5              
6 3     3   15 use Carp ();
  3         4  
  3         52  
7 3     3   1361 use Slack::BlockKit;
  3         14  
  3         402  
8              
9             # A small rant: Params::Util provided a perfectly good _HASH0 routine, which I
10             # knew I could use here. But it turns out that years ago, an XS implementation
11             # was added to Params::Util, and it's *broken*. It returns blessed references
12             # when the test is meant to exclude them. The broken Params::Util will prefer
13             # XS, but there's Params::Util::PP -- but that code doesn't export any of its
14             # symbols. There's a comment on the ticket from the current maintainer that
15             # he's not sure which is right, even though the original documentation makes
16             # the intended behavior clear.
17             #
18             # This cost me a good hour or two. I *wrote* some of this code, so I was
19             # absolutely positive I knew the behavior of _HASH0. Turns out, nope!
20             #
21             # https://rt.cpan.org/Ticket/Display.html?id=75561
22 0 0   0   0 my sub _HASH0 { return ref $_[0] eq 'HASH' ? $_[0] : undef; }
23              
24 3     3   2026 use experimental 'builtin'; # blessed
  3         15795  
  3         40  
25              
26             #pod =head1 ACHTUNG
27             #pod
28             #pod This library seems pretty good to me, its author, but it hasn't seen much use
29             #pod yet. As it gets used, it may change in somewhat backward-incompatible ways.
30             #pod Upgrade carefully until this warning goes away!
31             #pod
32             #pod =head1 OVERVIEW
33             #pod
34             #pod This module exports a bunch of functions that can be composed to build Block
35             #pod Kit block collections, which can then be easily turned into a data structure
36             #pod that can be serialized.
37             #pod
38             #pod If you learn to use I<this> library, you can generally ignore the other dozen
39             #pod (more!) modules in this distribution. On the other hand, you B<must> more or
40             #pod less understand how Block Kit works, which means reading the L<BlockKit
41             #pod documentation|https://api.slack.com/block-kit> at Slack's API site.
42             #pod
43             #pod The key is to have a decent idea of the Block Kit structure you want. Knowing
44             #pod that, you can look at the list of functions provided by this library and
45             #pod compose them together. Start with a call to C<blocks>, passing the result of
46             #pod calling other generators to it. In the end, you'll have an object on which you
47             #pod can call C<< ->as_struct >>.
48             #pod
49             #pod For example, to produce something basically equivalent to this Markdown (well,
50             #pod mrkdwn):
51             #pod
52             #pod Here is a *safe* link: **[click me](https://rjbs.cloud/)**
53             #pod * it will be fun
54             #pod * it will be cool 🙂
55             #pod * it will be enough
56             #pod
57             #pod You can use this set of calls to the sugar functions:
58             #pod
59             #pod blocks(
60             #pod richblock(
61             #pod richsection(
62             #pod "Here is a ", italic("safe"), " link: ",
63             #pod link("https://fastmail.com/", "click me", { style => { bold => 1 } }),
64             #pod ),
65             #pod ulist(
66             #pod "it will be fun",
67             #pod richsection("it will be cool", emoji('smile')),
68             #pod "it will be enough",
69             #pod ),
70             #pod )
71             #pod );
72             #pod
73             #pod =head2 Importing the functions
74             #pod
75             #pod This library uses L<Sub::Exporter> for exporting its functions. That means
76             #pod that they can be renamed during import. Since the names of these functions are
77             #pod fairly generic, you might prefer to write this:
78             #pod
79             #pod use Slack::BlockKit::Sugar -all => { -prefix => 'bk_' };
80             #pod
81             #pod This will import all the sugar functions with their names prefixed by C<bk_>.
82             #pod So, for example, C<italic> will be C<bk_italic>.
83             #pod
84             #pod =cut
85              
86 3         58 use Sub::Exporter -setup => {
87             exports => [
88             # top-level
89             qw( blocks ),
90              
91             # Rich Text
92             qw( richblock ),
93             qw( richsection list preformatted quote ), # top-level
94             qw( olist ulist ), # specialized list()
95             qw( channel date emoji link richtext user usergroup ), # deeper
96             qw( bold code italic strike ), # specialized richtext()
97              
98             # Other Things
99             qw( context divider header image markdown section ),
100             qw( mrkdwn text ),
101             ],
102 3     3   564 };
  3         7  
103              
104 0     0   0 my sub _rtextify (@things) {
  0         0  
  0         0  
105             [
106 0 0       0 map {; ref($_)
  0         0  
107             ? $_
108             : Slack::BlockKit::Block::RichText::Text->new({ text => $_ })
109             } @things
110             ]
111             }
112              
113 0     0   0 my sub _rsectionize (@things) {
  0         0  
  0         0  
114             [
115 0 0       0 map {; ref($_)
  0         0  
116             ? $_
117             : Slack::BlockKit::Block::RichText::Section->new({
118             elements => _rtextify($_),
119             })
120             } @things
121             ];
122             }
123              
124             #pod =func blocks
125             #pod
126             #pod my $block_col = blocks( @blocks );
127             #pod
128             #pod This returns a L<Slack::BlockKit::BlockCollection>, which just collects blocks
129             #pod and will return their serializable structure form in an arrayref when C<<
130             #pod ->as_struct >> is called on it.
131             #pod
132             #pod If any argument is a non-reference, it will be upgraded to a L</section> block
133             #pod containing a single L</mrkdwn> text object.
134             #pod
135             #pod This means the simplest non-trivial message you could send is something like:
136             #pod
137             #pod blocks("This is a *formatted* message.")->as_struct;
138             #pod
139             #pod =cut
140              
141 0     0 1 0 sub blocks (@blocks) {
  0         0  
  0         0  
142             return Slack::BlockKit::BlockCollection->new({
143 0 0       0 blocks => [ map {; ref($_) ? $_ : section(mrkdwn($_)) } @blocks ]
  0         0  
144             });
145             }
146              
147             #pod =func richblocks
148             #pod
149             #pod my $rich_text_block = richblocks( @rich_elements );
150             #pod
151             #pod This returns a L<Slack::BlockKit::Block::RichText>, representing a
152             #pod C<rich_text>-type block. It's a bit annoying that you need it, but you do.
153             #pod You'll also need to pass one or more rich text elements as arguments, something
154             #pod like this:
155             #pod
156             #pod blocks(
157             #pod richblocks(
158             #pod section("This is ", italic("very"), " important."),
159             #pod ),
160             #pod );
161             #pod
162             #pod Each non-reference passed in the list of rich text elements will be turned into
163             #pod a rich text objects inside its own section object.
164             #pod
165             #pod =cut
166              
167 0     0 0 0 sub richblock (@elements) {
  0         0  
  0         0  
168 0         0 Slack::BlockKit::Block::RichText->new({
169             elements => _rsectionize(@elements),
170             })
171             }
172              
173             #pod =func richsection
174             #pod
175             #pod $rich_text_section = richsection( @elements );
176             #pod
177             #pod This returns a single L<rich text
178             #pod section|Slack::BlockKit::Block::RichText::Section> object containing the passed
179             #pod elements as its C<elements> attribute value.
180             #pod
181             #pod Non-reference values in C<@elements> be converted to rich text objects.
182             #pod
183             #pod =cut
184              
185 0     0 1 0 sub richsection (@elements) {
  0         0  
  0         0  
186 0         0 Slack::BlockKit::Block::RichText::Section->new({
187             elements => _rtextify(@elements),
188             });
189             }
190              
191             #pod =func list
192             #pod
193             #pod my $rich_text_list = list(\%arg, @sections);
194             #pod
195             #pod This returns a L<rich text list|Slack::BlockKit::Block::RichText::List> object.
196             #pod C<@sections> must be a list of rich text sections or plain scalars. Plain
197             #pod scalars will each be converted to a rich text object contained in a rich text
198             #pod section.
199             #pod
200             #pod The C<%arg> hash is more arguments to the List object constructor. It B<must>
201             #pod contain a C<style> key. For shorthand, see L</olist> and L</ulist> below.
202             #pod
203             #pod =cut
204              
205 0     0 1 0 sub list ($arg, @sections) {
  0         0  
  0         0  
  0         0  
206 0         0 Slack::BlockKit::Block::RichText::List->new({
207             %$arg,
208             elements => _rsectionize(@sections),
209             });
210             }
211              
212             #pod =func olist
213             #pod
214             #pod my $rich_text_list = olist(@sections);
215             #pod # or
216             #pod my $rich_text_list = olist(\%arg, @sections);
217             #pod
218             #pod This is just shorthand for L</list>, but with a C<style> of C<ordered>.
219             #pod Because that's the only I<required> option for a list, you can omit the leading
220             #pod hashref in calls to C<olist>.
221             #pod
222             #pod =cut
223              
224 0     0 1 0 sub olist (@sections) {
  0         0  
  0         0  
225 0 0       0 my $arg = _HASH0($sections[0]) ? (shift @sections) : {};
226 0         0 Slack::BlockKit::Block::RichText::List->new({
227             %$arg,
228             style => 'ordered',
229             elements => _rsectionize(@sections),
230             });
231             }
232              
233             #pod =func ulist
234             #pod
235             #pod my $rich_text_list = ulist(@sections);
236             #pod # or
237             #pod my $rich_text_list = ulist(\%arg, @sections);
238             #pod
239             #pod This is just shorthand for L</list>, but with a C<style> of C<bullet>.
240             #pod Because that's the only I<required> option for a list, you can omit the leading
241             #pod hashref in calls to C<olist>.
242             #pod
243             #pod =cut
244              
245 0     0 1 0 sub ulist (@sections) {
  0         0  
  0         0  
246 0 0       0 my $arg = _HASH0($sections[0]) ? (shift @sections) : {};
247 0         0 Slack::BlockKit::Block::RichText::List->new({
248             %$arg,
249             style => 'bullet',
250             elements => _rsectionize(@sections),
251             });
252             }
253              
254             #pod =func preformatted
255             #pod
256             #pod my $rich_text_pre = preformatted(@elements);
257             #pod # or
258             #pod my $rich_text_pre = preformatted(\%arg, @elements);
259             #pod
260             #pod This returns a new L<preformatted rich
261             #pod text|Slack::BlockKit::Block::RichTextPreformatted> block object, with the given
262             #pod elements as its C<elements>. Any non-references in the list will be turned
263             #pod into text objects.
264             #pod
265             #pod The leading hashref, if given, will be used as extra arguments to the block
266             #pod object's constructor.
267             #pod
268             #pod B<Beware>: The Slack documentation suggests that C<emoji> objects can be
269             #pod present in the list of elements, but in practice, this always seems to get
270             #pod rejected by Slack.
271             #pod
272             #pod =cut
273              
274 0     0 1 0 sub preformatted (@elements) {
  0         0  
  0         0  
275 0 0       0 my $arg = _HASH0($elements[0]) ? (shift @elements) : {};
276              
277 0         0 Slack::BlockKit::Block::RichText::Preformatted->new({
278             %$arg,
279             elements => _rtextify(@elements),
280             });
281             }
282              
283             #pod =func quote
284             #pod
285             #pod my $rich_text_quote = quote(@elements);
286             #pod # or
287             #pod my $rich_text_quote = quote(\%arg, @elements);
288             #pod
289             #pod This returns a new L<quoted rich
290             #pod text|Slack::BlockKit::Block::Quote> block object, with the given elements as
291             #pod its C<elements>. Any non-references in the list will be turned into text
292             #pod objects.
293             #pod
294             #pod The leading hashref, if given, will be used as extra arguments to the block
295             #pod object's constructor.
296             #pod
297             #pod =cut
298              
299 0     0 1 0 sub quote (@elements) {
  0         0  
  0         0  
300 0 0       0 my $arg = _HASH0($elements[0]) ? (shift @elements) : {};
301              
302 0         0 Slack::BlockKit::Block::RichText::Quote->new({
303             %$arg,
304             elements => _rtextify(@elements),
305             });
306             }
307              
308             #pod =func channel
309             #pod
310             #pod my $rich_text_channel = channel($channel_id);
311             #pod # or
312             #pod my $rich_text_channel = channel(\%arg, $channel_id);
313             #pod
314             #pod This function returns a L<channel mention
315             #pod object|Slack::BlockKit::Block::RichText::Channel>, which can be used among
316             #pod other rich text elements to "mention" a channel. The C<$channel_id> should be
317             #pod the alphanumeric Slack channel id, not a channel name.
318             #pod
319             #pod If given, the C<%arg> hash is extra parameters to pass to the Channel
320             #pod constructor.
321             #pod
322             #pod =cut
323              
324             sub channel {
325 0 0   0 1 0 my ($arg, $id)
    0          
326             = @_ == 2 ? @_
327             : @_ == 1 ? ({}, $_[0])
328             : Carp::croak("BlockKit channel sugar called with wrong number of arguments");
329              
330 0         0 Slack::BlockKit::Block::RichText::Channel->new({
331             %$arg,
332             channel_id => $id,
333             });
334             }
335              
336             #pod =func date
337             #pod
338             #pod my $rich_text_date = date($timestamp, \%arg);
339             #pod
340             #pod This returns a L<rich text date object|Slack::BlockKit::Block::RichText::Date>
341             #pod for the given time (a unix timestamp). If given, the referenced C<%arg> can
342             #pod contain additional arguments to the Date constructor.
343             #pod
344             #pod Date formatting objects have a mandatory C<format> property. If none is given
345             #pod in C<%arg>, the default is:
346             #pod
347             #pod \x{200b}{date_short_pretty} at {time}
348             #pod
349             #pod Why that weird first character? It's a zero-width space, and suppresses the
350             #pod capitalization of "yesterday" (or other words) at the start. This
351             #pod capitalization seems like a bug (or bad design) in Slack.
352             #pod
353             #pod =cut
354              
355 0     0 1 0 sub date ($timestamp, $arg=undef) {
  0         0  
  0         0  
  0         0  
356 0   0     0 $arg //= {};
357              
358 0         0 Slack::BlockKit::Block::RichText::Date->new({
359             format => "\x{200b}{date_short_pretty} at {time}",
360             %$arg,
361             timestamp => $timestamp,
362             });
363             }
364              
365             #pod =func emoji
366             #pod
367             #pod my $rich_text_emoji = emoji($emoji_name);
368             #pod
369             #pod This function returns an L<emoji
370             #pod object|Slack::BlockKit::Block::RichText::Emoji> for the named emoji.
371             #pod
372             #pod =cut
373              
374 0     0 1 0 sub emoji ($name) {
  0         0  
  0         0  
375 0         0 Slack::BlockKit::Block::RichText::Emoji->new({
376             name => $name,
377             });
378             }
379              
380             #pod =func link
381             #pod
382             #pod my $rich_text_link = link($url);
383             #pod # or
384             #pod my $rich_text_link = link($url, \%arg);
385             #pod # or
386             #pod my $rich_text_link = link($url, $text_string);
387             #pod # or
388             #pod my $rich_text_link = link($url, $text_string, \%arg);
389             #pod
390             #pod This function returns a rich text L<link
391             #pod object|Slack::BlockKit::Block::RichText::Link> for the given URL.
392             #pod
393             #pod If given, the C<$text_string> string will be used as the display text for the
394             #pod link. If not, URL itself will be displayed.
395             #pod
396             #pod The optional C<\%arg> parameter contains additional attributes to be passed to
397             #pod the Link object constructor.
398             #pod
399             #pod =cut
400              
401             sub link {
402 0 0 0 0 1 0 Carp::croak("BlockKit link sugar called with wrong number of arguments")
403             if @_ > 3 || @_ < 1;
404              
405 0         0 my $url = $_[0];
406 0 0       0 my $text = ref $_[1] ? undef : $_[1];
407 0 0 0     0 my $arg = ref $_[1] ? $_[1] : ($_[2] // {});
408              
409 0 0       0 Slack::BlockKit::Block::RichText::Link->new({
410             %$arg,
411             (defined $text ? (text => $text) : ()),
412             url => $url,
413             });
414             }
415              
416             #pod =func richtext
417             #pod
418             #pod my $rich_text = richtext($text_string);
419             #pod # or
420             #pod my $rich_text = richtext(\@styles, $text_string);
421             #pod
422             #pod This function returns a new L<rich text text
423             #pod object|Slack::BlockKit::Block::RichText::Text> for the given text string. If a
424             #pod an arrayref is passed as the first argument, it is taken as a list of styles to
425             #pod apply to the string. So,
426             #pod
427             #pod richtext(['bold','italic'], "Hi");
428             #pod
429             #pod ...will produce an object that has this structure form:
430             #pod
431             #pod {
432             #pod type => 'text',
433             #pod styles => { bold => true(), italic => true() },
434             #pod text => "Hi",
435             #pod }
436             #pod
437             #pod =cut
438              
439             sub richtext {
440 0 0   0 1 0 my ($styles, $text)
    0          
    0          
    0          
441             = @_ == 2 ? (@_)
442             : @_ == 1 ? ([], $_[0])
443             : @_ == 0 ? Carp::croak("BlockKit richtext sugar called too few arguments")
444             : @_ > 2 ? Carp::croak("BlockKit richtext sugar called too many arguments")
445             : Carp::confess("unreachable code");
446              
447 0         0 my $arg = {};
448 0         0 $arg->{style}{$_} = 1 for $styles->@*;
449              
450 0         0 Slack::BlockKit::Block::RichText::Text->new({
451             %$arg,
452             text => $text,
453             });
454             }
455              
456             #pod =func bold
457             #pod
458             #pod =func code
459             #pod
460             #pod =func italic
461             #pod
462             #pod =func strike
463             #pod
464             #pod These functions are all called the same way:
465             #pod
466             #pod my $rich_text = bold($text_string);
467             #pod
468             #pod They are shortcuts for calling C<richtext> with one style applied: the style of
469             #pod their function name.
470             #pod
471             #pod =cut
472              
473 0     0 1 0 sub bold ($text) { richtext(['bold'], $text) }
  0         0  
  0         0  
  0         0  
474 0     0 1 0 sub code ($text) { richtext(['code'], $text) }
  0         0  
  0         0  
  0         0  
475 0     0 1 0 sub italic ($text) { richtext(['italic'], $text) }
  0         0  
  0         0  
  0         0  
476 0     0 1 0 sub strike ($text) { richtext(['strike'], $text) }
  0         0  
  0         0  
  0         0  
477              
478             #pod =func user
479             #pod
480             #pod my $rich_text_user = user($user_id);
481             #pod # or
482             #pod my $rich_text_user = user(\%arg, $user_id);
483             #pod
484             #pod This function returns a L<user mention
485             #pod object|Slack::BlockKit::Block::RichText::User>, which can be used among
486             #pod other rich text elements to "mention" a user. The C<$user_id> should be
487             #pod the alphanumeric Slack user id, not a user name.
488             #pod
489             #pod If given, the C<%arg> hash is extra parameters to pass to the User
490             #pod constructor.
491             #pod
492             #pod =cut
493              
494             sub user {
495 0 0   0 1 0 my ($arg, $id)
    0          
496             = @_ == 2 ? @_
497             : @_ == 1 ? ({}, $_[0])
498             : Carp::croak("BlockKit user sugar called with wrong number of arguments");
499              
500 0         0 Slack::BlockKit::Block::RichText::User->new({
501             %$arg,
502             user_id => $id,
503             });
504             }
505              
506             #pod =func usergroup
507             #pod
508             #pod my $rich_text_usergroup = usergroup($usergroup_id);
509             #pod # or
510             #pod my $rich_text_usergroup = usergroup(\%arg, $usergroup_id);
511             #pod
512             #pod This function returns a L<usergroup mention
513             #pod object|Slack::BlockKit::Block::RichText::UserGroup>, which can be used among
514             #pod other rich text elements to "mention" a user group. The C<$usergroup_id>
515             #pod should be the alphanumeric Slack usergroup id, not a group name.
516             #pod
517             #pod If given, the C<%arg> hash is extra parameters to pass to the UserGroup
518             #pod constructor.
519             #pod
520             #pod =cut
521              
522             sub usergroup {
523 0 0   0 1 0 my ($arg, $id)
    0          
524             = @_ == 2 ? @_
525             : @_ == 1 ? ({}, $_[0])
526             : Carp::croak("BlockKit usergroup sugar called with wrong number of arguments");
527              
528 0         0 Slack::BlockKit::Block::RichText::UserGroup->new({
529             %$arg,
530             user_group_id => $id,
531             });
532             }
533              
534             #pod =func context
535             #pod
536             #pod my $context = context(@text_objs_or_strings);
537             #pod
538             #pod This function returns a L<context object|Slack::BlockKit::Block::Context>. You
539             #pod can pass it a list of values, each of which is either a string or a L<text
540             #pod object|Slack::BlockKit::CompObj::Text>. Strings will be promoted to
541             #pod C<mrkdwn>-type text objects.
542             #pod
543             #pod =cut
544              
545 0     0 1 0 sub context (@args) {
  0         0  
  0         0  
546 0         0 my @elements;
547              
548 0         0 ARG: for my $arg (@args) {
549 0 0       0 if (builtin::blessed $arg) {
550 0 0       0 Carp::croak("non-Text section passed as argument to BlockKit context sugar")
551             unless $arg->isa('Slack::BlockKit::CompObj::Text');
552              
553 0         0 push @elements, $arg;
554 0         0 next ARG;
555             }
556              
557 0 0       0 if (ref $arg) {
558 0         0 Carp::croak("unblessed reference passed as argument to BlockKit context sugar");
559             }
560              
561 0         0 push @elements, Slack::BlockKit::CompObj::Text->new({
562             type => 'mrkdwn',
563             text => $arg,
564             });
565             }
566              
567 0         0 return Slack::BlockKit::Block::Context->new({ elements => \@elements });
568             }
569              
570             #pod =func divider
571             #pod
572             #pod my $divider = divider();
573             #pod
574             #pod This function returns a L<divider black
575             #pod object|Slack::BlockKit::Block::Divider>. It takes no parameters, because there
576             #pod is nothing simpler than a divider block.
577             #pod
578             #pod =cut
579              
580 0     0 1 0 sub divider () {
  0         0  
581 0         0 Slack::BlockKit::Block::Divider->new();
582             }
583              
584             #pod =func header
585             #pod
586             #pod my $header = header($text_string);
587             #pod # or
588             #pod my $header = header($text_obj);
589             #pod # or
590             #pod my $header = header(\%arg);
591             #pod
592             #pod This function returns a L<header object|Slack::BlockKit::Block::Header>, which
593             #pod generally has only one property: a C<text> object with type C<plain_text>. To
594             #pod make that easy to build, you can just pass a text string to C<header> and the
595             #pod text object will be created for you.
596             #pod
597             #pod Alternatively, you can pass in a text object (like you'd get from the
598             #pod C<text>) function or a reference to an hash of arguments. That last form is
599             #pod mostly useful if you need to give the header a C<block_id>.
600             #pod
601             #pod =cut
602              
603             # This isn't great, it should have the $text,$arg format.
604 2     2 1 4 sub header ($arg) {
  2         4  
  2         5  
605 2 50       7 if (builtin::blessed $arg) {
606             # I am unfairly assuming this is a Text block, but I can tighten this up
607             # later. -- rjbs, 2024-06-29
608 2 50       16 Carp::croak("non-Text section passed as argument to BlockKit header sugar")
609             unless $arg->isa('Slack::BlockKit::CompObj::Text');
610              
611 2         117 return Slack::BlockKit::Block::Header->new({ text => $arg })
612             }
613              
614 0 0       0 if (ref $arg) {
615 0         0 return Slack::BlockKit::Block::Header->new($arg);
616             }
617              
618 0         0 return Slack::BlockKit::Block::Header->new({ text => text($arg) });
619             }
620              
621             #pod =func image
622             #pod
623             #pod my $image = image($url, $alt_text);
624             #pod # or
625             #pod my $image = image($url, $alt_text, \%arg);
626             #pod
627             #pod This function returns an L<image block object|Slack::BlockKit::Block::Image>
628             #pod for the given URL, with the given alt text.
629             #pod
630             #pod The optional C<\%arg> parameter contains additional attributes to be passed to
631             #pod the Image object constructor.
632             #pod
633             #pod =cut
634              
635 0     0 1 0 sub image ($url, $alt_text, $arg = undef) {
  0         0  
  0         0  
  0         0  
  0         0  
636 0   0     0 $arg //= {};
637              
638 0         0 Slack::BlockKit::Block::Image->new({
639             %$arg,
640             image_url => $url,
641             alt_text => $alt_text,
642             });
643             }
644              
645             #pod =func markdown
646             #pod
647             #pod my $text_obj = markdown($text_string);
648             #pod
649             #pod This returns a L<Markdown block object|Slack::BlockKit::CompObj::Text> with the
650             #pod given string as its text. Don't confuse this with C<mrkdwn>, which produces a
651             #pod text composition object.
652             #pod
653             #pod =cut
654              
655 0     0 1 0 sub markdown ($text) {
  0         0  
  0         0  
656 0         0 Slack::BlockKit::Block::Markdown->new({
657             text => $text,
658             });
659             }
660              
661             #pod =func section
662             #pod
663             #pod my $section = section($text_obj);
664             #pod # or
665             #pod my $section = section($text_string);
666             #pod # or
667             #pod my $section = section(\%arg);
668             #pod
669             #pod This function returns a L<section object|Slack::BlockKit::Block::Section>. You
670             #pod can pass it a text object, which will be used as the C<text> property of the
671             #pod section. You can pass it a string, which will be promoted the a text object
672             #pod and then used the same way.
673             #pod
674             #pod Otherwise, you'll have to pass a reference to a hash of argument that will be
675             #pod passed to the section constructor. If this function feels weird, it might just
676             #pod be because the C<section> element in Block Kit is a bit weird. Sorry!
677             #pod
678             #pod =cut
679              
680             # Maybe we should add a fields() function? I'll do that in the future if I
681             # find myself ever wanting it. -- rjbs, 2024-07-03
682 3     3 1 2352 sub section ($arg) {
  3         7  
  3         7  
683 3 50       11 if (builtin::blessed $arg) {
684             # I am unfairly assuming this is a Text block, but I can tighten this up
685             # later. -- rjbs, 2024-06-29
686 0 0       0 Carp::croak("non-Text section passed as argument to BlockKit section sugar")
687             unless $arg->isa('Slack::BlockKit::CompObj::Text');
688              
689 0         0 return Slack::BlockKit::Block::Section->new({ text => $arg })
690             }
691              
692 3 50       10 if (ref $arg) {
693 3         205 return Slack::BlockKit::Block::Section->new($arg);
694             }
695              
696 0         0 return Slack::BlockKit::Block::Section->new({ text => mrkdwn($arg) });
697             }
698              
699             #pod =func mrkdwn
700             #pod
701             #pod my $text_obj = mrkdown($text_string, \%arg);
702             #pod
703             #pod This returns a L<text composition object|Slack::BlockKit::CompObj::Text> with a
704             #pod type of C<mrkdwn> and the given string as its text. The C<\%arg> option is
705             #pod optional. If given, it's extra parameters to pass to the text object
706             #pod constructor.
707             #pod
708             #pod For C<plain_text> text composition objects, see the C<text> function.
709             #pod
710             #pod =cut
711              
712 3     3 1 412939 sub mrkdwn ($text, $arg=undef) {
  3         10  
  3         10  
  3         6  
713 3   100     19 $arg //= {};
714              
715 3         230 Slack::BlockKit::CompObj::Text->new({
716             %$arg,
717             type => 'mrkdwn',
718             text => $text,
719             });
720             }
721              
722             #pod =func text
723             #pod
724             #pod my $text_obj = text($text_string, \%arg);
725             #pod
726             #pod This returns a L<text composition object|Slack::BlockKit::CompObj::Text> with a
727             #pod type of C<plain_text> and the given string as its text. The C<\%arg> option is
728             #pod optional. If given, it's extra parameters to pass to the text object
729             #pod constructor.
730             #pod
731             #pod For C<mrkdwn> text composition objects, see the C<mrkdwn> function.
732             #pod
733             #pod =cut
734              
735 4     4 1 894318 sub text ($text, $arg=undef) {
  4         15  
  4         12  
  4         10  
736 4   100     34 $arg //= {};
737              
738 4         288 Slack::BlockKit::CompObj::Text->new({
739             %$arg,
740             type => 'plain_text',
741             text => $text,
742             });
743             }
744              
745             1;
746              
747             __END__
748              
749             =pod
750              
751             =encoding UTF-8
752              
753             =head1 NAME
754              
755             Slack::BlockKit::Sugar - sugar for building Block Kit structures easily (start here!)
756              
757             =head1 VERSION
758              
759             version 0.005
760              
761             =head1 OVERVIEW
762              
763             This module exports a bunch of functions that can be composed to build Block
764             Kit block collections, which can then be easily turned into a data structure
765             that can be serialized.
766              
767             If you learn to use I<this> library, you can generally ignore the other dozen
768             (more!) modules in this distribution. On the other hand, you B<must> more or
769             less understand how Block Kit works, which means reading the L<BlockKit
770             documentation|https://api.slack.com/block-kit> at Slack's API site.
771              
772             The key is to have a decent idea of the Block Kit structure you want. Knowing
773             that, you can look at the list of functions provided by this library and
774             compose them together. Start with a call to C<blocks>, passing the result of
775             calling other generators to it. In the end, you'll have an object on which you
776             can call C<< ->as_struct >>.
777              
778             For example, to produce something basically equivalent to this Markdown (well,
779             mrkdwn):
780              
781             Here is a *safe* link: **[click me](https://rjbs.cloud/)**
782             * it will be fun
783             * it will be cool 🙂
784             * it will be enough
785              
786             You can use this set of calls to the sugar functions:
787              
788             blocks(
789             richblock(
790             richsection(
791             "Here is a ", italic("safe"), " link: ",
792             link("https://fastmail.com/", "click me", { style => { bold => 1 } }),
793             ),
794             ulist(
795             "it will be fun",
796             richsection("it will be cool", emoji('smile')),
797             "it will be enough",
798             ),
799             )
800             );
801              
802             =head2 Importing the functions
803              
804             This library uses L<Sub::Exporter> for exporting its functions. That means
805             that they can be renamed during import. Since the names of these functions are
806             fairly generic, you might prefer to write this:
807              
808             use Slack::BlockKit::Sugar -all => { -prefix => 'bk_' };
809              
810             This will import all the sugar functions with their names prefixed by C<bk_>.
811             So, for example, C<italic> will be C<bk_italic>.
812              
813             =head1 PERL VERSION
814              
815             This module should work on any version of perl still receiving updates from
816             the Perl 5 Porters. This means it should work on any version of perl
817             released in the last two to three years. (That is, if the most recently
818             released version is v5.40, then this module should work on both v5.40 and
819             v5.38.)
820              
821             Although it may work on older versions of perl, no guarantee is made that the
822             minimum required version will not be increased. The version may be increased
823             for any reason, and there is no promise that patches will be accepted to
824             lower the minimum required perl.
825              
826             =head1 FUNCTIONS
827              
828             =head2 blocks
829              
830             my $block_col = blocks( @blocks );
831              
832             This returns a L<Slack::BlockKit::BlockCollection>, which just collects blocks
833             and will return their serializable structure form in an arrayref when C<<
834             ->as_struct >> is called on it.
835              
836             If any argument is a non-reference, it will be upgraded to a L</section> block
837             containing a single L</mrkdwn> text object.
838              
839             This means the simplest non-trivial message you could send is something like:
840              
841             blocks("This is a *formatted* message.")->as_struct;
842              
843             =head2 richblocks
844              
845             my $rich_text_block = richblocks( @rich_elements );
846              
847             This returns a L<Slack::BlockKit::Block::RichText>, representing a
848             C<rich_text>-type block. It's a bit annoying that you need it, but you do.
849             You'll also need to pass one or more rich text elements as arguments, something
850             like this:
851              
852             blocks(
853             richblocks(
854             section("This is ", italic("very"), " important."),
855             ),
856             );
857              
858             Each non-reference passed in the list of rich text elements will be turned into
859             a rich text objects inside its own section object.
860              
861             =head2 richsection
862              
863             $rich_text_section = richsection( @elements );
864              
865             This returns a single L<rich text
866             section|Slack::BlockKit::Block::RichText::Section> object containing the passed
867             elements as its C<elements> attribute value.
868              
869             Non-reference values in C<@elements> be converted to rich text objects.
870              
871             =head2 list
872              
873             my $rich_text_list = list(\%arg, @sections);
874              
875             This returns a L<rich text list|Slack::BlockKit::Block::RichText::List> object.
876             C<@sections> must be a list of rich text sections or plain scalars. Plain
877             scalars will each be converted to a rich text object contained in a rich text
878             section.
879              
880             The C<%arg> hash is more arguments to the List object constructor. It B<must>
881             contain a C<style> key. For shorthand, see L</olist> and L</ulist> below.
882              
883             =head2 olist
884              
885             my $rich_text_list = olist(@sections);
886             # or
887             my $rich_text_list = olist(\%arg, @sections);
888              
889             This is just shorthand for L</list>, but with a C<style> of C<ordered>.
890             Because that's the only I<required> option for a list, you can omit the leading
891             hashref in calls to C<olist>.
892              
893             =head2 ulist
894              
895             my $rich_text_list = ulist(@sections);
896             # or
897             my $rich_text_list = ulist(\%arg, @sections);
898              
899             This is just shorthand for L</list>, but with a C<style> of C<bullet>.
900             Because that's the only I<required> option for a list, you can omit the leading
901             hashref in calls to C<olist>.
902              
903             =head2 preformatted
904              
905             my $rich_text_pre = preformatted(@elements);
906             # or
907             my $rich_text_pre = preformatted(\%arg, @elements);
908              
909             This returns a new L<preformatted rich
910             text|Slack::BlockKit::Block::RichTextPreformatted> block object, with the given
911             elements as its C<elements>. Any non-references in the list will be turned
912             into text objects.
913              
914             The leading hashref, if given, will be used as extra arguments to the block
915             object's constructor.
916              
917             B<Beware>: The Slack documentation suggests that C<emoji> objects can be
918             present in the list of elements, but in practice, this always seems to get
919             rejected by Slack.
920              
921             =head2 quote
922              
923             my $rich_text_quote = quote(@elements);
924             # or
925             my $rich_text_quote = quote(\%arg, @elements);
926              
927             This returns a new L<quoted rich
928             text|Slack::BlockKit::Block::Quote> block object, with the given elements as
929             its C<elements>. Any non-references in the list will be turned into text
930             objects.
931              
932             The leading hashref, if given, will be used as extra arguments to the block
933             object's constructor.
934              
935             =head2 channel
936              
937             my $rich_text_channel = channel($channel_id);
938             # or
939             my $rich_text_channel = channel(\%arg, $channel_id);
940              
941             This function returns a L<channel mention
942             object|Slack::BlockKit::Block::RichText::Channel>, which can be used among
943             other rich text elements to "mention" a channel. The C<$channel_id> should be
944             the alphanumeric Slack channel id, not a channel name.
945              
946             If given, the C<%arg> hash is extra parameters to pass to the Channel
947             constructor.
948              
949             =head2 date
950              
951             my $rich_text_date = date($timestamp, \%arg);
952              
953             This returns a L<rich text date object|Slack::BlockKit::Block::RichText::Date>
954             for the given time (a unix timestamp). If given, the referenced C<%arg> can
955             contain additional arguments to the Date constructor.
956              
957             Date formatting objects have a mandatory C<format> property. If none is given
958             in C<%arg>, the default is:
959              
960             \x{200b}{date_short_pretty} at {time}
961              
962             Why that weird first character? It's a zero-width space, and suppresses the
963             capitalization of "yesterday" (or other words) at the start. This
964             capitalization seems like a bug (or bad design) in Slack.
965              
966             =head2 emoji
967              
968             my $rich_text_emoji = emoji($emoji_name);
969              
970             This function returns an L<emoji
971             object|Slack::BlockKit::Block::RichText::Emoji> for the named emoji.
972              
973             =head2 link
974              
975             my $rich_text_link = link($url);
976             # or
977             my $rich_text_link = link($url, \%arg);
978             # or
979             my $rich_text_link = link($url, $text_string);
980             # or
981             my $rich_text_link = link($url, $text_string, \%arg);
982              
983             This function returns a rich text L<link
984             object|Slack::BlockKit::Block::RichText::Link> for the given URL.
985              
986             If given, the C<$text_string> string will be used as the display text for the
987             link. If not, URL itself will be displayed.
988              
989             The optional C<\%arg> parameter contains additional attributes to be passed to
990             the Link object constructor.
991              
992             =head2 richtext
993              
994             my $rich_text = richtext($text_string);
995             # or
996             my $rich_text = richtext(\@styles, $text_string);
997              
998             This function returns a new L<rich text text
999             object|Slack::BlockKit::Block::RichText::Text> for the given text string. If a
1000             an arrayref is passed as the first argument, it is taken as a list of styles to
1001             apply to the string. So,
1002              
1003             richtext(['bold','italic'], "Hi");
1004              
1005             ...will produce an object that has this structure form:
1006              
1007             {
1008             type => 'text',
1009             styles => { bold => true(), italic => true() },
1010             text => "Hi",
1011             }
1012              
1013             =head2 bold
1014              
1015             =head2 code
1016              
1017             =head2 italic
1018              
1019             =head2 strike
1020              
1021             These functions are all called the same way:
1022              
1023             my $rich_text = bold($text_string);
1024              
1025             They are shortcuts for calling C<richtext> with one style applied: the style of
1026             their function name.
1027              
1028             =head2 user
1029              
1030             my $rich_text_user = user($user_id);
1031             # or
1032             my $rich_text_user = user(\%arg, $user_id);
1033              
1034             This function returns a L<user mention
1035             object|Slack::BlockKit::Block::RichText::User>, which can be used among
1036             other rich text elements to "mention" a user. The C<$user_id> should be
1037             the alphanumeric Slack user id, not a user name.
1038              
1039             If given, the C<%arg> hash is extra parameters to pass to the User
1040             constructor.
1041              
1042             =head2 usergroup
1043              
1044             my $rich_text_usergroup = usergroup($usergroup_id);
1045             # or
1046             my $rich_text_usergroup = usergroup(\%arg, $usergroup_id);
1047              
1048             This function returns a L<usergroup mention
1049             object|Slack::BlockKit::Block::RichText::UserGroup>, which can be used among
1050             other rich text elements to "mention" a user group. The C<$usergroup_id>
1051             should be the alphanumeric Slack usergroup id, not a group name.
1052              
1053             If given, the C<%arg> hash is extra parameters to pass to the UserGroup
1054             constructor.
1055              
1056             =head2 context
1057              
1058             my $context = context(@text_objs_or_strings);
1059              
1060             This function returns a L<context object|Slack::BlockKit::Block::Context>. You
1061             can pass it a list of values, each of which is either a string or a L<text
1062             object|Slack::BlockKit::CompObj::Text>. Strings will be promoted to
1063             C<mrkdwn>-type text objects.
1064              
1065             =head2 divider
1066              
1067             my $divider = divider();
1068              
1069             This function returns a L<divider black
1070             object|Slack::BlockKit::Block::Divider>. It takes no parameters, because there
1071             is nothing simpler than a divider block.
1072              
1073             =head2 header
1074              
1075             my $header = header($text_string);
1076             # or
1077             my $header = header($text_obj);
1078             # or
1079             my $header = header(\%arg);
1080              
1081             This function returns a L<header object|Slack::BlockKit::Block::Header>, which
1082             generally has only one property: a C<text> object with type C<plain_text>. To
1083             make that easy to build, you can just pass a text string to C<header> and the
1084             text object will be created for you.
1085              
1086             Alternatively, you can pass in a text object (like you'd get from the
1087             C<text>) function or a reference to an hash of arguments. That last form is
1088             mostly useful if you need to give the header a C<block_id>.
1089              
1090             =head2 image
1091              
1092             my $image = image($url, $alt_text);
1093             # or
1094             my $image = image($url, $alt_text, \%arg);
1095              
1096             This function returns an L<image block object|Slack::BlockKit::Block::Image>
1097             for the given URL, with the given alt text.
1098              
1099             The optional C<\%arg> parameter contains additional attributes to be passed to
1100             the Image object constructor.
1101              
1102             =head2 markdown
1103              
1104             my $text_obj = markdown($text_string);
1105              
1106             This returns a L<Markdown block object|Slack::BlockKit::CompObj::Text> with the
1107             given string as its text. Don't confuse this with C<mrkdwn>, which produces a
1108             text composition object.
1109              
1110             =head2 section
1111              
1112             my $section = section($text_obj);
1113             # or
1114             my $section = section($text_string);
1115             # or
1116             my $section = section(\%arg);
1117              
1118             This function returns a L<section object|Slack::BlockKit::Block::Section>. You
1119             can pass it a text object, which will be used as the C<text> property of the
1120             section. You can pass it a string, which will be promoted the a text object
1121             and then used the same way.
1122              
1123             Otherwise, you'll have to pass a reference to a hash of argument that will be
1124             passed to the section constructor. If this function feels weird, it might just
1125             be because the C<section> element in Block Kit is a bit weird. Sorry!
1126              
1127             =head2 mrkdwn
1128              
1129             my $text_obj = mrkdown($text_string, \%arg);
1130              
1131             This returns a L<text composition object|Slack::BlockKit::CompObj::Text> with a
1132             type of C<mrkdwn> and the given string as its text. The C<\%arg> option is
1133             optional. If given, it's extra parameters to pass to the text object
1134             constructor.
1135              
1136             For C<plain_text> text composition objects, see the C<text> function.
1137              
1138             =head2 text
1139              
1140             my $text_obj = text($text_string, \%arg);
1141              
1142             This returns a L<text composition object|Slack::BlockKit::CompObj::Text> with a
1143             type of C<plain_text> and the given string as its text. The C<\%arg> option is
1144             optional. If given, it's extra parameters to pass to the text object
1145             constructor.
1146              
1147             For C<mrkdwn> text composition objects, see the C<mrkdwn> function.
1148              
1149             =head1 ACHTUNG
1150              
1151             This library seems pretty good to me, its author, but it hasn't seen much use
1152             yet. As it gets used, it may change in somewhat backward-incompatible ways.
1153             Upgrade carefully until this warning goes away!
1154              
1155             =head1 AUTHOR
1156              
1157             Ricardo SIGNES <cpan@semiotic.systems>
1158              
1159             =head1 COPYRIGHT AND LICENSE
1160              
1161             This software is copyright (c) 2024 by Ricardo SIGNES.
1162              
1163             This is free software; you can redistribute it and/or modify it under
1164             the same terms as the Perl 5 programming language system itself.
1165              
1166             =cut