diff options
author | Igor Pashev <pashev.igor@gmail.com> | 2019-10-05 21:54:27 +0200 |
---|---|---|
committer | Igor Pashev <pashev.igor@gmail.com> | 2019-10-05 22:02:27 +0200 |
commit | a65f7591894f67021df3490c07ab7e75627e476e (patch) | |
tree | a6137cfc9fa91c84c953e80e5a00a17b68a59640 | |
download | frotate.rs-a65f7591894f67021df3490c07ab7e75627e476e.tar.gz |
Initial version 0.1.00.1.0
-rw-r--r-- | CHANGELOG.md | 5 | ||||
-rw-r--r-- | Cargo.toml | 10 | ||||
-rw-r--r-- | LICENSE | 13 | ||||
-rw-r--r-- | README.md | 89 | ||||
-rw-r--r-- | src/lib.rs | 50 | ||||
-rw-r--r-- | src/main.rs | 70 |
6 files changed, 237 insertions, 0 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..70da280 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,5 @@ +0.1.0 (2019-10-05) +================== + + * First version. + diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..6cf0f55 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,10 @@ +[package] +authors = ["Igor Pashev <pashev.igor@gmail.com>"] +license = "WTFPL" +name = "frotate" +version = "0.1.0" + +[dependencies] +chrono = { version = "0.4", features = ["serde"] } +docopt = "1" +serde = { version = "1", features = ["derive"] } @@ -0,0 +1,13 @@ + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + Version 2, December 2004 + + Copyright (C) 2004 Sam Hocevar <sam@hocevar.net> + + Everyone is permitted to copy and distribute verbatim or modified + copies of this license document, and changing it is allowed as long + as the name is changed. + + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. You just DO WHAT THE FUCK YOU WANT TO. diff --git a/README.md b/README.md new file mode 100644 index 0000000..6e81c99 --- /dev/null +++ b/README.md @@ -0,0 +1,89 @@ +About +===== + +*This is the [Rust](https://www.rust-lang.org/) version of +[frotate](https://github.com/ip1981/frotate) ;)* + + +`frotate` stands for "functional rotate", whatever. +This is an evolution of the [log2rotate's ideas](http://jekor.com/log2rotate). +See also [pylog2rotate](https://github.com/avian2/pylog2rotate). + +`frotate` is designed to rotate backups with any balance between retention +and space usage. Instead of rotating backups using some familiar method such +as daily, weekly, monthly, and yearly periods, it rotates backups using any +periods. Thus "functional". + +The idea is simple, the rotation schedule is determined by an integer function. +This function gives us a period (number) of days when we must encounter at +least one backup or whatever we are rotating. When we use an exponential +function, the scheme is similar to the radioactive decay law. When the +function is simply a constant 1, we don't rotate anything and retain all +the backups. If it is 2, we retain each second backup. With some trivial +function we can achieve a well-known dayly-weekly-monthly-yearly scheme. + +The `frotate` command line utility implements only exponential periods with +arbitrary base (ensure it is > 1, or have fun otherwise). + + +Usage +===== + +Note that when neither `--keep` nor `--delete` option is given, the utility +prints all intervals with all days _to standard error_ and exits with non-zero +code. In production you will need to specify `--keep` or `--delete` explicitly. + +``` +Usage: frotate [-k|-d] [-b <base>] <day>... + frotate --help + +Options: + -k --keep Print days to keep + -d --delete Print days to delete + -b --base <base> Base of the exponent [default: 1.1] + -h --help Show this help text + +``` + + +Example +======= + +Different modes with the same days: + +``` +$ frotate --base 2 2019-08-31 2019-08-30 2019-08-29 2019-08-28 2019-08-27 2019-08-26 2019-08-25 2019-08-24 +2019-08-31 +2019-08-29 2019-08-30 +2019-08-25 2019-08-26 2019-08-27 2019-08-28 +2019-08-24 + +$ frotate --keep --base 2 2019-08-31 2019-08-30 2019-08-29 2019-08-28 2019-08-27 2019-08-26 2019-08-25 2019-08-24 +2019-08-31 2019-08-29 2019-08-25 2019-08-24 + +$ frotate --delete --base 2 2019-08-31 2019-08-30 2019-08-29 2019-08-28 2019-08-27 2019-08-26 2019-08-25 2019-08-24 +2019-08-30 2019-08-26 2019-08-27 2019-08-28 +``` + +More or less realistic example when we keep some backups and get new ones, but not every day: + +``` +$ frotate --keep --base 2 2019-09-01 2019-08-31 2019-08-30 2019-08-28 2019-08-24 +2019-09-01 2019-08-30 2019-08-28 2019-08-24 + +$ frotate --keep --base 2 2019-09-05 2019-09-01 2019-08-30 2019-08-28 2019-08-24 +2019-09-05 2019-08-30 2019-08-24 + +$ frotate --keep --base 2 2019-09-06 2019-09-05 2019-08-30 2019-08-24 +2019-09-06 2019-09-05 2019-08-24 + +$ frotate --keep --base 2 2019-09-07 2019-09-06 2019-09-05 2019-08-24 +2019-09-07 2019-09-05 2019-08-24 + +$ frotate --keep --base 2 2019-09-08 2019-09-07 2019-09-06 2019-08-24 +2019-09-08 2019-09-06 2019-08-24 + +$ frotate --keep --base 2 2019-09-09 2019-09-08 2019-09-06 2019-08-24 +2019-09-09 2019-09-08 2019-09-06 2019-08-24 +``` + diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..1fd07b4 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,50 @@ +extern crate chrono; + +use std::collections::LinkedList; + +use chrono::NaiveDate; +use chrono::Duration; + +pub fn partition (f: &Fn(u32) -> i64, v: &Vec<i64>) -> LinkedList<LinkedList<i64>> { + let mut term: LinkedList<i64> = LinkedList::new(); + let mut res: LinkedList<LinkedList<i64>> = LinkedList::new(); + let mut n : u32 = 1; + let mut a : i64 = v[0]; + + for &i in v.iter() { + while i >= a + f(n) { + res.push_back(term); + term = LinkedList::new(); + a += f(n); + n += 1; + } + term.push_back(i); + } + res.push_back(term); + + return res; +} + +pub fn partition_days (f: &Fn(u32) -> i64, days: &Vec<NaiveDate>) -> LinkedList<LinkedList<NaiveDate>> { + let day1 = days[0]; + let part; + + { + let mut v: Vec<i64> = Vec::with_capacity(days.len()); + + for &d in days.iter() { + v.push((day1-d).num_days()); + } + v.sort_unstable(); + v.dedup(); + + part = partition(f, &v); + } + + let res = part.into_iter().map( + |l| l.into_iter().map( + |d| day1 - Duration::days(d)).rev().collect() + ).collect(); + + return res +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..ba7b145 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,70 @@ +extern crate chrono; +extern crate docopt; +extern crate serde; + +use std::process::exit; + +use chrono::NaiveDate; +use docopt::Docopt; +use serde::Deserialize; + +mod lib; +use lib::partition_days; + +const USAGE: &'static str = " +Usage: frotate [-k|-d] [-b <base>] <day>... + frotate --help + +Options: + -k --keep Print days to keep + -d --delete Print days to delete + -b --base <base> Base of the exponent [default: 1.1] + -h --help Show this help text +"; + +#[derive(Deserialize)] +struct Args { + flag_keep: bool, + flag_delete: bool, + flag_base: f32, + arg_day: Vec<NaiveDate>, +} + +fn exponent(b:f32, n:u32) -> i64 { + let i : i32 = n as i32 - 1; + return b.powi(i).ceil() as i64; +} + +fn main() { + let args: Args = Docopt::new(USAGE) + .and_then(|d| d.deserialize()) + .unwrap_or_else(|e| e.exit()); + + let parts = partition_days(&|n| exponent(args.flag_base, n), &args.arg_day); + + if args.flag_keep { + for days in parts.iter() { + match days.front() { + Some(d) => print!("{:?} ", d), + None => {} + } + } + println!(); + } else if args.flag_delete { + for days in parts.iter() { + for d in days.iter().skip(1) { + print!("{:?} ", d); + } + } + println!(); + } else { + for days in parts.iter() { + for d in days.iter() { + eprint!("{:?} ", d); + } + eprintln!(); + } + exit(1); + } +} + |