Skip to content

Commit 3da3f3e

Browse files
aldracoErikSchierboom
authored andcommitted
Port concept exercise strings from C#
* Add scaffold files/directories. * Add instructions and introduction. * Add hints with some links. * Add config files. * Update description to use loglines module, and add prerequisite. * Add Python specific files, start on implementation. * Update tests. * Add more tests. * Update docs with text around links. * Update tests with reformat test, and fix example import from testing. * Add third function, reformat. * Move an item into after.md, and add Joel Spolsky's blog post as reference for strings. * Add design notes. * Add reference types note. * Alternate solution not using regex groups, just split. * Update languages/exercises/concept/strings/.docs/after.md Co-Authored-By: Erik Schierboom <[email protected]> * Update loglines test - updated filename to match tooling - import functions, not module - use testcase.assertEqual instead of assert * Use an example that does not use Regex. * Better regex for regex-based solution. * Update .docs and .meta. * Change strings-methods to strings-basic, for consistency and to better reflect the purpose of the exercise. Co-Authored-By: Erik Schierboom <[email protected]> * Add more information to introduction.md. TODO: add links. * Add extra context for hints.md. * Add UUID to strings exercise config. * Simpify initial description of strings. Co-Authored-By: Erik Schierboom <[email protected]> * Add more context around various string methods. * Remove After header. Co-Authored-By: Erik Schierboom <[email protected]> * Use link for Joel on Software post. Co-Authored-By: Erik Schierboom <[email protected]> * Cleanup header formatting, use actual link. Co-Authored-By: Erik Schierboom <[email protected]> * Change method -> function. Co-Authored-By: Erik Schierboom <[email protected]> * Name prerequisite with -basic suffix. * Leave a bit to the imagination for strings hints. * Move example.py into .meta folder per instructions. * Update links for after and hints.md * Update hints.md w.r.t. join() and fix link. * Add missing implementation link. * Single spacing between functions. * Remove leading whitespace in * Fix missing backticks and make Python examples read as if inside a REPL. * Update from review feedback. * Update languages/exercises/concept/strings/.docs/hints.md Co-Authored-By: Erik Schierboom <[email protected]> * Update languages/exercises/concept/strings/.docs/instructions.md Co-Authored-By: Erik Schierboom <[email protected]> * Update languages/exercises/concept/strings/.docs/introduction.md Co-Authored-By: Erik Schierboom <[email protected]> * Update languages/exercises/concept/strings/.docs/hints.md Co-Authored-By: Erik Schierboom <[email protected]> * Link to python3 latest docs. * Use named capture groups in regex for solution. * Add extra test for capitalized words. * Use msg parameter for assert statements in test. * Use dashed style UUID. * Rename test and clarify exactly what is being tested. Co-authored-by: Erik Schierboom <[email protected]>
1 parent 29478dd commit 3da3f3e

File tree

9 files changed

+222
-0
lines changed

9 files changed

+222
-0
lines changed
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
Python strings can be powerful. At their core, a string is an iterable of Unicode code points. There is no separate "character" type in Python. For a deep dive on what information a string is encoding (or, "how does the computer know how to translate zeroes and ones into letters?"), [this blog post is enduringly helpful][joel-on-text].
2+
3+
As a Python data type, strings can be manipulated by various methods, split into letters, used to join a list of strings into one string, amongst other things. The simplest way to create a string is by literally delimiting with `"` or `'` (e.g. `mystring = "astring"`); however, strings can also be written across multiple lines by using triple quotes (`"""`), or written to be used as a format string, like this: `f"{variable_will_be_interpolated_here}"`.
4+
5+
New strings can be created by concatenating other strings, using the `+` operator, or by using `.join()`. The `.format()` method or the format string syntax shown above are the most common way to interpolate strings. All of these methods return a new instance of a string, since a string itself is immutable.
6+
7+
### More about Python logging
8+
9+
Python has a useful [built-in logging library][logging-library], aptly named `logging`. It handles cases similar
10+
to the ones you just finished working on, and many more. It's very often used in projects, and is worthwhile to know.
11+
12+
[joel-on-text]: https://www.joelonsoftware.com/2003/10/08/the-absolute-minimum-every-software-developer-absolutely-positively-must-know-about-unicode-and-character-sets-no-excuses/
13+
[logging-library]: https://docs.python.org/3.8/library/logging.html
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
### General
2+
3+
- The [Python documentation for `str`][python-str-doc] has an overview of the Python `str` type.
4+
5+
### 1. Get message from a log line
6+
7+
- Strings in Python have [lots of convenient instance methods][str-type-methods] for cleaning, splitting, manipulating, and creating new strings. Extracting values from a string could be done by splitting it based on a substring, for example.
8+
9+
### 2. Get log level from a log line
10+
11+
- Strings also have methods that help convert letters from lower to uppercase and vice-versa.
12+
13+
### 3. Reformat a log line
14+
15+
Strings are immutable, but can be combined together to make new strings, or have elements replaced. This goal can be accomplished by using string methods, or operators like `+` or `+=` (which are overloaded to work with strings).
16+
Python also has a concept of string formatting, like many other languages.
17+
18+
- The [`str.join()`][str-join] method is useful to join an iterable of strings into one string by interspersing them with a common value, e.g. `":".join("abcde")` would create `"a:b:c:d:e"`.
19+
- [`str.format()`][str-format] is an idiomatic way to do string interpolation in Python (inserting one or more string value(s) into another).
20+
- [Format strings][format-str] are another convenient way to interpolate values into a string. This strategy is particularly useful when more than one named variable needs to be inserted into a final output.
21+
22+
[python-str-doc]: https://docs.python.org/3/library/stdtypes.html#text-sequence-type-str
23+
[str-type-methods]: https://docs.python.org/3/library/stdtypes.html#str
24+
[str-join]: https://docs.python.org/3/library/stdtypes.html#str.join
25+
[str-format]: https://docs.python.org/3/library/stdtypes.html#str.format
26+
[format-str]: https://docs.python.org/3/library/string.html#formatstrings
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
In this exercise you'll be processing log-lines.
2+
3+
Each log line is a string formatted as follows: `"[<LEVEL>]: <MESSAGE>"`.
4+
5+
There are three different log levels:
6+
7+
- `INFO`
8+
- `WARNING`
9+
- `ERROR`
10+
11+
You have three tasks, each of which will take a log line and ask you to do something with it.
12+
13+
### 1. Extract a message from a log line
14+
15+
Implement a function to return a log line's message:
16+
17+
```python
18+
>>> extract_message("[ERROR]: Invalid operation")
19+
'Invalid operation'
20+
```
21+
22+
The message should be trimmed of any whitespace.
23+
24+
```python
25+
>>> extract_message("[ERROR]: Invalid operation.\t\n")
26+
'Invalid operation.'
27+
```
28+
29+
### 2. Change a message's loglevel.
30+
31+
Implement a function that replaces a log line's current log level with a new one:
32+
33+
```python
34+
>>> change_log_level("[INFO]: Fatal Error.", "ERROR")
35+
'[ERROR]: Fatal Error.'
36+
```
37+
38+
### 3. Reformat a log line
39+
40+
Implement a function that reformats the log line, putting the message first and the log level after it in parentheses:
41+
42+
```python
43+
>>> reformat("[INFO]: Operation completed")
44+
'Operation completed (info)'
45+
```
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
A `str` typed object in Python is an immutable sequence of Unicode code points. This could include letters, numbers, punctuation, etc. To manipulate strings, Python provides string methods that can transform a string into other types, create new strings based on method arguments, or return information about the string. Strings can be concatenated with `+`.
2+
3+
Immutability means that a string's value doesn't change; methods that appear to modify a string actually return a new instance of `str`.
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"contributors": [
3+
{
4+
"github_username": "aldraco",
5+
"exercism_username": "aldraco"
6+
}
7+
]
8+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# Design
2+
3+
## Goal
4+
5+
The goal of this exercise is to teach the student about Python strings, and familiarize them with string manipulation in Python.
6+
7+
## Things to teach
8+
9+
- Know that Python has a `str` type.
10+
- Know how to find items in a string.
11+
- Know how to manipulate strings to create new strings.
12+
- Familiarize one's self with string instance methods in Python.
13+
- Learn about string formatting.
14+
15+
## Things not to teach
16+
17+
- Regex: `regex` is a useful tool for a solution, but isn't required.
18+
- Iteration: Although strings are iterable, this is not the focus of this exercise.
19+
20+
## Concepts
21+
22+
The Concepts this exercise unlocks are:
23+
24+
- `strings-basic`: know about `str` type in Python, know some basic methods, know about formatting.
25+
26+
## Prerequisites
27+
28+
- `functions`: The student should be familiar with creating functions.
29+
30+
## Representer
31+
32+
This exercise does not require any logic to be added to the [representer][representer]
33+
34+
## Analyzer
35+
36+
This exercise does not require any logic to be added to the [analyzer][analyzer].
37+
38+
[analyzer]: https://github.com/exercism/python-analyzer
39+
[representer]: https://github.com/exercism/python-representer
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import re
2+
3+
LOGLINE_RE = r"\[(?P<level>INFO|ERROR|WARN)\] (?P<msg>.*)"
4+
5+
6+
def _extract_pieces(message):
7+
pieces = re.search(LOGLINE_RE, message)
8+
return pieces.group("level"), pieces.group("msg")
9+
10+
def _extract_pieces_no_regex_groups(message):
11+
words = [word for word in re.split("[\s\[\]]", message) if word]
12+
return words[0], " ".join(words[1:])
13+
14+
def _extract_pieces_no_regex(message):
15+
words = [word for word in message.strip().replace("]", "[").split("[") if word]
16+
return words[0], words[1].strip()
17+
18+
def change_log_level(message, new_loglevel):
19+
"""Change loglevel of message to new_loglevel."""
20+
return f"[{new_loglevel}] {extract_message(message)}"
21+
22+
def extract_message(message):
23+
return _extract_pieces_no_regex(message)[1]
24+
25+
def reformat(message):
26+
loglevel, msg = _extract_pieces_no_regex_groups(message)
27+
return f"{msg} ({loglevel.lower()})"

exercises/concept/strings/loglines.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
def change_log_level():
2+
pass
3+
4+
def extract_message():
5+
pass
6+
7+
def reformat():
8+
pass
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import unittest
2+
from loglines import extract_message, change_log_level, reformat
3+
4+
5+
class TestLogLines(unittest.TestCase):
6+
def test_message(self):
7+
self.assertEqual(
8+
extract_message("[INFO] Hello there."),
9+
"Hello there.",
10+
msg="Should correctly extract a basic message.",
11+
)
12+
13+
def test_message_with_punctuation(self):
14+
self.assertEqual(
15+
extract_message("[WARN] File not found: exercism_practice.py"),
16+
"File not found: exercism_practice.py",
17+
msg="Should preserve punctuation and whitespace from original message.",
18+
)
19+
20+
def test_level_word_remains_in_message(self):
21+
self.assertEqual(
22+
extract_message("[ERROR] Error while serializing data."),
23+
"Error while serializing data.",
24+
msg="Should preserve a loglevel word that is actually part of the message.",
25+
)
26+
27+
def test_change_loglevel(self):
28+
self.assertEqual(
29+
change_log_level("[ERROR] No configs found, but not a big deal.", "INFO"),
30+
"[INFO] No configs found, but not a big deal.",
31+
msg="Should replace the loglevel.",
32+
)
33+
34+
def test_change_loglevel_with_loglevel_in_message(self):
35+
self.assertEqual(
36+
change_log_level("[WARN] Warning: file does not exist.", "INFO"),
37+
"[INFO] Warning: file does not exist.",
38+
msg="Should not replace loglevel names that are part of the message.",
39+
)
40+
41+
def test_reformat(self):
42+
self.assertEqual(
43+
reformat("[WARN] Warning: file not found."),
44+
"Warning: file not found. (warn)",
45+
msg="Should reformat with lowercase loglevel. ",
46+
)
47+
48+
def test_capture_message_when_contains_loglevel_string(self):
49+
self.assertEqual(
50+
extract_message("[WARN] Warning: file not found. Will WARN once."),
51+
"Warning: file not found. Will WARN once.",
52+
msg="Should extract loglevel inside brackets, not from messsage, even if message contains something that looks like a loglevel.",
53+
)

0 commit comments

Comments
 (0)