summaryrefslogtreecommitdiff
path: root/Master/texmf-dist/source/latex/proposal/bin
diff options
context:
space:
mode:
authorKarl Berry <karl@freefriends.org>2016-04-15 21:38:41 +0000
committerKarl Berry <karl@freefriends.org>2016-04-15 21:38:41 +0000
commit5e77de80c3619d378c8a85ab0e2413086a57a8a5 (patch)
tree485e88bc2ff057b1286d2d244b8c6afb2ac16fd8 /Master/texmf-dist/source/latex/proposal/bin
parentdac3dfcfa52f854fa71354a5f4c8c8bb3aa4bc43 (diff)
proposal (15apr16)
git-svn-id: svn://tug.org/texlive/trunk@40538 c570f23f-e606-0410-a88d-b1316a301751
Diffstat (limited to 'Master/texmf-dist/source/latex/proposal/bin')
-rw-r--r--Master/texmf-dist/source/latex/proposal/bin/Modparse.pm189
-rw-r--r--Master/texmf-dist/source/latex/proposal/bin/README32
-rwxr-xr-xMaster/texmf-dist/source/latex/proposal/bin/checksum71
-rwxr-xr-xMaster/texmf-dist/source/latex/proposal/bin/filedate45
-rwxr-xr-xMaster/texmf-dist/source/latex/proposal/bin/generate-issues470
5 files changed, 807 insertions, 0 deletions
diff --git a/Master/texmf-dist/source/latex/proposal/bin/Modparse.pm b/Master/texmf-dist/source/latex/proposal/bin/Modparse.pm
new file mode 100644
index 00000000000..2fd732b063d
--- /dev/null
+++ b/Master/texmf-dist/source/latex/proposal/bin/Modparse.pm
@@ -0,0 +1,189 @@
+#######################################################################################
+# A module for loading .tex files and feeding the data to the various applications. #
+# Copyright (c) 2005, Ioan Sucan, released under the Gnu General Public License (GPL) #
+# see http://www.gnu.org/copyleft/gpl.html #
+# $URL: svn://kwarc.eecs.iu-bremen.de/repos/kwarc/projects/content/bin/Modparse.pm$ #
+# $Date: 2005-09-26 12:44:28 -0500 (Mon, 26 Sep 2005) $ $Rev: 4243 $ #
+#######################################################################################
+
+package Modparse;
+
+use File::Basename;
+use File::Path;
+
+# if both snippath and snippathList are specified,
+# snippathList is ignored !
+
+# if a default snippath definition file is found
+# (snippath.def) it is used if no snippath is given
+# as argument;
+sub new {
+ my ($class, %options) = @_;
+
+ $options{recursion} = 1 unless defined $options{recursion};
+ $options{requireStatus} = 'include|exclude' unless defined $options{requireStatus};
+ $options{loadInputs} = 1 unless defined $options{loadInputs};
+ $options{onlyModuleLines} = 0 unless defined $options{onlyModuleLines};
+
+ if ( !(%{$options{snippath}}) && (-e "snippath.def") ) {
+ open(SNIPPATHDEF, 'snippath.def');
+ my @snlist = <SNIPPATHDEF>;
+ close(SNIPPATHDEF);
+ for (my $i = 0 ; $i <= $#snlist; $i++) { chomp($snlist[$i]); }
+ %{$options{snippath}} = @snlist; }
+ @{$options{snippathList}} = keys(%{$options{snippath}})
+ if ($options{snippath} && %{$options{snippath}});
+ $options{debug} = 0;
+
+ bless(\%options, $class); }
+
+sub execute{
+ my ($self, $file) = @_;
+
+ local *run_event = sub {
+ my ($event, $arg) = @_;
+ if ($self->{$event}) { $self->{$event}($arg); }
+ };
+
+ local *go = sub {
+ my ($location, %param) = @_;
+ my ($tex, $path, @tex_data) = $self->readLocation($location);
+ return unless @tex_data;
+ my $NL = $#tex_data+1;
+
+ $param{filename} = $tex;
+ $param{depth}++;
+ $param{filecount}++;
+ my $i = 0;
+
+ $param{nextline} = sub { $tex_data[++$i]; };
+ $param{prevline} = sub { $tex_data[--$i]; };
+ $param{rewriteline}= sub { $tex_data[$i] = $_[0]; };
+
+ print "Processing $tex...\n" if $self->{verbose};
+ run_event('onBeginFile', \%param);
+
+ while ($i<$NL){
+ $param{original} = $tex_data[$i];
+ $_ = $tex_data[$i]; chomp; /([^%]*)((%.*)?)/;
+ ($param{tex}, $param{comment}) = ($1, $2); $_ = $1;
+ s/\s+//g; $param{simple_tex} = $_;
+
+ if (/\\begin\{module\}(\[([^\]]*)\])?/) {
+ if ($param{in_module}) {
+ push(@{$param{module_stack}}, [$param{module_id}, $param{module_arg}]); }
+ $param{in_module} = 1;
+ $param{module_args} = $1 ? $2 : undef;
+ $param{module_id} = ($param{module_args} &&
+ $param{module_args} =~ /id=([^\],]+)/) ? $1 : undef;
+ $param{module_nesting}++;
+ run_event('onBeginModule', \%param); }
+
+ if (/\\end\{module\}/) {
+ run_event('onEndModule', \%param);
+ if ($#{$param{module_stack}} >= 0) {
+ ($param{module_id}, $param{module_args}) = @{pop(@{$param{module_stack}})};
+ } else {
+ $param{in_module} = 0;
+ ($param{module_id}, $param{module_args}) = (undef, undef); }
+ $param{module_nesting}--;
+ }
+
+ foreach my $srch_snippath(@{$self->{snippathList}}){
+ $self->{snippath}{$srch_snippath} = $1
+ if (/\\def\\$srch_snippath\#1\{([^\#]+)\#1\}$/); }
+
+ if ($self->{follow} && (/\\((requiremodules(\[($self->{requireStatus})\])?)|input|include)\{([^\}]+)\}/)){
+ my $req_filename = $5; my $req_spath = ''; my $command = $1;
+ my $is_require = ($command =~ /requiremodules/) ? 1 : 0;
+ if ($is_require || $self->{loadInputs}) {
+ foreach my $srch_snippath(@{$self->{snippathList}}){
+ if ($req_filename =~ /\\$srch_snippath\{(.+)/){
+ ($req_spath, $req_filename) = ($self->{snippath}{$srch_snippath}, $1);
+ last; }}
+ run_event('onRecurse', \%param);
+ if ($self->{recursion} || !$is_require) {
+ go($self->makeLocation($req_filename, $path, $req_spath), %param); }
+ }
+ }
+ run_event('onEveryLine', \%param) if ($param{in_module} || !$self->{onlyModuleLines});
+
+ last if (/\\end\{document\}/ && $self->{stopAtEnd});
+ $i++;
+ }
+ run_event('onEndFile', \%param);
+ $param{depth}--; return;
+ };
+
+ my %param = (pack=>$self, perm=>$self->{_AUX_DATA}, depth=>0, filecount=>0,
+ in_module=>0, module_nesting=>0);
+ run_event('onBegin', \%param);
+ go($self->makeLocation($file), %param);
+ run_event('onEnd', \%param);
+ return;
+}
+
+
+# add a [.tex] extension to the filename
+sub add_ext{
+ my ($self, $fn, $ext) = @_;
+ $ext = '.tex' unless $ext;
+ $fn.=$ext if $fn !~ /\Q$ext\E$/;
+ $fn; }
+
+# as long as the extension is .tex, drop it
+sub drop_ext{
+ my ($self, $filename) = @_;
+ while ($filename =~ /\.tex$/){
+ while ($filename !~ /\.$/ ) { chop($filename); }
+ chop($filename); }
+ $filename; }
+
+sub new_ext{
+ my ($self, $filename, $ext) = @_;
+ $self->add_ext($self->drop_ext($filename), $ext); }
+
+# location = pointer to array of strings
+# first element is filename; next are possible paths
+sub makeLocation{ my $self = shift; return \@_; }
+
+# read a location into an array of strings
+# aslo return filename and path to it (just in
+# case some changes were made to it, like adding
+# extension and/or relative paths)
+sub readLocation{
+ my ($self, $location) = @_;
+
+ my ($filename, @paths) = @{$location};
+ # try to add proper extension;
+ my $filename_ext = $self->new_ext($filename) unless -e $filename;
+ $filename = $filename_ext if $filename_ext && -e $filename_ext;
+
+ # try search paths
+ if (! -e $filename){
+ my $all_p = '';
+ foreach my $p(@paths){
+ $all_p .= $p;
+ $filename = $all_p.$filename and last if -e $all_p.$filename;
+ $filename = $p.$filename and last if -e $p.$filename;
+ if ($filename_ext){
+ $filename = $all_p.$filename_ext and last if -e $all_p.$filename_ext;
+ $filename = $p.$filename_ext and last if -e $p.$filename_ext; }
+ }}
+ # don't do the same work too many times
+ foreach my $file(@{$self->{loadedFiles}})
+ { return undef if $file eq $filename; }
+
+ push(@{$self->{loadedFiles}}, $filename);
+ open(FIN, $filename) or die "Cannot open '$filename'\n";
+ my @data = <FIN>;
+ close(FIN);
+
+ # find path of current file
+ my $path = dirname($filename);
+ $path.='/' if ($path ne '');
+
+ return ($filename, $path, @data);
+}
+
+1;
diff --git a/Master/texmf-dist/source/latex/proposal/bin/README b/Master/texmf-dist/source/latex/proposal/bin/README
new file mode 100644
index 00000000000..e33b26bf256
--- /dev/null
+++ b/Master/texmf-dist/source/latex/proposal/bin/README
@@ -0,0 +1,32 @@
+This directory contains the binaries for the proposal class.
+
+We have three utilities:
+- filedate and checksum are for managing metadata in the
+ DTX files of the sTeX distribution (written in perl)
+- Modparse.pm is a perl library they depend on
+- generate-issues is a ruby library that generates issues
+ on http://github.com for the project (experimental)
+
+============== Details =================
+
+checksum
+ "enable, update, and disable checksums in DTX files"
+
+Usage:
+ checksum [options]
+
+Options:
+ -- disable disable the checksum by wrapping in \iffalse ... \fi
+ -- enable remove those again
+ -- update update to the correct checksum
+
+
+filedate <input filename> <newdate>
+
+Purpose:
+ Update the dates for a \ProvidePackage invocation in a given .dtx source
+
+Example:
+ filedate omdoc.dtx
+
+
diff --git a/Master/texmf-dist/source/latex/proposal/bin/checksum b/Master/texmf-dist/source/latex/proposal/bin/checksum
new file mode 100755
index 00000000000..9f0e533a8d4
--- /dev/null
+++ b/Master/texmf-dist/source/latex/proposal/bin/checksum
@@ -0,0 +1,71 @@
+#!/usr/bin/perl -w
+#######################################################################################
+# A tool for updating .dtx files with a correspodning checksum counter #
+# Copyright (c) 2010, Deyan Ginev, released under the Gnu General Public License (GPL)#
+# see http://www.gnu.org/copyleft/gpl.html #
+# $URL: https://svn.kwarc.info/repos/stex/trunk/bin/checksum$ #
+#######################################################################################
+
+use strict;
+
+use Getopt::Long;
+use Modparse;
+use Pod::Usage;
+use Cwd qw(abs_path);
+
+####### start of program #######
+my $mode = "update"; #Default is update
+my $action = { "update" => \&update, "disable" => \&disable, "enable"=>\&enable };
+GetOptions("disable" => sub { $mode="disable"; },
+ "enable" => sub { $mode="enable"; },
+ "update" => sub { $mode="update"; });
+
+
+my ($path) = @ARGV;
+$path = abs_path($path);
+my ($volume,$dir,$file) = File::Spec->splitpath( $path );
+my @lines = `cd $dir; pdflatex "\\nonstopmode\\input{$file}"` if ($mode eq "update");
+my ($checksum) = map {$_=~/^\* The checksum should be (\d+)!/; $1;} grep ($_ =~ /^\* The checksum should be (\d+)!/,@lines) if @lines;
+if (!$checksum) {
+#One more possible error message:
+ ($checksum) = map {$_=~/^! Package doc Error: Checksum not passed \((\d+)<>(\d+)\)\./; $2;} grep ($_ =~ /^! Package doc Error: Checksum not passed \((\d+)<>(\d+)\)\./,@lines) if @lines;
+}
+open(IN,"<$path") or die "Cannot open DTX source: $path\n";
+@lines = ();
+while (<IN>) {
+ push @lines, &{$$action{$mode}}($_,$checksum);
+}
+close(IN);
+open(OUT,">$path") or die "Cannot write to DTX target: $path\n";
+print OUT join("",@lines);
+close(OUT);
+######### Subroutines ############
+sub update {
+ my $checksum=$_[1];
+ $_[0]=~s/\\CheckSum\{\d*\}/\\CheckSum{$checksum}/ if $checksum;
+ $_[0];
+}
+
+sub disable {
+ $_[0]=~s/\\CheckSum\{(\d*)\}/\\iffalse\\CheckSum\{$1}\\fi/ unless $_[0]=~/\\iffalse\\CheckSum\{(\d*)\}\\fi/;
+ $_[0];
+}
+
+sub enable {
+ $_[0]=~s/\\iffalse\\CheckSum\{(\d*)\}\\fi/\\CheckSum{$1}/;
+ $_[0];
+}
+
+
+__END__
+
+
+=head1 SYNOPSIS
+
+checksum <input filename> --update|enable|disable
+
+Purpose:
+ Update, enable or disable the \CheckSum macro for a given .dtx source
+
+Example:
+ checksum omd.dtx --update
diff --git a/Master/texmf-dist/source/latex/proposal/bin/filedate b/Master/texmf-dist/source/latex/proposal/bin/filedate
new file mode 100755
index 00000000000..af49b6550dd
--- /dev/null
+++ b/Master/texmf-dist/source/latex/proposal/bin/filedate
@@ -0,0 +1,45 @@
+#!/usr/bin/perl -w
+#######################################################################################
+# A tool for updating .dtx files with a given package date. #
+# Copyright (c) 2010, Deyan Ginev, released under the Gnu General Public License (GPL)#
+# see http://www.gnu.org/copyleft/gpl.html #
+# $URL: https://svn.kwarc.info/repos/stex/trunk/bin/filedate$ #
+#######################################################################################
+
+use strict;
+
+use Getopt::Long;
+use Modparse;
+use Pod::Usage;
+use Cwd qw(abs_path);
+
+my ($file) = @ARGV;
+my $date = `git log --format='%ai' $file`;
+$date = substr($date, 0, index($date, ' '));
+$date =~ s/-/\//g;
+$file = abs_path($file);
+open(IN,"<$file");
+my @lines = ();
+while (<IN>) {
+ if ($_ =~ /\\Provides(Package|Class)/) {
+ $_ =~ s/\[(.*?)\s/\[$date /;
+ }
+ push @lines, $_;
+}
+close(IN);
+open(OUT,">$file");
+print OUT join("",@lines);
+close(OUT);
+
+__END__
+
+
+=head1 SYNOPSIS
+
+filedate <input filename> <newdate>
+
+Purpose:
+ Update the dates for a \ProvidePackage invocation in a given .dtx source
+
+Example:
+ filedate omdoc.dtx
diff --git a/Master/texmf-dist/source/latex/proposal/bin/generate-issues b/Master/texmf-dist/source/latex/proposal/bin/generate-issues
new file mode 100755
index 00000000000..84ebfd80fd6
--- /dev/null
+++ b/Master/texmf-dist/source/latex/proposal/bin/generate-issues
@@ -0,0 +1,470 @@
+#!/usr/bin/env ruby
+# Generate GitHub Issues for tasks and deliverables
+# - Each work package gets a WPX/README.md file
+# - Each task gets an Issue
+# - Each deliverable gets a Milestone
+# - Each deliverable gets an Issue associated with its Milestone
+# - Each deliverable Issue gets 'deliverable', 'WPX' tags
+# - Each task Issue gets 'task', 'WPX' tags
+
+# Run me from the proposal dir containing proposal.pdata:
+# cd ~/OpenDreamKit/Proposal
+# ~/path/to/LaTeX-Proposal/bin/generate-issues
+
+require 'date'
+require 'Octokit'
+require 'netrc'
+
+# constants for running the script are for OpenDreamKit
+# update these for your own proposal
+
+START_DATE = Date::new(2015, 9, 1) # start date of the project
+HOMEPAGE = "http://opendreamkit.org"
+PROPOSAL_URL = "https://github.com/OpenDreamKit/OpenDreamKit/raw/master/Proposal/proposal-www.pdf"
+PROJECT = "OpenDreamKit"
+REPO = "OpenDreamKit/OpenDreamKit"
+TARGET = "final" # or 'proposal'
+
+# throttle github creation requests to 5 Hz to avoid getting flagged for abuse
+THROTTLE_SECONDS = 5
+
+NATURES = {
+ 'R' => 'Report',
+ 'DEM' => 'Demonstrator',
+ 'DEC' => 'Websites, Media, etc.',
+ 'OTHER' => 'Other',
+}
+
+DISSEMINATIONS = {
+ 'PU' => 'Public',
+ 'CO' => 'Confidential',
+ 'CI' => 'Classified',
+}
+
+SITES = {
+ 'PS' => 'Université Paris-Sud',
+ 'LL' => 'Logilab',
+ 'UV' => 'Université de Versailles Saint-Quentin',
+ 'UJF' => 'Université Joseph Fourier',
+ 'UB' => 'CNRS',
+ 'UO' => 'University of Oxford',
+ 'USH' => 'University of Sheffield',
+ 'USO' => 'University of Southampton',
+ 'SA' => 'University of St Andrews',
+ 'UW' => 'University of Warwick',
+ 'JU' => 'Jacobs University Bremen',
+ 'UK' => 'University of Kaiserslautern',
+ 'US' => 'University of Silesia',
+ 'ZH' => 'Universität Zürich',
+ 'SR' => 'Simula Research Laboratory',
+}
+
+REVERSE_SITES = {}
+SITES.each_pair do |key, name|
+ REVERSE_SITES[name] = key
+end
+
+# Some sites would prefer alternative abbreviations
+REVERSE_SITES['University of St Andrews'] = 'USTAN'
+REVERSE_SITES['Université de Versailles Saint-Quentin'] = 'UVSQ'
+
+#------------------- Parsing proposal.pdata ---------------------
+
+def split_line(line)
+ # super primitive state-machine line split (not going to regex this)
+ parts = []
+ level = 0
+ buffer = []
+ line.each_char do |c|
+ case c
+ when '{'
+ if level > 0
+ buffer.push c
+ end
+ level += 1
+ when '}'
+ level -= 1
+ if level > 0
+ buffer.push c
+ end
+ if level == 0
+ parts.push(scrub_tex(buffer.join('')))
+ buffer = []
+ end
+ else
+ if level > 0
+ buffer.push c
+ end
+ end
+ end
+ return parts
+end
+
+
+def scrub_tex(text)
+ # scrub some latex markup from text
+ text.gsub!(/\\\w+/, '')
+ text.gsub!(/[{}]/, '')
+ text.strip.split.join(' ')
+end
+
+
+def transform_value(key, value)
+ case key
+ when 'lead'
+ return SITES[value]
+ when 'partners'
+ return value.split(',').map { |v| SITES[v] }
+ when 'dissem'
+ return DISSEMINATIONS[value]
+ when 'nature'
+ return NATURES[value]
+ when 'month'
+ return value.to_i
+ when 'delivs'
+ return value.split(',')
+ else
+ return scrub_tex(value)
+ end
+end
+
+
+def load_pdata(proposal_dir)
+ pdata = File.join(proposal_dir, "#{TARGET}.pdata")
+ deliv_data = File.join(proposal_dir, "#{TARGET}.deliverables")
+
+ workpackages = {} # mapping of workpackage id => wp info
+ deliverables = {}
+
+ File.readlines(pdata).each do |line|
+ key, *args = split_line line
+
+ case key
+ when 'wp'
+ name, key, value = args
+ value = transform_value(key, value)
+ if not workpackages.include? name
+ workpackages[name] = {
+ "tasks" => {},
+ "unknown-task" => nil,
+ "deliverables" => [],
+ }
+ end
+ wp = workpackages[name]
+ wp[key] = value
+
+ when 'task'
+ name, key, value = args
+ value = transform_value(key, value)
+ # find my workpackage
+ if name.index('@')
+ wpkey, short_name = name.split('@')
+ if short_name.match(/task\d+/)
+ workpackages[wpkey]['unknown-task'] = short_name
+ name = short_name
+ end
+ else
+ wpkey = workpackages.keys.select { |wpkey| name.start_with? wpkey }.first
+ end
+ # handle workpackage@taskNN weirdness
+ wp = workpackages[wpkey]
+ tasks = wp['tasks']
+ if not wp['unknown-task'].nil? and wp['unknown-task'] != name
+ tasks[name] = tasks.delete(wp['unknown-task'])
+ wp['unknown-task'] = nil
+ end
+ if not tasks.include? name
+ tasks[name] = {}
+ end
+ tasks[name][key] = value
+ when 'deliv'
+ name, key, value = args
+
+ value = transform_value(key, value)
+ if not deliverables.include? name
+ deliverables[name] = {}
+ end
+ deliverables[name][key] = value
+
+ else
+ # DEBUG:
+ # puts " Ignored: #{args}"
+ end
+ end
+
+ # get deliverable data, workpackage id from proposal.deliverables
+ File.readlines(deliv_data).each do |line|
+ args = split_line line
+ name = args[3]
+ if deliverables.include? name
+ deliverable = deliverables[name]
+ else
+ deliverable = {}
+ end
+ month = args[0].to_i
+ deliverable = (deliverables[name] or {}).merge({
+ "month" => month,
+ # due date is last day of the given month, so subtract one day
+ "due_date" => (START_DATE >> month) - 1,
+ "label" => scrub_tex(args[2]),
+ "deliv_id" => args[3],
+ "dissem" => transform_value('dissem', args[4]),
+ "nature" => transform_value('nature', args[5]),
+ "title" => scrub_tex(args[6]),
+ "lead" => SITES[scrub_tex(args[8])],
+ })
+ wpid = scrub_tex(args[7])
+ wp = workpackages.values.find {|wp| wp['label'] == wpid}
+ wp['deliverables'].push(deliverable)
+ end
+
+ workpackages.values.each do |wp|
+ wp['deliverables'].sort_by! {|d| d['label']}
+ end
+
+ return workpackages.values.sort_by {|wp| wp['label']}
+end
+
+
+#--------------- GitHub-related ---------------------
+
+def check_token
+ # get GitHub auth token, creating one if we don't find it.
+ rc = Netrc.read Netrc.default_path
+ if not rc['api.github.com'].nil?
+ return
+ end
+ puts "We need your password to generate an OAuth token. The password will not be stored."
+ username = ask "Username: "
+ password = ask("Password: ") { |q| q.echo = '*' }
+ client = Octokit::Client.new(
+ :login => username,
+ :password => password,
+ )
+ reply = client.create_authorization(
+ :scopes => ["public_repo"],
+ :note => "Issue Migration",
+ )
+ token = reply.token
+ rc['api.github.com'] = username, token
+ rc.save
+end
+
+$cache = {
+ 'issues' => {},
+ 'milestones' => {},
+}
+
+def get_issues(github, repo)
+ # get issues for a repo (cached)
+ cache = $cache['issues']
+ if not cache.include? repo
+ cache[repo] = github.issues(repo)
+ end
+ return cache[repo]
+end
+
+def get_milestones(github, repo)
+ # get issues for a repo (cached)
+ cache = $cache['milestones']
+ if not cache.include? repo
+ cache[repo] = github.list_milestones(repo)
+ end
+ return cache[repo]
+end
+
+# Templates for making readmes, issues
+
+README_TPL = <<-END
+# %{title}
+
+Lead Institution: %{lead}
+
+See page %{page} of the [proposal](#{PROPOSAL_URL}) for the full description.
+END
+
+TASK_TPL = <<-END
+- **%{wplabel}:** [%{wptitle}](https://github.com/#{REPO}/tree/master/%{wplabel})
+- **Lead Institution:** %{lead}
+- **Partners:** %{partners}
+- **Work phases:** %{wphases}
+
+See page %{page} of the [proposal](#{PROPOSAL_URL}) for the full description.
+END
+
+DELIV_TPL = <<-END
+- **%{wplabel}:** [%{wptitle}](https://github.com/#{REPO}/tree/master/%{wplabel})
+- **Lead Institution:** %{lead}
+- **Due:** %{date} (month %{month})
+- **Nature:** %{nature}
+
+See page %{page} of the [proposal](#{PROPOSAL_URL}) for the full description.
+END
+
+DELIV_MILESTONE_TPL = "# %{title}\n\n#{DELIV_TPL}"
+
+
+def make_task_issue(github, repo, task, workpackage, options)
+ title = "#{task['label']}: #{task['title']}"
+ issues = get_issues(github, repo)
+ issue = issues.find { |i| i.title.start_with?(task['label'] + ':') }
+ if issue.nil?
+ body = TASK_TPL % {
+ wplabel: workpackage['label'],
+ wptitle: workpackage['title'],
+ lead: task['lead'],
+ page: task['page'],
+ wphases: task['wphases'],
+ partners: (task['partners'] or ['None']).join(', ')
+ }
+ puts "\n\nMaking Issue on #{repo}: #{title}"
+ puts body
+
+ github.create_issue(repo, title, body, options)
+ # throttle creation calls to avoid flags for abuse
+ sleep THROTTLE_SECONDS
+ else
+ puts "Found Issue #{repo}##{issue.number}: #{issue.title}"
+ existing_labels = issue.labels.map { |label| label['name']}
+ missing_labels = options[:labels].reject { |label| existing_labels.include? label }
+ if not missing_labels.empty?
+ puts "Updating milestone, labels on #{repo}##{issue.number}"
+ github.update_issue(repo, issue.number, options)
+ sleep THROTTLE_SECONDS
+ end
+ end
+end
+
+
+def make_deliverable_issue(github, repo, deliverable, workpackage, options)
+ title = "#{deliverable['label']}: #{deliverable['title']}"
+ issues = get_issues(github, repo)
+ issue = issues.find { |i| i.title.start_with?(deliverable['label'] + ':') }
+ if issue.nil?
+ body = DELIV_TPL % {
+ wplabel: workpackage['label'],
+ wptitle: workpackage['title'],
+ lead: deliverable['lead'],
+ date: deliverable['due_date'],
+ month: deliverable['month'],
+ nature: deliverable['nature'],
+ page: deliverable['page'],
+ }
+ puts "\n\nMaking Issue on #{repo}: #{title}"
+ puts body
+ github.create_issue(repo, title, body, options)
+ # throttle creation calls to avoid flags for abuse
+ sleep THROTTLE_SECONDS
+ else
+ puts "Found Issue #{repo}##{issue.number}: #{issue.title}"
+ existing_labels = issue.labels.map { |label| label['name']}
+ missing_labels = options[:labels].reject { |label| existing_labels.include? label }
+ if issue.milestone.nil? or not missing_labels.empty?
+ puts "Updating milestone, labels on #{repo}##{issue.number}"
+ github.update_issue(repo, issue.number, options)
+ sleep THROTTLE_SECONDS
+ end
+ end
+end
+
+
+def make_deliverable_milestone(github, repo, deliverable, workpackage)
+ title = deliverable['label']
+
+ milestone = get_milestones(github, repo).find { |ms| ms.title == title }
+ if milestone.nil?
+ puts "Making milestone on #{repo}: #{title}"
+ body = DELIV_MILESTONE_TPL % {
+ wplabel: workpackage['label'],
+ wptitle: workpackage['title'],
+ title: deliverable['title'],
+ lead: deliverable['lead'],
+ date: deliverable['due_date'],
+ month: deliverable['month'],
+ nature: deliverable['nature'],
+ page: deliverable['page'],
+ }
+
+ milestone = github.create_milestone(repo, title,
+ :due_on => deliverable['due_date'],
+ :description => body,
+ )
+ # throttle creation calls to avoid flags for abuse
+ sleep THROTTLE_SECONDS
+ else
+ puts "Milestone #{repo}@#{title} exists"
+ end
+ return milestone.number
+end
+
+
+def populate_workpackage(github, repo, workpackage)
+ # populate issues for a given workpackage
+ label = workpackage['label']
+
+ readme_path = "#{label}/README.md"
+ begin
+ github.contents(repo, :path => readme_path)
+ rescue Octokit::NotFound
+ readme = README_TPL % {
+ title: "#{workpackage['label']}: #{workpackage['title']}",
+ lead: workpackage['lead'],
+ page: workpackage['page'],
+ }
+
+ puts "Creating readme at #{repo}/#{readme_path}"
+ puts readme
+ github.create_contents(repo, readme_path, "Creating #{readme_path}", readme)
+ # throttle creation calls to avoid flags for abuse
+ sleep THROTTLE_SECONDS
+ end
+
+ workpackage['tasks'].each_value do |task|
+ org_labels = [REVERSE_SITES[task['lead']]] + \
+ (task['partners'] or []).map {|site| REVERSE_SITES[site]}
+
+ make_task_issue(github, repo, task, workpackage,
+ :labels => [
+ 'task',
+ workpackage['label'],
+ ] + org_labels
+ )
+ end
+ workpackage['deliverables'].each do |deliverable|
+ milestone = make_deliverable_milestone(github, repo, deliverable, workpackage)
+ make_deliverable_issue(github, repo, deliverable, workpackage, {
+ :milestone => milestone,
+ :labels => [
+ 'deliverable',
+ workpackage['label'],
+ REVERSE_SITES[deliverable['lead']]
+ ]
+ })
+ end
+end
+
+
+def main(proposal_dir)
+ # run the whole thing
+
+ # verify that there's a GitHub token in .netrc
+ check_token
+
+ # create client
+ github = Octokit::Client.new(:netrc => true)
+ github.auto_paginate = true
+
+ load_pdata(proposal_dir).each do |wp|
+ populate_workpackage(github, REPO, wp)
+ end
+end
+
+if __FILE__ == $0
+ puts ARGV
+ if ARGV.length > 1
+ proposal_dir = ARGV[1]
+ else
+ proposal_dir = '.'
+ end
+ main(proposal_dir)
+end \ No newline at end of file