haraka 0.2.14__py3-none-any.whl → 0.2.15__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -14,6 +14,7 @@ class PostGenConfig:
14
14
  description: str
15
15
  project_dir: Path
16
16
  create_repo: bool
17
+ verbose: bool = False
17
18
 
18
19
 
19
20
  def load_manifest(variant: str) -> list[str]:
haraka/post_gen/runner.py CHANGED
@@ -1,5 +1,3 @@
1
- from post_gen.gen_ascii.ascii_art import goLang
2
-
3
1
  from haraka.art.create import Create
4
2
  from haraka.art.ascii.assets import *
5
3
  from .config import PostGenConfig
@@ -13,35 +11,61 @@ from haraka.post_gen.service.gitops import GitOps
13
11
  def main(cfg: PostGenConfig) -> None:
14
12
 
15
13
  _logger = Logger(cfg.variant)
16
- logger = _logger.start_logger()
14
+
15
+ logger = _logger.start_logger(cfg.verbose)
16
+ logger.debug("Logger instance created for variant: {cfg.variant}")
17
+ logger.debug(f"Logger started with verbosity: {cfg.verbose}")
17
18
 
18
19
  try:
19
20
  cmd = CommandRunner(logger)
21
+ logger.debug("CommandRunner initialized")
22
+
20
23
  fops = FileOps(logger)
24
+ logger.debug("FileOps initialized")
25
+
21
26
  purge = ResourcePurger(fops, logger)
27
+ logger.debug("ResourcePurger initialized with FileOps")
28
+
22
29
  git = GitOps(cmd, logger)
30
+ logger.debug("GitOps initialized with CommandRunner")
23
31
  except Exception as e:
24
32
  logger.error(f"Failed to initialize components: {e}")
25
33
  raise
26
34
 
27
35
  divider("1️⃣ / 4️⃣ – Purge template junk")
36
+ logger.debug("Starting template junk purge")
37
+
28
38
  purge.purge(cfg.variant, cfg.project_dir)
39
+ logger.debug(f"Purge completed for variant: {cfg.variant} in directory: {cfg.project_dir}")
40
+
29
41
  logger.info("Skipping git repo creation (steps 2-4)...")
42
+ logger.debug(f"Configuration for create_repo: {cfg.create_repo}")
30
43
 
31
44
  if cfg.create_repo:
32
45
 
33
46
  divider("2️⃣ / 4️⃣ – Initialise Git repo")
47
+ logger.debug("Starting Git repository initialization")
48
+
34
49
  git.init_repo(cfg.project_dir)
50
+ logger.debug(f"Git repository initialized in directory: {cfg.project_dir}")
35
51
 
36
52
  divider("3️⃣ / 4️⃣ – Commit scaffold")
53
+ logger.debug("Starting staging and initial commit")
54
+
37
55
  git.stage_commit(cfg.project_dir)
56
+ logger.debug(f"Initial commit completed in directory: {cfg.project_dir}")
38
57
 
39
58
  divider("4️⃣ / 4️⃣ – Create GitHub repo & push")
59
+ logger.debug("Starting GitHub repository creation and push")
60
+
40
61
  git.push_to_github(cfg.project_dir, cfg.author, cfg.project_slug, cfg.description)
62
+ logger.debug(f"Pushed to GitHub: Author: {cfg.author}, Slug: {cfg.project_slug}, Description: {cfg.description}")
41
63
 
42
64
  divider("🎉 Project generation complete 🎉")
65
+ logger.debug("Project generation completed")
43
66
 
44
67
  if cfg.variant == "go-grpc-protoc":
68
+ logger.debug(f"Detected variant: {cfg.variant}, beginning Go-specific steps")
45
69
  go_emoji_logo = [emoji["go"]]
46
70
  go_performance_mode = [
47
71
  goLang, divider_xl, performance_mode, divider_l, tools, divider_s,
@@ -53,5 +77,10 @@ def main(cfg: PostGenConfig) -> None:
53
77
  by, wjb_dev
54
78
  ]
55
79
  Create.emoji(go_emoji_logo)
80
+ logger.debug("Generated Go emoji logo")
81
+
56
82
  Create.ascii(go_performance_mode)
83
+ logger.debug("Generated Go performance mode ASCII art")
84
+
57
85
  Create.logo(go_fast)
86
+ logger.debug("Generated Go fast ASCII logo")
@@ -9,6 +9,7 @@ class CommandRunner:
9
9
 
10
10
  def __init__(self, logger: Logger) -> None:
11
11
  self._log = logger
12
+ self._log.debug("CommandRunner initialized with logger")
12
13
 
13
14
  def run(
14
15
  self,
@@ -18,8 +19,10 @@ class CommandRunner:
18
19
  check: bool = True,
19
20
  ) -> Optional[subprocess.CompletedProcess]:
20
21
  cmd_str = " ".join(cmd)
22
+ self._log.debug(f"Command to be run: {cmd_str}")
21
23
  self._log.info(f"Running: {cmd_str}")
22
24
  try:
25
+ self._log.debug(f"Executing command with subprocess: {cmd_str}, cwd={cwd}, check={check}")
23
26
  result = subprocess.run(
24
27
  cmd,
25
28
  cwd=str(cwd) if cwd else None,
@@ -28,13 +31,19 @@ class CommandRunner:
28
31
  stderr=subprocess.PIPE,
29
32
  text=True,
30
33
  )
34
+ self._log.debug(f"Command execution completed with return code: {result.returncode}")
31
35
  if result.stdout:
36
+
37
+ self._log.debug("Processing command stdout")
32
38
  self._log.info(f"stdout:\n{result.stdout.strip()}")
39
+
40
+ self._log.debug("Checking for command stderr")
33
41
  if result.stderr:
34
42
  self._log.warn(f"stderr:\n{result.stderr.strip()}", file=sys.stderr)
35
43
  return result
36
44
  except subprocess.CalledProcessError as e:
37
- self._log.error(f"command failed: ({cmd_str})", file=sys.stderr)
45
+ self._log.error(f"Command execution raised CalledProcessError: ({cmd_str})", file=sys.stderr)
46
+ self._log.debug(f"Return code: {e.returncode}, stdout: {e.stdout}, stderr: {e.stderr}")
38
47
  if e.stdout:
39
48
  self._log.error(f"stdout:\n{e.stdout.strip()}", file=sys.stderr)
40
49
  if e.stderr:
@@ -43,7 +52,8 @@ class CommandRunner:
43
52
  sys.exit(e.returncode)
44
53
  return None
45
54
  except FileNotFoundError:
46
- self._log.error(f"command not found: {cmd[0]}", file=sys.stderr)
55
+ self._log.error(f"Command not found: {cmd[0]}", file=sys.stderr)
56
+ self._log.debug(f"Ensure that the command '{cmd[0]}' is installed and available in PATH")
47
57
  if check:
48
58
  sys.exit(1)
49
59
  return None
@@ -15,31 +15,49 @@ class FileOps:
15
15
  return str(path) if self.test_mode else f"<non-project-path>: {path}"
16
16
 
17
17
  def remove_file(self, path: Path) -> None:
18
+ self.logger.debug(f"Attempting to remove file: {self._relpath(path)}")
18
19
  try:
19
20
  if path.is_file():
21
+ self.logger.debug(f"Confirmed {self._relpath(path)} is a file")
22
+
20
23
  path.unlink()
24
+ self.logger.debug(f"File {self._relpath(path)} successfully unlinked")
21
25
  self.logger.info(f"Removed file: {self._relpath(path)}")
22
26
  elif path.is_dir():
27
+ self.logger.debug(f"{self._relpath(path)} is a directory, not a file. Proceeding to remove it as a directory")
28
+
23
29
  shutil.rmtree(path, ignore_errors=True)
30
+ self.logger.debug(f"Directory {self._relpath(path)} removed when file was expected")
24
31
  self.logger.info(f"Removed directory (expected file): {self._relpath(path)}")
25
32
  except Exception as e:
33
+ self.logger.error(f"Error occurred while attempting to remove file {self._relpath(path)}: {e}")
26
34
  self.logger.warn(f"Could not remove {self._relpath(path)}: {e}")
27
35
 
28
36
  def remove_dir(self, path: Path) -> None:
37
+ self.logger.debug(f"Attempting to remove directory: {self._relpath(path)}")
38
+
29
39
  if path.exists():
40
+ self.logger.debug(f"Confirmed {self._relpath(path)} exists")
30
41
  try:
31
42
  shutil.rmtree(path, ignore_errors=True)
43
+ self.logger.debug(f"Directory {self._relpath(path)} successfully removed")
32
44
  self.logger.info(f"Removed directory: {self._relpath(path)}")
33
45
  except Exception as e:
46
+ self.logger.error(f"Error occurred while attempting to remove directory {self._relpath(path)}: {e}")
34
47
  self.logger.warn(f"Could not remove directory {self._relpath(path)}: {e}")
35
48
 
36
49
  def print_tree(self, path: Path, prefix: str = "") -> None:
50
+ self.logger.debug(f"Printing directory tree starting at: {self._relpath(path)}")
51
+
37
52
  if not path.exists():
53
+ self.logger.debug(f"Path {self._relpath(path)} does not exist")
38
54
  self.logger.warn(f"Path does not exist: {path}")
39
55
  return
40
56
  entries = sorted(path.iterdir(), key=lambda p: (p.is_file(), p.name.lower()))
57
+ self.logger.debug(f"Found {len(entries)} entries in directory {self._relpath(path)}")
41
58
  for i, entry in enumerate(entries):
42
59
  branch = "└── " if i == len(entries) - 1 else "├── "
60
+ self.logger.debug(f"Processing entry: {entry.name} ({'directory' if entry.is_dir() else 'file'})")
43
61
  print(prefix + branch + entry.name)
44
62
  if entry.is_dir():
45
63
  ext = " " if i == len(entries) - 1 else "│ "
@@ -14,47 +14,75 @@ class GitOps:
14
14
 
15
15
  # ------------- public API ----------------------------------------- #
16
16
  def init_repo(self, project_dir: Path) -> None:
17
+ self._log.debug(f"Checking if {project_dir}/.git exists…")
17
18
  if not (project_dir / ".git").exists():
19
+
18
20
  self._log.info("Initializing Git repository…")
21
+ self._log.debug(f"Running 'git init' in {project_dir}…")
22
+
19
23
  self._r.run(["git", "init"], cwd=project_dir)
24
+ self._log.debug("Git repository initialized successfully.")
25
+
26
+ self._log.debug("Setting default branch to 'main'…")
20
27
  self._r.run(["git", "branch", "-M", "main"], cwd=project_dir)
28
+ self._log.debug("Default branch set successfully.")
21
29
  else:
22
- self._log.warn(".git already exists; skipping git init.")
30
+ self._log.warn(f"{project_dir}/.git already exists; skipping git init.")
23
31
 
24
32
  def stage_commit(self, project_dir: Path) -> None:
25
33
  self._log.info("Staging files…")
34
+ self._log.debug(f"Running 'git add .' in {project_dir}…")
35
+
26
36
  self._r.run(["git", "add", "."], cwd=project_dir)
37
+ self._log.debug("Files staged successfully.")
38
+
39
+ self._log.debug("Committing changes with message: 'Initial commit'…")
27
40
  res = self._r.run(["git", "commit", "-m", "Initial commit"],
28
41
  cwd=project_dir, check=False)
29
42
  if not res or res.returncode:
30
- self._log.error("'git commit' failed (maybe nothing to commit); continuing…", file=sys.stderr)
43
+ self._log.error("'git commit' failed (perhaps nothing to commit); continuing…", file=sys.stderr)
44
+ else:
45
+ self._log.debug("'git commit' executed successfully.")
31
46
 
32
47
  def push_to_github(self, project_dir: Path, author: str,
33
48
  slug: str, description: str) -> None:
49
+ self._log.debug("Checking if GitHub CLI ('gh') is installed…")
34
50
  if not self._has_gh():
51
+ self._log.warn("GitHub CLI ('gh') not found; skipping.")
35
52
  return
53
+ self._log.debug(f"Checking existing remotes in {project_dir}…")
36
54
  if "origin" in self._current_remotes(project_dir):
55
+ self._log.debug("Remote 'origin' already exists; skipping creation.")
37
56
  self._log.warn("Remote 'origin' already exists; skipping create.")
38
57
  return
58
+ self._log.debug("No existing 'origin' found; proceeding to create a new GitHub repo.")
39
59
  repo = f"{author}/{slug}"
60
+ self._log.debug(f"Prepared repository slug: {repo}")
61
+
40
62
  self._log.info(f"Creating GitHub repo {repo} & pushing…")
63
+ self._log.debug(f"Running 'gh repo create' for {repo} with description: '{description}'…")
41
64
  self._r.run([
42
65
  "gh", "repo", "create", repo,
43
66
  "--public", "--description", description,
44
67
  "--source", ".", "--remote", "origin", "--push", "--confirm"
45
68
  ], cwd=project_dir)
69
+ self._log.debug(f"GitHub repo {repo} created and code pushed successfully.")
46
70
 
47
71
  # ------------- internals ------------------------------------------ #
48
- @staticmethod
49
- def _current_remotes(project_dir: Path):
72
+ def _current_remotes(self, project_dir: Path):
50
73
  try:
74
+ self._log.debug(f"Fetching remotes for repository at {project_dir}…")
51
75
  res = subprocess.run(["git", "remote"], cwd=project_dir,
52
76
  check=True, text=True,
53
77
  stdout=subprocess.PIPE)
54
- return [r.strip() for r in res.stdout.splitlines()]
78
+ remotes = [r.strip() for r in res.stdout.splitlines()]
79
+ self._log.debug(f"Found remotes: {remotes}")
80
+ return remotes
55
81
  except subprocess.CalledProcessError:
82
+ self._log.warn("Failed to fetch remotes; returning an empty list.")
56
83
  return []
57
84
 
58
- @staticmethod
59
- def _has_gh() -> bool:
60
- return shutil.which("gh") is not None
85
+ def _has_gh(self) -> bool:
86
+ result = shutil.which("gh") is not None
87
+ self._log.debug(f"'gh' command found: {result}")
88
+ return result
@@ -42,6 +42,7 @@ class ResourcePurger:
42
42
  def __init__(self, fops: FileOps, logger: Logger | None = None) -> None:
43
43
  self._f = fops
44
44
  self._log = logger or Logger("ResourcePurger")
45
+ self._log.debug("ResourcePurger initialized with FileOps instance and Logger.")
45
46
 
46
47
  # ------------------------------ public API ----------------------------- #
47
48
  def purge(self, variant: str, project_dir: Path) -> None:
@@ -57,13 +58,20 @@ class ResourcePurger:
57
58
  """
58
59
  variant = variant.lower()
59
60
  self._log.info(f"Starting purge for variant: {variant}")
61
+ self._log.debug(f"Loaded variant for purge: {variant}")
60
62
 
61
63
  keep_patterns = config.load_manifest(variant)
64
+ self._log.debug(f"Loaded manifest for variant '{variant}': {keep_patterns}")
65
+
62
66
  spec = config.build_spec(keep_patterns)
67
+ self._log.debug(f"Built PathSpec for keep patterns. Total patterns: {len(keep_patterns)}")
63
68
 
64
69
  self._log.info(f"Keeping {len(keep_patterns)} pattern(s)")
70
+ for pattern in keep_patterns:
71
+ self._log.debug(f"Keep pattern: {pattern}")
65
72
 
66
73
  self._purge_unrelated(project_dir, spec)
74
+ self._log.debug(f"Finished purging unrelated paths in project directory: {project_dir}")
67
75
 
68
76
  divider("Project tree after purge…")
69
77
  self._f.print_tree(project_dir)
@@ -73,13 +81,20 @@ class ResourcePurger:
73
81
  """
74
82
  Walk the project tree; delete every path NOT matched by *spec*.
75
83
  """
84
+
76
85
  for path in root.rglob("*"):
86
+ self._log.debug(f"Scanning path: {path}")
77
87
  rel = path.relative_to(root).as_posix()
88
+ self._log.debug(f"Relative path for inspection: {rel}")
78
89
 
79
90
  if spec.match_file(rel):
80
- continue # keep it
91
+ self._log.debug(f"Path matches keep patterns: {rel}")
92
+ self._log.debug(f"Keeping: {rel}")
93
+ continue
81
94
 
82
95
  if path.is_dir():
83
96
  self._f.remove_dir(path)
97
+ self._log.debug(f"Deleted directory: {path}")
84
98
  else:
85
99
  self._f.remove_file(path)
100
+ self._log.debug(f"Deleted file: {path}")
@@ -4,16 +4,21 @@ from typing import TextIO
4
4
 
5
5
 
6
6
  class Logger:
7
- def __init__(self, label: str = ""):
7
+ def __init__(self, label: str = "", verbose: bool = False) -> None:
8
8
  self.label = label
9
+ self.verbose = verbose
9
10
 
10
- def start_logger(self) -> Logger:
11
+ def start_logger(self, verbose) -> Logger:
11
12
  label = Logger.get_label(self.label)
12
- return Logger(label)
13
+ return Logger(label, verbose)
13
14
 
14
15
  def info(self, msg: str) -> None:
15
16
  print(f"{self.label} INFO: {msg}")
16
17
 
18
+ def debug(self, msg: str) -> None:
19
+ if self.verbose:
20
+ print(f"🔴 DEBUG: {msg}")
21
+
17
22
  def warn(self, msg: str, file: TextIO = sys.stderr) -> None:
18
23
  print(f"{self.label} WARNING: {msg}", file=file)
19
24
 
@@ -2,15 +2,15 @@ variant: go-grpc-protoc
2
2
 
3
3
  keep:
4
4
  # ── application source (Go) ─────────────────────────
5
- - cmd/** # main binary entry-point
6
- - internal/** # config, handlers, server logic
7
- - pkg/proto/** # generated protobuf / proto defs
8
- - configs/** # runtime config files
5
+ - cmd/**
6
+ - internal/**
7
+ - pkg/proto/**
8
+ - configs/**
9
9
  - go.mod
10
10
  - git.ignore
11
11
 
12
12
  # ── tests ───────────────────────────────────────────
13
- - test/** # Go integration + unit tests
13
+ - test/**
14
14
 
15
15
  # ── build & project metadata ────────────────────────
16
16
  - Makefile
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: haraka
3
- Version: 0.2.14
3
+ Version: 0.2.15
4
4
  Summary: Reusable post-generation helper for Cookiecutter micro-service templates
5
5
  Author-email: Will Burks <will@example.com>
6
6
  License: MIT
@@ -8,25 +8,25 @@ haraka/art/ascii/frame/border.py,sha256=n7qaLxzzBmf8ewatbe5gN97DO4afZM67bBjpU1Oq
8
8
  haraka/art/ascii/frame/framer.py,sha256=s_-lsb-hGhlH_73q5iTtk8KHW0MSeSHSAvNFVmL1c9A,3951
9
9
  haraka/art/ascii/frame/width_utils.py,sha256=in4AV3gTBBXUX6N01j67Icu9vsHFomybBN8rpL3sQ5s,810
10
10
  haraka/post_gen/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
- haraka/post_gen/runner.py,sha256=J568noKQ3Cv44MzkFA3d2Vbwl6Tt8VPCfRj3_Y-hZSM,1923
11
+ haraka/post_gen/runner.py,sha256=OLdB3ugDkBzeGBoVZ8RgxOHEy4aNT4icD10a1WwnyOo,3248
12
12
  haraka/post_gen/config/__init__.py,sha256=-_XkJ_Dni28yJCMfIceQSiH1wa_hHsZMoBTyvR9Ikbc,62
13
- haraka/post_gen/config/config.py,sha256=iC0p3UYrx-miszn_pUNX86kP499tG-CBYYlEn-xOfQM,1284
13
+ haraka/post_gen/config/config.py,sha256=nmsQ19ONWE76GFQtah_PUO1HygE-ZCCOfK03M-t-kMs,1310
14
14
  haraka/post_gen/service/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
15
  haraka/post_gen/service/assets.py,sha256=-oz3vlBaVOjeCOO2QHRvOmfyurR1KgihqXqbTtxDNG4,461
16
- haraka/post_gen/service/command.py,sha256=5yxPSQmQpzAyPNoLtzxaUz1VWgPnTfD7XlkBUu5rIeI,1672
17
- haraka/post_gen/service/files.py,sha256=1hPHn_nYI5A2JKJqxmNFK5fpo9DNPcGR4K7qCPOO5GE,1993
18
- haraka/post_gen/service/gitops.py,sha256=hRnztg_5yW3L_bdh05gcgGQ25wwjSTn8pX1sja-m91s,2434
19
- haraka/post_gen/service/purge.py,sha256=cNs2pHkXN1EJS7RIGR7pAmqJcyQ4eUbhkGpPqrq0Zv4,2656
16
+ haraka/post_gen/service/command.py,sha256=yvEzW9rMSXWeQ_2DXuQNtGH6sRuZnK6viMud-amFQXw,2348
17
+ haraka/post_gen/service/files.py,sha256=fXhuCqp9iW34hEDKbZynHxB_QlZdxPHyKtLDV_KwG2I,3314
18
+ haraka/post_gen/service/gitops.py,sha256=UHJFEGrz88NTB0-WKge_AiKO_yLFci6A7AM5kBIy0VQ,4029
19
+ haraka/post_gen/service/purge.py,sha256=eDH5DLwaViu3jsqtPXeJWxS_Fs7RIEq6RKD8A8ny2vE,3536
20
20
  haraka/utils/__init__.py,sha256=3Cp4u0dyAGcmqes-tIr95KFsNsJx5Ji5Yzkto9546Hs,168
21
21
  haraka/utils/common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
22
22
  haraka/utils/common/utils.py,sha256=kMnMXe_hcxGkD0MKGmR1lIwsRND7BaFPRbGN4PwonfM,360
23
23
  haraka/utils/logging/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
24
- haraka/utils/logging/log_util.py,sha256=S876RggZrLVCLlsEM5i7w1PGKCvR1WECykwtJW83NO0,775
24
+ haraka/utils/logging/log_util.py,sha256=Pf5uuxPfeaQDygOy3AWVj2W_4QSKM6V4FCHCJk6KypY,961
25
25
  haraka/utils/manifests/go-grpc-gateway-buf.yml,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
26
- haraka/utils/manifests/go-grpc-protoc.yml,sha256=IqW6lQrB-UTmfQHYP4EaVK6NU6HknykXM7KxJPVENog,1049
26
+ haraka/utils/manifests/go-grpc-protoc.yml,sha256=L_6zKnEpHHkEZfRy4PYhJqgle7lqDmcUVbnFjgK4WTI,839
27
27
  haraka/utils/manifests/java-springboot.yml,sha256=PRqxw9Ygop19hH-TRH31UL1HfEYEJaTq5ukaZX18UNQ,811
28
28
  haraka/utils/manifests/python-fastapi.yml,sha256=650oXVlLV4JE9_fg_YAr37BsGSLb3KRdc6D759Zv1W0,814
29
- haraka-0.2.14.dist-info/METADATA,sha256=6vTRWJdGBXnGg2T0DO-w-CJ4cruD_MCI_vkeguPWJyo,579
30
- haraka-0.2.14.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
31
- haraka-0.2.14.dist-info/top_level.txt,sha256=1khpwypLKWoklVd_CgFiwAfcctVSXRoRPc3BI9lyIXo,7
32
- haraka-0.2.14.dist-info/RECORD,,
29
+ haraka-0.2.15.dist-info/METADATA,sha256=M9taSx27sjz-qp0EeCHN6t3iv0RBCHOdIGDcaLZgy_A,579
30
+ haraka-0.2.15.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
31
+ haraka-0.2.15.dist-info/top_level.txt,sha256=1khpwypLKWoklVd_CgFiwAfcctVSXRoRPc3BI9lyIXo,7
32
+ haraka-0.2.15.dist-info/RECORD,,