Skip to content

Commit

Permalink
Add SplitText action
Browse files Browse the repository at this point in the history
  • Loading branch information
tailhook committed Feb 12, 2018
1 parent af2e1d0 commit b15670c
Show file tree
Hide file tree
Showing 7 changed files with 256 additions and 64 deletions.
146 changes: 83 additions & 63 deletions Cargo.lock

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ serde = {version="1.0.0", features=["rc"]}
serde_derive = "1.0.0"
serde_json = "1.0.0"
serde_millis = "0.1.1"
serde_regex = "0.1.0"
crossbeam = "0.3.0"
trimmer = "0.3.6"
void = "1.0"
Expand All @@ -62,7 +63,7 @@ failure = "0.1.1"
http-file-headers = "0.1.6"
hex = "0.3.1"
deflate = {version="0.7.17", features=["gzip"]}
capturing-glob = "0.1.0"
capturing-glob = "0.1.1"
wasmi = "0.0.0"

[workspace]
Expand Down
9 changes: 9 additions & 0 deletions doc/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@ Verwalter Changes by Version
============================


.. _changelog-0.10.5:

Verwalter 0.10.5
----------------

* feature: new ``SplitText`` action, to deal with multiple generated
files easily


.. _changelog-0.10.4:

Verwalter 0.10.4
Expand Down
11 changes: 11 additions & 0 deletions src/render/apply/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,14 @@ pub mod cmd;
pub mod shell;
pub mod copy;
pub mod peek_log;
pub mod split_text;

const COMMANDS: &'static [&'static str] = &[
"RootCommand",
"Cmd",
"Sh",
"Copy",
"SplitText",
"PeekLog",
];

Expand All @@ -34,6 +36,7 @@ pub enum CommandName {
Cmd,
Sh,
Copy,
SplitText,
PeekLog,
}

Expand Down Expand Up @@ -80,6 +83,12 @@ quick_error!{
display("{}: {}", message, value)
description(message)
}
FormatError(message: String) {
display("{}", message)
}
Other(message: String) {
display("{}", message)
}
IoError(err: io::Error) {
from() cause(err)
display("io error: {}", err)
Expand Down Expand Up @@ -107,6 +116,7 @@ impl<'a> Visitor<'a> for NameVisitor {
"Cmd" => Cmd,
"Sh" => Sh,
"Copy" => Copy,
"SplitText" => SplitText,
"PeekLog" => PeekLog,
_ => return Err(E::custom("invalid command")),
};
Expand Down Expand Up @@ -138,6 +148,7 @@ impl<'a> Visitor<'a> for CommandVisitor {
Cmd => decode::<cmd::Cmd, _>(v),
Sh => decode::<shell::Sh, _>(v),
Copy => decode::<copy::Copy, _>(v),
SplitText => decode::<split_text::SplitText, _>(v),
PeekLog => decode::<peek_log::PeekLog, _>(v),
}
}
Expand Down
148 changes: 148 additions & 0 deletions src/render/apply/split_text.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
use std::collections::HashSet;
use std::io::{BufRead, BufReader, Write, BufWriter};
use std::fs::{self, File, remove_file};
use std::os::unix::fs::PermissionsExt;
use std::path::Path;

use capturing_glob::{self, glob_with, MatchOptions};
use regex::Regex;
use serde_regex;
use quire::validate as V;

use apply::{Task, Error, Action};
use apply::expand::Variables;

#[derive(Deserialize, Debug, Clone)]
pub struct SplitText {
#[serde(with="serde_regex")]
section: Regex,
#[serde(with="serde_regex")]
validate: Regex,
src: String,
dest: String,
mode: Option<u32>,
}

impl SplitText {
pub fn config() -> V::Structure<'static> {
V::Structure::new()
.member("section", V::Scalar::new())
.member("validate", V::Scalar::new())
.member("src", V::Scalar::new().default("{{ tmp_file }}"))
.member("dest", V::Scalar::new())
.member("mode", V::Numeric::new().optional())
}
}

fn open_file(dest: &str, name: &str) -> Result<File, Error> {
let fpath = capturing_glob::Pattern::new(dest)
.map_err(|_| Error::InvalidArgument(
"Split test destination is invalid pattern",
dest.to_string()))?
.substitute(&[name])
.map_err(|_| Error::InvalidArgument(
"Split test destination is invalid pattern",
dest.to_string()))?;
let fname = Path::new(&fpath).file_name()
.ok_or_else(|| Error::InvalidArgument(
"SplitText destination must be filename pattern not a directory",
dest.to_string()))?;
let tmpdest = Path::new(&fpath).with_file_name(
format!(".tmp.{}", fname.to_str().unwrap()));
return File::create(tmpdest).map_err(Error::IoError);
}

fn commit_file(dest: &str, name: &str, mode: Option<u32>) -> Result<(), Error>
{
let fpath = capturing_glob::Pattern::new(dest)
.map_err(|_| Error::InvalidArgument(
"Split test destination is invalid pattern",
dest.to_string()))?
.substitute(&[name])
.map_err(|_| Error::InvalidArgument(
"Split test destination is invalid pattern",
dest.to_string()))?;
let fname = Path::new(&fpath).file_name()
.ok_or_else(|| Error::InvalidArgument(
"SplitText destination must be filename pattern not a directory",
dest.to_string()))?;
let tmpdest = Path::new(&fpath).with_file_name(
format!(".tmp.{}", fname.to_str().unwrap()));
if let Some(mode) = mode {
fs::set_permissions(&tmpdest, fs::Permissions::from_mode(mode))
.map_err(|e| Error::IoError(e))?;
}
fs::rename(&tmpdest, &fpath)
.map_err(|e| Error::IoError(e))?;
Ok(())
}


impl Action for SplitText {
fn execute(&self, mut task: Task, variables: Variables)
-> Result<(), Error>
{
let src = variables.expand(&self.src);
let dest = variables.expand(&self.dest);
task.log(format_args!("SplitText {{ src: {:?}, dest: {:?} }}\n",
&self.src, &self.dest));

if !task.dry_run {
let src = BufReader::new(File::open(src).map_err(Error::IoError)?);
let mut visited = HashSet::new();
let mut file = None;
let mut name = None::<String>;
for (num, line) in src.lines().enumerate() {
let line = line.map_err(Error::IoError)?;
if file.is_none() {
if !line.trim().is_empty() {
return Err(Error::FormatError(format!(
"Error splitting file: \
Non-empty line {} before title", num)));
}
} else if let Some(capt) = self.section.captures(&line) {
let sect = capt.get(1).map(|x| x.as_str()).unwrap_or("");
if !self.validate.is_match(sect) {
return Err(Error::FormatError(format!(
"invalid section {:?} on line {}", capt, num)));
}

if let Some(cur_name) = name.take() {
commit_file(&dest, &cur_name, self.mode)?;
}
file = Some(BufWriter::new(open_file(&dest, sect)?));
name = Some(sect.to_string());
visited.insert(sect.to_string());
} else {
writeln!(&mut file.as_mut().unwrap(), "{}", line)
.map_err(|e| Error::IoError(e))?;
}
}
if let Some(cur_name) = name.take() {
commit_file(&dest, &cur_name, self.mode)?;
}
let items = glob_with(&dest, &MatchOptions {
case_sensitive: true,
require_literal_separator: true,
require_literal_leading_dot: true,
}).map_err(|_| Error::InvalidArgument(
"Split test destination is invalid pattern",
dest.to_string()))?;
for entry in items {
let entry = entry.map_err(|e| {
Error::Other(format!("{:?}: {}",
e.path().to_path_buf(), e.error()))
})?;
let name = entry.group(1)
.and_then(|x| x.to_str()).unwrap_or("");
if !visited.contains(name) {
remove_file(entry.path())?;
}
}
Ok(())
} else {
Ok(())
}
}

}
2 changes: 2 additions & 0 deletions src/render/main.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
extern crate argparse;
extern crate capturing_glob;
extern crate error_chain;
extern crate handlebars;
extern crate libc;
Expand All @@ -8,6 +9,7 @@ extern crate regex;
extern crate serde;
extern crate scan_dir;
extern crate serde_json;
extern crate serde_regex;
extern crate tempfile;
extern crate tera;
extern crate trimmer;
Expand Down
1 change: 1 addition & 0 deletions src/render/renderfile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ fn command_validator<'x>() -> V::Enum<'x> {
.option("Cmd", apply::cmd::Cmd::config())
.option("Sh", apply::shell::Sh::config())
.option("Copy", apply::copy::Copy::config())
.option("SplitText", apply::split_text::SplitText::config())
.option("PeekLog", apply::peek_log::PeekLog::config())
}

Expand Down

0 comments on commit b15670c

Please sign in to comment.