initial checkin of date2name v0.0.5

This commit is contained in:
vk 2009-04-06 16:02:34 +02:00
commit 5de2a7cb9b
3 changed files with 462 additions and 0 deletions

307
date2name Executable file
View file

@ -0,0 +1,307 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Latest change: Fri Mar 27 00:27:30 CET 2009
"""
date2name
~~~~~~~~~
This script adds (or removes) datestamps to (or from) file(s)
:copyright: (c) 2009 by Karl Voit <tools@Karl-Voit.at>
:license: GPL v2 or any later version
:bugreports: <tools@Karl-Voit.at>
"""
import re, os, time, logging, sys
from optparse import OptionParser
# global variables
PROG_VERSION = "0.0.5"
FORMATSTRING_COMPACT = "%Y%m%d"
FORMATSTRING_STANDARD = "%Y-%m-%d"
FORMATSTRING_MONTH = "%Y-%m"
FORMATSTRING_WITHTIME = "%Y-%m-%d_%H:%M:%S"
NODATESTAMP_PATTERN = re.compile('^\D')
COMPACT_PATTERN = re.compile('^\d{4,4}[01]\d[0123]\d[- _]')
STANDARD_PATTERN = re.compile('^\d{4,4}-[01]\d-[0123]\d[- _]')
MONTH_PATTERN = re.compile('^\d{4,4}-[01]\d[- _]')
WITHTIME_PATTERN = re.compile('^\d{4,4}-[01]\d-[0123]\d[ :_-][012]\d:[012345]\d:[012345]\d[- _]')
# cmdline parsing
USAGE = "\n\
%prog [options] file ...\n\
\n\
Per default, %prog gets the modification time of matching files\n\
and directories and adds a datestamp in standard ISO 8601+ format\n\
YYYY-MM-DD (http://datestamp.org/index.shtml) at the beginning of\n\
the file- or directoryname.\n\
If an existing timestamp is found, its style will be converted to the\n\
selected ISO datestamp format but the numbers stays the same.\n\
Executed with an examplefilename \"file\" this results e.g. in\n\
\"2008-12-31_file\".\n\
\n\
Run %prog --help for usage hints"
# pylint: disable-msg=C0103
parser = OptionParser(usage=USAGE)
parser.add_option("-d", "--directories", dest="onlydirectories",
action="store_true",
help="modify only directory names")
parser.add_option("-f", "--files", dest="onlyfiles",
action="store_true",
help="modify only file names")
parser.add_option("--compact", dest="compact",
action="store_true",
help="use compact datestamp (YYYYMMDD)")
parser.add_option("--month", dest="month",
action="store_true",
help="use datestamp with year and month (YYYY-MM)")
parser.add_option("--withtime", dest="withtime",
action="store_true",
help="use datestamp including seconds (YYYY-MM-DD_hh:mm:ss)")
# parser.add_option("-r", "--remove", dest="remove",
# action="store_true",
# help="remove all known datestamps")
parser.add_option("-m", "--mtime", dest="mtime",
action="store_true",
help="take modification time for datestamp [default]")
parser.add_option("-c", "--ctime", dest="ctime",
action="store_true",
help="take creation time for datestamp")
parser.add_option("-q", "--quiet", dest="quiet", action="store_true",
help="do not output anything but just errors on console")
parser.add_option("-v", "--verbose", dest="verbose", action="store_true",
help="enable verbose mode")
parser.add_option("--nocorrections", dest="nocorrections", action="store_true",
help="do not convert existing datestamps to new format")
parser.add_option("-s", "--dryrun", dest="dryrun", action="store_true",
help="enable dryrun mode: just simulate what would happen, do not modify files or directories")
parser.add_option("--version", dest="version", action="store_true",
help="display version and exit")
(options, args) = parser.parse_args()
def handle_logging():
"""Log handling and configuration"""
if options.verbose:
FORMAT = "%(levelname)-8s %(asctime)-15s %(message)s"
logging.basicConfig(level=logging.DEBUG, format=FORMAT)
elif options.quiet:
FORMAT = "%(levelname)-8s %(message)s"
logging.basicConfig(level=logging.CRITICAL, format=FORMAT)
else:
FORMAT = "%(message)s"
logging.basicConfig(level=logging.INFO, format=FORMAT)
def get_converted_itemname(matched_pattern, item):
"""returns a new filename based on found timestamp information and currently selected datestamp format"""
if matched_pattern == "compact":
logging.debug("item \"%s\" matches compact pattern, doing conversion" % item)
item_year = item[0:4]
item_month = item[4:6]
item_day = item[6:8]
name_without_datestamp = item[8:]
elif matched_pattern == "standard":
logging.debug("item \"%s\" matches standard pattern, doing conversion" % item)
item_year = item[0:4]
item_month = item[5:7]
item_day = item[8:10]
name_without_datestamp = item[10:]
elif matched_pattern == "month":
logging.debug("item \"%s\" matches month pattern, doing conversion" % item)
item_year = item[0:4]
item_month = item[5:7]
logging.info("%s no datestamp information for day, so I will take \"00\" for conversion" % item)
item_day = "00"
name_without_datestamp = item[7:]
elif matched_pattern == "withtime":
logging.debug("item \"%s\" matches withtime pattern, doing conversion" % item)
item_year = item[0:4]
item_month = item[5:7]
item_day = item[8:10]
logging.warn("%s ... time will be lost due to conversion" % item)
name_without_datestamp = item[19:]
else:
logging.error("unknown matched pattern (internal error)")
logging.debug("item \"%s\" got year \"%s\" month \"%s\" day \"%s\"" % (item, item_year, item_month, item_day))
if options.compact:
return item_year + item_month + item_day + name_without_datestamp
elif options.month:
return item_year + "-" + item_month + name_without_datestamp
elif options.withtime:
## FIXXME: probably implement some kind of conversion to withtime-format
logging.warn("%s: Sorry! Conversion to withtime-format not implemented yet, taking standard format" % item)
return item_year + "-" + item_month + "-" + item_day + name_without_datestamp
else:
return item_year + "-" + item_month + "-" + item_day + name_without_datestamp
def get_timestamp_from_file(formatstring, item):
"""read out ctime or mtime of file and return new itemname"""
if options.ctime:
return time.strftime(formatstring, time.localtime( os.path.getctime(item) ) ) + "_" + item
elif options.mtime:
return time.strftime(formatstring, time.localtime( os.path.getmtime(item) ) ) + "_" + item
else:
logging.error("internal error: not ctime nor mtime chosen! You can correct this by giving a parameter")
def generate_new_itemname(formatstring, item):
"""generates the new itemname; considering options.nocorrections"""
if options.nocorrections or NODATESTAMP_PATTERN.match( item ):
logging.debug("item \"%s\" matches nodatestamp-pattern or option nocorrections is set: skipping further pattern matching" % item)
new_itemname = get_timestamp_from_file(formatstring, item)
elif WITHTIME_PATTERN.match( item ):
logging.debug("item \"%s\" matches withtime-pattern" % item)
if options.withtime:
logging.debug("old pattern is the same as the recognised, itemname stays the same")
return item
else:
new_itemname = get_converted_itemname("withtime", item)
elif STANDARD_PATTERN.match( item ):
logging.debug("item \"%s\" matches standard-pattern" % item)
if not options.withtime and not options.compact and not options.month:
logging.debug("old pattern is the same as the recognised, itemname stays the same")
return item
else:
new_itemname = get_converted_itemname("standard", item)
elif COMPACT_PATTERN.match( item ):
logging.debug("item \"%s\" matches compact-pattern" % item)
if options.compact:
logging.debug("old pattern is the same as the recognised, itemname stays the same")
return item
else:
new_itemname = get_converted_itemname("compact", item)
elif MONTH_PATTERN.match( item ):
logging.debug("item \"%s\" matches month-pattern" % item)
if options.month:
logging.debug("old pattern is the same as the recognised, itemname stays the same")
return item
else:
new_itemname = get_converted_itemname("month", item)
else:
logging.debug("item \"%s\" does not match any known datestamp-pattern" % item)
new_itemname = get_timestamp_from_file(formatstring, item)
logging.debug("new itemname is \"%s\"" % new_itemname)
return new_itemname
def handle_item(item, formatstring):
"""Handle timestamp adding or removing with directories or files"""
options.remove = False
if options.remove:
logging.debug("removing timestamp from file \"%s\"" % item)
## FIXXME: implement datestamp removing
logging.error("Sorry! Removing of datestamps is not yet implemented")
else:
logging.debug("#######################+++~~- adding timestamp to file \"%s\"" % item)
if options.onlyfiles and os.path.isdir(item):
logging.debug("skipping directory \"%s\" because of command line option \"-f\"" % item)
return
if options.onlydirectories and os.path.isfile(item):
logging.debug("skipping file \"%s\" because of command line option \"-d\"" % item)
return
new_itemname = generate_new_itemname(formatstring, item)
logging.debug("new itemname for \"%s\" will be \"%s\"" % ( item, new_itemname ))
if options.dryrun:
if item == new_itemname:
logging.info("%s ... no modification" % item)
else:
logging.info("%-40s > %s" % ( item, new_itemname ) )
else:
if item == new_itemname:
logging.info("\"%s\" ... no modification" % item)
else:
logging.debug("\"%s\" > \"%s\"" % ( item, new_itemname ) )
logging.info("%-40s > %s" % ( item, new_itemname ) )
os.rename(item, new_itemname)
def main():
"""Main function [make pylint happy :)]"""
if options.version:
print os.path.basename(sys.argv[0]) + " " + PROG_VERSION
sys.exit(0)
if len(args) < 1:
parser.error("invalid usage")
if ( options.verbose and options.quiet ):
parser.error("please use either verbose (--verbose) or quiet (-q) option")
if ( options.onlyfiles and options.onlydirectories ):
parser.error("please use either option files (-f) or option directories (-f) or none of them (for renaming directories and files)")
if ( options.ctime and options.mtime ):
parser.error("please use either ctime (-c) or mtime (-m) option")
if ( not options.ctime and not options.mtime ):
options.mtime = True
if ( options.compact and options.withtime ) \
or ( options.compact and options.month) \
or ( options.month and options.withtime):
parser.error("please use either the default, short, month, or withtime format")
# log handling
handle_logging()
filelist = args[0:]
logging.debug("filelist: [%s]" % filelist)
if options.compact:
formatstring = FORMATSTRING_COMPACT
elif options.month:
formatstring = FORMATSTRING_MONTH
elif options.withtime:
formatstring = FORMATSTRING_WITHTIME
else:
logging.debug("no option given for format string; taking standard format")
formatstring = FORMATSTRING_STANDARD
for item in filelist:
if os.path.isdir(item):
logging.debug("is directory: %s" % item)
handle_item(item, formatstring)
elif os.path.isfile(item):
logging.debug("is file: %s" % item)
handle_item(item, formatstring)
else:
logging.critical("%s: is no file or directory (broken link?)" % item)
if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
logging.info("Received KeyboardInterrupt")
## END OF FILE #################################################################
# vim:foldmethod=indent expandtab ai ft=python tw=120 fileencoding=utf-8 shiftwidth=4

134
date2name.1.txt Normal file
View file

@ -0,0 +1,134 @@
date2name(1)
==========
Name
----
date2name - add datestamps to file- and directorynames
Synopsis
--------
date2name [ options ] <file[s]>
Introduction
------------
date2name adds ISO 8601+ compatible datestamps (YYYY-MM-DD,
link:http://datestamp.org/index.shtml[ISO 8601+ webpage]) to file- and
directorynames or converts datestamps between known datestamp formats.
Per default, date2name uses the modification time of matching files and
directories to add a datestamp at the beginning of the file- or directoryname.
Executed with an examplefilename "file" this results e.g. in "2008-12-31_file"
if the 31st of december 2008 is the modification time of the file.
If an existing timestamp is found, its style will be converted to the selected
ISO datestamp format but the numbers stays the same.
Executed with an examplefilename "20071130-file", date2name reformats the
existing datestamp to the (default) datestamp format. In this example it
results in "2007-11-30-file".
There are various options to modify the default behaviour of date2name.
Options
-------
The following options are supported:
*-h*, *--help*::
Display usage information and exit.
*-d*, *--directories*::
Modify only directory names. Default is: modify directory names as well as file
names (including links).
*-f*, *--files*::
Modify only file names including links. Default is: modify directory names as
well as file names.
*--compact*::
Use compact datestamp format: YYYYMMDD
*--month*::
Use datestamp format with year and month: YYYY-MM
*--withtime*::
Use long datestamp format including timestamp: YYYY-MM-DD_hh:mm:ss
*-m*, *--mtime*::
Use modification time for generating new datestamps. (default)
*-c*, *--ctime*::
Use creation time for generating new datestamps.
*-q*, *--quiet*::
Do not output anything but just errors on console.
*-v*, *--verbose*::
Be verbose when writing output. Good for investigating unwanted behaviour in
combination with dryrun mode.
*-s*, *--dryrun*::
Do not modify any names, just simulate a dry run.
*--nocorrections*::
Do not modify existing datestamps, just add a datestamp to each item given.
*--version*::
Print out version information and exit.
Usage examples
--------------
# date2name -fs *pdf
Simulate, what datestamps would be added to all files with the extension "pdf".
# date2name -f *pdf
Add datestamps to all files with the extension "pdf" in the current directory.
# date2name *
Add datestamp to all files and directories in the current directory. If any
known datestamp is found, modify the format to the default ISO format
YYYY-MM-DD.
# date2name --withtime procmail.log
Add a long datestamp (including timestamp) to the file "procmail.log".
# date2name --files --month 20*
Add datestamps to all files beginning with "20" in the current directory in the
format YYYY-MM or (more likely due to the "20"-condition) modify known
datestamps to the YYYY-MM one.
Online Ressources
-----------------
Check out the link:http://Karl-Voit.at/scripts/#date2name[date2name webpage].
Bugs
----
Please report feedback, bugreports and wishes <<X7,to the author>>.
[[X7]]
Author
------
Karl Voit <tools@Karl-Voit.at>

21
todos.txt Normal file
View file

@ -0,0 +1,21 @@
-++########## Latest change: Wed Mar 11 11:01:12 CET 2009
-++####################### closed issues ################################++-
-++####################### open issues ##################################++-
[20090406][][][] renaming items with timestamp *only*
item = "20081231" -> "2008-12-31_20081231
reason:
itemname is not recognised as datestamp because of missing space or dash or underscore after the datestamp.
-++####################### permanent entries ############################++-
-++####################### End ##########################################++-
%%% Local Variables:
## vim:foldmethod=expr
## vim:fde=getline(v\:lnum)=~'^-\+\+#\\{10\\}'?0\:getline(v\:lnum)=~'^\\%([[].*[]]\\)\\{4\\}'?'>1'\:'1':