[Bps-public-commit] r16343 - in sd/trunk: . lib/App/SD/CLI/Command/Ticket lib/App/SD/CLI/Model
jesse at bestpractical.com
jesse at bestpractical.com
Fri Oct 17 22:18:13 EDT 2008
Author: jesse
Date: Fri Oct 17 22:18:13 2008
New Revision: 16343
Added:
sd/trunk/lib/App/SD/CLI/Command/TextEditor.pm
Modified:
sd/trunk/ (props changed)
sd/trunk/lib/App/SD/CLI/Command/Ticket/Create.pm
sd/trunk/lib/App/SD/CLI/Command/Ticket/Update.pm
sd/trunk/lib/App/SD/CLI/Model/Ticket.pm
Log:
r46994 at 31b: jesse | 2008-10-17 22:42:18 +0100
* refactoring to make the cli commands more usable
Added: sd/trunk/lib/App/SD/CLI/Command/TextEditor.pm
==============================================================================
--- (empty file)
+++ sd/trunk/lib/App/SD/CLI/Command/TextEditor.pm Fri Oct 17 22:18:13 2008
@@ -0,0 +1,44 @@
+package App::SD::CLI::Command::TextEditor;
+use Moose::Role;
+use Params::Validate qw/validate/;
+
+sub try_to_edit {
+ my $self = shift;
+ my %args = validate(
+ @_,
+ { template => 1,
+ record => 1,
+ }
+ );
+
+ my $record = $args{'record'};
+
+ my $template = ${ $args{template} };
+
+ # do the edit
+ my $updated = $self->edit_text($template);
+
+ die "Aborted.\n" if $updated eq $template; # user didn't change anything
+
+ $self->process_template(
+ template => $args{template},
+ edited => $updated,
+ record => $record
+ );
+}
+
+sub handle_template_errors {
+ my $self = shift;
+ my %args = validate ( @_ , { error => 1, template_ref => 1, bad_template => 1});
+
+
+ $self->prompt_Yn("Want to return back to editing?") || die "Aborted.\n";
+
+ ${ $args{'template_ref'} }
+ = "=== Your template contained errors ====\n\n".$args{error}."\n\n"
+ . $args{bad_template};
+ return 0;
+ }
+
+no Moose::Role;
+1;
Modified: sd/trunk/lib/App/SD/CLI/Command/Ticket/Create.pm
==============================================================================
--- sd/trunk/lib/App/SD/CLI/Command/Ticket/Create.pm (original)
+++ sd/trunk/lib/App/SD/CLI/Command/Ticket/Create.pm Fri Oct 17 22:18:13 2008
@@ -1,58 +1,65 @@
package App::SD::CLI::Command::Ticket::Create;
use Moose;
+use Params::Validate qw/validate/;
extends 'Prophet::CLI::Command::Create';
with 'App::SD::CLI::Model::Ticket';
with 'App::SD::CLI::Command';
+with 'App::SD::CLI::Command::TextEditor';
# we want to launch an $EDITOR window to grab props and a comment if no
# props are specified on the commandline
+
override run => sub {
my $self = shift;
my @prop_set = $self->prop_set;
my $record = $self->_get_record_object;
- # only invoke editor if no props specified on the commandline or edit arg
- # specified
- if (!@prop_set || $self->has_arg('edit')) {
- my $ticket_string_to_edit = $self->create_record_string();
-
- TRY_AGAIN:
- my $ticket = $self->edit_text($ticket_string_to_edit);
-
- die "Aborted.\n"
- if $ticket eq $ticket_string_to_edit; # user didn't change anything
-
- (my $props_ref, my $comment) = $self->parse_record_string($ticket);
-
- for my $prop (keys %$props_ref) {
- $self->set_prop($prop => $props_ref->{$prop});
- }
-
- my $error;
- {
- local $@;
- eval { super(); } or $error = $@ || "Something went wrong!";
- }
- if ( $error ) {
- print STDERR "Couldn't create a record, error:\n\n", $error, "\n";
- die "Aborted.\n" unless $self->prompt_Yn( "Want to return back to editing?" );
-
- ($ticket_string_to_edit, $error) = ($ticket, '');
- goto TRY_AGAIN;
- }
+ # only invoke editor if no props specified on the commandline or edit arg # specified
+ return super() if (@{$self->prop_set} && !$self->has_arg('edit'));
+
- # retrieve the created record from the superclass
- $record = $self->record();
+ my $template_to_edit = $self->create_record_template();
- $self->add_comment( content => $comment, uuid => $record->uuid )
- if $comment;
+ my $done = 0;
- } else {
- super();
+ while (!$done) {
+ $done = $self->try_to_edit( template => \$template_to_edit, record => $record);
}
+
};
+sub process_template {
+ my $self = shift;
+ my %args = validate( @_, { template => 1, edited => 1, record => 1 } );
+
+ my $record = $args{record};
+ my $do_not_edit = $record->props_not_to_edit;
+ my $updated = $args{edited};
+ ( my $props_ref, my $comment ) = $self->parse_record_template($updated);
+
+ for my $prop ( keys %$props_ref ) {
+ $self->context->set_prop( $prop => $props_ref->{$prop} );
+ }
+
+ my $error;
+ local $@;
+ eval { super(); } or $error = $@ || "Something went wrong!";
+
+ return $self->handle_template_errors(
+ error => $error,
+ template_ref => $args{template},
+ bad_template => $updated
+ ) if ($error);
+
+ $self->add_comment( content => $comment, uuid => $self->record->uuid )
+ if $comment;
+
+ return 1;
+}
+
+
+
__PACKAGE__->meta->make_immutable;
no Moose;
Modified: sd/trunk/lib/App/SD/CLI/Command/Ticket/Update.pm
==============================================================================
--- sd/trunk/lib/App/SD/CLI/Command/Ticket/Update.pm (original)
+++ sd/trunk/lib/App/SD/CLI/Command/Ticket/Update.pm Fri Oct 17 22:18:13 2008
@@ -1,47 +1,52 @@
package App::SD::CLI::Command::Ticket::Update;
use Moose;
+use Params::Validate qw/validate/;
extends 'Prophet::CLI::Command::Update';
with 'App::SD::CLI::Model::Ticket';
with 'App::SD::CLI::Command';
+with 'App::SD::CLI::Command::TextEditor';
+
# use an editor to edit if no props are specified on the commandline,
# allowing the creation of a new comment in the process
override run => sub {
my $self = shift;
$self->require_uuid;
-
my $record = $self->_load_record;
- my $props = $record->get_props;
+ return super() if (@{$self->prop_set} && !$self->has_arg('edit'));
+ my $template_to_edit = $self->create_record_template($record);
- if (@{$self->prop_set} && !$self->has_arg('edit')) {
- return super();
- }
+ my $done = 0;
- my $ticket_string_to_edit = $self->create_record_string($record);
- my $do_not_edit = $record->props_not_to_edit;
+ while (!$done) {
+ $done = $self->try_to_edit( template => \$template_to_edit, record => $record);
+ }
- TRY_AGAIN:
- my $updated = $self->edit_text($ticket_string_to_edit);
+};
- die "Aborted.\n"
- if $updated eq $ticket_string_to_edit; # user didn't change anything
+sub process_template {
+ my $self = shift;
+ my %args = validate( @_, { template => 1, edited => 1, record => 1 } );
- my ($props_ref, $comment) = $self->parse_record_string($updated);
+ my $record = $args{record};
+ my $do_not_edit = $record->props_not_to_edit;
+ my $updated = $args{edited};
+ my ( $props_ref, $comment ) = $self->parse_record_template($updated);
no warnings 'uninitialized';
# if a formerly existing prop was removed from the output, delete it
# (deleting is currently the equivalent of setting to '', and
# we want to do this all in one changeset)
- for my $prop (keys %{$record->get_props}) {
- unless ($prop =~ $do_not_edit) {
+ for my $prop ( keys %{ $record->get_props } ) {
+ unless ( $prop =~ $do_not_edit ) {
$props_ref->{$prop} = '' if !exists $props_ref->{$prop};
}
}
# don't add props that didn't change to the changeset
- for my $prop (keys %$props_ref) {
+ for my $prop ( keys %$props_ref ) {
delete $props_ref->{$prop}
if $props_ref->{$prop} eq $record->prop($prop);
}
@@ -49,27 +54,24 @@
# set the new props
if ( keys %$props_ref ) {
my $error;
- {
- local $@;
- eval { $record->set_props( props => $props_ref ) }
- or $error = $@ || "Something went wrong!";
- }
- if ( $error ) {
- print STDERR "Couldn't update the record, error:\n\n", $error, "\n";
- die "Aborted.\n" unless $self->prompt_Yn( "Want to return back to editing?" );
+ local $@;
+ eval { $record->set_props( props => $props_ref ) }
+ or $error = $@ || "Something went wrong!";
+
+ return $self->handle_template_errors(
+ error => $error,
+ template_ref => $args{template},
+ bad_template => $updated
+ ) if ($error);
- ($ticket_string_to_edit, $error) = ($updated, '');
- goto TRY_AGAIN;
- }
print 'Updated ticket ' . $record->luid . ' (' . $record->uuid . ")\n";
- }
- else {
+ } else {
print "No changes in properties.\n";
}
- $self->add_comment( content => $comment, uuid => $record->uuid )
- if $comment;
-};
+ $self->add_comment( content => $comment, uuid => $record->uuid ) if $comment;
+ return 1;
+}
__PACKAGE__->meta->make_immutable;
no Moose;
Modified: sd/trunk/lib/App/SD/CLI/Model/Ticket.pm
==============================================================================
--- sd/trunk/lib/App/SD/CLI/Model/Ticket.pm (original)
+++ sd/trunk/lib/App/SD/CLI/Model/Ticket.pm Fri Oct 17 22:18:13 2008
@@ -4,6 +4,19 @@
use constant record_class => 'App::SD::Model::Ticket';
+=head2 separator_pattern
+
+A pattern that will match on lines that count as section separators
+in tickets represented as strings. Separator string text is remembered
+as C<$1>.
+
+=cut
+
+use constant separator_pattern => qr/^=== (.*) ===$/;
+use constant comment_pattern => qr/^\s*#/;
+
+
+
=head2 add_comment content => str, uuid => str
A convenience method that takes a content string and a ticket uuid and creates
@@ -29,7 +42,7 @@
$command->run();
}
-=head2 metadata_separator_text
+=head2 metadata_separator
Returns a string of text that goes in the comment denoting the beginning of
uneditable ticket metadata in a string representing a ticket.
@@ -41,68 +54,30 @@
=cut
-sub metadata_separator_text {
- 'required ticket metadata (changes here will not be saved)'
-}
-
-=head2 editable_props_separator_text
-
-Returns a string that denotes the text that goes in the comment denoting the
-beginning of prop: value pairs that are updatable in a string representing a
-ticket.
+use constant metadata_separator => 'required ticket metadata (changes here will not be saved)';
+use constant editable_props_separator => 'edit ticket details below';
+use constant comment_separator => 'add new ticket comment below';
-=cut
-
-sub editable_props_separator_text { 'edit ticket details below' }
-
-=head2 comment_separator_text
-
-Returns a string that goes in the comment that separates the prop: value lines
-from the ticket comment in a string representing a ticket. The ticket comment
-will be free text to the end of the new ticket. May contain arbitrary newlines.
-
-=cut
-
-sub comment_separator_text { 'add new ticket comment below' }
-
-=head2 separator_pattern
-
-A pattern that will match on lines that count as section separators
-in tickets represented as strings. Separator string text is remembered
-as C<$1>.
-
-=cut
-
-sub separator_pattern { qr/^=== (.*) ===$/ }
-
-=head2 create_separator $text
+=head2 _build_separator $text
Takes a string and returns it in separator form.
=cut
-sub create_separator {
+sub _build_separator {
my $self = shift;
my $text = shift;
return "=== $text ===";
}
-=head2 comment_pattern
-
-Returns a pattern that will match on lines that count as comments in
-tickets represented as strings.
-
-=cut
-sub comment_pattern { qr/^\s*#/ }
-
-=head2 create_record_string RECORD
+=head2 create_record_template RECORD
Creates a string representing a new record, prefilling default props
and props specified on the command line. Intended to be presented to
-the user for editing using L<Prophet::CLI::Command->edit_text>
-and then parsed using L</create_record_string>.
+the user for editing using L<Prophet::CLI::Command->edit>
+and then parsed using L</create_record_template>.
If RECORD is given, then we are updating that record rather than
creating a new one, and the ticket string will be created from its
@@ -110,16 +85,22 @@
=cut
-sub create_record_string {
+sub create_record_template {
my $self = shift;
my $record = shift;
- my $update = 0;
+ my $update ;
+
+ if ($record) { $update = 1 }
+ else {
+
+ $record = $self->_get_record_object;
+ $update = 0;
+ }
- defined($record) ? $update = 1 : $record = $self->_get_record_object;
my $props_not_to_edit = $record->props_not_to_edit;
my (@metadata_order, @editable_order);
- my (%metadata_props, %editable_props);
+ my (%immutable_props, %editable_props);
# separate out user-editable props so we can both show all
# the props that will be added to the new ticket and prevent
@@ -130,7 +111,7 @@
if ($prop eq 'id' && $update) {
# id isn't a *real* prop, so we have to mess with it some more
push @metadata_order, $prop;
- $metadata_props{$prop} = $record->luid . ' (' . $record->uuid . ")";
+ $immutable_props{$prop} = $record->luid . ' (' . $record->uuid . ")";
}
elsif (!(($prop eq 'id' or $prop eq 'created') && !$update)) {
push @metadata_order, $prop;
@@ -139,7 +120,7 @@
# we don't want to display id/created for ticket creates
# because they can't by their nature be specified until the
# ticket is actually created
- $metadata_props{$prop} = $update ? $record->prop($prop) : undef;
+ $immutable_props{$prop} = $update ? $record->prop($prop) : undef;
}
} else {
push @editable_order, $prop;
@@ -148,8 +129,8 @@
}
# fill in prop defaults if we're creating a new ticket
- unless ($update) {
- $record->default_props(\%metadata_props);
+ if (! $update) {
+ $record->default_props(\%immutable_props);
$record->default_props(\%editable_props);
}
@@ -159,31 +140,55 @@
$self->delete_arg('edit');
}
- # make undef values empty strings to avoid interpolation warnings
- # (we can't do this earlier because $record->default_props only
- # overrides undefined props)
- map { $metadata_props{$_} = '' if !defined($metadata_props{$_}) }
- @metadata_order;
- map { $editable_props{$_} = '' if !defined($editable_props{$_}) }
- @editable_order;
-
- my $metadata_separator = $self->create_separator(metadata_separator_text());
- my $editable_separator = $self->create_separator(editable_props_separator_text());
- my $comment_separator = $self->create_separator(comment_separator_text());
-
- my $metadata_props_string = join "\n",
- map { "$_: $metadata_props{$_}" } @metadata_order;
- my $editable_props_string = join "\n",
- map { "$_: $editable_props{$_}" } @editable_order;
+ my $immutable_props_string = $self->_build_kv_pairs(
+ order => \@metadata_order,
+ data => \%immutable_props
+ );
+ my $editable_props_string = $self->_build_kv_pairs(
+ order => \@editable_order,
+ data => \%editable_props
+ );
# glue all the parts together
- my $ticket_string = $metadata_separator . "\n\n" . $metadata_props_string
- . "\n\n" . $editable_separator . "\n\n" .
- $editable_props_string . "\n\n" . $comment_separator
- . "\n";
+ return join( "\n\n",
+
+ $self->_build_template_section(
+ header => metadata_separator,
+ data => $immutable_props_string
+ ),
+
+ $self->_build_template_section(
+ header => editable_props_separator,
+ data => $editable_props_string
+ ),
+ $self->_build_template_section(
+ header => comment_separator,
+ data => ''
+ )
+
+);
}
-=head2 parse_record_string $str
+sub _build_template_section {
+ my $self = shift;
+ my %args = validate (@_, { header => 1, data => 0 });
+ return $self->_build_separator($args{'header'}) ."\n\n". ( $args{data} || '');
+}
+
+sub _build_kv_pairs {
+ my $self = shift;
+ my %args = validate (@_, { order => 1, data => 1 });
+
+ my $string = '';
+ for my $prop ( @{$args{order}}) {
+ $string .= "$prop: ".($args{data}->{$prop} ||'') ."\n";
+ }
+ return $string;
+}
+
+
+
+=head2 parse_record_template $str
Takes a string containing a ticket record consisting of prop: value pairs
followed by a separator, followed by an optional comment.
@@ -193,7 +198,7 @@
=cut
-sub parse_record_string {
+sub parse_record_template {
my $self = shift;
my $ticket = shift;
@@ -203,21 +208,25 @@
my $comment = '';
for my $line (@lines) {
- if ($line =~ separator_pattern()) {
+ if ($line =~ separator_pattern) {
$last_seen_sep = $1;
- } elsif ($line =~ comment_pattern() or
- # skip comments and unchangeable props
- $last_seen_sep eq metadata_separator_text()) {
+ } elsif ($line =~ comment_pattern) {
+ # skip comments
+ next;
+ } elsif ( $last_seen_sep eq metadata_separator) {
+ # skip unchangeable props
next;
- } elsif ($last_seen_sep eq editable_props_separator_text()) {
+ } elsif ($last_seen_sep eq editable_props_separator) {
# match prop: value pairs. whitespace in between is ignored.
if ($line =~ m/^([^:]+):\s*(.*)$/) {
my $prop = $1;
my $val = $2;
$new_props{$prop} = $val unless !($val);
}
- } elsif ($last_seen_sep eq comment_separator_text()) {
+ } elsif ($last_seen_sep eq comment_separator) {
$comment .= $line . "\n";
+ } else {
+ # Throw away the section
}
}
More information about the Bps-public-commit
mailing list