|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+# Homework hw2 task2
|
|
|
2
|
+
|
|
|
3
|
+- [Überblick](#%C3%BCberblick)
|
|
|
4
|
+- [Vorbereitung](#vorbereitung)
|
|
|
5
|
+- [Struktur](#struktur)
|
|
|
6
|
+- [Funktionen](#funktionen)
|
|
|
7
|
+ - [Programmargumente in Rust](#programmargumente-in-rust)
|
|
|
8
|
+ - [Warmup Übung](#warmup-%C3%BCbung)
|
|
|
9
|
+ - [Argumente parsen](#argumente-parsen)
|
|
|
10
|
+ - [Parsen ohne Fehlerbehandlung](#parsen-ohne-fehlerbehandlung)
|
|
|
11
|
+ - [Parsen mit Fehlerbehandlung](#parsen-mit-fehlerbehandlung)
|
|
|
12
|
+ - [Zeichen im String suchen](#zeichen-im-string-suchen)
|
|
|
13
|
+ - [Ablauf des Programms](#ablauf-des-programms)
|
|
|
14
|
+ - [Dead Code](#dead-code)
|
|
|
15
|
+- [Restructuring](#restructuring)
|
|
|
16
|
+ - [Parsen der Config als Methode](#parsen-der-config-als-methode)
|
|
|
17
|
+ - [Tests](#tests)
|
|
|
18
|
+ - [Dokumentation](#dokumentation)
|
|
|
19
|
+
|
|
|
20
|
+## Überblick
|
|
|
21
|
+
|
|
|
22
|
+In dieser Aufgabe sollen Sie ein Programm entwickeln, welches 2 Parameter
|
|
|
23
|
+übergeben bekommt:
|
|
|
24
|
+
|
|
|
25
|
+- *1.* übergebene Parameter: Zu suchendes Zeichen
|
|
|
26
|
+- *2.* übergebene Parameter: String in dem die Zeichenkette gesucht werden soll.
|
|
|
27
|
+
|
|
|
28
|
+Das Programm gibt die Anzahl gefundener Treffer aus.
|
|
|
29
|
+
|
|
|
30
|
+```text
|
|
|
31
|
+You asked me to count all 'e' in "♥ The quick brown fox jumps over the lazy dog. ♥"
|
|
|
32
|
+Found 3 'e' in "♥ The quick brown fox jumps over the lazy dog. ♥"
|
|
|
33
|
+```
|
|
|
34
|
+
|
|
|
35
|
+Neben einer klaren Struktur für das Programm soll eine kompetente
|
|
|
36
|
+Fehlerbehandlung implementiert werden, sodass das Programm robust gegenüber
|
|
|
37
|
+Eingebefehlern wird. Benutzen Sie keine weiteren Module aus der Standardbibliothek, ausser:
|
|
|
38
|
+
|
|
|
39
|
+- std::env
|
|
|
40
|
+- std::process
|
|
|
41
|
+
|
|
|
42
|
+Ziele:
|
|
|
43
|
+
|
|
|
44
|
+- Parameter einlesen können
|
|
|
45
|
+- Weitere String-Funktionen kennen lernen
|
|
|
46
|
+- Fehlerbehandlung
|
|
|
47
|
+
|
|
|
48
|
+## Vorbereitung
|
|
|
49
|
+
|
|
|
50
|
+Erstellen Sie im `task2/` Ordner mittels cargo ein Binary Projekt.
|
|
|
51
|
+
|
|
|
52
|
+## Struktur
|
|
|
53
|
+
|
|
|
54
|
+Das Programm soll aus 3 Funktionen bestehen:
|
|
|
55
|
+
|
|
|
56
|
+- *main()*
|
|
|
57
|
+- *run()*
|
|
|
58
|
+- *parse_arguments()*
|
|
|
59
|
+
|
|
|
60
|
+Die *main()* Funktion ruft zuerst die *parse_arguments()* auf, um danach
|
|
|
61
|
+geeignet die *run()* Funktion zum Suchen der Zeichenkette zu starten.
|
|
|
62
|
+
|
|
|
63
|
+## Funktionen
|
|
|
64
|
+
|
|
|
65
|
+### Programmargumente in Rust
|
|
|
66
|
+
|
|
|
67
|
+Bisher waren in Ihren Programmen die Parameter fest codiert. Wie kann man in
|
|
|
68
|
+Rust die Parameter beim Aufruf des Programms auswerten?
|
|
|
69
|
+
|
|
|
70
|
+Dazu existiert das [std::env][std::env] Modul, welches Strukturen und Funktionen
|
|
|
71
|
+für die Prozessumgebung (process environment) bereit stellt.
|
|
|
72
|
+
|
|
|
73
|
+Die Funktion, die die Parameter zur weiteren Auswertung liefert heißt
|
|
|
74
|
+[`args()`][args]. Diese Funktion liefert einen Iterator mit Namen [`Arc`][Arc]
|
|
|
75
|
+zurück. Was Iteratoren genau sind und was damit alles anzustellen ist werden wir
|
|
|
76
|
+später im Semester kennen lernen. Zunächst kann man sich einen Iterator einfach
|
|
|
77
|
+als eine 'Datenquelle' vorstellen, die - wenn wir darauf zugreifen - eine Serie
|
|
|
78
|
+von Werten liefert. In unserem Fall liefert ein Zugriff auf [`args()`][args] die
|
|
|
79
|
+dem Programm übergebenen Parameter als Strings, sofern welche übergeben wurden.
|
|
|
80
|
+
|
|
|
81
|
+Und damit sind wir auch schon mitten in der Fehlerbehandlung. Auf mögliche
|
|
|
82
|
+Fehler wird in der Dokumentation bereits hingewiesen. Wir werden im weiteren von
|
|
|
83
|
+UNIX Systemen und korrektem unicode ausgehen, sodass uns ein paar
|
|
|
84
|
+Fehlerbehandlungen erspart bleiben.
|
|
|
85
|
+
|
|
|
86
|
+Um in unserem Programm auf die Parameter zugreifen zu können, werden wir diese
|
|
|
87
|
+in einer geeigneten Datentyp speichern. Es bieten sich Arrays oder Vektoren an.
|
|
|
88
|
+
|
|
|
89
|
+#### Warmup Übung
|
|
|
90
|
+
|
|
|
91
|
+1. Warum sollten Sie für die Argumente den Datentyp Vector vorziehen?
|
|
|
92
|
+1. Schreiben Sie die Funktion:
|
|
|
93
|
+
|
|
|
94
|
+```rust
|
|
|
95
|
+/// Prints elements of Vec
|
|
|
96
|
+///
|
|
|
97
|
+///
|
|
|
98
|
+/// This function will print all elements of Vec with "args found: <elem>" in
|
|
|
99
|
+/// each line
|
|
|
100
|
+///
|
|
|
101
|
+/// Returns nothing
|
|
|
102
|
+fn print_arguments(args: &Vec<String>)
|
|
|
103
|
+```
|
|
|
104
|
+
|
|
|
105
|
+Die Funktion gibt den Inhalt des übergeben Vektors zeilenweise aus.
|
|
|
106
|
+
|
|
|
107
|
+In der Main Funktion lesen Sie die bei Programmstart übergebenen Parameter ein
|
|
|
108
|
+und erstellen einen Vektor aus den eingelesenen Parametern, den Sie als Referenz
|
|
|
109
|
+übergeben.
|
|
|
110
|
+
|
|
|
111
|
+> Die Methode [*collect()*][collect] ist Teil des Iterator Traits und bietet
|
|
|
112
|
+> sich an um den Vector zu füllen. Überlegen Sie sich auch, warum es sinnvoll
|
|
|
113
|
+> ist den Vector an die Funktion *print_arguments* nur zu leihen ('&')?
|
|
|
114
|
+
|
|
|
115
|
+Wenn Sie nun Ihr Projekt mit `cargo run a b c`starten, so erhalten Sie folgende
|
|
|
116
|
+Ausgabe:
|
|
|
117
|
+
|
|
|
118
|
+```text
|
|
|
119
|
+ Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs
|
|
|
120
|
+ Running `target/debug/task1 a b c`
|
|
|
121
|
+args found: target/debug/task1
|
|
|
122
|
+args found: a
|
|
|
123
|
+args found: b
|
|
|
124
|
+args found: c
|
|
|
125
|
+```
|
|
|
126
|
+
|
|
|
127
|
+### Argumente parsen
|
|
|
128
|
+
|
|
|
129
|
+Unsere Argumente speichern wir am Besten in einer eigenen Datenstruktur
|
|
|
130
|
+*Config*. Dann sind unsere Parameter als entsprechende Datentypen einfach
|
|
|
131
|
+verfügbar.
|
|
|
132
|
+
|
|
|
133
|
+```rust
|
|
|
134
|
+/// a struct to hold all of our configuration
|
|
|
135
|
+#[derive(Debug,PartialEq)]
|
|
|
136
|
+struct Config{
|
|
|
137
|
+ search: char,
|
|
|
138
|
+ line: String,
|
|
|
139
|
+}
|
|
|
140
|
+```
|
|
|
141
|
+
|
|
|
142
|
+> Mit der *derive* Anweisung lassen wir den Rust Compiler etwas Magie anwenden.
|
|
|
143
|
+> Denn durch diese Makro Anweisung erhält unsere Datenstruktur automatisch 2
|
|
|
144
|
+> Traits wodurch wiederum automatisch die nötigen Methoden für uns erzeugt
|
|
|
145
|
+> werden um unsere Struct zu printen und deren Inhalt vergleichen zu können
|
|
|
146
|
+> ("==" Abfrage). Letzteres brauchen wir für die Tests.
|
|
|
147
|
+
|
|
|
148
|
+#### Parsen ohne Fehlerbehandlung
|
|
|
149
|
+
|
|
|
150
|
+1. Implementieren Sie die Funktion
|
|
|
151
|
+
|
|
|
152
|
+```rust
|
|
|
153
|
+/// Parses relevant arguments, returning the filled Config
|
|
|
154
|
+///
|
|
|
155
|
+///
|
|
|
156
|
+/// This function will parse the relevant arguments from the
|
|
|
157
|
+/// given <Strings>.
|
|
|
158
|
+/// Returns Config
|
|
|
159
|
+fn parse_arguments_simple(args: &Vec<String>) -> Config
|
|
|
160
|
+```
|
|
|
161
|
+
|
|
|
162
|
+Sie müssen nun in der Funktion:
|
|
|
163
|
+
|
|
|
164
|
+- den 1. String in einen char verwandeln
|
|
|
165
|
+- den 2. String der Config Struktur zuweisen
|
|
|
166
|
+
|
|
|
167
|
+>Beachten Sie, das die Strings, die die Argumente repräsentieren in der Funktion
|
|
|
168
|
+>geliehen sind und somit jemand anderen gehören. Ihre Config Struktur benötigt
|
|
|
169
|
+>somit EIGENE Strings.
|
|
|
170
|
+
|
|
|
171
|
+In *main()* geben Sie die geparsten Parameter aus, sodass Sie bei Aufruf Ihres
|
|
|
172
|
+Programms "cargo run a b c" folgende Ausgabe erhalten:
|
|
|
173
|
+
|
|
|
174
|
+```text
|
|
|
175
|
+ Finished dev [unoptimized + debuginfo] target(s) in 0.27 secs
|
|
|
176
|
+ Running `target/debug/task1 a b c`
|
|
|
177
|
+args found: target/debug/task1
|
|
|
178
|
+args found: a
|
|
|
179
|
+args found: b
|
|
|
180
|
+args found: c
|
|
|
181
|
+Config { search: 'a', line: "b" }
|
|
|
182
|
+```
|
|
|
183
|
+
|
|
|
184
|
+#### Parsen mit Fehlerbehandlung
|
|
|
185
|
+
|
|
|
186
|
+1. Unter Umständen ruft ein Benutzer Ihr Programm ohne die benötigten Parameter
|
|
|
187
|
+ auf. Welche Fehlermeldung erhalten Sie bei dem Aufruf Ihres Programms ohne
|
|
|
188
|
+ Parameter und warum?
|
|
|
189
|
+
|
|
|
190
|
+1. Ein Programmabsturz wäre die falsche Antwort darauf. Besser ist beim
|
|
|
191
|
+ Auftreten von Eingabefehler ein entsprechender Hinweis, wie das Programm zu
|
|
|
192
|
+ verwenden ist. Implementieren Sie die Funktion
|
|
|
193
|
+
|
|
|
194
|
+```rust
|
|
|
195
|
+/// Parses relevant arguments, returning the filled Config in Result
|
|
|
196
|
+///
|
|
|
197
|
+///
|
|
|
198
|
+/// This function will parse the relevant arguments from the
|
|
|
199
|
+/// given <Strings>.
|
|
|
200
|
+/// Returns Config or Error Message in Result
|
|
|
201
|
+fn parse_arguments(args: &Vec<String>) -> Result<Config, xx >
|
|
|
202
|
+```
|
|
|
203
|
+
|
|
|
204
|
+Implementieren Sie die folgende Fehlerbehandlung:
|
|
|
205
|
+
|
|
|
206
|
+- Werden vom Benutzer zuwenig übergeben, so gibt die Funktion den Fehlerstring
|
|
|
207
|
+ "not enough parameters" zurück. Werden vom Benutzer zu viele Parameter
|
|
|
208
|
+ übergeben, so wertet die Funktion nur die ersten beiden aus. In der API der
|
|
|
209
|
+ Funktion oben ist als Typ xx für den 'String' angedeutet und muss entsprechend
|
|
|
210
|
+ von Ihnen mit dem richtigen Typen versehen werden.
|
|
|
211
|
+
|
|
|
212
|
+- Tritt ein Fehler beim Wandeln des 1. Parameter in einen char auf, so geben Sie
|
|
|
213
|
+ den String "char mismatch" als Fehlerstring in Result zurück. Wird statt eines
|
|
|
214
|
+ chars als 1. Parameter ein String übergeben, so werten Sie nur den ersten
|
|
|
215
|
+ Buchstaben davon aus.
|
|
|
216
|
+
|
|
|
217
|
+Die main() Funktion kann keine Return Werte zurückliefern. Um aber der
|
|
|
218
|
+aufrufenden Shell den Abbruch des Programms mitteilen zu können steht die
|
|
|
219
|
+Funktion *[process::exit()]* aus der Standardbibliothek zu Verfügung. Durch die
|
|
|
220
|
+Rückgabe von '1' signalisieren Sie der Shell, dass das Programm abgebrochen
|
|
|
221
|
+wurde.
|
|
|
222
|
+
|
|
|
223
|
+### Zeichen im String suchen
|
|
|
224
|
+
|
|
|
225
|
+In Ihrer vorherigen Homework haben Sie bereits eine ähnliche Funktionalität
|
|
|
226
|
+implementiert, die Sie nun in der *run()* Funktion übernehmen können.
|
|
|
227
|
+
|
|
|
228
|
+### Ablauf des Programms
|
|
|
229
|
+
|
|
|
230
|
+Zur Abgabe des Programms soll die *main()* Funktion nur:
|
|
|
231
|
+
|
|
|
232
|
+- *parse_arguments()* und
|
|
|
233
|
+- *run()*
|
|
|
234
|
+
|
|
|
235
|
+benutzen. In diesen beiden Funktionen darf kein *unwrap()* mehr verwendet
|
|
|
236
|
+werden.
|
|
|
237
|
+
|
|
|
238
|
+Da die Tests jedoch auch die Funktion *parse_arguments_simple()* benutzt, darf
|
|
|
239
|
+diese nicht auskommentiert werden.
|
|
|
240
|
+
|
|
|
241
|
+#### Dead Code
|
|
|
242
|
+
|
|
|
243
|
+Um die Compiler Warnungen vor nicht benutzen Funktionen auszuschalten, fügen Sie
|
|
|
244
|
+direkt in der Zeile vor der Funktion folgende Anweisung hinzu
|
|
|
245
|
+
|
|
|
246
|
+```Rust
|
|
|
247
|
+#[allow(dead_code)]
|
|
|
248
|
+fn parse_arguments_simple(args: &Vec<String>) -> Config {
|
|
|
249
|
+...
|
|
|
250
|
+```
|
|
|
251
|
+
|
|
|
252
|
+## Restructuring
|
|
|
253
|
+
|
|
|
254
|
+Bisher ist alles in unserm `src/main.rs` Modul, wodurch dieses im Laufe der Zeit
|
|
|
255
|
+immer unübersichtlicher werden würde. Ziel ist es nun, das meiste aus der
|
|
|
256
|
+`main.rs` auszulagern, sodass nur noch die *main()* Funktion in `main.rs` steht.
|
|
|
257
|
+
|
|
|
258
|
+Ausgelagert wird der Code in eine Bibliothek, genauer gesagt in die Datei
|
|
|
259
|
+`src/lib.rs`. Um den Code aus `lib.rs` in `main.rs` verwenden zu können, müssen
|
|
|
260
|
+Sie ihre derzeitige 'Create' mit den entsprechenden 'extern' und 'use'
|
|
|
261
|
+Anweisungen in `main.rs`, erreichbar machen. Ergänzen Sie dazu in der
|
|
|
262
|
+`src/main.rs`:
|
|
|
263
|
+
|
|
|
264
|
+```Rust
|
|
|
265
|
+extern crate task2;
|
|
|
266
|
+use task2::Config;
|
|
|
267
|
+```
|
|
|
268
|
+
|
|
|
269
|
+Alle *use* Anweisungen stehen in der `main.rs`. Es müssen keine weiteren *use*
|
|
|
270
|
+Anweisungen in der lib.rs stehen
|
|
|
271
|
+
|
|
|
272
|
+### Parsen der Config als Methode
|
|
|
273
|
+
|
|
|
274
|
+Die *parse_arguments()* Methode steht noch in keinem Zusammenhang mit der
|
|
|
275
|
+Struktur, die die Methode mit Daten füllt.
|
|
|
276
|
+
|
|
|
277
|
+Schreiben Sie einen Konstruktor *new()* für die Datenstruktur *Config* und
|
|
|
278
|
+ersetzten Sie damit die *parse_arguments()* Funktion. Verwenden Sie nun Config
|
|
|
279
|
+geeignet in Ihrer *main()* Funktion.
|
|
|
280
|
+
|
|
|
281
|
+### Tests
|
|
|
282
|
+
|
|
|
283
|
+Die Test sind ausgelagert in die Datei `tests/task2.rs`. Der Aufruf von cargo
|
|
|
284
|
+test sucht nach Unit Tests in der `src/main.rs` und im Verzeichnis `tests/`. Die
|
|
|
285
|
+Tests in `tests/task2.rs` funktionieren erst, wenn eine `src/lib.rs` existiert
|
|
|
286
|
+(siehe Aufgabenstellung [Restructuring](#restructuring) oben).
|
|
|
287
|
+
|
|
|
288
|
+### Dokumentation
|
|
|
289
|
+
|
|
|
290
|
+Dokumentieren Sie die Funktionen in Ihrer `lib.rs`, sodass **cargo doc** eine
|
|
|
291
|
+aussagekräftige Dokumentation erstellt.
|
|
|
292
|
+
|
|
|
293
|
+Da Sie im `src/`Verzeichnis eine `main.rs` und eine `lib.rs` vorliegen haben,
|
|
|
294
|
+beschwert sich **cargo doc**, dass es nicht weiss wie es die Dokumentation
|
|
|
295
|
+erstellen soll:
|
|
|
296
|
+
|
|
|
297
|
+```text
|
|
|
298
|
+error: cannot document a package where a library and a binary have the same name. Consider renaming one or marking the target as `doc = false`
|
|
|
299
|
+```
|
|
|
300
|
+
|
|
|
301
|
+Damit **cargo doc** Ihre Dokumentation für die Funktionen in `lib.rs` erstellt,
|
|
|
302
|
+benötigen Sie folgende Erweiterung in Ihrer `Cargo.toml` Datei.
|
|
|
303
|
+
|
|
|
304
|
+```text
|
|
|
305
|
+[[bin]]
|
|
|
306
|
+doc = false
|
|
|
307
|
+name = "task2"
|
|
|
308
|
+```
|
|
|
309
|
+
|
|
|
310
|
+Damit weiß cargo, dass es die Dokumentation aus `lib.rs` erstellen soll und
|
|
|
311
|
+nicht aus `main.rs`.
|
|
|
312
|
+
|
|
|
313
|
+[args]: https://doc.rust-lang.org/std/env/fn.args.html
|
|
|
314
|
+[std::env]:https://doc.rust-lang.org/std/env/index.html
|
|
|
315
|
+[Arc]: https://doc.rust-lang.org/std/env/struct.Args.html
|
|
|
316
|
+[collect]: https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.collect
|
|
|
317
|
+[process::exit()]: https://doc.rust-lang.org/std/process/fn.exit.html
|