github.com/jbendotnet/noms@v0.0.0-20190904222105-c43e4293ea92/tools/noms/staging.py (about)

     1  #!/usr/bin/python
     2  
     3  # Copyright 2016 Attic Labs, Inc. All rights reserved.
     4  # Licensed under the Apache License, version 2.0:
     5  # http://www.apache.org/licenses/LICENSE-2.0
     6  
     7  import argparse
     8  import glob
     9  import hashlib
    10  import os
    11  import os.path
    12  import re
    13  import shutil
    14  
    15  def Main(projectName, stagingFunction):
    16      """Main should be called by all staging scripts when executed.
    17  
    18      Main takes a project name and a callable. It creates a staging directory for
    19      your project and then runs the callable, passing it the path to the
    20      newly-created staging directory.
    21  
    22      For the common case of simply copying a set of files into the staging
    23      directory, use GlobCopier:
    24  
    25      #!/usr/bin/python
    26  
    27      import noms.staging as staging
    28  
    29      if __name__ == '__main__':
    30          staging.Main('nerdosphere', staging.GlobCopier('index.html', 'styles.css', '*.js'))
    31      """
    32      parser = argparse.ArgumentParser(description='Stage build products from this directory.')
    33      parser.add_argument('staging_dir',
    34                          metavar='path/to/staging/directory',
    35                          type=_dir_path,
    36                          help='top-level dir into which project build products are staged')
    37      args = parser.parse_args()
    38      project_staging_dir = os.path.join(args.staging_dir, projectName)
    39  
    40      normalized = os.path.realpath(project_staging_dir)
    41      if not _is_sub_dir(project_staging_dir, args.staging_dir):
    42          raise Exception(project_staging_dir + ' must be a subdir of ' + args.staging_dir)
    43  
    44      if not os.path.exists(normalized):
    45          os.makedirs(normalized)
    46      stagingFunction(normalized)
    47  
    48  
    49  def run_globs(staging_dir, globs, exclude):
    50      for pattern in globs:
    51          for f in glob.glob(pattern):
    52              if os.path.isdir(f):
    53                  continue
    54              from_dir, name = os.path.split(f)
    55              if name in exclude:
    56                  continue
    57              to_dir = os.path.join(staging_dir, from_dir)
    58              if not os.path.exists(to_dir):
    59                  os.makedirs(to_dir)
    60              yield (f, to_dir)
    61  
    62  
    63  def rename_with_hash(f, to_dir, rename_dict):
    64      with open(f) as fh:
    65          sha = hashlib.sha256()
    66          sha.update(fh.read())
    67          digest = sha.hexdigest()
    68      basename = os.path.basename(f)
    69      name, ext = os.path.splitext(basename)
    70      new_name = '%s.%s%s' % (name, digest[:20], ext)
    71      rename_dict[basename] = new_name
    72      shutil.move(os.path.join(to_dir, basename), os.path.join(to_dir, new_name))
    73  
    74  
    75  def GlobCopier(*globs, **kwargs):
    76      '''
    77      Returns a function that copies files defined by globs into a staging dir.
    78  
    79      Arguments:
    80      - Zero or more globs used to determine which files to copy.
    81  
    82      Keyword arguments:
    83      - rename (bool) - If True then the files gets renamed to name.%%hash.ext
    84      - index_file (str) - If present then this file is copied to the staging dir
    85        and its content is updated where the paths to the files are updated to the
    86        renamed file paths.
    87      '''
    88      exclude = ('webpack.config.js',)
    89      rename = 'rename' in kwargs and kwargs['rename']
    90      def stage(staging_dir):
    91          if rename:
    92              rename_dict = dict()
    93          for f, to_dir in run_globs(staging_dir, globs, exclude):
    94              shutil.copy2(f, to_dir)
    95              if rename:
    96                  rename_with_hash(f, to_dir, rename_dict)
    97  
    98          # Update index_file and write it to to_dir.
    99          if 'index_file' not in kwargs:
   100              return
   101          index_file = kwargs['index_file']
   102          from_dir, name = os.path.split(index_file)
   103          to_dir = os.path.join(staging_dir, from_dir)
   104          with open(index_file, 'r') as f:
   105              data = f.read()
   106          if rename:
   107              for old_name, new_name in rename_dict.iteritems():
   108                  r = re.compile(r'\b%s\b' % re.escape(old_name))
   109                  data = r.sub(new_name, data)
   110          with open(os.path.join(to_dir, name), 'w') as f:
   111              f.write(data)
   112  
   113      return stage
   114  
   115  
   116  def _dir_path(arg):
   117      normalized = os.path.realpath(os.path.abspath(arg))
   118      if os.path.exists(normalized) and not os.path.isdir(normalized):
   119          raise ValueError(arg + ' is not a path to a directory.')
   120      return normalized
   121  
   122  
   123  def _is_sub_dir(subdir, directory):
   124      # Need the path-sep at the end to ensure that commonprefix returns the correct result below.
   125      directory = os.path.join(os.path.realpath(directory), '')
   126      subdir = os.path.realpath(subdir)
   127  
   128      # return true, if the common prefix of both is equal to directory e.g.  /a/b/c/d.rst and
   129      # directory is /a/b, the common prefix is /a/b
   130      return os.path.commonprefix([subdir, directory]) == directory