def report(self, bare=False, inspect_remotes=False): """ :param bool bare: Bare report only :param bool inspect_remotes: If True, report on which remote branches are cleanable :return GitRunReport: General report on current checkout state """ if not self.is_git_checkout: if self.remote_info: return GitRunReport(problem="not cloned yet") return GitRunReport.not_git() result = GitRunReport() if not self.config.remotes: result.add(problem="no remotes") if bare: return result if self.age is not None and self.age > FRESHNESS_THRESHOLD: result.add(note="last fetch %s ago" % runez.represented_duration(self.age)) orphan_branches = self.orphan_branches if self.branches.current in orphan_branches: # Current is no more on its remote (should possibly checkout another branch and cleanup, or push) orphan_branches = orphan_branches[:] orphan_branches.remove(self.branches.current) result.add(note="current branch '%s' is orphaned" % self.branches.current) if len(orphan_branches) == 1: result.add(note="local branch '%s' can be pruned" % orphan_branches[0]) elif orphan_branches: result.add(note="%s can be pruned" % runez.plural(orphan_branches, "local branch")) result.add(self.branches.report) if inspect_remotes and self.remote_cleanable_branches: if len(self.remote_cleanable_branches) == 1: cleanable = "'%s'" % next(iter(self.remote_cleanable_branches)) else: cleanable = runez.plural(self.remote_cleanable_branches, "remote branch") result.add(note="%s can be cleaned" % cleanable) return result
def replay(existing, tokens, samples): """Rerun samples and compare them with their current baseline""" kinds = [] if tokens: kinds.append(TestSamples.K_TOKEN) skipped = 0 for sample in samples: report = sample.replay(*kinds) if report is runez.UNSET: skipped += 1 report = None if existing else " %s" % runez.yellow("skipped") elif report: report = "\n%s" % "\n".join(" %s" % s for s in report.splitlines()) else: report = " %s" % runez.green("OK") if report is not None: print("** %s:%s" % (runez.bold(sample.name), report)) if skipped: print(runez.dim("-- %s skipped" % runez.plural(skipped, "sample")))
def clean_samples(cls, verbose=False): cleanable = [] for root, dirs, files in os.walk(cls.SAMPLE_FOLDER): if not dirs and not files: cleanable.append(root) if os.path.basename(root).startswith("_xpct-"): for fname in files: ypath = os.path.dirname(root) ypath = os.path.join(ypath, "%s.yml" % runez.basename(fname)) if not os.path.isfile(ypath): # Delete _xpct-* files that correspond to moved samples cleanable.append(os.path.join(root, fname)) if not cleanable: if verbose: print("No cleanable _xpct- files found") return for path in cleanable: runez.delete(path, logger=logging.info) for root, dirs, files in os.walk(cls.SAMPLE_FOLDER): if not dirs and not files: cleanable.append(root) runez.delete(root, logger=logging.info) print("%s cleaned" % runez.plural(cleanable, "file"))
def diff(compact, untyped, tokens, implementations, samples): """Compare deserialization of 2 implementations""" stringify = runez.stringified if untyped else decode if compact is None: compact = len(samples) > 1 with runez.TempFolder(): generated_files = [] for sample in samples: generated_files.append([sample]) for impl in implementations: assert isinstance(impl, Implementation) data = impl.get_outcome(sample, tokens=tokens) rep = TestSettings.represented(data, size=None, stringify=stringify, dt=simplified_date) fname = "%s-%s.txt" % (impl.name, sample.basename) generated_files[-1].extend([fname, rep]) if not compact: with open(fname, "w") as fh: fh.write(rep) if not rep.endswith("\n"): fh.write("\n") matches = 0 failed = 0 differ = 0 for sample, n1, r1, n2, r2 in generated_files: if isinstance(r1, dict) and isinstance( r2, dict) and r1.get("_error") and r2.get("_error"): matches += 1 failed += 1 print("%s: both failed" % sample) elif r1 == r2: matches += 1 print("%s: OK" % sample) else: differ += 1 if compact: print("%s: differ" % sample) if not compact: for sample, n1, r1, n2, r2 in generated_files: if r1 != r2: r = runez.run("diff", "-br", "-U1", n1, n2, fatal=None) print("======== %s ========" % sample) print(r.full_output) print() message = [ runez.plural(samples, "sample"), TestSettings.colored_if_meaningful(matches, "match", runez.green), TestSettings.colored_if_meaningful(differ, "differ", runez.orange), TestSettings.colored_if_meaningful(failed, "failed", runez.red), ] print("\n%s" % ", ".join(message))
def _callback(_ctx, _param, value): if not value: return None impls = ImplementationCollection(value, default=default) if impls.unknown: raise click.BadParameter("Unknown implementation(s): %s" % ", ".join(impls.unknown)) if count and len(impls) != count: if count == 1: raise click.BadParameter("Need exactly 1 implementation") raise click.BadParameter("Need exactly %s" % runez.plural(count, "implementation")) if count == 1: return impls.selected[0] return impls
def option(cls, default="zyaml,ruamel", count=None, **kwargs): """ Args: default (str | None): Default implementation(s) to use count (int | None): Optional: exact number of implementations that have to specified **kwargs: Passed-through to click """ kwargs["default"] = default def _callback(_ctx, _param, value): if not value: return None impls = ImplementationCollection(value, default=default) if impls.unknown: raise click.BadParameter("Unknown implementation(s): %s" % ", ".join(impls.unknown)) if count and len(impls) != count: if count == 1: raise click.BadParameter("Need exactly 1 implementation") raise click.BadParameter("Need exactly %s" % runez.plural(count, "implementation")) if count == 1: return impls.selected[0] return impls metavar = "I1,..." hlp = "Implementation(s)" if count: hlp = runez.plural(count, "implementation") metavar = ",".join("I%s" % (i + 1) for i in range(count)) kwargs.setdefault("help", "%s to use" % hlp) kwargs.setdefault("show_default", True) kwargs.setdefault("metavar", metavar) name = "implementation" if count == 1 else "implementations" return click.option(name, "-i", callback=_callback, **kwargs)
def freshness(self): """Short freshness overview""" result = [] if self.report._problem: result.append(runez.red(" ".join(self.report._problem))) if self.modified: result.append(runez.red(runez.plural(self.modified, "diff"))) if self.untracked: result.append(runez.orange("%s untracked" % len(self.untracked))) if self.report._note: result.append(runez.purple(" ".join(self.report._note))) if not self.report._problem and not self.report._note and self._parent.age is not None: message = "up to date" if self._parent.age > FRESHNESS_THRESHOLD: message += "*" result.append(runez.teal(message)) return ", ".join(result)
def _callback(_ctx, _param, value): if not value: value = default if count == 1 and hasattr(value, "endswith") and not value.endswith("."): value += "." if not value: raise click.BadParameter("No samples specified") s = cls.get_samples(value) if not s: raise click.BadParameter("No samples match %s" % value) if count and count != len(s): raise click.BadParameter( "Need exactly %s, filter yielded %s" % (runez.plural(count, "sample"), len(s))) if count == 1: return s[0] return s
def test_plural(): assert runez.plural(None) == "no values" assert runez.plural(None, None) == "no values" assert runez.plural("chair") == "chairs" assert runez.plural(None, "walker") == "no walkers" assert runez.plural(None) == "no values" assert runez.plural(1) == "1 value" assert runez.plural("foo", "character") == "3 characters" assert runez.plural(2, "match") == "2 matches" assert runez.plural(0, "dish") == "0 dishes" assert runez.plural(5, "goth") == "5 goths" assert runez.plural(7, "diff") == "7 diffs" assert runez.plural(1, "penny") == "1 penny" assert runez.plural(2, "penny") == "2 pennies" assert runez.plural([], "record") == "0 records" assert runez.plural([""], "record") == "1 record" assert runez.plural(1, "person") == "1 person" assert runez.plural(2, "person") == "2 people" assert runez.plural(2, "man") == "2 men" assert runez.plural(2, "woman") == "2 women" assert runez.plural(2, "status") == "2 statuses" assert runez.plural(0, "carrot") == "0 carrots" assert runez.plural(1, "carrot") == "1 carrot" assert runez.plural(20, "carrot") == "20 carrots" assert runez.plural(20000, "carrot") == "20K carrots" assert runez.plural(20000, "carrot", base=None) == "20K carrots" assert runez.plural(20000, "carrot", base=0) == "20000 carrots"
def handle_single_clean(target, what): """ :param GitCheckout target: Single checkout to clean :param str what: Operation """ report = target.git.fetch() if report.has_problems: if what != "reset": what = "clean" print( target.header( GitRunReport(report).add(problem="<can't %s" % what))) runez.abort("") if what == "reset": return clean_reset(target) if what == "show": return clean_show(target) total_cleaned = 0 print(target.header()) if what in "remote all": if not target.git.remote_cleanable_branches: print(" No remote branches can be cleaned") else: total = len(target.git.remote_cleanable_branches) cleaned = 0 for branch in target.git.remote_cleanable_branches: remote, _, name = branch.partition("/") if not remote and name: raise Exception("Unknown branch spec '%s'" % branch) if run_git(target, False, "branch", "--delete", "--remotes", branch): cleaned += run_git(target, False, "push", "--delete", remote, name) total_cleaned += cleaned if cleaned == total: print("%s cleaned" % runez.plural(cleaned, "remote branch")) else: print("%s/%s remote branches cleaned" % (cleaned, total)) target.git.reset_cached_properties() if what == "all": # Fetch to update remote branches (and correctly detect new dangling local) target.git.fetch() if what in "local all": if not target.git.local_cleanable_branches: print(" No local branches can be cleaned") else: total = len(target.git.local_cleanable_branches) cleaned = 0 for branch in target.git.local_cleanable_branches: if branch == target.git.branches.current: fallback = target.git.fallback_branch() if not fallback: print( "Skipping branch '%s', can't determine fallback branch" % target.git.branches.current) continue run_git(target, True, "checkout", fallback) run_git(target, True, "pull") cleaned += run_git(target, False, "branch", "--delete", branch) total_cleaned += cleaned if cleaned == total: print( runez.bold("%s cleaned" % runez.plural(cleaned, "local branch"))) else: print( runez.orange("%s/%s local branches cleaned" % (cleaned, total))) target.git.reset_cached_properties() if total_cleaned: print(target.header())