This one is for using Jira on the command line, maybe also together with Emacs.
I still need a way to work with Jira, org-jira just can’t fulfill my needs anymore.
This is simple using Perl’s “reflection”.
$ENV{SELECT_HISTORY_ORDER} = 'jkd';
(my $sub_command = "jkd_" . shift) =~ s,-,_,g;
$ENV{PERL5LIB} = join(":", "$ENV{scm_common_libdir}/jkd.pm", $ENV{PERL5LIB});
if (not defined &$sub_command) {
my $lib_command;
for ("jkd/", "jkd.user/") {
($lib_command = $sub_command) =~ s,^jkd_,$_,;
$lib_command =~ s,_,-,g;
$lib_command = "$ENV{scm_common_libdir}/$lib_command";
if (-x $lib_command) {
do {
use IPC::System::Simple qw(run runx capture capturex $EXITVAL EXIT_ANY);
my @args = ("$lib_command", @ARGV);
if ($ENV{JKD_TRACE} eq 'true') {
@args = ('debug-run', @args);
}
die "Can't run jkd " . join(" ", shell_quote(@args)) if runx(EXIT_ANY, @args) != 0;
exit;
}
}
}
&$handler_help(1, "Can't find sub-command: $lib_command");
}
$sub_command = \&{$sub_command};
&$sub_command(@ARGV);
sub subcmd_help() {
my $top_help_str = <<~'EOF';
Usage: jkd [GLOBAL_OPTIONS]... SUB_COMMAND SUBCMD_ARGS...
Here's the list of sub-commands:
EOF
my @subcmd_help_strs;
my %subcmd_helpstr_map = (
e => "Edit jira issue in emacs org-mode",
tri => "Transition of an issue",
);
my %help_printed_map;
for my $subcmd ((sort {$a cmp $b} grep {m/^jkd_/} keys %::), (sort {$a cmp $b} keys %subcmd_helpstr_map)) {
(my $raw_subcmd = $subcmd) =~ s,^jkd_,,;
$subcmd = "jkd_$raw_subcmd";
if ($help_printed_map{$raw_subcmd}) {
next;
} else {
$help_printed_map{$raw_subcmd} = 1;
}
my $subcmd_help_str = $subcmd_helpstr_map{$raw_subcmd} ||
"NO DESCRIPTION.";
if (not defined &$subcmd) {
$subcmd_help_str .= " (NO DEFINITION)"
}
push @subcmd_help_strs, sprintf(" %s\n\t%s", $raw_subcmd, $subcmd_help_str);
}
return join "\n", $top_help_str, @subcmd_help_strs;
}
# Local Variables: #
# eval: (read-only-mode 1) #
# End: #
#!/usr/bin/env bash
# Given a page, I will edit this
#!/usr/bin/env perl
use strict;
use v5.10.1; # for say and switch
use autodie qw(:all);
use IPC::System::Simple qw(run runx capture capturex $EXITVAL EXIT_ANY);
binmode(STDOUT, ":utf8");
binmode(STDERR, ":utf8");
use Encode;
use utf8;
@ARGV = map {decode_utf8 $_} @ARGV;
use JSON;
my $json = JSON->new->utf8->canonical->pretty;
BEGIN {
push @INC, "$ENV{scm_common_libdir}/jkd.pm";
}
use jkd;
## start code-generator "^\\s *#\\s *"
# generate-getopt -P -s perl -p jkd \
# '?subcmd_help()' \
# u:username='"$ENV{scm_jira_user}"' '?"Login Username"' \
# p:password='"$ENV{scm_jira_password}"' '?"Login Password"' \
# j:jiraurl='"$ENV{scm_jira_url}"' '?"Jira URL (only FQDN, no / and such)"' \
# vverbose='"$ENV{jkd_verbose}"' '?"Verbose debug output"'
## end code-generator
## start generated code
use Getopt::Long;
Getopt::Long::Configure("posix_default");
my $jkd_jiraurl = "$ENV{scm_jira_url}";
my $jkd_password = "$ENV{scm_jira_password}";
my $jkd_username = "$ENV{scm_jira_user}";
my $jkd_verbose = "$ENV{jkd_verbose}";
my $handler_help = sub {
print subcmd_help();
print "\n\n选项和参数:\n";
printf "%6s", '-j, ';
printf "%-24s", '--jiraurl=JIRAURL';
if (length('--jiraurl=JIRAURL') > 24 and length("Jira URL (only FQDN, no / and such)") > 0) {
print "\n";
printf "%30s", "";
}
printf "%s", "Jira URL (only FQDN, no / and such)";
print "\n";
printf "%6s", '-p, ';
printf "%-24s", '--password=PASSWORD';
if (length('--password=PASSWORD') > 24 and length("Login Password") > 0) {
print "\n";
printf "%30s", "";
}
printf "%s", "Login Password";
print "\n";
printf "%6s", '-u, ';
printf "%-24s", '--username=USERNAME';
if (length('--username=USERNAME') > 24 and length("Login Username") > 0) {
print "\n";
printf "%30s", "";
}
printf "%s", "Login Username";
print "\n";
printf "%6s", '-v, ';
printf "%-24s", '--[no]verbose';
if (length('--[no]verbose') > 24 and length("Verbose debug output") > 0) {
print "\n";
printf "%30s", "";
}
printf "%s", "Verbose debug output";
print "\n";
my $exit_value = 0;
if (@_ && $_[0] ne "help" && $_[1] != 1) {
$exit_value = shift @_;
print "@_\n";
}
exit($exit_value);
};
GetOptions (
'jiraurl|j=s' => \$jkd_jiraurl,
'password|p=s' => \$jkd_password,
'username|u=s' => \$jkd_username,
'verbose|v!' => \$jkd_verbose,
'help|h!' => \&$handler_help,
);
## end generated code
use v5.10;
use String::ShellQuote;
if ($jkd_verbose) {
say STDERR "jkd ", shell_quote(@ARGV);
}
my $secret_conf;
use Config::GitLike;
my ($config_file) = $ENV{scm_secrets_conf};
if (-e $config_file) {
$secret_conf = Config::GitLike->load_file($config_file);
}
if (not $jkd_password) {
$jkd_password = $secret_conf->{"ldap.${jkd_username}.password"};
}
if (not $jkd_password) {
&$handler_help(1, "Must specify the jira password")
}
if ($jkd_jiraurl =~ m/^\w+$/) {
my $new_jkd_jiraurl = $ENV{"scm_jira_${jkd_jiraurl}_url"};
die "Can't find jira url scm_jira_${jkd_jiraurl}_url from env" unless $new_jkd_jiraurl;
$jkd_jiraurl = $new_jkd_jiraurl;
}
$ENV{scm_jira_url} = $jkd_jiraurl;
$ENV{scm_jira_user} = $jkd_username; # for lib scripts
$ENV{scm_jira_password} = $jkd_password;
$ENV{jkd_verbose} = $jkd_verbose;
if (not $jkd_username) {
$jkd_username = $secret_conf->{"jkd.username"};
}
if (not $jkd_username) {
say STDERR "Must specify the jira username";
&$handler_help(1);
}
if (not $jkd_jiraurl) {
$jkd_jiraurl = $secret_conf->{"jkd.jiraurl"};
}
if (not $jkd_jiraurl) {
say STDERR "Must specify the jira url";
&$handler_help(1);
}
use File::Path;
<<global-args>>
<<create-1-issue>>
<<comment-issue>>
<<mv-issue-to-sprint>>
<<transition-1-issue>>
<<subcmd-helps>>
<<which-to-call>>
<<read-only>>
# Local Variables: #
# eval: (read-only-mode 1) #
# End: #
use v5.10;
use HTTP::Request::Common;
use LWP::UserAgent;
use JSON;
use File::Path qw(make_path);
use File::Basename;
use Encode;
sub jkd_url_for_api($) {
(my $api_path = $_[0]) =~ s,^/,,;
my $auth_str = sprintf "%s:%s@", $jkd_username, $jkd_password;
(my $scm_jira_site = $jkd_jiraurl) =~ s,(https?://),$1$auth_str,;
my $url = "${scm_jira_site}${api_path}";
if ($jkd_verbose) {
say STDERR "api: $url";
}
return "$url";
}
sub get($) {
my $ua = LWP::UserAgent->new;
my $api = $_[0];
my $url = jkd_url_for_api($api);
for (1..3) {
my $response = $ua->request(GET $url);
if ($response->code != 200) {
die "Can't get $api: code is " . $response->code . ", url is $url";
}
if ($response->content eq "") {
say STDERR "empty response for $api? try: $_";
sleep($_ * $_);
next unless $_ == 3;
}
return $response;
}
}
sub jkd_get(@) {
## start code-generator "^\\s *#\\s *"
# generate-getopt -s perl -P a:api '?"for e.g., rest/api/2/project/"'
## end code-generator
## start generated code
use Getopt::Long;
Getopt::Long::Configure("posix_default");
my $api = "";
my $handler_help = sub {
print ;
print "\n\n选项和参数:\n";
printf "%6s", '-a, ';
printf "%-24s", '--api=API';
if (length('--api=API') > 24 and length("for e.g., rest/api/2/project/") > 0) {
print "\n";
printf "%30s", "";
}
printf "%s", "for e.g., rest/api/2/project/";
print "\n";
exit(0);
};
GetOptions (
'api|a=s' => \$api,
'help|h!' => \&$handler_help,
);
## end generated code
if (not $api) {
$api = $ARGV[0];
}
if ($api !~ m,rest/api/,) {
($api = "rest/api/2/$api") =~ s,/+,/,g;
}
if (not $api) {
die "Must specify the api with -a API";
}
my $response = get($api);
print decode_utf8($response->content);
}
sub jkd_get_issue_type_fields(@) {
## start code-generator "^\\s *#\\s *"
# generate-getopt -s perl -l p:project t:issue-type vverbose '?"print the json"'
## end code-generator
## start generated code
use Getopt::Long;
Getopt::Long::Configure("default");
local @ARGV = @_;
my $issue_type = "";
my $project = "";
my $verbose = 0;
my $handler_help = sub {
print ;
print "\n\n选项和参数:\n";
printf "%6s", '-t, ';
printf "%-24s", '--issue-type=ISSUE-TYPE';
if (length('--issue-type=ISSUE-TYPE') > 24 and length() > 0) {
print "\n";
printf "%30s", "";
}
printf "%s", ;
print "\n";
printf "%6s", '-p, ';
printf "%-24s", '--project=PROJECT';
if (length('--project=PROJECT') > 24 and length() > 0) {
print "\n";
printf "%30s", "";
}
printf "%s", ;
print "\n";
printf "%6s", '-v, ';
printf "%-24s", '--[no]verbose';
if (length('--[no]verbose') > 24 and length("print the json") > 0) {
print "\n";
printf "%30s", "";
}
printf "%s", "print the json";
print "\n";
exit(0);
};
GetOptions (
'issue-type|t=s' => \$issue_type,
'project|p=s' => \$project,
'verbose|v!' => \$verbose,
'help|h!' => \&$handler_help,
);
## end generated code
if (not $issue_type or not $issue_type =~ m/^\d+$/) {
$issue_type = jkd_select_issue_type("-p", "$project", "-t", $issue_type);
}
my $issue_fields_resp = get("rest/api/2/issue/createmeta?projectKeys=${project}&issuetypeIds=${issue_type}&expand=projects.issuetypes.fields");
print $issue_fields_resp->content if $verbose;
return decode_json $issue_fields_resp->content;
}
use File::Slurp;
sub org_to_jira($) {
my $text = shell_quote($_[0]);
return scalar decode_utf8(qx(ejwo --o2j --text $text </dev/null));
}
sub jira_to_org($) {
my $text = shell_quote($_[0]);
return scalar decode_utf8(qx(ejwo --j2o --text $text </dev/null));
}
sub work_with_all_fields($$\%$) {
my ($project_id, $issue_type_id, $required_fields, $work_options) = @_;
my $edit_issue_json_obj = $work_options->{edit_issue_json_obj};
my $print_schemes = $work_options->{"print-schemes"};
my $issue_fields_obj = jkd_get_issue_type_fields("-p", "$project_id", "-t", "$issue_type_id");
for my $project (@{$issue_fields_obj->{projects}}) {
for my $it (@{$project->{issuetypes}}) {
if ($it->{id} != $issue_type_id) {
next;
}
my @fields_to_edit;
if ($edit_issue_json_obj) {
my @command = (
"select-args-n", "-p", "请输入你想要编辑的域",
map {
sprintf "%s: %s", $_, $it->{fields}{$_}{name}
}
grep {
$it->{fields}{$_}{required}
} sort keys %{$it->{fields}}
);
my $command = join(" ", shell_quote(@command));
my $values = decode_utf8 qx($command);
$values =~ s,:.*,,mg;
@fields_to_edit = split(" ", $values);
say STDERR "fields_to_edit is @fields_to_edit";
} else {
@fields_to_edit = sort keys %{$it->{fields}};
}
$required_fields->{assignee} = {name => $ENV{scm_jira_user}} if exists $it->{fields}{assignee} and not $edit_issue_json_obj;
for my $field_key (@fields_to_edit) {
my $field_name = ($it->{fields}{$field_key}{name} or "$field_key (field has no name)");
say STDERR "field ${field_name}'s value is ", ($edit_issue_json_obj->{fields}{$field_key} || "");
if ($print_schemes) {
print "--field-value $field_name= ";
next;
}
if ($required_fields->{$field_name}) {
$required_fields->{$field_key} = $required_fields->{$field_name};
delete $required_fields->{$field_name} unless ${field_key} eq ${field_name};
next;
}
if ($it->{fields}{$field_key}{required}) {
if ($field_key eq 'project' || $field_key eq 'issuetype') {
next;
}
my $schema_type = $it->{fields}{$field_key}{schema}{type};
my %selection_types = (
array => 1,
option => 1,
);
if ($schema_type eq 'string') {
my $init_text = ($edit_issue_json_obj->{fields}{$field_key} || $required_fields->{$field_key});
if ($field_key eq "description") {
say STDERR "special treatment for description";
$init_text = jira_to_org $init_text;
}
my @command = (
"ask-for-input-with-emacs", "-p", sprintf("Please input the %s (field key: %s)", $field_name, $field_key),
"--init-text", $init_text
);
my $command = join(" ", shell_quote(@command));
say STDERR "command is $command";
my $result_text = decode_utf8 qx($command);
if ($field_key eq "description") {
$result_text = org_to_jira $result_text;
}
$required_fields->{$field_key} = $result_text;
} elsif ($selection_types{$schema_type}) {
my %allowed_values_map;
map {
my $key = $_->{value} || $_->{name};
$allowed_values_map{$key} = $_->{id}} @{$it->{fields}{$field_key}{allowedValues}};
my $select_command;
if ($schema_type eq "array") {
$select_command = "select-args-n";
} else {
$select_command = "select-args";
}
my @command = (
$select_command, "-p", ("请输入你想要选择的 " . "$field_name"),
keys %allowed_values_map
);
my $command = join(" ", shell_quote(@command));
my $values = decode_utf8 qx($command);
$required_fields->{$field_key} = [] if $schema_type eq "array";
for (split "\n", $values) {
next unless $_;
say "Adding option for $field_name: ", $_;
die "invalid $_" unless ${allowed_values_map{$_}};
if ($schema_type eq "array") {
push @{$required_fields->{$field_key}}, {id => $allowed_values_map{$_}};
} else {
$required_fields->{$field_key} = {id => $allowed_values_map{$_}};
}
}
} else {
my $jsonParser = JSON->new->allow_nonref;
my $jsonText = decode_utf8($json->encode($it->{fields}{$field_key}));
say <<EOF;
$jsonText
Don't know how to deal with ${field_name}, please input with json.
EOF
if ($field_key eq "reporter") {
$required_fields->{$field_key} = "$ENV{scm_jira_user}";
} else {
while (1) {
$required_fields->{$field_key} = eval 'decode_json(qx(ask-for-input -p "what is your input json for $field_key?"))';
last unless $@;
}
}
}
}
}
}
}
if ($print_schemes) {
exit;
}
}
sub get_issue_type($$$) {
my ($projects_issuetypes, $project_id, $issue_type_id) = @_;
for (@{$projects_issuetypes->{projects}}) {
if ($_->{id} == $project_id || $_->{key} eq $project_id) {
for (@{$_->{issuetypes}}) {
if ($_->{id} == $issue_type_id) {
return $_;
}
}
}
}
}
sub jkd_e(@) {
## start code-generator "^\\s *#\\s *"
# generate-getopt -s perl i:issue-to-edit f:field-to-edit @:fields-json='"{}"'
## end code-generator
## start generated code
use Getopt::Long;
Getopt::Long::Configure("default");
my $field_to_edit = "";
my $fields_json = "{}";
my $issue_to_edit = "";
my $handler_help = sub {
print ;
print "\n\n选项和参数:\n";
printf "%6s", '-f, ';
printf "%-24s", '--field-to-edit=FIELD-TO-EDIT';
if (length('--field-to-edit=FIELD-TO-EDIT') > 24 and length() > 0) {
print "\n";
printf "%30s", "";
}
printf "%s", ;
print "\n";
printf "%6s", '';
printf "%-24s", '--fields-json=FIELDS-JSON';
if (length('--fields-json=FIELDS-JSON') > 24 and length() > 0) {
print "\n";
printf "%30s", "";
}
printf "%s", ;
print "\n";
printf "%6s", '-i, ';
printf "%-24s", '--issue-to-edit=ISSUE-TO-EDIT';
if (length('--issue-to-edit=ISSUE-TO-EDIT') > 24 and length() > 0) {
print "\n";
printf "%30s", "";
}
printf "%s", ;
print "\n";
exit(0);
};
GetOptions (
'field-to-edit|f=s' => \$field_to_edit,
'fields-json=s' => \$fields_json,
'issue-to-edit|i=s' => \$issue_to_edit,
'help|h!' => \&$handler_help,
);
## end generated code
if (not $issue_to_edit) {
die "You must specify -issue-to-edit";
}
my $json_issue = decode_json get("rest/api/2/issue/$issue_to_edit")->content;
my $issue_type_id = $json_issue->{fields}{issuetype}{id};
my $issue_project = $json_issue->{fields}{project}{key};
my $issue_fields_obj = jkd_get_issue_type_fields("-p", "$issue_project", "-t", "$issue_type_id");
my %edited_fields;
if ($fields_json eq "{}") {
work_with_all_fields (
$issue_project, $issue_type_id, %edited_fields,
{
edit_issue_json_obj => $json_issue,
}
);
} else {
my $issue_fields_obj = decode_json get("rest/api/2/issue/${issue_to_edit}?expand=names")->content;
my $fields_json_obj = decode_json(encode_utf8($fields_json));
update_names_with_fields($fields_json_obj, $issue_fields_obj->{names});
%edited_fields = %$fields_json_obj;
}
my $ua = LWP::UserAgent->new;
for my $try (1..3) {
my $request = PUT jkd_url_for_api("rest/api/2/issue/$issue_to_edit"),
'Content-Type' => 'application/json',
'Accept' => 'application/json',
"charset" => "utf-8",
Content => encode_json {
fields => \%edited_fields
};
my $response = $ua->request($request);
say "PUT \@${try} response code:" . $response->code, "result: ", decode_utf8($response->content);
last if $response->is_success;
my $errors = decode_json($response->content)->{errors};
for (keys %$errors) {
# die "Can't find $_" unless $required_fields{$_};
say STDERR "Delete $_ and try again";
delete $edited_fields{$_};
}
}
}
sub jkd_resolve(@) {
## start code-generator "^\\s *#\\s *"
# generate-getopt -s perl -P i:issue-to-edit r:resolution
## end code-generator
## start generated code
use Getopt::Long;
Getopt::Long::Configure("posix_default");
my $issue_to_edit = "";
my $resolution = "";
my $handler_help = sub {
print ;
print "\n\n选项和参数:\n";
printf "%6s", '-i, ';
printf "%-24s", '--issue-to-edit=ISSUE-TO-EDIT';
if (length('--issue-to-edit=ISSUE-TO-EDIT') > 24 and length() > 0) {
print "\n";
printf "%30s", "";
}
printf "%s", ;
print "\n";
printf "%6s", '-r, ';
printf "%-24s", '--resolution=RESOLUTION';
if (length('--resolution=RESOLUTION') > 24 and length() > 0) {
print "\n";
printf "%30s", "";
}
printf "%s", ;
print "\n";
exit(0);
};
GetOptions (
'issue-to-edit|i=s' => \$issue_to_edit,
'resolution|r=s' => \$resolution,
'help|h!' => \&$handler_help,
);
## end generated code
my $ua = LWP::UserAgent->new;
my $request = PUT jkd_url_for_api("rest/api/2/issue/$issue_to_edit"),
'Content-Type' => 'application/json',
'Accept' => 'application/json',
"charset" => "utf-8",
Content => encode_json {
fields => {
resolution => {
id => 10300
}
}
};
my $response = $ua->request($request);
say "PUT response code:" . $response->code, "result: ", decode_utf8($response->content);
}
sub jkd_c(@) { # create issue
if ($ENV{JKD_TRACE} eq "true") {
runx("debug-run", "log", "jkd", "c", @_);
}
## start code-generator "^\\s *#\\s *"
# generate-getopt -s perl -l \
# p:project \
# t:issue-type '?"指定要创建的 issue 类型,比如 bug、feature、story 等(取决于 project)"' \
# @assign-to-myself=1 \
# @:field-value='()' '?"可指定多次。格式为简单的 name=value。不支持复杂的数据"' \
# @print-schemes \
# @:fields-json
## end code-generator
## start generated code
use Getopt::Long;
Getopt::Long::Configure("default");
local @ARGV = @_;
my $assign_to_myself = 1;
my @field_value = ();
my $fields_json = "";
my $issue_type = "";
my $print_schemes = 0;
my $project = "";
my $handler_help = sub {
print ;
print "\n\n选项和参数:\n";
printf "%6s", '';
printf "%-24s", '--[no]assign-to-myself';
if (length('--[no]assign-to-myself') > 24 and length() > 0) {
print "\n";
printf "%30s", "";
}
printf "%s", ;
print "\n";
printf "%6s", '';
printf "%-24s", '--field-value=FIELD-VALUE';
if (length('--field-value=FIELD-VALUE') > 24 and length("可指定多次。格式为简单的 name=value。不支持复杂的数据") > 0) {
print "\n";
printf "%30s", "";
}
printf "%s", "可指定多次。格式为简单的 name=value。不支持复杂的数据";
print "\n";
printf "%6s", '';
printf "%-24s", '--fields-json=FIELDS-JSON';
if (length('--fields-json=FIELDS-JSON') > 24 and length() > 0) {
print "\n";
printf "%30s", "";
}
printf "%s", ;
print "\n";
printf "%6s", '-t, ';
printf "%-24s", '--issue-type=ISSUE-TYPE';
if (length('--issue-type=ISSUE-TYPE') > 24 and length("指定要创建的 issue 类型,比如 bug、feature、story 等(取决于 project)") > 0) {
print "\n";
printf "%30s", "";
}
printf "%s", "指定要创建的 issue 类型,比如 bug、feature、story 等(取决于 project)";
print "\n";
printf "%6s", '';
printf "%-24s", '--[no]print-schemes';
if (length('--[no]print-schemes') > 24 and length() > 0) {
print "\n";
printf "%30s", "";
}
printf "%s", ;
print "\n";
printf "%6s", '-p, ';
printf "%-24s", '--project=PROJECT';
if (length('--project=PROJECT') > 24 and length() > 0) {
print "\n";
printf "%30s", "";
}
printf "%s", ;
print "\n";
my $exit_value = 0;
if (@_ && $_[0] ne "help" && $_[1] != 1) {
$exit_value = shift @_;
print "@_\n";
}
exit($exit_value);
};
GetOptions (
'assign-to-myself!' => \$assign_to_myself,
'field-value=s' => \@field_value,
'fields-json=s' => \$fields_json,
'issue-type|t=s' => \$issue_type,
'print-schemes!' => \$print_schemes,
'project|p=s' => \$project,
'help|h!' => \&$handler_help,
);
## end generated code
if (not $project) {
$project = capturex("jkd", "select-project");
}
my $issue_type_json;
if (not $issue_type or not $issue_type =~ m/^\d+$/) {
$issue_type_json = from_json(capturex("jkd", "select-issuetype", "-i", "$issue_type", "-p", "${project}", "--json-data"));
$issue_type = $issue_type_json->{id};
}
my %required_fields;
for (@field_value) {
if (m/(.*?)=(.*)/s) {
my ($field, $value) = ($1, $2);
$required_fields{$field} = $value;
} else {
die "$_ not format of FIELD=VALUE?"
}
}
if ($fields_json) {
my $required_fields = $json->decode(scalar capturex("jkd", "customfield-json-names2ids", "-n", ("$fields_json"), "-f", decode_utf8(to_json($issue_type_json->{fields}))));
%required_fields = %$required_fields;
$required_fields{project} = {
key => $project,
};
$required_fields{issuetype} = {
id => $issue_type,
};
} else {
work_with_all_fields($project, $issue_type, %required_fields, {"print-schemes" => ${print_schemes}});
$required_fields{project} = {
key => $project,
};
$required_fields{issuetype} = {
id => $issue_type,
};
}
say STDERR "required_fields is " . decode_utf8($json->encode(\%required_fields)) if $jkd_verbose;
if ($assign_to_myself) {
$required_fields{assignee} = {
name => $ENV{scm_jira_user}
} unless exists $required_fields{assignee};
}
my $ua = LWP::UserAgent->new;
say "json is:\n", decode_utf8($json->encode({ fields => \%required_fields })) if $jkd_verbose;
for (my $try = 0; $try < 3; $try++) {
my $request = POST jkd_url_for_api("rest/api/2/issue"),
'Content-Type' => 'application/json',
'Accept' => 'application/json',
"charset" => "utf-8",
Content => encode_json {
fields => \%required_fields,
};
my $response = $ua->request($request);
say STDERR "POST \@${try} response code:" . $response->code, "result: ", decode_utf8($response->content);
if ($response->is_success){
print decode_utf8($response->content);
exit 0;
}
my $errors = decode_json($response->content)->{errors};
for (keys %$errors) {
# die "Can't find $_" unless $required_fields{$_};
say STDERR "Delete $_ and try again";
delete $required_fields{$_};
}
}
die "Can't create issue";
}
如果没有写明 transition,就让用户选择当前所有的可能的 transition
use v5.10;
sub jkd_comment(@) {
## start code-generator "^\\s *#\\s *"
# generate-getopt -l -s perl i:issue c:comment @once '?"以前已经添加过的 comment,就不会再重复添加了"'
## end code-generator
## start generated code
use Getopt::Long;
Getopt::Long::Configure("default");
local @ARGV = @_;
my $comment = "";
my $issue = "";
my $once = 0;
my $handler_help = sub {
print ;
print "\n\n选项和参数:\n";
printf "%6s", '-c, ';
printf "%-24s", '--comment=COMMENT';
if (length('--comment=COMMENT') > 24 and length() > 0) {
print "\n";
printf "%30s", "";
}
printf "%s", ;
print "\n";
printf "%6s", '-i, ';
printf "%-24s", '--issue=ISSUE';
if (length('--issue=ISSUE') > 24 and length() > 0) {
print "\n";
printf "%30s", "";
}
printf "%s", ;
print "\n";
printf "%6s", '';
printf "%-24s", '--[no]once';
if (length('--[no]once') > 24 and length("以前已经添加过的 comment,就不会再重复添加了") > 0) {
print "\n";
printf "%30s", "";
}
printf "%s", "以前已经添加过的 comment,就不会再重复添加了";
print "\n";
exit(0);
};
GetOptions (
'comment|c=s' => \$comment,
'issue|i=s' => \$issue,
'once!' => \$once,
'help|h!' => \&$handler_help,
);
## end generated code
if ($once) {
my $jira_issue = $json->decode(scalar capture("jkd rest issue/$issue"));
for (@{$jira_issue->{fields}{comment}{comments}}) {
if ($_->{body} eq "$comment") {
say "$comment already exists";
exit 0;
}
}
}
my $ua = LWP::UserAgent->new;
my $request = PUT jkd_url_for_api("rest/api/2/issue/${issue}"),
'Content-Type' => 'application/json',
'Accept' => 'application/json',
"charset" => "utf-8",
Content => encode_json {
update => {
comment => [
{
add =>
{
body => $comment
}
}
]
}
};
my $res = $ua->request($request);
say "PUT res code:" . $res->code, "result: ", $res->content;
die sprintf("invalid request result: code = %d, content = '%s', for request: %s", $res->code, $res->content, decode_utf8($request->as_string)) if ($res->code < 200 or $res->code >= 300);
}
sub jkd_get_comment(@) {
## start code-generator "^\\s *#\\s *"
# generate-getopt -s perl -l -P i:issue n:nth-comment=-1 c:comment '?"如果指定,在注释中找到此参数的话,即退出"'
## end code-generator
## start generated code
use Getopt::Long;
Getopt::Long::Configure("posix_default");
local @ARGV = @_;
my $comment = "";
my $issue = "";
my $nth_comment = -1;
my $handler_help = sub {
print ;
print "\n\n选项和参数:\n";
printf "%6s", '-c, ';
printf "%-24s", '--comment=COMMENT';
if (length('--comment=COMMENT') > 24 and length("如果指定,在注释中找到此参数的话,即退出") > 0) {
print "\n";
printf "%30s", "";
}
printf "%s", "如果指定,在注释中找到此参数的话,即退出";
print "\n";
printf "%6s", '-i, ';
printf "%-24s", '--issue=ISSUE';
if (length('--issue=ISSUE') > 24 and length() > 0) {
print "\n";
printf "%30s", "";
}
printf "%s", ;
print "\n";
printf "%6s", '-n, ';
printf "%-24s", '--nth-comment=NTH-COMMENT';
if (length('--nth-comment=NTH-COMMENT') > 24 and length() > 0) {
print "\n";
printf "%30s", "";
}
printf "%s", ;
print "\n";
exit(0);
};
GetOptions (
'comment|c=s' => \$comment,
'issue|i=s' => \$issue,
'nth-comment|n=s' => \$nth_comment,
'help|h!' => \&$handler_help,
);
## end generated code
my $jira_issue = $json->decode(scalar capture("jkd rest issue/$issue"));
if ($comment) {
for (@{$jira_issue->{fields}{comment}{comments}}) {
if ($_->{body} eq "$comment") {
exit 0;
}
}
exit 1;
}
print $jira_issue->{fields}{comment}{comments}[$nth_comment]{body};
}
use HTTP::Request::Common;
use LWP::UserAgent;
use JSON;
sub jkd_mits(@) {
## start code-generator "^\\s *#\\s *"
# generate-getopt -s perl -l i:issue s:sprint b:board
## end code-generator
## start generated code
use Getopt::Long;
Getopt::Long::Configure("default");
local @ARGV = @_;
my $board = "";
my $issue = "";
my $sprint = "";
my $handler_help = sub {
print ;
print "\n\n选项和参数:\n";
printf "%6s", '-b, ';
printf "%-24s", '--board=BOARD';
if (length('--board=BOARD') > 24 and length() > 0) {
print "\n";
printf "%30s", "";
}
printf "%s", ;
print "\n";
printf "%6s", '-i, ';
printf "%-24s", '--issue=ISSUE';
if (length('--issue=ISSUE') > 24 and length() > 0) {
print "\n";
printf "%30s", "";
}
printf "%s", ;
print "\n";
printf "%6s", '-s, ';
printf "%-24s", '--sprint=SPRINT';
if (length('--sprint=SPRINT') > 24 and length() > 0) {
print "\n";
printf "%30s", "";
}
printf "%s", ;
print "\n";
exit(0);
};
GetOptions (
'board|b=s' => \$board,
'issue|i=s' => \$issue,
'sprint|s=s' => \$sprint,
'help|h!' => \&$handler_help,
);
## end generated code
use v5.10;
if (not $sprint) {
if (not $board or $board !~ m/^\d+$/) {
my $json_boards = decode_json get("rest/agile/1.0/board/")->content;
$board = select_args("-i", $board, "-p", "which board do you want? (should be scrum, not kanban)", sort {$a cmp $b} map {sprintf "%s: %s", $_->{id}, $_->{name}} @{$json_boards->{values}});
$board =~ s,:.*,,;
}
if ($board) {
my $json_sprints = decode_json get("rest/agile/1.0/board/$board/sprint")->content;
$sprint = $json_sprints->{values}[-1]{id};
} else {
die "Must specify one of sprint or board, when using board, the last sprint will be used";
}
}
my $ua = LWP::UserAgent->new;
my $request = POST jkd_url_for_api("/rest/agile/1.0/sprint/${sprint}/issue"),
'Content-Type' => 'application/json',
'Accept' => 'application/json',
"charset" => "utf-8",
Content => encode_json {
issues => [
"$issue"
]
};
my $response = $ua->request($request);
say "POST response code:" . $response->code, "result: ", decode_utf8($response->content);
}