Skip to content

Commit 8f06957

Browse files
committed
add new dependencies for CSS and SQL formatting, and improve CLI with 'format' output mode
1 parent d305b0a commit 8f06957

4 files changed

Lines changed: 81 additions & 5 deletions

File tree

‎setup.py‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,13 @@
2020
install_requires=[
2121
"autopep8>=2.1.0",
2222
"clang-format>=18.1.2",
23+
"cssbeautifier>=1.15.4",
24+
"djhtml>=3.0",
2325
"icdiff",
2426
"jsbeautifier",
2527
"pycodestyle==2.12.0",
2628
"python-magic",
29+
"sqlparse>=0.5.0",
2730
"termcolor",
2831
"jinja2>=2.10",
2932
],
@@ -40,6 +43,6 @@
4043
]
4144
},
4245
url="https://github.com/cs50/style50",
43-
version="2.11.0",
46+
version="3.0.0",
4447
include_package_data=True,
4548
)

‎style50/__main__.py‎

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ def main():
3232
parser = argparse.ArgumentParser(prog="style50")
3333
parser.add_argument("file", metavar="FILE", nargs="+", help="file or directory to lint")
3434
parser.add_argument("-o", "--output", action="store", default="character",
35-
choices=["character", "split", "unified", "score", "json", "html"], metavar="MODE",
35+
choices=["character", "split", "unified", "score", "json", "html", "format"], metavar="MODE",
3636
help="output mode, which can be character (default), split, unified, score, or json")
3737
parser.add_argument("-v", "--verbose", action="store_true",
3838
help="print full tracebacks of errors")
@@ -43,10 +43,19 @@ def main():
4343
help="print supported file extensions (as JSON list) and exit")
4444
parser.add_argument("-i", "--ignore", action="append", metavar="PATTERN",
4545
help="paths/patterns to be ignored")
46+
parser.add_argument("--clang-format-style", metavar="STYLE",
47+
help="clang-format style string or file:// URI (overrides default CS50 config)")
4648

4749
args = parser.parse_args()
4850
ignore = args.ignore or filter(None, os.getenv("STYLE50_IGNORE", "").split(","))
49-
Style50(args.output).run(args.file, ignore=ignore)
51+
52+
if args.output == "format":
53+
if len(args.file) != 1:
54+
sys.exit("format mode requires exactly one file")
55+
sys.stdout.write(Style50("format", clang_format_style=args.clang_format_style).format_file(args.file[0]))
56+
return
57+
58+
Style50(args.output, clang_format_style=args.clang_format_style).run(args.file, ignore=ignore)
5059

5160

5261

‎style50/_api.py‎

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,13 @@ class Style50:
3333
# Dict that maps substrings of libmagic's outputs to classes. Used as fallback when file extension unrecognized
3434
magic_map = {}
3535

36-
def __init__(self, output="character"):
36+
def __init__(self, output="character", clang_format_style=None):
3737

3838
self._warn_chars = set()
39+
self.clang_format_style = clang_format_style
3940

4041
# Set run function as apropriate for output mode.
41-
if output == "score":
42+
if output in ["score", "format"]:
4243
self.diff = lambda old, new: ""
4344
elif output in ["json", "html"]:
4445
self.diff = self.html_diff
@@ -54,6 +55,11 @@ def __init__(self, output="character"):
5455

5556
self.output = output
5657

58+
def format_file(self, path):
59+
"""Format a single file and return the styled code."""
60+
results = self._check(path)
61+
return results.styled
62+
5763
def run(self, *args, **kwargs):
5864
"""Wraps Style50.check and renders the results using the renderer determined by self.output"""
5965
results = self.check(*args, **kwargs)
@@ -164,6 +170,14 @@ def _check(self, file):
164170
except IndexError:
165171
pass
166172

173+
# Optional per-run configuration hook (e.g., clang-format style override)
174+
if hasattr(check, "configure"):
175+
try:
176+
check.configure(clang_format_style=self.clang_format_style)
177+
except TypeError:
178+
# Backward compatible if configure() signature differs
179+
check.configure()
180+
167181
return check(code)
168182

169183
@staticmethod

‎style50/languages.py‎

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
from tokenize import generate_tokens, STRING, INDENT, COMMENT, TokenError
55

66
import autopep8
7+
import cssbeautifier
78
import jsbeautifier
9+
import sqlparse
810

911
from . import StyleCheck, Error
1012

@@ -18,6 +20,23 @@ class C(StyleCheck):
1820
"clang-format", f"-style={styleConfig}"
1921
]
2022

23+
_clang_format_style_override = None
24+
25+
@classmethod
26+
def configure(cls, clang_format_style=None):
27+
if clang_format_style is None:
28+
cls._clang_format_style_override = None
29+
return
30+
31+
# Guard against VS Code extension accidentally passing stringified undefined/null.
32+
if isinstance(clang_format_style, str):
33+
normalized = clang_format_style.strip().strip('"').strip("'").strip()
34+
if normalized.lower() in {"", "undefined", "null", "none"}:
35+
cls._clang_format_style_override = None
36+
return
37+
38+
cls._clang_format_style_override = clang_format_style
39+
2140
# Match (1) /**/ comments, and (2) // comments.
2241
match_comments = re.compile(r"(\/\*.*?\*\/)|(\/\/[^\n]*)", re.DOTALL)
2342

@@ -35,6 +54,8 @@ def count_comments(self, code):
3554
return sum(1 for _ in self.match_comments.finditer(stripped))
3655

3756
def style(self, code):
57+
if self._clang_format_style_override:
58+
return self.run(["clang-format", f"-style={self._clang_format_style_override}"], input=code)
3859
return self.run(self.clangFormat, input=code)
3960

4061

@@ -101,3 +122,32 @@ class Java(C):
101122
extensions = ["java"]
102123
magic_names = ["Java source"]
103124
clangFormat = C.clangFormat.copy() + ["-assume-filename=.java"]
125+
126+
127+
class Html(StyleCheck):
128+
extensions = ["html"]
129+
magic_names = ["HTML document"]
130+
131+
def style(self, code):
132+
# djhtml reads from stdin when path is '-'
133+
return self.run(["djhtml", "-"], input=code, exit=None)
134+
135+
136+
class Css(StyleCheck):
137+
extensions = ["css"]
138+
magic_names = []
139+
140+
def style(self, code):
141+
opts = cssbeautifier.default_options()
142+
opts.indent_size = 4
143+
opts.end_with_newline = True
144+
return cssbeautifier.beautify(code, opts)
145+
146+
147+
class Sql(StyleCheck):
148+
extensions = ["sql"]
149+
magic_names = []
150+
151+
def style(self, code):
152+
# Keep this lightweight and dependency-minimal while producing stable formatting.
153+
return sqlparse.format(code, reindent=True, keyword_case="upper", indent_width=4) + ("\n" if not code.endswith("\n") else "")

0 commit comments

Comments
 (0)