prettycsharp 1.0.0__py3-none-any.whl

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.
prettycsharp/verify.py ADDED
@@ -0,0 +1,312 @@
1
+ """Checking that formatting changed layout, never meaning.
2
+
3
+ The input and the output are parsed and reduced to canonical syntax trees,
4
+ which must be equal. Canonicalization removes the differences the rewrites
5
+ are allowed to make, each in its simplest form: every parenthesized
6
+ expression is replaced by what it holds (the tree keeps the grouping), the
7
+ modifiers of a declaration and a run of using directives are sorted, an
8
+ attribute list is split into one list per attribute, a trailing comma is
9
+ dropped, a single-statement body is put in expression form, a namespace is
10
+ made file-scoped, an embedded statement gets braces, and regions are
11
+ dropped. Comments must hold the same words, counted as a multiset, since
12
+ rewrapping and reordering move them.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ from collections import Counter
18
+ from collections.abc import Sequence
19
+
20
+ from prettycsharp.syntax import Comment, Node, SourceFile
21
+
22
+ Canon = tuple[object, ...]
23
+
24
+ _EMBEDDED = {
25
+ "if_statement": ("consequence", "alternative"),
26
+ "while_statement": ("body",),
27
+ "for_statement": ("body",),
28
+ "foreach_statement": ("body",),
29
+ "do_statement": ("body",),
30
+ "using_statement": ("body",),
31
+ "lock_statement": ("body",),
32
+ "fixed_statement": ("body",),
33
+ }
34
+
35
+ _BODY_OWNERS = frozenset(
36
+ {
37
+ "method_declaration",
38
+ "constructor_declaration",
39
+ "destructor_declaration",
40
+ "operator_declaration",
41
+ "conversion_operator_declaration",
42
+ "local_function_statement",
43
+ "accessor_declaration",
44
+ }
45
+ )
46
+
47
+ _MODIFIER_OWNERS = frozenset(
48
+ {
49
+ "class_declaration",
50
+ "struct_declaration",
51
+ "interface_declaration",
52
+ "record_declaration",
53
+ "enum_declaration",
54
+ "delegate_declaration",
55
+ "method_declaration",
56
+ "constructor_declaration",
57
+ "destructor_declaration",
58
+ "operator_declaration",
59
+ "conversion_operator_declaration",
60
+ "property_declaration",
61
+ "indexer_declaration",
62
+ "event_declaration",
63
+ "event_field_declaration",
64
+ "field_declaration",
65
+ "local_function_statement",
66
+ }
67
+ )
68
+
69
+
70
+ class VerificationError(Exception):
71
+ """The formatted text would not mean what the source meant."""
72
+
73
+
74
+ def verify(before: SourceFile, after: SourceFile) -> None:
75
+ left = canonical(before.root)
76
+ right = canonical(after.root)
77
+ if left != right:
78
+ raise VerificationError(f"the formatted code differs from the source ({_first_difference(left, right)})")
79
+ if comment_words(before) != comment_words(after):
80
+ missing = comment_words(before) - comment_words(after)
81
+ extra = comment_words(after) - comment_words(before)
82
+ detail = next(iter(missing or extra))
83
+ raise VerificationError(f"the comments changed ({detail!r})")
84
+
85
+
86
+ def comment_words(file: SourceFile) -> Counter[str]:
87
+ words: Counter[str] = Counter()
88
+ for comment in _all_comments(file):
89
+ text = comment.text
90
+ if text.startswith("///") and not text.startswith("////"):
91
+ words["///" + text[3:].strip()] += 1
92
+ elif text.startswith("//"):
93
+ words.update(text[2:].split())
94
+ else:
95
+ lines = [line.strip() for line in text.splitlines()]
96
+ words["/*" + "\n".join(lines)] += 1
97
+ return words
98
+
99
+
100
+ def _all_comments(file: SourceFile) -> list[Comment]:
101
+ comments: list[Comment] = list(file.trailing_comments)
102
+ for token in file.tokens:
103
+ comments.extend(token.leading)
104
+ comments.extend(token.trailing)
105
+ return comments
106
+
107
+
108
+ def canonical(node: Node) -> Canon:
109
+ if node.text is not None:
110
+ text = node.text.strip() if node.type == "preproc_arg" else node.text
111
+ return (node.type, text)
112
+ if node.type == "parenthesized_expression":
113
+ inner = [c for c in node.children if c.named]
114
+ if len(inner) == 1 and node.children[0].text == "(" and node.children[-1].text == ")":
115
+ return canonical(inner[0])
116
+ children = list(node.children)
117
+ if node.type == "compilation_unit":
118
+ children = _file_scoped(children)
119
+ if node.type in _BODY_OWNERS:
120
+ children = _expression_body(children)
121
+ if node.type in ("property_declaration", "indexer_declaration"):
122
+ children = _property_arrow(children)
123
+ out: list[object] = []
124
+ i = 0
125
+ while i < len(children):
126
+ child = children[i]
127
+ if child.type in ("preproc_region", "preproc_endregion"):
128
+ i += 1
129
+ continue
130
+ if child.type == "attribute_list" or child.type == "global_attribute":
131
+ out.extend(_split_attributes(child))
132
+ elif child.type == "using_directive":
133
+ run = []
134
+ while i < len(children) and children[i].type == "using_directive":
135
+ run.append(canonical(children[i]))
136
+ i += 1
137
+ out.extend(sorted(run, key=repr))
138
+ continue
139
+ elif child.type == "modifier" and node.type in _MODIFIER_OWNERS:
140
+ run = []
141
+ while i < len(children) and children[i].type == "modifier":
142
+ run.append(canonical(children[i]))
143
+ i += 1
144
+ out.extend(sorted(run, key=repr))
145
+ continue
146
+ elif (
147
+ child.text == ","
148
+ and i == len(children) - 2
149
+ and children[-1].text in ("}", "]")
150
+ and node.type != "array_rank_specifier"
151
+ ):
152
+ pass
153
+ elif node.type in _EMBEDDED and is_embedded(node, child) and _needs_braces(node, child):
154
+ out.append(("block", (("{", "{"), canonical(child), ("}", "}"))))
155
+ else:
156
+ out.append(canonical(child))
157
+ i += 1
158
+ return (node.type, tuple(out))
159
+
160
+
161
+ def is_embedded(parent: Node, child: Node) -> bool:
162
+ """Whether ``child`` is a statement embedded in ``parent``, such as an if's body."""
163
+ fields = _EMBEDDED.get(parent.type)
164
+ if fields is None:
165
+ return False
166
+ if child.field in fields:
167
+ return True
168
+ # lock and fixed statements keep their body as an unnamed last child.
169
+ return (
170
+ parent.type in ("lock_statement", "fixed_statement")
171
+ and child is parent.children[-1]
172
+ and child.named
173
+ and child.type.endswith(("statement", "block"))
174
+ )
175
+
176
+
177
+ def _needs_braces(parent: Node, child: Node) -> bool:
178
+ if child.type == "block":
179
+ return False
180
+ if parent.type == "if_statement" and child.field == "alternative" and child.type == "if_statement":
181
+ return False
182
+ return True
183
+
184
+
185
+ def _split_attributes(node: Node) -> list[Canon]:
186
+ """``[t: A, B]`` as ``[t: A]`` ``[t: B]``."""
187
+ target: list[Canon] = []
188
+ attributes: list[Node] = []
189
+ for child in node.children:
190
+ if child.type == "attribute":
191
+ attributes.append(child)
192
+ elif child.type == "attribute_target_specifier":
193
+ target.append(canonical(child))
194
+ elif child.text not in ("[", "]", ","):
195
+ target.append(canonical(child))
196
+ if not attributes:
197
+ return [canonical(node)]
198
+ return [("attribute_list", (("[", "["), *target, canonical(a), ("]", "]"))) for a in attributes]
199
+
200
+
201
+ def _expression_body(children: list[Node]) -> list[Node]:
202
+ """A block body of one return, expression or throw statement, in arrow form."""
203
+ for index, child in enumerate(children):
204
+ if child.field != "body" or child.type != "block":
205
+ continue
206
+ statements = child.children[1:-1]
207
+ if len(statements) != 1:
208
+ return children
209
+ statement = statements[0]
210
+ expression = _arrow_expression(statement)
211
+ if expression is None:
212
+ return children
213
+ arrow = Node(type="arrow_expression_clause", field="body")
214
+ arrow.children = [Node(type="=>", named=False, text="=>"), expression]
215
+ semicolon = Node(type=";", named=False, text=";")
216
+ rest = [c for c in children[index + 1 :] if c.text != ";"]
217
+ return [*children[:index], arrow, semicolon, *rest]
218
+ return children
219
+
220
+
221
+ def _arrow_expression(statement: Node) -> Node | None:
222
+ kids = statement.children
223
+ if statement.type == "expression_statement" and len(kids) == 2 and kids[1].text == ";":
224
+ return kids[0]
225
+ # return x; and return this;, not return ref x;
226
+ if statement.type == "return_statement" and len(kids) == 3 and kids[2].text == ";" and kids[1].text != "ref":
227
+ return kids[1]
228
+ if statement.type == "throw_statement" and len(kids) == 3 and kids[2].text == ";":
229
+ thrown = Node(type="throw_expression")
230
+ thrown.children = [kids[0], kids[1]]
231
+ return thrown
232
+ return None
233
+
234
+
235
+ def _property_arrow(children: list[Node]) -> list[Node]:
236
+ """A property with only a plain getter, in arrow form."""
237
+ for index, child in enumerate(children):
238
+ if child.type != "accessor_list":
239
+ continue
240
+ accessors = [c for c in child.children if c.type == "accessor_declaration"]
241
+ others = [c for c in child.children if c.type != "accessor_declaration" and c.text not in ("{", "}")]
242
+ if len(accessors) != 1 or others or index != len(children) - 1:
243
+ return children
244
+ accessor = accessors[0]
245
+ if [c.text for c in accessor.children if c.type != "block" and c.type != "arrow_expression_clause"] not in (
246
+ ["get"],
247
+ ["get", ";"],
248
+ ):
249
+ return children
250
+ body = _expression_body(accessor.children)
251
+ arrow = next((c for c in body if c.type == "arrow_expression_clause"), None)
252
+ if arrow is None:
253
+ return children
254
+ semicolon = Node(type=";", named=False, text=";")
255
+ return [*children[:index], arrow, semicolon]
256
+ return children
257
+
258
+
259
+ def _file_scoped(children: list[Node]) -> list[Node]:
260
+ """The one block namespace of a file, as a file-scoped namespace."""
261
+ namespaces = [c for c in children if c.type == "namespace_declaration"]
262
+ if len(namespaces) != 1 or any(c.type == "file_scoped_namespace_declaration" for c in children):
263
+ return children
264
+ namespace = namespaces[0]
265
+ index = children.index(namespace)
266
+ allowed = ("using_directive", "extern_alias_directive", "global_attribute")
267
+ if any(c.type not in allowed and not c.type.startswith("preproc") for c in children[:index]):
268
+ return children
269
+ if any(not c.type.startswith("preproc") for c in children[index + 1 :]):
270
+ return children
271
+ body = namespace.child("body")
272
+ if body is None or body.type != "declaration_list" or len(body.children) < 2:
273
+ return children
274
+ members = body.children[1:-1]
275
+ if any(_contains_namespace(m) for m in members):
276
+ return children
277
+ head = [c for c in namespace.children if c is not body]
278
+ if any(c.text == ";" for c in head):
279
+ return children
280
+ scoped = Node(type="file_scoped_namespace_declaration")
281
+ scoped.children = [*head, Node(type=";", named=False, text=";")]
282
+ return [*children[:index], scoped, *members, *children[index + 1 :]]
283
+
284
+
285
+ def _contains_namespace(node: Node) -> bool:
286
+ stack = [node]
287
+ while stack:
288
+ current = stack.pop()
289
+ if current.type in ("namespace_declaration", "file_scoped_namespace_declaration"):
290
+ return True
291
+ stack.extend(current.children)
292
+ return False
293
+
294
+
295
+ def _first_difference(left: object, right: object, path: Sequence[str] = ()) -> str:
296
+ if isinstance(left, tuple) and isinstance(right, tuple):
297
+ if len(left) == 2 and len(right) == 2 and isinstance(left[1], str) and isinstance(right[1], str):
298
+ if left != right:
299
+ return f"{'/'.join(path)}: {left[1]!r} became {right[1]!r}"
300
+ return ""
301
+ if len(left) == 2 and len(right) == 2 and left[0] == right[0]:
302
+ a, b = left[1], right[1]
303
+ if isinstance(a, tuple) and isinstance(b, tuple):
304
+ for x, y in zip(a, b, strict=False):
305
+ found = _first_difference(x, y, (*path, str(left[0])))
306
+ if found:
307
+ return found
308
+ if len(a) != len(b):
309
+ return f"{'/'.join((*path, str(left[0])))}: {len(a)} children became {len(b)}"
310
+ return ""
311
+ return f"{'/'.join(path)}: {left[0]} became {right[0]}"
312
+ return "/".join(path)
@@ -0,0 +1,212 @@
1
+ Metadata-Version: 2.5
2
+ Name: prettycsharp
3
+ Version: 1.0.0
4
+ Summary: An opinionated formatter for C#, in the spirit of Prettier
5
+ Project-URL: Homepage, https://github.com/WilliamSmithEdward/prettyCSharp
6
+ Project-URL: Repository, https://github.com/WilliamSmithEdward/prettyCSharp
7
+ Project-URL: Issues, https://github.com/WilliamSmithEdward/prettyCSharp/issues
8
+ Author: William Smith Edward
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: c#,code-style,csharp,dotnet,formatter,prettier,tree-sitter
12
+ Classifier: Development Status :: 5 - Production/Stable
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3 :: Only
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Programming Language :: Python :: 3.14
23
+ Classifier: Topic :: Software Development :: Quality Assurance
24
+ Classifier: Typing :: Typed
25
+ Requires-Python: >=3.10
26
+ Requires-Dist: defusedxml>=0.7.1
27
+ Requires-Dist: tomli>=1.1.0; python_version < '3.11'
28
+ Requires-Dist: tree-sitter-c-sharp>=0.23.1
29
+ Requires-Dist: tree-sitter>=0.25
30
+ Provides-Extra: dev
31
+ Requires-Dist: mypy==2.4.0; extra == 'dev'
32
+ Requires-Dist: pytest==9.1.1; extra == 'dev'
33
+ Requires-Dist: ruff==0.17.0; extra == 'dev'
34
+ Description-Content-Type: text/markdown
35
+
36
+ # prettycsharp
37
+
38
+ [![PyPI version](https://img.shields.io/pypi/v/prettycsharp)](https://pypi.org/project/prettycsharp/)
39
+ [![Python versions](https://img.shields.io/pypi/pyversions/prettycsharp)](https://pypi.org/project/prettycsharp/)
40
+ [![Downloads](https://static.pepy.tech/badge/prettycsharp/month)](https://pepy.tech/projects/prettycsharp)
41
+ [![CI](https://github.com/WilliamSmithEdward/prettyCSharp/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/WilliamSmithEdward/prettyCSharp/actions/workflows/ci.yml)
42
+ [![Security](https://github.com/WilliamSmithEdward/prettyCSharp/actions/workflows/security.yml/badge.svg?branch=main)](https://github.com/WilliamSmithEdward/prettyCSharp/actions/workflows/security.yml)
43
+ [![Malware scan](https://github.com/WilliamSmithEdward/prettyCSharp/actions/workflows/malware-scan.yml/badge.svg?branch=main)](https://github.com/WilliamSmithEdward/prettyCSharp/actions/workflows/malware-scan.yml)
44
+ [![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/WilliamSmithEdward/prettyCSharp/badge)](https://scorecard.dev/viewer/?uri=github.com/WilliamSmithEdward/prettyCSharp)
45
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue)](https://github.com/WilliamSmithEdward/prettyCSharp/blob/main/LICENSE)
46
+
47
+ An opinionated formatter for C#, written in Python, in the spirit of Prettier.
48
+ It parses each file with [tree-sitter](https://tree-sitter.github.io/) and
49
+ reprints the whole file in one consistent layout: braces, indentation, line
50
+ breaks, blank lines, alignment and a few safe rewrites such as file-scoped
51
+ namespaces, sorted usings and expression-bodied members. It needs no .NET
52
+ SDK.
53
+
54
+ Before:
55
+
56
+ ```csharp
57
+ using System;
58
+ using Microsoft.Extensions.Logging;
59
+ namespace Shop.Orders {
60
+ public class OrderService {
61
+ static private readonly int MaxRetries=3;
62
+ public decimal Total(Order order) { return order.Lines.Where(l => l.IsTaxable).Sum(l => l.Amount); }
63
+ public void Ship(Order order) {
64
+ if (order == null) return;
65
+ if (order.IsPaid) { Send(order); } else { Hold(order); }
66
+ }
67
+ }
68
+ }
69
+ ```
70
+
71
+ After:
72
+
73
+ ```csharp
74
+ using Microsoft.Extensions.Logging;
75
+ using System;
76
+
77
+ namespace Shop.Orders;
78
+
79
+ public class OrderService
80
+ {
81
+ private static readonly int MaxRetries = 3;
82
+
83
+ public decimal Total(Order order) =>
84
+ order.Lines
85
+ .Where(l => l.IsTaxable)
86
+ .Sum(l => l.Amount);
87
+
88
+ public void Ship(Order order)
89
+ {
90
+ if (order == null) return;
91
+
92
+ if (order.IsPaid)
93
+ {
94
+ Send(order);
95
+ }
96
+
97
+ else
98
+ {
99
+ Hold(order);
100
+ }
101
+ }
102
+ }
103
+ ```
104
+
105
+ ## Install
106
+
107
+ Python 3.10 or later.
108
+
109
+ ```bash
110
+ pip install prettycsharp
111
+ ```
112
+
113
+ ## Use
114
+
115
+ ```bash
116
+ prettycsharp format src/
117
+ ```
118
+ ```bash
119
+ prettycsharp check src/
120
+ ```
121
+ ```bash
122
+ prettycsharp diff Order.cs
123
+ ```
124
+
125
+ `format` rewrites files in place, `check` exits 1 when any file would
126
+ change, and `diff` prints a unified diff. `--write`, `--check` and `--diff`
127
+ are aliases. Given one file and no command, the formatted file goes to
128
+ stdout; `-` formats stdin to stdout, with `--stdin-filepath` naming the
129
+ path whose settings apply.
130
+
131
+ A target can be a `.cs` file, a folder, a `.csproj` (the files it
132
+ compiles, after its `Compile Include` and `Remove` items) or a `.sln` or
133
+ `.slnx` (every C# project in it). Folders are searched for `.cs` files.
134
+
135
+ | Option | Meaning |
136
+ | --- | --- |
137
+ | `-o KEY=VALUE`, `--option` | Set one setting, over every settings file. Repeatable. |
138
+ | `--config FILE` | Use this settings file instead of searching for one. |
139
+ | `--no-ignore` | Do not skip what `.gitignore` and `.prettycsharpignore` list. |
140
+ | `--stdin-filepath PATH` | The path stdin stands for. |
141
+
142
+ Exit status: 0 when nothing needs to change, 1 when `check` or `diff` found
143
+ a file that would change, 2 when a file could not be read, parsed or
144
+ formatted, or a setting is wrong. A file that does not parse is reported
145
+ and left untouched.
146
+
147
+ From Python:
148
+
149
+ ```python
150
+ from prettycsharp import CSharpSyntaxError, FormattingError, Settings, format_source
151
+
152
+ formatted = format_source(source)
153
+ narrow = format_source(source, Settings().with_values({"max_line_length": 100}))
154
+ ```
155
+
156
+ ## Settings
157
+
158
+ Settings come from, highest precedence first:
159
+
160
+ 1. `-o` options on the command line;
161
+ 2. the nearest `.prettycsharp.toml`, or `pyproject.toml` with a
162
+ `[tool.prettycsharp]` table, found by walking up from each file;
163
+ 3. the standard keys of `.editorconfig` (`indent_style`, `indent_size`,
164
+ `max_line_length`, `end_of_line`, `charset`, `insert_final_newline`,
165
+ and the Roslyn keys for braces, `else`/`catch`/`finally` placement, using
166
+ order, namespace style, braces and modifier order);
167
+ 4. the defaults.
168
+
169
+ ```toml
170
+ # .prettycsharp.toml
171
+ max_line_length = 100
172
+ brace_style = "kr"
173
+ continuation_keywords = "same_line"
174
+ align_assignments = false
175
+ ```
176
+
177
+ Every setting, its default, its alternatives and an example of each layout
178
+ are in
179
+ [DESIGN.md](https://github.com/WilliamSmithEdward/prettyCSharp/blob/main/DESIGN.md).
180
+
181
+ ## Skipping files and code
182
+
183
+ `bin/`, `obj/`, hidden folders and generated files (`*.g.cs`,
184
+ `*.Designer.cs`, and files marked `<auto-generated>`) are always skipped.
185
+ So is whatever `.gitignore` lists inside a git repository, and whatever a
186
+ `.prettycsharpignore` file lists, in gitignore syntax.
187
+
188
+ Inside a file, `// prettycsharp-ignore` leaves the next statement or member
189
+ as written, and `// prettycsharp-ignore-start` ... `// prettycsharp-ignore-end`
190
+ leaves everything between them as written.
191
+
192
+ ## How it stays safe
193
+
194
+ Every output is parsed again and compared with the input as syntax trees,
195
+ with only the changes the settings allow (parentheses, modifier and using
196
+ order, attribute lists, trailing commas, expression bodies, namespace style,
197
+ braces and regions) taken out of the comparison. The comments must hold the
198
+ same words. If anything else differs, the file is not changed and the
199
+ command reports the difference. String literals, including verbatim, raw and
200
+ interpolated strings, are printed exactly as written.
201
+
202
+ ## Limits
203
+
204
+ prettycsharp reads C# through tree-sitter's C# grammar, which parses every
205
+ `#if` branch as code. A declaration or statement split between `#if` and
206
+ `#else` therefore does not parse, and neither does syntax newer than the
207
+ grammar (such as C# 14's null-conditional assignment). Such files are
208
+ reported with their line and column and left untouched.
209
+
210
+ ## License
211
+
212
+ [MIT](https://github.com/WilliamSmithEdward/prettyCSharp/blob/main/LICENSE)
@@ -0,0 +1,24 @@
1
+ prettycsharp/__init__.py,sha256=2YPCftqUxMqYgSCIdxolZEgiJUTzze9bg7Lfukgv1vY,378
2
+ prettycsharp/__main__.py,sha256=YUL16nU2FBOx2nJWBvuMaqSUo73TWH4tEyat839_TIw,64
3
+ prettycsharp/cli.py,sha256=yEsWvztAd9ulnnkC5MNU5vHTVBq-D3zrPnV3M_qtPhw,9949
4
+ prettycsharp/config.py,sha256=uxyv-fESIrJewN9203MHCdx9NVeUNKT7BucXjoVQt_s,9221
5
+ prettycsharp/doc.py,sha256=3_b-qNVEbf_PodyBp59lMfCIz-8jSAu24OdfF6IafHM,20097
6
+ prettycsharp/formatter.py,sha256=hjTFhtvd1rQlVpYLRBmcM9hsmNqjihPD4tPpv2ed2OQ,3808
7
+ prettycsharp/ignore.py,sha256=gVltaT7c6IbA18AHW5w1EH6LvmG9vPfSVfze0wtpyPg,6205
8
+ prettycsharp/parsing.py,sha256=jBKIM4c8quIB0WAER1i0GgTkD7S-zcgJuBlGadfajtI,1809
9
+ prettycsharp/project.py,sha256=mzps6K-_vHBfWjBiltZiIXSwbmTKu0HHMOM0XKfKlnE,7178
10
+ prettycsharp/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
+ prettycsharp/rewrites.py,sha256=lrIJFmP3QkmmQLEBZcenljttrBZzgp8NJf50a1Ta7pw,27295
12
+ prettycsharp/settings.py,sha256=4Zo_dxUyZHFA0VqZQPmtFV88Y_1_zUwjbX6s7jEBViw,7037
13
+ prettycsharp/syntax.py,sha256=jzxS7JeYhrn4vNbO7UetDIm42ur3X3I5ESsnaDqd03A,12378
14
+ prettycsharp/verify.py,sha256=usnNWXu68bu3Wz-dafg7pA1VbNMj7rTlMwYbPI9oEgw,12210
15
+ prettycsharp/printer/__init__.py,sha256=Uz_MD6_IaW9GWuZXSQIX2pW3UDdwrig3e1d4ZMfAsKE,994
16
+ prettycsharp/printer/core.py,sha256=ugiGlig7u064cwUiUy5x_mHBKUgDeF8cBzSnaX-sEcU,20295
17
+ prettycsharp/printer/declarations.py,sha256=M3jsLOpY-GOPvPKynETb4Yt8ASla-sF16s8ykmcm_2s,26246
18
+ prettycsharp/printer/expressions.py,sha256=jOOuo9kp62fEzqexv5LwqzQHvvgsqh1_dCjJlnbWFdc,45581
19
+ prettycsharp/printer/statements.py,sha256=x7PhyAMfrnMQVDMhB-HM68tonq5EsceSvVguMznIQ3Y,26618
20
+ prettycsharp-1.0.0.dist-info/METADATA,sha256=wHaFQWHgVZbOtWkvtT8yg_Mr1RTpmgFbHupnY56nUwA,7922
21
+ prettycsharp-1.0.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
22
+ prettycsharp-1.0.0.dist-info/entry_points.txt,sha256=LezlCMNm4uvzhnLmoneMBwVwBbA7ye1HJSPlO8JI3Uc,55
23
+ prettycsharp-1.0.0.dist-info/licenses/LICENSE,sha256=iwoVDiLOSUadguAENb1gGfsHgtnNubULiepCUeG7RXw,1077
24
+ prettycsharp-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.32.4
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ prettycsharp = prettycsharp.cli:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 William Smith Edward
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.