dotfiles

My personal shell configs and stuff
git clone git://git.alex.balgavy.eu/dotfiles.git
Log | Files | Refs | Submodules | README | LICENSE

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:
Dscripts/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