commit 6250182b68f45850d2b2649e5157987f81548a6c
parent e545a6b05ea27b1deeb5fa561d1c714bdaab355f
Author: Alex Balgavy <a.balgavy@gmail.com>
Date: Fri, 31 Jan 2020 23:05:13 +0100
conf: moved to separate repo
conf is no longer in the scripts folder, it's been moved to
github.com/thezeroalpha/conf. Also added as a brew tap.
Former-commit-id: 6f0a13bce8b64a61b4dff9b49f42f6f7983f62fb
Diffstat:
D | scripts/conf | | | 445 | ------------------------------------------------------------------------------- |
1 file changed, 0 insertions(+), 445 deletions(-)
diff --git a/scripts/conf b/scripts/conf
@@ -1,445 +0,0 @@
-#!/usr/bin/perl
-use strict;
-use warnings;
-use Getopt::Long 'GetOptions';
-use Pod::Usage 'pod2usage';
-use Cwd 'realpath';
-use File::Copy 'move';
-use File::Path 'make_path';
-
-# Set these however you want
-my $DOTFILES = $ENV{'DOTFILES'};
-my $MAPFILE = $DOTFILES."/dot.map";
-
-=head1 NAME
-
-conf - Manage your dotfiles.
-
-=head1 SYNOPSIS
-
- conf [options] command [entry_1 [entry_2 ...]]
-
- Help Options:
- --help, -h Show this script's help information.
- --manual, -man Read this script's manual.
-
- Arguments:
- link Link an entry.
- unlink Unlink an entry.
- check Check an entry.
- list List entries.
- edit Edit the map file.
-
-=cut
-
-# Some preliminary checks
-die "\$DOTFILES not set." if not $DOTFILES;
-chdir "$DOTFILES" or die "Cannot cd into $DOTFILES.";
-(-e $MAPFILE) or die "Mapfile $MAPFILE does not exist.";
-
-# Print with a newline
-sub puts {
- print $_[0]."\n";
-}
-
-# Ask a confirmation question, return the boolean answer
-sub confirm {
- # Print the question
- my $question = $_[0];
- print $question." [Y/n] ";
-
- # Obtain and clean the response
- my $response = <>;
- chomp $response;
-
- # Check if the response is a "yes", return a boolean
- return ($response =~ /^\s*([Yy]|[Yy][Ee][Ss])\b/);
-}
-
-# Print a message to stderr and exit
-sub Die {
- print STDERR $_[0]."\n";
- exit 1;
-}
-
-# Does a line contain a mapping?
-sub IsMapLine {
- # It's a mapping if it's not blank or a comment
- return ($_[0] !~ /^\s*$/ and $_[0] !~ /^\s*\#/ and $_[0] !~ /^$/)
-}
-
-# Parse a single map line into a standalone key-value mapping
-sub ParseLine {
- # Split the line on colons
- my @mapline = split(':', $_[0]);
-
- # Grab the home directory
- my $homedir = $ENV{'HOME'};
-
- # Remove leading/trailing whitespace for both parts of the mapline
- s/^\s*|\s*$//g for @mapline;
-
- # Replace '~' with the full path of $HOME
- $mapline[1] =~ s/\~/$homedir/;
-
- # Return a reference to the parsed mapline
- return \@mapline;
-}
-
-# Parse the whole mapfile, return a reference to the key-value hash
-sub ParseMapfile {
- my (@nestdir, %mappings);
- my $lineno = 1;
-
- # Open up the mapfile for reading, using the handle MAP
- open(MAP, "<$_[0]") or Die("Couldn't open the file $_[0], $!");
-
- # While not EOF
- while(my $line = <MAP>) {
- if (IsMapLine($line)) {
- # Parse the mapline
- my ($src, $dst) = @{ParseLine($line)};
-
- # If top level mapping
- if ($src !~ /^-+/) {
- # Zero out the nesting directory
- @nestdir = ();
-
- # If the line defines a new nesting dir, remember it
- if (not defined $dst or $dst eq '') {
- push @nestdir, $src
- }
-
- # Otherwise, it's a one-off mapping (doesn't have related nested mappings below)
- else {
- # If the source doesn't exist, can't map
- if (not -e $src) {
- Die("error in mapfile: $src does not exist (line $lineno)");
- }
- # If it does, add it as a mapping
- else {
- $mappings{"$src"} = $dst;
- }
- }
- }
-
- # Nested mappings
- else {
- # Number of dashes == number of levels
- my $levels = length( ($src =~ /^(-+)/)[0] );
-
- # Remove the punctuation to get the actual source
- $src =~ s/(^-* *)|://g;
-
- # If/while the mapping is at a higher level than the previous, unnest
- if ($levels < scalar @nestdir) {
- pop @nestdir while ($levels < scalar @nestdir);
- }
-
- # If the mapping's another nesting dir, add it
- if (not defined $dst or $dst eq '') {
- push @nestdir, $src
- }
- # Otherwise, if it's a mapping, save it with the full path
- else {
- my $fullsrc = join("/", @nestdir)."/$src";
- # If it doesn't exist, can't map
- if (not -e $fullsrc) {
- Die("error in mapfile: $fullsrc does not exist (line $lineno)");
- }
- # Otherwise, add the mapping
- else {
- $mappings{"$fullsrc"} = $dst;
- }
- }
- }
- }
- # Increase the line number to keep track of where we are in the mapfile (error reporting)
- $lineno++;
- }
-
- # Return a ref to the hash of mappings
- return \%mappings;
-}
-
-# Dump a hash, for debugging purposes
-sub PrettyPrint {
- puts "hash length: ".keys(%{$_[0]});
- use Data::Dumper;
- print Dumper $_[0];
-}
-
-# Link to a source path at a destination path
-sub MakeLinkOp {
- my ($src, $dst) = @_;
-
- # If the destination exists
- if (-e $dst) {
- # If it's a link and points to the source, nothing to do here
- if (-l $dst and realpath($dst) eq $DOTFILES."/".$src) {
- puts "$dst is already linked to $src";
- return;
- }
- # Otherwise, if it's not a link, back it up
- else {
- puts "$dst already exists, renaming to $dst.bak";
- move $dst, $dst.".bak";
- }
- }
-
- # Create the intermediary dirs if they don't exist
- (my $linkpath = $dst) =~ s:/[^/]*$::;
- if (! -d $linkpath) {
- make_path($linkpath) or die "Couldn't make path $linkpath, $!";
- }
-
- # Print a message and link to the source at the destination
- puts "$src ==> $dst";
- symlink($DOTFILES."/".$src, $dst) or die "Couldn't symlink, $!";
-}
-
-# Remove a linked source path
-sub RmLinkOp {
- my ($src, $dst) = @_;
-
- # If the destination doesn't exist, nothing to do here.
- if (! -e "$dst") {
- puts "$dst does not exist.";
- }
- # If it exists, but it's not a link, don't do anything.
- elsif (! -l "$dst") {
- puts "$dst is not a link, not removing.";
- }
- # If it's a link, but doesn't point to the config file, don't do anything.
- elsif (not realpath($dst) eq $DOTFILES."/".$src) {
- puts "$dst does not point to $src, not removing.";
- }
- # Otherwise, it's a link that points to the config file, so remove it.
- else {
- puts "Removing link: $dst";
- unlink "$dst" or die "Failed to remove file $dst: $!\n";
- }
-}
-
-# Check whether a link points to the config file
-sub CheckLinkOp {
- my ($src, $dst) = @_;
- if (-e $dst) {
- if (-l $_[1] and realpath($_[1]) eq $DOTFILES."/".$_[0]) {
- puts "[ OK ] $src is linked at $dst.";
- }
- elsif (-l $dst) {
- puts "[ XX ] $dst is a link but does not point to $src.";
- }
- else {
- puts "[ XX ] $dst exists but does not point to $src.";
- }
- }
- else {
- puts "[ XX ] $src is not linked.";
- }
-}
-
-# Execute a link operation on maps in ARGV
-sub ExecLinkOp {
- # Retrieve the reference to map hash and the function to run
- my ($maps, $opref) = @_;
-
- # If operation should be on all tracked files
- if (not @ARGV) {
- keys %$maps; # reset `each` iterator
- foreach my $src (keys %$maps) {
- $opref->($src, $maps->{$src});
- }
- }
- # If the user specified what to operate on
- else {
- while (@ARGV) {
- my $src_part = shift @ARGV;
-
- # if there's a mapping that matches exactly, operate on that
- if (exists $maps->{$src_part}) {
- $opref->($src_part, $maps->{$src_part});
- }
- # otherwise, operate on everything starting with whatever's passed
- elsif ( my @matching = grep(/^$src_part/, keys %$maps) ) {
- foreach my $src(@matching) {
- $opref->($src, $maps->{$src});
- }
- }
- # if nothing matches, fail
- else {
- my %opnametab = (\&MakeLinkOp => 'link', \&RmLinkOp => 'unlink', \&CheckLinkOp => 'check');
- Die "Error: $src_part not present in mapfile, don't know how to ".$opnametab{$opref}.'.';
- }
- }
- }
-}
-
-# Link command
-sub LinkCmd {
- # If no args, default to linking all but confirm with user
- if (not @ARGV and !confirm("Link all?")) {
- Die "User cancelled.";
- }
-
- ExecLinkOp($_[0], \&MakeLinkOp);
-}
-
-# Unlink command
-sub UnlinkCmd {
- # If no args, default to unlinking all but confirm with user
- if (not @ARGV and !confirm("Unlink all?")) {
- Die "User cancelled";
- }
- ExecLinkOp($_[0], \&RmLinkOp);
-}
-
-# Edit command
-sub EditCmd {
- # Set a default editor value
- $ENV{EDITOR} ||= 'vim';
-
- # system() doesn't capture stdout
- system "$ENV{EDITOR} $MAPFILE";
-}
-
-sub CheckCmd {
- ExecLinkOp($_[0], \&CheckLinkOp);
-}
-
-sub ListCmd {
- puts "Mappings (from $MAPFILE), paths relative to $DOTFILES";
- puts "(format: source => name_of_symlink)\n";
-
- my $hash = $_[0];
- my @output = ();
- foreach my $k (keys %$hash) {
- push @output, "$k => $hash->{$k}";
- }
- foreach my $l (sort @output) {
- puts $l;
- }
-}
-
-# Run a subcommand based on string
-sub RunSubcommand {
- # Dispatch table
- my %subcommands = (
- link => \&LinkCmd,
- unlink => \&UnlinkCmd,
- edit => \&EditCmd,
- list => \&ListCmd,
- check => \&CheckCmd
- );
-
- # If the command isn't in the table, exit and print usage
- $subcommands{$_[0]} or pod2usage(2);
-
- # Parse the file
- my $mappings = ParseMapfile($MAPFILE);
- # Then execute the command on the extracted mappings
- $subcommands{$_[0]}->($mappings);
-}
-
-# If no arguments, show usage
-scalar @ARGV > 0 or pod2usage(2);
-
-# Get the commandline options, only recognise help/manual, everything else gets sent to dispatch subroutine
-my ($help, $manual);
-my %optctl = (help => \$help, manual => \$manual, '<>' => \&RunSubcommand);
-GetOptions(\%optctl, 'help|h', 'manual|man', '<>') or pod2usage(2);
-
-=head1 OPTIONS
-
-=over 4
-
-=item B<--help>
-Show the brief help information.
-
-=item B<--manual>
-Read the manual, with examples.
-
-=back
-=cut
-
-# If help set, show usage
-pod2usage(2) if $help;
-# If manual set, show full documentation
-pod2usage({ -verbose => 2, -exitval => 1}) if $manual;
-
-=head1 DESCRIPTION
-
-This is a script to manage filesystem-wide symbolic links to your dotfiles (configuration), based on definitions in the map file.
-The location of the map file is set in the script itself, using the $MAPFILE variable.
-The location of your dotfiles is set either using the $DOTFILES environment variable, or inside the script itself.
-
-=head1 ARGUMENTS
-
-=over 4
-
-=item * B<link [entry1 [entry2...]]>
-
-Link entries according to the map file.
-With no arguments, links all entries.
-
-=item * B<unlink [entry1 [entry2...]]>
-
-Unlink entries according to the map file.
-With no arguments, unlinks all entries.
-
-=item * B<check [entry1 [entry2...]]>
-
-Check that entries are linked accordint to the map file.
-With no arguments, checks all entries.
-
-=item * B<edit>
-
-Edit the map file with whatever you set as $EDITOR
-
-=item * B<list>
-
-List the current mappings.
-
-
-=back
-
-=head1 EXAMPLES
-
-Link everything in the mapfile:
-
- conf link
-
-Link all files in the "vim" directory:
-
- conf link vim
-
-Link specifically the shell/bashrc file:
-
- conf link shell/bashrc
-
-Link the shell/zprofile file and the vim/autoload directory:
-
- conf link shell/zprofile vim/autoload
-
-Check if everything is linked:
-
- conf check
-
-Check if everything in the "vim" directory is linked:
-
- conf check vim
-
-Remove the link to the "lf" directory:
-
- conf unlink lf
-
-Remove all links defined in the mapfile:
-
- conf unlink
-
-=head1 AUTHOR
-
-Alexander Balgavy (thezeroalpha), L<https://github.com/thezeroalpha>.
-
-=cut