diff --git a/config.json b/config.json index 01ec79ac..f56b9735 100644 --- a/config.json +++ b/config.json @@ -65,6 +65,18 @@ "booleans" ], "status": "wip" + }, + { + "slug": "log-levels", + "name": "Log Levels", + "uuid": "6c85ef11-cd2d-4718-9f75-84c467afb2f9", + "concepts": [ + "strings" + ], + "prerequisites": [ + "lists" + ], + "status": "wip" } ], "practice": [ diff --git a/exercises/concept/log-levels/.docs/hints.md b/exercises/concept/log-levels/.docs/hints.md new file mode 100644 index 00000000..b5296c36 --- /dev/null +++ b/exercises/concept/log-levels/.docs/hints.md @@ -0,0 +1 @@ +# Hints diff --git a/exercises/concept/log-levels/.docs/instructions.md b/exercises/concept/log-levels/.docs/instructions.md new file mode 100644 index 00000000..3fd20174 --- /dev/null +++ b/exercises/concept/log-levels/.docs/instructions.md @@ -0,0 +1,51 @@ +# Instructions + +In this exercise you'll be processing log-lines. + +Each log line is a string formatted as follows: `"[]: "`. + +There are three different log levels: + +- `INFO` +- `WARNING` +- `ERROR` + +You have three tasks, each of which will take a log line and ask you to do something with it. + +## 1. Get message from a log line + +Implement the `message` function to return a log line's message: + +```R +message("[ERROR]: Invalid operation") +# => "Invalid operation" +``` + +Any leading or trailing white space should be removed: + +```R +message("[WARNING]: Disk almost full\r\n") +# => "Disk almost full" +``` + +## 2. Get log level from a log line + +Implement the `log_level` function to return a log line's log level, which should be returned in lowercase: + +```R +log_level("[ERROR]: Invalid operation") +# => "error" +``` + +## 3. Reformat a log line + +Implement the `reformat` function that reformats the log line, putting the message first and the log level after it in parentheses: + +```R +reformat("[INFO]: Operation completed") +# => "Operation completed (info)" +``` + +## Note + +Please complete this exercise using ony R core functions. You may find internet references to the `stringr` package and functions with a `str_` prefix. Like most third-party packages, this is not available within Exercism. \ No newline at end of file diff --git a/exercises/concept/log-levels/.docs/introduction.md b/exercises/concept/log-levels/.docs/introduction.md new file mode 100644 index 00000000..be9cbfec --- /dev/null +++ b/exercises/concept/log-levels/.docs/introduction.md @@ -0,0 +1,70 @@ +# Introduction + +R makes no distinction between strings and characters, so `"A"` is just a one-character string. + +To find the length of a string, use `nchar()`. +Using `length()` will return the length of the underlying `vector` of strings, typically 1 as in this case: + +```R +> x <- "Some string" +> length(x) # length of underlying vector +[1] 1 +> nchar(x) # length of string +[1] 11 +```` + +## Constructing strings + +To concatenate several strings we have `paste()`. +The default separator is a space, so override this if necessary. + +```R +> paste("Mon", "Tue", "Wed", sep = "") +[1] "MonTueWed" +``` + +Numbers will be converted to strings as necessary. + +There is also `sprintf()`, taken directly from C with identical syntax. +This allows precise formatting of the output string. +At its simplest just use `%s` as a placeholder for each string you want to interpolate, then add the value(s) as comma-separated parameters. + +```R +> sprintf("Hello, %s, nice to see you", "Carol") +[1] "Hello, Carol, nice to see you" +``` + +## String parts + +For a substring whose position is known: + +```R +> x <- "Some string" +> substr(x, 3, 6) +[1] "me s" +``` + +The start and end indices are both inclusive. + +To split on some separator, such as space: + +```R +> strsplit(x, " ")[[1]] +[1] "Some" "string" +``` + +One thing to beware of is that this (like several string functions) returns a `list`. +If you have not yet unlocked that concept, just add `[[1]]` in double brackets to get the vector in the first element of the list. +Details will become clearer later in the syllabus. + +## Whitespace + +Leading and/or trailing whitespace can be removed with `trimws()`: + +```R +> s <- " messy string " +> trimws(s) +[1] "messy string" +``` + +There is a `which = "left"` (or "right") parameter to only trim one end. diff --git a/exercises/concept/log-levels/.meta/config.json b/exercises/concept/log-levels/.meta/config.json new file mode 100644 index 00000000..69903580 --- /dev/null +++ b/exercises/concept/log-levels/.meta/config.json @@ -0,0 +1,11 @@ +{ + "authors": ["colinleach"], + "contributors": [], + "files": { + "solution": ["log-levels.R"], + "test": ["test_log-levels.R"], + "exemplar": [".meta/exemplar.R"] + }, + "forked_from": ["fsharp/log-levels"], + "blurb": "Learn basic string manipulation in R by processing a log file." +} diff --git a/exercises/concept/log-levels/.meta/design.md b/exercises/concept/log-levels/.meta/design.md new file mode 100644 index 00000000..3115aa05 --- /dev/null +++ b/exercises/concept/log-levels/.meta/design.md @@ -0,0 +1,31 @@ +# Design + +## Goal + +The current goal of this exercise is to introduce the student to string-handling finctions in core R. + +In an ideal world it would cover `stringr` as the preferred alternative, but Docker seems to be blocking that package in the test runner for now. + +## Learning objectives + +- Understand `nchar()` versus `length()` +- Understand `paste()` and `sprintf()` to construct strings. +- Understand `substr()` and `strsplit()` to get parts of a string. +- Understand `trimws()` to remove whitespace. + +## Out of scope + +- `grep()`, `regexexpr()` and `gregexpr()` are postponed to the `regular-expressions` concept. +- Discussion of `lists` as return values, beyond the necessary minimum. + +## Concepts + +The concepts this exercise unlocks are: + +- `nothingness` +- `randomness` +- `regular-expressions` + +## Prerequisites + +- `vectors` diff --git a/exercises/concept/log-levels/.meta/exemplar.R b/exercises/concept/log-levels/.meta/exemplar.R new file mode 100644 index 00000000..f993794a --- /dev/null +++ b/exercises/concept/log-levels/.meta/exemplar.R @@ -0,0 +1,22 @@ +# Aiming to solve this early concept exercise without regex knowledge +# or access to stringr +# +# Makes me understand why stringr is so popular! + +split_msg <- function(msg) { + strsplit(msg, ": ")[[1]] +} + +message <- function(msg) { + trimws(split_msg(msg)[2]) +} + +log_level <- function(msg) { + raw <- tolower(split_msg(msg)[1]) + len <- nchar(raw) + substr(raw, 2, len - 1) +} + +reformat <- function(msg) { + paste(message(msg), " (", log_level(msg), ")", sep = "") +} diff --git a/exercises/concept/log-levels/log-levels.R b/exercises/concept/log-levels/log-levels.R new file mode 100644 index 00000000..3dfa9226 --- /dev/null +++ b/exercises/concept/log-levels/log-levels.R @@ -0,0 +1,8 @@ +message <- function(msg) { +} + +log_level <- function(msg) { +} + +reformat <- function(msg) { +} diff --git a/exercises/concept/log-levels/test_log-levels.R b/exercises/concept/log-levels/test_log-levels.R new file mode 100644 index 00000000..d1fad139 --- /dev/null +++ b/exercises/concept/log-levels/test_log-levels.R @@ -0,0 +1,58 @@ +source("./log-levels.R") +library(testthat) + +# message + +test_that("Error message", { + msg <- "[ERROR]: Stack overflow" + expect_equal(message(msg), "Stack overflow") +}) + +test_that("Warning message", { + msg <- "[WARNING]: Disk almost full" + expect_equal(message(msg), "Disk almost full") +}) + +test_that("Info message", { + msg <- "[INFO]: File moved" + expect_equal(message(msg), "File moved") +}) + +test_that("Message with leading and trailing white space", { + msg <- "[WARNING]: \tTimezone not set \r\n" + expect_equal(message(msg), "Timezone not set") +}) + +# log_level + +test_that("Error log level", { + msg <- "[ERROR]: Disk full" + expect_equal(log_level(msg), "error") +}) + +test_that("Warning log level", { + msg <- "[WARNING]: Unsafe password" + expect_equal(log_level(msg), "warning") +}) + +test_that("Info log level", { + msg <- "[INFO]: Timezone changed" + expect_equal(log_level(msg), "info") +}) + +# reformat + +test_that("Warning reformat", { + msg <- "[WARNING]: Decreased performance" + expect_equal(reformat(msg), "Decreased performance (warning)") +}) + +test_that("Info reformat", { + msg <- "[INFO]: Disk defragmented" + expect_equal(reformat(msg), "Disk defragmented (info)") +}) + +test_that("Reformat with leading and trailing white space", { + msg <- "[ERROR]: \t Corrupt disk\t \t \r\n" + expect_equal(reformat(msg), "Corrupt disk (error)") +})