#! /usr/bin/env python # Import all the various modules that are used. import configparser import getopt import inspect import os import shutil import string import stat import sys class Singleton(object): def __new__(cls, *p, **k): cls._do_initialization = False if not '_the_instance' in cls.__dict__: cls._do_initialization = True cls._the_instance = object.__new__(cls) return cls._the_instance class ModuleInfo(Singleton): """A class that may be used to obtain information about the current module.""" __version = 3.2 def __init__(self): """Constructor of the ModuleInfo class. Initializes the member variables that store the module information.""" Singleton(self) if (self._do_initialization): self.__module_path = "" if len(sys.argv) > 0 and len(sys.argv[0]) > 0 and os.path.isabs(sys.argv[0]): self.__module_path = sys.argv[0] else: self.__module_path = os.path.abspath(inspect.getfile(ModuleInfo)) if not os.path.exists(self.__module_path): # If cx_freeze is used the value of the self.__module_path variable at this # point is in the following format. # {PathToExeFile}\{NameOfPythonSourceFile}. # This makes it necessary to strip off the file name to get the correct # path. self.__module_path = os.path.dirname(self.__module_path) self.__module_dir = os.path.dirname(self.__module_path) self.__module_ini = os.path.splitext(self.__module_path)[0] + ".ini" def get_version(self): """Return the version number of the ModuleInfo class.""" return self.__version def get_module_path(self): """Returns the file name and directory of the module.""" return self.__module_path def get_module_dir(self): """Returns the directory of the module.""" return self.__module_dir def get_module_ini(self): """Returns the file name and directory of the module INI file.""" return self.__module_ini class CommandLineArgumentsParser(Singleton): """A class that parses the command line arguments that are passed to this script.""" __usage__ = """ Usage: '%s' {options} This program automates the process of copying files from your project build directories to your project installation directories. It is designed so that it obtains most of the information that is required to copy the files from an accompanying INI file and only a minimal amount of information is collected via command line arguments. This module was designed to meet the needs of the products JAWS and MAGic developed by Freedom Scientific but it should work for other products as well. Options: -h, -H, -?, --help, --HELP Displays this message. -d, -D, --dbg, --DBG Causes extended status messages to be printed that may be useful when diagnosing problems with this program. -b, -B, --branch, --BRANCH {branch_name} The short name for the code branch you wish to copy files from. A section named branch_{branch_name} must be found in the accompanying INI file. The branch name is case sensitive. If this parameter is not specified, the "default" branch will be used. -c, -C, --config, --CONFIG {configuration_name} The name of the output directory of the configuration from which you wish to copy files. If this parameter is not specified the default configuration for the specified branch will be used. The default configuration for a branch can be specified by adding the value default_configuration={config} to the branch_{branch_name} section in the accompanying INI file. If the parameter is not specified in either the INI file or by the command line the configuration "debug" will be used. -f, -F, --force, --FORCE Copy the file even if the destination is up to date. -l, -L, --components, --COMPONENTS {components_list} Allows you to specify the list of components you wish to copy on the command line, thus overriding the components list specified by the INI file. This parameter is a semicolon delimited list of components that you wish to copy. -p, -P, --pc, --PC {PC} Allows you to specify which PC the components should be copied to. If this parameter is not specified the default PC for the specified branch will be used. The default PC for a branch can be specified by adding the value default_pc={pc} to the branch_{branch_name} section in the accompanying INI file. If the parameter is not specified in either the INI file or by the command line the configuration "TEST-PC" will be used. """ def __init__(self): """Constructor of the CommandLineArgumentsParser class.""" Singleton(self) if (self._do_initialization): self.__first_arg_index = 0 self.__display_debug_messages = False self.__display_usage = False self.__branch = "" self.__config = "" self.__pc = "" self.__force = False self.__components = "" self.__argv = sys.argv def set_first_argument_index(self, index): """Allows the index of the first command line argument to be overridden.""" self.__first_arg_index = index def set_argument_vector(self, argv): """Allows the index of the first command line argument to be overridden.""" self.__argv = argv def parse_command_line(self): """Parses the command line options.""" parse_opts = True if (len(self.__argv) > 1): arg1 = self.__argv[1] arg1 = arg1.lower() if (("/?" == arg1) or ("/h" == arg1) or ("/help" == arg1)): self.__display_usage = True return 0 try: opts, args = getopt.getopt(self.__argv[self.__first_arg_index:], "hH?b:B:c:C:p:P:l:L:dDfF", [ "help", "HELP", "branch=", "BRANCH=", "config=", "CONFIG=", "components=", "COMPONENTS=", "pc=", "PC=", "dbg", "DBG", "force", "FORCE" ]) except getopt.GetoptError as err: print(err.msg) parse_opts = False if (parse_opts): for o, a in opts: if o in ("-h", "-H", "-?", "--help", "--HELP"): self.__display_usage = True elif o in ("-d", "-D", "--dbg", "-DBG"): self.__display_debug_messages = True elif o in ("-b", "-B", "--branch", "--BRANCH"): self.__branch = a elif o in ("-c", "-C", "--config", "--CONFIG"): self.__config = a elif o in ("-p", "-P", "--pc", "--PC"): self.__pc = a elif o in ("-f", "-F", "--force", "-FORCE"): self.__force = True elif o in ("-l", "-L", "--components", "--COMPONENTS"): self.__components = a def usage(self): """Prints a message describing the command line options that may be used.""" print(CommandLineArgumentsParser.__usage__ % (ModuleInfo().get_module_path(), )) def should_display_debug_messages(self): """Return the current value of the display_debug_messages member variable.""" return self.__display_debug_messages def should_force(self): """Return the current value of the force member variable.""" return self.__force def should_display_usage(self): """Return the current value of the display_usage member variable.""" return self.__display_usage def get_branch(self): """Return the current value of the branch member variable.""" return self.__branch def get_config(self): """Return the current value of the config member variable.""" return self.__config def get_pc(self): """Return the current value of the pc member variable.""" return self.__pc def get_components(self): """Return the current value of the components member variable.""" return self.__components class FileTransfer: """Class that is used to describe a file transfer operation.""" __copying__file__ = """ Copying %s to %s. """ __copying__failed__ = """ Failed to copy %s to %s. """ def __init__(self, source, destination): """Constructor for the FileTransfer class.""" self.__source = source self.__destination = destination def do_transfer(self, force, display_debug_messages): """Copies the source file to the destination.""" if ((os.path.exists(self.get_source())) and (os.path.isdir(os.path.dirname(self.get_destination())))): try: if (os.path.exists(self.get_destination())): if ((not force) and (os.stat(self.get_source())[stat.ST_SIZE] == os.stat(self.get_destination())[stat.ST_SIZE]) and (os.stat(self.get_source())[stat.ST_MTIME] == os.stat(self.get_destination())[stat.ST_MTIME])): if (display_debug_messages): print("The destination file (%s) is already up to date. Skipping." % (self.get_destination(), )) print("") return False os.chmod(self.get_destination(), stat.S_IWRITE) print(FileTransfer.__copying__file__ % (self.get_source(), self.get_destination())) shutil.copy2(self.get_source(), self.get_destination()) # Change the mode to write after copying incase the source file was read only. os.chmod(self.get_destination(), stat.S_IWRITE) return True except IOError as err: print(FileTransfer.__copying__failed__ % (self.get_source(), self.get_destination())) return False except WindowsError as err: print(FileTransfer.__copying__failed__ % (self.get_source(), self.get_destination())) return False else: if (display_debug_messages): if (not os.path.exists(self.get_source())): print("The source '%s' does not exist." % (self.get_source(), )) print("") if (not os.path.isdir(os.path.dirname(self.get_destination()))): print("The destination directory '%s' does not exist." % (os.path.dirname(self.get_destination()), )) print("") def get_source(self): """Retrieves the "source" class member variable.""" return self.__source def set_source(self, source): """Sets the "source" class member variable.""" self.__source = source def get_destination(self): """Retrieves the "destination" class member variable.""" return self.__destination def set_destination(self, destination): """Sets the "destination" class member variable.""" self.__destination = destination class IniFileParser(Singleton): """Class that reads in and processes options from the module ini file.""" def __init__(self): """Constructor of the IniFileParser class.""" Singleton(self) if (self._do_initialization): self.__config_parser = None self.__ini_file = "" self.__branch = "" self.__config = "" self.__pc = "" self.__components = "" self.__display_debug_messages = False self.__components_list = [] self.__variables_dictionary = {} self.__dest_dirs_lists = [] self.__file_transfer_list = [] def set_should_display_debug_messages(self, display_debug_messages): self.__display_debug_messages = display_debug_messages def load(self, ini_file, branch, config, pc, components): """Loads data from ini_file that will be used to build the file transfer list.""" ret_val = False self.__ini_file = ini_file self.__branch = branch self.__config = config self.__pc = pc self.__components = components if (0 == len(self.__branch)): self.__branch = "default" if (os.path.exists(self.__ini_file)): ret_val = self.__read_ini_file__() return ret_val def build_file_transfer_list(self): """Builds the file trasfer list.""" s = len(self.__dest_dirs_lists[0]) t = len(self.__dest_dirs_lists) i = 0 while (i < s): variables_dictionary_local = self.__variables_dictionary j = 0 while (j < t): if (len(self.__dest_dirs_lists[j]) > i): variable_name = "dest_dir_%i" % (j + 1, ) variables_dictionary_local[variable_name] = self.__dest_dirs_lists[j][i] j = j + 1 i = i + 1 for component in self.__components_list: section_name = "component_" + component if (self.__config_parser.has_section(section_name)): source = self.__config_parser.get(section_name, "source_file", vars = variables_dictionary_local) dest = self.__config_parser.get(section_name, "dest_file", vars = variables_dictionary_local) if (self.__should_include_in_file_transfer_list__(section_name)): if not self.__in_file_transfer_list__(dest): transfer = FileTransfer(source, dest) self.__file_transfer_list.append(transfer) else: if (self.__display_debug_messages): print("'%s' is already in the file transfer list." % (dest, )) else: if (self.__display_debug_messages): print("__should_include_in_file_transfer_list__(''%s') returned false." % (section_name, )) if (0 == len(self.__file_transfer_list)): if (self.__display_debug_messages): print("The File Transfer List is empty.") return False if (self.__display_debug_messages): ftl = self.__file_transfer_list print("The File Transfer List contains %i items." % (len(ftl), )) print("The File Transfer List is as follows:") itemNumber = 0 for ft in ftl: itemNumber += 1 print(" Item: %i" % (itemNumber, )) print(" Source: %s" % (ft.get_source(), )) print(" Destination: %s" % (ft.get_destination(), )) print("") return True def get_file_transfer_list(self): """Return the value of the file_transfer_list member variable.""" return self.__file_transfer_list def __is_meta_component__(self, component): """Determines if a component is a meta component.""" if (component == None): return False if (not isinstance(component, str)): return False if (self.__config_parser.has_section("global_settings") == False): return False if (self.__config_parser.has_option("global_settings", component + "_components_list")): return True return False def __expand_meta_component__(self, component): """Expands a meta component. Returns a list that contains one or more items.""" components_list = list() if (component == None): return components_list if (not isinstance(component, str)): return components_list if (self.__config_parser.has_section("global_settings") == False): # The best we can do is return a list containing one item, the specified component. components_list.append(component) return components_list if (self.__is_meta_component__(component)): component_1 = self.__config_parser.get("global_settings", component + "_components_list") component_list_1 = component_1.split(os.pathsep) # Support two levels, one meta-component referencing another meta-component. for item in component_list_1: if (self.__is_meta_component__(item)): component_2 = self.__config_parser.get("global_settings", item + "_components_list") components_list.extend(component_2.split(os.pathsep)) else: components_list.append(item) else: components_list.append(component) return components_list def __read_global_settings_section__(self): """Utility function for reading the global_settings section of the INI file.""" if (self.__config_parser.has_section("global_settings") == False): return False if (self.__config_parser.has_option("global_settings", "default_components_list")): if (0 == len(self.__components)): self.__components = self.__config_parser.get("global_settings", "default_components_list") components_list = list() if (self.__components.find(os.pathsep) == -1): components_list.extend(self.__expand_meta_component__(self.__components)) else: components_list = self.__components.split(os.pathsep) for component in components_list: self.__components_list.extend(self.__expand_meta_component__(component)) return True def __read_ini_file__(self): """Utility function that is intended to be called only from load that updates class member variables by reading values from the module INI file.""" try: self.__config_parser = configparser.ConfigParser() self.__config_parser.read(self.__ini_file) branch_section_name = "branch_" + self.__branch if (False == self.__config_parser.has_section(branch_section_name)): return False if (0 == len(self.__config)): if (self.__config_parser.has_option(branch_section_name, "default_configuration")): self.__config = self.__config_parser.get(branch_section_name, "default_configuration") if (0 == len(self.__config)): self.__config = "debug" if (0 == len(self.__pc)): if (self.__config_parser.has_option(branch_section_name, "default_pc")): self.__pc = self.__config_parser.get(branch_section_name, "default_pc") if (0 == len(self.__pc)): self.__pc = "TEST-PC" if (self.__config_parser.has_section("global_settings")): self.__read_global_settings_section__() variables_section_name = "%s-%s variables" % (self.__branch, self.__config) if (self.__config_parser.has_section(variables_section_name)): for (name, value) in self.__config_parser.items(variables_section_name): self.__variables_dictionary[name] = value index = 1 value_name = "" while (True): value_name = "source_dir_%i" % (index) if (not self.__config_parser.has_option(branch_section_name, value_name)): break self.__variables_dictionary[value_name] = self.__config_parser.get(branch_section_name, value_name, vars = os.environ) index = index + 1 index = 1 value_name = "" while (True): value_name = "dest_dirs_%i" % (index, ) if (not self.__config_parser.has_option(branch_section_name, value_name)): break dest_dirs = self.__config_parser.get(branch_section_name, value_name) dest_dirs = dest_dirs.replace("{pc}", self.__pc) self.__dest_dirs_lists.append(dest_dirs.split(os.pathsep)) index = index + 1 return True except configparser.Error as err: return False def __in_file_transfer_list__(self, file): """Utility function that determines if a file is already in the file transfer list.""" ret = False ftl = self.__file_transfer_list for ft in ftl: if file == ft.get_destination(): ret = True return ret def __should_include_in_file_transfer_list__(self, section_name): """Utility function that determines if a file should be included in the file transfer list.""" if (not self.__config_parser.has_option(section_name, "configurations")): return True try: configurations = self.__config_parser.get(section_name, "configurations").split(os.pathsep) return (self.__config in configurations) except configparser.Error as err: return True def ModuleMain(ini_file, branch, config, pc, components, display_debug_messages, force): """The main entry point when this python script is being used as a module.""" ini = IniFileParser() ini.set_should_display_debug_messages(display_debug_messages) ret = ini.load(ini_file, branch, config, pc, components) if (False == ret): print("ini.load(%s, %s, %s, %s, %s) returned False" % (ini_file, branch, config, pc, components)) return 1 ret = ini.build_file_transfer_list() if (False == ret): return 1 ftl = ini.get_file_transfer_list() num_files_copied = 0 for ft in ftl: if (ft.do_transfer(force, display_debug_messages)): num_files_copied = num_files_copied + 1 dest = ft.get_destination().lower() print("%i Files Copied." % (num_files_copied, )) return 0 def main(argv): """The main entry point when this python script is being used from the command line.""" if (None == argv): argv = sys.argv argumentsParser = CommandLineArgumentsParser() argumentsParser.set_argument_vector(argv) argumentsParser.parse_command_line() if (argumentsParser.should_display_usage()): argumentsParser.usage() return 0 ret_val = ModuleMain(ModuleInfo().get_module_ini(), argumentsParser.get_branch(), argumentsParser.get_config(), argumentsParser.get_pc(), argumentsParser.get_components(), argumentsParser.should_display_debug_messages(), argumentsParser.should_force()) return ret_val if (__name__ == "__main__"): CommandLineArgumentsParser().set_first_argument_index(1) sys.exit(main(None))