1 package MooseX::AutoDoc;
7 use Moose::Meta::Class;
8 use Scalar::Util qw/blessed/;
9 use namespace::autoclean;
11 # Create a special TypeConstraint for the View so you can just set it
12 # with a class name and it'll DWIM
14 use Moose::Util::TypeConstraints;
18 => where { $_->isa('MooseX::AutoDoc::View') }
19 => message { "Value should be a subclass of MooseX::AutoDoc::View" } ;
23 => via { Class::MOP::load_class($_); $_->new };
25 no Moose::Util::TypeConstraints;
29 has view => (is => 'rw', isa => 'AutoDocView', coerce => 1, lazy_build => 1);
31 #type constraint library to name mapping to make nice links
32 has tc_to_lib_map => (is => 'rw', isa => 'HashRef', lazy_build => 1);
34 #method metaclasses to ignore to avoid documenting some methods
35 has ignored_method_metaclasses => (is => 'rw', isa => 'HashRef', lazy_build => 1);
37 #defaults to artistic...
38 has license_text => (is => 'rw', isa => 'Str', lazy_build => 1);
40 #how can i get the data about the current user?
41 has authors => (is => 'rw', isa => 'ArrayRef[HashRef]',
42 predicate => 'has_authors');
44 sub _build_view { "MooseX::AutoDoc::View::TT" }
46 sub _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/;
53 sub _build_ignored_method_metaclasses {
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,
62 # 'Moose::Meta::Role::Method' => 1,
63 # 'Moose::Meta::Method::Overridden' => 1,
64 # 'Class::MOP::Method::Wrapped' => 1,
68 sub _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.";
74 sub generate_pod_for {
75 my ($self, $package, $view_args) = @_;
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 );
81 my $spec = $self->_package_info($package);
82 my $key = $package->meta->isa("Moose::Meta::Role") ? 'role' : 'class';
85 license => $self->license_text,
86 authors => $self->has_authors ? $self->authors : [],
88 my $render = "render_${key}";
89 return $self->view->$render($vars, $view_args);
94 my($self, $package) = @_;
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");
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 $@;
109 #get on with analyzing the package
110 my $meta = $package->meta;
112 my ($class, $is_role);
113 if($package->meta->isa('Moose::Meta::Role')){
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);
119 #roles don't have superclasses ...
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;
127 #these two are common to both roles and classes
128 my @attributes = map{ $class->get_attribute($_) } sort $class->get_attribute_list;
130 grep{ ! exists $self->ignored_method_metaclasses->{$_->meta->name} }
131 map { $class->get_method($_) }
132 grep { $_ ne 'meta' } sort $class->get_method_list;
134 my @method_specs = map{ $self->_method_info($_) } @methods;
135 my @attribute_specs = map{ $self->_attribute_info($_) } @attributes;
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;
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.
148 my @roles = sort{ $a->name cmp $b->name }
149 map { $_->isa("Moose::Meta::Role::Composite") ? @{ $_->get_roles } : $_ }
150 @{ $is_role ? $meta->get_roles : $meta->roles };
151 my @role_specs = map{ $self->_consumed_role_info($_) } @roles;
154 $spec->{name} = $meta->name;
155 $spec->{roles} = \ @role_specs;
156 $spec->{methods} = \ @method_specs;
157 $spec->{attributes} = \ @attribute_specs;
163 my($self, $attr) = @_;;
164 my $attr_name = $attr->name;
165 my $spec = { name => $attr_name };
166 my $info = $spec->{info} = {};
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;
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';
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;
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;
190 $info->{'constructor key'} = $attr->init_arg
191 if $attr->has_init_arg && $attr->init_arg ne $attr_name;
193 if( defined(my $lazy = $attr->is_lazy) ){
194 $description .= 'lazy-building ';
196 $description .= 'value';
197 if( defined(my $isa = $attr->_isa_metadata) ){
201 while( blessed $isa ){
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;
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};
218 if(defined $link_to){
219 $isa = "L<${isa}|${link_to}>";
221 $description .= " of type ${isa}";
223 if( $attr->should_auto_deref){
224 $description .=" that will be automatically dereferenced by ".
225 "the reader / accessor";
227 if( $attr->has_documentation ){
228 $description .= "\n\n" . $attr->documentation;
230 $spec->{description} = $description;
235 sub _superclass_info {
236 my($self, $superclass) = @_;
237 my $spec = { name => $superclass->name };
242 my($self, $method) = @_;
243 my $spec = { name => $method->name };
247 sub _consumed_role_info {
248 my($self, $role) = @_;;
249 my $spec = { name => $role->name };
259 MooseX::AutoDoc - Automatically generate documentation for Moose-based packages
264 my $autodoc = MooseX::AutoDoc->new
269 name => "Guillermo Roditi",
270 email => 'groditi@cpan.org',
276 my $class_pod = $autodoc->generate_pod_for("MyClass");
277 my $role_pod = $autodoc->generate_pod_for("MyRole");
281 MooseX::AutoDoc allows you to automatically generate POD documentation from
282 your Moose based objects by introspecting them and creating a POD skeleton
283 with extra information where it can be infered through the MOP.
285 =head1 NOTICE REGARDING ROLE CONSUMPTION
287 To accurantely detect which methods and attributes are part of the class / role
288 being examined and which are part of a consumed role the L</"generate_pod_for">
289 method need to delay role consumption. If your role or class has been loaded
290 prior to calling these methods you run a risk of recieving inacurate data and
291 a warning will be emitted. This is due to the fact that once a role is applied
292 there is no way to tell which attributes and methods came from the class and
293 which came from the role.
297 Unless noted otherwise, you may set any of these attributes at C<new> time by
298 passing key / value pairs to C<new> where the key is the name of the attribute
299 you wish to set. Unless noted otherwise accessor methods for attributes also
300 share the same name as the attribute.
306 =item B<predicate> - has_authors
310 Optional read-write value of type
311 L<ArrayRef[HashRef]|Moose::Util::TypeConstraints> representing the authors of
312 the class / role being documented. These values are passed directly to the view
313 and the default TT view accepts entries in the following form
314 (all fields optional)
317 name => 'Guillermo Roditi',
319 email => '<groditi@gmail.com>',
322 =head2 ignored_method_metaclasses
326 =item B<builder> - _build_ignored_method_metaclasses
328 Default to the Moose and Class::MOP method metaclasses for generated methods,
329 accessors, and constructors.
331 =item B<clearer> - clear_ignored_method_metaclasses
333 =item B<predicate> - has_ignored_method_metaclasses
337 Required read-write lazy-building value of type
338 L<HashRef|Moose::Util::TypeConstraints> where the keys are method metaclasses
339 MooseX::AutoDoc should ignore when creating a method list.
345 =item B<builder> - _build_license_text
347 =item B<clearer> - clear_license_text
349 =item B<predicate> - has_license_text
353 Required read-write lazy-building value of type
354 L<Str|Moose::Util::TypeConstraints>. By default it will use the following text:
356 This library is free software; you can redistribute it and/or modify it
357 under the same terms as Perl itself.
363 =item B<builder> - _build_tc_to_lib_map
365 =item B<clearer> - clear_tc_to_lib_map
367 =item B<predicate> - has_tc_to_lib_map
371 Required read-write lazy-building value of type
372 L<HashRef|Moose::Util::TypeConstraints>. The keys refer to type constraint
373 names and the values to the module where the documentation available for that
374 type is. Please note that if you are using MooseX::Types libraries the links
375 will be automatically generated if the library class can be found (most cases).
381 =item B<builder> - _build_view
383 Returns 'MooseX::AutoDoc::View::TT'
385 =item B<clearer> - clear_view
387 =item B<predicate> - has_view
391 Required read-write lazy-building value of type AutoDocView. The AutoDocView
392 type will accept an Object that isa L<MooseX::AutoDoc::View>. This attribute
393 will attempt to coerce string values to instances by treating them as class
394 names and attempting to load and instantiate a class of the same name.
398 =head2 new key => $value
400 Instantiate a new object. Please refer to L</"ATTRIBUTES"> for a list of valid
403 =head2 generate_pod_for $package_name, $view_args
405 Returns a string containing the Pod for the package. To make sure the data is
406 accurate please make sure the package has not been loaded prior to this step.
407 for more info see L</"NOTICE REGARDING ROLE CONSUMPTION">
409 =head2 _package_info $package_name
411 Will return a hashref representing the documentation components of the package
412 with the keys C<name>, C<attributes>, C<methods>, C<attributes> and--if the
413 case the package is a class--C<superclasses>; the latter four are array refs
414 of the hashrefs returned by L</"_superclass_info">, L</"_attribute_info">,
415 L</"_method_info">, and L</"_consumed_role_info"> respectively.
417 =head2 _attribute_info $attr
419 Accepts one argument, an attribute metaclass instance.
420 Returns a hashref representing the documentation components of the
421 attribute with the keys C<name>, C<description>, and C<info>, a hashref
422 of additional information. If you have set the documentation attribute of
423 your attributes the documentation text will be appended to the auto-generated
426 =head2 _consumed_role_info $role
428 Accepts one argument, a role metaclass instance. Returns a hashref representing
429 the documentation components of the role with the key C<name>.
431 =head2 _method_info $method
433 Accepts one argument, a method metaclass instance. Returns a hashref
434 representing the documentation components of the role with the key C<name>.
436 =head2 _superclass_info $class
438 Accepts one argument, the metaclass instance of a superclass. Returns a hashref
439 representing the documentation components of the role with the key C<name>.
443 Retrieve the metaclass instance. Please see L<Moose::Meta::Class> and
444 L<Class::MOP::Class> for more information.
448 Guillermo Roditi (Guillermo Roditi) <groditi@cpan.org>
450 =head1 COPYRIGHT AND LICENSE
452 This library is free software; you can redistribute it and/or modify it under
453 the same terms as Perl itself.