Skip to content

Commit 79db955

Browse files
authored
Merge pull request #22 from OPPIDA/feat/plot-format
2 parents 54afbb3 + 3a44aae commit 79db955

File tree

6 files changed

+50
-169
lines changed

6 files changed

+50
-169
lines changed

codesectools/sasts/all/cli.py

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import typer
99
from click import Choice
1010
from rich import print
11-
from typing_extensions import Annotated
11+
from typing_extensions import Annotated, Literal
1212

1313
from codesectools.datasets import DATASETS_ALL
1414
from codesectools.datasets.core.dataset import FileDataset, GitRepoDataset
@@ -200,26 +200,16 @@ def plot(
200200
help="Overwrite existing figures",
201201
),
202202
] = False,
203-
show: Annotated[
204-
bool,
205-
typer.Option(
206-
"--show",
207-
help="Display figures",
208-
),
209-
] = False,
210-
pgf: Annotated[
211-
bool,
212-
typer.Option(
213-
"--pgf",
214-
help="Export figures to pgf format (for LaTeX document)",
215-
),
216-
] = False,
203+
format: Annotated[
204+
Literal["png", "pdf", "svg"],
205+
typer.Option("--format", help="Figures export format"),
206+
] = "png",
217207
) -> None:
218208
"""Generate and display plots for a project's aggregated analysis results."""
219209
from codesectools.sasts.all.graphics import ProjectGraphics
220210

221211
project_graphics = ProjectGraphics(project_name=project)
222-
project_graphics.export(overwrite=overwrite, show=show, pgf=pgf)
212+
project_graphics.export(overwrite=overwrite, format=format)
223213

224214
@cli.command(help="Generate an HTML report")
225215
def report(

codesectools/sasts/all/graphics.py

Lines changed: 12 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,25 @@
11
"""Provides classes for generating plots and visualizations from aggregated SAST results."""
22

3-
import shutil
4-
import tempfile
5-
6-
import matplotlib
73
import matplotlib.pyplot as plt
8-
import typer
94
from matplotlib.figure import Figure
10-
from rich import print
115

126
from codesectools.sasts.all.sast import AllSAST
7+
from codesectools.sasts.core.graphics import Graphics as CoreGraphics
138
from codesectools.utils import shorten_path
149

15-
## Matplotlib config
16-
matplotlib.rcParams.update(
17-
{
18-
"font.family": "serif",
19-
"font.size": 11,
20-
}
21-
)
2210

11+
class Graphics(CoreGraphics):
12+
"""Base class for generating plots for aggregated SAST results.
13+
14+
Attributes:
15+
project_name (str): The name of the project being visualized.
16+
all_sast (AllSAST): The instance managing all SAST tools.
17+
output_dir (Path): The directory containing the aggregated results.
18+
color_mapping (dict): A dictionary mapping SAST tool names to colors.
19+
sast_names (list[str]): A list of names of the SAST tools involved in the analysis.
20+
plot_functions (list): A list of methods responsible for generating plots.
2321
24-
class Graphics:
25-
"""Base class for generating graphics from aggregated SAST results."""
22+
"""
2623

2724
def __init__(self, project_name: str) -> None:
2825
"""Initialize the Graphics object."""
@@ -38,61 +35,6 @@ def __init__(self, project_name: str) -> None:
3835
self.sast_names.append(sast.name)
3936
self.plot_functions = []
4037

41-
# Plot options
42-
self.limit = 10
43-
44-
self.has_latex = shutil.which("pdflatex")
45-
if self.has_latex:
46-
matplotlib.use("pgf")
47-
matplotlib.rcParams.update(
48-
{
49-
"pgf.texsystem": "pdflatex",
50-
"text.usetex": True,
51-
"pgf.rcfonts": False,
52-
}
53-
)
54-
else:
55-
print("pdflatex not found, pgf will not be generated")
56-
57-
def export(self, overwrite: bool, pgf: bool, show: bool) -> None:
58-
"""Generate, save, and optionally display all registered plots.
59-
60-
Args:
61-
overwrite: If True, overwrite existing figure files.
62-
pgf: If True and LaTeX is available, export figures in PGF format.
63-
show: If True, open the generated figures using the default viewer.
64-
65-
"""
66-
for plot_function in self.plot_functions:
67-
fig = plot_function()
68-
fig_name = plot_function.__name__.replace("plot_", "")
69-
fig.set_size_inches(12, 7)
70-
71-
if show:
72-
with tempfile.NamedTemporaryFile(delete=True) as temp:
73-
fig.savefig(f"{temp.name}.png", bbox_inches="tight")
74-
typer.launch(f"{temp.name}.png", wait=False)
75-
76-
figure_dir = self.output_dir / "_figures"
77-
figure_dir.mkdir(exist_ok=True, parents=True)
78-
figure_path = figure_dir / f"{fig_name}.png"
79-
if figure_path.is_file() and not overwrite:
80-
if not typer.confirm(
81-
f"Found existing figure at {figure_path}, would you like to overwrite?"
82-
):
83-
print(f"Figure {fig_name} not saved")
84-
continue
85-
86-
fig.savefig(figure_path, bbox_inches="tight")
87-
print(f"Figure {fig_name} saved at {figure_path}")
88-
89-
if pgf and self.has_latex:
90-
figure_path_pgf = figure_dir / f"{fig_name}.pgf"
91-
fig.savefig(figure_path_pgf, bbox_inches="tight")
92-
print(f"Figure {fig_name} exported to pgf")
93-
94-
plt.close(fig)
95-
9638

9739
## Single project
9840
class ProjectGraphics(Graphics):

codesectools/sasts/core/cli.py

Lines changed: 9 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
import typer
1313
from click import Choice
1414
from rich import print
15-
from typing_extensions import Annotated
15+
from typing_extensions import Annotated, Literal
1616

1717
from codesectools.datasets import DATASETS_ALL
1818
from codesectools.datasets.core.dataset import FileDataset, GitRepoDataset
@@ -314,30 +314,12 @@ def plot(
314314
help="Overwrite existing figures",
315315
),
316316
] = False,
317-
show: Annotated[
318-
bool,
319-
typer.Option(
320-
"--show",
321-
help="Display figures",
322-
),
323-
] = False,
324-
pgf: Annotated[
325-
bool,
326-
typer.Option(
327-
"--pgf",
328-
help="Export figures to pgf format (for LaTeX document)",
329-
),
330-
] = False,
317+
format: Annotated[
318+
Literal["png", "pdf", "svg"],
319+
typer.Option("--format", help="Figures export format"),
320+
] = "png",
331321
) -> None:
332-
"""Generate and export plots for a given project or dataset result.
333-
334-
Args:
335-
result: The name of the analysis result to plot.
336-
overwrite: If True, overwrite existing figure files.
337-
show: If True, display the generated figures.
338-
pgf: If True, export figures in PGF format for LaTeX documents.
339-
340-
"""
322+
"""Generate and export plots for a given project or dataset result."""
341323
from codesectools.sasts.core.graphics import (
342324
FileDatasetGraphics,
343325
GitRepoDatasetGraphics,
@@ -347,7 +329,7 @@ def plot(
347329
if result in self.sast.list_results(project=True):
348330
project = result
349331
project_graphics = ProjectGraphics(self.sast, project_name=project)
350-
project_graphics.export(overwrite=overwrite, show=show, pgf=pgf)
332+
project_graphics.export(overwrite=overwrite, format=format)
351333
elif result in self.sast.list_results(dataset=True):
352334
dataset = result
353335
dataset_name, lang = dataset.split("_")
@@ -356,15 +338,11 @@ def plot(
356338
file_dataset_graphics = FileDatasetGraphics(
357339
self.sast, dataset=dataset
358340
)
359-
file_dataset_graphics.export(
360-
overwrite=overwrite, show=show, pgf=pgf
361-
)
341+
file_dataset_graphics.export(overwrite=overwrite, format=format)
362342
elif isinstance(dataset, GitRepoDataset):
363343
git_repo_dataset_graphics = GitRepoDatasetGraphics(
364344
self.sast, dataset=dataset
365345
)
366-
git_repo_dataset_graphics.export(
367-
overwrite=overwrite, show=show, pgf=pgf
368-
)
346+
git_repo_dataset_graphics.export(overwrite=overwrite, format=format)
369347
else:
370348
print("Not supported yet")

codesectools/sasts/core/graphics.py

Lines changed: 21 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,6 @@
55
benchmark performance.
66
"""
77

8-
import shutil
9-
import tempfile
10-
118
import matplotlib
129
import matplotlib.pyplot as plt
1310
import numpy as np
@@ -20,28 +17,24 @@
2017
from codesectools.shared.cwe import CWE
2118
from codesectools.utils import shorten_path
2219

23-
## Matplotlib config
24-
matplotlib.rcParams.update(
25-
{
26-
"font.family": "serif",
27-
"font.size": 11,
28-
}
29-
)
30-
3120

3221
class Graphics:
33-
"""Base class for generating graphics from SAST results.
22+
"""Base class for generating plots and visualizations from SAST results.
3423
3524
Attributes:
36-
sast (SAST): The SAST tool instance.
37-
output_dir (Path): The directory containing the analysis results.
38-
color_mapping (dict): A mapping of categories to colors for plotting.
39-
plot_functions (list): A list of methods that generate plots.
40-
limit (int): The maximum number of items to show in top-N plots.
41-
has_latex (bool): True if a LaTeX installation is found.
25+
limit (int): The maximum number of items to display in charts (default is 10).
26+
filetypes (dict[str, str]): A mapping of file extensions to matplotlib backends.
27+
sast (SAST): The SAST tool instance associated with the graphics.
28+
output_dir (Path): The directory where the analysis results are stored.
29+
color_mapping (dict): A dictionary mapping categories to colors for plots.
30+
plot_functions (list): A list of methods responsible for generating plots.
4231
4332
"""
4433

34+
limit = 10
35+
36+
filetypes = {"png": "AGG", "pdf": "PDF", "svg": "SVG"}
37+
4538
def __init__(self, sast: SAST, project_name: str) -> None:
4639
"""Initialize the Graphics object.
4740
@@ -56,44 +49,27 @@ def __init__(self, sast: SAST, project_name: str) -> None:
5649
self.color_mapping["NONE"] = "BLACK"
5750
self.plot_functions = []
5851

59-
# Plot options
60-
self.limit = 10
61-
62-
self.has_latex = shutil.which("pdflatex")
63-
if self.has_latex:
64-
matplotlib.use("pgf")
65-
matplotlib.rcParams.update(
66-
{
67-
"pgf.texsystem": "pdflatex",
68-
"text.usetex": True,
69-
"pgf.rcfonts": False,
70-
}
71-
)
72-
else:
73-
print("pdflatex not found, pgf will not be generated")
74-
75-
def export(self, overwrite: bool, pgf: bool, show: bool) -> None:
76-
"""Generate, save, and optionally display all registered plots.
52+
def export(self, overwrite: bool, format: str) -> None:
53+
"""Generate and save the configured plots to the output directory.
54+
55+
Iterates through the registered plot functions, generates the figures,
56+
and saves them to a `_figures` subdirectory within the output directory.
7757
7858
Args:
79-
overwrite: If True, overwrite existing figure files.
80-
pgf: If True and LaTeX is available, export figures in PGF format.
81-
show: If True, open the generated figures using the default viewer.
59+
overwrite: If True, overwrite existing figure files without prompting.
60+
format: The file format for the exported figures (e.g., "png", "pdf", "svg").
8261
8362
"""
63+
matplotlib.use(self.filetypes[format])
64+
8465
for plot_function in self.plot_functions:
8566
fig = plot_function()
8667
fig_name = plot_function.__name__.replace("plot_", "")
8768
fig.set_size_inches(12, 7)
8869

89-
if show:
90-
with tempfile.NamedTemporaryFile(delete=True) as temp:
91-
fig.savefig(f"{temp.name}.png", bbox_inches="tight")
92-
typer.launch(f"{temp.name}.png", wait=False)
93-
9470
figure_dir = self.output_dir / "_figures"
9571
figure_dir.mkdir(exist_ok=True, parents=True)
96-
figure_path = figure_dir / f"{fig_name}.png"
72+
figure_path = figure_dir / f"{fig_name}.{format}"
9773
if figure_path.is_file() and not overwrite:
9874
if not typer.confirm(
9975
f"Found existing figure at {figure_path}, would you like to overwrite?"
@@ -104,11 +80,6 @@ def export(self, overwrite: bool, pgf: bool, show: bool) -> None:
10480
fig.savefig(figure_path, bbox_inches="tight")
10581
print(f"Figure {fig_name} saved at {figure_path}")
10682

107-
if pgf and self.has_latex:
108-
figure_path_pgf = figure_dir / f"{fig_name}.pgf"
109-
fig.savefig(figure_path_pgf, bbox_inches="tight")
110-
print(f"Figure {fig_name} exported to pgf")
111-
11283
plt.close(fig)
11384

11485

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "CodeSecTools"
3-
version = "0.13.4"
3+
version = "0.13.5"
44
description = "A framework for code security that provides abstractions for static analysis tools and datasets to support their integration, testing, and evaluation."
55
readme = "README.md"
66
license = "AGPL-3.0-only"

uv.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)