Skip to content

cpawd.loadConfiguration

Load and normalise the cpawd configuration

loadConfig(cliArgs)

Load the configuration by merging any cpawdConfig.yaml found in the current working directory, and then any other configuration files specified on the command line.

Then perform the following normalisation:

  • The base working directory is computed using the baseDir and prefix keys found in the workDir section of the merged configuration.

  • Compute workDir for each task in the tasks section of the merged configuration.

  • Ensure all workDir exists (both for the base and the individual tasks)

  • Expand all watched paths to an absolute path in the file system.

  • Check that the projectDir exists for each task.

  • Compute logFilePaths and open logFiles for each task.

Source code in cpawd/loadConfiguration.py
def loadConfig(cliArgs) :
  """

  Load the configuration by merging any `cpawdConfig.yaml` found in the
  current working directory, and then any other configuration files
  specified on the command line.

  Then perform the following normalisation:

  - The base working directory is computed using the `baseDir` and
  `prefix` keys found in the `workDir` section of the merged
  configuration.

  - Compute `workDir` for each task in the `tasks` section of the merged
  configuration.

  - Ensure all `workDir` exists (both for the base and the individual
  tasks)

  - Expand all watched paths to an absolute path in the file system.

  - Check that the `projectDir` exists for each task.

  - Compute logFilePaths and open logFiles for each task.

  """

  config = {
    'workDir' : {
      'baseDir' : '/tmp',
      'prefix' : 'cpawd'
    },
    'tasks' : {},
    'verbose' : False
  }

  if cliArgs.verbose :
    config['verbose'] = cliArgs.verbose

  if cliArgs.debug :
    config['debug'] = cliArgs.debug

  cliArgs.config.insert(0,'cpawdConfig.yaml')
  for aConfigPath in cliArgs.config :
    if os.path.exists(aConfigPath) :
      try :
        with open(aConfigPath) as aConfigFile :
          aConfig = yaml.safe_load(aConfigFile.read())
          mergeYamlData(config, aConfig, "")
      except Exception as err :
        print("Could not load configuration from [{}]".format(aConfigPath))
        print(err)

  # create the working directory
  if 'workDir' not in config['workDir'] :
    config['workDir']['workDir'] = os.path.join(
      config['workDir']['baseDir'],
      config['workDir']['prefix'] + '-' + time.strftime("%Y%m%d-%H%M%S")
    )

  workDir = config['workDir']['workDir']

  if os.path.exists(workDir) :
    shutil.rmtree(workDir)
  os.makedirs(workDir)

  # ensure the task work and project directories exist
  for aTaskName, aTask in config['tasks'].items() :
    aTask['workDir'] = os.path.join(workDir, aTaskName)
    os.makedirs(aTask['workDir'])
    aTask['logFilePath'] = os.path.join(workDir, aTaskName, 'command.log')
    if 'projectDir' in aTask :
      aTask['projectDir'] = os.path.abspath(os.path.expanduser(aTask['projectDir']))
    else:
      aTask['projectDir'] = aTask['workDir']

    if not os.path.exists(aTask['projectDir']) :
      taskError(
        "the projectDir for task {} MUST exist in the file system".format(aTaskName),
        aTask
      )

    if 'watch' not in aTask or not aTask['watch'] :
      taskError("all tasks MUST have a collection of files/directories to watch\nno 'watch' list provided in task [{}]:".format(aTaskName), aTask)
    expandedWatches = []
    for aWatch in aTask['watch'] :
      newWatch = os.path.expanduser(aWatch)
      if not newWatch.startswith('/') :
        newWatch = os.path.join(aTask['projectDir'], newWatch)
        os.makedirs(newWatch, exist_ok=True)
      expandedWatches.append(newWatch)
    aTask['watch'] = expandedWatches

  # expand toolTips and commands
  for aTaskName, aTask in config['tasks'].items() :
    if 'cmd' not in aTask :
      taskError("all tasks MUST have a cmd; no cmd provied in task [{}]".format(aTaskName), aTask)
    if type(aTask['cmd']) is not list :
      taskError("task cmds MUST be a list of command followed by arguments\nfound type: {} in task {}".format(type(aTask['cmd']), aTaskName), aTask)
    try :
      newCmd = []
      for anArgument in aTask['cmd'] :
        newCmd.append(anArgument.format(**config['tasks']))
      aTask['cmd'] = newCmd
    except Exception as err :
      print("Could not expand variables in cmd string:")
      print(yaml.dump(aTask['cmd']))
      print(repr(err))

    if 'toolTips' in aTask :
      try :
        aTask['toolTips'] = aTask['toolTips'].format(**config['tasks'])
      except Exception as err :
        print("Could not expand variables in toolTips string:")
        print(yaml.dump(aTask['toolTips']))
        print(repr(err))

  if config['verbose'] :
    print("configuration:")
    print("---------------------------------------------------------------")
    print(yaml.dump(config))

  # announce User Messages
  print("---------------------------------------------------------------")
  print("\nTool tips:\n")
  for aTaskName, aTask in config['tasks'].items() :
    if 'toolTips' in aTask :
      print("{}\n  {}".format(aTaskName, aTask['toolTips']))

  # announce log files
  print("\n---------------------------------------------------------------")
  print("\nLogfiles for each task:\n")
  for aTaskName, aTask in config['tasks'].items() :
    print("{}\n  tail -f {}".format(aTaskName, aTask['logFilePath']))
    print("  {} {}".format(cliArgs.pager, aTask['logFilePath']))
  print("")
  print("---------------------------------------------------------------")
  print("")

  return config

mergeYamlData(yamlData, newYamlData, thePath)

This is a generic Python merge. It is a deep merge and handles both dictionaries and arrays

Source code in cpawd/loadConfiguration.py
def mergeYamlData(yamlData, newYamlData, thePath) :
  """

  This is a generic Python merge. It is a *deep* merge and handles both
  dictionaries and arrays

  """

  if type(yamlData) is None :
    print("ERROR yamlData should NEVER be None ")
    sys.exit(-1)

  if type(yamlData) != type(newYamlData) :
    print("Incompatible types {} and {} while trying to merge YAML data at {}".format(type(yamlData), type(newYamlData), thePath))
    print("Stoping merge at {}".format(thePath))
    return

  if type(yamlData) is dict :
    for key, value in newYamlData.items() :
      if key not in yamlData :
        yamlData[key] = value
      elif type(yamlData[key]) is dict :
        mergeYamlData(yamlData[key], value, thePath+'.'+key)
      elif type(yamlData[key]) is list :
        for aValue in value :
          yamlData[key].append(aValue)
      else :
        yamlData[key] = value
  elif type(yamlData) is list :
    for value in newYamlData :
      yamlData.append(value)
  else :
    print("ERROR yamlData MUST be either a dictionary or an array.")
    sys.exit(-1)