conf

conf - my dotfiles manager
git clone git://git.alex.balgavy.eu/conf.git
Log | Files | Refs | README | LICENSE

commit 85f6059b50bb58b7ed5312dc3fa6b408d0a25add
Author: Alex Balgavy <a.balgavy@gmail.com>
Date:   Fri, 31 Jan 2020 21:27:50 +0100

Initial commit

Diffstat:
A.gitignore | 70++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
AREADME.md | 91+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aconf | 445+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aman/conf.1 | 248+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 854 insertions(+), 0 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -0,0 +1,70 @@ + +# Created by https://www.gitignore.io/api/macos,perl +# Edit at https://www.gitignore.io/?templates=macos,perl + +### macOS ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### Perl ### +!Build/ +.last_cover_stats +/META.yml +/META.json +/MYMETA.* +*.o +*.pm.tdy +*.bs + +# Devel::Cover +cover_db/ + +# Devel::NYTProf +nytprof.out + +# Dizt::Zilla +/.build/ + +# Module::Build +_build/ +Build +Build.bat + +# Module::Install +inc/ + +# ExtUtils::MakeMaker +/blib/ +/_eumm/ +/*.gz +/Makefile +/Makefile.old +/MANIFEST.bak +/pm_to_blib +/*.zip + +# End of https://www.gitignore.io/api/macos,perl diff --git a/README.md b/README.md @@ -0,0 +1,91 @@ +# conf - a personal configuration manager +## Installation +1. Download the `conf` script, make it executable, and put it in your `$PATH`. +2. Set value of `$DOTFILES` to the location of your dotfiles, either in the environment, or in the `conf` script itself. +3. Set up your dotfiles hierarchy in any way you want. +3. Put a map file in the root of $DOTFILES. This file defines how your dotfiles/folders map to other locations in your filesystem. +4. Write the name of your map file in the `conf` script. +5. Use `conf`, and profit, maybe. Run `conf -h` or `conf -man` to get help. + +## FAQ +### How do you write a map file? +This is easier by example. + +If you have a dotfile directory hierarchy that looks like this: + +``` +~/dotfiles +├── dot.map +├── shell +│   ├── zprofile +│   └── zshrc +└── vim +│ ├── after +│ │   └── ftplugin +│ ├── autoload +│ └── vimrc +└── tools + ├── lfrc + └── polybar +    └── config + +``` + +Then the `dot.map` file listed in the `dotfiles` directory might look like this: + +```map +# All file/directory names are relative to the value of the $DOTFILES variable, which is set in the environment +# or explicitly inside the `conf` script itself. +shell: +- zprofile: ~/.zprofile +- zshrc: ~/.zshrc + +vim: +- after/ftplugin: ~/.vim/after/ftplugin +- autoload: ~/.vim/autoload +- vimrc: ~/.vimrc + +tools/lfrc: ~/.config/lf/lfrc +tools: +- polybar: + -- config: ~/.config/polybar/config +``` + +And would result in the following symbolic links: + +* `~/.zprofile` pointing to `~/dotfiles/shell/zprofile` +* `~/.zshrc` pointing to `~/dotfiles/shell/zshrc` +* `~/.vim/after/ftplugin` pointing to `~/dotfiles/vim/after/ftplugin` +* `~/.vim/autoload` pointing to `~/dotfiles/vim/autoload` +* `~/.vimrc` pointing to `~/dotfiles/vimrc` +* `~/.config/lf/lfrc` pointing to `~/dotfiles/tools/lfrc` +* `~/.config/polybar/config` pointing to `~/dotfiles/tools/polybar/config` + +### Why would you make _another_ dotfiles manager? +Valid question, there's so many dotfile managers already available. +Why do I think I'm so special that none of the existing ones are good enough? + +Well, a few points about this manager: + +* It manages your dotfiles, and does nothing else. + It doesn't have git integration, it doesn't do any smart tracking, it doesn't replace your toilet paper - it just manages links between your configuration and the various configuration directories around your system. + You want git actions? Use git. +* It reads its configuration from a single, plain-text file that can be named whatever you want. +* It uses a configuration syntax that's intuitive _for me_. +* It leverages the filesystem structure for simpler configuration. +* It essentially runs anywhere out of the box. + +All this to make a dotfiles manager that works exactly the way I want it to work. + +### Why Perl? +Because: + +* It's ubiquitous. +* It's a step up from shell script in data structures, readability, maintainability, abstractions, etc. +* It's still low level enough to work with the shell and filesystem, unlike e.g. Python. + If I want to test for a file's existence, I can use `-e`, like in Bash. + Symlink functions are available without having to import anything. +* It's powerful for text manipulation, because it has regex built straight into the language. +* It's still quite fast. + In fact, it's super fast, compared to my previous version written in Bash. + On my computer, the "list all" operation is at least five times faster, and "check all" is at least seven times faster. diff --git a/conf b/conf @@ -0,0 +1,445 @@ +#!/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 diff --git a/man/conf.1 b/man/conf.1 @@ -0,0 +1,248 @@ +.\" Automatically generated by Pod::Man 4.11 (Pod::Simple 3.35) +.\" +.\" Standard preamble: +.\" ======================================================================== +.de Sp \" Vertical space (when we can't use .PP) +.if t .sp .5v +.if n .sp +.. +.de Vb \" Begin verbatim text +.ft CW +.nf +.ne \\$1 +.. +.de Ve \" End verbatim text +.ft R +.fi +.. +.\" Set up some character translations and predefined strings. \*(-- will +.\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left +.\" double quote, and \*(R" will give a right double quote. \*(C+ will +.\" give a nicer C++. Capital omega is used to do unbreakable dashes and +.\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff, +.\" nothing in troff, for use with C<>. +.tr \(*W- +.ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p' +.ie n \{\ +. ds -- \(*W- +. ds PI pi +. if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch +. if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch +. ds L" "" +. ds R" "" +. ds C` "" +. ds C' "" +'br\} +.el\{\ +. ds -- \|\(em\| +. ds PI \(*p +. ds L" `` +. ds R" '' +. ds C` +. ds C' +'br\} +.\" +.\" Escape single quotes in literal strings from groff's Unicode transform. +.ie \n(.g .ds Aq \(aq +.el .ds Aq ' +.\" +.\" If the F register is >0, we'll generate index entries on stderr for +.\" titles (.TH), headers (.SH), subsections (.SS), items (.Ip), and index +.\" entries marked with X<> in POD. Of course, you'll have to process the +.\" output yourself in some meaningful fashion. +.\" +.\" Avoid warning from groff about undefined register 'F'. +.de IX +.. +.nr rF 0 +.if \n(.g .if rF .nr rF 1 +.if (\n(rF:(\n(.g==0)) \{\ +. if \nF \{\ +. de IX +. tm Index:\\$1\t\\n%\t"\\$2" +.. +. if !\nF==2 \{\ +. nr % 0 +. nr F 2 +. \} +. \} +.\} +.rr rF +.\" +.\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2). +.\" Fear. Run. Save yourself. No user-serviceable parts. +. \" fudge factors for nroff and troff +.if n \{\ +. ds #H 0 +. ds #V .8m +. ds #F .3m +. ds #[ \f1 +. ds #] \fP +.\} +.if t \{\ +. ds #H ((1u-(\\\\n(.fu%2u))*.13m) +. ds #V .6m +. ds #F 0 +. ds #[ \& +. ds #] \& +.\} +. \" simple accents for nroff and troff +.if n \{\ +. ds ' \& +. ds ` \& +. ds ^ \& +. ds , \& +. ds ~ ~ +. ds / +.\} +.if t \{\ +. ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u" +. ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u' +. ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u' +. ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u' +. ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u' +. ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u' +.\} +. \" troff and (daisy-wheel) nroff accents +.ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V' +.ds 8 \h'\*(#H'\(*b\h'-\*(#H' +.ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#] +.ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H' +.ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u' +.ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#] +.ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#] +.ds ae a\h'-(\w'a'u*4/10)'e +.ds Ae A\h'-(\w'A'u*4/10)'E +. \" corrections for vroff +.if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u' +.if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u' +. \" for low resolution devices (crt and lpr) +.if \n(.H>23 .if \n(.V>19 \ +\{\ +. ds : e +. ds 8 ss +. ds o a +. ds d- d\h'-1'\(ga +. ds D- D\h'-1'\(hy +. ds th \o'bp' +. ds Th \o'LP' +. ds ae ae +. ds Ae AE +.\} +.rm #[ #] #H #V #F C +.\" ======================================================================== +.\" +.IX Title "CONF 1" +.TH CONF 1 "2020-01-31" "perl v5.30.1" "User Contributed Perl Documentation" +.\" For nroff, turn off justification. Always turn off hyphenation; it makes +.\" way too many mistakes in technical documents. +.if n .ad l +.nh +.SH "NAME" +conf \- Manage your dotfiles. +.SH "SYNOPSIS" +.IX Header "SYNOPSIS" +.Vb 1 +\& conf [options] command [entry_1 [entry_2 ...]] +\& +\& Help Options: +\& \-\-help, \-h Show this script\*(Aqs help information. +\& \-\-manual, \-man Read this script\*(Aqs manual. +\& +\& Arguments: +\& link Link an entry. +\& unlink Unlink an entry. +\& check Check an entry. +\& list List entries. +\& edit Edit the map file. +.Ve +.SH "OPTIONS" +.IX Header "OPTIONS" +.IP "\fB\-\-help\fR Show the brief help information." 4 +.IX Item "--help Show the brief help information." +.PD 0 +.IP "\fB\-\-manual\fR Read the manual, with examples." 4 +.IX Item "--manual Read the manual, with examples." +.PD +.SH "DESCRIPTION" +.IX Header "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 \f(CW$MAPFILE\fR variable. +The location of your dotfiles is set either using the \f(CW$DOTFILES\fR environment variable, or inside the script itself. +.SH "ARGUMENTS" +.IX Header "ARGUMENTS" +.IP "\(bu" 4 +\&\fBlink [entry1 [entry2...]]\fR +.Sp +Link entries according to the map file. +With no arguments, links all entries. +.IP "\(bu" 4 +\&\fBunlink [entry1 [entry2...]]\fR +.Sp +Unlink entries according to the map file. +With no arguments, unlinks all entries. +.IP "\(bu" 4 +\&\fBcheck [entry1 [entry2...]]\fR +.Sp +Check that entries are linked accordint to the map file. +With no arguments, checks all entries. +.IP "\(bu" 4 +\&\fBedit\fR +.Sp +Edit the map file with whatever you set as \f(CW$EDITOR\fR +.IP "\(bu" 4 +\&\fBlist\fR +.Sp +List the current mappings. +.SH "EXAMPLES" +.IX Header "EXAMPLES" +Link everything in the mapfile: +.PP +.Vb 1 +\& conf link +.Ve +.PP +Link all files in the \*(L"vim\*(R" directory: +.PP +.Vb 1 +\& conf link vim +.Ve +.PP +Link specifically the shell/bashrc file: +.PP +.Vb 1 +\& conf link shell/bashrc +.Ve +.PP +Link the shell/zprofile file and the vim/autoload directory: +.PP +.Vb 1 +\& conf link shell/zprofile vim/autoload +.Ve +.PP +Check if everything is linked: +.PP +.Vb 1 +\& conf check +.Ve +.PP +Check if everything in the \*(L"vim\*(R" directory is linked: +.PP +.Vb 1 +\& conf check vim +.Ve +.PP +Remove the link to the \*(L"lf\*(R" directory: +.PP +.Vb 1 +\& conf unlink lf +.Ve +.PP +Remove all links defined in the mapfile: +.PP +.Vb 1 +\& conf unlink +.Ve +.SH "AUTHOR" +.IX Header "AUTHOR" +Alexander Balgavy (thezeroalpha), <https://github.com/thezeroalpha>.