Skip to content

Commit 602a328

Browse files
authored
feat: add template voldemort closes #4 (#15)
* feat: add template voldemort Define rules that ensure that a specific name isn't used in the code. Create rules for: * function names * function arguments * class names * variable declarations * variable assignments version 0.4.0
1 parent 08a2f8e commit 602a328

File tree

10 files changed

+257
-6
lines changed

10 files changed

+257
-6
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
## [0.4.0] - 2023-01-05
11+
12+
### Added
13+
14+
* "voldemort" rule template and command: names to avoid in your code
15+
1016
## [0.3.3] - 2022-12-21
1117

1218
### Added

README.md

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,9 @@ sourcery-rules <TEMPLATE-NAME> create
1818

1919
Supported templates:
2020

21-
* dependencies
22-
* naming (coming soon)
21+
* [dependencies](#create-dependencies-rules)
22+
* [naming / voldemort](#create-voldemort-rules): avoid some names
23+
* naming / name vs type mismatch (coming soon)
2324

2425
For example:
2526

@@ -89,6 +90,37 @@ This [blog post](https://sourcery.ai/blog/dependency-rules/) shows a 3-step meth
8990
2. Phrase some rules in a human language based on the diagram: Which package should depend on which?
9091
3. Translate the rules into code with Sourcery Rules Generator.
9192

93+
## Create Voldemort Rules
94+
95+
With a "voldemort" template, you can create rules that ensure that a specific name isn't used in your code.
96+
97+
For example:
98+
99+
* The word `annual` shouldn't be used, because the preferred term is `yearly`.
100+
* The word `util` shouldn't be used, because it's overly general.
101+
102+
You can create a "voldemort" rule with the command:
103+
104+
```
105+
sourcery-rules voldemort create
106+
```
107+
108+
![screenshot sourcery-rules voldemort create](https://raw.githubusercontent.com/sourcery-ai/sourcery-rules-generator/main/voldemort_create.png)
109+
110+
You'll be prompted to provide:
111+
112+
* the name that you want to avoid
113+
114+
=>
115+
116+
5 rules will be generated:
117+
118+
* function names
119+
* function arguments
120+
* class names
121+
* variable declarations
122+
* variable assignments
123+
92124
## Using the Generated Rules
93125

94126
The generated rules can be used by Sourcery to review your project.

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "sourcery-rules-generator"
3-
version = "0.3.3"
3+
version = "0.4.0"
44
description = "Generate architecture rules for Python projects."
55
license = "MIT"
66
authors = ["reka <reka@sourcery.ai>"]
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "0.3.3"
1+
__version__ = "0.4.0"

sourcery_rules_generator/cli/cli.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@
44

55
from rich.console import Console
66

7-
from sourcery_rules_generator.cli import dependencies_cli
7+
from sourcery_rules_generator.cli import dependencies_cli, voldemort_cli
88
from sourcery_rules_generator import __version__
99

1010
app = typer.Typer(rich_markup_mode="markdown")
1111
app.add_typer(dependencies_cli.app, name="dependencies")
12+
app.add_typer(voldemort_cli.app, name="voldemort")
1213

1314

1415
@app.callback(invoke_without_command=True)
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
#! /usr/bin/env python3
2+
3+
import sys
4+
import typer
5+
from rich.console import Console
6+
from rich.markdown import Markdown
7+
from rich.prompt import Prompt
8+
from rich.syntax import Syntax
9+
10+
from sourcery_rules_generator import voldemort
11+
12+
app = typer.Typer(rich_markup_mode="markdown")
13+
14+
15+
DESCRIPTION_MARKDOWN = """
16+
# "Voldemort" Template
17+
18+
With the "Voldemort" template,
19+
you can generate rules that ensure that a specific word isn't used in your codebase.
20+
21+
For example:
22+
23+
* The word `annual` shouldn't be used, because the preferred term is `yearly`.
24+
* The word `util` shouldn't be used, because it's overly general.
25+
26+
## Parameters for the "Voldemort" Template
27+
28+
1. The word that should be avoided. Required.
29+
"""
30+
31+
32+
@app.command()
33+
def create(
34+
avoided_name_option: str = typer.Option(
35+
None,
36+
"--avoided-name",
37+
help="The name that should be avoided.",
38+
),
39+
interactive_flag: bool = typer.Option(
40+
True,
41+
"--interactive/--no-interactive",
42+
"--input/--no-input",
43+
help="Switch whether interactive prompts are shown. Use `--no-input` when you call this command from scripts.",
44+
),
45+
plain: bool = typer.Option(False, help="Print only plain text."),
46+
quiet: bool = typer.Option(
47+
False,
48+
"--quiet",
49+
"-q",
50+
help='Display less info about the "Voldemort" template.',
51+
),
52+
):
53+
"""Create a new Sourcery Voldemort rule."""
54+
interactive = sys.stdin.isatty() and interactive_flag
55+
stderr_console = Console(stderr=True)
56+
if interactive and not quiet:
57+
stderr_console.print(Markdown(DESCRIPTION_MARKDOWN))
58+
stderr_console.rule()
59+
60+
if interactive:
61+
stderr_console.print(Markdown('## Parameters for the "Voldemort" Template'))
62+
63+
name_to_avoid = (
64+
avoided_name_option
65+
or interactive
66+
and Prompt.ask("The name to avoid: (required)", console=stderr_console)
67+
)
68+
if not name_to_avoid:
69+
_raise_error("No name to avoid provided. Can't create voldemort rule.")
70+
71+
result = voldemort.create_yaml_rules(name_to_avoid)
72+
73+
stderr_console.rule()
74+
stderr_console.print(Markdown("## Generated YAML Rules"))
75+
if plain:
76+
Console().print(result)
77+
else:
78+
Console().print(Syntax(result, "YAML"))
79+
80+
81+
def _raise_error(error_msg: str, code: int = 1) -> None:
82+
stderr_console = Console(stderr=True, style="bold red")
83+
stderr_console.print(error_msg)
84+
raise typer.Exit(code=code)
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
from typing import Optional
2+
3+
from sourcery_rules_generator import yaml_converter
4+
from sourcery_rules_generator.models import SourceryCustomRule, PathsConfig
5+
6+
7+
def create_yaml_rules(name_to_avoid: str):
8+
9+
custom_rules = create_sourcery_custom_rules(name_to_avoid)
10+
11+
rules_dict = {"rules": [rule.dict(exclude_unset=True) for rule in custom_rules]}
12+
return yaml_converter.dumps(rules_dict)
13+
14+
15+
def create_sourcery_custom_rules(name_to_avoid: str) -> str:
16+
description = f"Don't use the name {name_to_avoid}"
17+
18+
function_name_rule = SourceryCustomRule(
19+
id=f"no-{name_to_avoid}-function-name",
20+
description=description,
21+
tags=["naming", f"no-{name_to_avoid}"],
22+
pattern="""
23+
def ${function_name}(...):
24+
...
25+
""",
26+
condition=f'function_name.contains("{name_to_avoid}")',
27+
)
28+
29+
function_arg_rule = SourceryCustomRule(
30+
id=f"no-{name_to_avoid}-function-arg",
31+
description=description,
32+
tags=["naming", f"no-{name_to_avoid}"],
33+
pattern="""
34+
def ...(...,${arg_name}: ${type?} = ${default_value?},...):
35+
...
36+
""",
37+
condition=f'arg_name.contains("{name_to_avoid}")',
38+
)
39+
40+
class_name_rule = SourceryCustomRule(
41+
id=f"no-{name_to_avoid}-class-name",
42+
description=description,
43+
tags=["naming", f"no-{name_to_avoid}"],
44+
pattern="""
45+
class ${class_name}(...):
46+
...
47+
""",
48+
condition=f'class_name.contains("{name_to_avoid}")',
49+
)
50+
51+
variable_declaration_rule = SourceryCustomRule(
52+
id=f"no-{name_to_avoid}-property",
53+
description=description,
54+
tags=["naming", f"no-{name_to_avoid}"],
55+
pattern="${var}: ${type}",
56+
condition=f'var.contains("{name_to_avoid}")',
57+
)
58+
59+
variable_assignment_rule = SourceryCustomRule(
60+
id=f"no-{name_to_avoid}-variable",
61+
description=description,
62+
tags=["naming", f"no-{name_to_avoid}"],
63+
pattern="${var} = ${value}",
64+
condition=f'var.contains("{name_to_avoid}")',
65+
)
66+
67+
return (
68+
function_name_rule,
69+
function_arg_rule,
70+
class_name_rule,
71+
variable_declaration_rule,
72+
variable_assignment_rule,
73+
)

tests/test_version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22

33

44
def test_version():
5-
assert __version__ == "0.3.3"
5+
assert __version__ == "0.4.0"

tests/unit/test_voldemort.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
from sourcery_rules_generator import voldemort
2+
from sourcery_rules_generator.models import SourceryCustomRule
3+
4+
5+
def test_1_allowed_importer():
6+
result = voldemort.create_sourcery_custom_rules("util")
7+
8+
expected = (
9+
SourceryCustomRule(
10+
id="no-util-function-name",
11+
description="Don't use the name util",
12+
tags=["naming", "no-util"],
13+
pattern="""
14+
def ${function_name}(...):
15+
...
16+
""",
17+
condition='function_name.contains("util")',
18+
),
19+
SourceryCustomRule(
20+
id="no-util-function-arg",
21+
description="Don't use the name util",
22+
tags=["naming", "no-util"],
23+
pattern="""
24+
def ...(...,${arg_name}: ${type?} = ${default_value?},...):
25+
...
26+
""",
27+
condition='arg_name.contains("util")',
28+
),
29+
SourceryCustomRule(
30+
id="no-util-class-name",
31+
description="Don't use the name util",
32+
tags=["naming", "no-util"],
33+
pattern="""
34+
class ${class_name}(...):
35+
...
36+
""",
37+
condition='class_name.contains("util")',
38+
),
39+
SourceryCustomRule(
40+
id="no-util-property",
41+
description="Don't use the name util",
42+
tags=["naming", "no-util"],
43+
pattern="${var}: ${type}",
44+
condition='var.contains("util")',
45+
),
46+
SourceryCustomRule(
47+
id="no-util-variable",
48+
description="Don't use the name util",
49+
tags=["naming", "no-util"],
50+
pattern="${var} = ${value}",
51+
condition='var.contains("util")',
52+
),
53+
)
54+
55+
assert result == expected

voldemort_create.png

99.5 KB
Loading

0 commit comments

Comments
 (0)