1 package MooseX::AutoDoc;
7 use Moose::Meta::Class;
8 use Scalar::Util qw(blessed);
9 use List::MoreUtils qw(any);
10 use namespace::autoclean;
12 # Create a special TypeConstraint for the View so you can just set it
13 # with a class name and it'll DWIM
15 use Moose::Util::TypeConstraints;
19 => where { $_->isa('MooseX::AutoDoc::View') }
20 => message { "Value should be a subclass of MooseX::AutoDoc::View" } ;
24 => via { Class::MOP::load_class($_); $_->new };
26 no Moose::Util::TypeConstraints;
30 has view => (is => 'rw', isa => 'AutoDocView', coerce => 1, lazy_build => 1);
32 #type constraint library to name mapping to make nice links
33 has tc_to_lib_map => (is => 'rw', isa => 'HashRef', lazy_build => 1);
35 #method metaclasses to ignore to avoid documenting some methods
36 has ignored_method_metaclasses => (is => 'rw', isa => 'HashRef', lazy_build => 1);
38 #defaults to artistic...
39 has license_text => (is => 'rw', isa => 'Str', lazy_build => 1);
41 #how can i get the data about the current user?
42 has authors => (is => 'rw', isa => 'ArrayRef[HashRef]',
43 predicate => 'has_authors');
45 sub _build_view { "MooseX::AutoDoc::View::TT" }
47 sub _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/;
54 sub _build_ignored_method_metaclasses {
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,
63 # 'Moose::Meta::Role::Method' => 1,
64 # 'Moose::Meta::Method::Overridden' => 1,
65 # 'Class::MOP::Method::Wrapped' => 1,
69 sub _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.";
75 sub generate_pod_for {
76 my ($self, $package, $view_args) = @_;
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 );
82 my $spec = $self->_package_info($package);
83 my $key = $package->meta->isa("Moose::Meta::Role") ? 'role' : 'class';
86 license => $self->license_text,
87 authors => $self->has_authors ? $self->authors : [],
89 my $render = "render_${key}";
90 return $self->view->$render($vars, $view_args);
95 my($self, $package) = @_;
98 eval { Class::MOP::load_class($package); };
99 confess "Failed to load package ${package} $@" if $@;
101 #get on with analyzing the package
102 my $meta = $package->meta;
105 if($package->meta->isa('Moose::Meta::Role')){
108 #roles don't have superclasses ...
109 my @superclasses = map{ $_->meta }
110 grep { $_ ne 'Moose::Object' } $meta->superclasses;
111 my @superclass_specs = map{ $self->_superclass_info($_) } @superclasses;
112 $spec->{superclasses} = \@superclass_specs;
115 #these two are common to both roles and classes
117 foreach ($meta->get_attribute_list) {
118 my $attr = $meta->get_attribute($_);
119 $attr = Moose::Meta::Attribute->new($_, %$attr) if ref($attr) eq 'HASH';
120 next if $attr->definition_context && $attr->definition_context->{package} ne $package;
121 push @attributes, $attr;
125 foreach ($meta->get_method_list) {
126 my $meth = $meta->get_method($_);
127 next if any { $meth->isa($_) } keys %{$self->ignored_method_metaclasses};
128 next if $meth->name eq 'meta';
129 next if $meth->original_method && $meth->original_method->{package_name} ne $package;
130 next if $meth->package_name ne $package;
131 push @methods, $meth;
134 my @method_specs = map{ $self->_method_info($_) } @methods;
135 my @attribute_specs = map{ $self->_attribute_info($_) } @attributes;
137 #Moose::Meta::Role and Class have different methods to get consumed roles..
138 #make sure we break up composite roles as well to get better names and nicer
139 #linking to packages.
140 my @roles = sort{ $a->name cmp $b->name }
141 map { $_->isa("Moose::Meta::Role::Composite") ? @{ $_->get_roles } : $_ }
142 @{ $is_role ? $meta->get_roles : $meta->roles };
143 my @role_specs = map{ $self->_consumed_role_info($_) } @roles;
146 $spec->{name} = $meta->name;
147 $spec->{roles} = \ @role_specs;
148 $spec->{methods} = \ @method_specs;
149 $spec->{attributes} = \ @attribute_specs;
155 my($self, $attr) = @_;
156 my $attr_name = $attr->name;
157 my $spec = { name => $attr_name };
158 my $info = $spec->{info} = {};
160 $info->{clearer} = $attr->clearer if $attr->has_clearer;
161 $info->{builder} = $attr->builder if $attr->has_builder;
162 $info->{predicate} = $attr->predicate if $attr->has_predicate;
165 my $description = $attr->is_required ? 'Required ' : 'Optional ';
166 if( defined(my $is = $attr->_is_metadata) ){
167 $description .= 'read-only ' if $is eq 'ro';
168 $description .= 'read-write ' if $is eq 'rw';
170 #If we have 'is' info only write out this info if it != attr_name
171 $info->{writer} = $attr->writer
172 if $attr->has_writer && $attr->writer ne $attr_name;
173 $info->{reader} = $attr->reader
174 if $attr->has_reader && $attr->reader ne $attr_name;
175 $info->{accessor} = $attr->accessor
176 if $attr->has_accessor && $attr->accessor ne $attr_name;
178 $info->{writer} = $attr->writer if $attr->has_writer;
179 $info->{reader} = $attr->reader if $attr->has_reader;
180 $info->{accessor} = $attr->accessor if $attr->has_accessor;
182 $info->{'constructor key'} = $attr->init_arg
183 if $attr->has_init_arg && $attr->init_arg ne $attr_name;
185 if( defined(my $lazy = $attr->is_lazy) ){
186 $description .= 'lazy-building ';
188 $description .= 'value';
189 if( defined(my $isa = $attr->_isa_metadata) ){
193 while( blessed $isa ){
196 my @parts = split '::', $isa;
197 my $type_name = pop @parts;
198 my $type_lib = join "::", @parts;
199 if(eval{$type_lib->isa("MooseX::Types::Base")}){
200 $link_to = $type_lib;
204 my ($isa_base) = ($isa =~ /^(.*?)(?:\[.*\])?$/);
205 if (exists $self->tc_to_lib_map->{$isa_base}){
206 $link_to = $self->tc_to_lib_map->{$isa_base};
210 if(defined $link_to){
211 $isa = "L<${isa}|${link_to}>";
213 $description .= " of type ${isa}";
215 if( $attr->should_auto_deref){
216 $description .=" that will be automatically dereferenced by ".
217 "the reader / accessor";
219 if( $attr->has_documentation ){
220 $description .= "\n\n" . $attr->documentation;
222 $spec->{description} = $description;
227 sub _superclass_info {
228 my($self, $superclass) = @_;
229 my $spec = { name => $superclass->name };
234 my($self, $method) = @_;
235 my $spec = { name => $method->name };
239 sub _consumed_role_info {
240 my($self, $role) = @_;;
241 my $spec = { name => $role->name };
251 MooseX::AutoDoc - Automatically generate documentation for Moose-based packages
256 my $autodoc = MooseX::AutoDoc->new
261 name => "Guillermo Roditi",
262 email => 'groditi@cpan.org',
268 my $class_pod = $autodoc->generate_pod_for("MyClass");
269 my $role_pod = $autodoc->generate_pod_for("MyRole");
273 MooseX::AutoDoc allows you to automatically generate POD documentation from
274 your Moose based objects by introspecting them and creating a POD skeleton
275 with extra information where it can be infered through the MOP.
277 =head1 NOTICE REGARDING ROLE CONSUMPTION
279 To accurantely detect which methods and attributes are part of the class / role
280 being examined and which are part of a consumed role the L</"generate_pod_for">
281 method need to delay role consumption. If your role or class has been loaded
282 prior to calling these methods you run a risk of recieving inacurate data and
283 a warning will be emitted. This is due to the fact that once a role is applied
284 there is no way to tell which attributes and methods came from the class and
285 which came from the role.
289 Unless noted otherwise, you may set any of these attributes at C<new> time by
290 passing key / value pairs to C<new> where the key is the name of the attribute
291 you wish to set. Unless noted otherwise accessor methods for attributes also
292 share the same name as the attribute.
298 =item B<predicate> - has_authors
302 Optional read-write value of type
303 L<ArrayRef[HashRef]|Moose::Util::TypeConstraints> representing the authors of
304 the class / role being documented. These values are passed directly to the view
305 and the default TT view accepts entries in the following form
306 (all fields optional)
309 name => 'Guillermo Roditi',
311 email => '<groditi@gmail.com>',
314 =head2 ignored_method_metaclasses
318 =item B<builder> - _build_ignored_method_metaclasses
320 Default to the Moose and Class::MOP method metaclasses for generated methods,
321 accessors, and constructors.
323 =item B<clearer> - clear_ignored_method_metaclasses
325 =item B<predicate> - has_ignored_method_metaclasses
329 Required read-write lazy-building value of type
330 L<HashRef|Moose::Util::TypeConstraints> where the keys are method metaclasses
331 MooseX::AutoDoc should ignore when creating a method list.
337 =item B<builder> - _build_license_text
339 =item B<clearer> - clear_license_text
341 =item B<predicate> - has_license_text
345 Required read-write lazy-building value of type
346 L<Str|Moose::Util::TypeConstraints>. By default it will use the following text:
348 This library is free software; you can redistribute it and/or modify it
349 under the same terms as Perl itself.
355 =item B<builder> - _build_tc_to_lib_map
357 =item B<clearer> - clear_tc_to_lib_map
359 =item B<predicate> - has_tc_to_lib_map
363 Required read-write lazy-building value of type
364 L<HashRef|Moose::Util::TypeConstraints>. The keys refer to type constraint
365 names and the values to the module where the documentation available for that
366 type is. Please note that if you are using MooseX::Types libraries the links
367 will be automatically generated if the library class can be found (most cases).
373 =item B<builder> - _build_view
375 Returns 'MooseX::AutoDoc::View::TT'
377 =item B<clearer> - clear_view
379 =item B<predicate> - has_view
383 Required read-write lazy-building value of type AutoDocView. The AutoDocView
384 type will accept an Object that isa L<MooseX::AutoDoc::View>. This attribute
385 will attempt to coerce string values to instances by treating them as class
386 names and attempting to load and instantiate a class of the same name.
390 =head2 new key => $value
392 Instantiate a new object. Please refer to L</"ATTRIBUTES"> for a list of valid
395 =head2 generate_pod_for $package_name, $view_args
397 Returns a string containing the Pod for the package. To make sure the data is
398 accurate please make sure the package has not been loaded prior to this step.
399 for more info see L</"NOTICE REGARDING ROLE CONSUMPTION">
401 =head2 _package_info $package_name
403 Will return a hashref representing the documentation components of the package
404 with the keys C<name>, C<attributes>, C<methods>, C<attributes> and--if the
405 case the package is a class--C<superclasses>; the latter four are array refs
406 of the hashrefs returned by L</"_superclass_info">, L</"_attribute_info">,
407 L</"_method_info">, and L</"_consumed_role_info"> respectively.
409 =head2 _attribute_info $attr
411 Accepts one argument, an attribute metaclass instance.
412 Returns a hashref representing the documentation components of the
413 attribute with the keys C<name>, C<description>, and C<info>, a hashref
414 of additional information. If you have set the documentation attribute of
415 your attributes the documentation text will be appended to the auto-generated
418 =head2 _consumed_role_info $role
420 Accepts one argument, a role metaclass instance. Returns a hashref representing
421 the documentation components of the role with the key C<name>.
423 =head2 _method_info $method
425 Accepts one argument, a method metaclass instance. Returns a hashref
426 representing the documentation components of the role with the key C<name>.
428 =head2 _superclass_info $class
430 Accepts one argument, the metaclass instance of a superclass. Returns a hashref
431 representing the documentation components of the role with the key C<name>.
435 Retrieve the metaclass instance. Please see L<Moose::Meta::Class> and
436 L<Class::MOP::Class> for more information.
440 Guillermo Roditi (Guillermo Roditi) <groditi@cpan.org>
442 =head1 COPYRIGHT AND LICENSE
444 This library is free software; you can redistribute it and/or modify it under
445 the same terms as Perl itself.