Apr-08-2017, 07:27 AM
this is my latest rewrite of a bash script which is a command called keep which is a quick command to keep a spare copy of a file. i am already using the python version/
keep.py
keep.py
#!/usr/bin/env python3 # -*- coding: utf-8 -*- from __future__ import absolute_import, division, print_function, unicode_literals """ file keep.py purpose keep file(s) in a date-time stamped named copy. author Phil D. Howard email 10054452614123394844460370234029112340408691 The intent is that this code works correctly under both Python 2 and Python 3. Please report failures or code improvement to the author. """ __license__ = """ Copyright (C) 2017, by Phil D. Howard - all other rights reserved Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA, OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE, OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. The author may be contacted by decoding the number 10054452614123394844460370234029112340408691 (provu igi la numeron al duuma) """ import os from filecmp import cmp from io import IOBase from pwd import getpwnam, getpwuid from sys import argv, stderr, stdout, version_info from shutil import copy2 from time import gmtime if version_info.major<3: ints=(int,long) strs=(str,unicode,) stru=(str,unicode,) else: ints=(int,) strs=(str,bytes,bytearray,) stru=(str,) zext = ('bz2','gz','xz','lz','zip','bz') fmt = '%04d-%02d-%02d-%02d%02d%02d-%09d' def pyconfig(*args,**opts): """Get settings from a Python style config file. function pyconfig purpose get settings from a python style config file arguments not used, must be omitted options home= explicit home directory to use maxsize= maximum file size name= file name below directories subdir= additional sub-directory below topdir topdir= directory (usually dot prefixed) below home uid= user id, uses its home directory purpose get settings from config file in python format returns dictionary with config settings note this function needs updating to support windows. """ if len(args)>0: m='positional arguments are not used: ' m+=', '.join([repr(a) for a in args]) raise Exception(m) uid=opts.pop('uid',os.getuid()) home=opts.pop('home',getpwuid(uid).pw_dir) maxsize=opts.pop('maxsize',1048576) if 'path' in opts: p=opts['path'] else: p=home if 'topdir' in opts: p += os.path.sep + opts.pop('topdir') if 'subdir' in opts: p += os.path.sep + opts.pop('subdir') p += os.path.sep + opts.pop('name') if len(opts)>0: m='unknown option' if len(opts)>1: m+='s' m+=': '+', '.join([str(n)+'='+repr(v) for n,v in opts.items()]) raise Exception(m) try: with open(p) as f: s=f.read(maxsize+1) except: s='pass' if len(s)>maxsize: m='config file {} size is larger than {}' raise Exception(m.format(repr(p),repr(maxsize))) c={} exec(s,c) del c['__builtins__'] return c def keepfile( *args, **opts ): """Save timestamped versions of the specified file(s) in the """ """designated keep location. function keepfile purpose save the specified file in the designated keep location arguments filename of file to make options altname= use this insyead to name saved file dir= dir name for saving file file= file to print to home= substitute for home directory user= alternate user name for home verbose= verbosity level for messages """ sdir = opts.pop('dir' ,None) # directory name (rel to home or abs) file = opts.pop('file' ,None) # open file to print to home = opts.pop('home' ,None) # substitute for home directory prefix = opts.pop('prefix' ,None) suffix = opts.pop('suffix' ,None) user = opts.pop('user' ,None) # alternate user name for home verbose = opts.pop('verbose',None) # verbosity level for messages errfile = file if isinstance(file,IOBase) else stderr rc = 0 # be sure prefix and suffix are strings if not isinstance(prefix,strs): prefix = '' if not isinstance(suffix,strs): suffix = '' # confine verbosity setting to 0..100 with a default of 40 if not isinstance(verbose,ints): verbose = 40 if verbose > 100: verbose = 100 if verbose < 0: verbose = 0 # report any unknown options l = len(opts) if l > 0 and isinstance( errfile, IOBase ) and verbose > 39: msg='%d unknown option%s passed to keepfile().' % \ (l,'' if l==1 else 's') print( msg, file=errfile ) for k,v in opts.items(): print('unknown option: %s=%s'% (str(k),repr(v)),file=errfile) raise Exception( msg ) # per each given file to make a list of existing files. fns = [] for arg in args: fn = prefix + arg + suffix if os.path.isfile( fn ): fns.append( fn ) if len(fns) < 1: return 0 # if home is not specified, find where it really is if not isinstance(home,strs): if 'HOME' in os.environ: home = os.environ['HOME'] else: if isinstance(user,strs): home = opts.pop('home', getpwnam(user).pw_sdir) else: home = opts.pop('home', getpwuid(os.getuid()).pw_sdir) # if dir= is specified, the file(s) will be saved there, # else the saved copy is in the same place as the source. if isinstance(sdir,strs): rdir = 1 # relative to {1: curr 2: home 3: root} directory if sdir[0] == os.path.sep: rdir = 3 if sdir[:2] == '.'+os.path.sep: sdir = sdir[2:] rdir = 1 if sdir[:2] == '~'+os.path.sep: sdir = sdir[2:] rdir = 2 if sdir[:6] == '$HOME'+os.path.sep: sdir = sdir[6:] rdir = 2 if sdir[:8] == '${HOME}'+os.path.sep: sdir = sdir[8:] rdir = 2 # clean directory path of // and ./ sequences sdir = os.path.normpath( sdir ) # normpath() does not reduce a case of 2 seps in front if sdir[0:1] == os.path.sep*2: sdir = sdir[1:] if rdir == 2: if not isinstance(home,strs): raise Exception( 'home cannot be determinend' ) sdir = os.path.join( home, sdir ) else: sdir = None # per each existing file: for arg in fns: # generate a timestamp from the st_mtime_ns valuefo this file fn = prefix + arg + suffix stat_info = os.lstat( fn ) if 'st_mtime_ns' in dir(stat_info): have_ns = True ns = stat_info.st_mtime_ns % 1000000000 else: have_ns = False ns = 0 sec = int( stat_info.st_mtime ) parts = gmtime(sec)[:6] + (ns,) ts = fmt % parts if not have_ns: ts = '-'.join(ts.split('-')[:-1]) # remove one end bunch # if the timestamp is already in the src name then skip it if ts in fn: if isinstance( file, IOBase ) and verbose > 19: print( repr(fn), '-- skipped', file=file ) continue # determine where to add the timestamp in the base part and do so parts = os.path.basename(fn).split('.') xts = len(parts)-1 if xts > 0: xts -= 1 if xts > 0: if len(parts[-1])==0 or parts[-1] in zext: xts -= 1 if len(parts[xts]) > 0: parts[xts] += '-' parts[xts] += ts kfn = '.'.join(parts) if kfn[-1] == '~': kfn = kfn[:-1] if sdir: kfn = os.path.join( sdir, kfn ) try: if not os.path.islink(kfn) and \ os.path.isfile(kfn) and \ cmp(fn,kfn,shallow=True) and \ cmp(fn,kfn,shallow=False): if isinstance( file, IOBase ) and verbose > 19: print( repr(fn), '==', repr(kfn), file=file ) else: if os.name == 'posix': tmp = '.tmp.' + str( os.getpid() ) if os.path.lexists(kfn+tmp): os.remove(kfn+tmp) copy2(fn,kfn+tmp) os.rename(kfn+tmp,kfn) else: copy2(fn,kfn) if isinstance( file, IOBase ) and verbose > 19: print( repr(fn), '->', repr(kfn), file=file ) except: rc += 1 return rc def main(args): """main""" config = pyconfig( maxsize=4095, name='config.py', subdir='keep', topdir='.config' ) fns = [] opts = { 'out': stderr, 'verbose': 20 } sep = config.pop('sep',os.path.sep) keepdir = config.pop('keepdir',None) if not keepdir: keepdir = config.pop('dir',None) if keepdir: parts = keepdir.split(sep) home = ''.join(parts[:1]) base = ''.join(parts[1:]) if home in ('~','$HOME','${HOME}'): home = os.environ['HOME'] opts['keepdir'] = os.path.join( home, base ) xnf = 0 invalid = None for fn in args[1:]: for part in fn.split(sep): if part in ('.','..'): invalid = 'is an invalid name' xnf += 1 if invalid: continue if not os.path.exists(fn): invalid = 'does not exist' continue if not os.path.isfile(fn): invalid = 'is not a regular file' continue if invalid: print( 'file', repr(fn), invalid, file=stderr ) stderr.flush() xnf += 1 else: fns.append( fn ) if xnf > 0: print( str( xnf ), 'file', 's' if xnf == 1 else '', 'not found', file=stderr ) print( 'file not' if len(args) == 2 else 'NO files', 'kept,', 'aborting', file=stderr ) return 1 rc = 1 v = 50 file = stderr for fn in args[1:]: try: keepfile( fn, dir=keepdir, file=file, verbose=v ) except BrokenPipeError: file=None v=0 keepfile( fn, dir=keepdir, file=file, verbose=v ) return rc if __name__ == '__main__': try: result=main(argv) stdout.flush() except BrokenPipeError: result=99 except KeyboardInterrupt: print('') result=98 if result is 0 or result is None or result is True: exit(0) if result is 1 or result is False: exit(1) if isinstance(result,strs): print(result,file=stderr) exit(1) try: exit(int(result)) except ValueError: print(str(result),file=stderr) except TypeError: exit(127) # EOF
Tradition is peer pressure from dead people
What do you call someone who speaks three languages? Trilingual. Two languages? Bilingual. One language? American.
What do you call someone who speaks three languages? Trilingual. Two languages? Bilingual. One language? American.