Skip to content

Commit 6187b03

Browse files
author
Stefan Fleck
committed
fixes system-time dependent unit tests that broke due to year change
1 parent 8c598f5 commit 6187b03

27 files changed

Lines changed: 3233 additions & 58 deletions

DESCRIPTION

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
Type: Package
22
Package: rotor
33
Title: Log Rotation and Conditional Backups
4-
Version: 0.2.3
4+
Version: 0.2.4
55
Authors@R:
66
person(given = "Stefan",
77
family = "Fleck",
@@ -29,4 +29,4 @@ Suggests:
2929
Encoding: UTF-8
3030
LazyData: true
3131
Roxygen: list(markdown = TRUE)
32-
RoxygenNote: 6.1.1
32+
RoxygenNote: 7.0.2.9000

NEWS.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
# rotor 0.2.4
2+
3+
* Fixes unit tests sensitive to year change.
4+
5+
16
# rotor 0.2.3
27

38
* Changed default behaviour of `rotate_date()`, `rotate_time()`, etc...: If

R/utils-sfmisc.R

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# sfmisc utils 0.0.1.9028
1+
# sfmisc utils 0.0.1.9031
22

33

44

@@ -19,7 +19,7 @@
1919
#' @return a `character` scalar
2020
#' @noRd
2121
#'
22-
#' @example
22+
#' @examples
2323
#' ptrunc(month.abb)
2424
#' ptrunc(month.abb, month.name)
2525
#'
@@ -536,6 +536,12 @@ is_distinct_from <- function(x, y){
536536

537537

538538

539+
is_windows_path <- function(x){
540+
nchar(x) >= 2 & grepl("^[A-Za-z].*", x) & substr(x, 2, 2) == ":"
541+
}
542+
543+
544+
539545
# equalish ----------------------------------------------------------------
540546

541547
#' Check for equality within a tolerance level
@@ -764,4 +770,35 @@ preview_object <- function(
764770

765771

766772

773+
#' Clean up paths to make them comparable, inspired by fs::path_tidy
774+
#'
775+
#' @param x `character` vector
776+
#'
777+
#' @return a `character` vector
778+
#' @noRd
779+
path_tidy <- function(x){
780+
x <- gsub("\\\\", "/", x)
781+
x <- gsub("(?!^)/+", "/", x, perl = TRUE)
782+
783+
sel <- x != "/"
784+
x[sel] <- gsub("/$", "", x[sel])
785+
786+
sel <- is_windows_path(x)
787+
788+
if (any(sel)){
789+
clean_win <- function(.x){
790+
substr(.x, 1, 1) <- toupper(substr(.x, 1 ,1))
791+
.sel <- nchar(.x) == 2
792+
.x[.sel] <- paste0(.x[.sel], "/")
793+
.x
794+
}
795+
796+
x[sel] <- clean_win(x[sel])
797+
}
798+
799+
x
800+
}
801+
802+
803+
767804
# nocov end

README.md

Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
2+
<!-- README.md is generated from README.Rmd. Please edit that file -->
3+
4+
# rotor
5+
6+
<!-- badges: start -->
7+
8+
[![Travis build
9+
status](https://travis-ci.org/s-fleck/rotor.svg?branch=master)](https://travis-ci.org/s-fleck/rotor)
10+
[![Lifecycle:
11+
maturing](https://img.shields.io/badge/lifecycle-maturing-blue.svg)](https://www.tidyverse.org/lifecycle/#maturing)
12+
[![Codecov test
13+
coverage](https://codecov.io/gh/s-fleck/rotor/branch/master/graph/badge.svg)](https://codecov.io/gh/s-fleck/rotor?branch=master)
14+
[![CRAN
15+
status](https://www.r-pkg.org/badges/version/rotor)](https://cran.r-project.org/package=rotor)
16+
<!-- badges: end -->
17+
18+
**rotor** provides a cross platform R reimagination of
19+
[logrotate](https://linux.die.net/man/8/logrotate). It is a companion
20+
package to the logging package [lgr](https://github.com/s-fleck/lgr). In
21+
contrast to logrotate, rotor relies solely on information encoded in a
22+
suffixes of file names for conditionally creating backups (i.e. a
23+
timestamp or index). It therefore also works with backups created by
24+
other tools, as long as the filename has a format that rotor can
25+
understand.
26+
27+
`rotate()`, `rotate_date()`, and `rotate_time()` move a file and insert
28+
a suffix (either an integer or a timestamp) into the filename. In
29+
addition, they create an empty file in place of the original one. This
30+
is useful for log rotation. `backup()`, `backup_date()` and
31+
`backup_time()` do the same but keep the original file.
32+
33+
rotor also includes utility functions for finding and examining the
34+
backups of a file: `list_backups()`, `backup_info()`, `n_backups`,
35+
`newest_backup()`, `oldest_backup()`. See the [function
36+
reference](https://s-fleck.github.io/rotor/reference/index.html) for
37+
details.
38+
39+
## Installation
40+
41+
You can install the released version of rotor from
42+
[CRAN](https://CRAN.R-project.org) with:
43+
44+
``` r
45+
install.packages("rotor")
46+
```
47+
48+
And the development version from [GitHub](https://github.com/) with:
49+
50+
``` r
51+
# install.packages("remotes")
52+
remotes::install_github("s-fleck/rotor")
53+
```
54+
55+
## Example
56+
57+
First we create a temporary directory for the files created by the code
58+
examples
59+
60+
``` r
61+
library(rotor)
62+
63+
# create a directory
64+
td <- file.path(tempdir(), "rotor")
65+
dir.create(td, recursive = TRUE)
66+
67+
# create an example logfile
68+
tf <- file.path(td, "mylogfile.log")
69+
writeLines("An important message", tf)
70+
```
71+
72+
### Indexed backups
73+
74+
`backup()` makes a copy of a file and inserts an index between the
75+
filename and the file extension. The file with the index `1` is always
76+
the most recently made backup.
77+
78+
``` r
79+
backup(tf)
80+
81+
# backup and rotate also support compression
82+
backup(tf, compression = TRUE)
83+
84+
# display backups of a file
85+
list_backups(tf)
86+
#> [1] "/tmp/RtmpTB9vY3/rotor/mylogfile.1.log.zip"
87+
#> [2] "/tmp/RtmpTB9vY3/rotor/mylogfile.2.log"
88+
```
89+
90+
`rotate()` also backs up a file, but replaces the original file with an
91+
empty one.
92+
93+
``` r
94+
rotate(tf)
95+
list_backups(tf)
96+
#> [1] "/tmp/RtmpTB9vY3/rotor/mylogfile.1.log"
97+
#> [2] "/tmp/RtmpTB9vY3/rotor/mylogfile.2.log.zip"
98+
#> [3] "/tmp/RtmpTB9vY3/rotor/mylogfile.3.log"
99+
100+
# the original file is now empty
101+
readLines(tf)
102+
#> character(0)
103+
104+
# its content was moved to the first backup
105+
readLines(list_backups(tf)[[1]])
106+
#> [1] "An important message"
107+
108+
# we can now safely write to the original file
109+
writeLines("another important message", tf)
110+
```
111+
112+
The `max_backups` parameter limits the maximum number of backups rotor
113+
will keep of a file. Notice how the zipped backup we created above moves
114+
to index 4 as we create two new backups.
115+
116+
``` r
117+
backup(tf, max_backups = 4)
118+
backup(tf, max_backups = 4)
119+
120+
list_backups(tf)
121+
#> [1] "/tmp/RtmpTB9vY3/rotor/mylogfile.1.log"
122+
#> [2] "/tmp/RtmpTB9vY3/rotor/mylogfile.2.log"
123+
#> [3] "/tmp/RtmpTB9vY3/rotor/mylogfile.3.log"
124+
#> [4] "/tmp/RtmpTB9vY3/rotor/mylogfile.4.log.zip"
125+
```
126+
127+
We can also use `prune_backups()` to delete old backups. Other than
128+
ensuring that no new backups is created, it works identically to using
129+
`backup()` with the `max_backups` parameter. By setting it to `0`, we
130+
delete all backups.
131+
132+
``` r
133+
prune_backups(tf, max_backups = 0)
134+
```
135+
136+
## Timestamped backups
137+
138+
**rotor** can also create timestamped backups. `backup_date()` creates
139+
uses a Date (`yyyy-mm-dd`) timestamp, `backup_time()` uses a full
140+
datetime-stamp by default (`yyyy-mm-dd--hh-mm-ss`). The format of the
141+
timestamp can be modified with a subset of the formatting tokens
142+
understood by `strftime()` (within certain restrictions). Backups
143+
created with both functions are compatible with each other (but not with
144+
those created with
145+
`backup_index()`).
146+
147+
``` r
148+
# be default backup_date() only makes a backup if the last backups is younger
149+
# than 1 day, so we set `age` to -1 for this example
150+
backup_date(tf, age = -1)
151+
backup_date(tf, format = "%Y-%m", age = -1)
152+
backup_time(tf)
153+
backup_time(tf, format = "%Y-%m-%d_%H-%M-%S") # Python logging
154+
backup_time(tf, format = "%Y%m%dT%H%M%S") # ISO 8601 compatible
155+
156+
backup_info(tf)
157+
#> path name
158+
#> 1 /tmp/RtmpTB9vY3/rotor/mylogfile.2020-01-02_10-05-52.log mylogfile
159+
#> 2 /tmp/RtmpTB9vY3/rotor/mylogfile.2020-01-02--10-05-52.log mylogfile
160+
#> 5 /tmp/RtmpTB9vY3/rotor/mylogfile.20200102T100552.log mylogfile
161+
#> 3 /tmp/RtmpTB9vY3/rotor/mylogfile.2020-01-02.log mylogfile
162+
#> 4 /tmp/RtmpTB9vY3/rotor/mylogfile.2020-01.log mylogfile
163+
#> sfx ext size isdir mode mtime
164+
#> 1 2020-01-02_10-05-52 log 26 FALSE 664 2020-01-02 10:05:52
165+
#> 2 2020-01-02--10-05-52 log 26 FALSE 664 2020-01-02 10:05:52
166+
#> 5 20200102T100552 log 26 FALSE 664 2020-01-02 10:05:52
167+
#> 3 2020-01-02 log 26 FALSE 664 2020-01-02 10:05:52
168+
#> 4 2020-01 log 26 FALSE 664 2020-01-02 10:05:52
169+
#> ctime atime uid gid uname grname
170+
#> 1 2020-01-02 10:05:52 2020-01-02 10:05:52 1032 1032 fleck fleck
171+
#> 2 2020-01-02 10:05:52 2020-01-02 10:05:52 1032 1032 fleck fleck
172+
#> 5 2020-01-02 10:05:52 2020-01-02 10:05:52 1032 1032 fleck fleck
173+
#> 3 2020-01-02 10:05:52 2020-01-02 10:05:52 1032 1032 fleck fleck
174+
#> 4 2020-01-02 10:05:52 2020-01-02 10:05:52 1032 1032 fleck fleck
175+
#> timestamp
176+
#> 1 2020-01-02 10:05:52
177+
#> 2 2020-01-02 10:05:52
178+
#> 5 2020-01-02 10:05:52
179+
#> 3 2020-01-02 00:00:00
180+
#> 4 2020-01-01 00:00:00
181+
```
182+
183+
If we examine the “timestamp” column in the example above, we see that
184+
missing date information is always interpreted as the start of the
185+
period; i.e. so `"2019-01"` is equivalent to `"2019-01-01--00--00--00"`
186+
for all intents and purposes.
187+
188+
``` r
189+
prune_backups(tf, max_backups = 0) # cleanup
190+
list_backups(tf)
191+
#> character(0)
192+
```
193+
194+
Besides passing a total number of backups to keep, `max_backups` can
195+
also be a period or a date / datetime for timestamped backups.
196+
197+
``` r
198+
# keep all backups younger than one year
199+
prune_backups(tf, "1 year")
200+
201+
# keep all backups from April 4th, 2018 and onwards
202+
prune_backups(tf, "2018-04-01")
203+
```
204+
205+
# Dependencies
206+
207+
**rotor**’s dependencies are intentionally kept slim. It only comes with
208+
two non-base dependencies:
209+
210+
- [R6](https://github.com/r-lib/R6): A light weight system for
211+
encapsulated object-oriented programming.
212+
- [dint](https://github.com/s-fleck/dint): A toolkit for working
213+
year-quarter and year-month dates that I am also the author of. It
214+
is used by `rotate_date()` and `rotate_time()` to deal with calendar
215+
periods (such as weeks or months).
216+
217+
Both packages have no transitive dependencies (i.e they do not depend on
218+
anything outside of base R)

cran-comments.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,4 @@
99

1010
0 errors | 0 warnings | 0 notes
1111

12-
maintenance release with saner default behaviour for rotate() and backup() if
13-
no backups exist yet
12+
fixes system-time dependent unit tests that broke due to year change

0 commit comments

Comments
 (0)