File Coverage

lib/UR/Namespace/Command/Base.pm
Criterion Covered Total %
statement 137 157 87.2
branch 40 66 60.6
condition 8 15 53.3
subroutine 17 18 94.4
pod 1 5 20.0
total 203 261 77.7


line stmt bran cond sub pod time code
1             package UR::Namespace::Command::Base;
2 8     8   190 use strict;
  8         15  
  8         205  
3 7     7   23 use warnings;
  7         8  
  7         165  
4 7     7   24 use UR;
  7         8  
  7         41  
5              
6 7     7   25 use Cwd;
  7         10  
  7         425  
7 7     7   27 use Carp;
  7         7  
  7         319  
8 7     7   24 use File::Find;
  7         8  
  7         9753  
9              
10             our $VERSION = "0.46"; # UR $VERSION;
11              
12             UR::Object::Type->define(
13             class_name => __PACKAGE__,
14             is => 'Command::V1',
15             is_abstract => 1,
16             has => [
17             namespace_name => { type => 'String',
18             is_optional => 1,
19             doc => 'Name of the Namespace to work in. Auto-detected if within a Namespace directory'
20             },
21             lib_path => { type => "FilesystemPath",
22             doc => "The directory in which the namespace module resides. Auto-detected normally.",
23             is_constant => 1,
24             calculate_from => ['namespace_name'],
25             calculate => q( # the namespace module should have gotten loaded in create()
26             my $namespace_module = $namespace_name;
27             $namespace_module =~ s#::#/#g;
28             my $namespace_path = Cwd::abs_path($INC{$namespace_module . ".pm"});
29             unless ($namespace_path) {
30             Carp::croak("Namespace module $namespace_name has not been loaded yet");
31             }
32             $namespace_path =~ s/\/[^\/]+.pm$//;
33             return $namespace_path;
34             ),
35             },
36             working_subdir => { type => "FilesystemPath",
37             doc => 'The current working directory relative to lib_path',
38             calculate => q( my $lib_path = $self->lib_path;
39             return UR::Util::path_relative_to($lib_path, Cwd::abs_path(Cwd::getcwd));
40             ),
41             },
42             namespace_path => { type => 'FilesystemPath',
43             doc => "The directory under which all the namespace's modules reside",
44             is_constant => 1,
45             calculate_from => ['namespace_name'],
46             calculate => q( my $lib_path = $self->lib_path;
47             return $lib_path . '/' . $namespace_name;
48             ),
49             },
50             verbose => { type => "Boolean", is_optional => 1,
51             doc => "Causes the command to show more detailed output."
52             },
53             ],
54             doc => 'a command which operates on classes/modules in a UR namespace directory'
55             );
56              
57             sub create {
58 11     11 1 2477 my $class = shift;
59            
60 11         118 my ($rule,%extra) = $class->define_boolexpr(@_);
61 11         34 my($namespace_name, $lib_path);
62 11 100       46 if ($rule->specifies_value_for('namespace_name')) {
63 6         31 $namespace_name = $rule->value_for('namespace_name');
64 6         46 $lib_path = $class->resolve_lib_path_for_namespace_name($namespace_name);
65              
66             } else {
67 5         40 ($namespace_name,$lib_path) = $class->resolve_namespace_name_from_cwd();
68 5 100       26 unless ($namespace_name) {
69 1         23 $class->error_message("Could not determine namespace name.");
70 1         3 $class->error_message("Run this command from within a namespace subdirectory or use the --namespace-name command line option");
71 1         13 return;
72             }
73 4         38 $rule = $rule->add_filter(namespace_name => $namespace_name);
74             }
75              
76             # Use the namespace.
77 10 50       93 $class->status_message("Loading namespace module $namespace_name") if ($rule->value_for('verbose'));
78              
79             # Ensure the right modules are visible to the command.
80             # Make the module accessible.
81             # We'd like to "use lib" this directory, but any other -I/use-lib
82             # requests should still come ahead of it. This requires a little munging.
83              
84             # Find the first thing in the compiled_inc list that exists
85 10         29 my $compiled = '';
86 10         75 for my $path ( UR::Util::compiled_inc() ) {
87 10 50       401 next unless -d $path;
88 10         563 $compiled = Cwd::abs_path($path);
89 10 50       51 last if defined $compiled;
90             }
91              
92 10         34 my $perl5lib = '';
93 10         72 foreach my $path ( split(':', $ENV{'PERL5LIB'}) ) {
94 10 50       160 next unless -d $path;
95 10         404 $perl5lib = Cwd::abs_path($path);
96 10 50       40 last if defined $perl5lib;
97             }
98              
99 10         30 my $i;
100 10         47 for ($i = 0; $i < @INC; $i++) {
101             # Find the index in @INC that's the first thing in
102             # compiled-in module paths
103             #
104             # since abs_path returns undef for non-existant dirs,
105             # skip the comparison if either is undef
106 45         1387 my $inc = Cwd::abs_path($INC[$i]);
107 45 50       300 next unless defined $inc;
108 45 100 66     228 last if ($inc eq $compiled or $inc eq $perl5lib);
109             }
110 10         55 splice(@INC, $i, 0, $lib_path);
111 6     6   798 eval "use $namespace_name";
  6     3   70  
  6         76  
  3         32  
  3         10  
  3         44  
  10         1151  
112 10 50       44 if ($@) {
113 0         0 $class->error_message("Error using namespace module '$namespace_name': $@");
114 0         0 return;
115             }
116              
117 10         157 my $self = $class->SUPER::create($rule);
118 10 50       41 return unless $self;
119              
120 10 50       17 unless (eval { UR::Namespace->get($namespace_name) }) {
  10         103  
121 0         0 $self->error_message("Namespace '$namespace_name' was not found");
122 0         0 return;
123             }
124              
125 10 50       103 if ($namespace_name->can("_set_context_for_schema_updates")) {
126 0         0 $namespace_name->_set_context_for_schema_updates();
127             }
128              
129 10         971 return $self;
130             }
131              
132             sub command_name {
133 1     1 0 4 my $class = shift;
134 1 50       5 return "ur" if $class eq __PACKAGE__;
135 1         12 my $name = $class->SUPER::command_name;
136 1         4 $name =~ s/^u-r namespace/ur/;
137 1         5 return $name;
138             }
139              
140             sub help_detail {
141             return shift->help_brief
142 0     0 0 0 }
143              
144             # Return a list of module pathnames relative to lib_path
145             sub _modules_in_tree {
146 4     4   547 my $self = shift;
147 4         7 my @modules;
148              
149 4         16 my $lib_path = $self->lib_path;
150 4         16 my $namespace_path = $self->namespace_path;
151              
152             my $wanted_closure = sub {
153 594 100 100 594   9585 if (-f $_ and m/\.pm$/) {
154 44         74 push @modules, UR::Util::path_relative_to($lib_path, $_);
155             }
156 4         19 };
157 4 100       12 unless (@_) {
158 2         182 File::Find::find({ no_chdir => 1,
159             wanted => $wanted_closure,
160             },
161             $namespace_path);
162             }
163             else {
164             # this method takes either module paths or class names as params
165             # normalize to module paths
166              
167              
168             NAME:
169 2         18 for (my $i = 0; $i < @_; $i++) {
170 7         13 my $name = $_[$i];
171              
172 7 100       20 if ($name =~ m/::/) {
173             # It's a class name
174 4         11 my @name_parts = split(/::/, $name);
175 4 50       14 unless ($self->namespace_name eq $name_parts[0]) {
176 0         0 $self->warning_message("Skipping class name $name: Not in namespace ".$self->namespace_name);
177 0         0 next NAME;
178             }
179 4         12 $name = join('/', @name_parts) . ".pm";
180             }
181              
182             # First, check the pathname relative to the cwd
183             CHECK_LIB_PATH:
184 7         22 foreach my $check_name ( $name, $lib_path.'/'.$name, $namespace_path.'/'.$name) {
185 16 100       152 if (-e $check_name) {
186 5 50 33     69 if (-f $check_name and $check_name =~ m/\.pm$/) {
    0          
    0          
187 5         19 push @modules, UR::Util::path_relative_to($lib_path, $check_name);
188 5         14 next NAME; # found it, don't check the other $check_name
189              
190             } elsif (-d $check_name) {
191 0         0 File::Find::find({ no_chdir => 1,
192             wanted => $wanted_closure,
193             },
194             $check_name);
195             } elsif (-e $check_name) {
196 0         0 $self->warning_message("Ignoring non-module $check_name");
197 0         0 next CHECK_LIB_PATH;
198             }
199             }
200              
201             }
202             }
203             }
204 4         44 return @modules;
205             }
206              
207             sub _class_names_in_tree {
208 2     2   640 my $self = shift;
209 2         12 my @modules = $self->_modules_in_tree(@_);
210 2         13 my $lib_path = $self->lib_path;
211 2         8 my @class_names;
212 2         8 for my $module (@modules) {
213 23         20 my $class = $module;
214 23         90 $class =~ s/^$lib_path\///;
215 23         36 $class =~ s/\//::/g;
216 23         39 $class =~ s/\.pm$//;
217              
218             # Paths can have invalid package names so are therefore packages in
219             # another "namespace" and should not be included.
220 23 50       46 next unless UR::Util::is_valid_class_name($class);
221              
222 23         27 push @class_names, $class;
223             }
224 2         19 return @class_names;
225             }
226              
227             sub _class_objects_in_tree {
228 1     1   2 my $self = shift;
229 1         10 my @class_names = $self->_class_names_in_tree(@_);
230 1         1 my @class_objects;
231 1         5 for my $class_name (sort { uc($a) cmp uc($b) } @class_names) {
  0         0  
232 1 50       9 unless(UR::Object::Type->use_module_with_namespace_constraints($class_name)) {
233             #if ($@) {
234 0         0 print STDERR "Failed to use class $class_name!\n";
235 0         0 print STDERR $@,"\n";
236 0         0 next;
237             }
238 1         4 my $c = UR::Object::Type->is_loaded(class_name => $class_name);
239 1 50       4 unless ($c) {
240             #print STDERR "Failed to find class object for class $class_name\n";
241 0         0 next;
242             }
243 1         2 push @class_objects, $c;
244             #print $class_name,"\n";
245             }
246 1         5 return @class_objects;
247             }
248              
249             # Tries to guess what namespace you are in from your current working
250             # directory. When called in list context, it also returns the directroy
251             # name the namespace module was found in
252             sub resolve_namespace_name_from_cwd {
253 13     13 0 21 my $class = shift;
254 13         20 my $cwd = shift;
255 13   33     41548 $cwd ||= Cwd::cwd();
256              
257 13         127 my @lib = grep { length($_) } split(/\//,$cwd);
  69         145  
258              
259             SUBDIR:
260 13         79 while (@lib) {
261 36         73 my $namespace_name = pop @lib;
262              
263 36         111 my $lib_path = "/" . join("/",@lib);
264 36         80 my $namespace_module_path = $lib_path . '/' . $namespace_name . '.pm';
265 36 100       767 if (-e $namespace_module_path) {
266 4 50       67 if ($class->_is_file_the_namespace_module($namespace_name, $namespace_module_path)) {
267 4 50       22 if (wantarray) {
268 4         30 return ($namespace_name, $lib_path);
269             } else {
270 0         0 return $namespace_name;
271             }
272             }
273             }
274             }
275 9         71 return;
276             }
277              
278             # Returns true if the given file is the namespace module we're looking for.
279             # The only certain way is to go ahead and load it, but this should be good
280             # enough for ligitimate use cases.
281             sub _is_file_the_namespace_module {
282 11     11   50 my($class,$namespace_name,$namespace_module_path) = @_;
283              
284 11         217 my $fh = IO::File->new($namespace_module_path);
285 11 50       1996 return unless $fh;
286 11         624 while (my $line = $fh->getline) {
287 38 100       1704 if ($line =~ m/package\s+$namespace_name\s*;/) {
288             # At this point $namespace_name should be a plain word with no ':'s
289             # and if the file sets the package to a single word with no colons,
290             # it's pretty likely that it's a namespace module.
291 11         183 return 1;
292             }
293             }
294 0         0 return;
295             }
296              
297              
298             # Return the pathname that the specified namespace module can be found
299             sub resolve_lib_path_for_namespace_name {
300 7     7 0 476 my($class,$namespace_name,$cwd) = @_;
301              
302 7 50       31 unless ($namespace_name) {
303 0         0 Carp::croak('namespace name is a required argument for UR::Util::resolve_lib_path_for_namespace_name()');
304             }
305              
306             # first, see if we're in a namespace dir
307 7         37 my($resolved_ns_name, $lib_path ) = $class->resolve_namespace_name_from_cwd($cwd);
308 7 50 33     63 return $lib_path if (defined($resolved_ns_name) and $resolved_ns_name eq $namespace_name);
309              
310 7         41 foreach $lib_path ( @main::INC ) {
311 7         40 my $expected_namespace_module = $lib_path . '/' . $namespace_name . '.pm';
312 7         39 $expected_namespace_module =~ s/::/\//g; # swap :: for /
313 7 50       145 if ( $class->_is_file_the_namespace_module($namespace_name, $expected_namespace_module)) {
314 7         53 return $lib_path;
315             }
316             }
317 0           return;
318             }
319              
320             1;
321              
322              
323             =pod
324              
325             =head1 NAME
326              
327             UR::Namespace::Command - Top-level Command module for the UR namespace commands
328              
329             =head1 DESCRIPTION
330              
331             This class is the parent class for all the namespace-manipluation command
332             modules, and the root for command handling behind the 'ur' command-line
333             script.
334              
335             There are several sub-commands for manipluating a namespace's metadata.
336              
337             =over 4
338              
339             =item browser
340              
341             Start a lightweight web server for viewing class and schema information
342              
343             =item commit
344              
345             Update data source schemas based on the current class structure
346              
347             =item define
348              
349             Define metadata instances such as classes, data sources or namespaces
350              
351             =item describe
352              
353             Get detailed information about a class
354              
355             =item diff
356              
357             Show a diff for various kinds of other ur commands.
358              
359             =item info
360              
361             Show brief information about class or schema metadata
362              
363             =item list
364              
365             List various types of things
366              
367             =item redescribe
368              
369             Outputs class description(s) formatted to the latest standard
370              
371             =item rename
372              
373             Rename logical schema elements.
374              
375             =item rewrite
376              
377             Rewrites class descriptions headers to normalize manual changes.
378              
379             =item test
380              
381             Sub-commands related to testing
382              
383             =item update
384              
385             Update metadata based on external data sources
386              
387             =back
388              
389             Some of these commands have sub-commands of their own. You can get more
390             detailed information by typing 'ur --help' at the command line.
391              
392             =head1 SEE ALSO
393              
394             Command, UR, UR::Namespace
395              
396             =cut
397