Skip to content

Commit

Permalink
added in basic csv functionality in prep for v0.1.4
Browse files Browse the repository at this point in the history
  • Loading branch information
CHRISCARLON committed Sep 19, 2024
1 parent c95de32 commit b2bdbbb
Show file tree
Hide file tree
Showing 6 changed files with 134 additions and 12 deletions.
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ colored = "2.1.0"
serde = "1.0.207"
serde_json = "1.0.124"
indicatif = "0.17.8"
bytes = "1.7.2"
csv = "1.3.0"

[lib]
name = "nebby"
Expand Down
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# Nebby 🤥

Nebby is a command-line tool for quickly reviewing basic information about remote Excel (xlsx) files and making simple API GET requests.
Nebby is a command-line tool for all your nosey parkers out there.

Quickly review basic information about a range of different file formats.

> [!NOTE]
> Nebby is currently under active development. Features and commands may change as the project evolves.
Expand Down Expand Up @@ -33,6 +35,7 @@ nebb quick-view <URL>

### `basic-idx`
Display basic information about an Excel file with a specified header index.
This is useful if the header is not on the first row.
```
nebb basic-idx <URL> [--header-index <INDEX>]
```
Expand All @@ -51,8 +54,7 @@ nebb basic-json <URL>
## Roadmap

- [ ] Add support for additional file formats
- [ ] Enhance JSON processing capabilities
- [ ] Implement DataFrame support
- ✅ Enhance JSON processing capabilities
- [ ] TBC

## Author
Expand Down
82 changes: 82 additions & 0 deletions src/csv/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
use bytes::Bytes;
use comfy_table::presets::UTF8_FULL;
use comfy_table::{Cell, Table};
use csv::ReaderBuilder;
use reqwest::blocking::get;
use std::error::Error;
use std::io::Cursor;

// Fetch the remote CSV as bytes
pub fn fetch_remote_csv(url: &str) -> Result<Bytes, Box<dyn Error>> {
let response = get(url)?;
let content = response.bytes()?;
Ok(content)
}

// Infer the type of a CSV value based on a sample value
fn infer_type(value: &str) -> &str {
if value.parse::<i64>().is_ok() {
"Integer"
} else if value.parse::<f64>().is_ok() {
"Float"
} else if value.parse::<bool>().is_ok() {
"Boolean"
} else {
"String"
}
}

// Read CSV headers and infer data types, using ComfyTable to display results
pub fn process_basic_csv(csv_content: Bytes) -> Result<(), Box<dyn Error>> {
// Create a cursor to read the bytes (since csv::Reader expects something that implements Read)
let mut rdr = ReaderBuilder::new().from_reader(Cursor::new(csv_content));

// Read the headers
let headers = rdr.headers()?.clone();

// Get the first row to infer data types
let mut records = rdr.records();
if let Some(result) = records.next() {
let first_row = result?;

// Create a comfy table for headers and their corresponding types
let mut type_table = Table::new();
type_table.load_preset(UTF8_FULL);
type_table.set_header(vec!["Header", "Data Type"]);

for (header, value) in headers.iter().zip(first_row.iter()) {
let data_type = infer_type(value);
type_table.add_row(vec![Cell::new(header), Cell::new(data_type)]);
}

// Print the headers and data type table
println!("{}", type_table);

// Create another table for the first 10 rows of data
let mut data_table = Table::new();
data_table.load_preset(UTF8_FULL);

// Set headers for the data table
data_table.set_header(headers.iter().map(|h| Cell::new(h)));

// Add the first row
data_table.add_row(first_row.iter().map(|value| Cell::new(value)));

// Add up to 9 more rows (total 10)
for (i, record) in records.enumerate() {
if i >= 9 {
break; // Stop after 10 rows (including the first row)
}
let row = record?;
data_table.add_row(row.iter().map(|value| Cell::new(value)));
}

// Print the data table
println!("\nFirst 10 rows of the CSV:");
println!("{}", data_table);
} else {
println!("No data rows available to infer types.");
}

Ok(())
}
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub mod api;
pub mod bytes;
pub mod csv;
pub mod excel;
pub mod utils;
32 changes: 23 additions & 9 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
mod api;
mod bytes;
mod csv;
mod excel;
mod utils;
use api::analyze_json_nesting;
use bytes::{get_file_type_string, view_bytes};
use clap::{Parser, Subcommand};
use csv::{fetch_remote_csv, process_basic_csv};
use excel::{
analyze_excel_formatting, display_remote_basic_info,
display_remote_basic_info_specify_header_idx, excel_quick_view, fetch_remote_file,
};
use utils::create_progress_bar;

#[derive(Parser, Debug)]
#[command(author = "Christopher Carlon", version = "0.1.3", about = "Nebby - quickly review basic information about remote xlsx files and API GET requests", long_about = None)]
#[command(author = "Christopher Carlon", version = "0.1.3", about = "Nebby! Quickly review basic information about a range of different file formats", long_about = None)]
struct Cli {
#[command(subcommand)]
command: Commands,
Expand All @@ -21,22 +23,22 @@ struct Cli {
#[derive(Subcommand, Debug)]
enum Commands {
/// Display basic information about an Excel file
Basic {
BasicXl {
/// URL of the Excel file
url: String,
},
/// Check formatting of an Excel file
Format {
FormatXl {
/// URL of the Excel file
url: String,
},
/// Quick view of an Excel file
QuickView {
QuickViewXl {
/// URL of the Excel file
url: String,
},
/// Experimental basic information feature with specified header index
BasicIdx {
BasicIdxXl {
/// URL of the Excel file
url: String,
/// Index of the header row (0-based)
Expand All @@ -53,17 +55,20 @@ enum Commands {
/// Url of the file
url: String,
},
/// Basic CSV feature
BasicCsv { url: String },
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
let cli = Cli::parse();
match &cli.command {
Commands::Basic { url } => process_excel(url, "basic info"),
Commands::Format { url } => process_excel(url, "formatting"),
Commands::QuickView { url } => process_excel(url, "quick view"),
Commands::BasicIdx { url, header_index } => process_excel_with_header(url, *header_index),
Commands::BasicXl { url } => process_excel(url, "basic info"),
Commands::FormatXl { url } => process_excel(url, "formatting"),
Commands::QuickViewXl { url } => process_excel(url, "quick view"),
Commands::BasicIdxXl { url, header_index } => process_excel_with_header(url, *header_index),
Commands::BasicJson { url } => process_json(url),
Commands::Nibble { url } => process_view_bytes(url),
Commands::BasicCsv { url } => process_csv(url),
}
}

Expand Down Expand Up @@ -122,6 +127,15 @@ fn process_view_bytes(url: &str) -> Result<(), Box<dyn std::error::Error>> {
Ok(())
}

fn process_csv(url: &str) -> Result<(), Box<dyn std::error::Error>> {
validate_url(url)?;
let pb = create_progress_bar("Processing CSV...");
let bytes = fetch_remote_csv(url)?;
let result = process_basic_csv(bytes);
pb.finish_with_message("CSV Processed");
result
}

fn validate_url(url: &str) -> Result<(), Box<dyn std::error::Error>> {
if url.is_empty() {
return Err("Error: URL cannot be empty".into());
Expand Down
21 changes: 21 additions & 0 deletions tests/csv_tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
use nebby::csv::{fetch_remote_csv, process_basic_csv}; // Assuming `nebby::csv` is your module path

#[test]
fn test_print_csv_headers() {
let url = "https://data.london.gov.uk/download/mpsantisocialbehaviour/5ee9e479-f719-4788-a233-ec26a295805f/MPS_Antisocial_Behaviour.csv"; // A valid CSV URL

// Fetch the remote CSV content
let bytes = fetch_remote_csv(url);

// Ensure the CSV fetch succeeded
assert!(bytes.is_ok(), "Failed to fetch CSV");

// Unwrap the result to get the bytes
let bytes = bytes.unwrap();

// Attempt to print the headers from the CSV
let result = process_basic_csv(bytes);

// Check that reading and printing headers was successful
assert!(result.is_ok(), "Failed to read and print CSV headers");
}

0 comments on commit b2bdbbb

Please sign in to comment.