aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIgor Pashev <pashev.igor@gmail.com>2019-10-05 21:54:27 +0200
committerIgor Pashev <pashev.igor@gmail.com>2019-10-05 22:02:27 +0200
commita65f7591894f67021df3490c07ab7e75627e476e (patch)
treea6137cfc9fa91c84c953e80e5a00a17b68a59640
downloadfrotate.rs-a65f7591894f67021df3490c07ab7e75627e476e.tar.gz
Initial version 0.1.00.1.0
-rw-r--r--CHANGELOG.md5
-rw-r--r--Cargo.toml10
-rw-r--r--LICENSE13
-rw-r--r--README.md89
-rw-r--r--src/lib.rs50
-rw-r--r--src/main.rs70
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"] }
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..456c488
--- /dev/null
+++ b/LICENSE
@@ -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);
+ }
+}
+