File Coverage

blib/lib/Distribution/Cooker.pm
Criterion Covered Total %
statement 142 205 69.2
branch 16 42 38.1
condition 9 23 39.1
subroutine 33 41 80.4
pod 24 24 100.0
total 224 335 66.8


line stmt bran cond sub pod time code
1 7     7   78929 use v5.26;
  7         41  
2 7     7   4117 use utf8;
  7         102  
  7         40  
3              
4             package Distribution::Cooker;
5 7     7   3485 use experimental qw(signatures);
  7         23993  
  7         40  
6              
7             our $VERSION = '2.002';
8              
9 7     7   1359 use Carp qw(croak carp);
  7         19  
  7         370  
10 7     7   55 use Cwd;
  7         16  
  7         419  
11 7     7   5444 use Config::IniFiles;
  7         210614  
  7         277  
12 7     7   72 use File::Find;
  7         17  
  7         439  
13 7     7   47 use File::Basename qw(dirname);
  7         29  
  7         296  
14 7     7   53 use File::Path qw(make_path);
  7         26  
  7         330  
15 7     7   3567 use File::Spec::Functions qw(catfile abs2rel);
  7         5746  
  7         487  
16 7     7   3269 use IO::Interactive qw(is_interactive);
  7         7290  
  7         46  
17 7     7   3615 use Mojo::File;
  7         1432477  
  7         409  
18 7     7   3891 use Mojo::Template;
  7         50427  
  7         62  
19 7     7   405 use Mojo::Util qw(decode encode trim dumper);
  7         18  
  7         17723  
20              
21             __PACKAGE__->run( @ARGV ) unless caller;
22              
23             =encoding utf8
24              
25             =head1 NAME
26              
27             Distribution::Cooker - Create a Perl module directory from your own templates
28              
29             =head1 SYNOPSIS
30              
31             # The dist_cooker is a wrapper for the module
32             % dist_cooker Foo::Bar "This module does that" repo_slug
33              
34             # The dist_cooker can prompt for what's missing
35             % dist_cooker Foo::Bar
36             Description> This module does that
37             Repo name> foo-bar
38              
39             # the script just passes @ARGV to the module
40             use Distribution::Cooker;
41             Distribution::Cooker->run( @ARGV );
42              
43             # if you don't like something, subclass and override
44             package Local::Distribution::Cooker {
45             use parent qw(Distribution::Cooker);
46             sub config_file_path { ... }
47             }
48              
49             =head1 DESCRIPTION
50              
51             This module takes a directory of templates and processes them with
52             L. It's specifically tooled toward Perl modules, and
53             the templates are given a set of variables.
54              
55             The templates have special values for C, C, and
56             C since the default L values get confused when
57             there's Perl code outside them.
58              
59             Tags use « (U+00AB) and » (U+00BB), and whole lines use ϕ (U+03D5):
60              
61             This is the « $module » module
62              
63             ϕ This is a line of Perl code
64              
65             My own templates are at L.
66              
67             =head2 Process methods
68              
69             =over 4
70              
71             =item * cook
72              
73             Take the templates and cook them. This version uses L
74             Toolkit, but you can make a subclass to override it. See the notes
75             about Mojo::Template.
76              
77             I assume my own favorite values, and haven't made these
78             customizable yet.
79              
80             =over 4
81              
82             =item * Your distribution template directory is F<~/.templates/dist_cooker>
83              
84             =item * Your module template name is F, which will be moved into place later
85              
86             =back
87              
88             This uses L to render the templates, and various
89             settings. The values from C are passed to the templates
90             and its keys are available as named variables.
91              
92             By default, these tag settings are used because these characters
93             are unlikely to appear in Perl code:
94              
95             * the line_start is ϕ (U+03D5)
96             * the tag start is »
97             * the line start is «
98              
99             For example:
100              
101             This is module « $module »
102              
103             When C processes the templates, it provides definitions for
104             these template variables listed for C.
105              
106             While processing the templates, C ignores F<.git>, F<.svn>, and
107             F directories.
108              
109              
110             =cut
111              
112 0     0 1 0 sub cook ( $self ) {
  0         0  
  0         0  
113 0         0 my $dir = lc $self->dist;
114              
115 0         0 my $cwd = Cwd::getcwd;
116              
117 0         0 make_path( $dir );
118 0 0       0 croak "<$dir> does not exist" unless -d $dir;
119 0 0       0 chdir $dir or croak "chdir $dir: $!";
120              
121 0         0 my $files = $self->template_files;
122              
123 0         0 my $old = catfile( 'lib', $self->module_template_basename );
124 0         0 my $new = catfile( 'lib', $self->module_path );
125              
126 0         0 my $vars = $self->template_vars;
127              
128             my $mt = Mojo::Template->new
129             ->line_start( $self->{line_start} )
130             ->tag_start( $self->{tag_start} )
131             ->tag_end( $self->{tag_end} )
132 0         0 ->vars(1);
133 0         0 foreach my $file ( $files->@* ) {
134 0         0 my $new_file = abs2rel( $file, $self->template_dir );
135              
136 0 0       0 if( -d $file ) {
137 0         0 make_path( $new_file );
138 0         0 next;
139             }
140              
141 0         0 my $contents = decode( 'UTF-8', Mojo::File->new( $file )->slurp );
142 0         0 my $rendered = $mt->vars(1)->render( $contents, $vars );
143 0         0 Mojo::File->new( $new_file )->spurt( encode( 'UTF-8', $rendered ) );
144             }
145              
146 0         0 make_path dirname($new);
147 0 0       0 rename $old => $new
148             or croak "Could not rename [$old] to [$new]: $!";
149             }
150              
151             =item * init
152              
153             Initialize the object. There's nothing fancy here, but if you need
154             something more powerful you can create a subclass and run some info here.
155              
156             This step happens right after object create and configuration handling
157             and before the C step. By default, this does nothing.
158              
159             =cut
160              
161 3     3 1 1201 sub init { 1 }
162              
163             =item * new
164              
165             Creates the bare object with the name and email of the module author,
166             looking for values in this order, with any combination for author and
167             email:
168              
169             * take values from the env: DIST_COOKER_AUTHOR and DIST_COOKER_EMAIL
170             * look at git config for C and C
171             * use default values from the method C and C
172              
173             This looks for F<~/.dist_cooker.ini> to read the INI config and add that
174             information to the object.
175              
176             Override C to use a different name.
177              
178              
179             =cut
180              
181 8     8 1 19265 sub new ( $class ) { bless $class->get_config, $class }
  8         41  
  8         15  
  8         34  
182              
183             =item * pre_run
184              
185             Runs right before C does its work.
186              
187             run() calls this method immediately after it creates the object and
188             after it initializes it. By default, this does nothing.
189              
190              
191             =cut
192              
193 3     3 1 2191 sub pre_run { 1 }
194              
195             =item * post_run
196              
197             C calls this method right after it processes the template files.
198             By default, this does nothing.
199              
200             =cut
201              
202 3     3 1 20 sub post_run { 1 }
203              
204             =item * report
205              
206             =cut
207              
208 2     2 1 9 sub report ( $self ) {
  2         20  
  2         17  
209 2 50       404 open my $fh, '>', 'cooker_report.txt' or return;
210              
211 2         14 print { $fh } "$0 " . localtime() . "\n";
  2         141  
212              
213 2         10 print { $fh } dumper( $self->template_vars ), "\n";
  2         41  
214             }
215              
216             =item * run( [ MODULE_NAME, [ DESCRIPTION ] ] )
217              
218             The C method kicks off everything, and gives you a chance to
219             do things between steps/.
220              
221             * create the object
222             * run init (by default, does nothing)
223             * run pre_run (by default, does nothing)
224             * collects information and prompts interactively for what it needs
225             * cooks the templates (~/.templates/modules by default)
226             * run post_run (by default, does nothing)
227             * create cooker_report.txt (it's in .gitignore)
228              
229             If you don't specify the module name, it prompts you. If you don't
230             specify a description, it prompts you.
231              
232             =cut
233              
234 3     3 1 12174 sub run ( $class, $module, @args ) {
  2         10  
  2         12  
  2         14  
  2         9  
235 2         8 my( $description, $repo_name ) = @args;
236              
237 2         19 my $self = $class->new;
238 2         88 $self->init;
239              
240 2         33 $self->pre_run;
241              
242 2   33     46 $self->module( $module || prompt( "Module name" ) );
243 2 50       23 croak( "No module specified!\n" ) unless $self->module;
244 2 50       23 croak( "Illegal module name [$module]\n" )
245             unless $self->module =~ m/ \A [A-Za-z0-9_]+ ( :: [A-Za-z0-9_]+ )* \z /x;
246 2   50     52 $self->description( $description || prompt( "Description" ) || "An undescribed module" );
247              
248 2   66     47 $self->repo_name( $repo_name || prompt( "Repo name" ) );
249              
250 2         14 $self->dist( $self->module_to_distname( $self->module ) );
251              
252 2         18 $self->cook;
253              
254 2         28 $self->post_run;
255              
256 2         22 $self->report;
257              
258 2         1090 $self;
259             }
260              
261              
262             =back
263              
264             =head2 Informative methods
265              
266             These provide information the processing needs to do its work.
267              
268             =over 4
269              
270             =item * config_file_name
271              
272             Return the filename (the basename) of the config file. The default is
273             F<.dist_cooker.ini>.
274              
275             =cut
276              
277 8     8 1 87 sub config_file_name { '.dist_cooker.ini' }
278              
279             =item * default_author_email
280              
281             =item * default_author_name
282              
283             Returns the last resort values for author name or email. These are
284             C and C.
285              
286             =cut
287              
288 0     0 1 0 sub default_author_email ( $class ) { 'serpico@example.com' }
  0         0  
  0         0  
  0         0  
289 0     0 1 0 sub default_author_name ( $class ) { 'Frank Serpico' }
  0         0  
  0         0  
  0         0  
290              
291             =item * description( [ DESCRIPTION ] )
292              
293             Returns the description of the module. With an argument, it sets
294             the value.
295              
296             The default name is C. You can override
297             this in a subclass.
298              
299             =cut
300              
301 3     3 1 38 sub description ( $class, @args ) {
  3         12  
  3         10  
  3         24  
302 3 100       26 $class->{description} = $args[0] if defined $args[0];
303 3 50       90 $class->{description} || 'TODO: describe this module'
304             }
305              
306             =item * template_dir
307              
308             Returns the path for the distribution templates. The default is
309             F<$ENV{HOME}/.templates/modules>. If that path is a symlink, this
310             returns that target of that link.
311              
312             =cut
313              
314             sub template_dir {
315 0     0 1 0 my $path = catfile( $ENV{HOME}, '.templates', 'modules' );
316 0 0       0 $path = readlink($path) if -l $path;
317              
318 0 0       0 croak "Couldn't find templates at $path!\n" unless -d $path;
319              
320 0         0 $path;
321             }
322              
323             =item * default_config
324              
325             Returns a hash reference of the config values.
326              
327             * author_name
328             * email
329             * line_start
330             * tag_end
331             * tag_start
332              
333             This looks for values in this order, and in any combination:
334              
335             * take values from the env: DIST_COOKER_AUTHOR and DIST_COOKER_EMAIL
336             * look at git config for C and C
337             * use default values from the method C and C
338              
339             =cut
340              
341             sub _git_user_name {
342 8     8   64282 my $name = `git config user.name`;
343 8         206 $name =~ s/\R//g;
344 8 50       210 trim( $name ) if length $name;
345 8         580 $name;
346             }
347              
348             sub _git_user_email {
349 8     8   44882 my $email = `git config user.name`;
350 8         196 $email =~ s/\R//g;
351 8 50       527 trim( $email ) if defined $email;
352 8         586 $email;
353             }
354              
355 8     8 1 16 sub default_config ( $class ) {
  8         14  
  8         15  
356             my( $author, $email ) = (
357             $ENV{DIST_COOKER_AUTHOR} // _git_user_name() // $class->default_author_name,
358 8   33     61 $ENV{DIST_COOKER_EMAIL} // _git_user_email() // $class->default_author_email,
      33        
      33        
      33        
359             );
360              
361             {
362 8         431 author_name => $author,
363             email => $email,
364             line_start => 'ϕ',
365             tag_end => '»',
366             tag_start => '«',
367             }
368              
369             }
370              
371             =item * dist( [ DIST_NAME ] )
372              
373             Return the dist name. With an argument, set the module name.
374              
375             =cut
376              
377 8     8 1 1442 sub dist ( $self, @args ) {
  8         21  
  8         27  
  8         18  
378 8 100       55 $self->{dist} = $args[0] if defined $args[0];
379 8         86 $self->{dist};
380             }
381              
382             =item * module( [ MODULE_NAME ] )
383              
384             Return the module name. With an argument, set the module name.
385              
386             =cut
387              
388 14     14 1 1746 sub module ( $self, @args ) {
  14         38  
  14         45  
  14         28  
389 14 100       83 $self->{module} = $args[0] if defined $args[0];
390 14         181 $self->{module};
391             }
392              
393             =item * module_path()
394              
395             Return the module path under F. You must have set C
396             already.
397              
398             =cut
399              
400 2     2 1 19 sub module_path ( $self ) {
  2         9  
  2         15  
401 2         33 my @parts = split /::/, $self->{module};
402 2 50       24 return unless @parts;
403 2         15 $parts[-1] .= '.pm';
404 2         260 my $path = catfile( @parts );
405             }
406              
407             =item * module_to_distname( MODULE_NAME )
408              
409             Take a module name, such as C, and turn it into a
410             distribution name, such as C.
411              
412             =cut
413              
414 3     3 1 2571 sub module_to_distname ( $self, $module ) { $module =~ s/::/-/gr }
  3         23  
  3         24  
  3         6  
  3         116  
415              
416             =item * module_template_basename
417              
418             Returns the name of the template file that is the module. The default
419             name is F. This file is moved to the right place under F
420             in the cooked templates.
421              
422             =cut
423              
424 0     0 1 0 sub module_template_basename ( $class ) { 'Foo.pm' }
  0         0  
  0         0  
  0         0  
425              
426             =item * repo_name
427              
428             Returns the repo_name for the project. This defaults to the module
429             name all lowercased with C<::> replaced with C<->. You can override
430             this in a subclass.
431              
432             =cut
433              
434 3     3 1 17 sub repo_name ( $class, @args ) {
  3         5  
  3         10  
  3         6  
435 3 100       27 $class->{repo_name} = $args[0] if defined $args[0];
436 3   33     53 $class->{repo_name} // $class->module =~ s/::/-/gr
437             }
438              
439             =item * template_files
440              
441             Return the list of templates to process. These are all the files in
442             the C excluding F<.git>, F<.svn>, F,
443             and C<.infra>.
444              
445             =cut
446              
447 0     0 1 0 sub template_files ( $self ) {
  0         0  
  0         0  
448 0         0 my @files;
449             my $wanted = sub {
450 0 0   0   0 if( /\A(\.git|\.svn|CVS|\.infra)\b/ ) {
451 0         0 $File::Find::prune = 1;
452 0         0 return;
453             }
454 0         0 push @files, $File::Find::name;
455 0         0 };
456              
457 0         0 find( $wanted, $self->template_dir );
458              
459 0         0 return \@files;
460             }
461              
462             =item * template_vars
463              
464             Returns a hash reference of values to fill in the templates. This hash
465             is passed to the L renderer.
466              
467             =over 4
468              
469             =item author_name => the name of the module author
470              
471             =item cooker_version => version of Distribution::Cooker
472              
473             =item cwd => the current working directory of the new module
474              
475             =item description => the module description
476              
477             =item dir => path to module file
478              
479             =item dist => dist name (Foo-Bar)
480              
481             =item email => author email
482              
483             =item module => the package name (Foo::Bar)
484              
485             =item module_path => module path under lib/ (Foo/Bar.pm)
486              
487             =item repo_name => lowercase module with hyphens (foo-bar)
488              
489             =item template_path => the source of the template files
490              
491             =item year => the current year
492              
493             =back
494              
495             =cut
496              
497 2     2 1 5 sub template_vars ( $self ) {
  2         6  
  2         3  
498             state $hash = {
499             author_name => $self->{author_name},
500             cooker_version => $VERSION,
501             cwd => cwd(),
502             description => $self->description,
503             dir => catfile( 'lib', dirname( $self->module_path ) ),
504             dist => $self->dist,
505             email => $self->{email},
506 2         7645 module => $self->module,
507             module_path => $self->module_path,
508             repo_name => $self->repo_name,
509             template_path => $self->template_dir,
510             year => ( localtime )[5] + 1900,
511             };
512              
513 2         177 $hash;
514             }
515              
516             =back
517              
518             =head2 Utility methods
519              
520             =over 4
521              
522             =item * config_file_path
523              
524             Returns the path to the config file. By default, this is the value of
525             C under the home directory.
526              
527             =cut
528              
529 8     8 1 17 sub config_file_path ( $class ) {
  8         14  
  8         23  
530 8         38 catfile( $ENV{HOME}, $class->config_file_name )
531             }
532              
533             =item * get_config
534              
535             Returns a hash reference of the config values. These are the values
536             that apply across runs.
537              
538             First, this populates a hash with C, then replaces
539             values from the config file (C).
540              
541             This version uses L
542              
543             [author]
544             name=...
545             email=...
546              
547             [templates]
548             line_start=...
549             tag_end=...
550             tag_start=...
551              
552             =cut
553              
554 8     8 1 14 sub get_config ( $class ) {
  8         17  
  8         14  
555 8         29 my $file = $class->config_file_path;
556              
557 8         35 my $hash = $class->default_config;
558              
559 8         469 my @table = (
560             [ qw( author_name author name ) ],
561             [ qw( author_email author email ) ],
562             [ qw( line_start templates line_start ) ],
563             [ qw( tag_end templates tag_end ) ],
564             [ qw( tag_start templates tag_start ) ],
565             );
566              
567 8 50       330 if( -e $file ) {
568 0         0 require Config::IniFiles;
569 0         0 my $config = Config::IniFiles->new( -file => $file );
570              
571 0         0 foreach my $row ( @table ) {
572 0         0 my( $config_name, $section, $field ) = @$row;
573 0 0       0 $hash->{$config_name} = $config->val( $section, $field )
574             if $config->exists( $section, $field );
575             }
576             }
577              
578 8         211 $hash;
579             }
580              
581             =item * prompt( MESSAGE )
582              
583             Show the user MESSAGE, grap a line from STDIN, and return it. If the
584             session is not interactive, this returns nothing.
585              
586             Most things that prompt should have a default value in the case that
587             C cannot work.
588              
589             =cut
590              
591 0     0 1   sub prompt ( @args ) {
  0            
  0            
592 0 0         return unless is_interactive();
593              
594 0           print join "\n", @args;
595 0           print "> ";
596              
597 0           chomp( my $line = );
598 0           $line;
599             }
600              
601             =back
602              
603             =head1 TO DO
604              
605             Right now, C uses the defaults that I like, but
606             that should come from a configuration file.
607              
608             =head1 SEE ALSO
609              
610             Other modules, such as C, do a similar job but don't
611             give you as much flexibility with your templates.
612              
613             =head1 SOURCE AVAILABILITY
614              
615             This module is in Github:
616              
617             http://github.com/briandfoy/distribution-cooker/
618              
619             =head1 AUTHOR
620              
621             brian d foy, C<< >>
622              
623             =head1 COPYRIGHT AND LICENSE
624              
625             Copyright © 2008-2021, brian d foy . All rights reserved.
626              
627             You may redistribute this under the same terms as Perl itself.
628              
629             =cut
630              
631             1;