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