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

Write full Jupyter notebook to stdout #7748

Merged
merged 1 commit into from Oct 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 2 additions & 2 deletions crates/ruff_cli/src/diagnostics.rs
Expand Up @@ -402,7 +402,7 @@ pub(crate) fn lint_stdin(
match fix_mode {
flags::FixMode::Apply => {
// Write the contents to stdout, regardless of whether any errors were fixed.
io::stdout().write_all(transformed.source_code().as_bytes())?;
transformed.write(&mut io::stdout().lock())?;
}
flags::FixMode::Diff => {
// But only write a diff if it's non-empty.
Expand Down Expand Up @@ -441,7 +441,7 @@ pub(crate) fn lint_stdin(

// Write the contents to stdout anyway.
if fix_mode.is_apply() {
io::stdout().write_all(source_kind.source_code().as_bytes())?;
source_kind.write(&mut io::stdout().lock())?;
}

(result, fixed)
Expand Down
171 changes: 169 additions & 2 deletions crates/ruff_cli/tests/integration_test.rs
Expand Up @@ -75,8 +75,8 @@ fn stdin_filename() {
"###);
}

#[test]
/// Raise `TCH` errors in `.py` files ...
#[test]
fn stdin_source_type_py() {
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.args(STDIN_BASE_OPTIONS)
Expand Down Expand Up @@ -136,7 +136,7 @@ fn stdin_json() {
}

#[test]
fn stdin_fix() {
fn stdin_fix_py() {
let args = ["--fix"];
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.args(STDIN_BASE_OPTIONS)
Expand All @@ -153,6 +153,173 @@ fn stdin_fix() {
"###);
}

#[test]
fn stdin_fix_jupyter() {
let args = ["--fix", "--stdin-filename", "Jupyter.ipynb"];
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.args(STDIN_BASE_OPTIONS)
.args(args)
.pass_stdin(r#"{
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"id": "dccc687c-96e2-4604-b957-a8a89b5bec06",
"metadata": {},
"outputs": [],
"source": [
"import os"
]
},
{
"cell_type": "markdown",
"id": "19e1b029-f516-4662-a9b9-623b93edac1a",
"metadata": {},
"source": [
"Foo"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "cdce7b92-b0fb-4c02-86f6-e233b26fa84f",
"metadata": {},
"outputs": [],
"source": [
"import sys"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "e40b33d2-7fe4-46c5-bdf0-8802f3052565",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"1\n"
]
}
],
"source": [
"print(1)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a1899bc8-d46f-4ec0-b1d1-e1ca0f04bf60",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.2"
}
},
"nbformat": 4,
"nbformat_minor": 5
}"#), @r###"
success: true
exit_code: 0
----- stdout -----
{
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"id": "dccc687c-96e2-4604-b957-a8a89b5bec06",
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "markdown",
"id": "19e1b029-f516-4662-a9b9-623b93edac1a",
"metadata": {},
"source": [
"Foo"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "cdce7b92-b0fb-4c02-86f6-e233b26fa84f",
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "code",
"execution_count": 3,
"id": "e40b33d2-7fe4-46c5-bdf0-8802f3052565",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"1\n"
]
}
],
"source": [
"print(1)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a1899bc8-d46f-4ec0-b1d1-e1ca0f04bf60",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.2"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
----- stderr -----
"###);
}

#[test]
fn stdin_fix_when_not_fixable_should_still_print_contents() {
let args = ["--fix"];
Expand Down
15 changes: 15 additions & 0 deletions crates/ruff_linter/src/source_kind.rs
@@ -1,3 +1,7 @@
use std::io::Write;

use anyhow::Result;

use ruff_diagnostics::SourceMap;
use ruff_notebook::Notebook;

Expand All @@ -22,10 +26,21 @@ impl SourceKind {
}
}

/// Returns the Python source code for this source kind.
pub fn source_code(&self) -> &str {
match self {
SourceKind::Python(source) => source,
SourceKind::IpyNotebook(notebook) => notebook.source_code(),
}
}

/// Write the transformed source file to the given writer.
///
/// For Jupyter notebooks, this will write out the notebook as JSON.
pub fn write(&self, writer: &mut dyn Write) -> Result<()> {
match self {
SourceKind::Python(source) => writer.write_all(source.as_bytes()).map_err(Into::into),
SourceKind::IpyNotebook(notebook) => notebook.write(writer).map_err(Into::into),
}
}
}
6 changes: 4 additions & 2 deletions crates/ruff_notebook/src/notebook.rs
Expand Up @@ -414,11 +414,13 @@ impl Notebook {
}

/// Write the notebook back to the given [`Write`] implementor.
pub fn write(&self, writer: &mut dyn Write) -> anyhow::Result<()> {
pub fn write(&self, writer: &mut dyn Write) -> Result<(), NotebookError> {
// https://github.com/psf/black/blob/69ca0a4c7a365c5f5eea519a90980bab72cab764/src/black/__init__.py#LL1041
let formatter = serde_json::ser::PrettyFormatter::with_indent(b" ");
let mut serializer = serde_json::Serializer::with_formatter(writer, formatter);
SortAlphabetically(&self.raw).serialize(&mut serializer)?;
SortAlphabetically(&self.raw)
.serialize(&mut serializer)
.map_err(NotebookError::Json)?;
if self.trailing_newline {
writeln!(serializer.into_inner())?;
}
Expand Down