Skip to content

Commit da3450d

Browse files
jondricekclaude
andcommitted
refactor: Extract changelog_helper functions into focused modules
Reorganize changelog_helper.py by extracting functions into separate modules for better code organization and maintainability: - Created cli/ module with argument parsing functionality - Created formatters/ module with HTML and layer output generation - Extracted core classes (AttackChangesEncoder, DiffStix, DomainStatistics) into separate files within core/ directory The changelog_helper.py file now serves as the main entry point, containing only main() and get_new_changelog_md() functions, while re-exporting all previously exported functions for backward compatibility. All 133 tests pass successfully. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent deb24fd commit da3450d

9 files changed

Lines changed: 2173 additions & 2041 deletions

File tree

mitreattack/diffStix/changelog_helper.py

Lines changed: 45 additions & 2041 deletions
Large diffs are not rendered by default.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
"""CLI utilities for changelog_helper."""
2+
3+
from mitreattack.diffStix.cli.argument_parser import get_parsed_args
4+
5+
__all__ = ["get_parsed_args"]
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
"""Command-line argument parser for changelog_helper."""
2+
3+
import argparse
4+
5+
from loguru import logger
6+
from tqdm import tqdm
7+
8+
from mitreattack.diffStix.utils.constants import LAYER_DEFAULTS as layer_defaults
9+
10+
11+
def get_parsed_args():
12+
"""Create argument parser and parse arguments."""
13+
parser = argparse.ArgumentParser(
14+
description=(
15+
"Create changelog reports on the differences between two versions of the ATT&CK content. "
16+
"Takes STIX bundles as input. For default operation, put "
17+
"enterprise-attack.json, mobile-attack.json, and ics-attack.json bundles "
18+
"in 'old' and 'new' folders for the script to compare."
19+
)
20+
)
21+
22+
parser.add_argument(
23+
"--old",
24+
type=str,
25+
# Default is really "old", set below
26+
default=None,
27+
help="Directory to load old STIX data from.",
28+
)
29+
30+
parser.add_argument(
31+
"--new",
32+
type=str,
33+
default="new",
34+
help="Directory to load new STIX data from.",
35+
)
36+
37+
parser.add_argument(
38+
"--domains",
39+
type=str,
40+
nargs="+",
41+
choices=["enterprise-attack", "mobile-attack", "ics-attack"],
42+
default=["enterprise-attack", "mobile-attack", "ics-attack"],
43+
help="Which domains to report on. Choices (and defaults) are %(choices)s",
44+
)
45+
46+
parser.add_argument(
47+
"--markdown-file",
48+
type=str,
49+
help="Create a markdown file reporting changes.",
50+
)
51+
52+
parser.add_argument(
53+
"--html-file",
54+
type=str,
55+
help="Create HTML page from markdown content.",
56+
)
57+
58+
parser.add_argument(
59+
"--html-file-detailed",
60+
type=str,
61+
help="Create an HTML file reporting detailed changes.",
62+
)
63+
64+
parser.add_argument(
65+
"--json-file",
66+
type=str,
67+
help="Create a JSON file reporting changes.",
68+
)
69+
70+
parser.add_argument(
71+
"--layers",
72+
type=str,
73+
nargs="*",
74+
help=f"""
75+
Create layer files showing changes in each domain
76+
expected order of filenames is 'enterprise', 'mobile', 'ics', 'pre attack'.
77+
If values are unspecified, defaults to {", ".join(layer_defaults)}
78+
""",
79+
)
80+
81+
parser.add_argument(
82+
"--site_prefix",
83+
type=str,
84+
default="",
85+
help="Prefix links in markdown output, e.g. [prefix]/techniques/T1484",
86+
)
87+
88+
parser.add_argument(
89+
"--unchanged",
90+
action="store_true",
91+
help="Show objects without changes in the markdown output",
92+
)
93+
94+
parser.add_argument(
95+
"--use-mitre-cti",
96+
action="store_true",
97+
help="Use content from the MITRE CTI repo for the -old data",
98+
)
99+
100+
parser.add_argument(
101+
"--show-key",
102+
action="store_true",
103+
help="Add a key explaining the change types to the markdown",
104+
)
105+
106+
parser.add_argument(
107+
"--contributors",
108+
action="store_true",
109+
help="Show new contributors between releases",
110+
)
111+
parser.add_argument(
112+
"--no-contributors",
113+
dest="contributors",
114+
action="store_false",
115+
help="Do not show new contributors between releases",
116+
)
117+
parser.set_defaults(contributors=True)
118+
119+
parser.add_argument(
120+
"-v",
121+
"--verbose",
122+
action="store_true",
123+
help="Print status messages",
124+
)
125+
126+
args = parser.parse_args()
127+
128+
# the default loguru logger logs up to Debug by default
129+
logger.remove()
130+
if args.verbose:
131+
logger.add(lambda msg: tqdm.write(msg, end=""), colorize=True)
132+
else:
133+
logger.add(lambda msg: tqdm.write(msg, end=""), colorize=True, level="INFO")
134+
135+
if args.use_mitre_cti and args.old:
136+
parser.error("--use-mitre-cti and -old cannot be used together")
137+
138+
# set a default directory that doesn't conflict with use_mitre_cti
139+
if not args.old:
140+
args.old = "old"
141+
142+
if args.layers is not None:
143+
if len(args.layers) not in [0, 3]:
144+
parser.error("-layers requires exactly three files to be specified or none at all")
145+
146+
return args
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
"""Custom JSON encoder for ATT&CK changelog generation."""
2+
3+
import json
4+
5+
from mitreattack.diffStix.utils.version_utils import AttackObjectVersion
6+
7+
8+
# TODO: Implement a custom decoder as well. Possible solution at this link
9+
# https://alexisgomes19.medium.com/custom-json-encoder-with-python-f52c91b48cd2
10+
class AttackChangesEncoder(json.JSONEncoder):
11+
"""Custom JSON encoder for changes made to ATT&CK between releases."""
12+
13+
def default(self, o):
14+
"""Handle custom object types so they can be serialized to JSON."""
15+
if isinstance(o, AttackObjectVersion):
16+
return str(o)
17+
18+
return json.JSONEncoder.default(self, o)

0 commit comments

Comments
 (0)