Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

format hidden code #3

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 106 additions & 15 deletions mdformat_rustfmt/__init__.py
Original file line number Diff line number Diff line change
@@ -1,45 +1,136 @@
__version__ = "0.0.3" # DO NOT EDIT THIS LINE MANUALLY. LET bump2version UTILITY DO IT

from collections.abc import Iterable
import re
import subprocess
from typing import Callable

in_commented = False


def flatten(deep_list):
for el in deep_list:
if isinstance(el, Iterable) and not isinstance(el, (str, bytes)):
yield from flatten(el)
else:
yield el


def format_rust(unformatted: str, _info_str: str) -> str:
global in_commented
global remove_newlines

unformatted = _for_each_line(unformatted, _hide_sharp)

unformatted_bytes = unformatted.encode("utf-8")
result = subprocess.run(
["rustfmt"],
stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL,
input=unformatted_bytes,
)
if result.returncode:
raise Exception("Failed to format Rust code")

formatted = result.stdout.decode("utf-8")
formatted = _for_each_line(formatted, _unhide_sharp)
return formatted

if result.returncode:
raise Exception("Failed to format Rust code\n" + formatted)

in_commented = False
remove_newlines = False

return _for_each_line(formatted, _unhide_sharp).replace("\r", "")


def _for_each_line(string: str, action: Callable[[str], str]) -> str:
lines = string.split("\n")
lines = (action(line) for line in lines)

lines = [action(line) for line in lines]

lines = list(flatten(lines))

lines = [x for x in lines if x is not None]
return "\n".join(lines)


_RUSTFMT_CUSTOM_COMMENT_PREFIX = "//#### "
_RUSTFMT_CUSTOM_COMMENT_BLOCK_BEGIN = "//__MDFORMAT_RUSTFMT_COMMENT_BEGIN__"
_RUSTFMT_CUSTOM_COMMENT_BLOCK_END = "//__MDFORMAT_RUSTFMT_COMMENT_END__"
_RUSTFMT_CUSTOM_COMMENT_ESCAPE = "//__MDFORMAT_RUSTFMT_COMMENT_ESCAPE__"
_RUSTFMT_CUSTOM_COMMENT_BLANK_LINE = "//__MDFORMAT_RUSTFMT_COMMENT_BLANK_LINE__"


def _hide_sharp(line: str) -> str:
def _hide_sharp(line: str):
global in_commented
stripped = line.strip()
if stripped.startswith("# ") or stripped == "#":
return _RUSTFMT_CUSTOM_COMMENT_PREFIX + line
return line

if stripped.startswith("# ") or stripped.startswith("##") or stripped == "#":
tokens = []

if not in_commented:
in_commented = True
tokens.append(_RUSTFMT_CUSTOM_COMMENT_BLOCK_BEGIN)

if stripped.startswith("##"):
tokens.append(_RUSTFMT_CUSTOM_COMMENT_ESCAPE)

# if stripped == "#":
# tokens.append(_RUSTFMT_CUSTOM_COMMENT_BLANK_LINE)

tokens.append(stripped[1:])

return tokens

if in_commented:
in_commented = False
return [_RUSTFMT_CUSTOM_COMMENT_BLOCK_END, stripped]

return stripped


next_line_escape = False
remove_newlines = False


def _unhide_sharp(line: str):
global in_commented
global next_line_escape
global remove_newlines

if _RUSTFMT_CUSTOM_COMMENT_BLOCK_BEGIN in line:
remove_newlines = True
in_commented = True
line = re.sub(
re.escape(_RUSTFMT_CUSTOM_COMMENT_BLOCK_BEGIN), "", line, 1
).rstrip()
return line or None

if _RUSTFMT_CUSTOM_COMMENT_BLOCK_END in line:
in_commented = False
line = re.sub(
re.escape(_RUSTFMT_CUSTOM_COMMENT_BLOCK_END), "", line, 1
).rstrip()
return line or None

if _RUSTFMT_CUSTOM_COMMENT_ESCAPE in line:
next_line_escape = True
line = re.sub(re.escape(_RUSTFMT_CUSTOM_COMMENT_ESCAPE), "", line, 1).rstrip()
return line or None

if _RUSTFMT_CUSTOM_COMMENT_BLANK_LINE in line:
return "#"

if in_commented:
if line == "" and remove_newlines:
return None

remove_newlines = False

if line.startswith("#") and next_line_escape:
next_line_escape = False
return "#" + line

if line.startswith(" "):
return "#" + line[1:]

return "# " + line

def _unhide_sharp(line: str) -> str:
if re.match(r"\s*" + re.escape(_RUSTFMT_CUSTOM_COMMENT_PREFIX), line):
# Remove the first "rustfmt custom comment prefix" and any leading
# whitespace the prefixed line originally had.
return re.sub(re.escape(_RUSTFMT_CUSTOM_COMMENT_PREFIX) + r"\s*", "", line, 1)
return line
1 change: 1 addition & 0 deletions rustfmt.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
blank_lines_lower_bound = 1
123 changes: 122 additions & 1 deletion tests/data/fixtures.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,131 @@ fn main() {
.
```rust
fn main() {
# let x=a();b();c();
# let x = a();
# b();
# c();
let y = d();
e();
f();
}
```
.

Format hidden lines
.
~~~rust
fn main() {
# let x=a();b();c();
}
~~~
.
```rust
fn main() {
# let x = a();
# b();
# c();
}
```
.

Handle empty comment lines
.
~~~rust
fn main() {
#
#
# // comment
let s = "asdf
## literal hash";
let x = 5;
let y = 6;
}
~~~
.
```rust
fn main() {
# // comment
let s = "asdf
## literal hash";
let x = 5;
let y = 6;
}
```
.

Handle hidden derive and attr statements
.
~~~rust
# #[derive(Debug)]
struct MyStruct {}
~~~
.
```rust
# #[derive(Debug)]
struct MyStruct {}
```
.
Handle derive and attr statements
.
~~~rust
#[derive(Debug)]
struct MyStruct {}
~~~
.
```rust
#[derive(Debug)]
struct MyStruct {}
```
.
[NIGHTLY] Preserve blank lines
.
~~~rust
# struct Something {}
#
#
# fn main() {}
#
#
trait SomeTrait { fn nothing() {} }
struct Another;

impl Another {}
~~~
.
```rust
# struct Something {}
#
# fn main() {}
#
trait SomeTrait {
fn nothing() {}
}

struct Another;

impl Another {}
```
.
Comment collapse does not delete lines
.
~~~rust
# fn main() -> Result<(), amethyst::Error> {
# let game_data = DispatcherBuilder::default().with_bundle(
// inside your rendering bundle setup
RenderingBundle::<DefaultBackend>::new()
.with_plugin(RenderFlat2D::default())
# )?;
# Ok(())
# }
~~~
.
```rust
# fn main() -> Result<(), amethyst::Error> {
# let game_data = DispatcherBuilder::default().with_bundle(
// inside your rendering bundle setup
RenderingBundle::<DefaultBackend>::new().with_plugin(RenderFlat2D::default()),
# )?;
# Ok(())
# }
```
.
2 changes: 2 additions & 0 deletions tests/test_mdformat_rustfmt.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
)
def test_fixtures(line, title, text, expected):
"""Test fixtures in tests/data/fixtures.md."""
if "NIGHTLY" in title:
pytest.skip("nightly test not supported on stable")
md_new = mdformat.text(text, codeformatters={"rust"})
if md_new != expected:
print("Formatted (unexpected) Markdown below:")
Expand Down