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

refactor and polish the source code of the library #29

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
remove useless comments and document
  • Loading branch information
amtoine committed Jun 16, 2023
commit 98324ec69c33828011f618546745269a3c6d7548
17 changes: 1 addition & 16 deletions src/config.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,9 @@
//! Config module
//!
//! This keeps all the options, tweaks and dials of the configuration.

use anyhow::Result;
//! Keeps all the options, tweaks and dials of the configuration.

#[derive(Debug)]
/// Configurations available to the formatter
pub struct Config {
/// Number of spaces of indent.
pub tab_spaces: usize,
/// Maximum width of each line.
pub max_width: usize,
/// Number of lines bafore and after a custom command.
pub margin: usize,
}

Expand All @@ -26,8 +18,6 @@ impl Default for Config {
}

impl Config {
/// Creates a new config. You need to pass every field to create the config.
/// You cannot skip any field yet.
pub fn new(tab_spaces: usize, max_width: usize, margin: usize) -> Self {
Config {
tab_spaces,
Expand All @@ -36,8 +26,3 @@ impl Config {
}
}
}

/// Returns a default config.
pub fn load(/* file_path: Option<&Path> */) -> Result<Config> {
Ok(Config::default())
}
72 changes: 17 additions & 55 deletions src/formatting.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
//! Formatting module
//!
//! In this module occurs most of the magic in `nufmt`.
//! It has functions to format slice of bytes and some help functions to separate concerns while doing the job.
//!
//! It has functions to format slice of bytes and some help functions to separate concerns while doing the job.
use crate::config::Config;
use log::{info, trace};
use nu_parser::{flatten_block, parse, FlatShape};
Expand All @@ -12,51 +10,41 @@ use nu_protocol::{
Span,
};

/// Format an array of bytes
/// format an array of bytes
///
/// Reading the file gives you a list of bytes
pub fn format_inner(contents: &[u8], _config: &Config) -> Vec<u8> {
// nice place to measure formatting time
// let mut timer = Timer::start();

// parsing starts
let engine_state = engine::EngineState::new();
let mut working_set = StateWorkingSet::new(&engine_state);

let parsed_block = parse(&mut working_set, None, contents, false);
trace!("parsed block:\n{:?}", &parsed_block);

// check if the block has at least 1 pipeline
if !block_has_pipelines(&parsed_block) {
trace!("block has no pipelines!");
info!("File has no code to format.");
return contents.to_vec();
}
// flat is a list of (Span , Flatshape)
//
// Span is the piece of code. You can stringfy the contents.
// Flatshape is an enum of the type of token read by the AST.

let flat = flatten_block(&working_set, &parsed_block);
trace!("flattened block:\n{:?}", &flat);
// timer = timer.done_parsing()

// formatting starts
let mut out: Vec<u8> = vec![];

let mut start = 0;
let end_of_file = contents.len();

for (span, shape) in flat.clone() {
// check if span skipped some bytes before the current span
if span.start > start {
trace!(
"Span didn't started on the beginning! span {0}, start: {1}",
"Span does not start at the beginning! span {0}, start: {1}",
span.start,
start
);

let skipped_contents = &contents[start..span.start];
let printable = String::from_utf8_lossy(skipped_contents).to_string();
trace!("contents: {:?}", printable);

if skipped_contents.contains(&b'#') {
trace!("This have a comment. Writing.");
out.extend(trim_ascii_whitespace(skipped_contents));
Expand All @@ -66,13 +54,12 @@ pub fn format_inner(contents: &[u8], _config: &Config) -> Vec<u8> {
}
}

// get the span contents and format it
let mut c_bites = working_set.get_span_contents(span);
let content = String::from_utf8_lossy(c_bites).to_string();
trace!("shape is {shape}");
trace!("shape contents: {:?}", &content);

match shape {
// if its one of these types, just do nothing. Write it away.
FlatShape::String | FlatShape::Int | FlatShape::Nothing => out.extend(c_bites),
FlatShape::List | FlatShape::Record => {
c_bites = trim_ascii_whitespace(c_bites);
Expand All @@ -81,50 +68,31 @@ pub fn format_inner(contents: &[u8], _config: &Config) -> Vec<u8> {
out.extend(c_bites);
}
FlatShape::Pipe => {
// here you don't have to strip the whitespace.
// The pipe is just a pipe `|`.
//
// return the pipe AND a space after that
out.extend("| ".to_string().bytes());
}
FlatShape::External => {
// External are some key commands
//
// List of what I've found: seq, each, str,
out.extend(c_bites);
// It doen't have a space after it. You have to add it here.
out.extend([b' '].iter());
out.extend(b"| ");
}
FlatShape::ExternalArg => {
// This shape is the argument of an External command (see previous case).
//
// As a result, ExternalArg may be an entire expression.
// like: "{ |row|\r\n let row_data = (seq ... r\n}"
FlatShape::External | FlatShape::ExternalArg => {
out.extend(c_bites);
// It doen't have a space after it. You have to add it here.
out.extend([b' '].iter());
out.extend(b" ");
}
FlatShape::Garbage => {
// Garbage is not garbage at all
//
// IDK what is it. I groups a bunch of commands like let my_var = 3
out.extend(c_bites);
out = insert_newline(out);
}

_ => out.extend(c_bites),
}

// check if span skipped some bytes between the final spann and the end of file
if is_last_span(span, &flat) && span.end < end_of_file {
trace!(
"The last span doesn't end the file! span: {0}, end: {1}",
span.end,
end_of_file
);

let remaining_contents = &contents[span.end..end_of_file];
let printable = String::from_utf8_lossy(remaining_contents).to_string();
trace!("contents: {:?}", printable);

if remaining_contents.contains(&b'#') {
trace!("This have a comment. Writing.");
out.push(b'\n');
Expand All @@ -134,36 +102,30 @@ pub fn format_inner(contents: &[u8], _config: &Config) -> Vec<u8> {
}
}

// cleanup
start = span.end + 1;
}
// just before writing, check if a new line is needed.
out = add_newline_at_end_of_file(out);

// timer = timer.done_formatting()
out
}

/// A wrapper to insert a new line
///
/// It is used frequently in `nufmt`, so
/// we have a wrapper to improve readability of the code.
/// insert a newline at the end of a buffer
fn insert_newline(mut bytes: Vec<u8>) -> Vec<u8> {
#[cfg(windows)]
bytes.extend(b"\r");
bytes.extend(b"\n");
bytes
}

/// Checks if it missing a new line. If true, adds it.
/// make sure there is a newline at the end of a buffer
fn add_newline_at_end_of_file(out: Vec<u8>) -> Vec<u8> {
match out.last() {
Some(&b'\n') => out,
_ => insert_newline(out),
}
}

/// Given a slice of bytes, strip all spaces, new lines and tabs found within
/// strip all spaces, new lines and tabs found a sequence of bytes
///
/// Because you don't know how the incoming code is formatted,
/// the best way to format is to strip all the whitespace
Expand All @@ -175,7 +137,7 @@ pub fn trim_ascii_whitespace(x: &[u8]) -> &[u8] {
&x[from..=to]
}

/// Returns true if the Block has at least 1 Pipeline
/// return true if the Nushell block has at least 1 pipeline
///
/// This function exists because sometimes is passed to `nufmt` an empty String,
/// or a nu code which the parser can't identify something runnable
Expand All @@ -188,7 +150,7 @@ fn block_has_pipelines(block: &Block) -> bool {
!block.pipelines.is_empty()
}

/// Returns true if the `Span` is the last Span in the slice of `flat`
/// return true if the given span is the last one
fn is_last_span(span: Span, flat: &[(Span, FlatShape)]) -> bool {
span == flat.last().unwrap().0
}
8 changes: 2 additions & 6 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,17 @@ use std::path::PathBuf;
pub mod config;
pub mod formatting;

/// Reads a file and format it. Then writes the file inplace.
/// format a Nushell file inplace
pub fn format_single_file(file: &PathBuf, config: &Config) {
// read the contents of the file
let contents = std::fs::read(file)
.unwrap_or_else(|_| panic!("something went wrong reading the file {}", file.display()));

// obtain the formatted file
let formatted_bytes = format_inner(&contents, config);

// compare the contents
if formatted_bytes == contents {
debug!("File is formatted correctly.");
}

// write down the file to path
let mut writer = File::create(file).unwrap();
let file_bites = formatted_bytes.as_slice();
writer
Expand All @@ -34,7 +30,7 @@ pub fn format_single_file(file: &PathBuf, config: &Config) {
trace!("written");
}

/// Take a `String` and format it. Then returns a new `String`
/// format a string of Nushell code
pub fn format_string(input_string: &String, config: &Config) -> String {
let contents = input_string.as_bytes();
let formatted_bytes = format_inner(contents, config);
Expand Down
21 changes: 6 additions & 15 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,36 +6,28 @@ use nu_formatter::config::Config;
use std::io::Write;
use std::path::PathBuf;

/// wrapper to the successful exit code
enum ExitCode {
Success,
Failure,
}

/// Main CLI struct.
///
/// The derive Clippy API starts from defining the CLI struct
/// the CLI signature of the `nufmt` executable.
#[derive(Parser)]
#[command(author, version, about)]
struct Cli {
/// The list of files passed in the cmdline
/// It is required and it cannot be used with `--stdin`
#[arg(
required_unless_present("stdin"),
help = "The file or files you want to format in nu"
help = "one of more Nushell files you want to format"
)]
files: Vec<PathBuf>,
/// The string you pass in stdin. You can pass only one string.
#[arg(
short,
long,
conflicts_with = "files",
help = "Format the code passed in stdin as a string."
help = "a string of Nushell directly given to the formatter"
)]
stdin: Option<String>,
/// The optional config file you can pass in the cmdline
/// You can only pass a file config, not a flag config
#[arg(short, long, help = "The configuration file")]
#[arg(short, long, help = "the configuration file")]
config: Option<PathBuf>,
}

Expand Down Expand Up @@ -74,7 +66,6 @@ fn main() {
_ => format_files(cli.files, &cli_config),
};

// Make sure standard output is flushed before we exit.
std::io::stdout().flush().unwrap();

exit_with_code(exit_code);
Expand All @@ -88,7 +79,7 @@ fn format_string(string: Option<String>, options: &Config) -> ExitCode {
ExitCode::Success
}

/// Sends the files to format in lib.rs
/// format a list of files, possibly one, and modify them inplace
fn format_files(files: Vec<PathBuf>, options: &Config) -> ExitCode {
for file in &files {
if !file.exists() {
Expand All @@ -101,7 +92,7 @@ fn format_files(files: Vec<PathBuf>, options: &Config) -> ExitCode {
);
return ExitCode::Failure;
}
// send the file to lib.rs

info!("formatting file: {:?}", file);
nu_formatter::format_single_file(file, options);
}
Expand Down