예제 #1
0
 def pre_run(self, args):
     # For Windows users, expand any glob patterns as needed.
     args.conf = list(expand_glob_list(args.conf))
예제 #2
0
    def run(self, args):
        # Ignores case sensitivity.  If you're on Windows, name your files right.
        conf_file_re = re.compile("([a-z]+\.conf|(default|local)\.meta)$")

        if args.target is None:
            self.stderr.write("Must provide the '--target' directory.\n")
            return EXIT_CODE_MISSING_ARG

        self.stderr.write("Combining conf files into directory {}\n".format(
            args.target))
        args.source = list(expand_glob_list(args.source))
        for src in args.source:
            self.stderr.write(
                "Reading conf files from directory {}\n".format(src))

        marker_file = os.path.join(args.target, CONTROLLED_DIR_MARKER)
        if os.path.isdir(args.target):
            if not args.disable_marker and not os.path.isfile(marker_file):
                self.stderr.write(
                    "Target directory already exists, but it appears to have been "
                    "created by some other means.  Marker file missing.\n")
                return EXIT_CODE_COMBINE_MARKER_MISSING
        elif args.dry_run:
            self.stderr.write(
                "Skipping creating destination directory {0} (dry-run)\n".
                format(args.target))
        else:
            self.stderr.write("Creating destination directory {0}\n".format(
                args.target))
            os.mkdir(args.target)
            if not args.disable_marker:
                open(marker_file, "w").write(
                    "This directory is managed by KSCONF.  Don't touch\n")

        # Build a common tree of all src files.
        src_file_index = defaultdict(list)
        for src_root in args.source:
            for (root, dirs,
                 files) in relwalk(src_root, followlinks=args.follow_symlink):
                for fn in files:
                    # Todo: Add blocklist CLI support:  defaults to consider: *sw[po], .git*, .bak, .~
                    if fn.endswith(".swp") or fn.endswith("*.bak"):
                        continue  # pragma: no cover  (peephole optimization)
                    src_file = os.path.join(root, fn)
                    src_path = os.path.join(src_root, root, fn)
                    src_file_index[src_file].append(src_path)

        # Find a set of files that exist in the target folder, but in NO source folder (for cleanup)
        target_extra_files = set()
        for (root, dirs, files) in relwalk(args.target,
                                           followlinks=args.follow_symlink):
            for fn in files:
                tgt_file = os.path.join(root, fn)
                if tgt_file not in src_file_index:
                    # Todo:  Add support for additional blocklist wildcards (using fnmatch)
                    if fn == CONTROLLED_DIR_MARKER or fn.endswith(".bak"):
                        continue  # pragma: no cover (peephole optimization)
                    target_extra_files.add(tgt_file)

        for (dest_fn, src_files) in sorted(src_file_index.items()):
            # Source file must be in sort order (10-x is lower prio and therefore replaced by 90-z)
            src_files = sorted(src_files)
            dest_path = os.path.join(args.target, dest_fn)

            # Make missing destination folder, if missing
            dest_dir = os.path.dirname(dest_path)
            if not os.path.isdir(dest_dir) and not args.dry_run:
                os.makedirs(dest_dir)

            # Handle conf files and non-conf files separately
            if not conf_file_re.search(dest_fn):
                # self.stderr.write("Considering {0:50}  NON-CONF Copy from source:  {1!r}\n".format(dest_fn, src_files[-1]))
                # Always use the last file in the list (since last directory always wins)
                src_file = src_files[-1]
                if args.dry_run:
                    if os.path.isfile(dest_path):
                        if file_compare(src_file, dest_path):
                            smart_rc = SMART_NOCHANGE
                        else:
                            if (_is_binary_file(src_file)
                                    or _is_binary_file(dest_path)):
                                # Binary files.  Can't compare...
                                smart_rc = "DRY-RUN (NO-DIFF=BIN)"
                            else:
                                show_text_diff(self.stdout, dest_path,
                                               src_file)
                                smart_rc = "DRY-RUN (DIFF)"
                    else:
                        smart_rc = "DRY-RUN (NEW)"
                else:
                    smart_rc = smart_copy(src_file, dest_path)
                if smart_rc != SMART_NOCHANGE:
                    self.stderr.write("Copy <{0}>   {1:50}  from {2}\n".format(
                        smart_rc, dest_path, src_file))
            else:
                try:
                    # Handle merging conf files
                    dest = ConfFileProxy(os.path.join(args.target, dest_fn),
                                         "r+",
                                         parse_profile=PARSECONF_MID)
                    srcs = [
                        ConfFileProxy(sf, "r", parse_profile=PARSECONF_STRICT)
                        for sf in src_files
                    ]
                    # self.stderr.write("Considering {0:50}  CONF MERGE from source:  {1!r}\n".format(dest_fn, src_files[0]))
                    smart_rc = merge_conf_files(dest,
                                                srcs,
                                                dry_run=args.dry_run,
                                                banner_comment=args.banner)
                    if smart_rc != SMART_NOCHANGE:
                        self.stderr.write(
                            "Merge <{0}>   {1:50}  from {2!r}\n".format(
                                smart_rc, dest_path, src_files))
                finally:
                    # Protect against any dangling open files:  (ResourceWarning: unclosed file)
                    dest.close()
                    for src in srcs:
                        src.close()

        if True and target_extra_files:  # Todo: Allow for cleanup to be disabled via CLI
            self.stderr.write(
                "Cleaning up extra files not part of source tree(s):  {0} files.\n"
                .format(len(target_extra_files)))
            for dest_fn in target_extra_files:
                self.stderr.write("Remove unwanted file {0}\n".format(dest_fn))
                os.unlink(os.path.join(args.target, dest_fn))