I finally pulled the plug on my semi-automatic git process, after 330+ challenges, replacing it with a fully automated raku script.
This script will not work the first time you submit a Weekly Challenge, as it relies on the initial steps for setting up a cloned repo to work. See the Weekly Challenge Step-by-step instructions for details.
This script assumes that the user writes the programs in a different location, and copies them to the github directory for you. It supports different programming languages, and moves the files into subdirectories for each one. Then the user is prompted to choose one file for each challenge, for each detected language.
This first version of the script works, but I will work on extending it if you (you at large, not literally) so desire. And probably also if you do not...
This version uses command line arguments for the configuration. I intend to replace that with a dedicated configuration file.
The language detection part of «weekly-submit» (see [34 to 41++]) will metamorphose into a stand alone module «File::Inspect::Code», regardless of the trajectory of the «weekly-push» program.
It will start with a «get-lang» procedure, detecting the programming language in the specified file through the «crash bang» line and the filename extension if the former is missing.
I am open for suggestions for a better module name, though...
#! /usr/bin/env raku
unit sub MAIN
(
$week where $week ~~ /^<[0..9]> ** 3 $/, # [1]
:s(:$source) = "/home/arne/Dropbox/perl6/raku-weeklychallenge/", # [2]
:d(:$destination) = "/home/arne/raku/perlweeklychallenge-club/", # [3]
:u(:$user) = "arne-sommer", # [4]
:k(:$kill-orig), # [5]
:v(:$verbose)
);
my %ext = # [6]
(
bash => 'sh',
c => 'c',
javascript => 'js',
perl => 'pl',
postscript => 'ps',
python => 'py',
raku => 'raku',
ruby => 'rb',
sql => 'sql',
);
[1] Specify the week number, as a three digit number. Use leading zeroes if working with weeks 1 to 99.
[2] The program assumes a workflow where you write (place) the programs in another location, with each weekly challenge in its own directory with the week number as the name. The program names and programming languages do not matter (for the program).
[3] This is the directory where you have checked out your clone of the git repo.
[4] This is your github username, used to locate where to place your files.
[5] I submit all the files, i.e. with the original names (as shown in the blog),
and also sometimes several solutions - some of which may not actually solve the
challenge, or indeed work. Use this command line option to only commit the
ch-[1|2].[pl|raku|..]
files.
[6] This is the mapping between language (retrieved from the «crash bang» line), and the filename extension to be used on the submissions.
File: weekly-submit (partial)
indir($destination, { # [11]
shell "git checkout master"; # [12]
shell "git fetch upstream";
shell "git merge upstream/master --ff-only";
shell "git push -u origin master";
shell "git checkout -b challenge-$week-$user"; # [13]
});
[11] We are going to execute a bunch of code in a directory,
and can do just this with indir
. It sets the current directory for
the given block only, leaving the previous one intact after finishing.
See docs.raku.org/routine/indir for more information about indir
.
[12] Then we use shell
to execute a command, or rather
a command line, passed through the system shell. This will expand
wildcards, so is not entirely without perils.
See docs.raku.org/routine/shell for more information about shell
.
The «git» shell
commands in this program follow the
explanation in the
How to add a new solution when you already have the forked repository?
section, except for the second point (the upstream check) that I skip.
I skip the upstream check as it would only confirm that you have a borked repository, and the program has no way of dealing with problems. Ignorance is bliss...
[13] The $week
and $user
variables are used here.
indir($destination ~ "challenge-$week/$user/", { # [21]
shell "cp $source$week/* ."; # [22]
my @langs = move2subdirs; # [23]
for @langs -> $lang # [24]
{
indir($destination ~ "challenge-$week/$user/$lang", { # [25]
ask-files($lang); # [26]
});
}
});
[21] In your directory for the specified week in the github repo.
[22] Copy the solutions from wherever they are located.
[23] Move the solutions into subdirectories for the language they are
written in. The return value of move2subdirs
is a list of found
languages, to be used in [24].
[24] Iterate over the languages.
[25] In each language subdirectory,
[26] • Ask which files should be used as challenge 1 and challenge 2 (for the current language).
File: weekly-submit (partial)
sub move2subdirs # [31]
{
my %lang; # [32]
for dir() -> $file { # [33]
next if $file.IO.d; # [34]
next if $file eq 'README'; # [35]
my $crashbang = $file.IO.lines.first; # [36]
$crashbang ~~ /^\#\! .* \s (<[a .. z]>+)$/; # [37]
my $lang = $0; # [38]
unless $lang # [39]
{
$file ~~ /.*\.(.*)/; # [40]
$lang = %ext2lang{$0} if $0; # [41]
unless $lang # [42]
{
say "*** Unable to detect language in file $file. \
Skipped and deleted...";
unlink $file; # [43]
next; # [44]
}
}
mkdir($lang) unless $lang.IO.d; # [45]
%lang{$lang}++; # [46]
shell "mv $file $lang/"; # [47]
say ": Moved file $file to $lang/" if $verbose;
}
return %lang.keys.sort; # [48]
}
[31] Move the source files into langauge specific subdirectories.
[32] The found languages will end up here.
[33] Iterate over the files in the current directory (as
set up in [21]) with dir
.
See docs.raku.org/routine/dir for more information about dir
.
See docs.raku.org/routine/d for more information about d
.
[35] Ignore the «README» file, placed here automatically by the Challenge(d) Overlords.
[36]
Read the file with IO.lines
, and get the first
line. This operation is lazy, thus not actually reading the whole file.
See docs.raku.org/routine/lines for more information about lines
.
See docs.raku.org/routine/first for more information about first
.
[37] Detect the language on the «crash bang« line. This supports
old style (e.g. #! /usr/bin/perl
#! /usr/bin/env perl
[38] Pick up the lanuage, if any.
[39] Not picked up a language?
[40] Get the filename extension.
[41] Look up the language name with the given extension.
[42] That did not work, i.e. no extension or not a supported extension.
[43] Delete the file with unlink
.
See docs.raku.org/routine/unlink for more information about unlink
.
[44] Skip the current iteration of the loop (in [33]).
[45] Create a subdirectory for the detected language, if not done already.
See docs.raku.org/routine/mkdir for more information about mkdir
.
[46] Register the detected langauge.
[47] Move the file into the language subdirectory.
[48] Return a (sorted, thus entropy resistant) list of detected languages.
File: weekly-submit (partial)
sub ask-files ($lang) # [51]
{
my @files = sort dir(); # [52]
my $i = 1; # [53]
for @files -> $file { # [54]
next if $file.IO.d; # [55]
say " { $i++ } $file"; # [56]
}
my $ext = %ext{$lang} // $lang; # [57]
my $index1 = prompt_int($i, "Enter number of ch-1.$ext (or 0 to skip): "); # [58]
my $index2 = prompt_int($i, "Enter number of ch-2.$ext (or 0 to skip): ", # [59]
$index1);
if $index1 # [60]
{
shell "cp @files[$index1 -1] ch-1.$ext";
}
if $index2 # [61]
{
shell "cp @files[$index2 -1] ch-2.$ext";
}
if $kill-orig # [62]
{
unlink $_ for @files; # [62a]
}
}
[51] Ask the user which file to use for challenge 1 and 2. The language name is used in [57] to get the filename extension.
[52] Get the files.
[53] The index of the files, starting at 1.
[54] Iterate over the files.
[55] Skip directories, if any. There really should not be any...
[56] Print the index and filename.
[57] Get the filename extension for the given language.
[58] Get the index of the file to use for challenge 1.
[59] Get the index of the file to use for challenge 2. The last argument means that the index used for challenge 1 (if any) will not be available for this challenge.
[60] Chosen a file for the first challenge? Copy it to the correct name.
[61] Ditto for the second challenge.
[62] Remove the original files if so specified with the «-k» command line option.
File: weekly-submit (partial)
sub prompt_int ($limit, $prompt, $ignore = -1) # [71]
{
my $int = -1; # [72]
while $int !~~ UInt || $int >= $limit || $int == $ignore # [73]
{
$int = prompt($prompt); # [74]
}
return $int; # [75]
}
[1] Print the prompt, and get a number between 1 (included) and the upper limit (not included).
[2] Bootstrap the loop.
[3] As long as we have not given a legal index (or 0 to exit).
[4] Use prompt
to prompt for user input.
See docs.raku.org/routine/prompt for more information about prompt
.
[5] Return a legal index, or 0 for no selection.
File: weekly-submit (partial)
indir($destination ~ "challenge-$week/$user", { # [81]
save-to-file("URL of blog (or empty for none): ", 'blog.txt'); # [82]
shell "git add .";
shell "git status .";
my $label = $user.split("-")>>.tc.join(" "); # [83]
shell "git commit -m \"week $week $label\""; # [84]
shell "git push -u origin challenge-$week-$user"; # [84a]
});
[81] In the top level directory of your weekly reply.
[82] Ask for the URL of the blog, if any.
[83] Use a more natural form of the github username, i.e.
«Arne Sommer» instead of «arne-sommer» in the commit message.
The tc
will «title case» the words for us.
See docs.raku.org/routine/tc for more information about tc
.
[84] This is (and has to be) the same branch name as in [13].
File: weekly-submit (partial)
sub save-to-file($prompt, $file) # [91]
{
my $url = prompt($prompt); # [92]
if $url # [93]
{
$file.IO.spurt($url); # [94]
say ": Saved file $file" if $verbose;
}
}
[91] Ask for the blog URL.
[92] Get the reply.
[93] Did we get a reply? Note the lack of input validation.
[94] Save the input in the «blog.txt» file, as set up in [82].
See docs.raku.org/routine/spurt for more information about spurt
.
Comments? Use the Reddit Link...