" />
| VCS-CMSynergy documentation | Contained in the VCS-CMSynergy distribution. |
VCS::CMSynergy::Project - convenience methods for VCS::CMSynergy::Objects of type "project"
VCS::CMSynergy::Project is a subclass of VCS::CMSynergy::Object
with additional methods for Synergy projects.
use VCS::CMSynergy;
$ccm = VCS::CMSynergy->new(%attr);
...
$proj = $ccm->object("editor-1_project:1");
print ref $proj; # "VCS::CMSynergy::Project"
$proj->chdir_into_wa;
$proj->traverse(
sub { print " " x VCS::CMSynergy::Traversal::depth(), $_, "\n"; } );
This synopsis only lists the major methods.
my $old_pwd = $proj->chdir_into_wa;
Changes into the toplevel workarea directory of project $proj.
Returns undef if $proj doesn't maintain a workarea or
the chdir() failed, otherwise returns the name of current working
directory before the call.
$proj->traverse(\&wanted, $dir); $proj->traverse(\%options, $dir);
traverse walks the tree below directory $dir
in the invocant project without the need for a workarea.
It is modelled on File::Find.
&wanted is a code reference described in
"wanted function" below. $dir
must be a VCS::CMSynergy::Object. If $dir is omitted,
it defaults to the top level directory of the invocant.
&wanted is called once for all objects below $dir
including $dir itself. It will also be called on subprojects
of the incocant project, but traverse will not recurse into
subprojects unless the subprojects flag is specified
(see "options" below).
On each call to &wanted, $_ will be bound to the
currently traversed object (a VCS::CMSynergy::Object).
@VCS::CMSynergy::Traversal::dirs will be bound to
an array of VCS::CMSynergy::Objects of cvtype dir representing
the path from $dir to $_ (in the context of the invocant project).
In particular, @VCS::CMSynergy::Traversal::dirs[-1]
is the parent dir of $_.
The convenience function VCS::CMSynergy::Traversal::path()
returns the filesystem path for $_. It is short for
join($pathsep, map { $_->name } @VCS::CMSynergy::Traversal::dirs, $_)
where $pathsep is your platform's path separator.
The convenience function VCS::CMSynergy::Traversal::depth() returns the
current depth, where the top level project has depth 0. It is short for
scalar @VCS::CMSynergy::Traversal::dirs
Similarly @VCS::CMSynergy::Traversal::projects represents the
subproject hierarchy starting with the invocant project.
In particular, $_ is a member of $VCS::CMSynergy::Traversal::projects[-1].
Note: @VCS::CMSynergy::Traversal::dirs and
@VCS::CMSynergy::Traversal::projects are both readonly arrays,
i.e. you can't modify them in any way.
You may set $VCS::CMSynergy::Traversal::prune to a true
value in &wanted to stop recursion into sub directories (or subprojects)
(this makes only sense when &wanted is called
on a dir or project object).
If recursion into subprojects is specfied, &wanted
will be called once for the project object and also for the
top level dir of the subproject.
The first argument of traverse may also be a hash reference.
The following keys are supported:
wanted (code reference)The value should be a code reference. It is described in "wanted function".
bydepth (boolean)If this option is set, traverse
calls &wanted on a directory (or project) only after
all its entries have been processed. It is "off" by default.
preprocess (code reference)The value should be a code reference. It is used to preprocess
the children of a dir or project, i.e. before traverse
starts traversing it. You can use it to impose an ordering
among "siblings" in the traversal. You can also filter out
objects, so that wanted will never be called on them
(and traversal will not recurse on them in case of
dirs or projects).
The preprocessing function is called with
a list of VCS::CMSynergy::Objects and is expected to return
a possibly reordered subset of this list. Note that
the list may contain dir and project objects.
When the preprocessing function is called,
$_ is bound to the parent object (which is always
of cvtype dir or project).
postprocess (code reference)The value should be a code reference. It is invoked just before
leaving the current dir or project.
When the postprocessing function is called,
$_ is bound to the current object (which is always
of cvtype dir or project).
subprojects (boolean)If this option is set, traverse
will recurse into subprojects. It is "off" by default.
pathsep (string)The path separator to use for VCS::CMSynergy::Traversal::path().
The default is your platform's path separator.
attributes (array ref)This option is only useful if :cached_attributes is in effect.
It should contain a reference to an
array of attribute names. If present, traverse
uses query_object_with_attributes rather than
query_object for the traversal. Hence all objects encountered
in the traversal (e.g. $_ when bound in wanted or the elements
of the directory stack @VCS::CMSynergy::Traversal::dirs) have
their attribute caches primed for the given attributes,
cf. query_object_with_attributes.
Note that for any particular dir (or project) object,
the above code references are always called in order
preprocess, wanted, postprocess.
Example:
my $proj = $ccm->object('toolkit-1.0:project:1');
$proj->traverse(
sub { print VCS::CMSynergy::Traversal::path(), "\n" } );
This prints the directory tree of project toolkit-1.0:project:1 similar to the Unix command find. The order of entries in a directory is unspecified and sub projects are not traversed:
toolkit toolkit/makefile toolkit/makefile.pc toolkit/misc toolkit/misc/toolkit.ini toolkit/misc/readme
Another example:
$proj->traverse(
{
wanted => sub {
return unless $_->cvtype eq "project";
my $proj_depth = @VCS::CMSynergy::Traversal::projects;
print " " x $proj_depth, $_->displayname, "\n";
},
preprocess => sub { sort { $a->name cmp $b->name } @_; },
subprojects => 1,
});
This prints the complete project hierarchy rooted at toolkit-1.0:project:1. Only projects will be shown, entries are sorted by name and are intended according to their depth:
toolkit-1.0
calculator-1.0
editor-1.0
guilib-1.0
NOTE: This methods are only useful if you have the optional Synergy command get_member_info (from the "PC Integrations" package) installed, cf. README.get_member_info for details.
$members1 = $proj->get_member_info_hashref(@keywords, \%options);
$members2 = $proj->get_member_info_object(@keywords, \%options);
while (my ($path, $member) = each %$members2)
{
print "$path $member\n";
}
get_member_info_hashref and get_member_info_object
execute ccm get_member_info to obtain the members of project $proj.
They both return a reference to a hash where the keys are the
workarea (relative) pathnames of the members. For get_member_info_hashref,
the value is a hash of attributes similar to query_hashref in VCS::CMSynergy.
For get_member_info_object, the value is the member itself
(a CVS::CMSYnergy::Object), similar to query_object in VCS::CMSynergy.
If there was an error, undef is returned.
See the description of query_hashref in VCS::CMSynergy or
query_object in VCS::CMSynergy, resp., for the meaning of
@keywords. Both methods also accept an optional trailing
hash reference. Possible keys are:
subprojects (boolean)whether to list members of sub projects (recursively), default: false
pathsep (string)separator to use for the workarea pathnames, default: the platform's native path separator
Note the following deficiencies inherited from ccm get_member_info:
subprojects is true the member hash contains
all members of all sub projects, but doesn't give any information
which sub project a certain member belongs to.Note the following differences from ccm get_member_info:
$proj currently maintains a workarea or not.These are convenience methods to enumerate recursively all members of the invocant project or just the sub projects.
$members = $proj->recursive_is_member_of($order_spec, @keywords); $sub_projs = $proj->hierarchy_project_members($order_spec, @keywords);
are exactly the same as
$members = $proj->ccm->query_object(
"recursive_is_member_of('$proj',$order_spec)", @keywords);
$sub_projs = $proj->ccm->query_object(
"hierarchy_project_members('$proj',$order_spec)", @keywords);
$order_spec and @keywords are optional. If $order_spec is
undef or not supplied, "none" is used.
If you supply @keywords these are passed down
to query_object in VCS::CMSynergy as additional keywords.
These are convenience methods to enumerate all members of a directory in the context of the invocant project.
$members = $proj->is_child_of($dir, @keywords);
is exactly the same as
$members = $proj->ccm->query_object(
"is_child_of('$dir','$proj')", @keywords);
$dir and @keywords are optional. If $dir is supplied
it must be a VCS::CMSynergy::Object of type "dir".
If $dir is undef or not supplied, is_child_of returns
the toplevel directory of the invocant project (NOTE: the return value
is actually a reference to an array with one element).
If you supply @keywords these are passed down
to query_object in VCS::CMSynergy as additional keywords.
$obj = $proj->object_from_proj_ref($path, @keywords); $obj = $proj->object_from_proj_ref(\@path_components, @keywords);
is exactly the same as
$obj = $proj->ccm->object_from_proj_ref($path, $proj, @keywords); $obj = $proj->ccm->object_from_proj_ref(\@path_components, $proj, @keywords);
$objects = $proj->show_reconfigure_properties($what, @keywords, \%options);
Shows information about the project's reconfigure properties
depending on $what. @keywords and \%options are optional.
Returns a reference to an array of VCS::CMSynergy::Objects.
$what must be one of the following strings:
"tasks"shows tasks that are directly in the project’s reconfigure properties
"folders"shows folders that are in the project’s reconfigure properties
"tasks_and_folders"shows tasks and folders that are directly in the project’s reconfigure properties
"all_tasks"shows all tasks that are directly or indirectly in the project’s reconfigure properties (indirectly means the task is in a folder that is in the project’s reconfigure properties)
"objects"shows objects in the task that are either directly or indirectly in the project’s reconfigure properties
See the description of query_hashref in VCS::CMSynergy or
query_object in VCS::CMSynergy, resp., for the meaning of
@keywords.
show_reconfigure_properties also accepts an optional trailing
hash reference. Possible keys are:
subprojects (boolean)whether to include the reconfigure properties of sub projects (recursively), default: false
automatic (boolean)whether automatic tasks are to be shown, default: false;
this option is only relevant if $what is "tasks", "tasks_and_folders"
or "all_tasks"
Example:
$tasks = $proj->show_reconfigure_properties(
all_tasks => qw/task_synopsis completion_date/,
{ subprojects => 1, automatic => 0 });
| VCS-CMSynergy documentation | Contained in the VCS-CMSynergy distribution. |
package VCS::CMSynergy::Project; # Copyright (c) 2001-2010 argumentum GmbH, # See COPYRIGHT section in VCS/CMSynergy.pod for usage and distribution rights. our $VERSION = do { (my $v = q$Revision: 381 $) =~ s/^.*:\s*//; $v };
use strict; use base qw(VCS::CMSynergy::Object); use Carp; use VCS::CMSynergy::Client qw(_usage); use File::Spec; use Cwd;
# FIXME needs test sub chdir_into_wa { my $self = shift; return $self->ccm->set_error("project `$self' doesn't maintain a workarea") unless $self->get_attribute("maintain_wa") eq "TRUE"; my $wa_top = File::Spec->catfile($self->get_attribute("wa_path"), $self->name); my $old_pwd = cwd(); chdir($wa_top) or return $self->ccm->set_error("can't chdir($wa_top) into workarea of project `$self': $!"); return $old_pwd; }
# tied array class that acts as a readonly front to a real array # NOTE: TIEARRAY expects as first parameter a closure that # returns a reference to the "back" array. Storing the array reference # itself in the tied arraay doesn't work when the "back" array is local'ized. { package Tie::ReadonlyArray; use Carp; sub TIEARRAY { bless $_[1], $_[0]; } sub FETCH { $_[0]->()->[$_[1]]; } sub FETCHSIZE { scalar @{$_[0]->()}; } *STORE = *STORESIZE = *EXTEND = *CLEAR = *UNTIE = *PUSH = *POP = *UNSHIFT = *SHIFT = *SPLICE = sub { croak "attempt to modify a readonly array"; }; } # put some items into the VCS::CMSynergy::Traversal namespace { package VCS::CMSynergy::Traversal; # private our (@_dirs, @_projects, $_pathsep, $_catdirs); # public our (@dirs, @projects, $prune); tie @dirs, "Tie::ReadonlyArray" => sub { \@_dirs }; tie @projects, "Tie::ReadonlyArray" => sub { \@_projects }; # NOTE:references $_ (the currently traversed object) sub path { return @_dirs ? $_catdirs.$_pathsep.$_->name : $_->name } sub depth { return scalar @_dirs } sub _catdirs { $_catdirs = join($_pathsep, map { $_->name } @_dirs) } } my %traverse_opts = ( wanted => "CODE", preprocess => "CODE", postprocess => "CODE", attributes => "ARRAY", bydepth => undef, subprojects => undef, pathsep => undef, ); sub traverse { my $self = shift; _usage(@_, 1, 2, '{ \\&wanted | \\%wanted } [, $dir_object]'); my ($arg_wanted, $dir) = @_; my %wanted; if (ref $arg_wanted eq 'CODE') { %wanted = ( wanted => $arg_wanted ); } elsif (ref $arg_wanted eq 'HASH') { %wanted = %$arg_wanted; # make a copy, so we can't inadvertently modify it while (my ($opt, $value) = each %wanted) { croak(__PACKAGE__.qq[::traverse: argument 1 ("wanted"): unrecognized option "$opt"]) unless exists $traverse_opts{$opt}; my $type = $traverse_opts{$opt} or next; croak(__PACKAGE__.qq[::traverse: argument 1 ("wanted"): option "$opt" must be a $type: $value]) unless UNIVERSAL::isa($value, $type); } croak(__PACKAGE__."::traverse: argument 1 (wanted hash ref): option `wanted' is mandatory") unless $wanted{wanted}; } else { croak(__PACKAGE__."::traverse: argument 1 (wanted) must be a CODE or HASH ref: $arg_wanted"); } if (defined $dir) { croak(__PACKAGE__."::traverse: argument 2 (dir) must be a VCS::CMSynergy::Object: $dir") unless UNIVERSAL::isa($dir, "VCS::CMSynergy::Object"); croak(__PACKAGE__."::traverse: argument 2 (dir) must have cvtype `dir': $dir") unless $dir->is_dir; # check that $dir is member of $self # FIXME there must be a better way to do this my $result = $self->ccm->query_object( { name => $dir->name, cvtype => $dir->cvtype, instance => $dir->instance, version => $dir->version, is_member_of => [ $self ] }, @{ $wanted{attributes} }); return $self->ccm->set_error("directory `$dir' doesn't exist or isn't a member of `$self'") unless @$result; $dir = $result->[0]; } else { $dir = $self; } local @VCS::CMSynergy::Traversal::_projects = ($self); local @VCS::CMSynergy::Traversal::_dirs = (); local $VCS::CMSynergy::Traversal::_pathsep = (delete $wanted{pathsep}) || VCS::CMSynergy::Client::_pathsep; $self->_traverse(\%wanted, $dir); } # helper method: grunt work of traverse sub _traverse { my ($self, $wanted, $parent) = @_; # NOTE: $parent is either a "dir" or "project" by construction my $children = $self->is_child_of( $parent->is_dir ? $parent : undef, @{ $wanted->{attributes} }) or return; if ($wanted->{preprocess}) { # make $_ the current dir/project during preprocess'ing local $_ = $parent; { $children = [ $wanted->{preprocess}->(@$children) ]; } } if (!$wanted->{bydepth}) { local $_ = $parent; local $VCS::CMSynergy::Traversal::prune = 0; { $wanted->{wanted}->(); } # protect against wild "next" return 1 if $VCS::CMSynergy::Traversal::prune; } push @VCS::CMSynergy::Traversal::_dirs, $parent unless $parent->is_project; VCS::CMSynergy::Traversal::_catdirs(); foreach (@$children) # localizes $_ { if ($_->is_project && $wanted->{subprojects}) { push @VCS::CMSynergy::Traversal::_projects, $_; $_->_traverse($wanted, $_) or return; pop @VCS::CMSynergy::Traversal::_projects; next; } if ($_->is_dir) { $self->_traverse($wanted, $_) or return; next; } { $wanted->{wanted}->(); } } pop @VCS::CMSynergy::Traversal::_dirs unless $parent->is_project; VCS::CMSynergy::Traversal::_catdirs(); if ($wanted->{bydepth}) { local $_ = $parent; local $VCS::CMSynergy::Traversal::prune = 0; { $wanted->{wanted}->(); } return 1 if $VCS::CMSynergy::Traversal::prune; } if ($wanted->{postprocess}) { # make $_ the current dir/project during postprocess'ing local $_ = $parent; { $wanted->{postprocess}->(); } } return 1; }
sub get_member_info_hashref { my $self = shift; my $opts = @_ && ref $_[-1] eq "HASH" ? pop : {}; return $self->_get_member_info(\@_, $opts, 0); } sub get_member_info_object { my $self = shift; my $opts = @_ && ref $_[-1] eq "HASH" ? pop : {}; return $self->_get_member_info(\@_, $opts, 1); } # private method: wrapper for get_member_info from PC integrations intlib.a # if $row_object is true, returns objects, otherwise hashes sub _get_member_info { my ($self, $keywords, $opts, $want_row_object) = @_; my $want = VCS::CMSynergy::_want($want_row_object, $keywords); # NOTE: $RS is at the end (because get_member_info _prepends_ the path) my $format = $VCS::CMSynergy::FS . join($VCS::CMSynergy::FS, values %$want) . $VCS::CMSynergy::RS; my @cmd = qw/get_member_info/; push @cmd, "-recurse" if $opts->{subprojects}; my ($rc, $out, $err) = $self->ccm->_ccm(@cmd, -format => $format, $self); return $self->ccm->set_error($err || $out) unless $rc == 0; my %result; my $wa_path_len = $self->get_attribute("maintain_wa") eq "TRUE" ? length($self->get_attribute("wa_path")) + 1 : 0; my $_pathsep = VCS::CMSynergy::Client::_pathsep; # split into records # NOTE: $RS is followed by \n foreach (split(/\Q${VCS::CMSynergy::RS}\E\s*/, $out)) { my @cols = split(/\Q${VCS::CMSynergy::FS}\E/, $_, -1); # path information is the first "column", strip wa_path if necessary my $path = shift @cols; substr($path, 0, $wa_path_len) = "" if $wa_path_len; $path =~ s/\Q$_pathsep\E/$opts->{pathsep}/g if $opts->{pathsep}; $result{$path} = $self->ccm->_parse_query_result($want, \@cols, $want_row_object); } return \%result; }
sub recursive_is_member_of { my $self = shift; _usage(@_, 0, undef, '[{ $order_spec | undef }, @keywords]'); my $order_spec = shift || "none"; return $self->ccm->query_object("recursive_is_member_of('$self',$order_spec)", @_); } sub hierarchy_project_members { my $self = shift; _usage(@_, 0, undef, '[{ $order_spec | undef }, @keywords]'); my $order_spec = shift || "none"; return $self->ccm->query_object("hierarchy_project_members('$self',$order_spec)", @_); }
sub is_child_of { my $self = shift; _usage(@_, 0, undef, '[{ $dir_object | undef }, @keywords]'); my $dir = shift; if (defined $dir) { croak(__PACKAGE__."::is_child_of: argument 1 ($dir) must be a VCS::CMSynergy::Object") unless UNIVERSAL::isa($dir, "VCS::CMSynergy::Object"); croak(__PACKAGE__."::is_child_of: argument 1 ($dir) must have cvtype `dir'") unless $dir->is_dir; } else { $dir = $self; } return $self->ccm->query_object("is_child_of('$dir','$self')", @_); }
sub object_from_proj_ref { my $self = shift; _usage(@_, 1, undef, '{ $path | \\@path_components }, @keywords'); my $path = shift; return $self->ccm->object_from_proj_ref($path, $self, @_); }
sub show_reconfigure_properties { my $self = shift; my $opts = @_ && ref $_[-1] eq "HASH" ? pop : {}; _usage(@_, 1, undef, '$what [, @keywords] [, \%options]'); my $what = shift; croak(__PACKAGE__."::show_reconfigure_properties:". " argument 1 (what) must be one of tasks|folders|tasks_and_folders|all_tasks|objects") unless $what =~ /^(tasks|folders|tasks_and_folders|all_tasks|objects)$/; my $want = VCS::CMSynergy::_want(1, \@_); my $format = $VCS::CMSynergy::RS . join($VCS::CMSynergy::FS, values %$want) . $VCS::CMSynergy::FS; my @cmd = qw/reconfigure_properties -u -ns/; push @cmd, $opts->{automatic} ? "-auto" : "-no_auto" if $what =~ /tasks/; push @cmd, "-r" if $opts->{subprojects}; my ($rc, $out, $err) = $self->ccm->_ccm( @cmd, -format => $format, -show => $what, $self); return $self->set_error($err || $out) unless $rc == 0; # NOTE: if the reconf properties are empty, Synergy shows the string "None" return [ ] if $out eq "None"; my @result; foreach (split(/\Q${VCS::CMSynergy::RS}\E/, $out)) # split into records { next unless length($_); # skip empty leading record my @cols = split(/\Q${VCS::CMSynergy::FS}\E/, $_, -1); # don't strip empty trailing fields push @result, $self->ccm->_parse_query_result($want, \@cols, 1); } return \@result; } 1;