add List::MoreUtils
[gitmo/MooseX-AutoDoc.git] / lib / MooseX / AutoDoc.pm
CommitLineData
3890b670 1package MooseX::AutoDoc;
2
3use Moose;
4use Carp;
5use Class::MOP;
6use Moose::Meta::Role;
7use Moose::Meta::Class;
9de0dc39 8use Scalar::Util qw(blessed);
9use List::MoreUtils qw(any);
fcac021f 10use namespace::autoclean;
3890b670 11
12# Create a special TypeConstraint for the View so you can just set it
13# with a class name and it'll DWIM
14{
15 use Moose::Util::TypeConstraints;
16
17 subtype 'AutoDocView'
18 => as 'Object'
19 => where { $_->isa('MooseX::AutoDoc::View') }
20 => message { "Value should be a subclass of MooseX::AutoDoc::View" } ;
21
22 coerce 'AutoDocView'
23 => from 'Str'
24 => via { Class::MOP::load_class($_); $_->new };
25
26 no Moose::Util::TypeConstraints;
27}
28
29#view object
30has view => (is => 'rw', isa => 'AutoDocView', coerce => 1, lazy_build => 1);
31
32#type constraint library to name mapping to make nice links
33has tc_to_lib_map => (is => 'rw', isa => 'HashRef', lazy_build => 1);
34
35#method metaclasses to ignore to avoid documenting some methods
36has ignored_method_metaclasses => (is => 'rw', isa => 'HashRef', lazy_build => 1);
37
38#defaults to artistic...
39has license_text => (is => 'rw', isa => 'Str', lazy_build => 1);
40
41#how can i get the data about the current user?
42has authors => (is => 'rw', isa => 'ArrayRef[HashRef]',
43 predicate => 'has_authors');
44
45sub _build_view { "MooseX::AutoDoc::View::TT" }
46
47sub _build_tc_to_lib_map {
48 my %types = map {$_ => 'Moose::Util::TypeConstraints'}
49 qw/Any Item Bool Undef Defined Value Num Int Str Role Maybe ClassName Ref
50 ScalarRef ArrayRef HashRef CodeRef RegexpRef GlobRef FileHandle Object/;
51 return \ %types;
52}
53
54sub _build_ignored_method_metaclasses {
55 return {
3890b670 56 'Moose::Meta::Method::Accessor' => 1,
57 'Moose::Meta::Method::Constructor' => 1,
58 'Class::MOP::Method::Accessor' => 1,
59 'Class::MOP::Method::Generated' => 1,
60 'Class::MOP::Method::Constructor' => 1,
61 };
62
ec75fdb0 63# 'Moose::Meta::Role::Method' => 1,
3890b670 64# 'Moose::Meta::Method::Overridden' => 1,
65# 'Class::MOP::Method::Wrapped' => 1,
ec75fdb0 66
3890b670 67}
68
69sub _build_license_text {
70 "This library is free software; you can redistribute it and/or modify it "
71 ."under the same terms as Perl itself.";
72}
73
74#make the actual POD
a3e8bacb 75sub generate_pod_for {
76 my ($self, $package, $view_args) = @_;
3890b670 77
a3e8bacb 78 carp("${package} is already loaded. This will cause inacurate output.".
79 "if ${package} is the consumer of any roles.")
80 if Class::MOP::is_class_loaded( $package );
3890b670 81
10873f5d 82 my $spec = $self->_package_info($package);
a3e8bacb 83 my $key = $package->meta->isa("Moose::Meta::Role") ? 'role' : 'class';
3890b670 84 my $vars = {
a3e8bacb 85 $key => $spec,
3890b670 86 license => $self->license_text,
87 authors => $self->has_authors ? $self->authors : [],
88 };
a3e8bacb 89 my $render = "render_${key}";
90 return $self->view->$render($vars, $view_args);
3890b670 91}
92
3890b670 93# *_info methods
a3e8bacb 94sub _package_info {
95 my($self, $package) = @_;
96
97 #intercept role application so we can accurately generate
98 #method and attribute information for the parent class.
99 #this is fragile, but there is not better way that i am aware of
100 my $rmeta = Moose::Meta::Role->meta;
101 $rmeta->make_mutable if $rmeta->is_immutable;
102 my $original_apply = $rmeta->get_method("apply")->body;
103 $rmeta->remove_method("apply");
104 my @roles_to_apply;
105 $rmeta->add_method("apply", sub{push(@roles_to_apply, [@_])});
106 #load the package with the hacked Moose::Meta::Role
107 eval { Class::MOP::load_class($package); };
108 confess "Failed to load package ${package} $@" if $@;
109
110 #get on with analyzing the package
111 my $meta = $package->meta;
112 my $spec = {};
113 my ($class, $is_role);
114 if($package->meta->isa('Moose::Meta::Role')){
115 $is_role = 1;
116 # we need to apply the role to a class to be able to properly introspect it
117 $class = Moose::Meta::Class->create_anon_class;
118 $original_apply->($meta, $class);
119 } else {
120 #roles don't have superclasses ...
121 $class = $meta;
122 my @superclasses = map{ $_->meta }
123 grep { $_ ne 'Moose::Object' } $meta->superclasses;
124 my @superclass_specs = map{ $self->_superclass_info($_) } @superclasses;
125 $spec->{superclasses} = \@superclass_specs;
3890b670 126 }
127
a3e8bacb 128 #these two are common to both roles and classes
129 my @attributes = map{ $class->get_attribute($_) } sort $class->get_attribute_list;
130 my @methods =
3890b670 131 grep{ ! exists $self->ignored_method_metaclasses->{$_->meta->name} }
a3e8bacb 132 map { $class->get_method($_) }
133 grep { $_ ne 'meta' } sort $class->get_method_list;
134
135 my @method_specs = map{ $self->_method_info($_) } @methods;
136 my @attribute_specs = map{ $self->_attribute_info($_) } @attributes;
137
138 #fix Moose::Meta::Role and apply the roles that were delayed
139 $rmeta->remove_method("apply");
140 $rmeta->add_method("apply", $original_apply);
141 $rmeta->make_immutable;
142 #we apply roles to be able to figure out which ones we are using although I
143 #could just cycle through $_->[0] for @roles_to_apply;
144 shift(@$_)->apply(@$_) for @roles_to_apply;
145
146 #Moose::Meta::Role and Class have different methods to get consumed roles..
147 #make sure we break up composite roles as well to get better names and nicer
148 #linking to packages.
3890b670 149 my @roles = sort{ $a->name cmp $b->name }
a3e8bacb 150 map { $_->isa("Moose::Meta::Role::Composite") ? @{ $_->get_roles } : $_ }
151 @{ $is_role ? $meta->get_roles : $meta->roles };
ec75fdb0 152 my @role_specs = map{ $self->_consumed_role_info($_) } @roles;
3890b670 153
a3e8bacb 154 #fill up the spec
155 $spec->{name} = $meta->name;
156 $spec->{roles} = \ @role_specs;
157 $spec->{methods} = \ @method_specs;
158 $spec->{attributes} = \ @attribute_specs;
3890b670 159
160 return $spec;
161}
162
ec75fdb0 163sub _attribute_info{
3890b670 164 my($self, $attr) = @_;;
165 my $attr_name = $attr->name;
166 my $spec = { name => $attr_name };
167 my $info = $spec->{info} = {};
168
169 $info->{clearer} = $attr->clearer if $attr->has_clearer;
170 $info->{builder} = $attr->builder if $attr->has_builder;
171 $info->{predicate} = $attr->predicate if $attr->has_predicate;
172
173
174 my $description = $attr->is_required ? 'Required ' : 'Optional ';
175 if( defined(my $is = $attr->_is_metadata) ){
176 $description .= 'read-only ' if $is eq 'ro';
177 $description .= 'read-write ' if $is eq 'rw';
178
179 #If we have 'is' info only write out this info if it != attr_name
180 $info->{writer} = $attr->writer
181 if $attr->has_writer && $attr->writer ne $attr_name;
182 $info->{reader} = $attr->reader
183 if $attr->has_reader && $attr->reader ne $attr_name;
184 $info->{accessor} = $attr->accessor
185 if $attr->has_accessor && $attr->accessor ne $attr_name;
186 } else {
187 $info->{writer} = $attr->writer if $attr->has_writer;
188 $info->{reader} = $attr->reader if $attr->has_reader;
189 $info->{accessor} = $attr->accessor if $attr->has_accessor;
190 }
a8676f0f 191 $info->{'constructor key'} = $attr->init_arg
192 if $attr->has_init_arg && $attr->init_arg ne $attr_name;
3890b670 193
194 if( defined(my $lazy = $attr->is_lazy) ){
195 $description .= 'lazy-building ';
196 }
197 $description .= 'value';
198 if( defined(my $isa = $attr->_isa_metadata) ){
199 my $link_to;
200 if( blessed $isa ){
201 my $from_type_lib;
202 while( blessed $isa ){
203 $isa = $isa->name;
204 }
205 my @parts = split '::', $isa;
206 my $type_name = pop @parts;
207 my $type_lib = join "::", @parts;
208 if(eval{$type_lib->isa("MooseX::Types::Base")}){
209 $link_to = $type_lib;
210 $isa = $type_name;
211 }
212 } else {
213 my ($isa_base) = ($isa =~ /^(.*?)(?:\[.*\])?$/);
214 if (exists $self->tc_to_lib_map->{$isa_base}){
215 $link_to = $self->tc_to_lib_map->{$isa_base};
216 }
217 my $isa = $isa_base;
218 }
219 if(defined $link_to){
220 $isa = "L<${isa}|${link_to}>";
221 }
222 $description .= " of type ${isa}";
223 }
224 if( $attr->should_auto_deref){
225 $description .=" that will be automatically dereferenced by ".
226 "the reader / accessor";
227 }
228 if( $attr->has_documentation ){
229 $description .= "\n\n" . $attr->documentation;
230 }
231 $spec->{description} = $description;
232
233 return $spec;
234}
235
ec75fdb0 236sub _superclass_info {
3890b670 237 my($self, $superclass) = @_;
238 my $spec = { name => $superclass->name };
239 return $spec;
240}
241
ec75fdb0 242sub _method_info {
3890b670 243 my($self, $method) = @_;
244 my $spec = { name => $method->name };
245 return $spec;
246}
247
ec75fdb0 248sub _consumed_role_info {
3890b670 249 my($self, $role) = @_;;
250 my $spec = { name => $role->name };
251 return $spec;
252}
253
2541;
255
256__END__;
257
ec75fdb0 258=head1 NAME
259
a3e8bacb 260MooseX::AutoDoc - Automatically generate documentation for Moose-based packages
ec75fdb0 261
0fe5ef1b 262=head1 SYNOPSIS
ec75fdb0 263
264 use MooseX::AutoDoc;
265 my $autodoc = MooseX::AutoDoc->new
266 (
267 authors =>
268 [
269 {
270 name => "Guillermo Roditi",
271 email => 'groditi@cpan.org',
272 handle => "groditi",
273 }
274 ],
275 );
276
a3e8bacb 277 my $class_pod = $autodoc->generate_pod_for("MyClass");
278 my $role_pod = $autodoc->generate_pod_for("MyRole");
ec75fdb0 279
280=head1 DESCRIPTION
281
282MooseX::AutoDoc allows you to automatically generate POD documentation from
a3e8bacb 283your Moose based objects by introspecting them and creating a POD skeleton
284with extra information where it can be infered through the MOP.
ec75fdb0 285
286=head1 NOTICE REGARDING ROLE CONSUMPTION
287
288To accurantely detect which methods and attributes are part of the class / role
a3e8bacb 289being examined and which are part of a consumed role the L</"generate_pod_for">
290method need to delay role consumption. If your role or class has been loaded
291prior to calling these methods you run a risk of recieving inacurate data and
292a warning will be emitted. This is due to the fact that once a role is applied
293there is no way to tell which attributes and methods came from the class and
294which came from the role.
ec75fdb0 295
296=head1 ATTRIBUTES
297
298Unless noted otherwise, you may set any of these attributes at C<new> time by
299passing key / value pairs to C<new> where the key is the name of the attribute
300you wish to set. Unless noted otherwise accessor methods for attributes also
301share the same name as the attribute.
302
303=head2 authors
304
305=over 4
306
307=item B<predicate> - has_authors
308
309=back
310
311Optional read-write value of type
312L<ArrayRef[HashRef]|Moose::Util::TypeConstraints> representing the authors of
313the class / role being documented. These values are passed directly to the view
314and the default TT view accepts entries in the following form
315(all fields optional)
316
317 {
318 name => 'Guillermo Roditi',
319 handle => 'groditi',
320 email => '<groditi@gmail.com>',
321 }
322
323=head2 ignored_method_metaclasses
324
325=over 4
326
327=item B<builder> - _build_ignored_method_metaclasses
328
329Default to the Moose and Class::MOP method metaclasses for generated methods,
330accessors, and constructors.
331
332=item B<clearer> - clear_ignored_method_metaclasses
333
334=item B<predicate> - has_ignored_method_metaclasses
335
336=back
337
338Required read-write lazy-building value of type
339L<HashRef|Moose::Util::TypeConstraints> where the keys are method metaclasses
340MooseX::AutoDoc should ignore when creating a method list.
341
342=head2 license_text
343
344=over 4
345
346=item B<builder> - _build_license_text
347
348=item B<clearer> - clear_license_text
349
350=item B<predicate> - has_license_text
351
352=back
353
354Required read-write lazy-building value of type
355L<Str|Moose::Util::TypeConstraints>. By default it will use the following text:
356
357 This library is free software; you can redistribute it and/or modify it
358 under the same terms as Perl itself.
359
360=head2 tc_to_lib_map
361
362=over 4
363
364=item B<builder> - _build_tc_to_lib_map
365
366=item B<clearer> - clear_tc_to_lib_map
367
368=item B<predicate> - has_tc_to_lib_map
369
370=back
371
372Required read-write lazy-building value of type
373L<HashRef|Moose::Util::TypeConstraints>. The keys refer to type constraint
374names and the values to the module where the documentation available for that
375type is. Please note that if you are using MooseX::Types libraries the links
376will be automatically generated if the library class can be found (most cases).
377
378=head2 view
379
380=over 4
381
382=item B<builder> - _build_view
383
384Returns 'MooseX::AutoDoc::View::TT'
385
386=item B<clearer> - clear_view
387
388=item B<predicate> - has_view
389
390=back
391
392Required read-write lazy-building value of type AutoDocView. The AutoDocView
393type will accept an Object that isa L<MooseX::AutoDoc::View>. This attribute
394will attempt to coerce string values to instances by treating them as class
395names and attempting to load and instantiate a class of the same name.
396
397=head1 METHODS
398
9afe15bd 399=head2 new key => $value
ec75fdb0 400
401Instantiate a new object. Please refer to L</"ATTRIBUTES"> for a list of valid
402key options.
403
a3e8bacb 404=head2 generate_pod_for $package_name, $view_args
ec75fdb0 405
a3e8bacb 406Returns a string containing the Pod for the package. To make sure the data is
407accurate please make sure the package has not been loaded prior to this step.
ec75fdb0 408for more info see L</"NOTICE REGARDING ROLE CONSUMPTION">
409
a3e8bacb 410=head2 _package_info $package_name
ec75fdb0 411
a3e8bacb 412Will return a hashref representing the documentation components of the package
413with the keys C<name>, C<attributes>, C<methods>, C<attributes> and--if the
414case the package is a class--C<superclasses>; the latter four are array refs
415of the hashrefs returned by L</"_superclass_info">, L</"_attribute_info">,
416L</"_method_info">, and L</"_consumed_role_info"> respectively.
ec75fdb0 417
418=head2 _attribute_info $attr
419
420Accepts one argument, an attribute metaclass instance.
421Returns a hashref representing the documentation components of the
422attribute with the keys C<name>, C<description>, and C<info>, a hashref
a3e8bacb 423of additional information. If you have set the documentation attribute of
424your attributes the documentation text will be appended to the auto-generated
425description.
ec75fdb0 426
427=head2 _consumed_role_info $role
428
429Accepts one argument, a role metaclass instance. Returns a hashref representing
430the documentation components of the role with the key C<name>.
431
432=head2 _method_info $method
433
434Accepts one argument, a method metaclass instance. Returns a hashref
435representing the documentation components of the role with the key C<name>.
436
437=head2 _superclass_info $class
438
439Accepts one argument, the metaclass instance of a superclass. Returns a hashref
440representing the documentation components of the role with the key C<name>.
441
442=head2 meta
443
444Retrieve the metaclass instance. Please see L<Moose::Meta::Class> and
445L<Class::MOP::Class> for more information.
446
447=head1 AUTHORS
448
449Guillermo Roditi (Guillermo Roditi) <groditi@cpan.org>
450
451=head1 COPYRIGHT AND LICENSE
3890b670 452
ec75fdb0 453This library is free software; you can redistribute it and/or modify it under
454the same terms as Perl itself.
3890b670 455
ec75fdb0 456=cut