Skip to content

Commit

Permalink
Refactor, add cli feature.
Browse files Browse the repository at this point in the history
  • Loading branch information
n3vu0r committed Aug 12, 2020
1 parent f3a99f9 commit 231892b
Show file tree
Hide file tree
Showing 7 changed files with 245 additions and 197 deletions.
8 changes: 7 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,17 @@ matrix:
- rust: stable
- rust: stable
env: FEATURES="cli"
- rust: stable
env: FEATURES="bin"
- rust: beta
- rust: beta
env: FEATURES="cli"
- rust: beta
env: FEATURES="bin"
- rust: nightly
- rust: nightly
env: FEATURES="cli"
- rust: nightly
env: FEATURES="bin"
script:
- cargo test --verbose --no-default-features --features "$FEATURES"
- cargo test --verbose --no-default-features --features "$FEATURES"
17 changes: 9 additions & 8 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "mif"
version = "0.1.1"
version = "0.2.0"
authors = ["Rouven Spreckels <rs@qu1x.dev>"]
edition = "2018"
description = "Memory Initialization File"
Expand Down Expand Up @@ -34,18 +34,19 @@ path = "src/lib.rs"

[[bin]]
name = "mif"
path = "src/main.rs"
required-features = ["cli"]
path = "src/bin/mif.rs"
required-features = ["bin"]

[dependencies]
serde = { version = "1", features = ["derive"] }
indexmap = { version = "1", features = ["serde-1"] }
num-traits = "0.2"
byteorder = "1"
anyhow = "1"
clap = { version = "3.0.0-beta.1", optional = true }
indexmap = { version = "1", features = ["serde-1"], optional = true }
serde = { version = "1", features = ["derive"], optional = true }
toml = { version = "0.5", features = ["preserve_order"], optional = true }
clap = { version = "3.0.0-beta.1", optional = true }

[features]
default = ["cli"]
cli = ["clap", "toml"]
default = ["bin"]
cli = ["indexmap", "serde", "toml"]
bin = ["cli", "clap"]
16 changes: 13 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,29 @@ Memory Initialization File

MIF creation and serialization is implemented via the `Mif` structure.

Disable default features like `cli` to reduce dependencies:
Disable default features like `cli` and `bin` to reduce dependencies:

```toml
[dependencies]
mif = { version = "0.1", default-features = false }
mif = { version = "0.2", default-features = false }
```

Default features:

* `cli`: Provides command-line interface functionality of `mif` binary.

Requires: `indexmap`, `serde`, `toml`

* `bin`: Enables compilation of `mif` binary.

Requires: `cli`, `clap`

## Command-line Interface

Provides two subcommands, `dump` and `join`.

```text
mif 0.1.1
mif 0.2.0
Rouven Spreckels <rs@qu1x.dev>
Memory Initialization File
Expand Down
5 changes: 5 additions & 0 deletions RELEASES.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
# Version 0.2.0 (2020-08-12)

* Refactor, add `cli` feature.
* Fix MIF output directory.

# Version 0.1.1 (2020-08-11)

* Fix optional dependencies.
Expand Down
6 changes: 3 additions & 3 deletions src/main.rs → src/bin/mif.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
//! Memory Initialization File
//! `mif` binary.
#![forbid(unsafe_code)]
#![forbid(missing_docs)]

use std::{path::PathBuf, io::stdout};
use clap::{crate_version, crate_authors, Clap, AppSettings};
use anyhow::Result;
use mif::{First, open, dump, load, join};
use Mif::*;
use mif::{First, cli::{open, dump, load, join}};
use Mif::{Dump, Join};

/// Memory Initialization File.
#[derive(Clap)]
Expand Down
180 changes: 180 additions & 0 deletions src/cli.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
#![forbid(unsafe_code)]
#![forbid(missing_docs)]

use std::{
convert::TryInto,
path::{PathBuf, Path},
fs::{OpenOptions, metadata},
io::{Cursor, BufReader, Read, stdin, BufWriter, Write},
};
use serde::Deserialize;
use indexmap::IndexMap;
use anyhow::{Result, Context, ensure};
use Instr::{Skips, Joins};
use crate::{Mif, First, default_width};

/// Opens file or standard input `"-"` as buffered bytes reader of known count.
///
/// * For a file, the count is determined by `metadata()`.
/// * For standard input, the bytes are completely read in and counted.
pub fn open(input: &dyn AsRef<Path>) -> Result<(Box<dyn Read>, usize)> {
let input = input.as_ref();
Ok(if input == Path::new("-") {
let mut bytes = Vec::new();
stdin().read_to_end(&mut bytes).context("Cannot read standard input")?;
let count = bytes.len();
(Box::new(Cursor::new(bytes)), count)
} else {
let (bytes, count) = OpenOptions::new().read(true).open(&input)
.and_then(|bytes| metadata(&input)
.map(|stats| (BufReader::new(bytes), stats.len())))
.with_context(|| format!("Cannot open `{}`", input.display()))?;
(Box::new(bytes), count.try_into().context("Address space exhausted")?)
})
}

/// Dumps known count of bytes from reader as MIF to writer.
///
/// * `lines`: Writer, MIF is written to.
/// * `bytes`: Reader, bytes are read from.
/// * `count`: Count of bytes to read.
/// * `width`: Word width in bits from 8 to 128.
/// * `first`: LSB/MSB first (little/big-endian).
pub fn dump(
lines: &mut dyn Write,
bytes: &mut dyn Read,
count: usize,
width: usize,
first: First,
) -> Result<()> {
let mut mif = Mif::<u128>::new(width)?;
let align = mif.align();
let depth = count / align;
ensure!(depth * align == count, "No integral multiple of word width");
mif.read(bytes, depth, first).context("Cannot read input")
.and_then(|()| mif.write(lines, false).context("Cannot write MIF"))
}

/// Load TOML from file or standard input `"-"` as `Files`.
pub fn load(input: &dyn AsRef<Path>) -> Result<Files> {
let input = input.as_ref();
let mut file: Box<dyn Read> = if input == Path::new("-") {
Box::new(stdin())
} else {
Box::new(OpenOptions::new().read(true).open(&input).map(BufReader::new)
.with_context(|| format!("Cannot open `{}`", input.display()))?)
};
let mut string = String::new();
file.read_to_string(&mut string)
.with_context(|| format!("Cannot read `{}`", input.display()))
.and_then(|_count| toml::from_str::<Files>(&string)
.with_context(|| format!("Cannot load `{}`", input.display())))
}

/// Joins memory areas of binary `Files` as MIFs.
///
/// * `files`: Binary files split into memory areas, see `Files`.
/// * `areas`: Whether to comment memory areas, see `write()`.
/// * `paths`: Prefix paths for input binaries and output MIFs in given order.
pub fn join(
files: &Files,
paths: (&dyn AsRef<Path>, &dyn AsRef<Path>),
areas: bool,
) -> Result<()> {
let mut mifs = IndexMap::new();
for (bin_path, areas) in files {
let mut abs_path = paths.0.as_ref().to_path_buf();
abs_path.push(&bin_path);
let mut bin_file = OpenOptions::new()
.read(true).open(&abs_path).map(BufReader::new)
.with_context(|| format!("Cannot open `{}`", abs_path.display()))?;
for &Area { first, width, depth, ref instr } in areas {
let mut mif_area = Mif::new(width)?;
mif_area.read(&mut bin_file, depth, first)?;
match instr {
Skips(skips) => if !skips.is_empty() {
ensure!(mif_area.words().iter()
.all(|&(word, _bulk)| skips.iter()
.any(|skip| skip.as_word() == word)),
"Invalid word to skip in `{}`", bin_path.display());
},
Joins(joins) => for mif_path in joins {
if !mifs.contains_key(mif_path) {
let mut abs_path = paths.1.as_ref().to_path_buf();
abs_path.push(mif_path);
let mif_file = OpenOptions::new()
.write(true).create(true).truncate(true)
.open(&abs_path).map(BufWriter::new)
.with_context(|| format!("Cannot open `{}`",
abs_path.display()))?;
let mif = (mif_file, Mif::new(width)?);
assert!(mifs.insert(mif_path.clone(), mif).is_none());
}
let (_mif_file, mif_data) = &mut mifs[mif_path];
ensure!(mif_data.width() == width,
"Different width to join `{}`", mif_path.display());
mif_data.area(bin_path.clone());
mif_data.join(&mif_area)?;
},
}
}
let mut bin_data = Vec::new();
bin_file.read_to_end(&mut bin_data)?;
ensure!(bin_data.is_empty(),
"{} B left over in `{}`", bin_data.len(), bin_path.display());
}
for (mif_path, (mut mif_file, mif_data)) in mifs {
mif_data.write(&mut mif_file, areas)
.with_context(|| format!("Cannot write `{}`", mif_path.display()))?;
}
Ok(())
}

/// Binary files split into memory areas.
pub type Files = IndexMap<PathBuf, Vec<Area>>;

/// Memory area.
#[derive(Debug, Eq, PartialEq, Clone, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct Area {
/// LSB/MSB first (little/big-endian).
#[serde(default)]
pub first: First,
/// Word width in bits from 8 to 128.
#[serde(default = "default_width")]
pub width: usize,
/// Depth in words.
pub depth: usize,
/// Whether to skip or join this memory area.
#[serde(flatten)]
pub instr: Instr,
}

/// Whether to skip or join a memory area.
#[derive(Debug, Eq, PartialEq, Clone, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum Instr {
/// Skips memory area and ensures it contains given words only.
Skips(Vec<Word>),
/// Joins memory area to given MIFs.
Joins(Vec<PathBuf>),
}

/// TOML `u128` workaround.
#[derive(Debug, Eq, PartialEq, Copy, Clone, Deserialize)]
#[serde(untagged)]
pub enum Word {
/// One `u64` TOML integer as `u128`.
One(u64),
/// Two `u64` TOML integers `[msb, lsb]` as `u128`.
Two([u64; 2])
}

impl Word {
fn as_word(&self) -> u128 {
match *self {
Word::One(one) => one as u128,
Word::Two(two) => (two[0] as u128) << 64 | two[1] as u128,
}
}
}
Loading

0 comments on commit 231892b

Please sign in to comment.