python-kacl 0.6.4__py3-none-any.whl → 0.6.6__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.
kacl/__init__.py CHANGED
@@ -1,5 +1,5 @@
1
1
  # Version of the python-kacl package
2
- __version__ = "0.6.4"
2
+ __version__ = "0.6.6"
3
3
 
4
4
  from kacl.document import KACLDocument
5
5
  from kacl.serializer import KACLMarkdownSerializer
@@ -14,14 +14,14 @@ def load(file):
14
14
  with open(file, "r") as f:
15
15
  document = f.read()
16
16
  try:
17
- doc = KACLDocument.parse(document)
17
+ doc = KACLDocument.parse(document, file)
18
18
  finally:
19
19
  f.close()
20
20
  return doc
21
21
 
22
22
 
23
- def parse(text):
24
- return KACLDocument.parse(text)
23
+ def parse(text, file=None):
24
+ return KACLDocument.parse(text, file)
25
25
 
26
26
 
27
27
  def dump(document):
kacl/changes.py CHANGED
@@ -11,6 +11,8 @@ class KACLChanges(KACLElement):
11
11
  line_number=element.line_number(),
12
12
  )
13
13
  self.__items = []
14
+ # initializes
15
+ self.items()
14
16
 
15
17
  def items(self):
16
18
  if not len(self.__items) and len(self.body().strip()):
@@ -45,4 +45,6 @@ kacl:
45
45
  ## 🧭 Reference
46
46
 
47
47
  Code: [Source Code Management System]({link})
48
-
48
+ stash:
49
+ directory: .kacl_stash
50
+ always: False
kacl/config.py CHANGED
@@ -72,6 +72,9 @@ class KACLConfig:
72
72
  if not self.issue_tracker_jira_password:
73
73
  self.issue_tracker_jira_password = os.getenv("JIRA_PASSWORD", None)
74
74
 
75
+ self.stash_dir = self.__config.get("stash", {}).get("dir", ".kacl_stash")
76
+ self.stash_always = self.__config.get("stash", {}).get("always", False)
77
+
75
78
  @staticmethod
76
79
  def merge(a, b, path=None):
77
80
  """merge two dictionaries
kacl/document.py CHANGED
@@ -4,6 +4,7 @@ import os
4
4
  import git
5
5
  import semver
6
6
 
7
+ import kacl
7
8
  from kacl.config import KACLConfig
8
9
  from kacl.element import KACLElement
9
10
  from kacl.exception import KACLException
@@ -28,6 +29,7 @@ class KACLDocument:
28
29
  versions=None,
29
30
  link_references=None,
30
31
  meta_data=None,
32
+ file_path=None,
31
33
  config=KACLConfig(),
32
34
  ):
33
35
  self.__data = data
@@ -38,6 +40,80 @@ class KACLDocument:
38
40
  self.__link_references = dict()
39
41
  self.__meta_data = meta_data
40
42
  self.config = config
43
+ self.stash = []
44
+ self.file_path = file_path
45
+
46
+ @property
47
+ def stash_directory(self):
48
+ return self._ensure_stash_directory()
49
+
50
+ def load_stash(self):
51
+ self._refresh_stash()
52
+ # get all unreleased changes from stash
53
+ for stashed_changelog in self.stash:
54
+ unreleased_stash_version = stashed_changelog.get("Unreleased")
55
+ for title, change in unreleased_stash_version.sections().items():
56
+ for change_item in change.items():
57
+ self.add(section=title, data=change_item)
58
+
59
+ def _refresh_stash(self):
60
+ if not self.file_path:
61
+ return
62
+
63
+ self.stash = []
64
+ if self.file_path is None:
65
+ local_stash_dir = os.path.join(
66
+ os.path.abspath(os.getcwd()), self.config.stash_dir
67
+ )
68
+ else:
69
+ # the stash needs always to be relative to the current file_path
70
+ local_stash_dir = os.path.join(
71
+ os.path.abspath(os.path.dirname(self.file_path)), self.config.stash_dir
72
+ )
73
+
74
+ if not os.path.isdir(local_stash_dir):
75
+ return
76
+
77
+ # read all md files in the stash directory
78
+ for f in os.listdir(local_stash_dir):
79
+ if f.endswith(".md"):
80
+ md_file = os.path.join(local_stash_dir, f)
81
+ with open(md_file, "r") as fh:
82
+ data = fh.read()
83
+ try:
84
+ doc = KACLDocument.parse(data, None)
85
+ self.stash.append(doc)
86
+ except Exception:
87
+ continue
88
+
89
+ def _ensure_stash_directory(self):
90
+ # the stash needs always to be relative to the current file_path
91
+ local_stash_dir = os.path.join(
92
+ os.path.abspath(os.path.dirname(self.file_path)), self.config.stash_dir
93
+ )
94
+
95
+ if not os.path.isdir(local_stash_dir):
96
+ os.makedirs(local_stash_dir, exist_ok=True)
97
+
98
+ return local_stash_dir
99
+
100
+ def _get_stash_file(self):
101
+ # the stash file should be named after the current git branch
102
+ local_stash_dir = self._ensure_stash_directory()
103
+ try:
104
+ repo = git.Repo(
105
+ os.path.abspath(os.path.dirname(self.file_path)),
106
+ search_parent_directories=True,
107
+ )
108
+ branch_name = repo.active_branch.name
109
+ # sanitize branch name for filename
110
+ branch_name = branch_name.replace("/", "__").replace("\\", "_")
111
+ stash_file_name = f"{branch_name}.md"
112
+ except Exception:
113
+ # fallback to with date
114
+ stash_file_name = f"{datetime.datetime.now().strftime('%Y%m%d')}.md"
115
+
116
+ return os.path.join(local_stash_dir, stash_file_name)
41
117
 
42
118
  def validate(self):
43
119
  """Validates the current changelog and returns KACLValidation object containing all information
@@ -46,6 +122,14 @@ class KACLDocument:
46
122
  [KACLValidation] -- object holding all error information
47
123
  """
48
124
  validation = KACLValidation()
125
+
126
+ # first load the stash
127
+ self._refresh_stash()
128
+ for stashed_changelog in self.stash:
129
+ val = stashed_changelog.validate()
130
+ for e in val.errors():
131
+ validation.add(e)
132
+
49
133
  # 1. assert only one header and starts on first line
50
134
  if len(self.__headers) == 0:
51
135
  validation.add_error(
@@ -213,18 +297,38 @@ class KACLDocument:
213
297
 
214
298
  return False
215
299
 
216
- def add(self, section, data):
300
+ def add(self, section, data, stash=False):
217
301
  """adds a new change to a given section in the 'unreleased' version
218
302
 
219
303
  Arguments:
220
304
  section {[str]} -- section to add data to
221
305
  data {[str]} -- change information
306
+ stash {[bool]} -- if true the change will be stashed instead of added to the changelog (default: {False})
222
307
  """
223
- unreleased_version = self.get("Unreleased")
224
- if unreleased_version is None:
225
- unreleased_version = KACLVersion(version="Unreleased")
226
- self.__versions.insert(0, unreleased_version)
227
- unreleased_version.add(section.capitalize(), data)
308
+
309
+ if stash:
310
+ # check if the stash directory already contains the required changelog file, if not initialize it
311
+ stash_file = self._get_stash_file()
312
+
313
+ if not os.path.exists(stash_file):
314
+ kacl_changelog = kacl.new()
315
+ kacl_changelog_content = kacl.dump(kacl_changelog)
316
+ with open(stash_file, "w") as f:
317
+ f.write(kacl_changelog_content)
318
+ f.close()
319
+
320
+ kacl_changelog = kacl.load(stash_file)
321
+ kacl_changelog.add(section.capitalize(), data)
322
+ kacl_changelog_content = kacl.dump(kacl_changelog)
323
+ with open(stash_file, "w") as f:
324
+ f.write(kacl_changelog_content)
325
+ f.close()
326
+ else:
327
+ unreleased_version = self.get("Unreleased")
328
+ if unreleased_version is None:
329
+ unreleased_version = KACLVersion(version="Unreleased")
330
+ self.__versions.insert(0, unreleased_version)
331
+ unreleased_version.add(section.capitalize(), data)
228
332
 
229
333
  def release(
230
334
  self,
@@ -584,7 +688,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
584
688
  )
585
689
 
586
690
  @staticmethod
587
- def parse(data):
691
+ def parse(data, file=None):
588
692
  """Parses a given text object and returns the KACLDocument
589
693
 
590
694
  Arguments:
@@ -624,6 +728,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
624
728
  versions=versions,
625
729
  link_references=link_references,
626
730
  meta_data=meta_data,
731
+ file_path=file,
627
732
  )
628
733
 
629
734
  def __get_link_provider(
kacl/kacl_cli.py CHANGED
@@ -69,6 +69,7 @@ def load_changelog(ctx):
69
69
  # read the changelog
70
70
  kacl_changelog = kacl.load(kacl_config.changelog_file_path)
71
71
  kacl_changelog.config = kacl_config
72
+ kacl_changelog.load_stash()
72
73
 
73
74
  # share the objects
74
75
  return kacl_changelog
@@ -128,14 +129,28 @@ def cli(ctx, version=None, config=None, file=None):
128
129
  is_flag=True,
129
130
  help="This option will add the changes directly into changelog file.",
130
131
  )
131
- def add(ctx, section, message, modify):
132
+ @click.option(
133
+ "-s",
134
+ "--stash",
135
+ is_flag=True,
136
+ help="This option will stash changes instead of applying them.",
137
+ )
138
+ def add(ctx, section, message, modify, stash):
132
139
  """Adds a given message to a specified unreleased section. Use '--modify' to directly modify the changelog file."""
133
140
  kacl_changelog = load_changelog(ctx)
134
141
 
142
+ if not modify:
143
+ stash = False
144
+ else:
145
+ # check if we need to stash
146
+ stash = stash or kacl_changelog.config.stash_always
147
+
135
148
  # add changes to changelog
136
- kacl_changelog.add(section=section, data=message)
149
+ kacl_changelog.add(section=section, data=message, stash=stash)
150
+
137
151
  kacl_changelog_content = kacl.dump(kacl_changelog)
138
- if modify:
152
+ # if stashing is enabled, do not modify the changelog file
153
+ if modify and not stash:
139
154
  with open(kacl_changelog.config.changelog_file_path, "w") as f:
140
155
  f.write(kacl_changelog_content)
141
156
  f.close()
@@ -472,6 +487,13 @@ def release(
472
487
  if no_commit:
473
488
  return
474
489
 
490
+ # delete all file in the stash directory
491
+ stash_files = []
492
+ for f in os.listdir(kacl_changelog.stash_directory):
493
+ stash_file = os.path.join(kacl_changelog.stash_directory, f)
494
+ os.remove(stash_file)
495
+ stash_files.append(stash_file)
496
+
475
497
  if commit or tag or kacl_config.git_create_commit or kacl_config.git_create_tag:
476
498
  vcs_context = {
477
499
  "latest_version": latest_version,
@@ -516,6 +538,11 @@ def release(
516
538
  repo.git.add(kacl_config.changelog_file_path)
517
539
  for f in kacl_config.git_commit_additional_files:
518
540
  repo.git.add(f)
541
+ for f in stash_files:
542
+ try:
543
+ repo.git.add(f)
544
+ except git.GitCommandError:
545
+ pass
519
546
  repo.git.commit("-m", commit_message.format(**vcs_context))
520
547
 
521
548
  if tag or kacl_config.git_create_tag:
@@ -618,6 +645,52 @@ def new(output_file):
618
645
  click.echo(kacl_changelog_content)
619
646
 
620
647
 
648
+ @cli.command()
649
+ @click.option(
650
+ "-f",
651
+ "--force",
652
+ required=False,
653
+ is_flag=True,
654
+ help="Will overwrite existing files.",
655
+ )
656
+ def init(force):
657
+ """Initializes a project with all necessary kacl setting."""
658
+ kacl_changelog = kacl.new()
659
+ kacl_changelog_content = kacl.dump(kacl_changelog)
660
+
661
+ if os.path.exists("CHANGELOG.md") and not force:
662
+ click.echo(
663
+ click.style("Error: ", fg="red")
664
+ + "CHANGELOG.md already exists. Use '--force' to overwrite."
665
+ )
666
+ sys.exit(1)
667
+
668
+ if os.path.exists(".kacl.yml") and not force:
669
+ click.echo(
670
+ click.style("Error: ", fg="red")
671
+ + ".kacl.yml already exists. Use '--force' to overwrite."
672
+ )
673
+ sys.exit(1)
674
+
675
+ with open("CHANGELOG.md", "w") as f:
676
+ f.write(kacl_changelog_content)
677
+ f.close()
678
+
679
+ default_config_file = os.path.join(
680
+ os.path.dirname(os.path.abspath(__file__)), "config", "kacl-default.yml"
681
+ )
682
+
683
+ # copy the default config file
684
+ if os.path.exists(default_config_file):
685
+ with open(default_config_file, "r") as df:
686
+ default_config_content = df.read()
687
+ df.close()
688
+
689
+ with open(".kacl.yml", "w") as cf:
690
+ cf.write(default_config_content)
691
+ cf.close()
692
+
693
+
621
694
  @cli.command("add-comments")
622
695
  @click.pass_context
623
696
  @click.option(
kacl/serializer.py CHANGED
@@ -19,7 +19,11 @@ class KACLMarkdownSerializer:
19
19
  data.extend(serialized_metadata)
20
20
 
21
21
  data.append(self.__serialize_header(document.header()))
22
- data.append(document.header().body())
22
+ # check if the body starts with a newline. if not add one
23
+ if document.header().body().startswith("\n"):
24
+ data.append(document.header().body())
25
+ else:
26
+ data.append("\n" + document.header().body())
23
27
 
24
28
  for version in document.versions():
25
29
  data.extend([self.__serialize_version(version), ""])
@@ -67,11 +71,12 @@ class KACLMarkdownSerializer:
67
71
  return f"# {obj.title()}"
68
72
 
69
73
  def __serialize_version(self, obj):
70
- lines = [self.__serialize_header(obj)]
74
+ lines = [self.__serialize_header(obj), ""]
71
75
  for title, changes in obj.sections().items():
72
76
  lines.extend(
73
77
  [
74
78
  self.__serialize_header(changes),
79
+ "",
75
80
  self.__serialize_list(changes.items()),
76
81
  "",
77
82
  ]