Compare commits

..

No commits in common. "86fad9b83fb3201c42b62654f9b6a0c3d87196b5" and "c995f07e673c28a436051a094f76e1373cdc0e0e" have entirely different histories.

13 changed files with 228 additions and 406 deletions

View File

@ -1,90 +0,0 @@
persoconf
=========
*persoconf* is a python script intended to keep track of all your personal
configuration files (also known as dotfiles).
Usage
-----
If you want to skip this and get to the point: there's a 'help' command
available, and +--help/-h+ options for every other command.
Add an app to your persoconf repo:
----
$> persoconf add myapp
----
Add dotfiles and config directories to this app:
----
$> persoconf add myapp ~/.myapp.config
$> persoconf add myapp ~/.config/myapp/
----
List the files and dirs saved in persoconf for an app:
----
$> persoconf list -f
tmux
tmux.conf
awesome
rc.lua
vim
vimrc
myapp
myapp.config
myapp/
----
Check if modifications occured on your config files:
----
$> echo "some change in my conf" >> ~/.myapp.config
$> persoconf check
persoconf:WARNING: File ~/.myapp.config was modified
----
Update persoconf saved files with the latest configuration available:
----
$> persoconf update
----
Package your dotfiles to move them on another machine:
----
$> persoconf package --pkgtype tgz
$> ls
persoconf.20160602.tgz
$> tar tzf persoconf.20160602.tgz
.config/awesome/rc.lua
.config/myapp/somefile
.config/myapp/someotherfile
.myapp.config
.tmux.conf
.vimrc
----
Internals
---------
All persoconf data is by default saved in a '.config/persoconf' directory. You
can override this location with the global +--rootdir+ option. If the rootdir
location does not exist when you first try to use persoconf, persoconf will ask
if you want to create it.
Basically, data is saved as is (which means textfiles and dirs, mostly), and
metadata is saved as json files.
// vim: set ft=asciidoc :

View File

@ -1,145 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os.path
import time
import tarfile
import utils
from metafile import Metafile, MalformedMetafileError
# -----------------------------------------------------------------------------
def init(parser):
parser.add_argument( "apps",
type=str,
nargs="*",
default=[],
help="The apps to package ; defaults to all " \
"apps" )
parser.add_argument( "--pkgtype", "-t",
type=str,
nargs="?",
default="tgz",
choices = [ "tgz" ],
help="The type of package to use" )
parser.add_argument( "--pkgname", "-p",
type=str,
nargs="?",
default="persoconf." + (time.strftime("%Y%m%d")),
help="The name of the package to create" )
parser.add_argument( "--nopersoconf", "-n",
action="store_true",
help="Do not include persoconf own config " \
"directory in the resulting package" )
# -----------------------------------------------------------------------------
def run(args, persoconf, logger):
pkgname = args.pkgname
# If no app is given: package all apps
if args.apps == []:
logger.info("Packaging all apps")
args.apps = persoconf.list_apps(logger=logger)
# Some apps were specified, so we list them with their files
appmetas = []
for app in args.apps :
# Load app META file
try:
appmetas.append(
Metafile(
json_path = os.path.join(
persoconf.path,
app,
persoconf.metafile
)
)
)
except FileNotFoundError:
logger.warning( "Metafile %s does not exist for app %s; ignoring"
% (persoconf.metafile, app) )
continue
except MalformedMetafileError:
logger.warning( "Malformed metafile %s in app %s; ignoring"
% (persoconf.metafile, app) )
continue
# TODO check that the app exists
# Adds the app name if there is only one packaged
if len(appmetas) == 1:
pkgname += ("." + appmetas[0].name)
# Switch according to the pkgtype
if args.pkgtype == "tgz":
pkgname += ".tgz"
# Create a tar containing the files in the right place
pkg = tarfile.open(pkgname, "w:gz")
for appmeta in appmetas:
# Add META file to the archive
if not args.nopersoconf:
filename_meta = os.path.join( persoconf.path,
appmeta.name,
persoconf.metafile )
filedest_meta = utils.contractuser( filename_meta )
if filedest_meta.startswith("~/"):
filedest_meta = filedest_meta[2:]
try:
pkg.add(filename_meta, arcname=filedest_meta)
logger.info(
"Adding app %s to package".format(appmeta.name)
)
except Exception as e:
logger.warning(
"Failed to add app {app} to tar package {pkg}: {err}" \
.format( app = appmeta.name,
pkg = pkgname,
err = str(e) )
)
for f in appmeta.files:
filename = os.path.join( persoconf.path,
appmeta.name,
f )
filedest = appmeta.files[f]["dest"]
filedest_pc = utils.contractuser( filename )
# Remove possible 'home'. The final targz should be extracted
# at $HOME.
if filedest.startswith("~/"):
filedest = filedest[2:]
if filedest_pc.startswith("~/"):
filedest_pc = filedest_pc[2:]
# TODO save and restore owners and permissions
try:
pkg.add(filename, arcname=filedest)
if not args.nopersoconf:
pkg.add(filename, arcname=filedest_pc)
logger.info("Adding %s to package" % f)
except Exception as e:
logger.warning( "Failed to add %s to tar package %s: %s"
% (f, pkgname, str(e)) )
pkg.close()

View File

@ -6,7 +6,7 @@ import os.path
import utils
from metafile import Metafile, MalformedMetafileError, NoPathDefinedError
# -----------------------------------------------------------------------------
# --------------------------------------
def init(parser):
parser.add_argument( "app" ,
@ -25,7 +25,7 @@ def init(parser):
"file/directory in the persoconf directory" )
# -----------------------------------------------------------------------------
# --------------------------------------
def run(args, persoconf, logger):
# Check (and create) the app directory
@ -51,6 +51,26 @@ def run(args, persoconf, logger):
if args.conf is None:
exit()
# Check that the conf file we are saving exists
if args.confname:
confname = args.confname
else:
confname = args.conf
# We need to remove trailing '/' because 'os.path.basename("toto/")'
# returns ''. And that's not cool.
while confname[-1] == os.path.sep :
confname = confname[:-1] # Remove trailing slashes
confname = os.path.basename(confname)
# Remove leading dots, so the name under which the conffile is saved is
# not hidden.
while confname[0] == ".":
confname = confname[1:] # Remove leading dots
confpath = appdir + "/" + confname
# Load app META file
try:
appmeta = Metafile( json_path = os.path.join( appdir ,
@ -87,51 +107,6 @@ def run(args, persoconf, logger):
% (absconf, dstdic[absconf], args.app) )
exit(1)
# Check that the conf file we are saving exists
if args.confname:
confname = args.confname
else:
confname = args.conf
# We need to remove trailing '/' because 'os.path.basename("toto/")'
# returns ''. And that's not cool.
while confname[-1] == os.path.sep :
confname = confname[:-1] # Remove trailing slashes
confname = os.path.basename(confname)
# Remove leading dots, so the name under which the conffile is saved is
# not hidden.
while confname[0] == ".":
confname = confname[1:] # Remove leading dots
# Check the conf file name
if ( confname == persoconf.metafile
or confname in appmeta.files ):
# If the confanme was user-defined, it's a serious error.
if args.confname:
logger.error( "Confname '%s' is already taken: try another one"
% confname )
exit(1)
# Otherwise it's cool, we just have to change the confname by ourselves
logger.info( "Confname '%s' is already taken: trying another one"
% confname )
ending = 1
while ( confname + "." + str(ending) == persoconf.metafile
or confname + "." + str(ending) in appmeta.files ):
ending += 1
confname = confname + "." + str(ending)
logger.info( "New confname is '%s'" % confname )
# Deduce the path from the conf file name
confpath = appdir + "/" + confname
# Copy the file (or directory)
if utils.copy_file_or_directory( logger ,
path_dst=confpath ,
@ -157,5 +132,3 @@ def run(args, persoconf, logger):
"defined"
% appmeta.name )
exit(1)
# TODO Add or update the app METAFILE in a 'persoconf' special app.

View File

@ -1,13 +1,12 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import os, os.path
import os.path
import filecmp
import subprocess
from metafile import Metafile, MalformedMetafileError
# -----------------------------------------------------------------------------
# --------------------------------------
def init(parser):
"""Initialize check subcommand"""
@ -24,21 +23,16 @@ def init(parser):
"added files" )
parser.add_argument( "-d", "--diff" ,
action="store_true",
type=str ,
help="Print the diff between the saved and the new " \
"files" )
"file/directory" )
# -----------------------------------------------------------------------------
# --------------------------------------
def run(args, persoconf, logger):
logger.debug("Starting 'check' command")
difftool = None
if args.diff:
difftool = os.environ.get('DIFFTOOL', 'vimdiff')
logger.info("Selecting %s as diff tool" % difftool)
# app == None => check all apps
apps_to_check = {}
if args.app is None:
@ -90,8 +84,7 @@ def run(args, persoconf, logger):
_compare_and_log( original_file_path,
persoconf_backup_path,
logger,
difftool )
logger )
# 2) If not, it must be a real filename
else:
@ -108,8 +101,7 @@ def run(args, persoconf, logger):
_compare_and_log( original_file_path,
persoconf_backup_path,
logger,
difftool )
logger )
# 3) Otherwise, no idea what it is
@ -133,15 +125,11 @@ def run(args, persoconf, logger):
_compare_and_log( original_file_path,
persoconf_backup_path,
logger,
difftool )
logger )
# -----------------------------------------------------------------------------
def _compare_and_log( original_file_path,
persoconf_backup_path,
logger,
difftool = None ):
# --------------------------------------
def _compare_and_log(original_file_path, persoconf_backup_path, logger):
"""Compare a file with its persoconf backup and log the result (at info
level if the files are matching, warning level if they are not)."""
@ -170,8 +158,6 @@ def _compare_and_log( original_file_path,
else:
logger.warning( "File %s was modified"
% original_file_path )
if difftool:
subprocess.call([ difftool, resolved_pbp, resolved_ofp ])
except FileNotFoundError as err:
if err.filename == resolved_pbp:

View File

@ -9,7 +9,7 @@ import utils
from metafile import Metafile, MalformedMetafileError
# -----------------------------------------------------------------------------
# --------------------------------------
def init(parser):
parser.add_argument( "app" ,
type=str ,
@ -28,7 +28,7 @@ def init(parser):
# -----------------------------------------------------------------------------
# --------------------------------------
def run(args, persoconf, logger):
# Does this app really exists ?

View File

@ -3,7 +3,7 @@
import os.path
# -----------------------------------------------------------------------------
# --------------------------------------
def init(parser):
parser.add_argument( "apps" ,
type=str ,
@ -20,7 +20,7 @@ def init(parser):
# -----------------------------------------------------------------------------
# --------------------------------------
def run(args, persoconf, logger):
app_list = persoconf.list_apps(logger=logger)

View File

@ -0,0 +1,101 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os.path
import time
import tarfile
from metafile import Metafile, MalformedMetafileError
# --------------------------------------
def init(parser):
parser.add_argument( "apps" ,
type=str ,
nargs="*" ,
default=[] ,
help="The apps to package ; defaults to all " \
"apps" )
parser.add_argument( "--pkgtype", "-t" ,
type=str ,
nargs="?" ,
default="tgz" ,
choices = [ "tgz" ] ,
help="The type of package to use" )
parser.add_argument( "--pkgname", "-p" ,
type=str ,
nargs="?" ,
default="persoconf." + (time.strftime("%Y%m%d")) ,
help="The name of the package to create" )
# --------------------------------------
def run(args, persoconf, logger):
pkgname = args.pkgname
# If no app is given: package all apps
if args.apps == []:
logger.info("Packaging all apps")
args.apps = persoconf.list_apps(logger=logger)
# Some apps were specified, so we list them with their files
appmetas = []
for app in args.apps :
# Load app META file
try:
appmetas.append(
Metafile(json_path = os.path.join(
persoconf.path ,
app ,
persoconf.metafile)
)
)
except FileNotFoundError:
logger.warning( "Metafile %s does not exist for app %s; ignoring"
% (persoconf.metafile, app) )
continue
except MalformedMetafileError:
logger.warning( "Malformed metafile %s in app %s; ignoring"
% (persoconf.metafile, app) )
continue
# TODO check that the app exists
# Adds the app name if there is only one packaged
if len(appmetas) == 1:
pkgname += ("." + appmetas[0].name)
# Switch according to the pkgtype
if args.pkgtype == "tgz":
pkgname += ".tgz"
# Create a tar containing the files in the right place
pkg = tarfile.open(pkgname, "w:gz")
for appmeta in appmetas:
for f in appmeta.files:
filename = persoconf.path +"/"+ appmeta.name +"/"+ f
filedest = appmeta.files[f]["dest"]
# Remove possible 'home'. The final targz should be extracted
# at $HOME.
if filedest.startswith("~/"):
filedest = filedest[2:]
# TODO save and restore owners and permissions
try:
pkg.add(filename, arcname=filedest)
logger.info("Adding %s to package" % f)
except Exception as e:
logger.warning( "Failed to add %s to tar package %s: %s"
% (f, pkgname, str(e)) )
pkg.close()

View File

@ -6,7 +6,7 @@ import os.path
import utils
from metafile import Metafile, MalformedMetafileError
# -----------------------------------------------------------------------------
# --------------------------------------
def init(parser):
parser.add_argument( "app" ,
@ -21,7 +21,7 @@ def init(parser):
help="The files to update ; default to all " \
"added files" )
# -----------------------------------------------------------------------------
# --------------------------------------
def run(args, persoconf, logger):
# app == None => update all apps

View File

@ -21,9 +21,8 @@ import commands.list, commands.package, commands.update, commands.add, \
# ARGPARSE
##############################################################################
########################################
# Argument parsing using argparse
# =============================================================================
parser = argparse.ArgumentParser()
parser.add_argument( "--rootdir" ,
@ -45,24 +44,20 @@ subparsers = parser.add_subparsers( dest="command",
title="commands" )
# Help
# -----------------------------------------------------------------------------
parser_help = subparsers.add_parser( "help" ,
help="Print this help" )
# List
# -----------------------------------------------------------------------------
parser_list = subparsers.add_parser( "list" ,
help="List backuped apps" )
commands.list.init(parser_list)
# Package
# -----------------------------------------------------------------------------
parser_package = subparsers.add_parser( "package" ,
help="Package a persoconf repository" )
commands.package.init(parser_package)
# Update
# -----------------------------------------------------------------------------
parser_update = subparsers.add_parser( "update" ,
help="Backup an app configuration in " \
"the persoconf directory with " \
@ -70,7 +65,6 @@ parser_update = subparsers.add_parser( "update",
commands.update.init(parser_update)
# Add
# -----------------------------------------------------------------------------
parser_add = subparsers.add_parser( "add" ,
help="Add an app to the persoconf " \
"directory, or add a config file " \
@ -78,14 +72,12 @@ parser_add = subparsers.add_parser( "add",
commands.add.init(parser_add)
# Check
# -----------------------------------------------------------------------------
parser_check = subparsers.add_parser( "check" ,
help="Checks if the backuped and the " \
"original file are different." )
commands.check.init(parser_check)
# Delete
# -----------------------------------------------------------------------------
parser_del = subparsers.add_parser( "delete" ,
help="Delete an app from the persoconf " \
"directory, or a config file from " \
@ -93,14 +85,13 @@ parser_del = subparsers.add_parser( "delete",
commands.delete.init(parser_del)
# Parse arguments
# =============================================================================
args = parser.parse_args()
# Transform paths
args.rootdir = os.path.expanduser(args.rootdir)
# LOGGING
##############################################################################
########################################
log = logging.getLogger("persoconf")
log.setLevel(logging.DEBUG)
@ -119,14 +110,13 @@ log_handler_console.setFormatter(log_formatter_console)
log.addHandler(log_handler_console)
# SCRIPT
##############################################################################
########################################
# If we were asked for help, let's give some
if args.command in [ "help", None ]:
parser.print_help()
exit()
# Try to create the Persoconf
# =============================================================================
try:
persoconf = Persoconf(path=args.rootdir, metafile=args.metafile)
@ -161,31 +151,38 @@ except PersoconfRootDoesNotExistError:
exit()
# LIST COMMAND
# =============================================================================
########################################
if args.command == "list":
commands.list.run(args, persoconf, log)
# DELETE COMMAND
# =============================================================================
########################################
if args.command == "delete":
commands.delete.run(args, persoconf, log)
# ADD COMMAND
# =============================================================================
########################################
if args.command == "add":
commands.add.run(args, persoconf, log)
# CHECK COMMAND
# =============================================================================
########################################
if args.command == "check":
commands.check.run(args, persoconf, log)
# UPDATE COMMAND
# =============================================================================
########################################
if args.command == "update":
commands.update.run(args, persoconf, log)
# PACKAGE COMMAND
# =============================================================================
########################################
if args.command == "package":
commands.package.run(args, persoconf, log)
########################################