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