File Coverage

blib/lib/Module/CAPIMaker.pm
Criterion Covered Total %
statement 30 125 24.0
branch 0 40 0.0
condition 0 44 0.0
subroutine 10 16 62.5
pod 0 6 0.0
total 40 231 17.3


line stmt bran cond sub pod time code
1             package Module::CAPIMaker;
2              
3             our $VERSION = '0.01';
4              
5 1     1   33455 use strict;
  1         2  
  1         32  
6 1     1   4 use warnings;
  1         2  
  1         25  
7              
8 1     1   1008 use Text::Template;
  1         3857  
  1         57  
9 1     1   10 use File::Spec;
  1         1  
  1         28  
10 1     1   862 use POSIX qw(strftime);
  1         6973  
  1         8  
11              
12 1     1   1060 use Exporter qw(import);
  1         2  
  1         59  
13             our @EXPORT = qw(make_c_api);
14              
15 1     1   742 use Module::CAPIMaker::Template::Module_H;
  1         4  
  1         32  
16 1     1   473 use Module::CAPIMaker::Template::Module_C;
  1         3  
  1         25  
17 1     1   514 use Module::CAPIMaker::Template::Sample_XS;
  1         3  
  1         30  
18 1     1   572 use Module::CAPIMaker::Template::C_API_H;
  1         5  
  1         1977  
19              
20             sub new {
21 0     0 0   my $class = shift;
22 0           my %config = @_;
23 0           my $self = { config => \%config,
24             function => {},
25             data => {}
26             };
27              
28 0   0       $config{c_api_decl_filename} //= 'c_api.decl';
29              
30 0           bless $self, $class;
31             }
32              
33             sub load_decl {
34 0     0 0   my $self = shift;
35 0           my $config = $self->{config};
36 0           my $fn = $config->{c_api_decl_filename};
37 0 0         open my $fh, '<', $fn or die "Unable to open $fn: $!\n";
38 0           while (<$fh>) {
39 0           chomp;
40 0           s/^\s+//; s/\s+$//;
  0            
41 0 0         next if /^(?:#.*)?$/;
42 0           while (s/\s*\\$/ /) {
43 0           my $next = <$fh>;
44 0           chomp $next;
45 0           $next =~ s/^\s+//; $next =~ s/\s+$//;
  0            
46 0           $_ .= $next;
47             }
48 0 0         if (my ($k, $v) = /^(\w+)\s*=\s*(.*)/) {
    0          
49 0 0         if (my ($mark) = $v =~ /^<<\s*(\w+)$/) {
50 0           $v = '';
51 0           while (1) {
52 0           my $line = <$fh>;
53 0 0         defined $line or die "Ending token '$mark' missing at $fn line $.\n";
54 0 0         last if $line =~ /^$mark$/;
55 0           $v .= $line;
56             }
57             }
58 0           $self->{config}{$k} = $v;
59             }
60             elsif (/^((?:\w+\b\s*(?:\*+\s*)?)*)(\w+)\s*\(\s*(.*?)\s*\)$/) {
61 0           my $args = $3;
62 0           my %f = ( decl => $_,
63             type => $1,
64             name => $2,
65             args => $args );
66 0           $self->{function}{$2} = \%f;
67              
68 0 0         if ($f{pTHX} = $args =~ s/^pTHX(?:_\s+|$)//) {
69 0           $args =~ s/^void$//;
70 0           my @args = split /\s*,\s*/, $args;
71             # warn "args |$args| => |". join('-', @args) . "|";
72 0           $f{macro_args} = join(', ', ('a'..'z')[0..$#args]);
73 0 0         $f{call_args} = (@args ? 'aTHX_ (' . join('), (', ('a'..'z')[0..$#args]) .')' : 'aTHX');
74             }
75              
76             }
77             else {
78 0           die "Invalid declaration at $fn line $.\n";
79             }
80             }
81             }
82              
83             sub check_config {
84 0     0 0   my $self = shift;
85 0           my $config = $self->{config};
86              
87 0           my $module_name = $config->{module_name};
88 0 0         die "module_name declaration missing from $config->{decl_filename}\n"
89             unless defined $module_name;
90              
91 0 0         die "Invalid value for module_name ($module_name)\n"
92             unless $module_name =~ /^\w+(?:::\w+)*$/;
93              
94 0   0       my $c_module_name = $config->{c_module_name} //= do { my $cmn = lc $module_name;
  0            
95 0           $cmn =~ s/\W+/_/g;
96 0           $cmn };
97 0 0         die "Invalid value for c_module_name ($c_module_name)\n"
98             unless $c_module_name =~ /^\w+$/;
99              
100 0   0       $config->{author} //= 'Unknown';
101 0   0       $config->{min_version} //= 1;
102 0   0       $config->{max_version} //= 1;
103              
104 0 0         die "Invalid version declaration, min_version ($config->{min_version}) > max_version ($config->{max_version})\n"
105             if $config->{max_version} < $config->{min_version};
106              
107 0   0       $config->{required_version} //= $config->{max_version};
108 0   0       $config->{module_version} //= '0';
109 0           $config->{capimaker_version} = $VERSION;
110              
111 0           $config->{now} = strftime("%F %T", localtime);
112              
113 0   0       $config->{client_dir} //= 'c_api_client';
114              
115 0   0       $config->{module_c_filename} //= "perl_$c_module_name.c";
116 0   0       $config->{module_h_filename} //= "perl_$c_module_name.h";
117 0   0       $config->{sample_xs_filename} //= "sample.xs";
118 0   0       $config->{c_api_h_filename} //= "c_api.h";
119              
120 0   0       $config->{module_h_barrier} //= do { my $ib = "$config->{module_h_filename}_INCLUDED";
  0            
121 0           $ib =~ s/\W+/_/g;
122 0           uc $ib };
123 0 0         die "Invalid value for module_h_barrier ($config->{module_h_barrier})\n"
124             unless $config->{module_h_barrier} =~ /^\w+$/;
125              
126 0   0       $config->{c_api_h_barrier} //= do { my $ib = "$config->{c_api_h_filename}_INCLUDED";
  0            
127 0           $ib =~ s/\W+/_/g;
128 0           uc $ib };
129 0 0         die "Invalid value for c_api_h_barrier ($config->{c_api_h_barrier})\n"
130             unless $config->{c_api_h_barrier} =~ /^\w+$/;
131              
132              
133 0   0       $config->{$_} //= '' for qw(export_prefix
134             module_c_beginning
135             module_c_end
136             module_h_beginning
137             module_h_end);
138             }
139              
140             sub gen_file {
141 0     0 0   my ($self, $template, $dir, $save_as) = @_;
142 0           my $config = $self->{config};
143 0 0         system mkdir => -p => $dir unless -d $dir; # FIX ME!
144 0           $save_as = File::Spec->rel2abs(File::Spec->join($dir, $save_as));
145 0 0         open my $fh, '>', $save_as or die "Unable to create $save_as: $!\n";
146 0           local $Text::Template::ERROR;
147 0 0         my $tt = Text::Template->new(TYPE => (ref $template ? 'ARRAY' : 'FILE'),
148             SOURCE => $template,
149             DELIMITERS => ['<%', '%>'] );
150 0           $tt->fill_in(HASH => { %$config, function => $self->{function} },
151             OUTPUT => $fh);
152 0 0         warn "Some error happened while generating $save_as: $Text::Template::ERROR\n"
153             if $Text::Template::ERROR;
154             }
155              
156             sub gen_all {
157 0     0 0   my $self = shift;
158 0           my $config = $self->{config};
159 0   0       $self->gen_file($config->{module_c_template_filename} // \@Module::CAPIMaker::Template::Module_C::template,
160             $config->{client_dir},
161             $config->{module_c_filename});
162 0   0       $self->gen_file($config->{module_h_template_filename} // \@Module::CAPIMaker::Template::Module_H::template,
163             $config->{client_dir},
164             $config->{module_h_filename});
165 0   0       $self->gen_file($config->{sample_xs_template_filename} // \@Module::CAPIMaker::Template::Sample_XS::template,
166             $config->{client_dir},
167             $config->{sample_xs_filename});
168 0   0       $self->gen_file($config->{c_api_h_template_filename} // \@Module::CAPIMaker::Template::C_API_H::template,
169             '.',
170             $config->{c_api_h_filename});
171             }
172              
173             sub make_c_api {
174 0     0 0   my %args;
175 0           for (@ARGV) {
176 0 0         /^\s*(\w+)\s*=\s*(.*?)\s*$/
177             or die "Bad argument '$_'\n";
178 0           $args{$1} = $2;
179             }
180 0           my $mcm = Module::CAPIMaker->new(%args);
181 0           $mcm->load_decl;
182 0           $mcm->check_config;
183 0           $mcm->gen_all;
184             }
185              
186             1;
187             __END__