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

Generate from file content #3

Open
wants to merge 5 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
2 changes: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
* text=auto
LICENSE text eol=lf
187 changes: 147 additions & 40 deletions src/release_entry.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
use std::fs;
use std::fs::{File};
use std::io::{BufReader, Read};
use std::iter::*;
use std::error::{Error};
use std::path::Path;

use hex::*;
use regex::Regex;
use semver::Version;
use std::iter::*;
use std::error::{Error};
use sha2::Sha256;
use sha2::Digest;
use url::{Url};
use url::percent_encoding::{percent_decode};
use url::percent_encoding::{percent_decode, utf8_percent_encode, DEFAULT_ENCODE_SET};

/* Example lines:

Expand All @@ -19,21 +26,103 @@ pub struct ReleaseEntry {
pub sha256: [u8; 32],
pub filename_or_url: String,
pub version: Version,
pub length: i64,
pub length: u64,
pub is_delta: bool,
pub percentage: i32,
}

impl Default for ReleaseEntry {
fn default() -> ReleaseEntry {
ReleaseEntry {
filename_or_url: "Foobdar".to_owned(),
version: Version::parse("1.0.0").unwrap(),
is_delta: true,
length: 42,
static HEADER: &'static str = "# SHA256 of the file Name Version Size [delta/full] release%";

//
// Create new release entries
impl ReleaseEntry {
pub fn from_file(path: &str) -> Result<ReleaseEntry, Box<Error>> {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be fn from_file<P: AsRef<std::path::Path>>(path: P). That way we can accept anything that can be referenced as a Path, which includes owned and borrowed strings, paths and OS strings.

For reference, that's what the sig for File::open looks like.

let mut sha256_sum = Sha256::new();
{
let file = try!(File::open(path));
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a ? operator that can be used instead of the try! macro.

So this line could be let file = File::open(path)?;

let mut content: [u8; 65536] = [0; 65536];
let mut buf = BufReader::new(file);

loop {
let count = try!(buf.read(&mut content));
if count == 0 { break; }

sha256_sum.input(&content[..count]);
}
}

let p = Path::new(path);
let stat = try!(fs::metadata(path));
let file = p.file_name().unwrap();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be able to use ? instead of unwrapping.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If P: AsRef<Path> then you could do p.as_ref().file_name()? and you could remove line 54.


let mut ret = ReleaseEntry {
sha256: [0; 32],
filename_or_url: file.to_str().unwrap().to_owned(),
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could use ? here instead of unwrap.

length: stat.len(),
version: Version::parse("0.0.0").unwrap(),
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could use ? here instead of unwrap.

is_delta: false,
percentage: 100,
};

let sha = sha256_sum.result();
for i in 0..32 {
ret.sha256[i] = sha[i];
}

return Ok(ret);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In Rust, the last expression statement in a block is the return. If that statement doesn't end with ; then its value is returned, otherwise () is.

So this could simply be Ok(ret).

If it was Ok(ret); then you'd get an error.

}
}

//
// Write release files
//

impl ReleaseEntry {
fn sha256_to_string(&self) -> String {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this could be self.sha256.into_iter().map(|x| format!("{:02x}", x)).collect().

Because collect is magic.

return self.sha256.into_iter().fold(
"".to_owned(),
|mut acc, x| {
let byte = format!("{:02x}", x);
acc.push_str(&byte);
return acc;
});
}

fn escape_filename(&self) -> String {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure how much you care about allocations here, but you might find the Cow type interesting. It would let you express a return type here that might be borrowed (in case SCHEME.is_match) or owned (in case it needs to be encoded).

It would basically look like:

fn escape_filename(&self) -> Cow<str> {
    if match {
        Cow::Borrowed(&self.filename_or_url)
    } else {
        Cow::Owned(utf8_percent_encode(&self.filename_or_url))
    }
}

if SCHEME.is_match(&self.filename_or_url) {
return self.filename_or_url.to_owned();
} else {
return utf8_percent_encode(&self.filename_or_url, DEFAULT_ENCODE_SET).to_string();
}
}

pub fn to_release_entry(&self) -> String {
if self.percentage == 100 {
return format!("{} {} {} {} {}",
self.sha256_to_string(),
self.escape_filename(),
self.version,
self.length,
if self.is_delta { "delta" } else { "full" });
} else {
return format!("{} {} {} {} {} {}%",
self.sha256_to_string(),
self.escape_filename(),
self.version,
self.length,
if self.is_delta { "delta" } else { "full" },
self.percentage);
}
}

pub fn to_releases_file(entries: &Vec<ReleaseEntry>) -> String {
let ret = entries.into_iter()
.fold("".to_owned(), |mut acc, x| {
acc.push_str(&format!("{}\n", x.to_release_entry()));
return acc;
});

return format!("{}\n{}", HEADER, ret.trim_right_matches("\n"));
}
}

Expand All @@ -45,6 +134,10 @@ lazy_static! {
static ref COMMENT: Regex = Regex::new(r"#.*$").unwrap();
}

//
// Parse release entries
//

impl ReleaseEntry {
fn parse_sha256(sha256: &str, to_fill: &mut ReleaseEntry) -> Result<bool, Box<Error>> {
let ret = try!(sha256.from_hex());
Expand Down Expand Up @@ -97,7 +190,7 @@ impl ReleaseEntry {
is_delta: try!(ReleaseEntry::parse_delta_full(delta_or_full)),
filename_or_url: try!(ReleaseEntry::parse_name(name)),
version: try!(Version::parse(version)),
length: try!(size.parse::<i64>()),
length: try!(size.parse::<u64>()),
percentage: 100,
};

Expand All @@ -111,7 +204,7 @@ impl ReleaseEntry {
is_delta: try!(ReleaseEntry::parse_delta_full(delta_or_full)),
filename_or_url: try!(ReleaseEntry::parse_name(name)).to_owned(),
version: try!(Version::parse(version)),
length: try!(size.parse::<i64>()),
length: try!(size.parse::<u64>()),
percentage: try!(ReleaseEntry::parse_percentage(percent))
};

Expand Down Expand Up @@ -149,21 +242,51 @@ impl ReleaseEntry {

#[cfg(test)]
mod tests {
use sha2::Sha256;
use sha2::Digest;
use std::env;
use std::path::Path;
use super::ReleaseEntry;

fn print_result(sum: &[u8], name: &str) {
for byte in sum {
print!("{:02x}", byte);
}
println!("\t{}", name);
const ENTRIES_EXAMPLE_1: &'static str = "# SHA256 of the file Name Version Size [delta/full] release%
e4548fba3f902e63e3fff36db7cbbd1837493e21c51f0751e51ee1483ddd0f35 myproject.7z 1.2.3 12345 full
a4548fba3f902e63e3fff36db7cbbd1837493e21c51f0751e51ee1483ddd0f35 myproject-delta.7z 1.2.3 555 delta
b4548fba3f902e63e3fff36db7cbbd1837493e21c51f0751e51ee1483ddd0f35 myproject-beta.7z 2.0.0-beta.1 34567 full 5%";

//
// Generate from file
//

#[test]
fn generate_from_license_file() {
let expected = "afb11426e09da40a1ae4f8fa17ddcc6b6a52d14df04c29bc5bcd06eb8730624d LICENSE 0.0.0 1057 full";
let dir = env::var("CARGO_MANIFEST_DIR").unwrap();
let input = Path::new(&dir).join("LICENSE");

let result = ReleaseEntry::from_file(input.to_str().unwrap())
.unwrap()
.to_release_entry();

assert_eq!(result, expected);
}

//
// Parse release entries
//

#[test]
fn roundtrip_parse_and_generate() {
let entry = "e4548fba3f902e63e3fff36db7cbbd1837493e21c51f0751e51ee1483ddd0f35 myproject.7z 1.2.3 12345 full";
let input = ReleaseEntry::parse(entry).unwrap();

let result = input.to_release_entry();
assert_eq!(entry, &result);
}

#[test]
fn create_a_release_entry() {
let f = ReleaseEntry::default();
assert_eq!(f.length, 42);
fn roundtrip_parse_and_generate_all() {
let entries = ReleaseEntry::parse_entries(ENTRIES_EXAMPLE_1).unwrap();
let result = ReleaseEntry::to_releases_file(&entries);

assert_eq!(ENTRIES_EXAMPLE_1, &result);
}

#[test]
Expand Down Expand Up @@ -236,23 +359,7 @@ mod tests {

#[test]
fn parse_all_entries() {
let input = "
# SHA256 of the file Name Version Size [delta/full] release%
e4548fba3f902e63e3fff36db7cbbd1837493e21c51f0751e51ee1483ddd0f35 myproject.7z 1.2.3 12345 full
a4548fba3f902e63e3fff36db7cbbd1837493e21c51f0751e51ee1483ddd0f35 myproject-delta.7z 1.2.3 555 delta
b4548fba3f902e63e3fff36db7cbbd1837493e21c51f0751e51ee1483ddd0f35 myproject-beta.7z 2.0.0-beta.1 34567 full 5%";

let result = ReleaseEntry::parse_entries(input).unwrap();
let result = ReleaseEntry::parse_entries(ENTRIES_EXAMPLE_1).unwrap();
assert_eq!(result.len(), 3);
}

#[test]
fn stringify_a_sha256() {
let mut sha = Sha256::default();
sha.input("This is a test".as_bytes());

let hash = sha.result();
print_result(&hash, "SHA256");
println!("Wat.");
}
}