msgram 3.2.0__tar.gz → 3.2.2__tar.gz

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.
Files changed (62) hide show
  1. {msgram-3.2.0 → msgram-3.2.2}/PKG-INFO +2 -2
  2. {msgram-3.2.0 → msgram-3.2.2}/msgram.egg-info/PKG-INFO +2 -2
  3. {msgram-3.2.0 → msgram-3.2.2}/msgram.egg-info/requires.txt +1 -1
  4. {msgram-3.2.0 → msgram-3.2.2}/pyproject.toml +2 -2
  5. msgram-3.2.2/src/cli/aggregate_metrics.py +147 -0
  6. {msgram-3.2.0 → msgram-3.2.2}/src/cli/commands/cmd_calculate.py +13 -7
  7. {msgram-3.2.0 → msgram-3.2.2}/src/cli/commands/cmd_diff.py +21 -11
  8. {msgram-3.2.0 → msgram-3.2.2}/src/cli/commands/cmd_extract.py +6 -1
  9. {msgram-3.2.0 → msgram-3.2.2}/src/cli/commands/cmd_norm_diff.py +36 -8
  10. {msgram-3.2.0 → msgram-3.2.2}/src/cli/jsonReader/jsonReader.py +9 -2
  11. {msgram-3.2.0 → msgram-3.2.2}/src/cli/parsers.py +37 -20
  12. msgram-3.2.2/src/cli/resources/characteristic.py +40 -0
  13. {msgram-3.2.0 → msgram-3.2.2}/src/cli/resources/measure.py +9 -7
  14. {msgram-3.2.0 → msgram-3.2.2}/src/cli/resources/subcharacteristic.py +14 -10
  15. {msgram-3.2.0 → msgram-3.2.2}/src/cli/utils.py +22 -15
  16. {msgram-3.2.0 → msgram-3.2.2}/tests/system/test_extract.py +3 -3
  17. msgram-3.2.2/tests/unit/test_aggregate.py +168 -0
  18. {msgram-3.2.0 → msgram-3.2.2}/tests/unit/test_calculate.py +88 -49
  19. {msgram-3.2.0 → msgram-3.2.2}/tests/unit/test_diff.py +18 -53
  20. {msgram-3.2.0 → msgram-3.2.2}/tests/unit/test_jsonReader.py +3 -1
  21. {msgram-3.2.0 → msgram-3.2.2}/tests/unit/test_norm_diff.py +69 -10
  22. {msgram-3.2.0 → msgram-3.2.2}/tests/unit/test_parser.py +2 -0
  23. {msgram-3.2.0 → msgram-3.2.2}/tests/unit/test_tsqmi.py +1 -1
  24. msgram-3.2.0/src/cli/aggregate_metrics.py +0 -229
  25. msgram-3.2.0/src/cli/resources/characteristic.py +0 -37
  26. msgram-3.2.0/tests/unit/test_aggregate.py +0 -223
  27. {msgram-3.2.0 → msgram-3.2.2}/LICENSE +0 -0
  28. {msgram-3.2.0 → msgram-3.2.2}/README.md +0 -0
  29. {msgram-3.2.0 → msgram-3.2.2}/msgram.egg-info/SOURCES.txt +0 -0
  30. {msgram-3.2.0 → msgram-3.2.2}/msgram.egg-info/dependency_links.txt +0 -0
  31. {msgram-3.2.0 → msgram-3.2.2}/msgram.egg-info/entry_points.txt +0 -0
  32. {msgram-3.2.0 → msgram-3.2.2}/msgram.egg-info/top_level.txt +0 -0
  33. {msgram-3.2.0 → msgram-3.2.2}/setup.cfg +0 -0
  34. {msgram-3.2.0 → msgram-3.2.2}/src/__init__.py +0 -0
  35. {msgram-3.2.0 → msgram-3.2.2}/src/cli/__init__.py +0 -0
  36. {msgram-3.2.0 → msgram-3.2.2}/src/cli/cli.py +0 -0
  37. {msgram-3.2.0 → msgram-3.2.2}/src/cli/commands/__init__.py +0 -0
  38. {msgram-3.2.0 → msgram-3.2.2}/src/cli/commands/cmd_init.py +0 -0
  39. {msgram-3.2.0 → msgram-3.2.2}/src/cli/commands/cmd_list.py +0 -0
  40. {msgram-3.2.0 → msgram-3.2.2}/src/cli/exceptions/__init__.py +0 -0
  41. {msgram-3.2.0 → msgram-3.2.2}/src/cli/exceptions/exceptions.py +0 -0
  42. {msgram-3.2.0 → msgram-3.2.2}/src/cli/jsonReader/__init__.py +0 -0
  43. {msgram-3.2.0 → msgram-3.2.2}/src/cli/resources/__init__.py +0 -0
  44. {msgram-3.2.0 → msgram-3.2.2}/src/cli/resources/metrics.py +0 -0
  45. {msgram-3.2.0 → msgram-3.2.2}/src/cli/resources/tsqmi.py +0 -0
  46. {msgram-3.2.0 → msgram-3.2.2}/src/config/__init__.py +0 -0
  47. {msgram-3.2.0 → msgram-3.2.2}/src/config/settings.py +0 -0
  48. {msgram-3.2.0 → msgram-3.2.2}/src/config/setup_log.py +0 -0
  49. {msgram-3.2.0 → msgram-3.2.2}/tests/__init__.py +0 -0
  50. {msgram-3.2.0 → msgram-3.2.2}/tests/system/__init__.py +0 -0
  51. {msgram-3.2.0 → msgram-3.2.2}/tests/system/test_help.py +0 -0
  52. {msgram-3.2.0 → msgram-3.2.2}/tests/system/test_init.py +0 -0
  53. {msgram-3.2.0 → msgram-3.2.2}/tests/system/test_list.py +0 -0
  54. {msgram-3.2.0 → msgram-3.2.2}/tests/unit/__init__.py +0 -0
  55. {msgram-3.2.0 → msgram-3.2.2}/tests/unit/data/file_reader_response.py +0 -0
  56. {msgram-3.2.0 → msgram-3.2.2}/tests/unit/test_characteristic.py +0 -0
  57. {msgram-3.2.0 → msgram-3.2.2}/tests/unit/test_extract.py +0 -0
  58. {msgram-3.2.0 → msgram-3.2.2}/tests/unit/test_init.py +0 -0
  59. {msgram-3.2.0 → msgram-3.2.2}/tests/unit/test_list.py +0 -0
  60. {msgram-3.2.0 → msgram-3.2.2}/tests/unit/test_measures.py +0 -0
  61. {msgram-3.2.0 → msgram-3.2.2}/tests/unit/test_setup_log.py +0 -0
  62. {msgram-3.2.0 → msgram-3.2.2}/tests/unit/test_subcharacteristic.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: msgram
3
- Version: 3.2.0
3
+ Version: 3.2.2
4
4
  Summary: The msgram CLI is a command-line interface to use MeasureSoftGram software
5
5
  Author-email: MeasureSoftGram <measuresoftgram@gmail.com>
6
6
  License: GNU AFFERO GENERAL PUBLIC LICENSE
@@ -684,7 +684,7 @@ Requires-Dist: setuptools~=72.1.0
684
684
  Requires-Dist: python-dotenv
685
685
  Requires-Dist: rich
686
686
  Requires-Dist: validators~=0.33.0
687
- Requires-Dist: msgram-core~=1.4.6
687
+ Requires-Dist: msgram-core~=1.4.9
688
688
  Requires-Dist: msgram-parser~=1.2.1
689
689
  Requires-Dist: anytree
690
690
  Provides-Extra: dev
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: msgram
3
- Version: 3.2.0
3
+ Version: 3.2.2
4
4
  Summary: The msgram CLI is a command-line interface to use MeasureSoftGram software
5
5
  Author-email: MeasureSoftGram <measuresoftgram@gmail.com>
6
6
  License: GNU AFFERO GENERAL PUBLIC LICENSE
@@ -684,7 +684,7 @@ Requires-Dist: setuptools~=72.1.0
684
684
  Requires-Dist: python-dotenv
685
685
  Requires-Dist: rich
686
686
  Requires-Dist: validators~=0.33.0
687
- Requires-Dist: msgram-core~=1.4.6
687
+ Requires-Dist: msgram-core~=1.4.9
688
688
  Requires-Dist: msgram-parser~=1.2.1
689
689
  Requires-Dist: anytree
690
690
  Provides-Extra: dev
@@ -8,7 +8,7 @@ setuptools~=72.1.0
8
8
  python-dotenv
9
9
  rich
10
10
  validators~=0.33.0
11
- msgram-core~=1.4.6
11
+ msgram-core~=1.4.9
12
12
  msgram-parser~=1.2.1
13
13
  anytree
14
14
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "msgram"
7
- version = "3.2.0"
7
+ version = "3.2.2"
8
8
  description = "The msgram CLI is a command-line interface to use MeasureSoftGram software"
9
9
  readme = "README.md"
10
10
  authors = [
@@ -32,7 +32,7 @@ dependencies = [
32
32
  "python-dotenv",
33
33
  "rich",
34
34
  "validators~=0.33.0",
35
- "msgram-core~=1.4.6",
35
+ "msgram-core~=1.4.9",
36
36
  "msgram-parser~=1.2.1",
37
37
  "anytree"
38
38
  ]
@@ -0,0 +1,147 @@
1
+ import os
2
+ import json
3
+
4
+ from src.cli.utils import print_error, print_info
5
+
6
+ metrics = {}
7
+ metrics["sonar"] = [
8
+ "tests",
9
+ "test_failures",
10
+ "test_errors",
11
+ "coverage",
12
+ "test_execution_time",
13
+ "functions",
14
+ "complexity",
15
+ "comment_lines_density",
16
+ "duplicated_lines_density",
17
+ ]
18
+
19
+ metrics["github"] = [
20
+ "resolved_issues",
21
+ "total_issues",
22
+ "sum_ci_feedback_times",
23
+ "total_builds",
24
+ ]
25
+
26
+ measures = {}
27
+ measures["sonarqube"] = [
28
+ "passed_tests",
29
+ "test_builds",
30
+ "test_errors",
31
+ "test_coverage",
32
+ "non_complex_file_density",
33
+ "commented_file_density",
34
+ "duplication_absense",
35
+ ]
36
+
37
+ measures["github"] = ["team_throughput", "ci_feedback_time"]
38
+
39
+
40
+ def should_process_metrics(config):
41
+ for characteristic in config.get("characteristics", []):
42
+ for subcharacteristic in characteristic.get("subcharacteristics", []):
43
+ for measure in subcharacteristic.get("measures", []):
44
+ if (
45
+ measure.get("key") not in measures["sonarqube"]
46
+ and measure.get("key") not in measures["github"]
47
+ ):
48
+ return False
49
+ return True
50
+
51
+
52
+ def read_msgram(file_path):
53
+ try:
54
+ with open(file_path, "r") as file:
55
+ return json.load(file)
56
+ except IsADirectoryError as e:
57
+ print_error(f"> [red] Error: {e}")
58
+ return False
59
+ except FileNotFoundError as e:
60
+ print_error(f"> [red] Error: {e}")
61
+ return False
62
+
63
+
64
+ def list_msgram_files(folder_path):
65
+ try:
66
+ if not os.path.isdir(folder_path):
67
+ raise NotADirectoryError(f"{folder_path} is not a directory.")
68
+
69
+ msgram_files = [
70
+ file for file in os.listdir(folder_path) if file.endswith(".msgram")
71
+ ]
72
+ return msgram_files
73
+
74
+ except NotADirectoryError as e:
75
+ print_error(f"> [red] Error: {e}")
76
+ return False
77
+
78
+
79
+ def save_metrics(file_name, metrics):
80
+ directory = os.path.dirname(file_name)
81
+
82
+ os.makedirs(directory, exist_ok=True)
83
+
84
+ output_file_path = os.path.join(
85
+ directory, os.path.basename(file_name).replace(".msgram", ".metrics")
86
+ )
87
+ with open(output_file_path, "w") as output_file:
88
+ json.dump(metrics, output_file, indent=2)
89
+
90
+ print_info(f"> [blue] Metrics saved to: {output_file_path}\n")
91
+
92
+
93
+ def process_metrics(folder_path, msgram_files):
94
+ processed_files = []
95
+
96
+ for file in msgram_files:
97
+ print_info(f"> [blue] Processing {file}")
98
+ metrics_dict = read_msgram(os.path.join(folder_path, file))
99
+
100
+ if not metrics_dict:
101
+ print_error(f"> [red] Error to read metrics in: {folder_path}\n")
102
+ return False
103
+
104
+ processed_files.append((file, metrics_dict))
105
+
106
+ return processed_files
107
+
108
+
109
+ def aggregate_metrics(input_format, folder_path, config: json):
110
+ msgram_files = list_msgram_files(folder_path)
111
+
112
+ if not msgram_files:
113
+ print_error("> [red]Error: Can not read msgram files in provided directory")
114
+ return False
115
+
116
+ github_files = [file for file in msgram_files if file.startswith("github_")]
117
+ sonar_files = [file for file in msgram_files if file not in github_files]
118
+ file_content = {}
119
+
120
+ result = []
121
+
122
+ have_metrics = False
123
+
124
+ if should_process_metrics(config):
125
+ result = process_metrics(
126
+ folder_path, github_files if input_format == "github" else sonar_files
127
+ )
128
+
129
+ if not result:
130
+ print_error("> [red]Error: Unexpected result from process_github_metrics")
131
+ return False
132
+
133
+ have_metrics = True
134
+ else:
135
+ print_error("> [red]Error: Unexpected measures from should_process_metrics")
136
+ return False
137
+
138
+ if not have_metrics:
139
+ print_error(
140
+ f"> [red]Error: No metrics where found in the .msgram files from the type: {input_format}"
141
+ )
142
+ return False
143
+
144
+ for filename, file_content in result:
145
+ save_metrics(os.path.join(folder_path, filename), file_content)
146
+
147
+ return True
@@ -34,26 +34,31 @@ def read_config_file(config_path):
34
34
  exit(1)
35
35
 
36
36
 
37
- def calculate_metrics(extracted_path, config):
37
+ def calculate_metrics(input_format, extracted_path, config):
38
38
  data_calculated = []
39
39
 
40
40
  if not extracted_path.is_file():
41
- if not aggregate_metrics(extracted_path, config):
41
+ if not aggregate_metrics(input_format, extracted_path, config):
42
42
  print_error(
43
43
  "> [red] Failed to aggregate metrics, calculate was not performed. \n"
44
44
  )
45
45
  return data_calculated, False
46
46
 
47
- for file, file_name in read_multiple_files(extracted_path, "metrics"):
47
+ for file, file_name in read_multiple_files(
48
+ extracted_path, input_format, "metrics"
49
+ ):
50
+ if file_name.startswith("github_"):
51
+ file_name = file_name[len("github_") :]
48
52
  result = calculate_all(file, file_name, config)
49
53
  data_calculated.append(result)
50
54
 
51
55
  return data_calculated, True
52
56
  else:
53
57
  try:
54
- result = calculate_all(
55
- open_json_file(extracted_path), extracted_path.name, config
56
- )
58
+ file_name = extracted_path.name
59
+ if extracted_path.name.startswith("github_"):
60
+ file_name = file_name[len("github_") :]
61
+ result = calculate_all(open_json_file(extracted_path), file_name, config)
57
62
  return result, True
58
63
  except exceptions.MeasureSoftGramCLIException as e:
59
64
  print_error(f"[red]Error calculating {extracted_path}: {e}\n")
@@ -63,6 +68,7 @@ def calculate_metrics(extracted_path, config):
63
68
  def command_calculate(args):
64
69
  try:
65
70
  output_format: str = args["output_format"]
71
+ input_format: str = args["input_format"]
66
72
  config_path = args["config_path"]
67
73
  extracted_path = args["extracted_path"]
68
74
 
@@ -80,7 +86,7 @@ def command_calculate(args):
80
86
 
81
87
  print_info("\n> [blue] Reading extracted files:[/]")
82
88
 
83
- data_calculated, success = calculate_metrics(extracted_path, config)
89
+ data_calculated, success = calculate_metrics(input_format, extracted_path, config)
84
90
 
85
91
  if success:
86
92
  print_info("\n[#A9A9A9]All calculations performed[/] successfully!")
@@ -17,20 +17,18 @@ from src.cli.utils import (
17
17
  print_rule,
18
18
  )
19
19
  from src.cli.exceptions import exceptions
20
- from src.config.settings import DEFAULT_CONFIG_PATH, DIFF_FILE_CONFIG
20
+ from src.config.settings import DEFAULT_CONFIG_PATH
21
21
 
22
22
  logger = logging.getLogger("msgram")
23
23
 
24
24
 
25
25
  def read_config_file(config_path):
26
26
  try:
27
- json = open_json_file(config_path / DIFF_FILE_CONFIG)
27
+ json = open_json_file(config_path)
28
28
 
29
29
  return sorted(json, key=lambda x: x["key"])
30
30
  except exceptions.MeasureSoftGramCLIException as e:
31
- print_error(
32
- f"[red]Error reading msgram_diff.json config file in {config_path}: {e}\n"
33
- )
31
+ print_error(f"[red]Error reading config file in {config_path}: {e}\n")
34
32
  print_rule()
35
33
  exit(1)
36
34
 
@@ -107,8 +105,8 @@ def calculate_diff(planned, calculated):
107
105
  def command_diff(args):
108
106
  try:
109
107
  output_format: str = args["output_format"]
110
- config_path = args["config_path"]
111
- extracted_calculation = args["extracted_calculation"]
108
+ config_path = args["rp_path"]
109
+ extracted_calculation = args["rd_path"]
112
110
 
113
111
  except KeyError as e:
114
112
  logger.error(f"KeyError: args[{e}] - non-existent parameters")
@@ -130,6 +128,8 @@ def command_diff(args):
130
128
 
131
129
  if success:
132
130
  print_info("\n[#A9A9A9]Diff calculation performed[/] successfully!")
131
+ else:
132
+ exit(1)
133
133
 
134
134
  show_results(output_format, diff_calculated, config_path)
135
135
  print_rule()
@@ -167,7 +167,7 @@ def show_tabulate(data_calculated):
167
167
 
168
168
 
169
169
  def export_json(data_calculated: list, file_path: Path = DEFAULT_CONFIG_PATH):
170
- file_path = file_path.joinpath("calc_diff_msgram.json")
170
+ file_path = file_path.parent.joinpath("calc_diff_msgram.json")
171
171
  with open(file_path, "w", encoding="utf-8") as write_file:
172
172
  json.dump(
173
173
  data_calculated,
@@ -178,10 +178,17 @@ def export_json(data_calculated: list, file_path: Path = DEFAULT_CONFIG_PATH):
178
178
 
179
179
 
180
180
  def export_csv(data_calculated: list, file_path: Path = Path("DEFAULT_CONFIG_PATH")):
181
- file_path = file_path.joinpath("calc_diff_msgram.csv")
181
+ file_path = file_path.parent.joinpath("calc_diff_msgram.csv")
182
182
 
183
183
  with open(file_path, "w", newline="") as csv_file:
184
- fieldnames = ["repository", "version", "key", "planned", "developed", "diff"]
184
+ fieldnames = [
185
+ "repository",
186
+ "version",
187
+ "characteristic",
188
+ "planned",
189
+ "developed",
190
+ "diff",
191
+ ]
185
192
  writer = csv.DictWriter(csv_file, fieldnames=fieldnames)
186
193
 
187
194
  writer.writeheader()
@@ -191,7 +198,10 @@ def export_csv(data_calculated: list, file_path: Path = Path("DEFAULT_CONFIG_PAT
191
198
  {
192
199
  "repository": data["repository"],
193
200
  "version": data["version"],
194
- **row,
201
+ "characteristic": row.get("key", ""),
202
+ "planned": row.get("planned", ""),
203
+ "developed": row.get("developed", ""),
204
+ "diff": row.get("diff", ""),
195
205
  }
196
206
  )
197
207
 
@@ -102,7 +102,7 @@ def command_extract(args):
102
102
  if repository_path and output_origin == "github":
103
103
  filters = {
104
104
  "labels": label if label else "US,User Story,User Stories",
105
- "workflows": workflows if workflows.split(',') else "build",
105
+ "workflows": workflows.split(",") if workflows else "build",
106
106
  "dates": filter_date if filter_date else None,
107
107
  }
108
108
  result = parser.parse(
@@ -132,6 +132,11 @@ def command_extract(args):
132
132
  logger.debug(f"extracted_path: {extracted_path}")
133
133
 
134
134
  files = list(data_path.glob("*.json"))
135
+
136
+ if not files:
137
+ print_warn(f"No JSON files found in the specified data_path: {data_path}\n")
138
+ sys.exit(1)
139
+
135
140
  valid_files = len(files)
136
141
 
137
142
  print_info(f"\n> Extract and save metrics [[blue ]{output_origin}[/]]:")
@@ -43,26 +43,38 @@ def read_calculated_file(file_path):
43
43
 
44
44
  def command_norm_diff(args):
45
45
  try:
46
- planned_path = args["planned_path"]
47
- calculated_path = args["calculated_path"]
46
+ rp_path = args["rp_path"]
47
+ rd_path = args["rd_path"]
48
48
  except KeyError as e:
49
49
  logger.error(f"KeyError: args[{e}] - non-existent parameters")
50
50
  print_error(f"KeyError: args[{e}] - non-existent parameters")
51
51
  exit(1)
52
52
 
53
- planned_data = read_planned_file(planned_path, sort_key="key")
54
- calculated_data = read_calculated_file(calculated_path)
53
+ planned_data = read_planned_file(rp_path, sort_key="key")
54
+ calculated_data = read_calculated_file(rd_path)
55
55
 
56
- planned_vector, calculated_vector = extract_values(planned_data, calculated_data)
56
+ planned_vector, calculated_vector = extract_values(
57
+ planned_data, calculated_data, rp_path, rd_path
58
+ )
57
59
  norm_diff_value = norm_diff(planned_vector, calculated_vector)
58
60
 
59
- print_info("\n[#A9A9A9]Norm diff calculation performed successfully![/]")
60
- print_info("[#A9A9A9]For more detailed informations use 'diff' command.[/]")
61
+ print_info("\n[#A9A9A9]Norm diff calculation performed successfully![/]\n")
62
+
63
+ print_info(
64
+ "[#A9A9A9]The norm_diff value indicates the difference between the observed "
65
+ "quality (Rd) and the planned target (Rp). A norm_diff of 0 means that the "
66
+ "observed quality perfectly aligns with the planned target. If norm_diff is "
67
+ "not equal to 0, it shows a deviation from the target. In this case, you "
68
+ "should determine whether the performance is above or below the planned "
69
+ "quality. For a detailed analysis of these differences, use the msgram diff "
70
+ "command.[/]\n"
71
+ )
72
+
61
73
  print(f"Norm Diff: {norm_diff_value}")
62
74
  print_rule()
63
75
 
64
76
 
65
- def extract_values(planned_data, calculated_data):
77
+ def extract_values(planned_data, calculated_data, rp_path, rd_path):
66
78
  try:
67
79
  planned = planned_data
68
80
  calculated = []
@@ -86,6 +98,22 @@ def extract_values(planned_data, calculated_data):
86
98
  planned_values = [planned_dict[key] for key in planned_keys]
87
99
  calculated_values = [calculated_dict[key] for key in planned_keys]
88
100
 
101
+ for value in planned_values:
102
+ if value > 1 or value < 0:
103
+ print_error(
104
+ f"[red]The values informed in the .json file {rp_path} must be between 0 and 1.\n"
105
+ )
106
+ print_rule()
107
+ exit(1)
108
+
109
+ for value in calculated_values:
110
+ if value > 1 or value < 0:
111
+ print_error(
112
+ f"[red]The values informed in the .json file {rd_path} must be between 0 and 1.\n"
113
+ )
114
+ print_rule()
115
+ exit(1)
116
+
89
117
  return (np.array(planned_values), np.array(calculated_values))
90
118
  except exceptions.MeasureSoftGramCLIException as e:
91
119
  print_error(f"[red]Error extracting values: {e}\n")
@@ -29,8 +29,15 @@ def file_reader(path_file):
29
29
  return components
30
30
 
31
31
 
32
- def read_multiple_files(directory: Path, pattern: str):
33
- for path_file in directory.glob(f"*.{pattern}"):
32
+ def read_multiple_files(directory: Path, format: str, pattern: str):
33
+ if format == "github":
34
+ glob_pattern = f"github_*.{pattern}"
35
+ elif format == "sonarqube":
36
+ glob_pattern = f"[!github_]*.{pattern}"
37
+ else:
38
+ glob_pattern = f"*.{pattern}"
39
+
40
+ for path_file in directory.glob(glob_pattern):
34
41
  try:
35
42
  yield open_json_file(path_file), path_file.name
36
43
  except exceptions.MeasureSoftGramCLIException:
@@ -170,34 +170,51 @@ def create_parser():
170
170
  help="Path to the config directory",
171
171
  )
172
172
 
173
+ parser_calculate.add_argument(
174
+ "-in",
175
+ "--input_format",
176
+ required=True,
177
+ type=str,
178
+ choices=AVAILABLE_IMPORTS,
179
+ default="sonarqube",
180
+ help=(
181
+ "Format of .msgram files. Valid values are: " + ", ".join(AVAILABLE_IMPORTS)
182
+ ),
183
+ )
184
+
173
185
  parser_calculate.add_argument(
174
186
  "-o",
175
187
  "--output_format",
176
188
  type=str,
177
189
  choices=AVAILABLE_GEN_FORMATS,
178
190
  default="csv",
179
- help=("The format of the output (export) values are: ".join(SUPPORTED_FORMATS)),
191
+ help=(
192
+ "The format of the output (export) values are: "
193
+ + ", ".join(SUPPORTED_FORMATS)
194
+ ),
180
195
  )
181
196
  parser_calculate.set_defaults(func=command_calculate) # function command calculate
182
197
 
183
- # =====================================< COMMAND norm-diff >=====================================
198
+ # =====================================< COMMAND norm_diff >=====================================
184
199
  parser_norm_diff = subparsers.add_parser(
185
- "norm-diff",
200
+ "norm_diff",
186
201
  help="Calculate the norm difference between the planned metrics and the developed.",
187
202
  )
188
203
 
189
204
  parser_norm_diff.add_argument(
190
- "-p",
191
- "--planned_path",
205
+ "-rp",
206
+ "--rp_path",
192
207
  type=lambda p: Path(p).absolute(),
193
- help="Path to the json with the planned metrics.",
208
+ help="Path to the .json file with the planned/wished values ​​for the quality "
209
+ "characteristics of a release. Quality requirements goals for a release.",
194
210
  )
195
211
 
196
212
  parser_norm_diff.add_argument(
197
- "-c",
198
- "--calculated_path",
213
+ "-rd",
214
+ "--rd_path",
199
215
  type=lambda p: Path(p).absolute(),
200
- help="Path to the json with the calculated metrics.",
216
+ help="Path to the .json file with the model-calculated values ​​for a release's "
217
+ "quality characteristics observed/developed.",
201
218
  )
202
219
 
203
220
  parser_norm_diff.set_defaults(
@@ -211,28 +228,28 @@ def create_parser():
211
228
  )
212
229
 
213
230
  parser_calculate.add_argument(
214
- "-ec",
215
- "--extracted_calculation",
231
+ "-rd",
232
+ "--rd_path",
216
233
  type=lambda p: Path(p).absolute(),
217
- help="Path to the calculated directory",
234
+ help="Path to the .json file with the model-calculated values for a release's quality"
235
+ "characteristics observed/developed.",
218
236
  )
219
237
 
220
238
  parser_calculate.add_argument(
221
- "-cp",
222
- "--config_path",
239
+ "-rp",
240
+ "--rp_path",
223
241
  type=lambda p: Path(p).absolute(),
224
- default=DEFAULT_CONFIG_PATH,
225
- help="Path to the config directory",
242
+ help="Path to the .json file with the planned/wished values for the quality characteristics"
243
+ "of a release. Quality requirements goals for a release.",
226
244
  )
227
245
 
228
246
  parser_calculate.add_argument(
229
- "-o",
247
+ "-of",
230
248
  "--output_format",
231
249
  type=str,
232
250
  choices=AVAILABLE_GEN_FORMATS,
233
- default="json",
234
- help=("The format of the output (export) values are: ".join(SUPPORTED_FORMATS)),
251
+ help=("The format of the output (export) values is tabular"),
235
252
  )
236
- parser_calculate.set_defaults(func=command_diff) # function command calculate
253
+ parser_calculate.set_defaults(func=command_diff)
237
254
 
238
255
  return parser
@@ -0,0 +1,40 @@
1
+ from src.cli.resources.subcharacteristic import get_subcharacteristic_value
2
+
3
+ from resources import calculate_characteristics as core_calculate
4
+
5
+
6
+ def get_characteristic_value(chars, tsqmi):
7
+ char_calculated = []
8
+ for char in tsqmi:
9
+ char_key = char["key"]
10
+
11
+ found = any(char_key == m["key"] for m in chars)
12
+ if found:
13
+ char_calculated.append(
14
+ {
15
+ "key": char_key,
16
+ "value": {m["key"]: m["value"] for m in chars}[char_key],
17
+ "weight": char["weight"],
18
+ }
19
+ )
20
+
21
+ return char_calculated
22
+
23
+
24
+ def calculate_characteristics(config, subchars):
25
+ characteristics = config["characteristics"]
26
+ calculate_infos = []
27
+
28
+ for char in characteristics:
29
+ char_info = {
30
+ "key": char["key"],
31
+ "subcharacteristics": get_subcharacteristic_value(
32
+ subchars, char["subcharacteristics"]
33
+ ),
34
+ }
35
+ if len(char_info["subcharacteristics"]) > 0:
36
+ calculate_infos.append(char_info)
37
+
38
+ headers = ["Id", "Name", "Description", "Value", "Created at"]
39
+
40
+ return core_calculate({"characteristics": calculate_infos}), headers
@@ -12,13 +12,15 @@ def get_measure_value(measures, subchar):
12
12
  measures_calculated = []
13
13
  for measure in subchar:
14
14
  measure_key = measure["key"]
15
- measures_calculated.append(
16
- {
17
- "key": measure_key,
18
- "value": {m["key"]: m["value"] for m in measures}[measure_key],
19
- "weight": measure["weight"],
20
- }
21
- )
15
+ found = any(measure_key == m["key"] for m in measures)
16
+ if found:
17
+ measures_calculated.append(
18
+ {
19
+ "key": measure_key,
20
+ "value": {m["key"]: m["value"] for m in measures}[measure_key],
21
+ "weight": measure["weight"],
22
+ }
23
+ )
22
24
 
23
25
  return measures_calculated
24
26
 
@@ -8,15 +8,17 @@ def get_subcharacteristic_value(subchars, char):
8
8
  for subchar in char:
9
9
  subchar_key = subchar["key"]
10
10
 
11
- subchar_calculated.append(
12
- {
13
- "key": subchar_key,
14
- "value": {m["key"]: m["value"] for m in subchars}.get(
15
- subchar_key, None
16
- ),
17
- "weight": subchar["weight"],
18
- }
19
- )
11
+ found = any(subchar_key == m["key"] for m in subchars)
12
+ if found:
13
+ subchar_calculated.append(
14
+ {
15
+ "key": subchar_key,
16
+ "value": {m["key"]: m["value"] for m in subchars}.get(
17
+ subchar_key, None
18
+ ),
19
+ "weight": subchar["weight"],
20
+ }
21
+ )
20
22
 
21
23
  return subchar_calculated
22
24
 
@@ -32,7 +34,9 @@ def calculate_subcharacteristics(config, measures):
32
34
  "key": subchar["key"],
33
35
  "measures": get_measure_value(measures, subchar["measures"]),
34
36
  }
35
- calculate_infos.append(subchar_info)
37
+ if len(subchar_info["measures"]) > 0:
38
+ calculate_infos.append(subchar_info)
36
39
 
37
40
  headers = ["Id", "Name", "Description", "Value", "Created at"]
41
+
38
42
  return core_calculate({"subcharacteristics": calculate_infos}), headers