Python Forum
bash to python: keep.py
Thread Rating:
  • 1 Vote(s) - 4 Average
  • 1
  • 2
  • 3
  • 4
  • 5
bash to python: keep.py
#1
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
#!/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.
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  A bash script that is a hack to enable very easy Python Imports vedant13 2 3,120 Aug-04-2018, 06:10 AM
Last Post: wavic

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020