diff --git a/LICENSE.md b/LICENSE.md
new file mode 100644
index 0000000..7d57ea0
--- /dev/null
+++ b/LICENSE.md
@@ -0,0 +1,27 @@
+# License
+
+***Nomen*** - Multi-purpose rename tool
+
+Copyright © 2018 by Ralf Kilian
+
+Distributed under the *MIT License*:
+
+```
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+```
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..8f34655
--- /dev/null
+++ b/README.md
@@ -0,0 +1,61 @@
+# *Nomen*
+
+**Table of contents**
+* [Definition](#definition)
+* [Details](#details)
+* [Requirements](#requirements)
+* [Documentation](#documentation)
+* [Useless facts](#useless-facts)
+
+----
+
+## Definition
+
+The *Nomen* project is a multi-purpose rename tool to consistently rename the base name as well as the extension of files in a variety of ways and also to remove unnecessary whitespaces from directory names.
+
+[Top](#nomen-)
+
+## Details
+
+Basically, *Nomen* allows to consistently rename the base name as well as the extension of files and to remove unnecessary whitespaces from directory names.
+
+For now, it is capable of the following, briefly stated:
+
+* Convert the case of the base name (prefix or stem) of files.
+* Convert the case of the extension (suffix) of files.
+* Adjust differently spelled extensions from files of the same file type.
+* Rename base names based on the name of the directory where the files are stored in.
+* Modify base names by adding, removing or replacing certain strings.
+* Remove leading, trailing and duplicate whitespaces from directory names.
+
+It also comes with an integrated simulation mode that simulates the rename process and writes the details into a report file. This allows checking which files would have been renamed.
+
+The project also consists of [multiple components](../../wiki#components).
+
+Please be sure to read [this](../../wiki#important-notice) before using *Nomen*.
+
+[Top](#nomen-)
+
+## Requirements
+
+In order to use *Nomen*, the *Python* framework must be installed on the system.
+
+Depending on which version of the framework you are using:
+
+* *Python* 2.x (version 2.7 or higher is recommended, may also work with earlier versions)
+* *Python* 3.x (version 3.2 or higher is recommended, may also work with earlier versions)
+
+[Top](#nomen-)
+
+## Documentation
+
+In the corresponding `docs` sub-directories, there are plain text files containing a detailed documentation for each component with further information and usage examples.
+
+[Top](#nomen-)
+
+## Useless facts
+
+Whoever cares can find them [here](../../wiki#useless-facts).
+
+[Top](#nomen-)
+
diff --git a/nomen.png b/nomen.png
new file mode 100644
index 0000000..bc89e91
Binary files /dev/null and b/nomen.png differ
diff --git a/python2/cfg/sample_lower.cfg b/python2/cfg/sample_lower.cfg
new file mode 100644
index 0000000..1e79765
--- /dev/null
+++ b/python2/cfg/sample_lower.cfg
@@ -0,0 +1,67 @@
+##############################################################################
+# #
+# Nomen sample configuration file for strings that should ALWAYS be #
+# LOWERCASE when converting the case of file names. #
+# #
+# This will NOT convert the case of the file extension. For renaming the #
+# extension, you may use the Nomen Extention Case Converter script. #
+# #
+# One string per line, empty and commented out lines (like this header) #
+# will be ignored. #
+# #
+# Please read the documentation before using any component of Nomen. #
+# #
+##############################################################################
+
+# Example 1:
+#
+# The word "foo" shall always be lowercase in file names.
+#
+# FOOBAR.TXT => file will NOT be renamed (because "FOO" is not a
+# separate word)
+# FOO BAR.TXT => file will be renamed to "foo BAR.txt"
+#
+foo
+
+# Example 2:
+#
+# The string "foo" shall always be lowercase in file names.
+#
+# FOOBAR.TXT => file will be renamed to "fooBAR.txt"
+# FOO BAR.TXT => file will be renamed to "foo BAR.txt"
+#
+$(foo)
+
+# Example 3:
+#
+# The string "bar" shall always be lowercase in file names.
+#
+# FOOBAR.TXT => file will be renamed to "FOObar.txt"
+# FOO BAR.TXT => file will be renamed to "FOO bar.txt"
+#
+$(bar)
+
+# Example 4:
+#
+# When converting the case of the files
+#
+# FOOBAR'S ADVENTURE.TXT
+# MARTIN O'SULLIVAN.TXT
+#
+# using title case, the file will be renamed as follows:
+#
+# Foobar'S Adventure.TXT
+# Martin O'Sullivan.TXT
+#
+# So, if you want to convert the "'S" to lowercase, you could use
+#
+# $('s)
+#
+# but this will also rename "O'Sullivan" to "O'sullivan". To avoid this you
+# may use the following statement. It only converts "'S" to lowercase if a
+# whitespace follows.
+#
+$('s )
+
+# EOF
+
diff --git a/python2/cfg/sample_mixed.cfg b/python2/cfg/sample_mixed.cfg
new file mode 100644
index 0000000..7a42e96
--- /dev/null
+++ b/python2/cfg/sample_mixed.cfg
@@ -0,0 +1,67 @@
+##############################################################################
+# #
+# Nomen sample configuration file for strings that should ALWAYS be MIXED #
+# CASE when converting the case of file names. #
+# #
+# This will NOT convert the case of the file extension. For renaming the #
+# extension, you may use the Nomen Extention Case Converter script. #
+# #
+# One string per line, empty and commented out lines (like this header) #
+# will be ignored. #
+# #
+# Please read the documentation before using any component of Nomen. #
+# #
+##############################################################################
+
+# Example 1:
+#
+# The word "FoO" shall always be mixed case in file names.
+#
+# FOOBAR.TXT => file will NOT be renamed (because "FOO" is not a
+# separate word)
+# FOO BAR.TXT => file will be renamed to "FoO BAR.txt"
+#
+FoO
+
+# Example 2:
+#
+# The string "FoO" shall always be mixed case in file names.
+#
+# FOOBAR.TXT => file will be renamed to "FoOBAR.txt"
+# FOO BAR.TXT => file will be renamed to "FoO BAR.txt"
+#
+$(FoO)
+
+# Example 3:
+#
+# The string "bAr" shall always be mixed case in file names.
+#
+# FOOBAR.TXT => file will be renamed to "FOObAr.txt"
+# FOO BAR.TXT => file will be renamed to "FOO bAr.txt"
+#
+$(bAr)
+
+# Example 4:
+#
+# When converting the case of the files
+#
+# FOOBAR'S ADVENTURE.TXT
+# MARTIN O'SULLIVAN.TXT
+#
+# using title case, the file will be renamed as follows:
+#
+# Foobar'S Adventure.TXT
+# Martin O'Sullivan.TXT
+#
+# So, if you want to convert the "'S" to lowercase, you could use
+#
+# $('s)
+#
+# but this will also rename "O'Sullivan" to "O'sullivan". To avoid this you
+# may use the following statement. It only converts "'S" to lowercase if a
+# whitespace follows.
+#
+$('s )
+
+# EOF
+
diff --git a/python2/cfg/sample_title.cfg b/python2/cfg/sample_title.cfg
new file mode 100644
index 0000000..2eecc26
--- /dev/null
+++ b/python2/cfg/sample_title.cfg
@@ -0,0 +1,45 @@
+##############################################################################
+# #
+# Nomen sample configuration file for strings that should ALWAYS be TITLE #
+# CASE when converting the case of file names. #
+# #
+# This will NOT convert the case of the file extension. For renaming the #
+# extension, you may use the Nomen Extention Case Converter script. #
+# #
+# One string per line, empty and commented out lines (like this header) #
+# will be ignored. #
+# #
+# Please read the documentation before using any component of Nomen. #
+# #
+##############################################################################
+
+# Example 1:
+#
+# The word "Foo" shall always be title case in file names.
+#
+# FOOBAR.TXT => file will NOT be renamed (because "FOO" is not a
+# separate word)
+# FOO BAR.TXT => file will be renamed to "Foo BAR.txt"
+#
+Foo
+
+# Example 2:
+#
+# The string "Foo" shall always be title case in file names.
+#
+# FOOBAR.TXT => file will be renamed to "FooBAR.txt"
+# FOO BAR.TXT => file will be renamed to "Foo BAR.txt"
+#
+$(Foo)
+
+# Example 3:
+#
+# The string "Foo" shall always be title case in file names.
+#
+# FOOBAR.TXT => file will be renamed to "FOOBar.txt"
+# FOO BAR.TXT => file will be renamed to "FOO Bar.txt"
+#
+$(Bar)
+
+# EOF
+
diff --git a/python2/cfg/sample_upper.cfg b/python2/cfg/sample_upper.cfg
new file mode 100644
index 0000000..6eb268a
--- /dev/null
+++ b/python2/cfg/sample_upper.cfg
@@ -0,0 +1,45 @@
+##############################################################################
+# #
+# Nomen sample configuration file for strings that should ALWAYS be #
+# UPPERCASE when converting the case of file names. #
+# #
+# This will NOT convert the case of the file extension. For renaming the #
+# extension, you may use the Nomen Extention Case Converter script. #
+# #
+# One string per line, empty and commented out lines (like this header) #
+# will be ignored. #
+# #
+# Please read the documentation before using any component of Nomen. #
+# #
+##############################################################################
+
+# Example 1:
+#
+# The word "FOO" shall always be uppercase in file names.
+#
+# foobar.txt => file will NOT be renamed (because "foo" is not a
+# separate word)
+# foo bar.txt => file will be renamed to "FOO bar.txt"
+#
+FOO
+
+# Example 2:
+#
+# The string "FOO" shall always be uppercase in file names.
+#
+# foobar.txt => file will be renamed to "FOObar.txt"
+# foo bar.txt => file will be renamed to "FOO bar.txt"
+#
+$(FOO)
+
+# Example 3:
+#
+# The string "BAR" shall always be uppercase in file names.
+#
+# foobar.txt => file will be renamed to "fooBAR.txt"
+# foo bar.txt => file will be renamed to "foo BAR.txt"
+#
+$(BAR)
+
+# EOF
+
diff --git a/python2/changelog.txt b/python2/changelog.txt
new file mode 100644
index 0000000..b2a03db
--- /dev/null
+++ b/python2/changelog.txt
@@ -0,0 +1,195 @@
+
+CHANGELOG (Nomen)
+
+ Version 2.3.5 (2018-04-14)
+
+ + Added new versions of the Clap and PaVal core modules (replaced the
+ existing ones).
+
+ * Revised (refurbished) all components of the project in general
+ (negligible changes).
+
+ Version 2.3.4 (2016-07-12)
+
+ + Added a simple random string generator method to the Nomen Common
+ core module.
+
+ * Revised the confirmation prompt inside the Nomen Common core module
+ (now uses random strings instead of a fixed one).
+
+ # Fixed the type mismatch bug inside the Nomen File Renamer core
+ module (occurred in case a file had the correct prefix but without
+ a trailing number).
+
+ Version 2.3.3 (2016-06-20)
+
+ + Added a new feature to the Nomen File Renamer component that allows
+ to order the files by their access, creation and modification time.
+
+ # Fixed the maximum recursion depth exceeded bug inside the Nomen
+ Common core module (occurred under certain circumstances, only)
+
+ Version 2.3.2 (2016-05-19)
+
+ * Revised some code inside the Nomen Directory Name Space Modifier
+ script changes).
+
+ # Fixed the path referenced before assignment bug inside the Nomen
+ Common core module (occurred under certain circumstances, only).
+
+ Version 2.3.1 (2016-05-17)
+
+ + Added a basic exclude feature to the Nomen Directory Name Space
+ Modifier script (case-insensitive, without regex syntax).
+
+ # Fixed the character replacement bug inside a method of the Nomen
+ Common core module (used by Nomen Directory Name Space Modifier)
+ which occurred when the recurisve parameter was set.
+
+ Version 2.3.0 (2016-04-16)
+
+ + Added a new feature to the Nomen File Renamer component that allows
+ user-defined steps between each numeric ID.
+
+ * Revised some code inside the Nomen Common core module (negligible
+ changes).
+ * Revised some parameter validation code in various core modules.
+
+ Version 2.2.6 (2015-06-05)
+
+ + Added some additional command-line arguments to the Nomen Directory
+ Name Space Modifier script to insert and remove spaces around
+ brackets and hyphens.
+
+ * Revised some code inside the Nomen Common core module (negligible
+ changes).
+
+ # Fixed the character replacement bug inside a method of the Nomen
+ Common core module (used by Nomen Directory Name Space Modifier).
+
+ Version 2.2.5 (2015-01-24)
+
+ + Added an optional feature to the Nomen Directory Name Space Modifier
+ (former "Nomen Directory Name Space Remover") to insert and remove
+ spaces next to punctuation characters.
+
+ * Revised the confirmation prompt inside the Nomen Common core module.
+
+ Version 2.2.4 (2015-01-03)
+
+ * Revised the code for handling regular expressions inside the core
+ modules.
+
+ - Removed unnecessary imports and unused variables from the core
+ modules.
+
+ Version 2.2.3 (2014-08-25)
+
+ + Added the Nomen Directory Name Space Remover component.
+
+ * Revised (enhanced) the Nomen Common core module.
+
+ Version 2.2.2 (2014-06-28)
+
+ + Added a feature that allows omitting the config file path if the
+ file is located in the corresponding config sub-directory.
+
+ * Revised the method to parse the case config files inside the Nomen
+ Static Case core module (added default values).
+
+ Version 2.2.1 (2014-05-22)
+
+ + Added an optional command-line argument to the Nomen File Name
+ Modifier script to remove certain leading and trailing characters
+ from the processed base name.
+
+ # Fixed the file skip bug inside the Nomen File Name Modifier module
+ when processing files whose base name contains dots.
+
+ Version 2.2.0 (2014-05-16)
+
+ + Added the new Nomen File Name Modifier component.
+
+ * Revised (reduced) some code inside the Nomen Static Case core
+ module.
+ * Revised (renamed) some command-line arguments inside all scripts
+ (for more clarity).
+
+ - Removed some unnecessary code from the Nomen File Renamer core
+ module.
+
+ Version 2.1.6 (2014-05-14)
+
+ + Added a method to the Nomen Common core module that returns invalid
+ file name characters.
+
+ * Revised (reduced) some code inside the Nomen File Renamer as the
+ well as Nomen Extension Renamer core module (negligible changes).
+ * Revised the list of invalid file name characters.
+
+ Version 2.1.5 (2014-04-22)
+
+ * Revised (reduced) the code inside the Nomen Common core module that
+ determines if a file with the new name already exists.
+ * Revised (reduced) the code inside the Nomen Common core module that
+ determines the case sensitivity of the file system.
+
+ # Fixed the rename bug ("no such file or directory") inside the Nomen
+ Common core module when renaming files using the "rename-new" or
+ "fill-gaps" mode (only occurred on case-insensitive file systems).
+
+ Version 2.1.4 (2014-04-19)
+
+ # Fixed the attribute error inside the code of the Nomen Common core
+ module that determines if a file with the new name already exists.
+ # Fixed the file system case sensitivity bug inside Nomen Common core
+ module (which did not determine the case sensitivity properly).
+ # Fixed the list bug inside the Nomen File Renamer core module (which
+ did not append all skipped files to the list when using "fill-gaps"
+ mode).
+
+ Version 2.1.3 (2014-04-12)
+
+ + Added an optional command-line argument to the Nomen File Renamer
+ script to set a custom file name.
+
+ * Revised the message text of some exceptions (for more clarity).
+
+ Version 2.1.2 (2014-03-28)
+
+ * Revised (reduced) the code inside the Nomen Common core module to
+ check if a file already has been processed.
+ * Revised the code inside the Nomen Common core module to check if the
+ file system is case-sensitive.
+
+ Version 2.1.1 (2014-03-22)
+
+ + Added an optional command-line argument to the Nomen File Renamer
+ script to ignore the file extension when numerating files.
+ + Added the estimated time to the simulation report file.
+
+ * Revised (reduced) some code inside the Nomen Common core module.
+
+ # Fixed the overwrite bug that occurred due to different cases of the
+ file extensions when renaming files via the Nomen File Renamer on
+ case-insensitive file systems (such as FAT32 and NTFS).
+
+ Version 2.1.0 (2014-03-19)
+
+ * Revised the "fill" rename methods (merged them to one method and
+ removed unused variables) inside the Nomen File Renamer core module.
+ * Revised (changed) the names of the rename modes inside the Nomen
+ File Renamer component (for more clarity).
+ * Revised the way of renaming files (numeration is now dependent of
+ the file extension) inside the Nomen File Renamer core module.
+
+ # Fixed the attribute error inside the Nomen Common core module when
+ reading out the major version of the Python framework using Python
+ version 2.6 or below.
+ # Fixed the empty list bug inside the Nomen File Renamer core module
+ (which terminated the whole file rename process, if occurred).
+
+ Version 2.0.0 (2014-03-18)
+
+ * First official release of this major version.
+
diff --git a/python2/core/__init__.py b/python2/core/__init__.py
new file mode 100755
index 0000000..e69de29
diff --git a/python2/core/clap.py b/python2/core/clap.py
new file mode 100755
index 0000000..03305c0
--- /dev/null
+++ b/python2/core/clap.py
@@ -0,0 +1,216 @@
+#!/usr/bin/env python2
+# -*- coding: utf-8 -*-
+
+# ============================================================================
+# Clap - Command-line argument parser module
+# Copyright (C) 2018 by Ralf Kilian
+# Distributed under the MIT License (https://opensource.org/licenses/MIT)
+#
+# Website: http://www.urbanware.org
+# GitHub: https://github.com/urbanware-org/clap
+# ============================================================================
+
+__version__ = "1.1.10"
+
+
+def get_version():
+ """
+ Return the version of this module.
+ """
+ return __version__
+
+
+class Parser(object):
+ """
+ Project independent command-line argument parser class.
+ """
+ __arg_grp_opt = None
+ __arg_grp_req = None
+ __arg_parser = None
+ __is_argparser = False
+ __conflict_handler = "resolve" # used by OptionParser, only
+
+ def __init__(self):
+ try:
+ from argparse import ArgumentParser
+ self.__arg_parser = ArgumentParser(add_help=False)
+ self.__arg_grp_req = \
+ self.__arg_parser.add_argument_group("required arguments")
+ self.__arg_grp_opt = \
+ self.__arg_parser.add_argument_group("optional arguments")
+ self.__is_argparser = True
+ return
+ except ImportError:
+ # Ignore the exception and proceed with the fallback
+ pass
+
+ try:
+ from optparse import OptionParser
+ self.__arg_parser = \
+ OptionParser(conflict_handler=self.__conflict_handler)
+ self.__arg_grp_req = \
+ self.__arg_parser.add_option_group("Required arguments")
+ self.__arg_grp_opt = \
+ self.__arg_parser.add_option_group("Optional arguments")
+ return
+ except ImportError:
+ # This should never happen
+ raise ImportError("Failed to initialize an argument parser.")
+
+ def add_avalue(self, arg_short, arg_long, arg_help, arg_dest, arg_default,
+ arg_required):
+ """
+ Add an argument that expects a single user-defined value.
+ """
+ if arg_required:
+ obj = self.__arg_grp_req
+ else:
+ obj = self.__arg_grp_opt
+
+ if arg_default is not None:
+ # Enclose the value with quotes in case it is not an integer
+ quotes = "'"
+ try:
+ arg_default = int(arg_default)
+ quotes = ""
+ except ValueError:
+ pass
+
+ if arg_help.strip().endswith(")"):
+ arg_help = arg_help.rstrip(")")
+ arg_help += ", default is %s%s%s)" % \
+ (quotes, str(arg_default), quotes)
+ else:
+ arg_help += " (default is %s%s%s)" % \
+ (quotes, str(arg_default), quotes)
+
+ if self.__is_argparser:
+ if arg_short is None:
+ obj.add_argument(arg_long, help=arg_help, dest=arg_dest,
+ default=arg_default, required=arg_required)
+ else:
+ obj.add_argument(arg_short, arg_long, help=arg_help,
+ dest=arg_dest, default=arg_default,
+ required=arg_required)
+ else:
+ if arg_short is None:
+ obj.add_option(arg_long, help=arg_help, dest=arg_dest,
+ default=arg_default)
+ else:
+ obj.add_option(arg_short, arg_long, help=arg_help,
+ dest=arg_dest, default=arg_default)
+
+ def add_predef(self, arg_short, arg_long, arg_help, arg_dest, arg_choices,
+ arg_required):
+ """
+ Add an argument that expects a certain predefined value.
+ """
+ if arg_required:
+ obj = self.__arg_grp_req
+ else:
+ obj = self.__arg_grp_opt
+
+ if self.__is_argparser:
+ if arg_short is None:
+ obj.add_argument(arg_long, help=arg_help, dest=arg_dest,
+ choices=arg_choices, required=arg_required)
+ else:
+ obj.add_argument(arg_short, arg_long, help=arg_help,
+ dest=arg_dest, choices=arg_choices,
+ required=arg_required)
+ else:
+ if arg_short is None:
+ obj.add_option(arg_long, help=arg_help, dest=arg_dest,
+ choices=arg_choices)
+ else:
+ # The OptionParser does not print the values to choose from,
+ # so these have to be added manually to the description of
+ # the argument first
+ arg_help += " (choose from "
+ for item in arg_choices:
+ arg_help += "'%s', " % item
+ arg_help = arg_help.rstrip(", ") + ")"
+
+ obj.add_option(arg_short, arg_long, help=arg_help,
+ dest=arg_dest)
+
+ def add_switch(self, arg_short, arg_long, arg_help, arg_dest, arg_store,
+ arg_required):
+ """
+ Add an argument that does not expect anything, but returns a
+ boolean value.
+ """
+ if arg_required:
+ obj = self.__arg_grp_req
+ else:
+ obj = self.__arg_grp_opt
+
+ if arg_store:
+ arg_store = "store_true"
+ else:
+ arg_store = "store_false"
+
+ if self.__is_argparser:
+ if arg_short is None:
+ obj.add_argument(arg_long, help=arg_help, dest=arg_dest,
+ action=arg_store, required=arg_required)
+ else:
+ obj.add_argument(arg_short, arg_long, help=arg_help,
+ dest=arg_dest, action=arg_store,
+ required=arg_required)
+ else:
+ if arg_short is None:
+ obj.add_option(arg_long, help=arg_help, dest=arg_dest,
+ action=arg_store)
+ else:
+ obj.add_option(arg_short, arg_long, help=arg_help,
+ dest=arg_dest, action=arg_store)
+
+ def dependency(self, arg_name, arg_value, dependency):
+ """
+ Check the dependency of a command-line argument.
+ """
+ if dependency is not None:
+ if arg_value is None or str(arg_value) == "":
+ raise Exception("The '%s' argument depends on %s'." %
+ (arg_name, dependency))
+
+ def error(self, obj):
+ """
+ Raise an error and cause the argument parser to print the error
+ message.
+ """
+ if type(obj) == str:
+ obj = obj.strip()
+
+ self.__arg_parser.error(obj)
+
+ def parse_args(self):
+ """
+ Parse and return the command-line arguments.
+ """
+ if self.__is_argparser:
+ args = self.__arg_parser.parse_args()
+ else:
+ (args, values) = self.__arg_parser.parse_args()
+ return args
+
+ def print_help(self):
+ """
+ Print the usage, description, argument details and epilog.
+ """
+ self.__arg_parser.print_help()
+
+ def set_description(self, string):
+ """
+ Set the description text.
+ """
+ self.__arg_parser.description = string.strip()
+
+ def set_epilog(self, string):
+ """
+ Set the epilog text.
+ """
+ self.__arg_parser.epilog = string.strip()
+
+# EOF
diff --git a/python2/core/common.py b/python2/core/common.py
new file mode 100755
index 0000000..243e332
--- /dev/null
+++ b/python2/core/common.py
@@ -0,0 +1,555 @@
+#!/usr/bin/env python2
+# -*- coding: utf-8 -*-
+
+# ============================================================================
+# Nomen - Multi-purpose rename tool
+# Common core module
+# Copyright (C) 2018 by Ralf Kilian
+# Distributed under the MIT License (https://opensource.org/licenses/MIT)
+#
+# Website: http://www.urbanware.org
+# GitHub: https://github.com/urbanware-org/nomen
+# ============================================================================
+
+__version__ = "2.3.5"
+
+import os
+import paval as pv
+import random
+import re
+import sys
+import tempfile
+
+from datetime import datetime as dt
+
+def compile_regex(string, ignore_case=True, regex_syntax=False):
+ """
+ Compile a regular expression from the given pattern string.
+ """
+ pv.string(string, "regular expression", True, None)
+ if regex_syntax:
+ pattern = ".*" + string + ".*"
+ else:
+ spec_chars = [ "\\", ".", "^", "$", "+", "?", "{", "}", "[", "]",
+ "|", "(", ")" ]
+ for char in spec_chars:
+ string = string.replace(char, "\\" + char)
+ string = string.strip("*").strip(";")
+ while ("*" * 2) in string:
+ string = string.replace(("*" * 2), "*")
+ while (";" * 2) in string:
+ string = string.replace((";" * 2), ";")
+
+ list_string = string.split(";")
+ if len(list_string) > 0:
+ pattern = ""
+ for crit in list_string:
+ if not crit == "":
+ pattern += "(.*" + crit.replace("*", ".*") + ".*)|"
+ pattern = pattern.rstrip("|")
+ if pattern == "":
+ raise Exception("The given string does not make sense this " \
+ "way.")
+
+ if ignore_case:
+ regex = re.compile(pattern, re.IGNORECASE)
+ else:
+ regex = re.compile(pattern)
+
+ return regex
+
+def confirm_notice():
+ """
+ Display a notice which must be confirmed by the user to proceed.
+ """
+ string = random_string(6, True, True, True, True)
+ proceed = False
+ notice_text = """ o o o 88
+ 8 8 88
+ 8 8 .oPYo. oPYo. odYo. o8 odYo. .oPYo. 88
+ 8 db 8 .oooo8 8 '' 8' '8 8 8' '8 8 8 88
+ 'b.PY.d' 8 8 8 8 8 8 8 8 8 8
+ '8 8' 'YooP8 8 8 8 8 8 8 'YooP8 88
+ . 8
+ 'oooP'
+
+Please use this tool with care to avoid data damage or loss!
+
+There is no function to undo the changes done by this tool, so you
+should be aware of what you are doing. Improper use (e. g. modifying
+files inside system directories) will corrupt your system!
+
+If you wish to proceed, type '%s' (case-sensitive, without any
+quotes or spaces) and press the key. Otherwise, the process
+will be cancelled.""" % string
+
+ print_text_box("", notice_text)
+ choice = raw_input("> ")
+
+ if choice == string:
+ choice = "Proceeding."
+ proceed = True
+ else:
+ choice = "cancelled."
+ print "\n%s\n" % choice
+
+ return proceed
+
+def dir_space_modifier(directory, remove_duplicate=False,
+ remove_leading=False, remove_trailing=False,
+ brackets=False, hyphens=False, punctuation=False,
+ ignore_symlinks=False, recursive=False, exclude=None):
+ """
+ Modify a directory name by removing leading, trailing and duplicate
+ spaces or by inserting and removing spaces next to punctuation
+ characters.
+ """
+
+ list_exclude = []
+ if not exclude == None:
+ list_exclude = exclude.split(";")
+
+ for item in os.listdir(directory):
+ excluded = False
+ if os.path.isdir(os.path.join(directory, item)):
+ path = os.path.join(directory, item)
+ for excl in list_exclude:
+ if excl.lower() in path.lower():
+ excluded = True
+ break
+ else:
+ continue
+
+ if excluded:
+ nextdir = path
+ else:
+ if remove_duplicate:
+ while (" " * 2) in item:
+ item = item.replace((" " * 2), " ")
+
+ if hyphens:
+ item = item.replace("-", " - ")
+ while "- " in item:
+ item = item.replace("- ", "- ")
+ while " -" in item:
+ item = item.replace(" -", " -")
+
+ if brackets:
+ while "( " in item:
+ item = item.replace("( ", "(")
+ item = item.replace("(", " (")
+ while " )" in item:
+ item = item.replace(" )", ")")
+ item = item.replace(")", ") ").replace(") .", ").")
+ while "[ " in item:
+ item = item.replace("[ ", "[")
+ item = item.replace("[", " [")
+ while " ]" in item:
+ item = item.replace(" ]", "]")
+ item = item.replace("]", "] ").replace("] .", "].")
+ item = item.replace("( [", "([").replace("] )", "])")
+ item = item.replace("[ (", "[(").replace(") ]", ")]")
+
+ if punctuation:
+ item = item.replace(".", ". ")
+ while " ." in item:
+ item = item.replace(" .", ".")
+ item = item.replace(",", ", ")
+ while " ," in item:
+ item = item.replace(" ,", ",")
+ item = item.replace(":", ": ")
+ while " :" in item:
+ item = item.replace(" :", ":")
+ item = item.replace(";", "; ")
+ while " ;" in item:
+ item = item.replace(" ;", ";")
+ item = item.replace("!", "! ")
+ while " !" in item:
+ item = item.replace(" !", "!")
+ item = item.replace("?", "? ")
+ while " ?" in item:
+ item = item.replace(" ?", "?")
+ remove_leading = True
+ remove_trailing = True
+
+ if remove_leading:
+ item = item.lstrip()
+ if remove_trailing:
+ item = item.rstrip()
+
+ newpath = os.path.join(directory, item)
+ if remove_duplicate:
+ # Repeat this step after the replace actions above
+ while (" " * 2) in newpath:
+ newpath = newpath.replace((" " * 2), " ")
+
+ if not os.path.exists(newpath):
+ os.rename(path, newpath)
+ nextdir = newpath
+ else:
+ nextdir = path
+
+ if recursive:
+ dir_space_modifier(nextdir, remove_duplicate, remove_leading,
+ remove_trailing, brackets, hyphens,
+ punctuation, ignore_symlinks, True, exclude)
+
+def file_exists(file_path, list_files, fs_case_sensitive):
+ """
+ Check if a file already exists on the file system as well as in a
+ given list.
+ """
+ file_path = os.path.abspath(file_path)
+ if os.path.exists(file_path):
+ file_exists = True
+ else:
+ file_exists = False
+
+ for item in list_files:
+ if item[1] == None:
+ item[1] = ""
+
+ if fs_case_sensitive:
+ if file_path == item[1] or file_path == item[2]:
+ file_exists = True
+ break
+ else:
+ if file_path.lower() == item[1].lower() or \
+ file_path.lower() == item[2].lower():
+ file_exists = True
+ break
+
+ return file_exists
+
+def format_timestamp(float_stamp=0):
+ """
+ Convert a timestamp float into a readable format.
+ """
+ return str(dt.fromtimestamp(float(str(float_stamp))))
+
+def get_files(directory, recursive=False, ignore_case=True, regex=None,
+ regex_exclude=True, ignore_symlinks=False, order_by=None):
+ """
+ Get the files and sub-directories from the given directory.
+ """
+ pv.path(directory, "given", False, True)
+
+ directory = os.path.abspath(directory)
+ list_files = []
+ list_excluded = []
+
+ list_files, list_excluded = \
+ __get_files( \
+ directory, ignore_case, regex, regex_exclude, ignore_symlinks,
+ recursive, list_files, list_excluded, order_by)
+
+ if order_by == None:
+ list_files.sort()
+ list_excluded.sort()
+
+ return list_files, list_excluded
+
+def get_fs_case_sensitivity(directory):
+ """
+ Determine if the file system of the given directory is case-sensitive.
+ """
+ # This should be done with every directory that is processed, due to the
+ # fact, that e. g. a device containing a case-insensitive file system can
+ # be mounted into a directory of a case-sensitive file system.
+
+ pv.path(directory, "given", False, True)
+ directory = os.path.abspath(directory)
+
+ fd_temp, file_temp = tempfile.mkstemp(dir=directory)
+ file_name = os.path.basename(file_temp)
+ if os.path.exists(os.path.join(directory, file_name.upper())):
+ fs_case_sensitive = False
+ else:
+ fs_case_sensitive = True
+ os.close(fd_temp)
+ os.remove(file_temp)
+
+ return fs_case_sensitive
+
+def get_invalid_chars():
+ """
+ Return the invalid file name characters (which must or should not be
+ part of a file name).
+ """
+ # This list of characters depends on the file system where the files are
+ # being renamed on. Due to the fact, that e. g. a device containing a
+ # different file system can be mounted into a directory of the local file
+ # system, the following characters will be handled as invalid on every
+ # file system.
+ invalid_chars = "/\\?%*:|\"<>\n\r\t"
+
+ return invalid_chars
+
+def get_version():
+ """
+ Return the version of this module.
+ """
+ return __version__
+
+def print_text_box(heading, text):
+ """
+ Print a text message outlined with an ASCII character frame.
+ """
+ heading = heading.strip()
+ if len(heading) > 72:
+ raise Exception("The text box heading must not be longer than 72 " \
+ "characters.")
+ if text == "":
+ raise Exception("The text box text must not be empty.")
+
+ text_box = "\n+" + ("-" * 76) + "+" + \
+ "\n|" + (" " * 76) + "|"
+ if not heading == "":
+ padding = int((72 - len(heading)) / 2)
+ heading = (" " * (padding + 2) + heading).ljust(76, " ")
+ text_box += ("\n|%s|\n|" + (" " * 76) + "|") % heading
+ list_text = text.split("\n")
+ for text in list_text:
+ list_words = text.split(" ")
+ count = 1
+ line = ""
+ for word in list_words:
+ if len(line + word + " ") > 73:
+ text_box += "\n| " + line.ljust(74, " ") + "|"
+ line = word + " "
+ else:
+ line = line + word + " "
+ count += 1
+ if count > len(list_words):
+ text_box += "\n| " + line.ljust(74, " ") + "|"
+ text_box += "\n|" + (" " * 76) + "|" \
+ "\n+" + ("-" * 76) + "+\n"
+
+ print text_box
+
+def random_string(length, uppercase=True, lowercase=False, numbers=False,
+ unique=False):
+ """
+ Generate a random string out of literals and numbers.
+ """
+ literals = "ABCDEFGHIJLMNOPQRSTUVWXYZ"
+ numbers = "0123456789"
+ chars = ""
+ string = ""
+
+ if uppercase:
+ chars += literals
+ if lowercase:
+ chars += literals.lower()
+ if numbers:
+ chars += numbers
+
+ if len(chars) == 0:
+ return string
+ if len(chars) < length:
+ length = len(chars)
+
+ while len(string) < length:
+ rnd = random.randint(0, len(chars) - 1)
+ char = chars[rnd]
+ if unique:
+ if char in string:
+ continue
+ string += char
+
+ return string
+
+
+def rename(list_files, reverse=False):
+ """
+ Rename the files which have neither been excluded nor skipped.
+ """
+ list_skipped = []
+
+ if len(list_files) > 0:
+ if reverse:
+ list_files = reversed(list_files)
+
+ for item in list_files:
+ if os.path.exists(item[0]):
+ if os.path.exists(item[2]):
+ list_skipped.append([item[0], item[1], item[2]])
+ continue
+
+ # In some cases the file will get a temporary name first and
+ # then its name will be changed to what it should be.
+ #
+ # This behaviour is required when using file systems that are
+ # case-insensitive (such as FAT32 or NTFS) where e. g. the
+ # file "FOOBAR.txt" would overwrite the file "foobar.txt"
+ # inside the same directory.
+ if item[1] == None or \
+ item[1] == "":
+ os.rename(item[0], item[2])
+ else:
+ os.rename(item[0], item[1])
+ os.rename(item[1], item[2])
+
+ if len(list_skipped) > 0:
+ if not list_skipped == list_files:
+ rename(list_skipped, reverse)
+
+def report(report_file=None, list_header=[], list_renamed=[],
+ list_excluded=[], list_skipped=[], time_start=None):
+ """
+ Write the details of the simulated rename process (simulation report)
+ into a file.
+ """
+ files_total = str(len(list_renamed) + len(list_excluded) + \
+ len(list_skipped))
+ just = len(files_total)
+ files_renamed = str(len(list_renamed)).rjust(just, " ")
+ files_excluded = str(len(list_excluded)).rjust(just, " ")
+ files_skipped = str(len(list_skipped)).rjust(just, " ")
+ time_end = dt.now()
+
+ try:
+ time_elapsed = str(time_end - time_start)
+ time_start = str(time_start)
+ except:
+ raise Exception("An invalid start date was given.")
+
+ output = "\r\n" + "=" * 78 + \
+ "\r\nFile type: " + list_header[0] + \
+ "\r\n" + "-" * 78
+
+ for i in range(1, len(list_header)):
+ output += "\r\n" + list_header[i][0].ljust(20, " ") + \
+ str(list_header[i][1])
+
+ output += "\r\n" + "-" * 78 + \
+ "\r\nFiles renamed: " + files_renamed + \
+ "\r\nFiles excluded: " + files_excluded + \
+ "\r\nFiles skipped: " + files_skipped + \
+ "\r\nFiles total: " + files_total + \
+ "\r\n" + "-" * 78 + \
+ "\r\nTimestamp: " + time_start[:-7] + \
+ "\r\nElapsed time: " + time_elapsed + \
+ "\r\nNomen version: " + __version__ + \
+ "\r\n" + "=" * 78 + "\r\n\r\n"
+
+ if len(list_renamed) > 0:
+ output += "\r\n [Renamed]\r\n"
+ for item in list_renamed:
+ output += " - Old: %s\r\n" % item[0]
+ output += " - New: %s\r\n\r\n" % item[2]
+ output += "\r\n"
+
+ if len(list_excluded) > 0:
+ output += "\r\n [Excluded]\r\n"
+ for item in list_excluded:
+ output += " - %s\r\n" % item
+ output += "\r\n"
+
+ if len(list_skipped) > 0:
+ output += "\r\n [Skipped]\r\n"
+ for item in list_skipped:
+ output += " - %s\r\n" % item
+ output += "\r\n"
+
+ fh_report = open(report_file, "wb")
+
+ # Run the appropriate code for the Python framework used
+ if sys.version_info[0] == 2:
+ fh_report.write(output)
+ elif sys.version_info[0] > 2:
+ fh_report.write(output.encode(sys.getdefaultencoding()))
+
+ fh_report.close()
+
+def __get_files(directory, ignore_case, regex, regex_exclude, ignore_symlinks,
+ recursive, list_content, list_excluded, order_by):
+ """
+ Core method to get the files from the given directory and its
+ sub-directories.
+ """
+ list_dirs = []
+ list_files = []
+
+ for item in os.listdir(directory):
+ path = os.path.join(directory, item)
+ if ignore_symlinks:
+ if os.path.islink(path):
+ continue
+ if os.path.isfile(path):
+ if regex == None:
+ list_files.append(path)
+ else:
+ if regex_exclude:
+ if ignore_case:
+ if regex.match(item.lower()):
+ list_excluded.append(path)
+ else:
+ list_files.append(path)
+ else:
+ if regex.match(item):
+ list_excluded.append(path)
+ else:
+ list_files.append(path)
+ else:
+ if ignore_case:
+ if regex.match(item.lower()):
+ list_files.append(path)
+ else:
+ list_excluded.append(path)
+ else:
+ if regex.match(item):
+ list_files.append(path)
+ else:
+ list_excluded.append(path)
+ else:
+ list_dirs.append(path)
+
+ if len(list_files) > 0:
+ if order_by == None:
+ list_files.sort()
+ else:
+ list_files = __set_order(list_files, order_by)
+ list_content.append([directory, list_files])
+
+ if recursive:
+ for directory in list_dirs:
+ list_content, list_excluded = \
+ __get_files(directory, ignore_case, regex, regex_exclude,
+ ignore_symlinks, True, list_content,
+ list_excluded, order_by)
+
+ return list_content, list_excluded
+
+def __set_order(file_list, order_by):
+ """
+ Set a certain order of the files before renaming them.
+ """
+ list_files = []
+ list_temp = []
+
+ for file_name in file_list:
+ if "." in file_name:
+ file_ext = file_name.split(".")[-1]
+ else:
+ file_ext = ""
+
+ time_access = format_timestamp(os.stat(file_name).st_atime)
+ time_create = format_timestamp(os.stat(file_name).st_ctime)
+ time_modify = format_timestamp(os.stat(file_name).st_mtime)
+
+ if order_by == "accessed":
+ list_temp.append([time_access, file_name, file_ext])
+ elif order_by == "created":
+ list_temp.append([time_create, file_name, file_ext])
+ else:
+ list_temp.append([time_modify, file_name, file_ext])
+
+ list_temp.sort()
+ for item in list_temp:
+ list_files.append(item[1])
+
+ return list_files
+
+# EOF
+
diff --git a/python2/core/extren.py b/python2/core/extren.py
new file mode 100755
index 0000000..790c333
--- /dev/null
+++ b/python2/core/extren.py
@@ -0,0 +1,278 @@
+#!/usr/bin/env python2
+# -*- coding: utf-8 -*-
+
+# ============================================================================
+# Nomen - Multi-purpose rename tool
+# Extension renamer core module
+# Copyright (C) 2018 by Ralf Kilian
+# Distributed under the MIT License (https://opensource.org/licenses/MIT)
+#
+# Website: http://www.urbanware.org
+# GitHub: https://github.com/urbanware-org/nomen
+# ============================================================================
+
+__version__ = "2.3.5"
+
+import common
+import os
+import paval as pv
+import re
+
+from datetime import datetime as dt
+
+def convert_case(directory, case, conflict_mode, recursive=False,
+ report_file=None, ignore_symlinks=False):
+ """
+ Convert the case of the file extensions.
+ """
+ pv.path(directory, "given", False, True)
+ pv.compstr(case, "case", ["lower", "title", "upper"])
+ pv.compstr(conflict_mode, "conflict mode", ["rename", "skip"])
+
+ case = case.lower()
+ conflict_mode = conflict_mode.lower()
+ directory = os.path.abspath(directory)
+ if not directory.endswith(os.path.sep):
+ directory += os.path.sep
+
+ if report_file == None:
+ simulate = False
+ else:
+ pv.path(report_file, "report", True, False)
+ report_file = os.path.abspath(report_file)
+ simulate = True
+
+ time_start = dt.now()
+
+ list_content = []
+ list_excluded = []
+ list_renamed = []
+ list_skipped = []
+ regex = re.compile("(.*)")
+
+ list_content, list_excluded = \
+ common.get_files(directory, recursive, True, regex, False,
+ ignore_symlinks)
+ for item in list_content:
+ list_files = item[1]
+ list_renamed, list_skipped = \
+ __convert_case(list_files, list_renamed, list_skipped, case,
+ conflict_mode, recursive)
+
+ if simulate:
+ list_header = []
+ list_header.append("Nomen Extension Case Converter simulation report")
+ list_header.append(["Report file name:", report_file])
+ list_header.append(["Directory:", directory])
+ list_header.append(["Recursive:", recursive])
+ list_header.append(["Ignore symlinks:", ignore_symlinks])
+ list_header.append(["Conflict mode:", conflict_mode.capitalize()])
+ list_header.append(["Case:", case.capitalize()])
+
+ common.report(report_file, list_header, list_renamed, list_excluded,
+ list_skipped, time_start)
+ else:
+ common.rename(list_renamed)
+
+def get_version():
+ """
+ Return the version of this module.
+ """
+ return __version__
+
+def rename_extensions(directory, conflict_mode, extension, extension_target,
+ recursive=False, ignore_case=True, report_file=None,
+ ignore_symlinks=False):
+ """
+ Rename the file extensions in the given directory and all of its
+ sub-directories (if requested).
+ """
+ pv.path(directory, "given", False, True)
+ pv.compstr(conflict_mode, "conflict mode", ["rename", "skip"])
+ pv.string(extension, "extension", False, common.get_invalid_chars())
+ pv.string(extension_target, "target extension", False,
+ common.get_invalid_chars())
+
+ conflict_mode = conflict_mode.lower()
+ directory = os.path.abspath(directory)
+ if not directory.endswith(os.path.sep):
+ directory += os.path.sep
+
+ if report_file == None:
+ simulate = False
+ else:
+ pv.path(report_file, "report", True, False)
+ report_file = os.path.abspath(report_file)
+ simulate = True
+
+ time_start = dt.now()
+
+ list_content = []
+ list_excluded = []
+ list_extensions = []
+ list_renamed = []
+ list_skipped = []
+
+ if ";" in extension:
+ while (";" * 2) in extension:
+ extension = extension.replace((";" * 2), ";")
+
+ list_temp = extension.split(";")
+ for extension in list_temp:
+ if not extension == "":
+ list_extensions.append(extension)
+
+ if len(list_extensions) == 0:
+ raise Exception("The given extension list does not contain any " \
+ "extensions.")
+ else:
+ list_extensions.append(extension)
+
+ pattern = ""
+ for extension in list_extensions:
+ pattern += "(.*\." + str(extension) + "$)|"
+ pattern = pattern.rstrip("|")
+
+ if ignore_case:
+ regex = re.compile(pattern, re.IGNORECASE)
+ else:
+ regex = re.compile(pattern)
+
+ list_content, list_excluded = \
+ common.get_files(directory, recursive, ignore_case, regex, False,
+ ignore_symlinks)
+ for item in list_content:
+ list_files = item[1]
+ list_renamed, list_skipped = \
+ __rename_extensions(list_files, list_extensions, list_renamed,
+ list_skipped, conflict_mode, extension_target)
+
+ if simulate:
+ list_header = []
+ list_header.append("Nomen Extension Renamer simulation report")
+ list_header.append(["Report file name:", report_file])
+ list_header.append(["Directory:", directory])
+ list_header.append(["Recursive:", recursive])
+ list_header.append(["Ignore symlinks:", ignore_symlinks])
+ list_header.append(["Conflict mode:", conflict_mode.capitalize()])
+ list_header.append(["Extensions:", extension])
+ list_header.append(["Target extension:", extension_target])
+ list_header.append(["Ignore case:", ignore_case])
+
+ common.report(report_file, list_header, list_renamed, list_excluded,
+ list_skipped, time_start)
+ else:
+ common.rename(list_renamed)
+
+def __convert_case(list_files, list_renamed, list_skipped, case,
+ conflict_mode, recursive):
+ """
+ Core method to convert the case of the file extensions.
+ """
+ fs_case = common.get_fs_case_sensitivity(os.path.dirname(list_files[0]))
+
+ for file_path in list_files:
+ num = 1
+ list_path = file_path.split(os.path.sep)
+ file_name = list_path[-1]
+ file_ext = os.path.splitext(file_name)[1]
+
+ if file_ext == "":
+ list_skipped.append(file_path)
+ continue
+
+ if case == "lower":
+ extension_target = file_ext.lower()
+ elif case == "title":
+ extension_target = file_ext.title()
+ elif case == "upper":
+ extension_target = file_ext.upper()
+
+ file_newpath = file_path.replace(file_ext, extension_target)
+ if (file_path == file_newpath):
+ list_skipped.append(file_path)
+ continue
+
+ if conflict_mode == "rename":
+ while True:
+ if common.file_exists(file_newpath, list_renamed, fs_case):
+ if not fs_case:
+ if file_path.lower() == file_newpath.lower():
+ break
+
+ file_newpath = \
+ file_path.replace(file_ext,
+ "_" + str(num) + extension_target)
+ num += 1
+ else:
+ break
+ elif conflict_mode == "skip":
+ if common.file_exists(file_newpath, list_renamed, fs_case):
+ if not fs_case:
+ if not file_path.lower() == file_newpath.lower():
+ list_skipped.append(file_newpath)
+ continue
+ else:
+ list_skipped.append(file_newpath)
+ continue
+
+ if os.path.exists(file_path):
+ list_renamed.append([file_path, file_newpath + ".__temp__",
+ file_newpath])
+
+ return list_renamed, list_skipped
+
+def __rename_extensions(list_files, list_extensions, list_renamed,
+ list_skipped, conflict_mode, extension_target):
+ """
+ Core method to rename the file extensions.
+ """
+ fs_case = common.get_fs_case_sensitivity(os.path.dirname(list_files[0]))
+
+ for file_path in list_files:
+ num = 1
+ list_path = file_path.split(os.path.sep)
+ file_name = list_path[-1]
+ file_ext = os.path.splitext(file_name)[1]
+
+ if file_ext == "":
+ list_skipped.append(file_path)
+ continue
+
+ file_newpath = file_path.replace(file_ext,
+ os.path.extsep + extension_target)
+ if file_path == file_newpath:
+ list_skipped.append(file_path)
+ continue
+
+ if conflict_mode == "rename":
+ while True:
+ if common.file_exists(file_newpath, list_renamed, fs_case):
+ if not fs_case:
+ if file_path.lower() == file_newpath.lower():
+ break
+ file_newpath = file_path.replace(file_ext, "_" +
+ str(num) +
+ os.path.extsep +
+ extension_target)
+ num += 1
+ else:
+ break
+ elif conflict_mode == "skip":
+ if common.file_exists(file_newpath, list_renamed, fs_case):
+ if not fs_case:
+ if not file_path.lower() == file_newpath.lower():
+ list_skipped.append(file_path)
+ continue
+ else:
+ list_skipped.append(file_path)
+ continue
+
+ if os.path.exists(file_path):
+ list_renamed.append([file_path, file_newpath + ".__temp__",
+ file_newpath])
+
+ return list_renamed, list_skipped
+
+# EOF
+
diff --git a/python2/core/fileren.py b/python2/core/fileren.py
new file mode 100755
index 0000000..5dd51b9
--- /dev/null
+++ b/python2/core/fileren.py
@@ -0,0 +1,838 @@
+#!/usr/bin/env python2
+# -*- coding: utf-8 -*-
+
+# ============================================================================
+# Nomen - Multi-purpose rename tool
+# File renamer core module
+# Copyright (C) 2018 by Ralf Kilian
+# Distributed under the MIT License (https://opensource.org/licenses/MIT)
+#
+# Website: http://www.urbanware.org
+# GitHub: https://github.com/urbanware-org/nomen
+# ============================================================================
+
+__version__ = "2.3.5"
+
+import common
+import os
+import paval as pv
+import re
+import statcase
+
+from datetime import datetime as dt
+
+def convert_case(directory, case, conflict_mode, recursive=False,
+ cfg_lower=None, cfg_mixed=None, cfg_title=None,
+ cfg_upper=None, report_file=None, ignore_symlinks=False):
+ """
+ Convert the case of the base name of files.
+ """
+ pv.path(directory, "given", False, True)
+ pv.compstr(case, "case", ["lower", "title", "upper", "config"])
+ pv.compstr(conflict_mode, "conflict mode", ["rename", "skip"])
+
+ case = case.lower()
+ conflict_mode = conflict_mode.lower()
+ directory = os.path.abspath(directory)
+ if not directory.endswith(os.path.sep):
+ directory += os.path.sep
+
+ if report_file == None:
+ simulate = False
+ else:
+ pv.path(report_file, "report", True, False)
+ report_file = os.path.abspath(report_file)
+ simulate = True
+
+ time_start = dt.now()
+
+ list_content = []
+ list_excluded = []
+ list_renamed = []
+ list_skipped = []
+ regex = re.compile("(.*)")
+
+ __check_config_mismatch(case, cfg_lower, cfg_title, cfg_mixed, cfg_upper)
+ list_content, list_excluded = \
+ common.get_files(directory, recursive, True, regex, False,
+ ignore_symlinks)
+
+ for item in list_content:
+ list_files = item[1]
+ list_renamed, list_skipped = \
+ __convert_case(list_files, list_renamed, list_skipped, case,
+ conflict_mode, recursive, cfg_lower, cfg_mixed,
+ cfg_title, cfg_upper)
+
+ if simulate:
+ list_header = []
+ list_header.append("Nomen File Name Case Converter simulation report")
+ list_header.append(["Report file name:", report_file])
+ list_header.append(["Directory:", directory])
+ list_header.append(["Recursive:", recursive])
+ list_header.append(["Ignore symlinks:", ignore_symlinks])
+ list_header.append(["Conflict mode:", conflict_mode.capitalize()])
+ list_header.append(["Case:", case.capitalize()])
+ list_header.append(["Lowercase config:", cfg_lower])
+ list_header.append(["Mixed case config:", cfg_mixed])
+ list_header.append(["Title case config:", cfg_title])
+ list_header.append(["Uppercase config:", cfg_upper])
+
+ common.report(report_file, list_header, list_renamed, list_excluded,
+ list_skipped, time_start)
+ else:
+ common.rename(list_renamed)
+
+def get_version():
+ """
+ Return the version of this module.
+ """
+ return __version__
+
+def modify_names(directory, action, position, input_string,
+ replace_string=None, recursive=False, exclude=None,
+ pattern=None, ignore_case=True, regex_syntax=False,
+ report_file=None, ignore_symlinks=False, strip_chars=None):
+ """
+ Modify the base name of files by adding, removing or replacing a
+ user-defined string.
+ """
+ pv.path(directory, "given", False, True)
+ pv.compstr(action, "action", ["add", "remove", "replace"])
+ pv.compstr(position, "position", ["any", "prefix", "suffix"])
+ pv.string(input_string, "input string", False, common.get_invalid_chars())
+
+ action = action.lower()
+ position = position.lower()
+ directory = os.path.abspath(directory)
+ if not directory.endswith(os.path.sep):
+ directory += os.path.sep
+
+ if report_file == None:
+ simulate = False
+ else:
+ pv.path(report_file, "report", True, False)
+ report_file = os.path.abspath(report_file)
+ simulate = True
+
+ if not replace_string == None:
+ if not action == "replace":
+ raise Exception("The replace string argument can only be used " \
+ "together with the action 'replace'.")
+ else:
+ pv.string(replace_string, "string False", False,
+ common.get_invalid_chars())
+
+ if action == "add" and position == "any":
+ raise Exception("The position 'any' cannot be used together with " \
+ "the action 'add'.")
+
+ if len(input_string) == 0:
+ raise Exception("The input string must not be empty.")
+ else:
+ pv.string(input_string, "input string", False,
+ common.get_invalid_chars())
+
+ if not strip_chars == None:
+ pv.string(strip_chars, "strip chars string", False,
+ common.get_invalid_chars())
+
+ time_start = dt.now()
+
+ list_content = []
+ list_excluded = []
+ list_renamed = []
+ list_skipped = []
+ regex = None
+ if not pattern == None:
+ regex = common.compile_regex(pattern, ignore_case, regex_syntax)
+
+ list_content, list_excluded = \
+ common.get_files(directory, recursive, ignore_case, regex, exclude,
+ ignore_symlinks)
+ for item in list_content:
+ list_files = item[1]
+ __modify_names(list_files, list_renamed, list_skipped, action,
+ position, input_string, replace_string, strip_chars)
+
+ if simulate:
+ explicit = None
+ if exclude == None:
+ exclude = False
+ explicit = False
+ elif exclude:
+ explicit = False
+ else:
+ explicit = True
+
+ list_header = []
+ list_header.append("Nomen File Name Modifier simulation report")
+ list_header.append(["Report file name:", report_file])
+ list_header.append(["Directory:", directory])
+ list_header.append(["Recursive:", recursive])
+ list_header.append(["Ignore symlinks:", ignore_symlinks])
+ list_header.append(["Action to perform:", action.capitalize()])
+ list_header.append(["Position:", position.capitalize()])
+ list_header.append(["Input string:", "\"" + input_string + "\" " \
+ "(without double quotes)"])
+ if not replace_string == None:
+ list_header.append(["Replace string:", "\"" + replace_string + \
+ "\" (without double quotes)"])
+ if strip_chars == None:
+ list_header.append(["Strip chars:", "None"])
+ else:
+ list_header.append(["Strip chars:", "\"" + strip_chars + "\" " \
+ "(without double quotes)"])
+
+ list_header.append(["Exclude files:", exclude])
+ list_header.append(["Explicit files:", explicit])
+ list_header.append(["Pattern:", pattern])
+ list_header.append(["Ignore case:", ignore_case])
+ list_header.append(["Regex syntax:", regex_syntax])
+
+ common.report(report_file, list_header, list_renamed, list_excluded,
+ list_skipped, time_start)
+ else:
+ common.rename(list_renamed)
+
+def rename_files(directory, rename_mode, separator=" ", recursive=False,
+ padding=0, exclude=None, pattern=None, ignore_case=True,
+ regex_syntax=False, report_file=None, ignore_symlinks=False,
+ ignore_file_ext=False, custom_name=None, step=1,
+ order_by=None):
+ """
+ Rename the base name of files based on the name of the directory where
+ they are stored in and add a numeric ID.
+ """
+ pv.path(directory, "given", False, True)
+ pv.compstr(rename_mode, "rename mode",
+ ["fill-gaps", "increase", "keep-order", "rename-new"])
+ pv.intrange(padding, "padding", 0, 12, True)
+ pv.string(separator, "seperator", False, common.get_invalid_chars())
+ pv.intvalue(step, "step", True, False, False)
+
+ if not order_by == None:
+ pv.compstr(order_by, "order by", ["accessed", "created", "modified"])
+ if not rename_mode == "keep-order":
+ raise Exception("The order-by argument can only be used in " \
+ "combination with keep-order mode.")
+
+ step = int(step)
+ rename_mode = rename_mode.lower()
+ directory = os.path.abspath(directory)
+ if not directory.endswith(os.path.sep):
+ directory += os.path.sep
+
+ if report_file == None:
+ simulate = False
+ else:
+ pv.path(report_file, "report", True, False)
+ report_file = os.path.abspath(report_file)
+ simulate = True
+
+ if not custom_name == None:
+ pv.string(custom_name, "custom file name", False,
+ common.get_invalid_chars())
+
+ time_start = dt.now()
+
+ list_content = []
+ list_excluded = []
+ list_renamed = []
+ list_skipped = []
+ regex = None
+ if not pattern == None:
+ regex = common.compile_regex(pattern, ignore_case, regex_syntax)
+
+ list_content, list_excluded = \
+ common.get_files(directory, recursive, ignore_case, regex, exclude,
+ ignore_symlinks, order_by)
+
+ for item in list_content:
+ list_files = item[1]
+ if rename_mode == "fill-gaps":
+ list_renamed, list_skipped = \
+ __rename_files_fill(list_files, list_renamed, list_skipped,
+ separator, padding, True, ignore_file_ext,
+ custom_name, step)
+ elif rename_mode == "rename-new":
+ list_renamed, list_skipped = \
+ __rename_files_fill(list_files, list_renamed, list_skipped,
+ separator, padding, False,
+ ignore_file_ext, custom_name, step)
+ elif rename_mode == "keep-order":
+ list_renamed, list_skipped = \
+ __rename_files_keep_order(list_files, list_renamed,
+ list_skipped, separator, padding,
+ ignore_file_ext, custom_name, step,
+ order_by)
+ else:
+ raise Exception("An invalid rename mode was given.")
+
+ if simulate:
+ if padding == 0:
+ padding = "Set automatically"
+ else:
+ padding = str(padding)
+
+ explicit = None
+ if exclude == None:
+ exclude = False
+ explicit = False
+ elif exclude:
+ explicit = False
+ else:
+ explicit = True
+
+ if order_by == "accessed":
+ order_by = "Access time"
+ elif order_by == "created":
+ order_by = "Creation time"
+ elif order_by == "modified":
+ order_by = "Modification time"
+ else:
+ order_by = "False"
+
+ list_header = []
+ list_header.append("Nomen File Renamer simulation report")
+ list_header.append(["Report file name:", report_file])
+ list_header.append(["Directory:", directory])
+ list_header.append(["Recursive:", recursive])
+ list_header.append(["Ignore symlinks:", ignore_symlinks])
+ list_header.append(["Rename mode:", rename_mode.capitalize()])
+ list_header.append(["Order by time:", order_by])
+ list_header.append(["Separator:", "\"" + separator + "\" " \
+ "(without double quotes)"])
+ list_header.append(["Numeric padding:", padding])
+ list_header.append(["Step size:", step])
+ list_header.append(["Exclude files:", exclude])
+ list_header.append(["Explicit files:", explicit])
+ list_header.append(["Pattern:", pattern])
+ list_header.append(["Ignore case:", ignore_case])
+ list_header.append(["Regex syntax:", regex_syntax])
+
+ common.report(report_file, list_header, list_renamed, list_excluded,
+ list_skipped, time_start)
+ else:
+ common.rename(list_renamed)
+
+def __check_config_mismatch(case, cfg_lower, cfg_title, cfg_mixed, cfg_upper):
+ """
+ Check for a case config mismatch.
+ """
+ cfg_mismatch = False
+
+ if case == "lower":
+ if not cfg_lower == None:
+ cfg_mismatch = True
+ elif case == "title":
+ if not cfg_title == None:
+ cfg_mismatch = True
+ elif case == "upper":
+ if not cfg_upper == None:
+ cfg_mismatch = True
+ elif case == "config":
+ if cfg_lower == None and \
+ cfg_mixed == None and \
+ cfg_title == None and \
+ cfg_upper == None:
+ raise Exception("The config target case requires at least one " \
+ "case config file to operate.")
+ else:
+ raise Exception("An unsupported case string was given.")
+
+ if cfg_mismatch:
+ if case == "title":
+ case += " "
+ raise Exception("Config file mismatch (cannot use %scase config " \
+ "file when using %scase as target case anyway)." % \
+ (case, case))
+
+def __convert_case(list_files, list_renamed, list_skipped, case,
+ conflict_mode, recursive, cfg_lower, cfg_mixed, cfg_title,
+ cfg_upper):
+ """
+ Core method to convert the case of the base name of files.
+ """
+ file_ext = ""
+ list_lower = []
+ list_mixed = []
+ list_title = []
+ list_upper = []
+
+ fs_case = common.get_fs_case_sensitivity(os.path.dirname(list_files[0]))
+
+ if cfg_lower == None and cfg_mixed == None and \
+ cfg_title == None and cfg_upper == None:
+ static_case = False
+ else:
+ list_lower, list_mixed, list_title, list_upper = \
+ statcase.parse_case_configs(cfg_lower, cfg_mixed, cfg_title,
+ cfg_upper)
+ static_case = True
+
+ for file_path in list_files:
+ num = 1
+ list_path = file_path.split(os.path.sep)
+ file_name = list_path[-1]
+
+ if os.path.extsep in file_name:
+ file_ext = os.path.splitext(file_name)[1]
+ else:
+ file_ext = ""
+
+ base_name = os.path.splitext(file_name)[0]
+ if case == "lower":
+ base_name_target = base_name.lower()
+ elif case == "title":
+ base_name_target = base_name.title()
+ elif case == "upper":
+ base_name_target = base_name.upper()
+ else:
+ base_name_target = base_name
+
+ if static_case:
+ base_name_target = __static_case(base_name_target, case,
+ list_lower, list_mixed,
+ list_title,
+ list_upper).rstrip()
+
+ file_newpath = file_path.replace(base_name + file_ext,
+ base_name_target + file_ext)
+ if file_path == file_newpath:
+ list_skipped.append(file_path)
+ continue
+
+ if conflict_mode == "rename":
+ while True:
+ if common.file_exists(file_newpath, list_renamed, fs_case):
+ if not fs_case:
+ if file_path.lower() == file_newpath.lower():
+ break
+ file_newpath = \
+ file_path.replace(base_name,
+ base_name_target + "_" + str(num))
+ num += 1
+ else:
+ break
+ elif conflict_mode == "skip":
+ if common.file_exists(file_newpath, list_renamed, fs_case):
+ if not fs_case:
+ if not file_path.lower() == file_newpath.lower():
+ list_skipped.append(file_path)
+ continue
+ else:
+ list_skipped.append(file_path)
+ continue
+
+ if os.path.exists(file_path):
+ list_renamed.append([file_path, file_newpath + ".__temp__",
+ file_newpath])
+
+ return list_renamed, list_skipped
+
+def __fill_num_gaps(list_files, separator, padding, list_renamed,
+ list_skipped, fs_case, step):
+ """
+ Core method to fill numeration gaps.
+ """
+ list_temp = []
+ list_temp.extend(list_skipped)
+
+ for i in list_renamed:
+ list_temp.append(i[2])
+ list_temp.sort()
+ list_gaps = __get_num_gaps(list_files, separator, padding, step)
+
+ if len(list_gaps) > 0:
+ list_gaps.sort(reverse=True)
+ list_skipped.sort(reverse=True)
+
+ while len(list_gaps) > 0:
+ if len(list_skipped) < 1:
+ break
+
+ file_path = list_skipped.pop(0)
+ list_path = file_path.split(os.path.sep)
+ file_dir = list_path[-2]
+ file_name = list_path[-1]
+
+ if os.path.extsep in file_name:
+ file_ext = os.path.splitext(file_name)[1]
+ else:
+ file_ext = ""
+
+ num = list_gaps.pop(0)
+ file_num = str(num).rjust(int(padding), "0")
+ file_newname = file_dir + separator + \
+ file_num.replace(" ", "0") + file_ext
+ file_newpath = file_path.replace(file_name, file_newname)
+
+ if common.file_exists(file_newpath, list_renamed, fs_case):
+ list_skipped.append(file_path)
+ else:
+ list_renamed.append([file_path, None, file_newpath])
+
+ return list_renamed, list_skipped
+
+def __get_num_gaps(list_files, separator, padding, step):
+ """
+ Method to determine numeration gaps.
+ """
+ list_gaps = []
+
+ for i in range(len(list_files)):
+ x = (i + 1) * step
+ n = separator + str(x).rjust(int(padding),"0")
+ if not any(n in s for s in list_files):
+ list_gaps.append(int(n.replace(separator, "")))
+
+ return list_gaps
+
+def __modify_name_add(file_name, string, position):
+ """
+ Core method to add a string to the base name of a file.
+ """
+ file_newname = ""
+
+ if position == "prefix":
+ file_newname = string + file_name
+ elif position == "suffix":
+ file_newname = file_name + string
+
+ return file_newname
+
+def __modify_name_remove(file_name, string, position):
+ """
+ Core method to remove a string from the base name of a file.
+ """
+ file_newname = ""
+
+ if position == "any":
+ file_newname = file_name.replace(string, "")
+ elif position == "prefix":
+ file_newname = re.sub("^" + string, "", file_name)
+ elif position == "suffix":
+ file_newname = re.sub(string + "$", "", file_name)
+
+ return file_newname
+
+def __modify_name_replace(file_name, string, replace_string, position):
+ """
+ Core method to replace a string inside the base name of a file.
+ """
+ file_newname = ""
+
+ if position == "any":
+ file_newname = file_name.replace(string, replace_string)
+ elif position == "prefix":
+ file_newname = re.sub("^" + string, replace_string, file_name)
+ elif position == "suffix":
+ file_newname = re.sub(string + "$", replace_string, file_name)
+
+ return file_newname
+
+def __modify_names(list_files, list_renamed, list_skipped, action, position,
+ input_string, replace_string, strip_chars):
+ """
+ Core method to modify the base name of files by adding, removing or
+ replacing a user-defined string.
+ """
+ for file_path in list_files:
+ list_path = file_path.split(os.path.sep)
+ file_name = list_path[-1]
+ file_newname = ""
+ file_newpath = ""
+
+ if os.path.extsep in file_name:
+ file_temp = os.path.splitext(file_name)
+ file_name = file_temp[0]
+ file_ext = file_temp[1]
+ else:
+ file_ext = ""
+
+ if action == "add":
+ file_newname = __modify_name_add(file_name, input_string,
+ position)
+ elif action == "remove":
+ file_newname = __modify_name_remove(file_name, input_string,
+ position)
+ elif action == "replace":
+ file_newname = __modify_name_replace(file_name, input_string,
+ replace_string, position)
+
+ if not strip_chars == None:
+ if len(strip_chars) > 0:
+ file_newname = file_newname.strip(strip_chars)
+
+ file_newname += file_ext
+ file_newpath = file_path.replace(file_name + file_ext, file_newname)
+ if file_newpath == "":
+ list_skipped.append(file_path)
+ elif file_newname == "" or file_newname == file_ext:
+ list_skipped.append(file_path)
+ else:
+ if file_path == file_newpath:
+ list_skipped.append(file_path)
+ else:
+ if os.path.exists(file_newpath):
+ list_skipped.append(file_path)
+ else:
+ list_renamed.append([file_path, None, file_newpath])
+
+ return list_renamed, list_skipped
+
+def __process_case_list(case, case_list):
+ """
+ Process a case list and separate words from regular expressions.
+ """
+ list_strings = []
+ list_words = []
+
+ for item in case_list:
+ if case == "lower":
+ if item.startswith("$("):
+ list_strings.append(item.replace("$", "").lower())
+ else:
+ list_words.append(item.lower())
+ elif case == "title":
+ if item.startswith("$("):
+ list_strings.append(item.replace("$", "").title())
+ else:
+ list_words.append(item.title())
+ elif case == "upper":
+ if item.startswith("$("):
+ list_strings.append(item.replace("$", "").upper())
+ else:
+ list_words.append(item.upper())
+ else:
+ if item.startswith("$("):
+ list_strings.append(item.replace("$", ""))
+ else:
+ list_words.append(item)
+
+ return list_words, list_strings
+
+def __rename_files_fill(list_files, list_renamed, list_skipped, separator,
+ padding, fill_gaps=False, ignore_file_ext=False,
+ custom_name=None, step=1):
+ """
+ Core method to rename the base name of files based on the name of the
+ directory where they are stored in using one of the "fill" rename
+ modes (such as "fill-gaps" and "rename-new").
+ """
+ file_newpath = ""
+ num = 0
+
+ fs_case = common.get_fs_case_sensitivity(os.path.dirname(list_files[0]))
+
+ if fill_gaps:
+ list_temp_renamed = []
+ list_temp_skipped = []
+ obj_ren = list_temp_renamed
+ obj_skip = list_temp_skipped
+ else:
+ obj_ren = list_renamed
+ obj_skip = list_skipped
+
+ if padding == 0:
+ padding = len(str(len(list_files)))
+
+ for file_path in list_files:
+ list_path = file_path.split(os.path.sep)
+ file_name = list_path[-1]
+
+ if custom_name == None:
+ file_dir = list_path[-2]
+ else:
+ file_dir = custom_name
+
+ if os.path.extsep in file_name:
+ file_ext = os.path.splitext(file_name)[1]
+ else:
+ file_ext = ""
+
+ if file_name.startswith(file_dir + separator):
+ try:
+ temp = file_name.replace(file_dir + separator, "")
+ list_pad = temp.split(".")
+ file_padding = len(list_pad[0])
+
+ if step > 1:
+ if int(list_pad[0]) % step == 0:
+ obj_skip.append(file_path)
+ continue
+ else:
+ if int(padding) == file_padding:
+ obj_skip.append(file_path)
+ continue
+ except:
+ pass
+
+ if not ignore_file_ext:
+ num = 0
+
+ file_newpath = file_path
+ while common.file_exists(file_newpath, obj_ren, fs_case) or \
+ common.file_exists(file_newpath, obj_skip, fs_case):
+ num += step
+ file_num = str(num).rjust(int(padding), "0")
+ file_newname = \
+ file_dir + separator + file_num.replace(" ", "0") + file_ext
+ file_newpath = file_path.replace(file_name, file_newname)
+
+ if os.path.exists(file_path):
+ if file_path == file_newpath:
+ obj_skip.append(file_path)
+ else:
+ obj_ren.append([file_path, None, file_newpath])
+
+ if fill_gaps:
+ list_temp_renamed, list_temp_skipped = \
+ __fill_num_gaps(list_files, separator, padding,
+ list_temp_renamed, list_temp_skipped,
+ fs_case, step)
+ list_renamed.extend(list_temp_renamed)
+ list_skipped.extend(list_temp_skipped)
+
+ return list_renamed, list_skipped
+
+def __rename_files_keep_order(list_files, list_renamed, list_skipped,
+ separator, padding, ignore_file_ext=False,
+ custom_name=None, step=1, order_by=None):
+ """
+ Core method to rename the base name of files based on the name of the
+ directory where they are stored in using "keep-order" rename mode.
+ """
+ file_newpath = ""
+ file_temppath = ""
+ temp_file_ext = ""
+ list_new = []
+ list_ren = []
+ num = 0
+
+ fs_case = common.get_fs_case_sensitivity(os.path.dirname(list_files[0]))
+
+ if padding == 0:
+ padding = len(str(len(list_files)))
+
+ for file_path in list_files:
+ list_path = file_path.split(os.path.sep)
+ file_dir = list_path[-2]
+ file_name = list_path[-1]
+
+ if file_name.startswith(file_dir + separator):
+ list_ren.append(file_path)
+ else:
+ list_new.append(file_path)
+
+ list_files = []
+ list_files.extend(list_ren)
+ list_files.extend(list_new)
+
+ for file_path in list_files:
+ list_path = file_path.split(os.path.sep)
+ file_name = list_path[-1]
+
+ if custom_name == None:
+ file_dir = list_path[-2]
+ else:
+ file_dir = custom_name
+
+ if os.path.extsep in file_name:
+ file_ext = os.path.splitext(file_name)[1]
+ else:
+ file_ext = ""
+
+ if not ignore_file_ext:
+ if not file_ext == temp_file_ext:
+ num = 0
+
+ file_temppath = file_path
+ temp_file_ext = file_ext
+ while common.file_exists(file_temppath, list_renamed, fs_case):
+ num += step
+ file_num = str(num).rjust(int(padding), "0")
+ file_newname = \
+ file_dir + separator + file_num.replace(" ", "0") + file_ext
+ file_newpath = file_path.replace(file_name, file_newname)
+ if not file_newpath in list_skipped:
+ file_temppath = file_newpath + ".__temp__"
+
+ if os.path.exists(file_path):
+ if file_path == file_newpath:
+ list_skipped.append(file_path)
+ else:
+ list_renamed.append([file_path, file_temppath, file_newpath])
+
+ return list_renamed, list_skipped
+
+def __static_case(base_name, case, list_lower, list_mixed, list_title,
+ list_upper):
+ """
+ Convert the case of the base name of a file to lowercase, mixed case,
+ title case or uppercase based on the read out config files.
+ """
+ list_file_str = base_name.split(" ")
+ list_cfg_str, list_cfg_regex = \
+ __process_case_list("lower", list_lower)
+ base_name = __static_case_word(list_cfg_str, list_file_str)
+ base_name = __static_case_string(list_cfg_regex, base_name)
+
+ list_file_str = base_name.split(" ")
+ list_cfg_str, list_cfg_regex = \
+ __process_case_list("mixed", list_mixed)
+ base_name = __static_case_word(list_cfg_str, list_file_str)
+ base_name = __static_case_string(list_cfg_regex, base_name)
+
+ list_file_str = base_name.split(" ")
+ list_cfg_str, list_cfg_regex = \
+ __process_case_list("title", list_title)
+ base_name = __static_case_word(list_cfg_str, list_file_str)
+ base_name = __static_case_string(list_cfg_regex, base_name)
+
+ list_file_str = base_name.split(" ")
+ list_cfg_str, list_cfg_regex = \
+ __process_case_list("upper", list_upper)
+ base_name = __static_case_word(list_cfg_str, list_file_str)
+ base_name = __static_case_string(list_cfg_regex, base_name)
+
+ return base_name
+
+def __static_case_word(list_cfg_str, list_file_str):
+ """
+ Convert the case of each word of the base name of a file.
+ """
+ base_name = ""
+
+ for file_str in list_file_str:
+ for cfg_str in list_cfg_str:
+ spec_chars = "\\?*+$.:; ^|()[]{}"
+ for char in spec_chars:
+ cfg_str = cfg_str.replace(char, "")
+
+ if file_str.lower() == cfg_str.lower():
+ file_str = cfg_str
+
+ base_name += (file_str + " ")
+
+ return base_name
+
+def __static_case_string(list_cfg_regex, base_name):
+ """
+ Convert the case of a string inside the base name of a file.
+ """
+ for item in list_cfg_regex:
+ spec_chars = "\\?*+$.:;^|()[]{}"
+ for char in spec_chars:
+ item = item.replace(char, "")
+
+ regex = re.compile(".*" + item.lower() + ".*")
+ if regex.match(base_name.lower()):
+ base_name = re.sub(item.lower(), item, base_name,
+ flags=re.IGNORECASE)
+
+ return base_name
+
+# EOF
+
diff --git a/python2/core/paval.py b/python2/core/paval.py
new file mode 100755
index 0000000..3138fcb
--- /dev/null
+++ b/python2/core/paval.py
@@ -0,0 +1,203 @@
+#!/usr/bin/env python2
+# -*- coding: utf-8 -*-
+
+# ============================================================================
+# PaVal - Parameter validation module
+# Copyright (C) 2018 by Ralf Kilian
+# Distributed under the MIT License (https://opensource.org/licenses/MIT)
+#
+# Website: http://www.urbanware.org
+# GitHub: https://github.com/urbanware-org/paval
+# ============================================================================
+
+__version__ = "1.2.7"
+
+import filecmp
+import os
+
+
+def compfile(input_path, name="", list_files=None):
+ """
+ Compare files to avoid that the same file is given multiple times or
+ in different ways (e. g. different name but same content).
+ """
+ __string(input_path, "%s path" % name, True)
+
+ if list_files is None:
+ list_files = []
+ elif not list_files:
+ __ex("File list is empty (no files to compare with).", True)
+ else:
+ for item in list_files:
+ if not isinstance(item, list):
+ __ex("Every list item must be a sub-list.", True)
+ if not len(item) == 2:
+ __ex("Every sub-list must contain two items.", True)
+
+ input_path = os.path.abspath(input_path)
+ for item in list_files:
+ path_compare = os.path.abspath(str(item[0]))
+ name_compare = str(item[1])
+ if input_path == path_compare:
+ __ex("The %s and the %s file path must not be identical." %
+ (name, name_compare), False)
+ if os.path.exists(input_path) and os.path.exists(path_compare):
+ if filecmp.cmp(input_path, path_compare, 0):
+ __ex("The %s and %s file content must not be identical." %
+ (name, name_compare), False)
+
+
+def compstr(input_string, name="", list_strings=None):
+ """
+ Compare a string with a list of strings and check if it is an item of
+ that list.
+ """
+ __string(input_string, name, False)
+ if not list_strings:
+ __ex("No %s strings to compare with." % name, True)
+ if input_string not in list_strings:
+ __ex("The %s '%s' does not exist." % (name, input_string), False)
+
+
+def get_version():
+ """
+ Return the version of this module.
+ """
+ return __version__
+
+
+def intrange(value, name="", value_min=None, value_max=None, zero=False):
+ """
+ Validate an integer range.
+ """
+ value = __integer(value, "%s value" % name, False)
+ if value_min is not None:
+ value_min = __integer(value_min, "minimal %s value" % name, True)
+ intvalue(value_min, name, True, True, True)
+ if value_max is not None:
+ value_max = __integer(value_max, "maximal %s value" % name, True)
+ intvalue(value_max, name, True, True, True)
+ if not zero:
+ if value == 0:
+ __ex("The %s value must not be zero." % name, False)
+ if (value_min is not None) and (value_max is not None):
+ if value_min > value_max:
+ __ex("The maximal %s value must be greater than the minimal "
+ "value." % name, False)
+ if (value_min == value_max) and (value != value_min):
+ __ex("The %s value can only be %s (depending on further range "
+ "further range arguments)." % (name, value_min), False)
+ if (value < value_min) or (value > value_max):
+ __ex("The %s value must be between %s and %s (depending on "
+ "further range arguments)." % (name, value_min, value_max),
+ False)
+ elif value_min is not None:
+ if value < value_min:
+ __ex("The %s value must not be less than %s." % (name, value_min),
+ False)
+ elif value_max is not None:
+ if value > value_max:
+ __ex("The %s value must not be greater than %s." %
+ (name, value_max), False)
+
+
+def intvalue(value, name="", positive=True, zero=False, negative=False):
+ """
+ Validate a single integer value.
+ """
+ value = __integer(value, "%s value" % name, False)
+ if not positive:
+ if value > 0:
+ __ex("The %s value must not be positive." % name, False)
+ if not zero:
+ if value == 0:
+ __ex("The %s value must not be zero." % name, False)
+ if not negative:
+ if value < 0:
+ __ex("The %s value must not be negative." % name, False)
+
+
+def path(input_path, name="", is_file=False, exists=False):
+ """
+ Validate a path of a file or directory.
+ """
+ string(input_path, "%s path" % name, False, None)
+ input_path = os.path.abspath(input_path)
+
+ if is_file:
+ path_type = "file"
+ else:
+ path_type = "directory"
+ if exists:
+ if not os.path.exists(input_path):
+ __ex("The given %s %s does not exist." % (name, path_type), False)
+ if (is_file and not os.path.isfile(input_path)) or \
+ (not is_file and not os.path.isdir(input_path)):
+ __ex("The given %s %s path is not a %s." % (name, path_type,
+ path_type), False)
+ else:
+ if os.path.exists(input_path):
+ __ex("The given %s %s path already exists." % (name, path_type),
+ False)
+
+
+def string(input_string, name="", wildcards=False, invalid_chars=None):
+ """
+ Validate a string.
+ """
+ __string(input_string, name, False)
+ if invalid_chars is None:
+ invalid_chars = ""
+ if not wildcards:
+ if ("*" in input_string) or ("?" in input_string):
+ __ex("The %s must not contain wildcards." % name, False)
+ if invalid_chars:
+ for char in invalid_chars:
+ if char in input_string:
+ # Use single quotes by default or double quotes in case the
+ # single quotes are the invalid character
+ quotes = "'"
+ if char == quotes:
+ quotes = "\""
+
+ __ex("The %s contains at least one invalid character "
+ "(%s%s%s)." % (name, quotes, char, quotes), False)
+
+
+def __ex(exception_string, internal=False):
+ """
+ Internal method to raise an exception.
+ """
+ ex = str(exception_string).strip()
+ while " " * 2 in ex:
+ ex = ex.replace((" " * 2), " ")
+ if internal:
+ ex = "PaVal: " + ex
+ raise Exception(ex)
+
+
+def __integer(value, name="", internal=False):
+ """
+ Internal method for basic integer validation.
+ """
+ if value is None:
+ __ex("The %s is missing." % name, internal)
+ if value == "":
+ __ex("The %s must not be empty." % name, internal)
+ try:
+ value = int(value)
+ except ValueError:
+ __ex("The %s must be an integer." % name, internal)
+ return int(value)
+
+
+def __string(input_string, name="", internal=False):
+ """
+ Internal method for basic string validation.
+ """
+ if input_string is None:
+ __ex("The %s is missing." % name, internal)
+ if input_string == "":
+ __ex("The %s must not be empty." % name, internal)
+
+# EOF
diff --git a/python2/core/statcase.py b/python2/core/statcase.py
new file mode 100755
index 0000000..656da7e
--- /dev/null
+++ b/python2/core/statcase.py
@@ -0,0 +1,118 @@
+#!/usr/bin/env python2
+# -*- coding: utf-8 -*-
+
+# ============================================================================
+# Nomen - Multi-purpose rename tool
+# Static case core module
+# Copyright (C) 2018 by Ralf Kilian
+# Distributed under the MIT License (https://opensource.org/licenses/MIT)
+#
+# Website: http://www.urbanware.org
+# GitHub: https://github.com/urbanware-org/nomen
+# ============================================================================
+
+__version__ = "2.3.5"
+
+import os
+import paval as pv
+import sys
+
+def get_version():
+ """
+ Return the version of this module.
+ """
+ return __version__
+
+def parse_case_configs(cfg_lower=None, cfg_mixed=None, cfg_title=None,
+ cfg_upper=None):
+ """
+ Parse the configuration files for static file name case
+ conversion.
+ """
+ list_lower = []
+ list_mixed = []
+ list_title = []
+ list_upper = []
+
+ if not cfg_lower == None:
+ list_lower = __read_config(__config_abspath(cfg_lower, "lowercase"))
+ if not cfg_mixed == None:
+ list_mixed = __read_config(__config_abspath(cfg_mixed, "mixed case"))
+ if not cfg_title == None:
+ list_title = __read_config(__config_abspath(cfg_title, "title case"))
+ if not cfg_upper == None:
+ list_upper = __read_config(__config_abspath(cfg_upper, "uppercase"))
+
+ __check_dupes(list_lower, list_mixed, list_title, list_upper)
+
+ return list_lower, list_mixed, list_title, list_upper
+
+def __check_dupes(list_lower, list_mixed, list_title, list_upper):
+ """
+ Check for duplicate case list entries.
+ """
+ list_diff = []
+
+ list_diff = __process_case_list(list_lower, list_diff)
+ list_diff = __process_case_list(list_mixed, list_diff)
+ list_diff = __process_case_list(list_title, list_diff)
+ __process_case_list(list_upper, list_diff)
+
+def __config_abspath(config, description):
+ """
+ Get the absolute path of a config file.
+ """
+ description += " config"
+ try:
+ pv.path(config, description, True, True)
+ except:
+ config_dir = os.path.abspath(os.path.dirname(sys.argv[0]))
+ config = os.path.join(config_dir, "cfg", os.path.basename(config))
+ pv.path(config, description, True, True)
+ config = os.path.abspath(config)
+
+ return config
+
+def __process_case_list(list_input, list_output):
+ """
+ Process a case list and raise an exception in case of duplicate
+ entries.
+ """
+ duplicate = ""
+
+ for item in list_input:
+ if item == None or item == "":
+ continue
+ elif item.lower() in list_output:
+ duplicate = item
+ break
+ else:
+ list_output.append(item.lower())
+
+ if not duplicate == "":
+ raise Exception("Duplicate config file entries. The same string " \
+ "must not exist in multiple config files. The " \
+ "duplicate string was \"%s\" (without the " \
+ "enclosing quotes)." % duplicate)
+
+ return list_output
+
+def __read_config(config_file):
+ """
+ Read out the contents of a config file.
+ """
+ list_config = []
+
+ fh_config = open(config_file, "r")
+ for line in fh_config:
+ line = line.strip()
+ if line == "" or line.startswith("#"):
+ continue
+ list_config.append(line.replace("\n", ""))
+ fh_config.close()
+ list_config.sort
+
+ return list_config
+
+# EOF
+
diff --git a/python2/docs/usage_nomen-dirspace.txt b/python2/docs/usage_nomen-dirspace.txt
new file mode 100644
index 0000000..a91c061
--- /dev/null
+++ b/python2/docs/usage_nomen-dirspace.txt
@@ -0,0 +1,195 @@
+
+USAGE (nomen-dirspace.py)
+
+ Contents:
+
+ 1. Definition
+ 2. General stuff
+ 2.1 How to run Python scripts
+ 2.2 Overview of all command-line arguments
+ 2.3 Warning
+ 2.4 Simulation mode
+ 2.5 Identical directory names
+ 3. Remove spaces from directory names
+ 3.1 Duplicate spaces
+ 3.2 Leading spaces
+ 3.3 Trailing spaces
+ 3.4 Duplicate, leading as well as trailing spaces
+ 4. Spaces next to certain characters
+ 4.1 Brackets
+ 4.2 Hyphens
+ 4.3 Punctuation characters
+
+ 1. Definition
+
+ The Nomen Directory Name Space Modifier (former "Nomen Directory Name
+ Space Remover") script modifies a directory name by removing leading,
+ trailing and duplicate spaces or by inserting and removing spaces next
+ to punctuation characters.
+
+ 2. General stuff
+
+ 2.1 How to run Python scripts
+
+ All usage examples below show how to execute the Python scripts on
+ the shell of a Unix-like system. If you do not know, how to run
+ those scripts on your operating system, you may have a look at
+ this page:
+
+ http://www.urbanware.org/howto_python.html
+
+ 2.2 Overview of all command-line arguments
+
+ Usually, each script requires command-line arguments to operate.
+ So, to get an overview of all arguments available, simply run the
+ script with the "--help" argument. For example:
+
+ $ ./nomen-dirspace.py --help
+
+ 2.3 Warning
+
+ Please use this tool with care to avoid data damage or loss!
+
+ As a matter of fact, there is no function to undo the changes done
+ by this tool, so you should be aware of what you are doing. If
+ not, do NOT use this tool.
+
+ Improper use (e. g. modifying system directories) will corrupt
+ your system!
+
+ 2.4 Simulation mode
+
+ Due to the fact that this tool simply removes unnecessary spaces
+ in directory names, it does not provide any simulation mode.
+
+ 2.5 Identical directory names
+
+ For example, if you have the directories
+
+ /tmp/pics/My Holiday/
+ /tmp/pics/My Holiday/
+
+ and want to remove the duplicate spaces, both directories would
+ have the same name after performing the space removal operation.
+ Due to this, they would be combined and the data inside of them
+ would get mixed up.
+
+ To avoid this, the Nomen Directory Name Space Remover script does
+ not remove the spaces from a directory if another directory with
+ the new name already exists. The directory will simply be ignored,
+ but the script will process its sub-directories if the recursive
+ command-line argument was given.
+
+ 3. Remove spaces from directory names
+
+ The following operations can also be combined using the corresponding
+ command-line arguments (as described in section 3.4).
+
+ 3.1 Duplicate spaces
+
+ For example, if you have the following directory
+
+ /tmp/pics/My Holiday/
+
+ and you want to remove the duplicate spaces, type:
+
+ ./nomen-dirspace.py -d /tmp/pics/ -s
+
+ 3.2 Leading spaces
+
+ For example, if you have the following directory
+
+ /tmp/pics/ My Holiday/
+
+ and you want to remove the leading spaces, type:
+
+ ./nomen-dirspace.py -d /tmp/pics/ -l
+
+ 3.3 Trailing spaces
+
+ For example, if you have the following directory
+
+ /tmp/pics/My Holiday /
+
+ and you want to remove the trailing spaces, type:
+
+ ./nomen-dirspace.py -d /tmp/pics/ -t
+
+ 3.4 Duplicate, leading as well as trailing spaces
+
+ For example, if you have the following directory
+
+ /tmp/pics/ My Holiday /
+
+ and you want to remove the duplicate, leading and trailing spaces,
+ type:
+
+ ./nomen-dirspace.py -d /tmp/pics/ -s -l -t
+
+ 4. Spaces next to certain characters
+
+ The Nomen Directory Name Space Modifier also allows inserting and
+ removing spaces next to certain characters such as brackets, dashes
+ and punctuation characters. These are automated processes without any
+ further options.
+
+ The following command-line arguments can be combined with those
+ mentioned in section 3 as well as with each other.
+
+ Please notice that inserting spaces may result in duplicate spaces
+ again. So, when using the following command-line arguments it is
+ recommended to also add the "-s" (or "--remove-duplicate") argument
+ (as shown in the examples below).
+
+ 4.1 Brackets
+
+ The "-b" (or "--brackets") command-line argument will remove all
+ unnecessary spaces around brackets. For example, if you have a
+ directory like this
+
+ /tmp/music/ Foo( Bar [ Album ] ) /
+
+ and run Nomen Directory Name Space Modifier with the corresponding
+ argument
+
+ ./nomen-dirspace.py -d /tmp/music/ -s -b
+
+ the directory will be changed as follows:
+
+ /tmp/music/Foo (Bar [Album])/
+
+ 4.2 Hyphens
+
+ The --hyphens" command-line argument will add additional spaces
+ around hyphens ("-").
+
+ /tmp/music/ John Doe -Foo /
+
+ and run Nomen Directory Name Space Modifier with the corresponding
+ argument
+
+ ./nomen-dirspace.py -d /tmp/music/ -s -p
+
+ the directory will be changed as follows:
+
+ /tmp/music/John Doe - Foo/
+
+ 4.3 Punctuation characters
+
+ The "-p" (or "--punctuation") command-line argument will remove
+ all unnecessary spaces around the punctuation characters such as
+ dots ("."), commas (","), colons (":"), semi-colons (";") as well
+ as exclamation ("!") and question marks ("?"). Furthermore, it
+ adds spaces next to them when necessary.
+
+ /tmp/music/ Foo,Bar feat.John Doe - Foo ! /
+
+ and run Nomen Directory Name Space Modifier with the corresponding
+ argument
+
+ ./nomen-dirspace.py -d /tmp/music/ -s -p
+
+ the directory will be changed as follows:
+
+ /tmp/music/Foo, Bar feat. John Doe - Foo!/
+
diff --git a/python2/docs/usage_nomen-extcase.txt b/python2/docs/usage_nomen-extcase.txt
new file mode 100644
index 0000000..f75c612
--- /dev/null
+++ b/python2/docs/usage_nomen-extcase.txt
@@ -0,0 +1,126 @@
+
+USAGE (nomen-extcase.py)
+
+ Contents:
+
+ 1. Definition
+ 2. General stuff
+ 2.1 How to run Python scripts
+ 2.2 Overview of all command-line arguments
+ 2.3 Warning
+ 2.4 Simulation mode
+ 3. Convert the case of file extensions
+ 3.1 Rename mode
+ 3.2 Skip mode
+
+ 1. Definition
+
+ The Nomen Extension Case Converter script converts the case of the
+ extension of all files inside a directory and (if requested) in all of
+ its sub-directories.
+
+ 2. General stuff
+
+ 2.1 How to run Python scripts
+
+ All usage examples below show how to execute the Python scripts on
+ the shell of a Unix-like system. If you do not know, how to run
+ those scripts on your operating system, you may have a look at
+ this page:
+
+ http://www.urbanware.org/howto_python.html
+
+ 2.2 Overview of all command-line arguments
+
+ Usually, each script requires command-line arguments to operate.
+ So, to get an overview of all arguments available, simply run the
+ script with the "--help" argument. For example:
+
+ $ ./nomen-extcase.py --help
+
+ 2.3 Warning
+
+ Please use this tool with care to avoid data damage or loss!
+
+ It is strongly recommended to simulate every rename process first
+ to check which files would have been renamed. As a matter of fact,
+ there is no function to undo the changes done by this tool, so you
+ should be aware of what you are doing. If not, do NOT use this
+ tool.
+
+ Improper use (e. g. modifying files inside system directories)
+ will corrupt your system!
+
+ 2.4 Simulation mode
+
+ As already mentioned above, before renaming any files, you should
+ simulate the rename process by using the "--simulate" argument.
+
+ This argument requires a file path where the report of the rename
+ process will be written to. When in simulation mode, no files will
+ be renamed at all.
+
+ So, if you have a command line like this
+
+ ./nomen-extcase.py -m rename -c lower -d /tmp/pics/Holidays
+
+ but want to simulate the rename process and write the report into
+ the file "/tmp/report.txt", simply add the argument
+
+ --simulate /tmp/report.txt
+
+ to the command line:
+
+ ./nomen-extcase.py -m rename -c lower -d /tmp/pics/Holidays \
+ --simulate /tmp/report.txt
+
+ 3. Convert the case of file extensions
+
+ For example, you have two picture files inside a directory called
+ "Holidays" with these extensions
+
+ Holidays 1.JPG
+ Holidays 2.Jpg
+
+ and you convert the extension case to lowercase, the file extensions
+ will look like this:
+
+ Holidays 1.jpg
+ Holidays 2.jpg
+
+ But, for example, if file system is case senstive and the directory
+ contains the files
+
+ Holidays 1.JPG
+ Holidays 1.Jpg
+
+ there would be a problem converting the file extension case, because
+ both target file names would be identical. So, to avoid that, the
+ script also offers two operating modes.
+
+ 3.1 Rename mode
+
+ The "rename mode" adds a numeric ID to the duplicate target file
+ and converts the file extension:
+
+ Holidays 1.JPG will be renamed to "Holidays 1.jpg"
+ Holidays 1.Jpg will be renamed to "Holidays 1_1.jpg"
+
+ To use this mode and convert the file extensions to lowercase,
+ type:
+
+ ./nomen-extcase.py -m rename -c lower -d /tmp/pics/Holidays
+
+ 3.2 Skip mode
+
+ The "skip mode" simply skips the files where converting the case
+ of the file extension would result in duplicate file names:
+
+ Holidays 1.JPG will be renamed to "Holidays 1.jpg"
+ Holidays 1.Jpg will be skipped
+
+ To use this mode and convert the file extensions to lowercase,
+ type:
+
+ ./nomen-extcase.py -m skip -c lower -d /tmp/pics/Holidays
+
diff --git a/python2/docs/usage_nomen-extren.txt b/python2/docs/usage_nomen-extren.txt
new file mode 100644
index 0000000..33719ea
--- /dev/null
+++ b/python2/docs/usage_nomen-extren.txt
@@ -0,0 +1,134 @@
+
+USAGE (nomen-extren.py)
+
+ Contents:
+
+ 1. Definition
+ 2. General stuff
+ 2.1 How to run Python scripts
+ 2.2 Overview of all command-line arguments
+ 2.3 Warning
+ 2.4 Simulation mode
+ 3. Rename file extensions
+ 3.1 Rename mode
+ 3.2 Skip mode
+
+ 1. Definition
+
+ The Nomen Extension Renamer script renames (and adjusts) differently
+ spelled file extensions of the same file type file within a directory
+ and (if requested) in all of its sub-directories.
+
+ 2. General stuff
+
+ 2.1 How to run Python scripts
+
+ All usage examples below show how to execute the Python scripts on
+ the shell of a Unix-like system. If you do not know, how to run
+ those scripts on your operating system, you may have a look at
+ this page:
+
+ http://www.urbanware.org/howto_python.html
+
+ 2.2 Overview of all command-line arguments
+
+ Usually, each script requires command-line arguments to operate.
+ So, to get an overview of all arguments available, simply run the
+ script with the "--help" argument. For example:
+
+ $ ./nomen-extren.py --help
+
+ 2.3 Warning
+
+ Please use this tool with care to avoid data damage or loss!
+
+ It is strongly recommended to simulate every rename process first
+ to check which files would have been renamed. As a matter of fact,
+ there is no function to undo the changes done by this tool, so you
+ should be aware of what you are doing. If not, do NOT use this
+ tool.
+
+ Improper use (e. g. modifying files inside system directories)
+ will corrupt your system!
+
+ 2.4 Simulation mode
+
+ As already mentioned above, before renaming any files, you should
+ simulate the rename process by using the "--simulate" argument.
+
+ This argument requires a file path where the report of the rename
+ process will be written to. When in simulation mode, no files will
+ be renamed at all.
+
+ So, if you have a command line like this
+
+ ./nomen-extren.py -m rename -d /tmp/pics/Holidays \
+ -e "JPG;JPEG" -t "jpg"
+
+ but want to simulate the rename process and write the report into
+ the file "/tmp/report.txt", simply add the argument
+
+ --simulate /tmp/report.txt
+
+ to the command line:
+
+ ./nomen-extren.py -m rename -d /tmp/pics/Holidays \
+ -e "JPG;JPEG" -t "jpg" \
+ --simulate /tmp/report.txt
+
+ 3. Rename file extensions
+
+ For example, you have four picture files inside a directory called
+ "Holidays" with these extensions:
+
+ Holidays 1.jpg
+ Holidays 2.jpeg
+ Holidays 3.JPG
+ Holidays 4.Jpeg
+
+ All of these files are JPEG pictures, but their extensions are spelled
+ differently. So, the Nomen Extension Renamer script can be used to get
+ consistent file extensions:
+
+ Holidays 1.jpg
+ Holidays 2.jpg
+ Holidays 3.jpg
+ Holidays 4.jpg
+
+ But, for example, if file system is case senstive and the directory
+ contains the files
+
+ Holidays 1.Jpg
+ Holidays 1.jpeg
+
+ there would be a problem renaming the file extension, because both
+ file target names would be identical. So, to avoid that, the script
+ also offers two operating modes.
+
+ 3.1 Rename mode
+
+ The "rename mode" adds a numeric ID to the duplicate target file
+ and converts the file:
+
+ Holidays 1.Jpg will be renamed to "Holidays 1.jpg"
+ Holidays 1.jpeg will be renamed to "Holidays 1_1.jpg"
+
+ To use this mode and rename the extensions of JPEG files,
+ type:
+
+ ./nomen-extren.py -m rename -d /tmp/pics/Holidays \
+ -e "JPG;JPEG" -t "jpg"
+
+ 3.2 Skip mode
+
+ The "skip mode" simply skips the files where converting the case
+ of the file extension would result in duplicate file names:
+
+ Holidays 1.Jpg will be renamed to "Holidays 1.jpg"
+ Holidays 1.jpeg will be skipped
+
+ To use this mode and rename the extensions of JPEG files, type:
+
+ ./nomen-extren.py -m skip -d /tmp/pics/Holidays \
+ -e "JPG;JPEG" -t "jpg"
+
diff --git a/python2/docs/usage_nomen-filecase.txt b/python2/docs/usage_nomen-filecase.txt
new file mode 100644
index 0000000..2af0fdc
--- /dev/null
+++ b/python2/docs/usage_nomen-filecase.txt
@@ -0,0 +1,186 @@
+
+USAGE (nomen-filecase.py)
+
+ Contents:
+
+ 1. Definition
+ 2. General stuff
+ 2.1 How to run Python scripts
+ 2.2 Overview of all command-line arguments
+ 2.3 Warning
+ 2.4 Simulation mode
+ 3. Convert the case of base names
+ 3.1 Rename mode
+ 3.2 Skip mode
+ 4. Using config files
+ 4.1 With a certain target case
+ 4.2 Without a certain target case
+
+ 1. Definition
+
+ The Nomen File Name Case Converter script converts the case of the
+ base name of all files inside a directory and (if requested) in all of
+ its sub-directories.
+
+ 2. General stuff
+
+ 2.1 How to run Python scripts
+
+ All usage examples below show how to execute the Python scripts on
+ the shell of a Unix-like system. If you do not know, how to run
+ those scripts on your operating system, you may have a look at
+ this page:
+
+ http://www.urbanware.org/howto_python.html
+
+ 2.2 Overview of all command-line arguments
+
+ Usually, each script requires command-line arguments to operate.
+ So, to get an overview of all arguments available, simply run the
+ script with the "--help" argument. For example:
+
+ $ ./nomen-filecase.py --help
+
+ 2.3 Warning
+
+ Please use this tool with care to avoid data damage or loss!
+
+ It is strongly recommended to simulate every rename process first
+ to check which files would have been renamed. As a matter of fact,
+ there is no function to undo the changes done by this tool, so you
+ should be aware of what you are doing. If not, do NOT use this
+ tool.
+
+ Improper use (e. g. modifying files inside system directories)
+ will corrupt your system!
+
+ 2.4 Simulation mode
+
+ As already mentioned above, before renaming any files, you should
+ simulate the rename process by using the "--simulate" argument.
+
+ This argument requires a file path where the report of the rename
+ process will be written to. When in simulation mode, no files will
+ be renamed at all.
+
+ So, if you have a command line like this
+
+ ./nomen-filecase.py -m rename -c title -d /tmp/pics/Holidays
+
+ but want to simulate the rename process and write the report into
+ the file "/tmp/report.txt", simply add the argument
+
+ --simulate /tmp/report.txt
+
+ to the command line:
+
+ ./nomen-filecase.py -m rename -c title -d /tmp/pics/Holidays \
+ --simulate /tmp/report.txt
+
+ 3. Convert the case of base names
+
+ For example, you have a picture file with this file name
+
+ my favorite holiday.jpg
+
+ and you convert the case of the base name to title case, the file name
+ will look like this:
+
+ My Favorite Holiday.jpg
+
+ But, for example, if file system is case senstive and the directory
+ contains the files
+
+ HOLIDAY.jpg
+ holiday.jpg
+
+ there would be a problem converting the case of the base name, because
+ both target file names would be identical. So, to avoid that, the
+ script also offers two operating modes.
+
+ 3.1 Rename mode
+
+ The "rename mode" adds a numeric ID to the base name of the
+ duplicate target file and converts the case:
+
+ HOLIDAY.jpg will be renamed to "Holiday.jpg"
+ holiday.jpg will be renamed to "Holiday_1.jpg"
+
+ To use this mode and convert the base name of the files to title
+ case, type:
+
+ ./nomen-filecase.py -m rename -c title -d /tmp/pics/Holidays
+
+ 3.2 Skip mode
+
+ The "skip mode" simply skips the files where converting the case
+ of the base name would result in duplicate file names:
+
+ HOLIDAY.jpg will be renamed to "Holiday.jpg"
+ holiday.jpg will be skipped
+
+ To use this mode and convert the base name of the files to title
+ case, type:
+
+ ./nomen-filecase.py -m skip -c title -d /tmp/pics/Holidays
+
+ 4. Using config files
+
+ The Nomen File Name Case Converter script also comes with the feature
+ that allows using config files to convert the case of certain strings
+ always to lowercase, title case, uppercase or a user-defined mixed
+ case, nomatter which target case (using the "-c" or "--case" argument)
+ was given.
+
+ For details see the included sample config files which can be found
+ inside the "cfg" sub-directory.
+
+ If the sample config file is missing for some reason, you may download
+ the project from the website:
+
+ http://www.urbanware.org/nomen.html
+
+ To use these config files, the corresponding command-line argument
+ followed by the full path to the config file is required, depending on
+ which of the config files you want to use.
+
+ 4.1 With a certain target case
+
+ For example, if you want to use title case as target case as well
+ as the lowercase config file named "lower.cfg" which is located
+ inside the directory "/tmp", the command line would look like
+ this:
+
+ ./nomen-filecase.py -m skip -c title -d /tmp/pics/Holidays \
+ --cfg-lower /tmp/lower.cfg
+
+ To additionally use the uppercase config file "upper.cfg" which
+ is also located inside the "/tmp" directory, simply add the
+ appropriate argument:
+
+ ./nomen-filecase.py -m skip -c title -d /tmp/pics/Holidays \
+ --cfg-lower /tmp/lower.cfg \
+ --cfg-upper /tmp/upper.cfg
+
+ Same with the mixed case config:
+
+ ./nomen-filecase.py -m skip -c title -d /tmp/pics/Holidays \
+ --cfg-lower /tmp/lower.cfg \
+ --cfg-mixed /tmp/mixed.cfg \
+ --cfg-upper /tmp/upper.cfg
+
+ However, you cannot use the title case config file when using
+ title case as target case anyway.
+
+ 4.2 Without a certain target case
+
+ If you just want to convert the case of the certain strings stored
+ inside the case config files, you can do this by giving "config"
+ as the target case:
+
+ ./nomen-filecase.py -m skip -c config -d /tmp/pics/Holidays \
+ --cfg-lower /tmp/lower.cfg \
+ --cfg-mixed /tmp/mixed.cfg \
+ --cfg-title /tmp/title.cfg \
+ --cfg-upper /tmp/upper.cfg
+
diff --git a/python2/docs/usage_nomen-filemod.txt b/python2/docs/usage_nomen-filemod.txt
new file mode 100644
index 0000000..5de39cd
--- /dev/null
+++ b/python2/docs/usage_nomen-filemod.txt
@@ -0,0 +1,376 @@
+
+USAGE (nomen-filemod.py)
+
+ Contents:
+
+ 1. Definition
+ 2. General stuff
+ 2.1 How to run Python scripts
+ 2.2 Overview of all command-line arguments
+ 2.3 Warning
+ 2.4 Simulation mode
+ 2.5 Prefixes and suffixes
+ 3. Add a user-defined string to file names
+ 3.1 Add a prefix
+ 3.2 Add a suffix
+ 4. Remove a user-defined string from file names
+ 4.1 Remove a prefix
+ 4.2 Remove a suffix
+ 4.3 Remove a string
+ 5. Replace a user-defined string inside file names
+ 5.1 Replace a prefix
+ 5.2 Replace a suffix
+ 5.3 Replace a string
+ 6. Strip file names
+ 7. Exclude certain files
+ 8. Rename certain files only
+
+ 1. Definition
+
+ The Nomen File Name Modifier script adds, removes or replaces a user-
+ defined string inside the base name of files.
+
+ 2. General stuff
+
+ 2.1 How to run Python scripts
+
+ All usage examples below show how to execute the Python scripts on
+ the shell of a Unix-like system. If you do not know, how to run
+ those scripts on your operating system, you may have a look at
+ this page:
+
+ http://www.urbanware.org/howto_python.html
+
+ 2.2 Overview of all command-line arguments
+
+ Usually, each script requires command-line arguments to operate.
+ So, to get an overview of all arguments available, simply run the
+ script with the "--help" argument. For example:
+
+ $ ./nomen-filemod.py --help
+
+ 2.3 Warning
+
+ Please use this tool with care to avoid data damage or loss!
+
+ It is strongly recommended to simulate every rename process first
+ to check which files would have been renamed. As a matter of fact,
+ there is no function to undo the changes done by this tool, so you
+ should be aware of what you are doing. If not, do NOT use this
+ tool.
+
+ Improper use (e. g. modifying files inside system directories)
+ will corrupt your system!
+
+ 2.4 Simulation mode
+
+ As already mentioned above, before renaming any files, you should
+ simulate the rename process by using the "--simulate" argument.
+
+ This argument requires a file path where the report of the rename
+ process will be written to. When in simulation mode, no files will
+ be renamed at all.
+
+ So, if you have a command line like this
+
+ ./nomen-filemod.py -d /tmp/pics -a add -p prefix -s "Foobar"
+
+ but want to simulate the rename process and write the report into
+ the file "/tmp/report.txt", simply add the argument
+
+ --simulate /tmp/report.txt
+
+ to the command line:
+
+ ./nomen-filemod.py -d /tmp/pics -a add -p prefix -s "Foobar" \
+ --simulate /tmp/report.txt
+
+ 2.5 Prefixes and suffixes
+
+ Typically a file name consists of a base name (prefix) and an
+ extension (suffix) separated by a separator, for example:
+
+ holiday.jpg
+
+ In this case, "holiday" is the base name, the dot is the separator
+ and "jpg" is the extension of the file.
+
+ The Nomen File Name Modifier only modifies the base name of file
+ names. The "--position" argument expects either "any", "prefix" or
+ "suffix". These positions are only related to the base name, not
+ to the entire file name.
+
+ For example, if you add the prefix "foo" to the base name it would
+ result in
+
+ fooholiday.jpg
+
+ and if you add the suffix "foo" (instead of the prefix) the base
+ name would look as follows:
+
+ holidayfoo.jpg
+
+ As already mentioned above, only the base name (the prefix of the
+ whole file name) will be modified and the file extension (the
+ suffix of the whole file name) will be ignored.
+
+ 3. Add a user-defined string to file names
+
+ Before modifying the base name of any files, please see section 2.5
+ above. Notice that the given input string is processed case-sensitive.
+
+ 3.1 Add a prefix
+
+ For example, if you have the files
+
+ holiday 1.jpg
+ holiday 2.jpg
+ holiday 3.jpg
+ holiday 4.jpg
+
+ inside the directory "/tmp/pics" and want to add the string
+ "Summer " (without any quotes) as prefix to the base name of these
+ files, type:
+
+ ./nomen-filemod.py -d /tmp/pics -a add -p prefix -s "Summer "
+
+ This will rename the base name of the files as follows:
+
+ Summer holiday 1.jpg
+ Summer holiday 2.jpg
+ Summer holiday 3.jpg
+ Summer holiday 4.jpg
+
+ 3.2 Add a suffix
+
+ For example, if you have the files
+
+ Holiday 1.jpg
+ Holiday 2.jpg
+ Holiday 3.jpg
+ Holiday 4.jpg
+
+ inside the directory "/tmp/pics" and want to add the string
+ " - Foobar" (without any quotes) as suffix to the base name of
+ these files, type:
+
+ ./nomen-filemod.py -d /tmp/pics -a add -p suffix \
+ -s " - Foobar"
+
+ This will rename the base name of the files as follows:
+
+ Holiday 1 - Foobar.jpg
+ Holiday 2 - Foobar.jpg
+ Holiday 3 - Foobar.jpg
+ Holiday 4 - Foobar.jpg
+
+ 4. Remove a user-defined string from file names
+
+ Before modifying the base name of any files, please see section 2.5
+ above. Notice that the given input string is processed case-sensitive.
+
+ 4.1 Remove a prefix
+
+ For example, if you have the files
+
+ Summer holiday 1.jpg
+ Summer holiday 2.jpg
+ Summer holiday 3.jpg
+ Summer holiday 4.jpg
+
+ inside the directory "/tmp/pics" and want to remove the prefix
+ "Summer " (without any quotes) from the base name of these files,
+ type:
+
+ ./nomen-filemod.py -d /tmp/pics -a remove -p prefix \
+ -s "Summer "
+
+ This will rename the base name of the files as follows:
+
+ holiday 1.jpg
+ holiday 2.jpg
+ holiday 3.jpg
+ holiday 4.jpg
+
+ 4.2 Remove a suffix
+
+ For example, if you have the files
+
+ Holiday 1 - Foobar.jpg
+ Holiday 2 - Foobar.jpg
+ Holiday 3 - Foobar.jpg
+ Holiday 4 - Foobar.jpg
+
+ inside the directory "/tmp/pics" and want to remove the suffix
+ " - Foobar" (without any quotes) from the base name of these
+ files, type:
+
+ ./nomen-filemod.py -d /tmp/pics -a remove -p suffix \
+ -s " - Foobar"
+
+ This will rename the base name of the files as follows:
+
+ Holiday 1.jpg
+ Holiday 2.jpg
+ Holiday 3.jpg
+ Holiday 4.jpg
+
+ 4.3 Remove a string
+
+ This will remove all occurrences of the input string (no matter if
+ prefix, suffix or somewhere else) in the base name.
+
+ For example, if you have the files
+
+ Foobar Holiday Foobar 1.jpg
+ Foobar Holiday Foobar 2.jpg
+ Foobar Holiday Foobar 3.jpg
+ Foobar Holiday Foobar 4.jpg
+
+ inside the directory "/tmp/pics" and want to remove the string
+ "Foobar " (without any quotes) from the base name of these files,
+ type:
+
+ ./nomen-filemod.py -d /tmp/pics -a remove -p any -s "Foobar "
+
+ This will rename the base name of the files as follows:
+
+ Holiday 1.jpg
+ Holiday 2.jpg
+ Holiday 3.jpg
+ Holiday 4.jpg
+
+ 5. Replace a user-defined string inside file names
+
+ Before modifying the base name of any files, please see section 2.5
+ above. Notice that the given string (input and replace string) are
+ processed case-sensitive.
+
+ 5.1 Replace a prefix
+
+ For example, if you have the files
+
+ Winter holiday 1.jpg
+ Winter holiday 2.jpg
+ Winter holiday 3.jpg
+ Winter holiday 4.jpg
+
+ inside the directory "/tmp/pics" and want to replace the prefix
+ "Winter " with "Summer " (both without any quotes) inside the base
+ name of these files, type:
+
+ ./nomen-filemod.py -d /tmp/pics -a replace -p prefix \
+ -s "Winter " --replace-string "Summer "
+
+ This will rename the base name of the files as follows:
+
+ Summer holiday 1.jpg
+ Summer holiday 2.jpg
+ Summer holiday 3.jpg
+ Summer holiday 4.jpg
+
+ 5.2 Replace a suffix
+
+ For example, if you have the files
+
+ Holiday 1 - Foobar.jpg
+ Holiday 2 - Foobar.jpg
+ Holiday 3 - Foobar.jpg
+ Holiday 4 - Foobar.jpg
+
+ inside the directory "/tmp/pics" and want to replace the suffix
+ " - Foobar" with " Foo" (both without any quotes) inside the base
+ name of these files, type:
+
+ ./nomen-filemod.py -d /tmp/pics -a replace -p suffix \
+ -s " - Foobar" --replace-string " Foo"
+
+ This will rename the base name of the files as follows:
+
+ Holiday 1 Foo.jpg
+ Holiday 2 Foo.jpg
+ Holiday 3 Foo.jpg
+ Holiday 4 Foo.jpg
+
+ 5.3 Replace a string
+
+ This will replace all occurrences of the input string (no matter
+ if prefix, suffix or somewhere else) in the base name.
+
+ For example, if you have the files
+
+ Fu - Fubar - Fu 1.jpg
+ Fu - Fubar - Fu 2.jpg
+ Fu - Fubar - Fu 3.jpg
+ Fu - Fubar - Fu 4.jpg
+
+ inside the directory "/tmp/pics" and want to replace the string
+ "Fu" with "Foo" (both without any quotes) inside the base name of
+ these files, type:
+
+ ./nomen-filemod.py -d /tmp/pics -a replace -p any -s "Fu" \
+ --replace-string "Foo"
+
+ This will rename the base name of the files as follows:
+
+ Foo - Foobar - Foo 1.jpg
+ Foo - Foobar - Foo 2.jpg
+ Foo - Foobar - Foo 3.jpg
+ Foo - Foobar - Foo 4.jpg
+
+ 6. Strip file names
+
+ You can also remove certain leading and trailing characters (such as
+ whitespaces) from the file name. For example, if you have the file
+
+ Foo Holiday 1 Foo.jpg
+
+ and want to remove the string "Foo" inside the entire file name, you
+ can do this using
+
+ ./nomen-filemod.py -d /tmp/pics -a remove -p any -s "Foo"
+
+ but this would result in a file name with a leading as well as a
+ trailing whitespace (" Holiday 1 .jpg").
+
+ Now, there are multiple ways to remove these whitespaces. The first
+ way would be explicitly removing the prefix and suffix containing the
+ whitespace.
+
+ ./nomen-filemod.py -d /tmp/pics -a remove -p prefix -s "Foo "
+ ./nomen-filemod.py -d /tmp/pics -a remove -p suffix -s " Foo"
+
+ Another way would be removing the string "Foo" first and after that
+ the leading and trailing whitespaces separately.
+
+ ./nomen-filemod.py -d /tmp/pics -a remove -p any -s "Foo"
+ ./nomen-filemod.py -d /tmp/pics -a remove -p prefix -s " "
+ ./nomen-filemod.py -d /tmp/pics -a remove -p suffix -s " "
+
+ The simplest way would be using the included command-line argument:
+
+ ./nomen-filemod.py -d /tmp/pics -a remove -p any -s "Foo" \
+ --strip " "
+
+ The "--strip" argument also allows giving multiple characters that
+ should be stripped. For example, to strip the letter "H" in uppercase
+ as well as whitespaces, type:
+
+ ./nomen-filemod.py -d /tmp/pics -a remove -p any -s "Foo" \
+ --strip "H "
+
+ However, this will result in "oliday 1.jpg", due to the fact, that the
+ list of strip characters will not be processed in a certain order.
+
+ 7. Exclude certain files
+
+ See section 4 of the documentation of the Nomen File Renamer script
+ how to do this. The command-line arguments to exclude files used there
+ can also be applied to the Nomen File Modifier script.
+
+ 8. Rename certain files only
+
+ See section 5 of the documentation of the Nomen File Renamer script
+ how to do this. The command-line arguments to rename files only used
+ there can also be applied to the Nomen File Modifier script.
+
diff --git a/python2/docs/usage_nomen-fileren.txt b/python2/docs/usage_nomen-fileren.txt
new file mode 100644
index 0000000..321188f
--- /dev/null
+++ b/python2/docs/usage_nomen-fileren.txt
@@ -0,0 +1,496 @@
+
+USAGE (nomen-fileren.py)
+
+ Contents:
+
+ 1. Definition
+ 2. General stuff
+ 2.1 How to run Python scripts
+ 2.2 Overview of all command-line arguments
+ 2.3 Warning
+ 2.4 Simulation mode
+ 2.5 Significant changes (released with version 2.1.0)
+ 2.5.1 Names of the rename modes
+ 2.5.2 Way of renaming files
+ 3. Rename files
+ 3.1 Using "keep-order" mode
+ 3.1.1 Order by name (default)
+ 3.1.2 Order by access time
+ 3.1.3 Order by creation time
+ 3.1.4 Order by modification time
+ 3.2 Using "rename-new" mode
+ 3.3 Using "fill-gaps" mode
+ 4. Exclude certain files
+ 4.1 Exclude certain files using basic strings
+ 4.2 Exclude certain files using wildcards
+ 5. Rename certain files only
+ 5.1 Rename certain files using basic strings
+ 5.2 Rename certain files using wildcards
+ 6. Steps between each numeric ID
+ 6.1 Issue before
+ 6.2 Solution
+
+ 1. Definition
+
+ The Nomen File Renamer script renames the base name of files within a
+ directory and (if requested) in all of its sub-directories based on
+ the name of the directory where the files are stored in and adds a
+ unique numeric ID.
+
+ 2. General stuff
+
+ 2.1 How to run Python scripts
+
+ All usage examples below show how to execute the Python scripts on
+ the shell of a Unix-like system. If you do not know, how to run
+ those scripts on your operating system, you may have a look at
+ this page:
+
+ http://www.urbanware.org/howto_python.html
+
+ 2.2 Overview of all command-line arguments
+
+ Usually, each script requires command-line arguments to operate.
+ So, to get an overview of all arguments available, simply run the
+ script with the "--help" argument. For example:
+
+ $ ./nomen-fileren.py --help
+
+ 2.3 Warning
+
+ Please use this tool with care to avoid data damage or loss!
+
+ It is strongly recommended to simulate every rename process first
+ to check which files would have been renamed. As a matter of fact,
+ there is no function to undo the changes done by this tool, so you
+ should be aware of what you are doing. If not, do NOT use this
+ tool.
+
+ Improper use (e. g. modifying files inside system directories)
+ will corrupt your system!
+
+ 2.4 Simulation mode
+
+ As already mentioned above, before renaming any files, you should
+ simulate the rename process by using the "--simulate" argument.
+
+ This argument requires a file path where the report of the rename
+ process will be written to. When in simulation mode, no files will
+ be renamed at all.
+
+ So, if you have a command line like this
+
+ ./nomen-fileren.py -m keep-order -d /tmp/pics/Holidays
+
+ but want to simulate the rename process and write the report into
+ the file "/tmp/report.txt", simply add the argument
+
+ --simulate /tmp/report.txt
+
+ to the command line:
+
+ ./nomen-fileren.py -m keep-order -d /tmp/pics/Holidays \
+ --simulate /tmp/report.txt
+
+ 2.5 Significant changes (released with version 2.1.0)
+
+ 2.5.1 Names of the rename modes
+
+ Before Nomen version 2.0.0, there only were two rename modes,
+ "consecutive" and "fill".
+
+ In version 2.0.0 the "fill" mode has been forked into the two
+ modes "fillin" and "fillnew".
+
+ With the release of version 2.1.0, all these names have been
+ replaced with new ones for more clarity.
+
+ +--------------------------+--------------------------+
+ | Before | After |
+ +--------------------------+--------------------------+
+ | consecutive | keep-order |
+ | fillin | fill-gaps |
+ | fillnew | rename-new |
+ +--------------------------+--------------------------+
+
+ 2.5.2 Way of renaming files
+
+ In the past, the files have been renamed with a continuous
+ number ignoring the file type.
+
+ With the release of version 2.1.0, by default the file types
+ will not longer be ignored, so every file type has its own
+ range of numbers:
+
+ +--------------------------+--------------------------+
+ | Before | After |
+ +--------------------------+--------------------------+
+ | Picture_0001.bmp | Picture_0001.bmp |
+ | Picture_0002.bmp | Picture_0002.bmp |
+ | Picture_0003.jpg | Picture_0001.jpg |
+ | Picture_0004.jpg | Picture_0002.jpg |
+ | Picture_0005.png | Picture_0001.png |
+ | Picture_0006.png | Picture_0002.png |
+ +--------------------------+--------------------------+
+
+ However, with the "--ignore-file-ext" command-line argument
+ this behaviour can be changed, so the files will be renamed
+ with a continuous number ignoring the file type again.
+
+ 3. Rename files
+
+ For example, if you have four picture files inside the directory
+ "/tmp/pics/Holidays":
+
+ pic_0001.jpg
+ pic_0002.jpg
+ pic_0003.jpg
+ pic_0004.jpg
+
+ Basically, the Nomen File Renamer script will rename the files inside
+ that directory based on the directory name where they are stored in
+ and add a unique numeric ID. For example:
+
+ Holidays 1.jpg
+ Holidays 2.jpg
+ Holidays 3.jpg
+ Holidays 4.jpg
+
+ The script offers some file rename modes.
+
+ 3.1 Using "keep-order" mode
+
+ The "keep-order" mode keeps the chronological order even if files
+ have been deleted. There are multiple ways this mode can be used.
+
+ 3.1.1 Order by name (default)
+
+ In case no order command-line argument was given, the default
+ behaviour will be used. For example, if you delete the picture
+ file "Holidays 2.jpg" and re-run the script with this rename
+ mode it will do the following:
+
+ Holidays 1.jpg stays (no need to rename)
+ Holidays 3.jpg will be renamed to "Holidays 2.jpg"
+ Holidays 4.jpg will be renamed to "Holidays 3.jpg"
+
+ Now, if you add another file to that directory, "foobar.jpg"
+ for example, and re-run the Nomen File Renamer script with
+ this rename mode, the new file will be appended to the
+ existing and already renamed files:
+
+ Holidays 1.jpg stays (no need to rename)
+ Holidays 2.jpg stays (no need to rename)
+ Holidays 3.jpg stays (no need to rename)
+ foobar.jpg will be renamed to "Holidays 4.jpg"
+
+ To use this rename mode, type:
+
+ ./nomen-fileren.py -m keep-order -d /tmp/pics/Holidays
+
+ In case you want to reorganize these pictures, put them into a
+ new directory called "Summer" among with some other files and
+ re-run the script with this rename mode, the files will not be
+ renamed chronologically, but alphabetically. For example:
+
+ Beach 1.jpg will be renamed to "Summer 1.jpg"
+ Beach 2.jpg will be renamed to "Summer 2.jpg"
+ Holidays 1.jpg will be renamed to "Summer 3.jpg"
+ Holidays 2.jpg will be renamed to "Summer 4.jpg"
+ Holidays 3.jpg will be renamed to "Summer 5.jpg"
+ Holidays 4.jpg will be renamed to "Summer 6.jpg"
+
+ So, even if the "Beach" pictures are newer than the "Holidays"
+ pictures the files will be renamed like this.
+
+ You can also keep a certain order, for example, beginning with
+ the "Holidays" pictures followed by the "Beach" pictures. At
+ first, put only the "Holidays" pictures into the "Summer"
+ directory and run the script with this rename mode:
+
+ Holidays 1.jpg will be renamed to "Summer 1.jpg"
+ Holidays 2.jpg will be renamed to "Summer 2.jpg"
+ Holidays 3.jpg will be renamed to "Summer 3.jpg"
+ Holidays 4.jpg will be renamed to "Summer 4.jpg"
+
+ After that, put the "Beach" pictures in the "Summer" directory
+ and run the script with this mode again:
+
+ Summer 1.jpg stays (no need to rename)
+ Summer 2.jpg stays (no need to rename)
+ Summer 3.jpg stays (no need to rename)
+ Summer 4.jpg stays (no need to rename)
+ Beach 1.jpg will be renamed to "Summer 5.jpg"
+ Beach 2.jpg will be renamed to "Summer 6.jpg"
+
+ 3.1.2 Order by access time
+
+ Please read section 3.1.1 above first before continuing with
+ this one.
+
+ You can also rename and order the files by the time they have
+ been accessed in ascending order. So, the file with the oldest
+ time will be the first one to be renamed.
+
+ However, the access time is not being determined by Nomen, it
+ is the timestamp provided by the metadata of the file.
+
+ Notice that this will irreversibly change the order of the
+ files.
+
+ To use this rename mode, type:
+
+ ./nomen-fileren.py -m keep-order -d /tmp/pics/Holidays \
+ -o accessed
+
+ 3.1.3 Order by creation time
+
+ Please read section 3.1.1 above first before continuing with
+ this one.
+
+ You can also rename and order the files by the time they have
+ been created in ascending order. So, the file with the oldest
+ time will be the first one to be renamed.
+
+ However, the creation time is not being determined by Nomen,
+ it is the timestamp provided by the metadata of the file.
+
+ Notice that this will irreversibly change the order of the
+ files.
+
+ According to the official Python documentation (see "Common
+ pathname manipulations"), the creation time information
+ depends on the operation system.
+
+ On some systems (like Unix) it is the time of the last change
+ of the metadata, and, on others (like Windows), is the
+ creation time of the file.
+
+ To use this rename mode, type:
+
+ ./nomen-fileren.py -m keep-order -d /tmp/pics/Holidays \
+ -o created
+
+ 3.1.4 Order by modification time
+
+ Please read section 3.1.1 above first before continuing with
+ this one.
+
+ You can also rename and order the files by the time they have
+ been modified in ascending order. So, the file with the oldest
+ time will be the first one to be renamed.
+
+ However, the modification time is not being determined by
+ Nomen, it is the timestamp provided by the metadata of the
+ file.
+
+ Notice that this will irreversibly change the order of the
+ files.
+
+ To use this rename mode, type:
+
+ ./nomen-fileren.py -m keep-order -d /tmp/pics/Holidays \
+ -o modified
+
+ 3.2 Using "rename-new" mode
+
+ The "rename-new" mode does NOT keep the chronological order if
+ files have been deleted. It simply fills the numeration gaps with
+ new files or files that have been renamed manually.
+
+ For example, if you delete the picture file "Holidays 2.jpg" and
+ re-run the Nomen File Renamer script with this rename mode, no
+ file names will be changed.
+
+ Holidays 1.jpg stays (no need to rename)
+ Holidays 3.jpg stays (no need to rename)
+ Holidays 4.jpg stays (no need to rename)
+
+ If you then add another file to that directory, e. g. "foobar.jpg"
+ and re-run the script this rename mode again, it will simply fill
+ the gap "Holiday 2.jpg" has left using the new file:
+
+ Holidays 1.jpg stays (no need to rename)
+ Holidays 3.jpg stays (no need to rename)
+ Holidays 4.jpg stays (no need to rename)
+ foobar.jpg will be renamed to "Holidays 2.jpg"
+
+ To use this rename mode, type:
+
+ ./nomen-fileren.py -m rename-new -d /tmp/pics/Holidays
+
+ 3.3 Using "fill-gaps" mode
+
+ The "fill-gaps" mode works like the "rename-new" mode (for details
+ see section 3.2) with the difference, that it fills all numeration
+ gaps using new as well as the existing files with the greatest
+ numbers. For example:
+
+ Holidays 1.jpg stays (no need to rename)
+ Holidays 4.jpg stays (no need to rename)
+ Holidays 5.jpg will be renamed to "Holidays 3.jpg"
+ foobar.jpg will be renamed to "Holidays 2.jpg"
+
+ To use this rename mode, type:
+
+ ./nomen-fileren.py -m fill-gaps -d /tmp/pics/Holidays
+
+ 4. Exclude certain files
+
+ 4.1 Exclude certain files using basic strings
+
+ For example, to exclude all files whose file name (base name or
+ extension) contains the string "foo", type:
+
+ $ ./nomen-fileren.py -m fill-gaps -d /tmp/pics/Holidays \
+ --exclude "foo"
+
+ By default, the given pattern is being processed case-insensitive.
+ That means, the string given above would match "foo" as well as
+ e. g. "FOO", "Foo", "FoO" and so on.
+
+ The pattern can also consist of multiple strings (separated with
+ semicolons):
+
+ $ ./nomen-fileren.py -m fill-gaps -d /tmp/pics/Holidays \
+ --exclude "foo2;foo4"
+
+ 4.2 Exclude certain files using wildcards
+
+ The pattern also supports asterisk wildcards. So, if you want to
+ exclude all files whose file name (base name or extension)
+ contains a string that starts with "foo" and ends with "bar"
+ (e. g. "foobar", "foo-bar" or "foo bar"), type:
+
+ $ ./nomen-fileren.py -m fill-gaps -d /tmp/pics/Holidays \
+ --exclude "foo*bar"
+
+ The pattern can also consist of multiple strings (separated with
+ semicolons) with wildcards:
+
+ $ ./nomen-fileren.py -m fill-gaps -d /tmp/pics/Holidays \
+ --exclude "f*o;b*r"
+
+ 5. Rename certain files only
+
+ 5.1 Rename certain files using basic strings
+
+ So, for example, to only rename the files whose file name (base
+ name or extension) contains the string "foo", type:
+
+ $ ./nomen-fileren.py -m fill-gaps -d /tmp/pics/Holidays \
+ --explicit "foo"
+
+ By default, the given pattern is being processed case-insensitive.
+ That means, the string given above would match "foo" as well as
+ e. g. "FOO", "Foo", "FoO" and so on.
+
+ The pattern can also consist of multiple strings (separated with
+ semicolons):
+
+ $ ./nomen-fileren.py -m fill-gaps -d /tmp/pics/Holidays \
+ --explicit "foo2;foo4"
+
+ 5.2 Rename certain files using wildcards
+
+ The pattern also supports asterisk wildcards. So, if you want to
+ only rename the files whose file name (base name or extension)
+ contains a string that starts with "foo" and ends with "bar"
+ (e. g. "foobar", "foo-bar" or "foo bar"), type:
+
+ $ ./nomen-fileren.py -m fill-gaps -d /tmp/pics/Holidays \
+ --explicit "foo*bar"
+
+ The pattern can also consist of multiple strings (separated with
+ semicolons) with wildcards:
+
+ $ ./nomen-fileren.py -m fill-gaps -d /tmp/pics/Holidays \
+ --explicit "f*o;b*r"
+
+ 6. Steps between each numeric ID
+
+ Since version 2.3.0, it is also possible to set an additional user-
+ defined step value, which can be combined with all rename modes
+ mentioned in section 3 above.
+
+ Especially when using the the "keep-order" mode, adding new files to
+ existing ones between two numeric IDs was quite inconvenient. Below
+ you can find an example issue and its solution.
+
+ 6.1 Issue before
+
+ There is a directory containing already renamed files and a new
+ one that should be between others like this
+
+ Holidays 1.jpg
+ Holidays 2.jpg
+ Holidays 3.jpg
+ pic_0001.jpg should actually be "Holidays 2.jpg"
+
+ and running
+
+ ./nomen-fileren.py -m keep-order -d /tmp/pics/Holidays
+
+ would rename "pic_0001.jpg" to "Holidays 4.jpg", which is not
+ what we want.
+
+ One way to solve that is:
+
+ 1. Create a new temporary sub-directory with a different name,
+ for example "Temp".
+
+ 2. Move all files whose names are starting with "Holdays" into
+ that directory.
+
+ 3. Then, run the Nomen File Renamer script again. The file
+ "pic_0001.jpg" file will then renamed to "Holidays 2.jpg".
+
+ 4. In case that the target directory has not been processed
+ recursively before, run the script once again with the path
+ of the sub-directory as target directory, so the term
+ "Holidays" inside the file name will be replaced by "Temp".
+
+ 5. Move all files from the temporary directory back into the
+ directory where the "Holidays" files are.
+
+ 6. Delete the temporary directory.
+
+ 7. Now, run the Nomen File Renamer script a third time with
+ the path of the directory that contains the "Holidays"
+ files as the target directory again.
+
+ 6.2 Solution
+
+ Now, before adding new files that should be between others, run
+ the Nomen File Renamer script (before adding new files) with a
+ user-defined step value, for example 10.
+
+ ./nomen-fileren.py -m keep-order -d /tmp/pics/Holidays \
+ --step 10
+
+ This will rename the files as follows:
+
+ Holidays 10.jpg
+ Holidays 20.jpg
+ Holidays 30.jpg
+
+ Then add the "pic_0001.jpg" file to that directiry and rename it
+ like "Holidays 11.jpg" (or with any number between 10 and 20).
+
+ Holidays 10.jpg stays (no need to rename)
+ Holidays 11.jpg will be renamed to "Holidays 20.jpg"
+ Holidays 20.jpg will be renamed to "Holidays 30.jpg"
+ Holidays 30.jpg will be renamed to "Holidays 3.jpg"
+
+ Now, run the Nomen File Renamer script once again
+
+ ./nomen-fileren.py -m keep-order -d /tmp/pics/Holidays \
+ --step 10
+
+ to apply the changes:
+
+ Holidays 10.jpg stayed (no need to rename)
+ Holidays 20.jpg was "Holidays 11.jpg" before
+ Holidays 30.jpg was "Holidays 20.jpg" before
+ Holidays 40.jpg was "Holidays 30.jpg" before
+
diff --git a/python2/license.txt b/python2/license.txt
new file mode 100644
index 0000000..5c861af
--- /dev/null
+++ b/python2/license.txt
@@ -0,0 +1,27 @@
+
+LICENSE (Nomen)
+
+ Nomen - Multi-purpose rename tool
+ Copyright (C) 2018 by Ralf Kilian
+
+ Distributed under the MIT License:
+
+ Permission is hereby granted, free of charge, to any person obtaining
+ a copy of this software and associated documentation files (the
+ "Software"), to deal in the Software without restriction, including
+ without limitation the rights to use, copy, modify, merge, publish,
+ distribute, sublicense, and/or sell copies of the Software, and to
+ permit persons to whom the Software is furnished to do so, subject to
+ the following conditions:
+
+ The above copyright notice and this permission notice shall be
+ included in all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
diff --git a/python2/nomen-dirspace.py b/python2/nomen-dirspace.py
new file mode 100755
index 0000000..c8f6fac
--- /dev/null
+++ b/python2/nomen-dirspace.py
@@ -0,0 +1,90 @@
+#!/usr/bin/env python2
+# -*- coding: utf-8 -*-
+
+# ============================================================================
+# Nomen - Multi-purpose rename tool
+# Directory name space modifier script
+# Copyright (C) 2018 by Ralf Kilian
+# Distributed under the MIT License (https://opensource.org/licenses/MIT)
+#
+# Website: http://www.urbanware.org
+# GitHub: https://github.com/urbanware-org/nomen
+# ============================================================================
+
+import os
+import sys
+
+def main():
+ from core import clap
+ from core import common
+ from core import fileren
+
+ try:
+ p = clap.Parser()
+ except Exception as e:
+ print "%s: error: %s" % (os.path.basename(sys.argv[0]), e)
+ sys.exit(1)
+
+ p.set_description("Modify a directory name by removing leading, " \
+ "trailing and duplicate spaces or by inserting and " \
+ "removing spaces next to punctuation characters.")
+ p.set_epilog("Further information and usage examples can be found " \
+ "inside the documentation file for this script.")
+
+ # Required arguments
+ p.add_avalue("-d", "--directory", "directory that contains the files " \
+ "to process", "directory", None, True)
+
+ # Optional arguments
+ p.add_switch("-b", "--brackets", "insert and remove spaces next to " \
+ "brackets", "brackets", True, False)
+ p.add_avalue(None, "--exclude", "pattern to exclude certain files " \
+ "(case-insensitive, multiple patterns separated via " \
+ "semicolon)", "exclude_pattern", None, False)
+ p.add_switch("-h", "--help", "print this help message and exit", None,
+ True, False)
+ p.add_switch(None, "--hyphens", "insert spaces next to hyphens", \
+ "hyphens", True, False)
+ p.add_switch(None, "--ignore-symlinks", "ignore symbolic links",
+ "ignore_symlinks", True, False)
+ p.add_switch("-l", "--remove-leading", "remove leading spaces",
+ "remove_leading", True, False)
+ p.add_switch("-r", "--recursive", "process the given directory " \
+ "recursively", "recursive", True, False)
+ p.add_switch("-p", "--punctuation", "insert and remove spaces next to " \
+ "punctuation characters", "punctuation", True, False)
+ p.add_switch("-s", "--remove-duplicate", "remove duplicate spaces",
+ "remove_duplicate", True, False)
+ p.add_switch("-t", "--remove-trailing", "remove trailing spaces",
+ "remove_trailing", True, False)
+ p.add_switch(None, "--version", "print the version number and exit", None,
+ True, False)
+
+ if len(sys.argv) == 1:
+ p.error("At least one required argument is missing.")
+ elif ("-h" in sys.argv) or ("--help" in sys.argv):
+ p.print_help()
+ sys.exit(0)
+ elif "--version" in sys.argv:
+ print fileren.get_version()
+ sys.exit(0)
+
+ try:
+ args = p.parse_args()
+ if not args.punctuation and not args.remove_duplicate and \
+ not args.remove_leading and not args.remove_trailing:
+ p.error("Nothing to do (no optional arguments were given).")
+
+ common.dir_space_modifier(args.directory, args.remove_duplicate,
+ args.remove_leading, args.remove_trailing,
+ args.brackets, args.hyphens,
+ args.punctuation, args.ignore_symlinks,
+ args.recursive, args.exclude_pattern)
+ except Exception as e:
+ p.error(e)
+
+if __name__ == "__main__":
+ main()
+
+# EOF
+
diff --git a/python2/nomen-extcase.py b/python2/nomen-extcase.py
new file mode 100755
index 0000000..2631613
--- /dev/null
+++ b/python2/nomen-extcase.py
@@ -0,0 +1,87 @@
+#!/usr/bin/env python2
+# -*- coding: utf-8 -*-
+
+# ============================================================================
+# Nomen - Multi-purpose rename tool
+# Extension case converter script
+# Copyright (C) 2018 by Ralf Kilian
+# Distributed under the MIT License (https://opensource.org/licenses/MIT)
+#
+# Website: http://www.urbanware.org
+# GitHub: https://github.com/urbanware-org/nomen
+# ============================================================================
+
+import os
+import sys
+
+def main():
+ from core import clap
+ from core import common
+ from core import extren
+
+ try:
+ p = clap.Parser()
+ except Exception as e:
+ print "%s: error: %s" % (os.path.basename(sys.argv[0]), e)
+ sys.exit(1)
+
+ p.set_description("Convert the case of the extension of all files " \
+ "inside a directory and (if requested) in all of its " \
+ "sub-directories.")
+ p.set_epilog("Further information and usage examples can be found " \
+ "inside the documentation file for this script.")
+
+ # Required arguments
+ p.add_predef("-c", "--case", "target extension case", "case",
+ ["lower", "title", "upper"], True)
+ p.add_avalue("-d", "--directory", "directory that contains the files " \
+ "to process", "directory", None, True)
+ p.add_predef("-m", "--conflict-mode", "conflict mode (in case of " \
+ "duplicate file names)", "conflict_mode", ["rename", "skip"],
+ True)
+
+ # Optional arguments
+ p.add_switch(None, "--confirm", "skip the confirmation prompt and " \
+ "instantly rename files", "confirm", True, False)
+ p.add_switch("-h", "--help", "print this help message and exit", None,
+ True, False)
+ p.add_switch(None, "--ignore-symlinks", "ignore symbolic links",
+ "ignore_symlinks", True, False)
+ p.add_switch("-r", "--recursive", "process the given directory " \
+ "recursively", "recursive", True, False)
+ p.add_avalue(None, "--simulate", "simulate the rename process and " \
+ "write the details into a report file", "report_file", None,
+ False)
+ p.add_switch(None, "--version", "print the version number and exit", None,
+ True, False)
+
+ if len(sys.argv) == 1:
+ p.error("At least one required argument is missing.")
+ elif ("-h" in sys.argv) or ("--help" in sys.argv):
+ p.print_help()
+ sys.exit(0)
+ elif "--version" in sys.argv:
+ print extren.get_version()
+ sys.exit(0)
+
+ args = p.parse_args()
+ if args.confirm and not args.report_file == None:
+ p.error("The confirm and the simulate argument cannot be given at " \
+ "the same time.")
+
+ try:
+ if not args.confirm and args.report_file == None:
+ if not common.confirm_notice():
+ sys.exit(0)
+
+ extren.convert_case(args.directory, args.case, args.conflict_mode,
+ args.recursive, args.report_file,
+ args.ignore_symlinks)
+ except Exception as e:
+ p.error(e)
+
+if __name__ == "__main__":
+ main()
+
+# EOF
+
diff --git a/python2/nomen-extren.py b/python2/nomen-extren.py
new file mode 100755
index 0000000..cc52243
--- /dev/null
+++ b/python2/nomen-extren.py
@@ -0,0 +1,94 @@
+#!/usr/bin/env python2
+# -*- coding: utf-8 -*-
+
+# ============================================================================
+# Nomen - Multi-purpose rename tool
+# Extension renamer script
+# Copyright (C) 2018 by Ralf Kilian
+# Distributed under the MIT License (https://opensource.org/licenses/MIT)
+#
+# Website: http://www.urbanware.org
+# GitHub: https://github.com/urbanware-org/nomen
+# ============================================================================
+
+import os
+import sys
+
+def main():
+ from core import clap
+ from core import common
+ from core import extren
+
+ try:
+ p = clap.Parser()
+ except Exception as e:
+ print "%s: error: %s" % (os.path.basename(sys.argv[0]), e)
+ sys.exit(1)
+
+ p.set_description("Rename (and adjust) differently spelled file " \
+ "extensions of the same file type file within a " \
+ "directory and (if requested) in all of its sub-" \
+ "directories.")
+ p.set_epilog("Further information and usage examples can be found " \
+ "inside the documentation file for this script.")
+
+ # Required arguments
+ p.add_avalue("-d", "--directory", "directory that contains the files " \
+ "to process", "directory", None, True)
+ p.add_avalue("-e", "--extension", "extension to rename (case-" \
+ "sensitive, multiple extensions separated via semicolon)",
+ "extension", None, True)
+ p.add_predef("-m", "--conflict-mode", "conflict mode (in case of " \
+ "duplicate file names)", "conflict_mode", ["rename", "skip"],
+ True)
+ p.add_avalue("-t", "--target-extension", "target extension (case-" \
+ "sensitive)", "extension_target", None, True)
+
+ # Optional arguments
+ p.add_switch("-c", "--case-sensitive", "do not ignore the case of the " \
+ "given extension list", "case", False, False)
+ p.add_switch(None, "--confirm", "skip the confirmation prompt and " \
+ "instantly rename files", "confirm", True, False)
+ p.add_switch("-h", "--help", "print this help message and exit", None,
+ True, False)
+ p.add_switch(None, "--ignore-symlinks", "ignore symbolic links",
+ "ignore_symlinks", True, False)
+ p.add_switch("-r", "--recursive", "process the given directory " \
+ "recursively", "recursive", True, False)
+ p.add_avalue(None, "--simulate", "simulate the rename process and " \
+ "write the details into a report file", "report_file", None,
+ False)
+ p.add_switch(None, "--version", "print the version number and exit", None,
+ True, False)
+
+ if len(sys.argv) == 1:
+ p.error("At least one required argument is missing.")
+ elif ("-h" in sys.argv) or ("--help" in sys.argv):
+ p.print_help()
+ sys.exit(0)
+ elif "--version" in sys.argv:
+ print extren.get_version()
+ sys.exit(0)
+
+ args = p.parse_args()
+ if args.confirm and not args.report_file == None:
+ p.error("The confirm and the simulate argument cannot be given at " \
+ "the same time.")
+
+ try:
+ if not args.confirm and args.report_file == None:
+ if not common.confirm_notice():
+ sys.exit(0)
+
+ extren.rename_extensions(args.directory, args.conflict_mode,
+ args.extension, args.extension_target,
+ args.recursive, args.case, args.report_file,
+ args.ignore_symlinks)
+ except Exception as e:
+ p.error(e)
+
+if __name__ == "__main__":
+ main()
+
+# EOF
+
diff --git a/python2/nomen-filecase.py b/python2/nomen-filecase.py
new file mode 100755
index 0000000..3ff0c72
--- /dev/null
+++ b/python2/nomen-filecase.py
@@ -0,0 +1,100 @@
+#!/usr/bin/env python2
+# -*- coding: utf-8 -*-
+
+# ============================================================================
+# Nomen - Multi-purpose rename tool
+# File name case converter script
+# Copyright (C) 2018 by Ralf Kilian
+# Distributed under the MIT License (https://opensource.org/licenses/MIT)
+#
+# Website: http://www.urbanware.org
+# GitHub: https://github.com/urbanware-org/nomen
+# ============================================================================
+
+import os
+import sys
+
+def main():
+ from core import clap
+ from core import common
+ from core import fileren
+
+ try:
+ p = clap.Parser()
+ except Exception as e:
+ print "%s: error: %s" % (os.path.basename(sys.argv[0]), e)
+ sys.exit(1)
+
+ p.set_description("Convert the case of the base name of all files " \
+ "inside a directory and (if requested) in all of its " \
+ "sub-directories.")
+ p.set_epilog("Further information and usage examples can be found " \
+ "inside the documentation file for this script.")
+
+ # Required arguments
+ p.add_predef("-c", "--case", "target case of the base name", "case",
+ ["lower", "title", "upper", "config"], True)
+ p.add_avalue("-d", "--directory", "directory that contains the files " \
+ "to process", "directory", None, True)
+ p.add_predef("-m", "--conflict-mode", "conflict mode (in case of " \
+ "duplicate file names)", "conflict_mode", ["rename", "skip"],
+ True)
+
+ # Optional arguments
+ p.add_avalue(None, "--cfg-lower", "path to the config file for strings " \
+ "which should always be lowercase inside the file " \
+ "name", "cfg_lower", None, False)
+ p.add_avalue(None, "--cfg-mixed", "path to the config file for strings " \
+ "which should always be mixed case inside the " \
+ "file name", "cfg_mixed", None, False)
+ p.add_avalue(None, "--cfg-title", "path to the config file for strings " \
+ "which should always be title case inside the " \
+ "file name", "cfg_title", None, False)
+ p.add_avalue(None, "--cfg-upper", "path to the config file for strings " \
+ "which should always be uppercase inside the file " \
+ "name", "cfg_upper", None, False)
+ p.add_switch(None, "--confirm", "skip the confirmation prompt and " \
+ "instantly rename files", "confirm", True, False)
+ p.add_switch("-h", "--help", "print this help message and exit", None,
+ True, False)
+ p.add_switch(None, "--ignore-symlinks", "ignore symbolic links",
+ "ignore_symlinks", True, False)
+ p.add_switch("-r", "--recursive", "process the given directory " \
+ "recursively", "recursive", True, False)
+ p.add_avalue(None, "--simulate", "simulate the rename process and " \
+ "write the details into a report file", "report_file", None,
+ False)
+ p.add_switch(None, "--version", "print the version number and exit", None,
+ True, False)
+
+ if len(sys.argv) == 1:
+ p.error("At least one required argument is missing.")
+ elif ("-h" in sys.argv) or ("--help" in sys.argv):
+ p.print_help()
+ sys.exit(0)
+ elif "--version" in sys.argv:
+ print fileren.get_version()
+ sys.exit(0)
+
+ args = p.parse_args()
+ if args.confirm and not args.report_file == None:
+ p.error("The confirm and the simulate argument cannot be given at " \
+ "the same time.")
+
+ try:
+ if not args.confirm and args.report_file == None:
+ if not common.confirm_notice():
+ sys.exit(0)
+
+ fileren.convert_case(args.directory, args.case, args.conflict_mode,
+ args.recursive, args.cfg_lower, args.cfg_mixed,
+ args.cfg_title, args.cfg_upper, args.report_file,
+ args.ignore_symlinks)
+ except Exception as e:
+ p.error(e)
+
+if __name__ == "__main__":
+ main()
+
+# EOF
+
diff --git a/python2/nomen-filemod.py b/python2/nomen-filemod.py
new file mode 100755
index 0000000..e69a824
--- /dev/null
+++ b/python2/nomen-filemod.py
@@ -0,0 +1,120 @@
+#!/usr/bin/env python2
+# -*- coding: utf-8 -*-
+
+# ============================================================================
+# Nomen - Multi-purpose rename tool
+# File name modifier script
+# Copyright (C) 2018 by Ralf Kilian
+# Distributed under the MIT License (https://opensource.org/licenses/MIT)
+#
+# Website: http://www.urbanware.org
+# GitHub: https://github.com/urbanware-org/nomen
+# ============================================================================
+
+import os
+import sys
+
+def main():
+ from core import clap
+ from core import common
+ from core import fileren
+
+ try:
+ p = clap.Parser()
+ except Exception as e:
+ print "%s: error: %s" % (os.path.basename(sys.argv[0]), e)
+ sys.exit(1)
+
+ p.set_description("Modify the base name of files by adding, removing " \
+ "or replacing a user-defined string.")
+ p.set_epilog("Further information and usage examples can be found " \
+ "inside the documentation file for this script.")
+
+ # Required arguments
+ p.add_predef("-a", "--action", "action to perform", "action",
+ ["add", "remove", "replace"], True)
+ p.add_avalue("-d", "--directory", "directory that contains the files " \
+ "to process", "directory", None, True)
+ p.add_predef("-p", "--position", "position where to perform the action",
+ "position", ["any", "prefix", "suffix"], True)
+ p.add_avalue("-s", "--string", "input string to perform the action " \
+ "with (case-sensitive)", "input_string", None, True)
+
+ # Optional arguments
+ p.add_switch("-c", "--case-sensitive", "do not ignore the case of the " \
+ "given exclude or explicit pattern", "case", False, False)
+ p.add_switch(None, "--confirm", "skip the confirmation prompt and " \
+ "instantly rename files", "confirm", True, False)
+ p.add_avalue(None, "--exclude", "pattern to exclude certain files " \
+ "(case-insensitive, multiple patterns separated via " \
+ "semicolon)", "exclude_pattern", None, False)
+ p.add_avalue(None, "--explicit", "explicit pattern to only process " \
+ "certain files (case-insensitive, multiple patterns " \
+ "separated via semicolon)", "explicit_pattern", None, False)
+ p.add_switch("-h", "--help", "print this help message and exit", None,
+ True, False)
+ p.add_switch(None, "--ignore-symlinks", "ignore symbolic links",
+ "ignore_symlinks", True, False)
+ p.add_switch("-r", "--recursive", "process the given directory " \
+ "recursively", "recursive", True, False)
+ p.add_switch(None, "--regex", "use regex syntax for the exclude or " \
+ "explicit pattern instead of just asterisk wildcards and " \
+ "semicolon separators (for details see the section " \
+ "'Regular expression operations' inside the official " \
+ "Python documentation)", "regex_syntax", True, False)
+ p.add_avalue(None, "--replace-string", "string to replace the input" \
+ "string with (when using the action 'replace')",
+ "replace_string", None, False)
+ p.add_avalue(None, "--simulate", "simulate the rename process and " \
+ "write the details into a report file", "report_file", None,
+ False)
+ p.add_avalue(None, "--strip", "remove certain leading and trailing " \
+ "characters from the base name", "strip_chars", None, False)
+ p.add_switch(None, "--version", "print the version number and exit", None,
+ True, False)
+
+ if len(sys.argv) == 1:
+ p.error("At least one required argument is missing.")
+ elif ("-h" in sys.argv) or ("--help" in sys.argv):
+ p.print_help()
+ sys.exit(0)
+ elif "--version" in sys.argv:
+ print fileren.get_version()
+ sys.exit(0)
+
+ args = p.parse_args()
+ if args.confirm and not args.report_file == None:
+ p.error("The confirm and the simulate argument cannot be given at " \
+ "the same time.")
+ if args.exclude_pattern and args.explicit_pattern:
+ p.error("The exclude and the explicit pattern argument cannot be " \
+ "given at the same time.")
+
+ try:
+ if not args.confirm and args.report_file == None:
+ if not common.confirm_notice():
+ sys.exit(0)
+
+ if args.exclude_pattern:
+ pattern = args.exclude_pattern
+ exclude = True
+ elif args.explicit_pattern:
+ exclude = False
+ pattern = args.explicit_pattern
+ else:
+ exclude = None
+ pattern = None
+
+ fileren.modify_names(args.directory, args.action, args.position,
+ args.input_string, args.replace_string,
+ args.recursive, exclude, pattern, args.case,
+ args.regex_syntax, args.report_file,
+ args.ignore_symlinks, args.strip_chars)
+ except Exception as e:
+ p.error(e)
+
+if __name__ == "__main__":
+ main()
+
+# EOF
+
diff --git a/python2/nomen-fileren.py b/python2/nomen-fileren.py
new file mode 100755
index 0000000..e2014d3
--- /dev/null
+++ b/python2/nomen-fileren.py
@@ -0,0 +1,130 @@
+#!/usr/bin/env python2
+# -*- coding: utf-8 -*-
+
+# ============================================================================
+# Nomen - Multi-purpose rename tool
+# File renamer script
+# Copyright (C) 2018 by Ralf Kilian
+# Distributed under the MIT License (https://opensource.org/licenses/MIT)
+#
+# Website: http://www.urbanware.org
+# GitHub: https://github.com/urbanware-org/nomen
+# ============================================================================
+
+import os
+import sys
+
+def main():
+ from core import clap
+ from core import common
+ from core import fileren
+
+ try:
+ p = clap.Parser()
+ except Exception as e:
+ print "%s: error: %s" % (os.path.basename(sys.argv[0]), e)
+ sys.exit(1)
+
+ p.set_description("Rename the base name of files within a directory " \
+ "and (if requested) in all of its sub-directories " \
+ "based on the name of the directory where the files " \
+ "are stored in and add a unique numeric ID.")
+ p.set_epilog("Further information and usage examples can be found " \
+ "inside the documentation file for this script.")
+
+ # Required arguments
+ p.add_avalue("-d", "--directory", "directory that contains the files " \
+ "to process", "directory", None, True)
+ p.add_predef("-m", "--rename-mode", "rename mode to use", "rename_mode",
+ ["fill-gaps", "increase", "keep-order", "rename-new"], True)
+
+ # Optional arguments
+ p.add_switch("-c", "--case-sensitive", "do not ignore the case of the " \
+ "given exclude or explicit pattern", "case", False, False)
+ p.add_switch(None, "--confirm", "skip the confirmation prompt and " \
+ "instantly rename files", "confirm", True, False)
+ p.add_avalue(None, "--custom-name", "custom file name (instead of the " \
+ "directory name where the files are stored in)",
+ "custom_name", None, False)
+ p.add_avalue(None, "--exclude", "pattern to exclude certain files " \
+ "(case-insensitive, multiple patterns separated via " \
+ "semicolon)", "exclude_pattern", None, False)
+ p.add_avalue(None, "--explicit", "explicit pattern to only process " \
+ "certain files (case-insensitive, multiple patterns " \
+ "separated via semicolon)", "explicit_pattern", None, False)
+ p.add_switch("-h", "--help", "print this help message and exit", None,
+ True, False)
+ p.add_switch(None, "--ignore-file-ext", "ignore file extensions when " \
+ "numerating files", "ignore_file_ext", True, False)
+ p.add_switch(None, "--ignore-symlinks", "ignore symbolic links",
+ "ignore_symlinks", True, False)
+ p.add_predef("-o", "--order-by", "order files by last accessed, " \
+ "created or modified date", "order_by", ["accessed",
+ "created", "modified"], False)
+ p.add_avalue("-p", "--padding", "set a user-defined numeric padding " \
+ "(if no user-defined padding value is given, it will be " \
+ "set automatically based on the amount of files per " \
+ "directory)", "padding", 0, False)
+ p.add_switch("-r", "--recursive", "process the given directory " \
+ "recursively", "recursive", True, False)
+ p.add_switch(None, "--regex", "use regex syntax for the exclude or " \
+ "explicit pattern instead of just asterisk wildcards and " \
+ "semicolon separators (for details see the section " \
+ "'Regular expression operations' inside the official " \
+ "Python documentation)", "regex_syntax", True, False)
+ p.add_avalue("-s", "--separator", "use a user-defined character or " \
+ "string as a separator between the directory name and the " \
+ "unique numeric ID", "separator", " ", False)
+ p.add_avalue(None, "--simulate", "simulate the rename process and " \
+ "write the details into a report file", "report_file", None,
+ False)
+ p.add_avalue(None, "--step", "steps between each numeric ID", "step", 1,
+ False)
+ p.add_switch(None, "--version", "print the version number and exit", None,
+ True, False)
+
+ if len(sys.argv) == 1:
+ p.error("At least one required argument is missing.")
+ elif ("-h" in sys.argv) or ("--help" in sys.argv):
+ p.print_help()
+ sys.exit(0)
+ elif "--version" in sys.argv:
+ print fileren.get_version()
+ sys.exit(0)
+
+ args = p.parse_args()
+ if args.confirm and not args.report_file == None:
+ p.error("The confirm and the simulate argument cannot be given at " \
+ "the same time.")
+ if args.exclude_pattern and args.explicit_pattern:
+ p.error("The exclude and the explicit pattern argument cannot be " \
+ "given at the same time.")
+
+ try:
+ if not args.confirm and args.report_file == None:
+ if not common.confirm_notice():
+ sys.exit(0)
+
+ if args.exclude_pattern:
+ pattern = args.exclude_pattern
+ exclude = True
+ elif args.explicit_pattern:
+ exclude = False
+ pattern = args.explicit_pattern
+ else:
+ exclude = None
+ pattern = None
+
+ fileren.rename_files(args.directory, args.rename_mode, args.separator,
+ args.recursive, args.padding, exclude, pattern,
+ args.case, args.regex_syntax, args.report_file,
+ args.ignore_symlinks, args.ignore_file_ext,
+ args.custom_name, args.step, args.order_by)
+ except Exception as e:
+ p.error(e)
+
+if __name__ == "__main__":
+ main()
+
+# EOF
+
diff --git a/python2/readme.txt b/python2/readme.txt
new file mode 100644
index 0000000..bbf06d3
--- /dev/null
+++ b/python2/readme.txt
@@ -0,0 +1,64 @@
+
+README (Nomen)
+
+ Project
+
+ Nomen
+ Version 2.3.5 (based on Python framework 2.x)
+ Copyright (C) 2018 by Ralf Kilian
+
+ Website: http://www.urbanware.org
+ GitHub: https://github.com/urbanware-org/nomen
+
+ Definition
+
+ The Nomen project is a multi-purpose rename tool to consistently
+ rename the base name as well as the extension of files in a variety of
+ ways and also to remove unnecessary whitespaces from directory names.
+
+ License
+
+ Permission is hereby granted, free of charge, to any person obtaining
+ a copy of this software and associated documentation files (the
+ "Software"), to deal in the Software without restriction, including
+ without limitation the rights to use, copy, modify, merge, publish,
+ distribute, sublicense, and/or sell copies of the Software, and to
+ permit persons to whom the Software is furnished to do so, subject to
+ the following conditions:
+
+ The above copyright notice and this permission notice shall be
+ included in all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+ Requirements
+
+ This version of Nomen was built for the Python 2.x framework. If you
+ need a version that works with Python 3.x, you can also find one on
+ the website of the project mentioned above.
+
+ In order to use the project, Python 2.7 or higher is recommended, but
+ it may also work with earlier versions.
+
+ Usage
+
+ For fundamental documentation as well as some usage examples for each
+ component of the project, you may have a look at the text files inside
+ the included 'docs' sub-directory.
+
+ Legal information
+
+ The project name is completely fictitious. Any correspondences with
+ existing websites, applications, companies and/or other projects are
+ purely coincidental.
+
+ All trademarks belong to their respective owners.
+
+ Errors and omissions excepted.
+
diff --git a/python3/cfg/sample_lower.cfg b/python3/cfg/sample_lower.cfg
new file mode 100644
index 0000000..1e79765
--- /dev/null
+++ b/python3/cfg/sample_lower.cfg
@@ -0,0 +1,67 @@
+##############################################################################
+# #
+# Nomen sample configuration file for strings that should ALWAYS be #
+# LOWERCASE when converting the case of file names. #
+# #
+# This will NOT convert the case of the file extension. For renaming the #
+# extension, you may use the Nomen Extention Case Converter script. #
+# #
+# One string per line, empty and commented out lines (like this header) #
+# will be ignored. #
+# #
+# Please read the documentation before using any component of Nomen. #
+# #
+##############################################################################
+
+# Example 1:
+#
+# The word "foo" shall always be lowercase in file names.
+#
+# FOOBAR.TXT => file will NOT be renamed (because "FOO" is not a
+# separate word)
+# FOO BAR.TXT => file will be renamed to "foo BAR.txt"
+#
+foo
+
+# Example 2:
+#
+# The string "foo" shall always be lowercase in file names.
+#
+# FOOBAR.TXT => file will be renamed to "fooBAR.txt"
+# FOO BAR.TXT => file will be renamed to "foo BAR.txt"
+#
+$(foo)
+
+# Example 3:
+#
+# The string "bar" shall always be lowercase in file names.
+#
+# FOOBAR.TXT => file will be renamed to "FOObar.txt"
+# FOO BAR.TXT => file will be renamed to "FOO bar.txt"
+#
+$(bar)
+
+# Example 4:
+#
+# When converting the case of the files
+#
+# FOOBAR'S ADVENTURE.TXT
+# MARTIN O'SULLIVAN.TXT
+#
+# using title case, the file will be renamed as follows:
+#
+# Foobar'S Adventure.TXT
+# Martin O'Sullivan.TXT
+#
+# So, if you want to convert the "'S" to lowercase, you could use
+#
+# $('s)
+#
+# but this will also rename "O'Sullivan" to "O'sullivan". To avoid this you
+# may use the following statement. It only converts "'S" to lowercase if a
+# whitespace follows.
+#
+$('s )
+
+# EOF
+
diff --git a/python3/cfg/sample_mixed.cfg b/python3/cfg/sample_mixed.cfg
new file mode 100644
index 0000000..7a42e96
--- /dev/null
+++ b/python3/cfg/sample_mixed.cfg
@@ -0,0 +1,67 @@
+##############################################################################
+# #
+# Nomen sample configuration file for strings that should ALWAYS be MIXED #
+# CASE when converting the case of file names. #
+# #
+# This will NOT convert the case of the file extension. For renaming the #
+# extension, you may use the Nomen Extention Case Converter script. #
+# #
+# One string per line, empty and commented out lines (like this header) #
+# will be ignored. #
+# #
+# Please read the documentation before using any component of Nomen. #
+# #
+##############################################################################
+
+# Example 1:
+#
+# The word "FoO" shall always be mixed case in file names.
+#
+# FOOBAR.TXT => file will NOT be renamed (because "FOO" is not a
+# separate word)
+# FOO BAR.TXT => file will be renamed to "FoO BAR.txt"
+#
+FoO
+
+# Example 2:
+#
+# The string "FoO" shall always be mixed case in file names.
+#
+# FOOBAR.TXT => file will be renamed to "FoOBAR.txt"
+# FOO BAR.TXT => file will be renamed to "FoO BAR.txt"
+#
+$(FoO)
+
+# Example 3:
+#
+# The string "bAr" shall always be mixed case in file names.
+#
+# FOOBAR.TXT => file will be renamed to "FOObAr.txt"
+# FOO BAR.TXT => file will be renamed to "FOO bAr.txt"
+#
+$(bAr)
+
+# Example 4:
+#
+# When converting the case of the files
+#
+# FOOBAR'S ADVENTURE.TXT
+# MARTIN O'SULLIVAN.TXT
+#
+# using title case, the file will be renamed as follows:
+#
+# Foobar'S Adventure.TXT
+# Martin O'Sullivan.TXT
+#
+# So, if you want to convert the "'S" to lowercase, you could use
+#
+# $('s)
+#
+# but this will also rename "O'Sullivan" to "O'sullivan". To avoid this you
+# may use the following statement. It only converts "'S" to lowercase if a
+# whitespace follows.
+#
+$('s )
+
+# EOF
+
diff --git a/python3/cfg/sample_title.cfg b/python3/cfg/sample_title.cfg
new file mode 100644
index 0000000..2eecc26
--- /dev/null
+++ b/python3/cfg/sample_title.cfg
@@ -0,0 +1,45 @@
+##############################################################################
+# #
+# Nomen sample configuration file for strings that should ALWAYS be TITLE #
+# CASE when converting the case of file names. #
+# #
+# This will NOT convert the case of the file extension. For renaming the #
+# extension, you may use the Nomen Extention Case Converter script. #
+# #
+# One string per line, empty and commented out lines (like this header) #
+# will be ignored. #
+# #
+# Please read the documentation before using any component of Nomen. #
+# #
+##############################################################################
+
+# Example 1:
+#
+# The word "Foo" shall always be title case in file names.
+#
+# FOOBAR.TXT => file will NOT be renamed (because "FOO" is not a
+# separate word)
+# FOO BAR.TXT => file will be renamed to "Foo BAR.txt"
+#
+Foo
+
+# Example 2:
+#
+# The string "Foo" shall always be title case in file names.
+#
+# FOOBAR.TXT => file will be renamed to "FooBAR.txt"
+# FOO BAR.TXT => file will be renamed to "Foo BAR.txt"
+#
+$(Foo)
+
+# Example 3:
+#
+# The string "Foo" shall always be title case in file names.
+#
+# FOOBAR.TXT => file will be renamed to "FOOBar.txt"
+# FOO BAR.TXT => file will be renamed to "FOO Bar.txt"
+#
+$(Bar)
+
+# EOF
+
diff --git a/python3/cfg/sample_upper.cfg b/python3/cfg/sample_upper.cfg
new file mode 100644
index 0000000..6eb268a
--- /dev/null
+++ b/python3/cfg/sample_upper.cfg
@@ -0,0 +1,45 @@
+##############################################################################
+# #
+# Nomen sample configuration file for strings that should ALWAYS be #
+# UPPERCASE when converting the case of file names. #
+# #
+# This will NOT convert the case of the file extension. For renaming the #
+# extension, you may use the Nomen Extention Case Converter script. #
+# #
+# One string per line, empty and commented out lines (like this header) #
+# will be ignored. #
+# #
+# Please read the documentation before using any component of Nomen. #
+# #
+##############################################################################
+
+# Example 1:
+#
+# The word "FOO" shall always be uppercase in file names.
+#
+# foobar.txt => file will NOT be renamed (because "foo" is not a
+# separate word)
+# foo bar.txt => file will be renamed to "FOO bar.txt"
+#
+FOO
+
+# Example 2:
+#
+# The string "FOO" shall always be uppercase in file names.
+#
+# foobar.txt => file will be renamed to "FOObar.txt"
+# foo bar.txt => file will be renamed to "FOO bar.txt"
+#
+$(FOO)
+
+# Example 3:
+#
+# The string "BAR" shall always be uppercase in file names.
+#
+# foobar.txt => file will be renamed to "fooBAR.txt"
+# foo bar.txt => file will be renamed to "foo BAR.txt"
+#
+$(BAR)
+
+# EOF
+
diff --git a/python3/changelog.txt b/python3/changelog.txt
new file mode 100644
index 0000000..b2a03db
--- /dev/null
+++ b/python3/changelog.txt
@@ -0,0 +1,195 @@
+
+CHANGELOG (Nomen)
+
+ Version 2.3.5 (2018-04-14)
+
+ + Added new versions of the Clap and PaVal core modules (replaced the
+ existing ones).
+
+ * Revised (refurbished) all components of the project in general
+ (negligible changes).
+
+ Version 2.3.4 (2016-07-12)
+
+ + Added a simple random string generator method to the Nomen Common
+ core module.
+
+ * Revised the confirmation prompt inside the Nomen Common core module
+ (now uses random strings instead of a fixed one).
+
+ # Fixed the type mismatch bug inside the Nomen File Renamer core
+ module (occurred in case a file had the correct prefix but without
+ a trailing number).
+
+ Version 2.3.3 (2016-06-20)
+
+ + Added a new feature to the Nomen File Renamer component that allows
+ to order the files by their access, creation and modification time.
+
+ # Fixed the maximum recursion depth exceeded bug inside the Nomen
+ Common core module (occurred under certain circumstances, only)
+
+ Version 2.3.2 (2016-05-19)
+
+ * Revised some code inside the Nomen Directory Name Space Modifier
+ script changes).
+
+ # Fixed the path referenced before assignment bug inside the Nomen
+ Common core module (occurred under certain circumstances, only).
+
+ Version 2.3.1 (2016-05-17)
+
+ + Added a basic exclude feature to the Nomen Directory Name Space
+ Modifier script (case-insensitive, without regex syntax).
+
+ # Fixed the character replacement bug inside a method of the Nomen
+ Common core module (used by Nomen Directory Name Space Modifier)
+ which occurred when the recurisve parameter was set.
+
+ Version 2.3.0 (2016-04-16)
+
+ + Added a new feature to the Nomen File Renamer component that allows
+ user-defined steps between each numeric ID.
+
+ * Revised some code inside the Nomen Common core module (negligible
+ changes).
+ * Revised some parameter validation code in various core modules.
+
+ Version 2.2.6 (2015-06-05)
+
+ + Added some additional command-line arguments to the Nomen Directory
+ Name Space Modifier script to insert and remove spaces around
+ brackets and hyphens.
+
+ * Revised some code inside the Nomen Common core module (negligible
+ changes).
+
+ # Fixed the character replacement bug inside a method of the Nomen
+ Common core module (used by Nomen Directory Name Space Modifier).
+
+ Version 2.2.5 (2015-01-24)
+
+ + Added an optional feature to the Nomen Directory Name Space Modifier
+ (former "Nomen Directory Name Space Remover") to insert and remove
+ spaces next to punctuation characters.
+
+ * Revised the confirmation prompt inside the Nomen Common core module.
+
+ Version 2.2.4 (2015-01-03)
+
+ * Revised the code for handling regular expressions inside the core
+ modules.
+
+ - Removed unnecessary imports and unused variables from the core
+ modules.
+
+ Version 2.2.3 (2014-08-25)
+
+ + Added the Nomen Directory Name Space Remover component.
+
+ * Revised (enhanced) the Nomen Common core module.
+
+ Version 2.2.2 (2014-06-28)
+
+ + Added a feature that allows omitting the config file path if the
+ file is located in the corresponding config sub-directory.
+
+ * Revised the method to parse the case config files inside the Nomen
+ Static Case core module (added default values).
+
+ Version 2.2.1 (2014-05-22)
+
+ + Added an optional command-line argument to the Nomen File Name
+ Modifier script to remove certain leading and trailing characters
+ from the processed base name.
+
+ # Fixed the file skip bug inside the Nomen File Name Modifier module
+ when processing files whose base name contains dots.
+
+ Version 2.2.0 (2014-05-16)
+
+ + Added the new Nomen File Name Modifier component.
+
+ * Revised (reduced) some code inside the Nomen Static Case core
+ module.
+ * Revised (renamed) some command-line arguments inside all scripts
+ (for more clarity).
+
+ - Removed some unnecessary code from the Nomen File Renamer core
+ module.
+
+ Version 2.1.6 (2014-05-14)
+
+ + Added a method to the Nomen Common core module that returns invalid
+ file name characters.
+
+ * Revised (reduced) some code inside the Nomen File Renamer as the
+ well as Nomen Extension Renamer core module (negligible changes).
+ * Revised the list of invalid file name characters.
+
+ Version 2.1.5 (2014-04-22)
+
+ * Revised (reduced) the code inside the Nomen Common core module that
+ determines if a file with the new name already exists.
+ * Revised (reduced) the code inside the Nomen Common core module that
+ determines the case sensitivity of the file system.
+
+ # Fixed the rename bug ("no such file or directory") inside the Nomen
+ Common core module when renaming files using the "rename-new" or
+ "fill-gaps" mode (only occurred on case-insensitive file systems).
+
+ Version 2.1.4 (2014-04-19)
+
+ # Fixed the attribute error inside the code of the Nomen Common core
+ module that determines if a file with the new name already exists.
+ # Fixed the file system case sensitivity bug inside Nomen Common core
+ module (which did not determine the case sensitivity properly).
+ # Fixed the list bug inside the Nomen File Renamer core module (which
+ did not append all skipped files to the list when using "fill-gaps"
+ mode).
+
+ Version 2.1.3 (2014-04-12)
+
+ + Added an optional command-line argument to the Nomen File Renamer
+ script to set a custom file name.
+
+ * Revised the message text of some exceptions (for more clarity).
+
+ Version 2.1.2 (2014-03-28)
+
+ * Revised (reduced) the code inside the Nomen Common core module to
+ check if a file already has been processed.
+ * Revised the code inside the Nomen Common core module to check if the
+ file system is case-sensitive.
+
+ Version 2.1.1 (2014-03-22)
+
+ + Added an optional command-line argument to the Nomen File Renamer
+ script to ignore the file extension when numerating files.
+ + Added the estimated time to the simulation report file.
+
+ * Revised (reduced) some code inside the Nomen Common core module.
+
+ # Fixed the overwrite bug that occurred due to different cases of the
+ file extensions when renaming files via the Nomen File Renamer on
+ case-insensitive file systems (such as FAT32 and NTFS).
+
+ Version 2.1.0 (2014-03-19)
+
+ * Revised the "fill" rename methods (merged them to one method and
+ removed unused variables) inside the Nomen File Renamer core module.
+ * Revised (changed) the names of the rename modes inside the Nomen
+ File Renamer component (for more clarity).
+ * Revised the way of renaming files (numeration is now dependent of
+ the file extension) inside the Nomen File Renamer core module.
+
+ # Fixed the attribute error inside the Nomen Common core module when
+ reading out the major version of the Python framework using Python
+ version 2.6 or below.
+ # Fixed the empty list bug inside the Nomen File Renamer core module
+ (which terminated the whole file rename process, if occurred).
+
+ Version 2.0.0 (2014-03-18)
+
+ * First official release of this major version.
+
diff --git a/python3/core/__init__.py b/python3/core/__init__.py
new file mode 100755
index 0000000..e69de29
diff --git a/python3/core/clap.py b/python3/core/clap.py
new file mode 100755
index 0000000..e502975
--- /dev/null
+++ b/python3/core/clap.py
@@ -0,0 +1,216 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+# ============================================================================
+# Clap - Command-line argument parser module
+# Copyright (C) 2018 by Ralf Kilian
+# Distributed under the MIT License (https://opensource.org/licenses/MIT)
+#
+# Website: http://www.urbanware.org
+# GitHub: https://github.com/urbanware-org/clap
+# ============================================================================
+
+__version__ = "1.1.10"
+
+
+def get_version():
+ """
+ Return the version of this module.
+ """
+ return __version__
+
+
+class Parser(object):
+ """
+ Project independent command-line argument parser class.
+ """
+ __arg_grp_opt = None
+ __arg_grp_req = None
+ __arg_parser = None
+ __is_argparser = False
+ __conflict_handler = "resolve" # used by OptionParser, only
+
+ def __init__(self):
+ try:
+ from argparse import ArgumentParser
+ self.__arg_parser = ArgumentParser(add_help=False)
+ self.__arg_grp_req = \
+ self.__arg_parser.add_argument_group("required arguments")
+ self.__arg_grp_opt = \
+ self.__arg_parser.add_argument_group("optional arguments")
+ self.__is_argparser = True
+ return
+ except ImportError:
+ # Ignore the exception and proceed with the fallback
+ pass
+
+ try:
+ from optparse import OptionParser
+ self.__arg_parser = \
+ OptionParser(conflict_handler=self.__conflict_handler)
+ self.__arg_grp_req = \
+ self.__arg_parser.add_option_group("Required arguments")
+ self.__arg_grp_opt = \
+ self.__arg_parser.add_option_group("Optional arguments")
+ return
+ except ImportError:
+ # This should never happen
+ raise ImportError("Failed to initialize an argument parser.")
+
+ def add_avalue(self, arg_short, arg_long, arg_help, arg_dest, arg_default,
+ arg_required):
+ """
+ Add an argument that expects a single user-defined value.
+ """
+ if arg_required:
+ obj = self.__arg_grp_req
+ else:
+ obj = self.__arg_grp_opt
+
+ if arg_default is not None:
+ # Enclose the value with quotes in case it is not an integer
+ quotes = "'"
+ try:
+ arg_default = int(arg_default)
+ quotes = ""
+ except ValueError:
+ pass
+
+ if arg_help.strip().endswith(")"):
+ arg_help = arg_help.rstrip(")")
+ arg_help += ", default is %s%s%s)" % \
+ (quotes, str(arg_default), quotes)
+ else:
+ arg_help += " (default is %s%s%s)" % \
+ (quotes, str(arg_default), quotes)
+
+ if self.__is_argparser:
+ if arg_short is None:
+ obj.add_argument(arg_long, help=arg_help, dest=arg_dest,
+ default=arg_default, required=arg_required)
+ else:
+ obj.add_argument(arg_short, arg_long, help=arg_help,
+ dest=arg_dest, default=arg_default,
+ required=arg_required)
+ else:
+ if arg_short is None:
+ obj.add_option(arg_long, help=arg_help, dest=arg_dest,
+ default=arg_default)
+ else:
+ obj.add_option(arg_short, arg_long, help=arg_help,
+ dest=arg_dest, default=arg_default)
+
+ def add_predef(self, arg_short, arg_long, arg_help, arg_dest, arg_choices,
+ arg_required):
+ """
+ Add an argument that expects a certain predefined value.
+ """
+ if arg_required:
+ obj = self.__arg_grp_req
+ else:
+ obj = self.__arg_grp_opt
+
+ if self.__is_argparser:
+ if arg_short is None:
+ obj.add_argument(arg_long, help=arg_help, dest=arg_dest,
+ choices=arg_choices, required=arg_required)
+ else:
+ obj.add_argument(arg_short, arg_long, help=arg_help,
+ dest=arg_dest, choices=arg_choices,
+ required=arg_required)
+ else:
+ if arg_short is None:
+ obj.add_option(arg_long, help=arg_help, dest=arg_dest,
+ choices=arg_choices)
+ else:
+ # The OptionParser does not print the values to choose from,
+ # so these have to be added manually to the description of
+ # the argument first
+ arg_help += " (choose from "
+ for item in arg_choices:
+ arg_help += "'%s', " % item
+ arg_help = arg_help.rstrip(", ") + ")"
+
+ obj.add_option(arg_short, arg_long, help=arg_help,
+ dest=arg_dest)
+
+ def add_switch(self, arg_short, arg_long, arg_help, arg_dest, arg_store,
+ arg_required):
+ """
+ Add an argument that does not expect anything, but returns a
+ boolean value.
+ """
+ if arg_required:
+ obj = self.__arg_grp_req
+ else:
+ obj = self.__arg_grp_opt
+
+ if arg_store:
+ arg_store = "store_true"
+ else:
+ arg_store = "store_false"
+
+ if self.__is_argparser:
+ if arg_short is None:
+ obj.add_argument(arg_long, help=arg_help, dest=arg_dest,
+ action=arg_store, required=arg_required)
+ else:
+ obj.add_argument(arg_short, arg_long, help=arg_help,
+ dest=arg_dest, action=arg_store,
+ required=arg_required)
+ else:
+ if arg_short is None:
+ obj.add_option(arg_long, help=arg_help, dest=arg_dest,
+ action=arg_store)
+ else:
+ obj.add_option(arg_short, arg_long, help=arg_help,
+ dest=arg_dest, action=arg_store)
+
+ def dependency(self, arg_name, arg_value, dependency):
+ """
+ Check the dependency of a command-line argument.
+ """
+ if dependency is not None:
+ if arg_value is None or str(arg_value) == "":
+ raise Exception("The '%s' argument depends on %s'." %
+ (arg_name, dependency))
+
+ def error(self, obj):
+ """
+ Raise an error and cause the argument parser to print the error
+ message.
+ """
+ if type(obj) == str:
+ obj = obj.strip()
+
+ self.__arg_parser.error(obj)
+
+ def parse_args(self):
+ """
+ Parse and return the command-line arguments.
+ """
+ if self.__is_argparser:
+ args = self.__arg_parser.parse_args()
+ else:
+ (args, values) = self.__arg_parser.parse_args()
+ return args
+
+ def print_help(self):
+ """
+ Print the usage, description, argument details and epilog.
+ """
+ self.__arg_parser.print_help()
+
+ def set_description(self, string):
+ """
+ Set the description text.
+ """
+ self.__arg_parser.description = string.strip()
+
+ def set_epilog(self, string):
+ """
+ Set the epilog text.
+ """
+ self.__arg_parser.epilog = string.strip()
+
+# EOF
diff --git a/python3/core/common.py b/python3/core/common.py
new file mode 100755
index 0000000..430fb5e
--- /dev/null
+++ b/python3/core/common.py
@@ -0,0 +1,555 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+# ============================================================================
+# Nomen - Multi-purpose rename tool
+# Common core module
+# Copyright (C) 2018 by Ralf Kilian
+# Distributed under the MIT License (https://opensource.org/licenses/MIT)
+#
+# Website: http://www.urbanware.org
+# GitHub: https://github.com/urbanware-org/nomen
+# ============================================================================
+
+__version__ = "2.3.5"
+
+import os
+from . import paval as pv
+import random
+import re
+import sys
+import tempfile
+
+from datetime import datetime as dt
+
+def compile_regex(string, ignore_case=True, regex_syntax=False):
+ """
+ Compile a regular expression from the given pattern string.
+ """
+ pv.string(string, "regular expression", True, None)
+ if regex_syntax:
+ pattern = ".*" + string + ".*"
+ else:
+ spec_chars = [ "\\", ".", "^", "$", "+", "?", "{", "}", "[", "]",
+ "|", "(", ")" ]
+ for char in spec_chars:
+ string = string.replace(char, "\\" + char)
+ string = string.strip("*").strip(";")
+ while ("*" * 2) in string:
+ string = string.replace(("*" * 2), "*")
+ while (";" * 2) in string:
+ string = string.replace((";" * 2), ";")
+
+ list_string = string.split(";")
+ if len(list_string) > 0:
+ pattern = ""
+ for crit in list_string:
+ if not crit == "":
+ pattern += "(.*" + crit.replace("*", ".*") + ".*)|"
+ pattern = pattern.rstrip("|")
+ if pattern == "":
+ raise Exception("The given string does not make sense this " \
+ "way.")
+
+ if ignore_case:
+ regex = re.compile(pattern, re.IGNORECASE)
+ else:
+ regex = re.compile(pattern)
+
+ return regex
+
+def confirm_notice():
+ """
+ Display a notice which must be confirmed by the user to proceed.
+ """
+ string = random_string(6, True, True, True, True)
+ proceed = False
+ notice_text = """ o o o 88
+ 8 8 88
+ 8 8 .oPYo. oPYo. odYo. o8 odYo. .oPYo. 88
+ 8 db 8 .oooo8 8 '' 8' '8 8 8' '8 8 8 88
+ 'b.PY.d' 8 8 8 8 8 8 8 8 8 8
+ '8 8' 'YooP8 8 8 8 8 8 8 'YooP8 88
+ . 8
+ 'oooP'
+
+Please use this tool with care to avoid data damage or loss!
+
+There is no function to undo the changes done by this tool, so you
+should be aware of what you are doing. Improper use (e. g. modifying
+files inside system directories) will corrupt your system!
+
+If you wish to proceed, type '%s' (case-sensitive, without any
+quotes or spaces) and press the key. Otherwise, the process
+will be cancelled.""" % string
+
+ print_text_box("", notice_text)
+ choice = input("> ")
+
+ if choice == string:
+ choice = "Proceeding."
+ proceed = True
+ else:
+ choice = "cancelled."
+ print("\n%s\n" % choice)
+
+ return proceed
+
+def dir_space_modifier(directory, remove_duplicate=False,
+ remove_leading=False, remove_trailing=False,
+ brackets=False, hyphens=False, punctuation=False,
+ ignore_symlinks=False, recursive=False, exclude=None):
+ """
+ Modify a directory name by removing leading, trailing and duplicate
+ spaces or by inserting and removing spaces next to punctuation
+ characters.
+ """
+
+ list_exclude = []
+ if not exclude == None:
+ list_exclude = exclude.split(";")
+
+ for item in os.listdir(directory):
+ excluded = False
+ if os.path.isdir(os.path.join(directory, item)):
+ path = os.path.join(directory, item)
+ for excl in list_exclude:
+ if excl.lower() in path.lower():
+ excluded = True
+ break
+ else:
+ continue
+
+ if excluded:
+ nextdir = path
+ else:
+ if remove_duplicate:
+ while (" " * 2) in item:
+ item = item.replace((" " * 2), " ")
+
+ if hyphens:
+ item = item.replace("-", " - ")
+ while "- " in item:
+ item = item.replace("- ", "- ")
+ while " -" in item:
+ item = item.replace(" -", " -")
+
+ if brackets:
+ while "( " in item:
+ item = item.replace("( ", "(")
+ item = item.replace("(", " (")
+ while " )" in item:
+ item = item.replace(" )", ")")
+ item = item.replace(")", ") ").replace(") .", ").")
+ while "[ " in item:
+ item = item.replace("[ ", "[")
+ item = item.replace("[", " [")
+ while " ]" in item:
+ item = item.replace(" ]", "]")
+ item = item.replace("]", "] ").replace("] .", "].")
+ item = item.replace("( [", "([").replace("] )", "])")
+ item = item.replace("[ (", "[(").replace(") ]", ")]")
+
+ if punctuation:
+ item = item.replace(".", ". ")
+ while " ." in item:
+ item = item.replace(" .", ".")
+ item = item.replace(",", ", ")
+ while " ," in item:
+ item = item.replace(" ,", ",")
+ item = item.replace(":", ": ")
+ while " :" in item:
+ item = item.replace(" :", ":")
+ item = item.replace(";", "; ")
+ while " ;" in item:
+ item = item.replace(" ;", ";")
+ item = item.replace("!", "! ")
+ while " !" in item:
+ item = item.replace(" !", "!")
+ item = item.replace("?", "? ")
+ while " ?" in item:
+ item = item.replace(" ?", "?")
+ remove_leading = True
+ remove_trailing = True
+
+ if remove_leading:
+ item = item.lstrip()
+ if remove_trailing:
+ item = item.rstrip()
+
+ newpath = os.path.join(directory, item)
+ if remove_duplicate:
+ # Repeat this step after the replace actions above
+ while (" " * 2) in newpath:
+ newpath = newpath.replace((" " * 2), " ")
+
+ if not os.path.exists(newpath):
+ os.rename(path, newpath)
+ nextdir = newpath
+ else:
+ nextdir = path
+
+ if recursive:
+ dir_space_modifier(nextdir, remove_duplicate, remove_leading,
+ remove_trailing, brackets, hyphens,
+ punctuation, ignore_symlinks, True, exclude)
+
+def file_exists(file_path, list_files, fs_case_sensitive):
+ """
+ Check if a file already exists on the file system as well as in a
+ given list.
+ """
+ file_path = os.path.abspath(file_path)
+ if os.path.exists(file_path):
+ file_exists = True
+ else:
+ file_exists = False
+
+ for item in list_files:
+ if item[1] == None:
+ item[1] = ""
+
+ if fs_case_sensitive:
+ if file_path == item[1] or file_path == item[2]:
+ file_exists = True
+ break
+ else:
+ if file_path.lower() == item[1].lower() or \
+ file_path.lower() == item[2].lower():
+ file_exists = True
+ break
+
+ return file_exists
+
+def format_timestamp(float_stamp=0):
+ """
+ Convert a timestamp float into a readable format.
+ """
+ return str(dt.fromtimestamp(float(str(float_stamp))))
+
+def get_files(directory, recursive=False, ignore_case=True, regex=None,
+ regex_exclude=True, ignore_symlinks=False, order_by=None):
+ """
+ Get the files and sub-directories from the given directory.
+ """
+ pv.path(directory, "given", False, True)
+
+ directory = os.path.abspath(directory)
+ list_files = []
+ list_excluded = []
+
+ list_files, list_excluded = \
+ __get_files( \
+ directory, ignore_case, regex, regex_exclude, ignore_symlinks,
+ recursive, list_files, list_excluded, order_by)
+
+ if order_by == None:
+ list_files.sort()
+ list_excluded.sort()
+
+ return list_files, list_excluded
+
+def get_fs_case_sensitivity(directory):
+ """
+ Determine if the file system of the given directory is case-sensitive.
+ """
+ # This should be done with every directory that is processed, due to the
+ # fact, that e. g. a device containing a case-insensitive file system can
+ # be mounted into a directory of a case-sensitive file system.
+
+ pv.path(directory, "given", False, True)
+ directory = os.path.abspath(directory)
+
+ fd_temp, file_temp = tempfile.mkstemp(dir=directory)
+ file_name = os.path.basename(file_temp)
+ if os.path.exists(os.path.join(directory, file_name.upper())):
+ fs_case_sensitive = False
+ else:
+ fs_case_sensitive = True
+ os.close(fd_temp)
+ os.remove(file_temp)
+
+ return fs_case_sensitive
+
+def get_invalid_chars():
+ """
+ Return the invalid file name characters (which must or should not be
+ part of a file name).
+ """
+ # This list of characters depends on the file system where the files are
+ # being renamed on. Due to the fact, that e. g. a device containing a
+ # different file system can be mounted into a directory of the local file
+ # system, the following characters will be handled as invalid on every
+ # file system.
+ invalid_chars = "/\\?%*:|\"<>\n\r\t"
+
+ return invalid_chars
+
+def get_version():
+ """
+ Return the version of this module.
+ """
+ return __version__
+
+def print_text_box(heading, text):
+ """
+ Print a text message outlined with an ASCII character frame.
+ """
+ heading = heading.strip()
+ if len(heading) > 72:
+ raise Exception("The text box heading must not be longer than 72 " \
+ "characters.")
+ if text == "":
+ raise Exception("The text box text must not be empty.")
+
+ text_box = "\n+" + ("-" * 76) + "+" + \
+ "\n|" + (" " * 76) + "|"
+ if not heading == "":
+ padding = int((72 - len(heading)) / 2)
+ heading = (" " * (padding + 2) + heading).ljust(76, " ")
+ text_box += ("\n|%s|\n|" + (" " * 76) + "|") % heading
+ list_text = text.split("\n")
+ for text in list_text:
+ list_words = text.split(" ")
+ count = 1
+ line = ""
+ for word in list_words:
+ if len(line + word + " ") > 73:
+ text_box += "\n| " + line.ljust(74, " ") + "|"
+ line = word + " "
+ else:
+ line = line + word + " "
+ count += 1
+ if count > len(list_words):
+ text_box += "\n| " + line.ljust(74, " ") + "|"
+ text_box += "\n|" + (" " * 76) + "|" \
+ "\n+" + ("-" * 76) + "+\n"
+
+ print(text_box)
+
+def random_string(length, uppercase=True, lowercase=False, numbers=False,
+ unique=False):
+ """
+ Generate a random string out of literals and numbers.
+ """
+ literals = "ABCDEFGHIJLMNOPQRSTUVWXYZ"
+ numbers = "0123456789"
+ chars = ""
+ string = ""
+
+ if uppercase:
+ chars += literals
+ if lowercase:
+ chars += literals.lower()
+ if numbers:
+ chars += numbers
+
+ if len(chars) == 0:
+ return string
+ if len(chars) < length:
+ length = len(chars)
+
+ while len(string) < length:
+ rnd = random.randint(0, len(chars) - 1)
+ char = chars[rnd]
+ if unique:
+ if char in string:
+ continue
+ string += char
+
+ return string
+
+
+def rename(list_files, reverse=False):
+ """
+ Rename the files which have neither been excluded nor skipped.
+ """
+ list_skipped = []
+
+ if len(list_files) > 0:
+ if reverse:
+ list_files = reversed(list_files)
+
+ for item in list_files:
+ if os.path.exists(item[0]):
+ if os.path.exists(item[2]):
+ list_skipped.append([item[0], item[1], item[2]])
+ continue
+
+ # In some cases the file will get a temporary name first and
+ # then its name will be changed to what it should be.
+ #
+ # This behaviour is required when using file systems that are
+ # case-insensitive (such as FAT32 or NTFS) where e. g. the
+ # file "FOOBAR.txt" would overwrite the file "foobar.txt"
+ # inside the same directory.
+ if item[1] == None or \
+ item[1] == "":
+ os.rename(item[0], item[2])
+ else:
+ os.rename(item[0], item[1])
+ os.rename(item[1], item[2])
+
+ if len(list_skipped) > 0:
+ if not list_skipped == list_files:
+ rename(list_skipped, reverse)
+
+def report(report_file=None, list_header=[], list_renamed=[],
+ list_excluded=[], list_skipped=[], time_start=None):
+ """
+ Write the details of the simulated rename process (simulation report)
+ into a file.
+ """
+ files_total = str(len(list_renamed) + len(list_excluded) + \
+ len(list_skipped))
+ just = len(files_total)
+ files_renamed = str(len(list_renamed)).rjust(just, " ")
+ files_excluded = str(len(list_excluded)).rjust(just, " ")
+ files_skipped = str(len(list_skipped)).rjust(just, " ")
+ time_end = dt.now()
+
+ try:
+ time_elapsed = str(time_end - time_start)
+ time_start = str(time_start)
+ except:
+ raise Exception("An invalid start date was given.")
+
+ output = "\r\n" + "=" * 78 + \
+ "\r\nFile type: " + list_header[0] + \
+ "\r\n" + "-" * 78
+
+ for i in range(1, len(list_header)):
+ output += "\r\n" + list_header[i][0].ljust(20, " ") + \
+ str(list_header[i][1])
+
+ output += "\r\n" + "-" * 78 + \
+ "\r\nFiles renamed: " + files_renamed + \
+ "\r\nFiles excluded: " + files_excluded + \
+ "\r\nFiles skipped: " + files_skipped + \
+ "\r\nFiles total: " + files_total + \
+ "\r\n" + "-" * 78 + \
+ "\r\nTimestamp: " + time_start[:-7] + \
+ "\r\nElapsed time: " + time_elapsed + \
+ "\r\nNomen version: " + __version__ + \
+ "\r\n" + "=" * 78 + "\r\n\r\n"
+
+ if len(list_renamed) > 0:
+ output += "\r\n [Renamed]\r\n"
+ for item in list_renamed:
+ output += " - Old: %s\r\n" % item[0]
+ output += " - New: %s\r\n\r\n" % item[2]
+ output += "\r\n"
+
+ if len(list_excluded) > 0:
+ output += "\r\n [Excluded]\r\n"
+ for item in list_excluded:
+ output += " - %s\r\n" % item
+ output += "\r\n"
+
+ if len(list_skipped) > 0:
+ output += "\r\n [Skipped]\r\n"
+ for item in list_skipped:
+ output += " - %s\r\n" % item
+ output += "\r\n"
+
+ fh_report = open(report_file, "wb")
+
+ # Run the appropriate code for the Python framework used
+ if sys.version_info[0] == 2:
+ fh_report.write(output)
+ elif sys.version_info[0] > 2:
+ fh_report.write(output.encode(sys.getdefaultencoding()))
+
+ fh_report.close()
+
+def __get_files(directory, ignore_case, regex, regex_exclude, ignore_symlinks,
+ recursive, list_content, list_excluded, order_by):
+ """
+ Core method to get the files from the given directory and its
+ sub-directories.
+ """
+ list_dirs = []
+ list_files = []
+
+ for item in os.listdir(directory):
+ path = os.path.join(directory, item)
+ if ignore_symlinks:
+ if os.path.islink(path):
+ continue
+ if os.path.isfile(path):
+ if regex == None:
+ list_files.append(path)
+ else:
+ if regex_exclude:
+ if ignore_case:
+ if regex.match(item.lower()):
+ list_excluded.append(path)
+ else:
+ list_files.append(path)
+ else:
+ if regex.match(item):
+ list_excluded.append(path)
+ else:
+ list_files.append(path)
+ else:
+ if ignore_case:
+ if regex.match(item.lower()):
+ list_files.append(path)
+ else:
+ list_excluded.append(path)
+ else:
+ if regex.match(item):
+ list_files.append(path)
+ else:
+ list_excluded.append(path)
+ else:
+ list_dirs.append(path)
+
+ if len(list_files) > 0:
+ if order_by == None:
+ list_files.sort()
+ else:
+ list_files = __set_order(list_files, order_by)
+ list_content.append([directory, list_files])
+
+ if recursive:
+ for directory in list_dirs:
+ list_content, list_excluded = \
+ __get_files(directory, ignore_case, regex, regex_exclude,
+ ignore_symlinks, True, list_content,
+ list_excluded, order_by)
+
+ return list_content, list_excluded
+
+def __set_order(file_list, order_by):
+ """
+ Set a certain order of the files before renaming them.
+ """
+ list_files = []
+ list_temp = []
+
+ for file_name in file_list:
+ if "." in file_name:
+ file_ext = file_name.split(".")[-1]
+ else:
+ file_ext = ""
+
+ time_access = format_timestamp(os.stat(file_name).st_atime)
+ time_create = format_timestamp(os.stat(file_name).st_ctime)
+ time_modify = format_timestamp(os.stat(file_name).st_mtime)
+
+ if order_by == "accessed":
+ list_temp.append([time_access, file_name, file_ext])
+ elif order_by == "created":
+ list_temp.append([time_create, file_name, file_ext])
+ else:
+ list_temp.append([time_modify, file_name, file_ext])
+
+ list_temp.sort()
+ for item in list_temp:
+ list_files.append(item[1])
+
+ return list_files
+
+# EOF
+
diff --git a/python3/core/extren.py b/python3/core/extren.py
new file mode 100755
index 0000000..0457d44
--- /dev/null
+++ b/python3/core/extren.py
@@ -0,0 +1,278 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+# ============================================================================
+# Nomen - Multi-purpose rename tool
+# Extension renamer core module
+# Copyright (C) 2018 by Ralf Kilian
+# Distributed under the MIT License (https://opensource.org/licenses/MIT)
+#
+# Website: http://www.urbanware.org
+# GitHub: https://github.com/urbanware-org/nomen
+# ============================================================================
+
+__version__ = "2.3.5"
+
+from . import common
+import os
+from . import paval as pv
+import re
+
+from datetime import datetime as dt
+
+def convert_case(directory, case, conflict_mode, recursive=False,
+ report_file=None, ignore_symlinks=False):
+ """
+ Convert the case of the file extensions.
+ """
+ pv.path(directory, "given", False, True)
+ pv.compstr(case, "case", ["lower", "title", "upper"])
+ pv.compstr(conflict_mode, "conflict mode", ["rename", "skip"])
+
+ case = case.lower()
+ conflict_mode = conflict_mode.lower()
+ directory = os.path.abspath(directory)
+ if not directory.endswith(os.path.sep):
+ directory += os.path.sep
+
+ if report_file == None:
+ simulate = False
+ else:
+ pv.path(report_file, "report", True, False)
+ report_file = os.path.abspath(report_file)
+ simulate = True
+
+ time_start = dt.now()
+
+ list_content = []
+ list_excluded = []
+ list_renamed = []
+ list_skipped = []
+ regex = re.compile("(.*)")
+
+ list_content, list_excluded = \
+ common.get_files(directory, recursive, True, regex, False,
+ ignore_symlinks)
+ for item in list_content:
+ list_files = item[1]
+ list_renamed, list_skipped = \
+ __convert_case(list_files, list_renamed, list_skipped, case,
+ conflict_mode, recursive)
+
+ if simulate:
+ list_header = []
+ list_header.append("Nomen Extension Case Converter simulation report")
+ list_header.append(["Report file name:", report_file])
+ list_header.append(["Directory:", directory])
+ list_header.append(["Recursive:", recursive])
+ list_header.append(["Ignore symlinks:", ignore_symlinks])
+ list_header.append(["Conflict mode:", conflict_mode.capitalize()])
+ list_header.append(["Case:", case.capitalize()])
+
+ common.report(report_file, list_header, list_renamed, list_excluded,
+ list_skipped, time_start)
+ else:
+ common.rename(list_renamed)
+
+def get_version():
+ """
+ Return the version of this module.
+ """
+ return __version__
+
+def rename_extensions(directory, conflict_mode, extension, extension_target,
+ recursive=False, ignore_case=True, report_file=None,
+ ignore_symlinks=False):
+ """
+ Rename the file extensions in the given directory and all of its
+ sub-directories (if requested).
+ """
+ pv.path(directory, "given", False, True)
+ pv.compstr(conflict_mode, "conflict mode", ["rename", "skip"])
+ pv.string(extension, "extension", False, common.get_invalid_chars())
+ pv.string(extension_target, "target extension", False,
+ common.get_invalid_chars())
+
+ conflict_mode = conflict_mode.lower()
+ directory = os.path.abspath(directory)
+ if not directory.endswith(os.path.sep):
+ directory += os.path.sep
+
+ if report_file == None:
+ simulate = False
+ else:
+ pv.path(report_file, "report", True, False)
+ report_file = os.path.abspath(report_file)
+ simulate = True
+
+ time_start = dt.now()
+
+ list_content = []
+ list_excluded = []
+ list_extensions = []
+ list_renamed = []
+ list_skipped = []
+
+ if ";" in extension:
+ while (";" * 2) in extension:
+ extension = extension.replace((";" * 2), ";")
+
+ list_temp = extension.split(";")
+ for extension in list_temp:
+ if not extension == "":
+ list_extensions.append(extension)
+
+ if len(list_extensions) == 0:
+ raise Exception("The given extension list does not contain any " \
+ "extensions.")
+ else:
+ list_extensions.append(extension)
+
+ pattern = ""
+ for extension in list_extensions:
+ pattern += "(.*\." + str(extension) + "$)|"
+ pattern = pattern.rstrip("|")
+
+ if ignore_case:
+ regex = re.compile(pattern, re.IGNORECASE)
+ else:
+ regex = re.compile(pattern)
+
+ list_content, list_excluded = \
+ common.get_files(directory, recursive, ignore_case, regex, False,
+ ignore_symlinks)
+ for item in list_content:
+ list_files = item[1]
+ list_renamed, list_skipped = \
+ __rename_extensions(list_files, list_extensions, list_renamed,
+ list_skipped, conflict_mode, extension_target)
+
+ if simulate:
+ list_header = []
+ list_header.append("Nomen Extension Renamer simulation report")
+ list_header.append(["Report file name:", report_file])
+ list_header.append(["Directory:", directory])
+ list_header.append(["Recursive:", recursive])
+ list_header.append(["Ignore symlinks:", ignore_symlinks])
+ list_header.append(["Conflict mode:", conflict_mode.capitalize()])
+ list_header.append(["Extensions:", extension])
+ list_header.append(["Target extension:", extension_target])
+ list_header.append(["Ignore case:", ignore_case])
+
+ common.report(report_file, list_header, list_renamed, list_excluded,
+ list_skipped, time_start)
+ else:
+ common.rename(list_renamed)
+
+def __convert_case(list_files, list_renamed, list_skipped, case,
+ conflict_mode, recursive):
+ """
+ Core method to convert the case of the file extensions.
+ """
+ fs_case = common.get_fs_case_sensitivity(os.path.dirname(list_files[0]))
+
+ for file_path in list_files:
+ num = 1
+ list_path = file_path.split(os.path.sep)
+ file_name = list_path[-1]
+ file_ext = os.path.splitext(file_name)[1]
+
+ if file_ext == "":
+ list_skipped.append(file_path)
+ continue
+
+ if case == "lower":
+ extension_target = file_ext.lower()
+ elif case == "title":
+ extension_target = file_ext.title()
+ elif case == "upper":
+ extension_target = file_ext.upper()
+
+ file_newpath = file_path.replace(file_ext, extension_target)
+ if (file_path == file_newpath):
+ list_skipped.append(file_path)
+ continue
+
+ if conflict_mode == "rename":
+ while True:
+ if common.file_exists(file_newpath, list_renamed, fs_case):
+ if not fs_case:
+ if file_path.lower() == file_newpath.lower():
+ break
+
+ file_newpath = \
+ file_path.replace(file_ext,
+ "_" + str(num) + extension_target)
+ num += 1
+ else:
+ break
+ elif conflict_mode == "skip":
+ if common.file_exists(file_newpath, list_renamed, fs_case):
+ if not fs_case:
+ if not file_path.lower() == file_newpath.lower():
+ list_skipped.append(file_newpath)
+ continue
+ else:
+ list_skipped.append(file_newpath)
+ continue
+
+ if os.path.exists(file_path):
+ list_renamed.append([file_path, file_newpath + ".__temp__",
+ file_newpath])
+
+ return list_renamed, list_skipped
+
+def __rename_extensions(list_files, list_extensions, list_renamed,
+ list_skipped, conflict_mode, extension_target):
+ """
+ Core method to rename the file extensions.
+ """
+ fs_case = common.get_fs_case_sensitivity(os.path.dirname(list_files[0]))
+
+ for file_path in list_files:
+ num = 1
+ list_path = file_path.split(os.path.sep)
+ file_name = list_path[-1]
+ file_ext = os.path.splitext(file_name)[1]
+
+ if file_ext == "":
+ list_skipped.append(file_path)
+ continue
+
+ file_newpath = file_path.replace(file_ext,
+ os.path.extsep + extension_target)
+ if file_path == file_newpath:
+ list_skipped.append(file_path)
+ continue
+
+ if conflict_mode == "rename":
+ while True:
+ if common.file_exists(file_newpath, list_renamed, fs_case):
+ if not fs_case:
+ if file_path.lower() == file_newpath.lower():
+ break
+ file_newpath = file_path.replace(file_ext, "_" +
+ str(num) +
+ os.path.extsep +
+ extension_target)
+ num += 1
+ else:
+ break
+ elif conflict_mode == "skip":
+ if common.file_exists(file_newpath, list_renamed, fs_case):
+ if not fs_case:
+ if not file_path.lower() == file_newpath.lower():
+ list_skipped.append(file_path)
+ continue
+ else:
+ list_skipped.append(file_path)
+ continue
+
+ if os.path.exists(file_path):
+ list_renamed.append([file_path, file_newpath + ".__temp__",
+ file_newpath])
+
+ return list_renamed, list_skipped
+
+# EOF
+
diff --git a/python3/core/fileren.py b/python3/core/fileren.py
new file mode 100755
index 0000000..14a6038
--- /dev/null
+++ b/python3/core/fileren.py
@@ -0,0 +1,838 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+# ============================================================================
+# Nomen - Multi-purpose rename tool
+# File renamer core module
+# Copyright (C) 2018 by Ralf Kilian
+# Distributed under the MIT License (https://opensource.org/licenses/MIT)
+#
+# Website: http://www.urbanware.org
+# GitHub: https://github.com/urbanware-org/nomen
+# ============================================================================
+
+__version__ = "2.3.5"
+
+from . import common
+import os
+from . import paval as pv
+import re
+from . import statcase
+
+from datetime import datetime as dt
+
+def convert_case(directory, case, conflict_mode, recursive=False,
+ cfg_lower=None, cfg_mixed=None, cfg_title=None,
+ cfg_upper=None, report_file=None, ignore_symlinks=False):
+ """
+ Convert the case of the base name of files.
+ """
+ pv.path(directory, "given", False, True)
+ pv.compstr(case, "case", ["lower", "title", "upper", "config"])
+ pv.compstr(conflict_mode, "conflict mode", ["rename", "skip"])
+
+ case = case.lower()
+ conflict_mode = conflict_mode.lower()
+ directory = os.path.abspath(directory)
+ if not directory.endswith(os.path.sep):
+ directory += os.path.sep
+
+ if report_file == None:
+ simulate = False
+ else:
+ pv.path(report_file, "report", True, False)
+ report_file = os.path.abspath(report_file)
+ simulate = True
+
+ time_start = dt.now()
+
+ list_content = []
+ list_excluded = []
+ list_renamed = []
+ list_skipped = []
+ regex = re.compile("(.*)")
+
+ __check_config_mismatch(case, cfg_lower, cfg_title, cfg_mixed, cfg_upper)
+ list_content, list_excluded = \
+ common.get_files(directory, recursive, True, regex, False,
+ ignore_symlinks)
+
+ for item in list_content:
+ list_files = item[1]
+ list_renamed, list_skipped = \
+ __convert_case(list_files, list_renamed, list_skipped, case,
+ conflict_mode, recursive, cfg_lower, cfg_mixed,
+ cfg_title, cfg_upper)
+
+ if simulate:
+ list_header = []
+ list_header.append("Nomen File Name Case Converter simulation report")
+ list_header.append(["Report file name:", report_file])
+ list_header.append(["Directory:", directory])
+ list_header.append(["Recursive:", recursive])
+ list_header.append(["Ignore symlinks:", ignore_symlinks])
+ list_header.append(["Conflict mode:", conflict_mode.capitalize()])
+ list_header.append(["Case:", case.capitalize()])
+ list_header.append(["Lowercase config:", cfg_lower])
+ list_header.append(["Mixed case config:", cfg_mixed])
+ list_header.append(["Title case config:", cfg_title])
+ list_header.append(["Uppercase config:", cfg_upper])
+
+ common.report(report_file, list_header, list_renamed, list_excluded,
+ list_skipped, time_start)
+ else:
+ common.rename(list_renamed)
+
+def get_version():
+ """
+ Return the version of this module.
+ """
+ return __version__
+
+def modify_names(directory, action, position, input_string,
+ replace_string=None, recursive=False, exclude=None,
+ pattern=None, ignore_case=True, regex_syntax=False,
+ report_file=None, ignore_symlinks=False, strip_chars=None):
+ """
+ Modify the base name of files by adding, removing or replacing a
+ user-defined string.
+ """
+ pv.path(directory, "given", False, True)
+ pv.compstr(action, "action", ["add", "remove", "replace"])
+ pv.compstr(position, "position", ["any", "prefix", "suffix"])
+ pv.string(input_string, "input string", False, common.get_invalid_chars())
+
+ action = action.lower()
+ position = position.lower()
+ directory = os.path.abspath(directory)
+ if not directory.endswith(os.path.sep):
+ directory += os.path.sep
+
+ if report_file == None:
+ simulate = False
+ else:
+ pv.path(report_file, "report", True, False)
+ report_file = os.path.abspath(report_file)
+ simulate = True
+
+ if not replace_string == None:
+ if not action == "replace":
+ raise Exception("The replace string argument can only be used " \
+ "together with the action 'replace'.")
+ else:
+ pv.string(replace_string, "string False", False,
+ common.get_invalid_chars())
+
+ if action == "add" and position == "any":
+ raise Exception("The position 'any' cannot be used together with " \
+ "the action 'add'.")
+
+ if len(input_string) == 0:
+ raise Exception("The input string must not be empty.")
+ else:
+ pv.string(input_string, "input string", False,
+ common.get_invalid_chars())
+
+ if not strip_chars == None:
+ pv.string(strip_chars, "strip chars string", False,
+ common.get_invalid_chars())
+
+ time_start = dt.now()
+
+ list_content = []
+ list_excluded = []
+ list_renamed = []
+ list_skipped = []
+ regex = None
+ if not pattern == None:
+ regex = common.compile_regex(pattern, ignore_case, regex_syntax)
+
+ list_content, list_excluded = \
+ common.get_files(directory, recursive, ignore_case, regex, exclude,
+ ignore_symlinks)
+ for item in list_content:
+ list_files = item[1]
+ __modify_names(list_files, list_renamed, list_skipped, action,
+ position, input_string, replace_string, strip_chars)
+
+ if simulate:
+ explicit = None
+ if exclude == None:
+ exclude = False
+ explicit = False
+ elif exclude:
+ explicit = False
+ else:
+ explicit = True
+
+ list_header = []
+ list_header.append("Nomen File Name Modifier simulation report")
+ list_header.append(["Report file name:", report_file])
+ list_header.append(["Directory:", directory])
+ list_header.append(["Recursive:", recursive])
+ list_header.append(["Ignore symlinks:", ignore_symlinks])
+ list_header.append(["Action to perform:", action.capitalize()])
+ list_header.append(["Position:", position.capitalize()])
+ list_header.append(["Input string:", "\"" + input_string + "\" " \
+ "(without double quotes)"])
+ if not replace_string == None:
+ list_header.append(["Replace string:", "\"" + replace_string + \
+ "\" (without double quotes)"])
+ if strip_chars == None:
+ list_header.append(["Strip chars:", "None"])
+ else:
+ list_header.append(["Strip chars:", "\"" + strip_chars + "\" " \
+ "(without double quotes)"])
+
+ list_header.append(["Exclude files:", exclude])
+ list_header.append(["Explicit files:", explicit])
+ list_header.append(["Pattern:", pattern])
+ list_header.append(["Ignore case:", ignore_case])
+ list_header.append(["Regex syntax:", regex_syntax])
+
+ common.report(report_file, list_header, list_renamed, list_excluded,
+ list_skipped, time_start)
+ else:
+ common.rename(list_renamed)
+
+def rename_files(directory, rename_mode, separator=" ", recursive=False,
+ padding=0, exclude=None, pattern=None, ignore_case=True,
+ regex_syntax=False, report_file=None, ignore_symlinks=False,
+ ignore_file_ext=False, custom_name=None, step=1,
+ order_by=None):
+ """
+ Rename the base name of files based on the name of the directory where
+ they are stored in and add a numeric ID.
+ """
+ pv.path(directory, "given", False, True)
+ pv.compstr(rename_mode, "rename mode",
+ ["fill-gaps", "increase", "keep-order", "rename-new"])
+ pv.intrange(padding, "padding", 0, 12, True)
+ pv.string(separator, "seperator", False, common.get_invalid_chars())
+ pv.intvalue(step, "step", True, False, False)
+
+ if not order_by == None:
+ pv.compstr(order_by, "order by", ["accessed", "created", "modified"])
+ if not rename_mode == "keep-order":
+ raise Exception("The order-by argument can only be used in " \
+ "combination with keep-order mode.")
+
+ step = int(step)
+ rename_mode = rename_mode.lower()
+ directory = os.path.abspath(directory)
+ if not directory.endswith(os.path.sep):
+ directory += os.path.sep
+
+ if report_file == None:
+ simulate = False
+ else:
+ pv.path(report_file, "report", True, False)
+ report_file = os.path.abspath(report_file)
+ simulate = True
+
+ if not custom_name == None:
+ pv.string(custom_name, "custom file name", False,
+ common.get_invalid_chars())
+
+ time_start = dt.now()
+
+ list_content = []
+ list_excluded = []
+ list_renamed = []
+ list_skipped = []
+ regex = None
+ if not pattern == None:
+ regex = common.compile_regex(pattern, ignore_case, regex_syntax)
+
+ list_content, list_excluded = \
+ common.get_files(directory, recursive, ignore_case, regex, exclude,
+ ignore_symlinks, order_by)
+
+ for item in list_content:
+ list_files = item[1]
+ if rename_mode == "fill-gaps":
+ list_renamed, list_skipped = \
+ __rename_files_fill(list_files, list_renamed, list_skipped,
+ separator, padding, True, ignore_file_ext,
+ custom_name, step)
+ elif rename_mode == "rename-new":
+ list_renamed, list_skipped = \
+ __rename_files_fill(list_files, list_renamed, list_skipped,
+ separator, padding, False,
+ ignore_file_ext, custom_name, step)
+ elif rename_mode == "keep-order":
+ list_renamed, list_skipped = \
+ __rename_files_keep_order(list_files, list_renamed,
+ list_skipped, separator, padding,
+ ignore_file_ext, custom_name, step,
+ order_by)
+ else:
+ raise Exception("An invalid rename mode was given.")
+
+ if simulate:
+ if padding == 0:
+ padding = "Set automatically"
+ else:
+ padding = str(padding)
+
+ explicit = None
+ if exclude == None:
+ exclude = False
+ explicit = False
+ elif exclude:
+ explicit = False
+ else:
+ explicit = True
+
+ if order_by == "accessed":
+ order_by = "Access time"
+ elif order_by == "created":
+ order_by = "Creation time"
+ elif order_by == "modified":
+ order_by = "Modification time"
+ else:
+ order_by = "False"
+
+ list_header = []
+ list_header.append("Nomen File Renamer simulation report")
+ list_header.append(["Report file name:", report_file])
+ list_header.append(["Directory:", directory])
+ list_header.append(["Recursive:", recursive])
+ list_header.append(["Ignore symlinks:", ignore_symlinks])
+ list_header.append(["Rename mode:", rename_mode.capitalize()])
+ list_header.append(["Order by time:", order_by])
+ list_header.append(["Separator:", "\"" + separator + "\" " \
+ "(without double quotes)"])
+ list_header.append(["Numeric padding:", padding])
+ list_header.append(["Step size:", step])
+ list_header.append(["Exclude files:", exclude])
+ list_header.append(["Explicit files:", explicit])
+ list_header.append(["Pattern:", pattern])
+ list_header.append(["Ignore case:", ignore_case])
+ list_header.append(["Regex syntax:", regex_syntax])
+
+ common.report(report_file, list_header, list_renamed, list_excluded,
+ list_skipped, time_start)
+ else:
+ common.rename(list_renamed)
+
+def __check_config_mismatch(case, cfg_lower, cfg_title, cfg_mixed, cfg_upper):
+ """
+ Check for a case config mismatch.
+ """
+ cfg_mismatch = False
+
+ if case == "lower":
+ if not cfg_lower == None:
+ cfg_mismatch = True
+ elif case == "title":
+ if not cfg_title == None:
+ cfg_mismatch = True
+ elif case == "upper":
+ if not cfg_upper == None:
+ cfg_mismatch = True
+ elif case == "config":
+ if cfg_lower == None and \
+ cfg_mixed == None and \
+ cfg_title == None and \
+ cfg_upper == None:
+ raise Exception("The config target case requires at least one " \
+ "case config file to operate.")
+ else:
+ raise Exception("An unsupported case string was given.")
+
+ if cfg_mismatch:
+ if case == "title":
+ case += " "
+ raise Exception("Config file mismatch (cannot use %scase config " \
+ "file when using %scase as target case anyway)." % \
+ (case, case))
+
+def __convert_case(list_files, list_renamed, list_skipped, case,
+ conflict_mode, recursive, cfg_lower, cfg_mixed, cfg_title,
+ cfg_upper):
+ """
+ Core method to convert the case of the base name of files.
+ """
+ file_ext = ""
+ list_lower = []
+ list_mixed = []
+ list_title = []
+ list_upper = []
+
+ fs_case = common.get_fs_case_sensitivity(os.path.dirname(list_files[0]))
+
+ if cfg_lower == None and cfg_mixed == None and \
+ cfg_title == None and cfg_upper == None:
+ static_case = False
+ else:
+ list_lower, list_mixed, list_title, list_upper = \
+ statcase.parse_case_configs(cfg_lower, cfg_mixed, cfg_title,
+ cfg_upper)
+ static_case = True
+
+ for file_path in list_files:
+ num = 1
+ list_path = file_path.split(os.path.sep)
+ file_name = list_path[-1]
+
+ if os.path.extsep in file_name:
+ file_ext = os.path.splitext(file_name)[1]
+ else:
+ file_ext = ""
+
+ base_name = os.path.splitext(file_name)[0]
+ if case == "lower":
+ base_name_target = base_name.lower()
+ elif case == "title":
+ base_name_target = base_name.title()
+ elif case == "upper":
+ base_name_target = base_name.upper()
+ else:
+ base_name_target = base_name
+
+ if static_case:
+ base_name_target = __static_case(base_name_target, case,
+ list_lower, list_mixed,
+ list_title,
+ list_upper).rstrip()
+
+ file_newpath = file_path.replace(base_name + file_ext,
+ base_name_target + file_ext)
+ if file_path == file_newpath:
+ list_skipped.append(file_path)
+ continue
+
+ if conflict_mode == "rename":
+ while True:
+ if common.file_exists(file_newpath, list_renamed, fs_case):
+ if not fs_case:
+ if file_path.lower() == file_newpath.lower():
+ break
+ file_newpath = \
+ file_path.replace(base_name,
+ base_name_target + "_" + str(num))
+ num += 1
+ else:
+ break
+ elif conflict_mode == "skip":
+ if common.file_exists(file_newpath, list_renamed, fs_case):
+ if not fs_case:
+ if not file_path.lower() == file_newpath.lower():
+ list_skipped.append(file_path)
+ continue
+ else:
+ list_skipped.append(file_path)
+ continue
+
+ if os.path.exists(file_path):
+ list_renamed.append([file_path, file_newpath + ".__temp__",
+ file_newpath])
+
+ return list_renamed, list_skipped
+
+def __fill_num_gaps(list_files, separator, padding, list_renamed,
+ list_skipped, fs_case, step):
+ """
+ Core method to fill numeration gaps.
+ """
+ list_temp = []
+ list_temp.extend(list_skipped)
+
+ for i in list_renamed:
+ list_temp.append(i[2])
+ list_temp.sort()
+ list_gaps = __get_num_gaps(list_files, separator, padding, step)
+
+ if len(list_gaps) > 0:
+ list_gaps.sort(reverse=True)
+ list_skipped.sort(reverse=True)
+
+ while len(list_gaps) > 0:
+ if len(list_skipped) < 1:
+ break
+
+ file_path = list_skipped.pop(0)
+ list_path = file_path.split(os.path.sep)
+ file_dir = list_path[-2]
+ file_name = list_path[-1]
+
+ if os.path.extsep in file_name:
+ file_ext = os.path.splitext(file_name)[1]
+ else:
+ file_ext = ""
+
+ num = list_gaps.pop(0)
+ file_num = str(num).rjust(int(padding), "0")
+ file_newname = file_dir + separator + \
+ file_num.replace(" ", "0") + file_ext
+ file_newpath = file_path.replace(file_name, file_newname)
+
+ if common.file_exists(file_newpath, list_renamed, fs_case):
+ list_skipped.append(file_path)
+ else:
+ list_renamed.append([file_path, None, file_newpath])
+
+ return list_renamed, list_skipped
+
+def __get_num_gaps(list_files, separator, padding, step):
+ """
+ Method to determine numeration gaps.
+ """
+ list_gaps = []
+
+ for i in range(len(list_files)):
+ x = (i + 1) * step
+ n = separator + str(x).rjust(int(padding),"0")
+ if not any(n in s for s in list_files):
+ list_gaps.append(int(n.replace(separator, "")))
+
+ return list_gaps
+
+def __modify_name_add(file_name, string, position):
+ """
+ Core method to add a string to the base name of a file.
+ """
+ file_newname = ""
+
+ if position == "prefix":
+ file_newname = string + file_name
+ elif position == "suffix":
+ file_newname = file_name + string
+
+ return file_newname
+
+def __modify_name_remove(file_name, string, position):
+ """
+ Core method to remove a string from the base name of a file.
+ """
+ file_newname = ""
+
+ if position == "any":
+ file_newname = file_name.replace(string, "")
+ elif position == "prefix":
+ file_newname = re.sub("^" + string, "", file_name)
+ elif position == "suffix":
+ file_newname = re.sub(string + "$", "", file_name)
+
+ return file_newname
+
+def __modify_name_replace(file_name, string, replace_string, position):
+ """
+ Core method to replace a string inside the base name of a file.
+ """
+ file_newname = ""
+
+ if position == "any":
+ file_newname = file_name.replace(string, replace_string)
+ elif position == "prefix":
+ file_newname = re.sub("^" + string, replace_string, file_name)
+ elif position == "suffix":
+ file_newname = re.sub(string + "$", replace_string, file_name)
+
+ return file_newname
+
+def __modify_names(list_files, list_renamed, list_skipped, action, position,
+ input_string, replace_string, strip_chars):
+ """
+ Core method to modify the base name of files by adding, removing or
+ replacing a user-defined string.
+ """
+ for file_path in list_files:
+ list_path = file_path.split(os.path.sep)
+ file_name = list_path[-1]
+ file_newname = ""
+ file_newpath = ""
+
+ if os.path.extsep in file_name:
+ file_temp = os.path.splitext(file_name)
+ file_name = file_temp[0]
+ file_ext = file_temp[1]
+ else:
+ file_ext = ""
+
+ if action == "add":
+ file_newname = __modify_name_add(file_name, input_string,
+ position)
+ elif action == "remove":
+ file_newname = __modify_name_remove(file_name, input_string,
+ position)
+ elif action == "replace":
+ file_newname = __modify_name_replace(file_name, input_string,
+ replace_string, position)
+
+ if not strip_chars == None:
+ if len(strip_chars) > 0:
+ file_newname = file_newname.strip(strip_chars)
+
+ file_newname += file_ext
+ file_newpath = file_path.replace(file_name + file_ext, file_newname)
+ if file_newpath == "":
+ list_skipped.append(file_path)
+ elif file_newname == "" or file_newname == file_ext:
+ list_skipped.append(file_path)
+ else:
+ if file_path == file_newpath:
+ list_skipped.append(file_path)
+ else:
+ if os.path.exists(file_newpath):
+ list_skipped.append(file_path)
+ else:
+ list_renamed.append([file_path, None, file_newpath])
+
+ return list_renamed, list_skipped
+
+def __process_case_list(case, case_list):
+ """
+ Process a case list and separate words from regular expressions.
+ """
+ list_strings = []
+ list_words = []
+
+ for item in case_list:
+ if case == "lower":
+ if item.startswith("$("):
+ list_strings.append(item.replace("$", "").lower())
+ else:
+ list_words.append(item.lower())
+ elif case == "title":
+ if item.startswith("$("):
+ list_strings.append(item.replace("$", "").title())
+ else:
+ list_words.append(item.title())
+ elif case == "upper":
+ if item.startswith("$("):
+ list_strings.append(item.replace("$", "").upper())
+ else:
+ list_words.append(item.upper())
+ else:
+ if item.startswith("$("):
+ list_strings.append(item.replace("$", ""))
+ else:
+ list_words.append(item)
+
+ return list_words, list_strings
+
+def __rename_files_fill(list_files, list_renamed, list_skipped, separator,
+ padding, fill_gaps=False, ignore_file_ext=False,
+ custom_name=None, step=1):
+ """
+ Core method to rename the base name of files based on the name of the
+ directory where they are stored in using one of the "fill" rename
+ modes (such as "fill-gaps" and "rename-new").
+ """
+ file_newpath = ""
+ num = 0
+
+ fs_case = common.get_fs_case_sensitivity(os.path.dirname(list_files[0]))
+
+ if fill_gaps:
+ list_temp_renamed = []
+ list_temp_skipped = []
+ obj_ren = list_temp_renamed
+ obj_skip = list_temp_skipped
+ else:
+ obj_ren = list_renamed
+ obj_skip = list_skipped
+
+ if padding == 0:
+ padding = len(str(len(list_files)))
+
+ for file_path in list_files:
+ list_path = file_path.split(os.path.sep)
+ file_name = list_path[-1]
+
+ if custom_name == None:
+ file_dir = list_path[-2]
+ else:
+ file_dir = custom_name
+
+ if os.path.extsep in file_name:
+ file_ext = os.path.splitext(file_name)[1]
+ else:
+ file_ext = ""
+
+ if file_name.startswith(file_dir + separator):
+ try:
+ temp = file_name.replace(file_dir + separator, "")
+ list_pad = temp.split(".")
+ file_padding = len(list_pad[0])
+
+ if step > 1:
+ if int(list_pad[0]) % step == 0:
+ obj_skip.append(file_path)
+ continue
+ else:
+ if int(padding) == file_padding:
+ obj_skip.append(file_path)
+ continue
+ except:
+ pass
+
+ if not ignore_file_ext:
+ num = 0
+
+ file_newpath = file_path
+ while common.file_exists(file_newpath, obj_ren, fs_case) or \
+ common.file_exists(file_newpath, obj_skip, fs_case):
+ num += step
+ file_num = str(num).rjust(int(padding), "0")
+ file_newname = \
+ file_dir + separator + file_num.replace(" ", "0") + file_ext
+ file_newpath = file_path.replace(file_name, file_newname)
+
+ if os.path.exists(file_path):
+ if file_path == file_newpath:
+ obj_skip.append(file_path)
+ else:
+ obj_ren.append([file_path, None, file_newpath])
+
+ if fill_gaps:
+ list_temp_renamed, list_temp_skipped = \
+ __fill_num_gaps(list_files, separator, padding,
+ list_temp_renamed, list_temp_skipped,
+ fs_case, step)
+ list_renamed.extend(list_temp_renamed)
+ list_skipped.extend(list_temp_skipped)
+
+ return list_renamed, list_skipped
+
+def __rename_files_keep_order(list_files, list_renamed, list_skipped,
+ separator, padding, ignore_file_ext=False,
+ custom_name=None, step=1, order_by=None):
+ """
+ Core method to rename the base name of files based on the name of the
+ directory where they are stored in using "keep-order" rename mode.
+ """
+ file_newpath = ""
+ file_temppath = ""
+ temp_file_ext = ""
+ list_new = []
+ list_ren = []
+ num = 0
+
+ fs_case = common.get_fs_case_sensitivity(os.path.dirname(list_files[0]))
+
+ if padding == 0:
+ padding = len(str(len(list_files)))
+
+ for file_path in list_files:
+ list_path = file_path.split(os.path.sep)
+ file_dir = list_path[-2]
+ file_name = list_path[-1]
+
+ if file_name.startswith(file_dir + separator):
+ list_ren.append(file_path)
+ else:
+ list_new.append(file_path)
+
+ list_files = []
+ list_files.extend(list_ren)
+ list_files.extend(list_new)
+
+ for file_path in list_files:
+ list_path = file_path.split(os.path.sep)
+ file_name = list_path[-1]
+
+ if custom_name == None:
+ file_dir = list_path[-2]
+ else:
+ file_dir = custom_name
+
+ if os.path.extsep in file_name:
+ file_ext = os.path.splitext(file_name)[1]
+ else:
+ file_ext = ""
+
+ if not ignore_file_ext:
+ if not file_ext == temp_file_ext:
+ num = 0
+
+ file_temppath = file_path
+ temp_file_ext = file_ext
+ while common.file_exists(file_temppath, list_renamed, fs_case):
+ num += step
+ file_num = str(num).rjust(int(padding), "0")
+ file_newname = \
+ file_dir + separator + file_num.replace(" ", "0") + file_ext
+ file_newpath = file_path.replace(file_name, file_newname)
+ if not file_newpath in list_skipped:
+ file_temppath = file_newpath + ".__temp__"
+
+ if os.path.exists(file_path):
+ if file_path == file_newpath:
+ list_skipped.append(file_path)
+ else:
+ list_renamed.append([file_path, file_temppath, file_newpath])
+
+ return list_renamed, list_skipped
+
+def __static_case(base_name, case, list_lower, list_mixed, list_title,
+ list_upper):
+ """
+ Convert the case of the base name of a file to lowercase, mixed case,
+ title case or uppercase based on the read out config files.
+ """
+ list_file_str = base_name.split(" ")
+ list_cfg_str, list_cfg_regex = \
+ __process_case_list("lower", list_lower)
+ base_name = __static_case_word(list_cfg_str, list_file_str)
+ base_name = __static_case_string(list_cfg_regex, base_name)
+
+ list_file_str = base_name.split(" ")
+ list_cfg_str, list_cfg_regex = \
+ __process_case_list("mixed", list_mixed)
+ base_name = __static_case_word(list_cfg_str, list_file_str)
+ base_name = __static_case_string(list_cfg_regex, base_name)
+
+ list_file_str = base_name.split(" ")
+ list_cfg_str, list_cfg_regex = \
+ __process_case_list("title", list_title)
+ base_name = __static_case_word(list_cfg_str, list_file_str)
+ base_name = __static_case_string(list_cfg_regex, base_name)
+
+ list_file_str = base_name.split(" ")
+ list_cfg_str, list_cfg_regex = \
+ __process_case_list("upper", list_upper)
+ base_name = __static_case_word(list_cfg_str, list_file_str)
+ base_name = __static_case_string(list_cfg_regex, base_name)
+
+ return base_name
+
+def __static_case_word(list_cfg_str, list_file_str):
+ """
+ Convert the case of each word of the base name of a file.
+ """
+ base_name = ""
+
+ for file_str in list_file_str:
+ for cfg_str in list_cfg_str:
+ spec_chars = "\\?*+$.:; ^|()[]{}"
+ for char in spec_chars:
+ cfg_str = cfg_str.replace(char, "")
+
+ if file_str.lower() == cfg_str.lower():
+ file_str = cfg_str
+
+ base_name += (file_str + " ")
+
+ return base_name
+
+def __static_case_string(list_cfg_regex, base_name):
+ """
+ Convert the case of a string inside the base name of a file.
+ """
+ for item in list_cfg_regex:
+ spec_chars = "\\?*+$.:;^|()[]{}"
+ for char in spec_chars:
+ item = item.replace(char, "")
+
+ regex = re.compile(".*" + item.lower() + ".*")
+ if regex.match(base_name.lower()):
+ base_name = re.sub(item.lower(), item, base_name,
+ flags=re.IGNORECASE)
+
+ return base_name
+
+# EOF
+
diff --git a/python3/core/paval.py b/python3/core/paval.py
new file mode 100755
index 0000000..aba44a8
--- /dev/null
+++ b/python3/core/paval.py
@@ -0,0 +1,203 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+# ============================================================================
+# PaVal - Parameter validation module
+# Copyright (C) 2018 by Ralf Kilian
+# Distributed under the MIT License (https://opensource.org/licenses/MIT)
+#
+# Website: http://www.urbanware.org
+# GitHub: https://github.com/urbanware-org/paval
+# ============================================================================
+
+__version__ = "1.2.7"
+
+import filecmp
+import os
+
+
+def compfile(input_path, name="", list_files=None):
+ """
+ Compare files to avoid that the same file is given multiple times or
+ in different ways (e. g. different name but same content).
+ """
+ __string(input_path, "%s path" % name, True)
+
+ if list_files is None:
+ list_files = []
+ elif not list_files:
+ __ex("File list is empty (no files to compare with).", True)
+ else:
+ for item in list_files:
+ if not isinstance(item, list):
+ __ex("Every list item must be a sub-list.", True)
+ if not len(item) == 2:
+ __ex("Every sub-list must contain two items.", True)
+
+ input_path = os.path.abspath(input_path)
+ for item in list_files:
+ path_compare = os.path.abspath(str(item[0]))
+ name_compare = str(item[1])
+ if input_path == path_compare:
+ __ex("The %s and the %s file path must not be identical." %
+ (name, name_compare), False)
+ if os.path.exists(input_path) and os.path.exists(path_compare):
+ if filecmp.cmp(input_path, path_compare, 0):
+ __ex("The %s and %s file content must not be identical." %
+ (name, name_compare), False)
+
+
+def compstr(input_string, name="", list_strings=None):
+ """
+ Compare a string with a list of strings and check if it is an item of
+ that list.
+ """
+ __string(input_string, name, False)
+ if not list_strings:
+ __ex("No %s strings to compare with." % name, True)
+ if input_string not in list_strings:
+ __ex("The %s '%s' does not exist." % (name, input_string), False)
+
+
+def get_version():
+ """
+ Return the version of this module.
+ """
+ return __version__
+
+
+def intrange(value, name="", value_min=None, value_max=None, zero=False):
+ """
+ Validate an integer range.
+ """
+ value = __integer(value, "%s value" % name, False)
+ if value_min is not None:
+ value_min = __integer(value_min, "minimal %s value" % name, True)
+ intvalue(value_min, name, True, True, True)
+ if value_max is not None:
+ value_max = __integer(value_max, "maximal %s value" % name, True)
+ intvalue(value_max, name, True, True, True)
+ if not zero:
+ if value == 0:
+ __ex("The %s value must not be zero." % name, False)
+ if (value_min is not None) and (value_max is not None):
+ if value_min > value_max:
+ __ex("The maximal %s value must be greater than the minimal "
+ "value." % name, False)
+ if (value_min == value_max) and (value != value_min):
+ __ex("The %s value can only be %s (depending on further range "
+ "further range arguments)." % (name, value_min), False)
+ if (value < value_min) or (value > value_max):
+ __ex("The %s value must be between %s and %s (depending on "
+ "further range arguments)." % (name, value_min, value_max),
+ False)
+ elif value_min is not None:
+ if value < value_min:
+ __ex("The %s value must not be less than %s." % (name, value_min),
+ False)
+ elif value_max is not None:
+ if value > value_max:
+ __ex("The %s value must not be greater than %s." %
+ (name, value_max), False)
+
+
+def intvalue(value, name="", positive=True, zero=False, negative=False):
+ """
+ Validate a single integer value.
+ """
+ value = __integer(value, "%s value" % name, False)
+ if not positive:
+ if value > 0:
+ __ex("The %s value must not be positive." % name, False)
+ if not zero:
+ if value == 0:
+ __ex("The %s value must not be zero." % name, False)
+ if not negative:
+ if value < 0:
+ __ex("The %s value must not be negative." % name, False)
+
+
+def path(input_path, name="", is_file=False, exists=False):
+ """
+ Validate a path of a file or directory.
+ """
+ string(input_path, "%s path" % name, False, None)
+ input_path = os.path.abspath(input_path)
+
+ if is_file:
+ path_type = "file"
+ else:
+ path_type = "directory"
+ if exists:
+ if not os.path.exists(input_path):
+ __ex("The given %s %s does not exist." % (name, path_type), False)
+ if (is_file and not os.path.isfile(input_path)) or \
+ (not is_file and not os.path.isdir(input_path)):
+ __ex("The given %s %s path is not a %s." % (name, path_type,
+ path_type), False)
+ else:
+ if os.path.exists(input_path):
+ __ex("The given %s %s path already exists." % (name, path_type),
+ False)
+
+
+def string(input_string, name="", wildcards=False, invalid_chars=None):
+ """
+ Validate a string.
+ """
+ __string(input_string, name, False)
+ if invalid_chars is None:
+ invalid_chars = ""
+ if not wildcards:
+ if ("*" in input_string) or ("?" in input_string):
+ __ex("The %s must not contain wildcards." % name, False)
+ if invalid_chars:
+ for char in invalid_chars:
+ if char in input_string:
+ # Use single quotes by default or double quotes in case the
+ # single quotes are the invalid character
+ quotes = "'"
+ if char == quotes:
+ quotes = "\""
+
+ __ex("The %s contains at least one invalid character "
+ "(%s%s%s)." % (name, quotes, char, quotes), False)
+
+
+def __ex(exception_string, internal=False):
+ """
+ Internal method to raise an exception.
+ """
+ ex = str(exception_string).strip()
+ while " " * 2 in ex:
+ ex = ex.replace((" " * 2), " ")
+ if internal:
+ ex = "PaVal: " + ex
+ raise Exception(ex)
+
+
+def __integer(value, name="", internal=False):
+ """
+ Internal method for basic integer validation.
+ """
+ if value is None:
+ __ex("The %s is missing." % name, internal)
+ if value == "":
+ __ex("The %s must not be empty." % name, internal)
+ try:
+ value = int(value)
+ except ValueError:
+ __ex("The %s must be an integer." % name, internal)
+ return int(value)
+
+
+def __string(input_string, name="", internal=False):
+ """
+ Internal method for basic string validation.
+ """
+ if input_string is None:
+ __ex("The %s is missing." % name, internal)
+ if input_string == "":
+ __ex("The %s must not be empty." % name, internal)
+
+# EOF
diff --git a/python3/core/statcase.py b/python3/core/statcase.py
new file mode 100755
index 0000000..551112d
--- /dev/null
+++ b/python3/core/statcase.py
@@ -0,0 +1,118 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+# ============================================================================
+# Nomen - Multi-purpose rename tool
+# Static case core module
+# Copyright (C) 2018 by Ralf Kilian
+# Distributed under the MIT License (https://opensource.org/licenses/MIT)
+#
+# Website: http://www.urbanware.org
+# GitHub: https://github.com/urbanware-org/nomen
+# ============================================================================
+
+__version__ = "2.3.5"
+
+import os
+from . import paval as pv
+import sys
+
+def get_version():
+ """
+ Return the version of this module.
+ """
+ return __version__
+
+def parse_case_configs(cfg_lower=None, cfg_mixed=None, cfg_title=None,
+ cfg_upper=None):
+ """
+ Parse the configuration files for static file name case
+ conversion.
+ """
+ list_lower = []
+ list_mixed = []
+ list_title = []
+ list_upper = []
+
+ if not cfg_lower == None:
+ list_lower = __read_config(__config_abspath(cfg_lower, "lowercase"))
+ if not cfg_mixed == None:
+ list_mixed = __read_config(__config_abspath(cfg_mixed, "mixed case"))
+ if not cfg_title == None:
+ list_title = __read_config(__config_abspath(cfg_title, "title case"))
+ if not cfg_upper == None:
+ list_upper = __read_config(__config_abspath(cfg_upper, "uppercase"))
+
+ __check_dupes(list_lower, list_mixed, list_title, list_upper)
+
+ return list_lower, list_mixed, list_title, list_upper
+
+def __check_dupes(list_lower, list_mixed, list_title, list_upper):
+ """
+ Check for duplicate case list entries.
+ """
+ list_diff = []
+
+ list_diff = __process_case_list(list_lower, list_diff)
+ list_diff = __process_case_list(list_mixed, list_diff)
+ list_diff = __process_case_list(list_title, list_diff)
+ __process_case_list(list_upper, list_diff)
+
+def __config_abspath(config, description):
+ """
+ Get the absolute path of a config file.
+ """
+ description += " config"
+ try:
+ pv.path(config, description, True, True)
+ except:
+ config_dir = os.path.abspath(os.path.dirname(sys.argv[0]))
+ config = os.path.join(config_dir, "cfg", os.path.basename(config))
+ pv.path(config, description, True, True)
+ config = os.path.abspath(config)
+
+ return config
+
+def __process_case_list(list_input, list_output):
+ """
+ Process a case list and raise an exception in case of duplicate
+ entries.
+ """
+ duplicate = ""
+
+ for item in list_input:
+ if item == None or item == "":
+ continue
+ elif item.lower() in list_output:
+ duplicate = item
+ break
+ else:
+ list_output.append(item.lower())
+
+ if not duplicate == "":
+ raise Exception("Duplicate config file entries. The same string " \
+ "must not exist in multiple config files. The " \
+ "duplicate string was \"%s\" (without the " \
+ "enclosing quotes)." % duplicate)
+
+ return list_output
+
+def __read_config(config_file):
+ """
+ Read out the contents of a config file.
+ """
+ list_config = []
+
+ fh_config = open(config_file, "r")
+ for line in fh_config:
+ line = line.strip()
+ if line == "" or line.startswith("#"):
+ continue
+ list_config.append(line.replace("\n", ""))
+ fh_config.close()
+ list_config.sort
+
+ return list_config
+
+# EOF
+
diff --git a/python3/docs/usage_nomen-dirspace.txt b/python3/docs/usage_nomen-dirspace.txt
new file mode 100644
index 0000000..a91c061
--- /dev/null
+++ b/python3/docs/usage_nomen-dirspace.txt
@@ -0,0 +1,195 @@
+
+USAGE (nomen-dirspace.py)
+
+ Contents:
+
+ 1. Definition
+ 2. General stuff
+ 2.1 How to run Python scripts
+ 2.2 Overview of all command-line arguments
+ 2.3 Warning
+ 2.4 Simulation mode
+ 2.5 Identical directory names
+ 3. Remove spaces from directory names
+ 3.1 Duplicate spaces
+ 3.2 Leading spaces
+ 3.3 Trailing spaces
+ 3.4 Duplicate, leading as well as trailing spaces
+ 4. Spaces next to certain characters
+ 4.1 Brackets
+ 4.2 Hyphens
+ 4.3 Punctuation characters
+
+ 1. Definition
+
+ The Nomen Directory Name Space Modifier (former "Nomen Directory Name
+ Space Remover") script modifies a directory name by removing leading,
+ trailing and duplicate spaces or by inserting and removing spaces next
+ to punctuation characters.
+
+ 2. General stuff
+
+ 2.1 How to run Python scripts
+
+ All usage examples below show how to execute the Python scripts on
+ the shell of a Unix-like system. If you do not know, how to run
+ those scripts on your operating system, you may have a look at
+ this page:
+
+ http://www.urbanware.org/howto_python.html
+
+ 2.2 Overview of all command-line arguments
+
+ Usually, each script requires command-line arguments to operate.
+ So, to get an overview of all arguments available, simply run the
+ script with the "--help" argument. For example:
+
+ $ ./nomen-dirspace.py --help
+
+ 2.3 Warning
+
+ Please use this tool with care to avoid data damage or loss!
+
+ As a matter of fact, there is no function to undo the changes done
+ by this tool, so you should be aware of what you are doing. If
+ not, do NOT use this tool.
+
+ Improper use (e. g. modifying system directories) will corrupt
+ your system!
+
+ 2.4 Simulation mode
+
+ Due to the fact that this tool simply removes unnecessary spaces
+ in directory names, it does not provide any simulation mode.
+
+ 2.5 Identical directory names
+
+ For example, if you have the directories
+
+ /tmp/pics/My Holiday/
+ /tmp/pics/My Holiday/
+
+ and want to remove the duplicate spaces, both directories would
+ have the same name after performing the space removal operation.
+ Due to this, they would be combined and the data inside of them
+ would get mixed up.
+
+ To avoid this, the Nomen Directory Name Space Remover script does
+ not remove the spaces from a directory if another directory with
+ the new name already exists. The directory will simply be ignored,
+ but the script will process its sub-directories if the recursive
+ command-line argument was given.
+
+ 3. Remove spaces from directory names
+
+ The following operations can also be combined using the corresponding
+ command-line arguments (as described in section 3.4).
+
+ 3.1 Duplicate spaces
+
+ For example, if you have the following directory
+
+ /tmp/pics/My Holiday/
+
+ and you want to remove the duplicate spaces, type:
+
+ ./nomen-dirspace.py -d /tmp/pics/ -s
+
+ 3.2 Leading spaces
+
+ For example, if you have the following directory
+
+ /tmp/pics/ My Holiday/
+
+ and you want to remove the leading spaces, type:
+
+ ./nomen-dirspace.py -d /tmp/pics/ -l
+
+ 3.3 Trailing spaces
+
+ For example, if you have the following directory
+
+ /tmp/pics/My Holiday /
+
+ and you want to remove the trailing spaces, type:
+
+ ./nomen-dirspace.py -d /tmp/pics/ -t
+
+ 3.4 Duplicate, leading as well as trailing spaces
+
+ For example, if you have the following directory
+
+ /tmp/pics/ My Holiday /
+
+ and you want to remove the duplicate, leading and trailing spaces,
+ type:
+
+ ./nomen-dirspace.py -d /tmp/pics/ -s -l -t
+
+ 4. Spaces next to certain characters
+
+ The Nomen Directory Name Space Modifier also allows inserting and
+ removing spaces next to certain characters such as brackets, dashes
+ and punctuation characters. These are automated processes without any
+ further options.
+
+ The following command-line arguments can be combined with those
+ mentioned in section 3 as well as with each other.
+
+ Please notice that inserting spaces may result in duplicate spaces
+ again. So, when using the following command-line arguments it is
+ recommended to also add the "-s" (or "--remove-duplicate") argument
+ (as shown in the examples below).
+
+ 4.1 Brackets
+
+ The "-b" (or "--brackets") command-line argument will remove all
+ unnecessary spaces around brackets. For example, if you have a
+ directory like this
+
+ /tmp/music/ Foo( Bar [ Album ] ) /
+
+ and run Nomen Directory Name Space Modifier with the corresponding
+ argument
+
+ ./nomen-dirspace.py -d /tmp/music/ -s -b
+
+ the directory will be changed as follows:
+
+ /tmp/music/Foo (Bar [Album])/
+
+ 4.2 Hyphens
+
+ The --hyphens" command-line argument will add additional spaces
+ around hyphens ("-").
+
+ /tmp/music/ John Doe -Foo /
+
+ and run Nomen Directory Name Space Modifier with the corresponding
+ argument
+
+ ./nomen-dirspace.py -d /tmp/music/ -s -p
+
+ the directory will be changed as follows:
+
+ /tmp/music/John Doe - Foo/
+
+ 4.3 Punctuation characters
+
+ The "-p" (or "--punctuation") command-line argument will remove
+ all unnecessary spaces around the punctuation characters such as
+ dots ("."), commas (","), colons (":"), semi-colons (";") as well
+ as exclamation ("!") and question marks ("?"). Furthermore, it
+ adds spaces next to them when necessary.
+
+ /tmp/music/ Foo,Bar feat.John Doe - Foo ! /
+
+ and run Nomen Directory Name Space Modifier with the corresponding
+ argument
+
+ ./nomen-dirspace.py -d /tmp/music/ -s -p
+
+ the directory will be changed as follows:
+
+ /tmp/music/Foo, Bar feat. John Doe - Foo!/
+
diff --git a/python3/docs/usage_nomen-extcase.txt b/python3/docs/usage_nomen-extcase.txt
new file mode 100644
index 0000000..f75c612
--- /dev/null
+++ b/python3/docs/usage_nomen-extcase.txt
@@ -0,0 +1,126 @@
+
+USAGE (nomen-extcase.py)
+
+ Contents:
+
+ 1. Definition
+ 2. General stuff
+ 2.1 How to run Python scripts
+ 2.2 Overview of all command-line arguments
+ 2.3 Warning
+ 2.4 Simulation mode
+ 3. Convert the case of file extensions
+ 3.1 Rename mode
+ 3.2 Skip mode
+
+ 1. Definition
+
+ The Nomen Extension Case Converter script converts the case of the
+ extension of all files inside a directory and (if requested) in all of
+ its sub-directories.
+
+ 2. General stuff
+
+ 2.1 How to run Python scripts
+
+ All usage examples below show how to execute the Python scripts on
+ the shell of a Unix-like system. If you do not know, how to run
+ those scripts on your operating system, you may have a look at
+ this page:
+
+ http://www.urbanware.org/howto_python.html
+
+ 2.2 Overview of all command-line arguments
+
+ Usually, each script requires command-line arguments to operate.
+ So, to get an overview of all arguments available, simply run the
+ script with the "--help" argument. For example:
+
+ $ ./nomen-extcase.py --help
+
+ 2.3 Warning
+
+ Please use this tool with care to avoid data damage or loss!
+
+ It is strongly recommended to simulate every rename process first
+ to check which files would have been renamed. As a matter of fact,
+ there is no function to undo the changes done by this tool, so you
+ should be aware of what you are doing. If not, do NOT use this
+ tool.
+
+ Improper use (e. g. modifying files inside system directories)
+ will corrupt your system!
+
+ 2.4 Simulation mode
+
+ As already mentioned above, before renaming any files, you should
+ simulate the rename process by using the "--simulate" argument.
+
+ This argument requires a file path where the report of the rename
+ process will be written to. When in simulation mode, no files will
+ be renamed at all.
+
+ So, if you have a command line like this
+
+ ./nomen-extcase.py -m rename -c lower -d /tmp/pics/Holidays
+
+ but want to simulate the rename process and write the report into
+ the file "/tmp/report.txt", simply add the argument
+
+ --simulate /tmp/report.txt
+
+ to the command line:
+
+ ./nomen-extcase.py -m rename -c lower -d /tmp/pics/Holidays \
+ --simulate /tmp/report.txt
+
+ 3. Convert the case of file extensions
+
+ For example, you have two picture files inside a directory called
+ "Holidays" with these extensions
+
+ Holidays 1.JPG
+ Holidays 2.Jpg
+
+ and you convert the extension case to lowercase, the file extensions
+ will look like this:
+
+ Holidays 1.jpg
+ Holidays 2.jpg
+
+ But, for example, if file system is case senstive and the directory
+ contains the files
+
+ Holidays 1.JPG
+ Holidays 1.Jpg
+
+ there would be a problem converting the file extension case, because
+ both target file names would be identical. So, to avoid that, the
+ script also offers two operating modes.
+
+ 3.1 Rename mode
+
+ The "rename mode" adds a numeric ID to the duplicate target file
+ and converts the file extension:
+
+ Holidays 1.JPG will be renamed to "Holidays 1.jpg"
+ Holidays 1.Jpg will be renamed to "Holidays 1_1.jpg"
+
+ To use this mode and convert the file extensions to lowercase,
+ type:
+
+ ./nomen-extcase.py -m rename -c lower -d /tmp/pics/Holidays
+
+ 3.2 Skip mode
+
+ The "skip mode" simply skips the files where converting the case
+ of the file extension would result in duplicate file names:
+
+ Holidays 1.JPG will be renamed to "Holidays 1.jpg"
+ Holidays 1.Jpg will be skipped
+
+ To use this mode and convert the file extensions to lowercase,
+ type:
+
+ ./nomen-extcase.py -m skip -c lower -d /tmp/pics/Holidays
+
diff --git a/python3/docs/usage_nomen-extren.txt b/python3/docs/usage_nomen-extren.txt
new file mode 100644
index 0000000..33719ea
--- /dev/null
+++ b/python3/docs/usage_nomen-extren.txt
@@ -0,0 +1,134 @@
+
+USAGE (nomen-extren.py)
+
+ Contents:
+
+ 1. Definition
+ 2. General stuff
+ 2.1 How to run Python scripts
+ 2.2 Overview of all command-line arguments
+ 2.3 Warning
+ 2.4 Simulation mode
+ 3. Rename file extensions
+ 3.1 Rename mode
+ 3.2 Skip mode
+
+ 1. Definition
+
+ The Nomen Extension Renamer script renames (and adjusts) differently
+ spelled file extensions of the same file type file within a directory
+ and (if requested) in all of its sub-directories.
+
+ 2. General stuff
+
+ 2.1 How to run Python scripts
+
+ All usage examples below show how to execute the Python scripts on
+ the shell of a Unix-like system. If you do not know, how to run
+ those scripts on your operating system, you may have a look at
+ this page:
+
+ http://www.urbanware.org/howto_python.html
+
+ 2.2 Overview of all command-line arguments
+
+ Usually, each script requires command-line arguments to operate.
+ So, to get an overview of all arguments available, simply run the
+ script with the "--help" argument. For example:
+
+ $ ./nomen-extren.py --help
+
+ 2.3 Warning
+
+ Please use this tool with care to avoid data damage or loss!
+
+ It is strongly recommended to simulate every rename process first
+ to check which files would have been renamed. As a matter of fact,
+ there is no function to undo the changes done by this tool, so you
+ should be aware of what you are doing. If not, do NOT use this
+ tool.
+
+ Improper use (e. g. modifying files inside system directories)
+ will corrupt your system!
+
+ 2.4 Simulation mode
+
+ As already mentioned above, before renaming any files, you should
+ simulate the rename process by using the "--simulate" argument.
+
+ This argument requires a file path where the report of the rename
+ process will be written to. When in simulation mode, no files will
+ be renamed at all.
+
+ So, if you have a command line like this
+
+ ./nomen-extren.py -m rename -d /tmp/pics/Holidays \
+ -e "JPG;JPEG" -t "jpg"
+
+ but want to simulate the rename process and write the report into
+ the file "/tmp/report.txt", simply add the argument
+
+ --simulate /tmp/report.txt
+
+ to the command line:
+
+ ./nomen-extren.py -m rename -d /tmp/pics/Holidays \
+ -e "JPG;JPEG" -t "jpg" \
+ --simulate /tmp/report.txt
+
+ 3. Rename file extensions
+
+ For example, you have four picture files inside a directory called
+ "Holidays" with these extensions:
+
+ Holidays 1.jpg
+ Holidays 2.jpeg
+ Holidays 3.JPG
+ Holidays 4.Jpeg
+
+ All of these files are JPEG pictures, but their extensions are spelled
+ differently. So, the Nomen Extension Renamer script can be used to get
+ consistent file extensions:
+
+ Holidays 1.jpg
+ Holidays 2.jpg
+ Holidays 3.jpg
+ Holidays 4.jpg
+
+ But, for example, if file system is case senstive and the directory
+ contains the files
+
+ Holidays 1.Jpg
+ Holidays 1.jpeg
+
+ there would be a problem renaming the file extension, because both
+ file target names would be identical. So, to avoid that, the script
+ also offers two operating modes.
+
+ 3.1 Rename mode
+
+ The "rename mode" adds a numeric ID to the duplicate target file
+ and converts the file:
+
+ Holidays 1.Jpg will be renamed to "Holidays 1.jpg"
+ Holidays 1.jpeg will be renamed to "Holidays 1_1.jpg"
+
+ To use this mode and rename the extensions of JPEG files,
+ type:
+
+ ./nomen-extren.py -m rename -d /tmp/pics/Holidays \
+ -e "JPG;JPEG" -t "jpg"
+
+ 3.2 Skip mode
+
+ The "skip mode" simply skips the files where converting the case
+ of the file extension would result in duplicate file names:
+
+ Holidays 1.Jpg will be renamed to "Holidays 1.jpg"
+ Holidays 1.jpeg will be skipped
+
+ To use this mode and rename the extensions of JPEG files, type:
+
+ ./nomen-extren.py -m skip -d /tmp/pics/Holidays \
+ -e "JPG;JPEG" -t "jpg"
+
diff --git a/python3/docs/usage_nomen-filecase.txt b/python3/docs/usage_nomen-filecase.txt
new file mode 100644
index 0000000..2af0fdc
--- /dev/null
+++ b/python3/docs/usage_nomen-filecase.txt
@@ -0,0 +1,186 @@
+
+USAGE (nomen-filecase.py)
+
+ Contents:
+
+ 1. Definition
+ 2. General stuff
+ 2.1 How to run Python scripts
+ 2.2 Overview of all command-line arguments
+ 2.3 Warning
+ 2.4 Simulation mode
+ 3. Convert the case of base names
+ 3.1 Rename mode
+ 3.2 Skip mode
+ 4. Using config files
+ 4.1 With a certain target case
+ 4.2 Without a certain target case
+
+ 1. Definition
+
+ The Nomen File Name Case Converter script converts the case of the
+ base name of all files inside a directory and (if requested) in all of
+ its sub-directories.
+
+ 2. General stuff
+
+ 2.1 How to run Python scripts
+
+ All usage examples below show how to execute the Python scripts on
+ the shell of a Unix-like system. If you do not know, how to run
+ those scripts on your operating system, you may have a look at
+ this page:
+
+ http://www.urbanware.org/howto_python.html
+
+ 2.2 Overview of all command-line arguments
+
+ Usually, each script requires command-line arguments to operate.
+ So, to get an overview of all arguments available, simply run the
+ script with the "--help" argument. For example:
+
+ $ ./nomen-filecase.py --help
+
+ 2.3 Warning
+
+ Please use this tool with care to avoid data damage or loss!
+
+ It is strongly recommended to simulate every rename process first
+ to check which files would have been renamed. As a matter of fact,
+ there is no function to undo the changes done by this tool, so you
+ should be aware of what you are doing. If not, do NOT use this
+ tool.
+
+ Improper use (e. g. modifying files inside system directories)
+ will corrupt your system!
+
+ 2.4 Simulation mode
+
+ As already mentioned above, before renaming any files, you should
+ simulate the rename process by using the "--simulate" argument.
+
+ This argument requires a file path where the report of the rename
+ process will be written to. When in simulation mode, no files will
+ be renamed at all.
+
+ So, if you have a command line like this
+
+ ./nomen-filecase.py -m rename -c title -d /tmp/pics/Holidays
+
+ but want to simulate the rename process and write the report into
+ the file "/tmp/report.txt", simply add the argument
+
+ --simulate /tmp/report.txt
+
+ to the command line:
+
+ ./nomen-filecase.py -m rename -c title -d /tmp/pics/Holidays \
+ --simulate /tmp/report.txt
+
+ 3. Convert the case of base names
+
+ For example, you have a picture file with this file name
+
+ my favorite holiday.jpg
+
+ and you convert the case of the base name to title case, the file name
+ will look like this:
+
+ My Favorite Holiday.jpg
+
+ But, for example, if file system is case senstive and the directory
+ contains the files
+
+ HOLIDAY.jpg
+ holiday.jpg
+
+ there would be a problem converting the case of the base name, because
+ both target file names would be identical. So, to avoid that, the
+ script also offers two operating modes.
+
+ 3.1 Rename mode
+
+ The "rename mode" adds a numeric ID to the base name of the
+ duplicate target file and converts the case:
+
+ HOLIDAY.jpg will be renamed to "Holiday.jpg"
+ holiday.jpg will be renamed to "Holiday_1.jpg"
+
+ To use this mode and convert the base name of the files to title
+ case, type:
+
+ ./nomen-filecase.py -m rename -c title -d /tmp/pics/Holidays
+
+ 3.2 Skip mode
+
+ The "skip mode" simply skips the files where converting the case
+ of the base name would result in duplicate file names:
+
+ HOLIDAY.jpg will be renamed to "Holiday.jpg"
+ holiday.jpg will be skipped
+
+ To use this mode and convert the base name of the files to title
+ case, type:
+
+ ./nomen-filecase.py -m skip -c title -d /tmp/pics/Holidays
+
+ 4. Using config files
+
+ The Nomen File Name Case Converter script also comes with the feature
+ that allows using config files to convert the case of certain strings
+ always to lowercase, title case, uppercase or a user-defined mixed
+ case, nomatter which target case (using the "-c" or "--case" argument)
+ was given.
+
+ For details see the included sample config files which can be found
+ inside the "cfg" sub-directory.
+
+ If the sample config file is missing for some reason, you may download
+ the project from the website:
+
+ http://www.urbanware.org/nomen.html
+
+ To use these config files, the corresponding command-line argument
+ followed by the full path to the config file is required, depending on
+ which of the config files you want to use.
+
+ 4.1 With a certain target case
+
+ For example, if you want to use title case as target case as well
+ as the lowercase config file named "lower.cfg" which is located
+ inside the directory "/tmp", the command line would look like
+ this:
+
+ ./nomen-filecase.py -m skip -c title -d /tmp/pics/Holidays \
+ --cfg-lower /tmp/lower.cfg
+
+ To additionally use the uppercase config file "upper.cfg" which
+ is also located inside the "/tmp" directory, simply add the
+ appropriate argument:
+
+ ./nomen-filecase.py -m skip -c title -d /tmp/pics/Holidays \
+ --cfg-lower /tmp/lower.cfg \
+ --cfg-upper /tmp/upper.cfg
+
+ Same with the mixed case config:
+
+ ./nomen-filecase.py -m skip -c title -d /tmp/pics/Holidays \
+ --cfg-lower /tmp/lower.cfg \
+ --cfg-mixed /tmp/mixed.cfg \
+ --cfg-upper /tmp/upper.cfg
+
+ However, you cannot use the title case config file when using
+ title case as target case anyway.
+
+ 4.2 Without a certain target case
+
+ If you just want to convert the case of the certain strings stored
+ inside the case config files, you can do this by giving "config"
+ as the target case:
+
+ ./nomen-filecase.py -m skip -c config -d /tmp/pics/Holidays \
+ --cfg-lower /tmp/lower.cfg \
+ --cfg-mixed /tmp/mixed.cfg \
+ --cfg-title /tmp/title.cfg \
+ --cfg-upper /tmp/upper.cfg
+
diff --git a/python3/docs/usage_nomen-filemod.txt b/python3/docs/usage_nomen-filemod.txt
new file mode 100644
index 0000000..5de39cd
--- /dev/null
+++ b/python3/docs/usage_nomen-filemod.txt
@@ -0,0 +1,376 @@
+
+USAGE (nomen-filemod.py)
+
+ Contents:
+
+ 1. Definition
+ 2. General stuff
+ 2.1 How to run Python scripts
+ 2.2 Overview of all command-line arguments
+ 2.3 Warning
+ 2.4 Simulation mode
+ 2.5 Prefixes and suffixes
+ 3. Add a user-defined string to file names
+ 3.1 Add a prefix
+ 3.2 Add a suffix
+ 4. Remove a user-defined string from file names
+ 4.1 Remove a prefix
+ 4.2 Remove a suffix
+ 4.3 Remove a string
+ 5. Replace a user-defined string inside file names
+ 5.1 Replace a prefix
+ 5.2 Replace a suffix
+ 5.3 Replace a string
+ 6. Strip file names
+ 7. Exclude certain files
+ 8. Rename certain files only
+
+ 1. Definition
+
+ The Nomen File Name Modifier script adds, removes or replaces a user-
+ defined string inside the base name of files.
+
+ 2. General stuff
+
+ 2.1 How to run Python scripts
+
+ All usage examples below show how to execute the Python scripts on
+ the shell of a Unix-like system. If you do not know, how to run
+ those scripts on your operating system, you may have a look at
+ this page:
+
+ http://www.urbanware.org/howto_python.html
+
+ 2.2 Overview of all command-line arguments
+
+ Usually, each script requires command-line arguments to operate.
+ So, to get an overview of all arguments available, simply run the
+ script with the "--help" argument. For example:
+
+ $ ./nomen-filemod.py --help
+
+ 2.3 Warning
+
+ Please use this tool with care to avoid data damage or loss!
+
+ It is strongly recommended to simulate every rename process first
+ to check which files would have been renamed. As a matter of fact,
+ there is no function to undo the changes done by this tool, so you
+ should be aware of what you are doing. If not, do NOT use this
+ tool.
+
+ Improper use (e. g. modifying files inside system directories)
+ will corrupt your system!
+
+ 2.4 Simulation mode
+
+ As already mentioned above, before renaming any files, you should
+ simulate the rename process by using the "--simulate" argument.
+
+ This argument requires a file path where the report of the rename
+ process will be written to. When in simulation mode, no files will
+ be renamed at all.
+
+ So, if you have a command line like this
+
+ ./nomen-filemod.py -d /tmp/pics -a add -p prefix -s "Foobar"
+
+ but want to simulate the rename process and write the report into
+ the file "/tmp/report.txt", simply add the argument
+
+ --simulate /tmp/report.txt
+
+ to the command line:
+
+ ./nomen-filemod.py -d /tmp/pics -a add -p prefix -s "Foobar" \
+ --simulate /tmp/report.txt
+
+ 2.5 Prefixes and suffixes
+
+ Typically a file name consists of a base name (prefix) and an
+ extension (suffix) separated by a separator, for example:
+
+ holiday.jpg
+
+ In this case, "holiday" is the base name, the dot is the separator
+ and "jpg" is the extension of the file.
+
+ The Nomen File Name Modifier only modifies the base name of file
+ names. The "--position" argument expects either "any", "prefix" or
+ "suffix". These positions are only related to the base name, not
+ to the entire file name.
+
+ For example, if you add the prefix "foo" to the base name it would
+ result in
+
+ fooholiday.jpg
+
+ and if you add the suffix "foo" (instead of the prefix) the base
+ name would look as follows:
+
+ holidayfoo.jpg
+
+ As already mentioned above, only the base name (the prefix of the
+ whole file name) will be modified and the file extension (the
+ suffix of the whole file name) will be ignored.
+
+ 3. Add a user-defined string to file names
+
+ Before modifying the base name of any files, please see section 2.5
+ above. Notice that the given input string is processed case-sensitive.
+
+ 3.1 Add a prefix
+
+ For example, if you have the files
+
+ holiday 1.jpg
+ holiday 2.jpg
+ holiday 3.jpg
+ holiday 4.jpg
+
+ inside the directory "/tmp/pics" and want to add the string
+ "Summer " (without any quotes) as prefix to the base name of these
+ files, type:
+
+ ./nomen-filemod.py -d /tmp/pics -a add -p prefix -s "Summer "
+
+ This will rename the base name of the files as follows:
+
+ Summer holiday 1.jpg
+ Summer holiday 2.jpg
+ Summer holiday 3.jpg
+ Summer holiday 4.jpg
+
+ 3.2 Add a suffix
+
+ For example, if you have the files
+
+ Holiday 1.jpg
+ Holiday 2.jpg
+ Holiday 3.jpg
+ Holiday 4.jpg
+
+ inside the directory "/tmp/pics" and want to add the string
+ " - Foobar" (without any quotes) as suffix to the base name of
+ these files, type:
+
+ ./nomen-filemod.py -d /tmp/pics -a add -p suffix \
+ -s " - Foobar"
+
+ This will rename the base name of the files as follows:
+
+ Holiday 1 - Foobar.jpg
+ Holiday 2 - Foobar.jpg
+ Holiday 3 - Foobar.jpg
+ Holiday 4 - Foobar.jpg
+
+ 4. Remove a user-defined string from file names
+
+ Before modifying the base name of any files, please see section 2.5
+ above. Notice that the given input string is processed case-sensitive.
+
+ 4.1 Remove a prefix
+
+ For example, if you have the files
+
+ Summer holiday 1.jpg
+ Summer holiday 2.jpg
+ Summer holiday 3.jpg
+ Summer holiday 4.jpg
+
+ inside the directory "/tmp/pics" and want to remove the prefix
+ "Summer " (without any quotes) from the base name of these files,
+ type:
+
+ ./nomen-filemod.py -d /tmp/pics -a remove -p prefix \
+ -s "Summer "
+
+ This will rename the base name of the files as follows:
+
+ holiday 1.jpg
+ holiday 2.jpg
+ holiday 3.jpg
+ holiday 4.jpg
+
+ 4.2 Remove a suffix
+
+ For example, if you have the files
+
+ Holiday 1 - Foobar.jpg
+ Holiday 2 - Foobar.jpg
+ Holiday 3 - Foobar.jpg
+ Holiday 4 - Foobar.jpg
+
+ inside the directory "/tmp/pics" and want to remove the suffix
+ " - Foobar" (without any quotes) from the base name of these
+ files, type:
+
+ ./nomen-filemod.py -d /tmp/pics -a remove -p suffix \
+ -s " - Foobar"
+
+ This will rename the base name of the files as follows:
+
+ Holiday 1.jpg
+ Holiday 2.jpg
+ Holiday 3.jpg
+ Holiday 4.jpg
+
+ 4.3 Remove a string
+
+ This will remove all occurrences of the input string (no matter if
+ prefix, suffix or somewhere else) in the base name.
+
+ For example, if you have the files
+
+ Foobar Holiday Foobar 1.jpg
+ Foobar Holiday Foobar 2.jpg
+ Foobar Holiday Foobar 3.jpg
+ Foobar Holiday Foobar 4.jpg
+
+ inside the directory "/tmp/pics" and want to remove the string
+ "Foobar " (without any quotes) from the base name of these files,
+ type:
+
+ ./nomen-filemod.py -d /tmp/pics -a remove -p any -s "Foobar "
+
+ This will rename the base name of the files as follows:
+
+ Holiday 1.jpg
+ Holiday 2.jpg
+ Holiday 3.jpg
+ Holiday 4.jpg
+
+ 5. Replace a user-defined string inside file names
+
+ Before modifying the base name of any files, please see section 2.5
+ above. Notice that the given string (input and replace string) are
+ processed case-sensitive.
+
+ 5.1 Replace a prefix
+
+ For example, if you have the files
+
+ Winter holiday 1.jpg
+ Winter holiday 2.jpg
+ Winter holiday 3.jpg
+ Winter holiday 4.jpg
+
+ inside the directory "/tmp/pics" and want to replace the prefix
+ "Winter " with "Summer " (both without any quotes) inside the base
+ name of these files, type:
+
+ ./nomen-filemod.py -d /tmp/pics -a replace -p prefix \
+ -s "Winter " --replace-string "Summer "
+
+ This will rename the base name of the files as follows:
+
+ Summer holiday 1.jpg
+ Summer holiday 2.jpg
+ Summer holiday 3.jpg
+ Summer holiday 4.jpg
+
+ 5.2 Replace a suffix
+
+ For example, if you have the files
+
+ Holiday 1 - Foobar.jpg
+ Holiday 2 - Foobar.jpg
+ Holiday 3 - Foobar.jpg
+ Holiday 4 - Foobar.jpg
+
+ inside the directory "/tmp/pics" and want to replace the suffix
+ " - Foobar" with " Foo" (both without any quotes) inside the base
+ name of these files, type:
+
+ ./nomen-filemod.py -d /tmp/pics -a replace -p suffix \
+ -s " - Foobar" --replace-string " Foo"
+
+ This will rename the base name of the files as follows:
+
+ Holiday 1 Foo.jpg
+ Holiday 2 Foo.jpg
+ Holiday 3 Foo.jpg
+ Holiday 4 Foo.jpg
+
+ 5.3 Replace a string
+
+ This will replace all occurrences of the input string (no matter
+ if prefix, suffix or somewhere else) in the base name.
+
+ For example, if you have the files
+
+ Fu - Fubar - Fu 1.jpg
+ Fu - Fubar - Fu 2.jpg
+ Fu - Fubar - Fu 3.jpg
+ Fu - Fubar - Fu 4.jpg
+
+ inside the directory "/tmp/pics" and want to replace the string
+ "Fu" with "Foo" (both without any quotes) inside the base name of
+ these files, type:
+
+ ./nomen-filemod.py -d /tmp/pics -a replace -p any -s "Fu" \
+ --replace-string "Foo"
+
+ This will rename the base name of the files as follows:
+
+ Foo - Foobar - Foo 1.jpg
+ Foo - Foobar - Foo 2.jpg
+ Foo - Foobar - Foo 3.jpg
+ Foo - Foobar - Foo 4.jpg
+
+ 6. Strip file names
+
+ You can also remove certain leading and trailing characters (such as
+ whitespaces) from the file name. For example, if you have the file
+
+ Foo Holiday 1 Foo.jpg
+
+ and want to remove the string "Foo" inside the entire file name, you
+ can do this using
+
+ ./nomen-filemod.py -d /tmp/pics -a remove -p any -s "Foo"
+
+ but this would result in a file name with a leading as well as a
+ trailing whitespace (" Holiday 1 .jpg").
+
+ Now, there are multiple ways to remove these whitespaces. The first
+ way would be explicitly removing the prefix and suffix containing the
+ whitespace.
+
+ ./nomen-filemod.py -d /tmp/pics -a remove -p prefix -s "Foo "
+ ./nomen-filemod.py -d /tmp/pics -a remove -p suffix -s " Foo"
+
+ Another way would be removing the string "Foo" first and after that
+ the leading and trailing whitespaces separately.
+
+ ./nomen-filemod.py -d /tmp/pics -a remove -p any -s "Foo"
+ ./nomen-filemod.py -d /tmp/pics -a remove -p prefix -s " "
+ ./nomen-filemod.py -d /tmp/pics -a remove -p suffix -s " "
+
+ The simplest way would be using the included command-line argument:
+
+ ./nomen-filemod.py -d /tmp/pics -a remove -p any -s "Foo" \
+ --strip " "
+
+ The "--strip" argument also allows giving multiple characters that
+ should be stripped. For example, to strip the letter "H" in uppercase
+ as well as whitespaces, type:
+
+ ./nomen-filemod.py -d /tmp/pics -a remove -p any -s "Foo" \
+ --strip "H "
+
+ However, this will result in "oliday 1.jpg", due to the fact, that the
+ list of strip characters will not be processed in a certain order.
+
+ 7. Exclude certain files
+
+ See section 4 of the documentation of the Nomen File Renamer script
+ how to do this. The command-line arguments to exclude files used there
+ can also be applied to the Nomen File Modifier script.
+
+ 8. Rename certain files only
+
+ See section 5 of the documentation of the Nomen File Renamer script
+ how to do this. The command-line arguments to rename files only used
+ there can also be applied to the Nomen File Modifier script.
+
diff --git a/python3/docs/usage_nomen-fileren.txt b/python3/docs/usage_nomen-fileren.txt
new file mode 100644
index 0000000..321188f
--- /dev/null
+++ b/python3/docs/usage_nomen-fileren.txt
@@ -0,0 +1,496 @@
+
+USAGE (nomen-fileren.py)
+
+ Contents:
+
+ 1. Definition
+ 2. General stuff
+ 2.1 How to run Python scripts
+ 2.2 Overview of all command-line arguments
+ 2.3 Warning
+ 2.4 Simulation mode
+ 2.5 Significant changes (released with version 2.1.0)
+ 2.5.1 Names of the rename modes
+ 2.5.2 Way of renaming files
+ 3. Rename files
+ 3.1 Using "keep-order" mode
+ 3.1.1 Order by name (default)
+ 3.1.2 Order by access time
+ 3.1.3 Order by creation time
+ 3.1.4 Order by modification time
+ 3.2 Using "rename-new" mode
+ 3.3 Using "fill-gaps" mode
+ 4. Exclude certain files
+ 4.1 Exclude certain files using basic strings
+ 4.2 Exclude certain files using wildcards
+ 5. Rename certain files only
+ 5.1 Rename certain files using basic strings
+ 5.2 Rename certain files using wildcards
+ 6. Steps between each numeric ID
+ 6.1 Issue before
+ 6.2 Solution
+
+ 1. Definition
+
+ The Nomen File Renamer script renames the base name of files within a
+ directory and (if requested) in all of its sub-directories based on
+ the name of the directory where the files are stored in and adds a
+ unique numeric ID.
+
+ 2. General stuff
+
+ 2.1 How to run Python scripts
+
+ All usage examples below show how to execute the Python scripts on
+ the shell of a Unix-like system. If you do not know, how to run
+ those scripts on your operating system, you may have a look at
+ this page:
+
+ http://www.urbanware.org/howto_python.html
+
+ 2.2 Overview of all command-line arguments
+
+ Usually, each script requires command-line arguments to operate.
+ So, to get an overview of all arguments available, simply run the
+ script with the "--help" argument. For example:
+
+ $ ./nomen-fileren.py --help
+
+ 2.3 Warning
+
+ Please use this tool with care to avoid data damage or loss!
+
+ It is strongly recommended to simulate every rename process first
+ to check which files would have been renamed. As a matter of fact,
+ there is no function to undo the changes done by this tool, so you
+ should be aware of what you are doing. If not, do NOT use this
+ tool.
+
+ Improper use (e. g. modifying files inside system directories)
+ will corrupt your system!
+
+ 2.4 Simulation mode
+
+ As already mentioned above, before renaming any files, you should
+ simulate the rename process by using the "--simulate" argument.
+
+ This argument requires a file path where the report of the rename
+ process will be written to. When in simulation mode, no files will
+ be renamed at all.
+
+ So, if you have a command line like this
+
+ ./nomen-fileren.py -m keep-order -d /tmp/pics/Holidays
+
+ but want to simulate the rename process and write the report into
+ the file "/tmp/report.txt", simply add the argument
+
+ --simulate /tmp/report.txt
+
+ to the command line:
+
+ ./nomen-fileren.py -m keep-order -d /tmp/pics/Holidays \
+ --simulate /tmp/report.txt
+
+ 2.5 Significant changes (released with version 2.1.0)
+
+ 2.5.1 Names of the rename modes
+
+ Before Nomen version 2.0.0, there only were two rename modes,
+ "consecutive" and "fill".
+
+ In version 2.0.0 the "fill" mode has been forked into the two
+ modes "fillin" and "fillnew".
+
+ With the release of version 2.1.0, all these names have been
+ replaced with new ones for more clarity.
+
+ +--------------------------+--------------------------+
+ | Before | After |
+ +--------------------------+--------------------------+
+ | consecutive | keep-order |
+ | fillin | fill-gaps |
+ | fillnew | rename-new |
+ +--------------------------+--------------------------+
+
+ 2.5.2 Way of renaming files
+
+ In the past, the files have been renamed with a continuous
+ number ignoring the file type.
+
+ With the release of version 2.1.0, by default the file types
+ will not longer be ignored, so every file type has its own
+ range of numbers:
+
+ +--------------------------+--------------------------+
+ | Before | After |
+ +--------------------------+--------------------------+
+ | Picture_0001.bmp | Picture_0001.bmp |
+ | Picture_0002.bmp | Picture_0002.bmp |
+ | Picture_0003.jpg | Picture_0001.jpg |
+ | Picture_0004.jpg | Picture_0002.jpg |
+ | Picture_0005.png | Picture_0001.png |
+ | Picture_0006.png | Picture_0002.png |
+ +--------------------------+--------------------------+
+
+ However, with the "--ignore-file-ext" command-line argument
+ this behaviour can be changed, so the files will be renamed
+ with a continuous number ignoring the file type again.
+
+ 3. Rename files
+
+ For example, if you have four picture files inside the directory
+ "/tmp/pics/Holidays":
+
+ pic_0001.jpg
+ pic_0002.jpg
+ pic_0003.jpg
+ pic_0004.jpg
+
+ Basically, the Nomen File Renamer script will rename the files inside
+ that directory based on the directory name where they are stored in
+ and add a unique numeric ID. For example:
+
+ Holidays 1.jpg
+ Holidays 2.jpg
+ Holidays 3.jpg
+ Holidays 4.jpg
+
+ The script offers some file rename modes.
+
+ 3.1 Using "keep-order" mode
+
+ The "keep-order" mode keeps the chronological order even if files
+ have been deleted. There are multiple ways this mode can be used.
+
+ 3.1.1 Order by name (default)
+
+ In case no order command-line argument was given, the default
+ behaviour will be used. For example, if you delete the picture
+ file "Holidays 2.jpg" and re-run the script with this rename
+ mode it will do the following:
+
+ Holidays 1.jpg stays (no need to rename)
+ Holidays 3.jpg will be renamed to "Holidays 2.jpg"
+ Holidays 4.jpg will be renamed to "Holidays 3.jpg"
+
+ Now, if you add another file to that directory, "foobar.jpg"
+ for example, and re-run the Nomen File Renamer script with
+ this rename mode, the new file will be appended to the
+ existing and already renamed files:
+
+ Holidays 1.jpg stays (no need to rename)
+ Holidays 2.jpg stays (no need to rename)
+ Holidays 3.jpg stays (no need to rename)
+ foobar.jpg will be renamed to "Holidays 4.jpg"
+
+ To use this rename mode, type:
+
+ ./nomen-fileren.py -m keep-order -d /tmp/pics/Holidays
+
+ In case you want to reorganize these pictures, put them into a
+ new directory called "Summer" among with some other files and
+ re-run the script with this rename mode, the files will not be
+ renamed chronologically, but alphabetically. For example:
+
+ Beach 1.jpg will be renamed to "Summer 1.jpg"
+ Beach 2.jpg will be renamed to "Summer 2.jpg"
+ Holidays 1.jpg will be renamed to "Summer 3.jpg"
+ Holidays 2.jpg will be renamed to "Summer 4.jpg"
+ Holidays 3.jpg will be renamed to "Summer 5.jpg"
+ Holidays 4.jpg will be renamed to "Summer 6.jpg"
+
+ So, even if the "Beach" pictures are newer than the "Holidays"
+ pictures the files will be renamed like this.
+
+ You can also keep a certain order, for example, beginning with
+ the "Holidays" pictures followed by the "Beach" pictures. At
+ first, put only the "Holidays" pictures into the "Summer"
+ directory and run the script with this rename mode:
+
+ Holidays 1.jpg will be renamed to "Summer 1.jpg"
+ Holidays 2.jpg will be renamed to "Summer 2.jpg"
+ Holidays 3.jpg will be renamed to "Summer 3.jpg"
+ Holidays 4.jpg will be renamed to "Summer 4.jpg"
+
+ After that, put the "Beach" pictures in the "Summer" directory
+ and run the script with this mode again:
+
+ Summer 1.jpg stays (no need to rename)
+ Summer 2.jpg stays (no need to rename)
+ Summer 3.jpg stays (no need to rename)
+ Summer 4.jpg stays (no need to rename)
+ Beach 1.jpg will be renamed to "Summer 5.jpg"
+ Beach 2.jpg will be renamed to "Summer 6.jpg"
+
+ 3.1.2 Order by access time
+
+ Please read section 3.1.1 above first before continuing with
+ this one.
+
+ You can also rename and order the files by the time they have
+ been accessed in ascending order. So, the file with the oldest
+ time will be the first one to be renamed.
+
+ However, the access time is not being determined by Nomen, it
+ is the timestamp provided by the metadata of the file.
+
+ Notice that this will irreversibly change the order of the
+ files.
+
+ To use this rename mode, type:
+
+ ./nomen-fileren.py -m keep-order -d /tmp/pics/Holidays \
+ -o accessed
+
+ 3.1.3 Order by creation time
+
+ Please read section 3.1.1 above first before continuing with
+ this one.
+
+ You can also rename and order the files by the time they have
+ been created in ascending order. So, the file with the oldest
+ time will be the first one to be renamed.
+
+ However, the creation time is not being determined by Nomen,
+ it is the timestamp provided by the metadata of the file.
+
+ Notice that this will irreversibly change the order of the
+ files.
+
+ According to the official Python documentation (see "Common
+ pathname manipulations"), the creation time information
+ depends on the operation system.
+
+ On some systems (like Unix) it is the time of the last change
+ of the metadata, and, on others (like Windows), is the
+ creation time of the file.
+
+ To use this rename mode, type:
+
+ ./nomen-fileren.py -m keep-order -d /tmp/pics/Holidays \
+ -o created
+
+ 3.1.4 Order by modification time
+
+ Please read section 3.1.1 above first before continuing with
+ this one.
+
+ You can also rename and order the files by the time they have
+ been modified in ascending order. So, the file with the oldest
+ time will be the first one to be renamed.
+
+ However, the modification time is not being determined by
+ Nomen, it is the timestamp provided by the metadata of the
+ file.
+
+ Notice that this will irreversibly change the order of the
+ files.
+
+ To use this rename mode, type:
+
+ ./nomen-fileren.py -m keep-order -d /tmp/pics/Holidays \
+ -o modified
+
+ 3.2 Using "rename-new" mode
+
+ The "rename-new" mode does NOT keep the chronological order if
+ files have been deleted. It simply fills the numeration gaps with
+ new files or files that have been renamed manually.
+
+ For example, if you delete the picture file "Holidays 2.jpg" and
+ re-run the Nomen File Renamer script with this rename mode, no
+ file names will be changed.
+
+ Holidays 1.jpg stays (no need to rename)
+ Holidays 3.jpg stays (no need to rename)
+ Holidays 4.jpg stays (no need to rename)
+
+ If you then add another file to that directory, e. g. "foobar.jpg"
+ and re-run the script this rename mode again, it will simply fill
+ the gap "Holiday 2.jpg" has left using the new file:
+
+ Holidays 1.jpg stays (no need to rename)
+ Holidays 3.jpg stays (no need to rename)
+ Holidays 4.jpg stays (no need to rename)
+ foobar.jpg will be renamed to "Holidays 2.jpg"
+
+ To use this rename mode, type:
+
+ ./nomen-fileren.py -m rename-new -d /tmp/pics/Holidays
+
+ 3.3 Using "fill-gaps" mode
+
+ The "fill-gaps" mode works like the "rename-new" mode (for details
+ see section 3.2) with the difference, that it fills all numeration
+ gaps using new as well as the existing files with the greatest
+ numbers. For example:
+
+ Holidays 1.jpg stays (no need to rename)
+ Holidays 4.jpg stays (no need to rename)
+ Holidays 5.jpg will be renamed to "Holidays 3.jpg"
+ foobar.jpg will be renamed to "Holidays 2.jpg"
+
+ To use this rename mode, type:
+
+ ./nomen-fileren.py -m fill-gaps -d /tmp/pics/Holidays
+
+ 4. Exclude certain files
+
+ 4.1 Exclude certain files using basic strings
+
+ For example, to exclude all files whose file name (base name or
+ extension) contains the string "foo", type:
+
+ $ ./nomen-fileren.py -m fill-gaps -d /tmp/pics/Holidays \
+ --exclude "foo"
+
+ By default, the given pattern is being processed case-insensitive.
+ That means, the string given above would match "foo" as well as
+ e. g. "FOO", "Foo", "FoO" and so on.
+
+ The pattern can also consist of multiple strings (separated with
+ semicolons):
+
+ $ ./nomen-fileren.py -m fill-gaps -d /tmp/pics/Holidays \
+ --exclude "foo2;foo4"
+
+ 4.2 Exclude certain files using wildcards
+
+ The pattern also supports asterisk wildcards. So, if you want to
+ exclude all files whose file name (base name or extension)
+ contains a string that starts with "foo" and ends with "bar"
+ (e. g. "foobar", "foo-bar" or "foo bar"), type:
+
+ $ ./nomen-fileren.py -m fill-gaps -d /tmp/pics/Holidays \
+ --exclude "foo*bar"
+
+ The pattern can also consist of multiple strings (separated with
+ semicolons) with wildcards:
+
+ $ ./nomen-fileren.py -m fill-gaps -d /tmp/pics/Holidays \
+ --exclude "f*o;b*r"
+
+ 5. Rename certain files only
+
+ 5.1 Rename certain files using basic strings
+
+ So, for example, to only rename the files whose file name (base
+ name or extension) contains the string "foo", type:
+
+ $ ./nomen-fileren.py -m fill-gaps -d /tmp/pics/Holidays \
+ --explicit "foo"
+
+ By default, the given pattern is being processed case-insensitive.
+ That means, the string given above would match "foo" as well as
+ e. g. "FOO", "Foo", "FoO" and so on.
+
+ The pattern can also consist of multiple strings (separated with
+ semicolons):
+
+ $ ./nomen-fileren.py -m fill-gaps -d /tmp/pics/Holidays \
+ --explicit "foo2;foo4"
+
+ 5.2 Rename certain files using wildcards
+
+ The pattern also supports asterisk wildcards. So, if you want to
+ only rename the files whose file name (base name or extension)
+ contains a string that starts with "foo" and ends with "bar"
+ (e. g. "foobar", "foo-bar" or "foo bar"), type:
+
+ $ ./nomen-fileren.py -m fill-gaps -d /tmp/pics/Holidays \
+ --explicit "foo*bar"
+
+ The pattern can also consist of multiple strings (separated with
+ semicolons) with wildcards:
+
+ $ ./nomen-fileren.py -m fill-gaps -d /tmp/pics/Holidays \
+ --explicit "f*o;b*r"
+
+ 6. Steps between each numeric ID
+
+ Since version 2.3.0, it is also possible to set an additional user-
+ defined step value, which can be combined with all rename modes
+ mentioned in section 3 above.
+
+ Especially when using the the "keep-order" mode, adding new files to
+ existing ones between two numeric IDs was quite inconvenient. Below
+ you can find an example issue and its solution.
+
+ 6.1 Issue before
+
+ There is a directory containing already renamed files and a new
+ one that should be between others like this
+
+ Holidays 1.jpg
+ Holidays 2.jpg
+ Holidays 3.jpg
+ pic_0001.jpg should actually be "Holidays 2.jpg"
+
+ and running
+
+ ./nomen-fileren.py -m keep-order -d /tmp/pics/Holidays
+
+ would rename "pic_0001.jpg" to "Holidays 4.jpg", which is not
+ what we want.
+
+ One way to solve that is:
+
+ 1. Create a new temporary sub-directory with a different name,
+ for example "Temp".
+
+ 2. Move all files whose names are starting with "Holdays" into
+ that directory.
+
+ 3. Then, run the Nomen File Renamer script again. The file
+ "pic_0001.jpg" file will then renamed to "Holidays 2.jpg".
+
+ 4. In case that the target directory has not been processed
+ recursively before, run the script once again with the path
+ of the sub-directory as target directory, so the term
+ "Holidays" inside the file name will be replaced by "Temp".
+
+ 5. Move all files from the temporary directory back into the
+ directory where the "Holidays" files are.
+
+ 6. Delete the temporary directory.
+
+ 7. Now, run the Nomen File Renamer script a third time with
+ the path of the directory that contains the "Holidays"
+ files as the target directory again.
+
+ 6.2 Solution
+
+ Now, before adding new files that should be between others, run
+ the Nomen File Renamer script (before adding new files) with a
+ user-defined step value, for example 10.
+
+ ./nomen-fileren.py -m keep-order -d /tmp/pics/Holidays \
+ --step 10
+
+ This will rename the files as follows:
+
+ Holidays 10.jpg
+ Holidays 20.jpg
+ Holidays 30.jpg
+
+ Then add the "pic_0001.jpg" file to that directiry and rename it
+ like "Holidays 11.jpg" (or with any number between 10 and 20).
+
+ Holidays 10.jpg stays (no need to rename)
+ Holidays 11.jpg will be renamed to "Holidays 20.jpg"
+ Holidays 20.jpg will be renamed to "Holidays 30.jpg"
+ Holidays 30.jpg will be renamed to "Holidays 3.jpg"
+
+ Now, run the Nomen File Renamer script once again
+
+ ./nomen-fileren.py -m keep-order -d /tmp/pics/Holidays \
+ --step 10
+
+ to apply the changes:
+
+ Holidays 10.jpg stayed (no need to rename)
+ Holidays 20.jpg was "Holidays 11.jpg" before
+ Holidays 30.jpg was "Holidays 20.jpg" before
+ Holidays 40.jpg was "Holidays 30.jpg" before
+
diff --git a/python3/license.txt b/python3/license.txt
new file mode 100644
index 0000000..5c861af
--- /dev/null
+++ b/python3/license.txt
@@ -0,0 +1,27 @@
+
+LICENSE (Nomen)
+
+ Nomen - Multi-purpose rename tool
+ Copyright (C) 2018 by Ralf Kilian
+
+ Distributed under the MIT License:
+
+ Permission is hereby granted, free of charge, to any person obtaining
+ a copy of this software and associated documentation files (the
+ "Software"), to deal in the Software without restriction, including
+ without limitation the rights to use, copy, modify, merge, publish,
+ distribute, sublicense, and/or sell copies of the Software, and to
+ permit persons to whom the Software is furnished to do so, subject to
+ the following conditions:
+
+ The above copyright notice and this permission notice shall be
+ included in all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
diff --git a/python3/nomen-dirspace.py b/python3/nomen-dirspace.py
new file mode 100755
index 0000000..0b85eb7
--- /dev/null
+++ b/python3/nomen-dirspace.py
@@ -0,0 +1,90 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+# ============================================================================
+# Nomen - Multi-purpose rename tool
+# Directory name space modifier script
+# Copyright (C) 2018 by Ralf Kilian
+# Distributed under the MIT License (https://opensource.org/licenses/MIT)
+#
+# Website: http://www.urbanware.org
+# GitHub: https://github.com/urbanware-org/nomen
+# ============================================================================
+
+import os
+import sys
+
+def main():
+ from core import clap
+ from core import common
+ from core import fileren
+
+ try:
+ p = clap.Parser()
+ except Exception as e:
+ print("%s: error: %s" % (os.path.basename(sys.argv[0]), e))
+ sys.exit(1)
+
+ p.set_description("Modify a directory name by removing leading, " \
+ "trailing and duplicate spaces or by inserting and " \
+ "removing spaces next to punctuation characters.")
+ p.set_epilog("Further information and usage examples can be found " \
+ "inside the documentation file for this script.")
+
+ # Required arguments
+ p.add_avalue("-d", "--directory", "directory that contains the files " \
+ "to process", "directory", None, True)
+
+ # Optional arguments
+ p.add_switch("-b", "--brackets", "insert and remove spaces next to " \
+ "brackets", "brackets", True, False)
+ p.add_avalue(None, "--exclude", "pattern to exclude certain files " \
+ "(case-insensitive, multiple patterns separated via " \
+ "semicolon)", "exclude_pattern", None, False)
+ p.add_switch("-h", "--help", "print this help message and exit", None,
+ True, False)
+ p.add_switch(None, "--hyphens", "insert spaces next to hyphens", \
+ "hyphens", True, False)
+ p.add_switch(None, "--ignore-symlinks", "ignore symbolic links",
+ "ignore_symlinks", True, False)
+ p.add_switch("-l", "--remove-leading", "remove leading spaces",
+ "remove_leading", True, False)
+ p.add_switch("-r", "--recursive", "process the given directory " \
+ "recursively", "recursive", True, False)
+ p.add_switch("-p", "--punctuation", "insert and remove spaces next to " \
+ "punctuation characters", "punctuation", True, False)
+ p.add_switch("-s", "--remove-duplicate", "remove duplicate spaces",
+ "remove_duplicate", True, False)
+ p.add_switch("-t", "--remove-trailing", "remove trailing spaces",
+ "remove_trailing", True, False)
+ p.add_switch(None, "--version", "print the version number and exit", None,
+ True, False)
+
+ if len(sys.argv) == 1:
+ p.error("At least one required argument is missing.")
+ elif ("-h" in sys.argv) or ("--help" in sys.argv):
+ p.print_help()
+ sys.exit(0)
+ elif "--version" in sys.argv:
+ print(fileren.get_version())
+ sys.exit(0)
+
+ try:
+ args = p.parse_args()
+ if not args.punctuation and not args.remove_duplicate and \
+ not args.remove_leading and not args.remove_trailing:
+ p.error("Nothing to do (no optional arguments were given).")
+
+ common.dir_space_modifier(args.directory, args.remove_duplicate,
+ args.remove_leading, args.remove_trailing,
+ args.brackets, args.hyphens,
+ args.punctuation, args.ignore_symlinks,
+ args.recursive, args.exclude_pattern)
+ except Exception as e:
+ p.error(e)
+
+if __name__ == "__main__":
+ main()
+
+# EOF
+
diff --git a/python3/nomen-extcase.py b/python3/nomen-extcase.py
new file mode 100755
index 0000000..78af59e
--- /dev/null
+++ b/python3/nomen-extcase.py
@@ -0,0 +1,87 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+# ============================================================================
+# Nomen - Multi-purpose rename tool
+# Extension case converter script
+# Copyright (C) 2018 by Ralf Kilian
+# Distributed under the MIT License (https://opensource.org/licenses/MIT)
+#
+# Website: http://www.urbanware.org
+# GitHub: https://github.com/urbanware-org/nomen
+# ============================================================================
+
+import os
+import sys
+
+def main():
+ from core import clap
+ from core import common
+ from core import extren
+
+ try:
+ p = clap.Parser()
+ except Exception as e:
+ print("%s: error: %s" % (os.path.basename(sys.argv[0]), e))
+ sys.exit(1)
+
+ p.set_description("Convert the case of the extension of all files " \
+ "inside a directory and (if requested) in all of its " \
+ "sub-directories.")
+ p.set_epilog("Further information and usage examples can be found " \
+ "inside the documentation file for this script.")
+
+ # Required arguments
+ p.add_predef("-c", "--case", "target extension case", "case",
+ ["lower", "title", "upper"], True)
+ p.add_avalue("-d", "--directory", "directory that contains the files " \
+ "to process", "directory", None, True)
+ p.add_predef("-m", "--conflict-mode", "conflict mode (in case of " \
+ "duplicate file names)", "conflict_mode", ["rename", "skip"],
+ True)
+
+ # Optional arguments
+ p.add_switch(None, "--confirm", "skip the confirmation prompt and " \
+ "instantly rename files", "confirm", True, False)
+ p.add_switch("-h", "--help", "print this help message and exit", None,
+ True, False)
+ p.add_switch(None, "--ignore-symlinks", "ignore symbolic links",
+ "ignore_symlinks", True, False)
+ p.add_switch("-r", "--recursive", "process the given directory " \
+ "recursively", "recursive", True, False)
+ p.add_avalue(None, "--simulate", "simulate the rename process and " \
+ "write the details into a report file", "report_file", None,
+ False)
+ p.add_switch(None, "--version", "print the version number and exit", None,
+ True, False)
+
+ if len(sys.argv) == 1:
+ p.error("At least one required argument is missing.")
+ elif ("-h" in sys.argv) or ("--help" in sys.argv):
+ p.print_help()
+ sys.exit(0)
+ elif "--version" in sys.argv:
+ print(extren.get_version())
+ sys.exit(0)
+
+ args = p.parse_args()
+ if args.confirm and not args.report_file == None:
+ p.error("The confirm and the simulate argument cannot be given at " \
+ "the same time.")
+
+ try:
+ if not args.confirm and args.report_file == None:
+ if not common.confirm_notice():
+ sys.exit(0)
+
+ extren.convert_case(args.directory, args.case, args.conflict_mode,
+ args.recursive, args.report_file,
+ args.ignore_symlinks)
+ except Exception as e:
+ p.error(e)
+
+if __name__ == "__main__":
+ main()
+
+# EOF
+
diff --git a/python3/nomen-extren.py b/python3/nomen-extren.py
new file mode 100755
index 0000000..fdf6e33
--- /dev/null
+++ b/python3/nomen-extren.py
@@ -0,0 +1,94 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+# ============================================================================
+# Nomen - Multi-purpose rename tool
+# Extension renamer script
+# Copyright (C) 2018 by Ralf Kilian
+# Distributed under the MIT License (https://opensource.org/licenses/MIT)
+#
+# Website: http://www.urbanware.org
+# GitHub: https://github.com/urbanware-org/nomen
+# ============================================================================
+
+import os
+import sys
+
+def main():
+ from core import clap
+ from core import common
+ from core import extren
+
+ try:
+ p = clap.Parser()
+ except Exception as e:
+ print("%s: error: %s" % (os.path.basename(sys.argv[0]), e))
+ sys.exit(1)
+
+ p.set_description("Rename (and adjust) differently spelled file " \
+ "extensions of the same file type file within a " \
+ "directory and (if requested) in all of its sub-" \
+ "directories.")
+ p.set_epilog("Further information and usage examples can be found " \
+ "inside the documentation file for this script.")
+
+ # Required arguments
+ p.add_avalue("-d", "--directory", "directory that contains the files " \
+ "to process", "directory", None, True)
+ p.add_avalue("-e", "--extension", "extension to rename (case-" \
+ "sensitive, multiple extensions separated via semicolon)",
+ "extension", None, True)
+ p.add_predef("-m", "--conflict-mode", "conflict mode (in case of " \
+ "duplicate file names)", "conflict_mode", ["rename", "skip"],
+ True)
+ p.add_avalue("-t", "--target-extension", "target extension (case-" \
+ "sensitive)", "extension_target", None, True)
+
+ # Optional arguments
+ p.add_switch("-c", "--case-sensitive", "do not ignore the case of the " \
+ "given extension list", "case", False, False)
+ p.add_switch(None, "--confirm", "skip the confirmation prompt and " \
+ "instantly rename files", "confirm", True, False)
+ p.add_switch("-h", "--help", "print this help message and exit", None,
+ True, False)
+ p.add_switch(None, "--ignore-symlinks", "ignore symbolic links",
+ "ignore_symlinks", True, False)
+ p.add_switch("-r", "--recursive", "process the given directory " \
+ "recursively", "recursive", True, False)
+ p.add_avalue(None, "--simulate", "simulate the rename process and " \
+ "write the details into a report file", "report_file", None,
+ False)
+ p.add_switch(None, "--version", "print the version number and exit", None,
+ True, False)
+
+ if len(sys.argv) == 1:
+ p.error("At least one required argument is missing.")
+ elif ("-h" in sys.argv) or ("--help" in sys.argv):
+ p.print_help()
+ sys.exit(0)
+ elif "--version" in sys.argv:
+ print(extren.get_version())
+ sys.exit(0)
+
+ args = p.parse_args()
+ if args.confirm and not args.report_file == None:
+ p.error("The confirm and the simulate argument cannot be given at " \
+ "the same time.")
+
+ try:
+ if not args.confirm and args.report_file == None:
+ if not common.confirm_notice():
+ sys.exit(0)
+
+ extren.rename_extensions(args.directory, args.conflict_mode,
+ args.extension, args.extension_target,
+ args.recursive, args.case, args.report_file,
+ args.ignore_symlinks)
+ except Exception as e:
+ p.error(e)
+
+if __name__ == "__main__":
+ main()
+
+# EOF
+
diff --git a/python3/nomen-filecase.py b/python3/nomen-filecase.py
new file mode 100755
index 0000000..6ad55dd
--- /dev/null
+++ b/python3/nomen-filecase.py
@@ -0,0 +1,100 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+# ============================================================================
+# Nomen - Multi-purpose rename tool
+# File name case converter script
+# Copyright (C) 2018 by Ralf Kilian
+# Distributed under the MIT License (https://opensource.org/licenses/MIT)
+#
+# Website: http://www.urbanware.org
+# GitHub: https://github.com/urbanware-org/nomen
+# ============================================================================
+
+import os
+import sys
+
+def main():
+ from core import clap
+ from core import common
+ from core import fileren
+
+ try:
+ p = clap.Parser()
+ except Exception as e:
+ print("%s: error: %s" % (os.path.basename(sys.argv[0]), e))
+ sys.exit(1)
+
+ p.set_description("Convert the case of the base name of all files " \
+ "inside a directory and (if requested) in all of its " \
+ "sub-directories.")
+ p.set_epilog("Further information and usage examples can be found " \
+ "inside the documentation file for this script.")
+
+ # Required arguments
+ p.add_predef("-c", "--case", "target case of the base name", "case",
+ ["lower", "title", "upper", "config"], True)
+ p.add_avalue("-d", "--directory", "directory that contains the files " \
+ "to process", "directory", None, True)
+ p.add_predef("-m", "--conflict-mode", "conflict mode (in case of " \
+ "duplicate file names)", "conflict_mode", ["rename", "skip"],
+ True)
+
+ # Optional arguments
+ p.add_avalue(None, "--cfg-lower", "path to the config file for strings " \
+ "which should always be lowercase inside the file " \
+ "name", "cfg_lower", None, False)
+ p.add_avalue(None, "--cfg-mixed", "path to the config file for strings " \
+ "which should always be mixed case inside the " \
+ "file name", "cfg_mixed", None, False)
+ p.add_avalue(None, "--cfg-title", "path to the config file for strings " \
+ "which should always be title case inside the " \
+ "file name", "cfg_title", None, False)
+ p.add_avalue(None, "--cfg-upper", "path to the config file for strings " \
+ "which should always be uppercase inside the file " \
+ "name", "cfg_upper", None, False)
+ p.add_switch(None, "--confirm", "skip the confirmation prompt and " \
+ "instantly rename files", "confirm", True, False)
+ p.add_switch("-h", "--help", "print this help message and exit", None,
+ True, False)
+ p.add_switch(None, "--ignore-symlinks", "ignore symbolic links",
+ "ignore_symlinks", True, False)
+ p.add_switch("-r", "--recursive", "process the given directory " \
+ "recursively", "recursive", True, False)
+ p.add_avalue(None, "--simulate", "simulate the rename process and " \
+ "write the details into a report file", "report_file", None,
+ False)
+ p.add_switch(None, "--version", "print the version number and exit", None,
+ True, False)
+
+ if len(sys.argv) == 1:
+ p.error("At least one required argument is missing.")
+ elif ("-h" in sys.argv) or ("--help" in sys.argv):
+ p.print_help()
+ sys.exit(0)
+ elif "--version" in sys.argv:
+ print(fileren.get_version())
+ sys.exit(0)
+
+ args = p.parse_args()
+ if args.confirm and not args.report_file == None:
+ p.error("The confirm and the simulate argument cannot be given at " \
+ "the same time.")
+
+ try:
+ if not args.confirm and args.report_file == None:
+ if not common.confirm_notice():
+ sys.exit(0)
+
+ fileren.convert_case(args.directory, args.case, args.conflict_mode,
+ args.recursive, args.cfg_lower, args.cfg_mixed,
+ args.cfg_title, args.cfg_upper, args.report_file,
+ args.ignore_symlinks)
+ except Exception as e:
+ p.error(e)
+
+if __name__ == "__main__":
+ main()
+
+# EOF
+
diff --git a/python3/nomen-filemod.py b/python3/nomen-filemod.py
new file mode 100755
index 0000000..63b303e
--- /dev/null
+++ b/python3/nomen-filemod.py
@@ -0,0 +1,120 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+# ============================================================================
+# Nomen - Multi-purpose rename tool
+# File name modifier script
+# Copyright (C) 2018 by Ralf Kilian
+# Distributed under the MIT License (https://opensource.org/licenses/MIT)
+#
+# Website: http://www.urbanware.org
+# GitHub: https://github.com/urbanware-org/nomen
+# ============================================================================
+
+import os
+import sys
+
+def main():
+ from core import clap
+ from core import common
+ from core import fileren
+
+ try:
+ p = clap.Parser()
+ except Exception as e:
+ print("%s: error: %s" % (os.path.basename(sys.argv[0]), e))
+ sys.exit(1)
+
+ p.set_description("Modify the base name of files by adding, removing " \
+ "or replacing a user-defined string.")
+ p.set_epilog("Further information and usage examples can be found " \
+ "inside the documentation file for this script.")
+
+ # Required arguments
+ p.add_predef("-a", "--action", "action to perform", "action",
+ ["add", "remove", "replace"], True)
+ p.add_avalue("-d", "--directory", "directory that contains the files " \
+ "to process", "directory", None, True)
+ p.add_predef("-p", "--position", "position where to perform the action",
+ "position", ["any", "prefix", "suffix"], True)
+ p.add_avalue("-s", "--string", "input string to perform the action " \
+ "with (case-sensitive)", "input_string", None, True)
+
+ # Optional arguments
+ p.add_switch("-c", "--case-sensitive", "do not ignore the case of the " \
+ "given exclude or explicit pattern", "case", False, False)
+ p.add_switch(None, "--confirm", "skip the confirmation prompt and " \
+ "instantly rename files", "confirm", True, False)
+ p.add_avalue(None, "--exclude", "pattern to exclude certain files " \
+ "(case-insensitive, multiple patterns separated via " \
+ "semicolon)", "exclude_pattern", None, False)
+ p.add_avalue(None, "--explicit", "explicit pattern to only process " \
+ "certain files (case-insensitive, multiple patterns " \
+ "separated via semicolon)", "explicit_pattern", None, False)
+ p.add_switch("-h", "--help", "print this help message and exit", None,
+ True, False)
+ p.add_switch(None, "--ignore-symlinks", "ignore symbolic links",
+ "ignore_symlinks", True, False)
+ p.add_switch("-r", "--recursive", "process the given directory " \
+ "recursively", "recursive", True, False)
+ p.add_switch(None, "--regex", "use regex syntax for the exclude or " \
+ "explicit pattern instead of just asterisk wildcards and " \
+ "semicolon separators (for details see the section " \
+ "'Regular expression operations' inside the official " \
+ "Python documentation)", "regex_syntax", True, False)
+ p.add_avalue(None, "--replace-string", "string to replace the input" \
+ "string with (when using the action 'replace')",
+ "replace_string", None, False)
+ p.add_avalue(None, "--simulate", "simulate the rename process and " \
+ "write the details into a report file", "report_file", None,
+ False)
+ p.add_avalue(None, "--strip", "remove certain leading and trailing " \
+ "characters from the base name", "strip_chars", None, False)
+ p.add_switch(None, "--version", "print the version number and exit", None,
+ True, False)
+
+ if len(sys.argv) == 1:
+ p.error("At least one required argument is missing.")
+ elif ("-h" in sys.argv) or ("--help" in sys.argv):
+ p.print_help()
+ sys.exit(0)
+ elif "--version" in sys.argv:
+ print(fileren.get_version())
+ sys.exit(0)
+
+ args = p.parse_args()
+ if args.confirm and not args.report_file == None:
+ p.error("The confirm and the simulate argument cannot be given at " \
+ "the same time.")
+ if args.exclude_pattern and args.explicit_pattern:
+ p.error("The exclude and the explicit pattern argument cannot be " \
+ "given at the same time.")
+
+ try:
+ if not args.confirm and args.report_file == None:
+ if not common.confirm_notice():
+ sys.exit(0)
+
+ if args.exclude_pattern:
+ pattern = args.exclude_pattern
+ exclude = True
+ elif args.explicit_pattern:
+ exclude = False
+ pattern = args.explicit_pattern
+ else:
+ exclude = None
+ pattern = None
+
+ fileren.modify_names(args.directory, args.action, args.position,
+ args.input_string, args.replace_string,
+ args.recursive, exclude, pattern, args.case,
+ args.regex_syntax, args.report_file,
+ args.ignore_symlinks, args.strip_chars)
+ except Exception as e:
+ p.error(e)
+
+if __name__ == "__main__":
+ main()
+
+# EOF
+
diff --git a/python3/nomen-fileren.py b/python3/nomen-fileren.py
new file mode 100755
index 0000000..9308e31
--- /dev/null
+++ b/python3/nomen-fileren.py
@@ -0,0 +1,130 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+# ============================================================================
+# Nomen - Multi-purpose rename tool
+# File renamer script
+# Copyright (C) 2018 by Ralf Kilian
+# Distributed under the MIT License (https://opensource.org/licenses/MIT)
+#
+# Website: http://www.urbanware.org
+# GitHub: https://github.com/urbanware-org/nomen
+# ============================================================================
+
+import os
+import sys
+
+def main():
+ from core import clap
+ from core import common
+ from core import fileren
+
+ try:
+ p = clap.Parser()
+ except Exception as e:
+ print("%s: error: %s" % (os.path.basename(sys.argv[0]), e))
+ sys.exit(1)
+
+ p.set_description("Rename the base name of files within a directory " \
+ "and (if requested) in all of its sub-directories " \
+ "based on the name of the directory where the files " \
+ "are stored in and add a unique numeric ID.")
+ p.set_epilog("Further information and usage examples can be found " \
+ "inside the documentation file for this script.")
+
+ # Required arguments
+ p.add_avalue("-d", "--directory", "directory that contains the files " \
+ "to process", "directory", None, True)
+ p.add_predef("-m", "--rename-mode", "rename mode to use", "rename_mode",
+ ["fill-gaps", "increase", "keep-order", "rename-new"], True)
+
+ # Optional arguments
+ p.add_switch("-c", "--case-sensitive", "do not ignore the case of the " \
+ "given exclude or explicit pattern", "case", False, False)
+ p.add_switch(None, "--confirm", "skip the confirmation prompt and " \
+ "instantly rename files", "confirm", True, False)
+ p.add_avalue(None, "--custom-name", "custom file name (instead of the " \
+ "directory name where the files are stored in)",
+ "custom_name", None, False)
+ p.add_avalue(None, "--exclude", "pattern to exclude certain files " \
+ "(case-insensitive, multiple patterns separated via " \
+ "semicolon)", "exclude_pattern", None, False)
+ p.add_avalue(None, "--explicit", "explicit pattern to only process " \
+ "certain files (case-insensitive, multiple patterns " \
+ "separated via semicolon)", "explicit_pattern", None, False)
+ p.add_switch("-h", "--help", "print this help message and exit", None,
+ True, False)
+ p.add_switch(None, "--ignore-file-ext", "ignore file extensions when " \
+ "numerating files", "ignore_file_ext", True, False)
+ p.add_switch(None, "--ignore-symlinks", "ignore symbolic links",
+ "ignore_symlinks", True, False)
+ p.add_predef("-o", "--order-by", "order files by last accessed, " \
+ "created or modified date", "order_by", ["accessed",
+ "created", "modified"], False)
+ p.add_avalue("-p", "--padding", "set a user-defined numeric padding " \
+ "(if no user-defined padding value is given, it will be " \
+ "set automatically based on the amount of files per " \
+ "directory)", "padding", 0, False)
+ p.add_switch("-r", "--recursive", "process the given directory " \
+ "recursively", "recursive", True, False)
+ p.add_switch(None, "--regex", "use regex syntax for the exclude or " \
+ "explicit pattern instead of just asterisk wildcards and " \
+ "semicolon separators (for details see the section " \
+ "'Regular expression operations' inside the official " \
+ "Python documentation)", "regex_syntax", True, False)
+ p.add_avalue("-s", "--separator", "use a user-defined character or " \
+ "string as a separator between the directory name and the " \
+ "unique numeric ID", "separator", " ", False)
+ p.add_avalue(None, "--simulate", "simulate the rename process and " \
+ "write the details into a report file", "report_file", None,
+ False)
+ p.add_avalue(None, "--step", "steps between each numeric ID", "step", 1,
+ False)
+ p.add_switch(None, "--version", "print the version number and exit", None,
+ True, False)
+
+ if len(sys.argv) == 1:
+ p.error("At least one required argument is missing.")
+ elif ("-h" in sys.argv) or ("--help" in sys.argv):
+ p.print_help()
+ sys.exit(0)
+ elif "--version" in sys.argv:
+ print(fileren.get_version())
+ sys.exit(0)
+
+ args = p.parse_args()
+ if args.confirm and not args.report_file == None:
+ p.error("The confirm and the simulate argument cannot be given at " \
+ "the same time.")
+ if args.exclude_pattern and args.explicit_pattern:
+ p.error("The exclude and the explicit pattern argument cannot be " \
+ "given at the same time.")
+
+ try:
+ if not args.confirm and args.report_file == None:
+ if not common.confirm_notice():
+ sys.exit(0)
+
+ if args.exclude_pattern:
+ pattern = args.exclude_pattern
+ exclude = True
+ elif args.explicit_pattern:
+ exclude = False
+ pattern = args.explicit_pattern
+ else:
+ exclude = None
+ pattern = None
+
+ fileren.rename_files(args.directory, args.rename_mode, args.separator,
+ args.recursive, args.padding, exclude, pattern,
+ args.case, args.regex_syntax, args.report_file,
+ args.ignore_symlinks, args.ignore_file_ext,
+ args.custom_name, args.step, args.order_by)
+ except Exception as e:
+ p.error(e)
+
+if __name__ == "__main__":
+ main()
+
+# EOF
+
diff --git a/python3/readme.txt b/python3/readme.txt
new file mode 100644
index 0000000..e4b8fba
--- /dev/null
+++ b/python3/readme.txt
@@ -0,0 +1,67 @@
+
+README (Nomen)
+
+ Project
+
+ Nomen
+ Version 2.3.5 (based on Python framework 3.x)
+ Copyright (C) 2018 by Ralf Kilian
+
+ Website: http://www.urbanware.org
+ GitHub: https://github.com/urbanware-org/nomen
+
+ Definition
+
+ The Nomen project is a multi-purpose rename tool to consistently
+ rename the base name as well as the extension of files in a variety of
+ ways and also to remove unnecessary whitespaces from directory names.
+
+ License
+
+ Permission is hereby granted, free of charge, to any person obtaining
+ a copy of this software and associated documentation files (the
+ "Software"), to deal in the Software without restriction, including
+ without limitation the rights to use, copy, modify, merge, publish,
+ distribute, sublicense, and/or sell copies of the Software, and to
+ permit persons to whom the Software is furnished to do so, subject to
+ the following conditions:
+
+ The above copyright notice and this permission notice shall be
+ included in all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+ Requirements
+
+ The code of this Nomen version was simply converted from Python 2.x
+ to 3.x syntax using the 2to3 tool and has only been tested
+ fundamentally.
+
+ In order to use the project, Python 3.2 or higher is recommended, but
+ it may also work with earlier versions.
+
+ If you need a version that works with Python 2.x, you can also find
+ one on the website of the project mentioned above.
+
+ Usage
+
+ For fundamental documentation as well as some usage examples for each
+ component of the project, you may have a look at the text files inside
+ the included 'docs' sub-directory.
+
+ Legal information
+
+ The project name is completely fictitious. Any correspondences with
+ existing websites, applications, companies and/or other projects are
+ purely coincidental.
+
+ All trademarks belong to their respective owners.
+
+ Errors and omissions excepted.
+