]>
Commit | Line | Data |
---|---|---|
13cf67c4 XL |
1 | ## Working with Environment Variables |
2 | ||
3 | We’ll improve `minigrep` by adding an extra feature: an option for | |
4 | case-insensitive searching that the user can turn on via an environment | |
5 | variable. We could make this feature a command line option and require that | |
04454e1e FG |
6 | users enter it each time they want it to apply, but by instead making it an |
7 | environment variable, we allow our users to set the environment variable once | |
8 | and have all their searches be case insensitive in that terminal session. | |
13cf67c4 XL |
9 | |
10 | ### Writing a Failing Test for the Case-Insensitive `search` Function | |
11 | ||
04454e1e FG |
12 | We first add a new `search_case_insensitive` function that will be called when |
13 | the environment variable has a value. We’ll continue to follow the TDD process, | |
14 | so the first step is again to write a failing test. We’ll add a new test for | |
15 | the new `search_case_insensitive` function and rename our old test from | |
13cf67c4 | 16 | `one_result` to `case_sensitive` to clarify the differences between the two |
9fa01778 | 17 | tests, as shown in Listing 12-20. |
13cf67c4 XL |
18 | |
19 | <span class="filename">Filename: src/lib.rs</span> | |
20 | ||
136023e0 | 21 | ```rust,ignore,does_not_compile |
74b04a01 | 22 | {{#rustdoc_include ../listings/ch12-an-io-project/listing-12-20/src/lib.rs:here}} |
13cf67c4 XL |
23 | ``` |
24 | ||
25 | <span class="caption">Listing 12-20: Adding a new failing test for the | |
26 | case-insensitive function we’re about to add</span> | |
27 | ||
28 | Note that we’ve edited the old test’s `contents` too. We’ve added a new line | |
29 | with the text `"Duct tape."` using a capital D that shouldn’t match the query | |
9fa01778 | 30 | `"duct"` when we’re searching in a case-sensitive manner. Changing the old test |
13cf67c4 XL |
31 | in this way helps ensure that we don’t accidentally break the case-sensitive |
32 | search functionality that we’ve already implemented. This test should pass now | |
33 | and should continue to pass as we work on the case-insensitive search. | |
34 | ||
35 | The new test for the case-*insensitive* search uses `"rUsT"` as its query. In | |
36 | the `search_case_insensitive` function we’re about to add, the query `"rUsT"` | |
37 | should match the line containing `"Rust:"` with a capital R and match the line | |
532ac7d7 | 38 | `"Trust me."` even though both have different casing from the query. This is |
13cf67c4 XL |
39 | our failing test, and it will fail to compile because we haven’t yet defined |
40 | the `search_case_insensitive` function. Feel free to add a skeleton | |
41 | implementation that always returns an empty vector, similar to the way we did | |
42 | for the `search` function in Listing 12-16 to see the test compile and fail. | |
43 | ||
44 | ### Implementing the `search_case_insensitive` Function | |
45 | ||
46 | The `search_case_insensitive` function, shown in Listing 12-21, will be almost | |
47 | the same as the `search` function. The only difference is that we’ll lowercase | |
48 | the `query` and each `line` so whatever the case of the input arguments, | |
49 | they’ll be the same case when we check whether the line contains the query. | |
50 | ||
51 | <span class="filename">Filename: src/lib.rs</span> | |
52 | ||
fc512014 | 53 | ```rust,noplayground |
74b04a01 | 54 | {{#rustdoc_include ../listings/ch12-an-io-project/listing-12-21/src/lib.rs:here}} |
13cf67c4 XL |
55 | ``` |
56 | ||
57 | <span class="caption">Listing 12-21: Defining the `search_case_insensitive` | |
58 | function to lowercase the query and the line before comparing them</span> | |
59 | ||
60 | First, we lowercase the `query` string and store it in a shadowed variable with | |
04454e1e FG |
61 | the same name. Calling `to_lowercase` on the query is necessary so no |
62 | matter whether the user’s query is `"rust"`, `"RUST"`, `"Rust"`, or `"rUsT"`, | |
63 | we’ll treat the query as if it were `"rust"` and be insensitive to the case. | |
64 | While `to_lowercase` will handle basic Unicode, it won’t be 100% accurate. If | |
65 | we were writing a real application, we’d want to do a bit more work here, but | |
66 | this section is about environment variables, not Unicode, so we’ll leave it at | |
67 | that here. | |
13cf67c4 XL |
68 | |
69 | Note that `query` is now a `String` rather than a string slice, because calling | |
70 | `to_lowercase` creates new data rather than referencing existing data. Say the | |
71 | query is `"rUsT"`, as an example: that string slice doesn’t contain a lowercase | |
72 | `u` or `t` for us to use, so we have to allocate a new `String` containing | |
73 | `"rust"`. When we pass `query` as an argument to the `contains` method now, we | |
74 | need to add an ampersand because the signature of `contains` is defined to take | |
75 | a string slice. | |
76 | ||
04454e1e FG |
77 | Next, we add a call to `to_lowercase` on each `line` to lowercase all |
78 | characters. Now that we’ve converted `line` and `query` to lowercase, we’ll | |
79 | find matches no matter what the case of the query is. | |
13cf67c4 XL |
80 | |
81 | Let’s see if this implementation passes the tests: | |
82 | ||
f035d41b | 83 | ```console |
74b04a01 | 84 | {{#include ../listings/ch12-an-io-project/listing-12-21/output.txt}} |
13cf67c4 XL |
85 | ``` |
86 | ||
87 | Great! They passed. Now, let’s call the new `search_case_insensitive` function | |
88 | from the `run` function. First, we’ll add a configuration option to the | |
89 | `Config` struct to switch between case-sensitive and case-insensitive search. | |
9fa01778 XL |
90 | Adding this field will cause compiler errors because we aren’t initializing |
91 | this field anywhere yet: | |
13cf67c4 XL |
92 | |
93 | <span class="filename">Filename: src/lib.rs</span> | |
94 | ||
74b04a01 XL |
95 | ```rust,ignore,does_not_compile |
96 | {{#rustdoc_include ../listings/ch12-an-io-project/listing-12-22/src/lib.rs:here}} | |
13cf67c4 XL |
97 | ``` |
98 | ||
04454e1e FG |
99 | We added the `ignore_case` field that holds a Boolean. Next, we need the `run` |
100 | function to check the `ignore_case` field’s value and use that to decide | |
101 | whether to call the `search` function or the `search_case_insensitive` | |
102 | function, as shown in Listing 12-22. This still won’t compile yet. | |
13cf67c4 XL |
103 | |
104 | <span class="filename">Filename: src/lib.rs</span> | |
105 | ||
74b04a01 XL |
106 | ```rust,ignore,does_not_compile |
107 | {{#rustdoc_include ../listings/ch12-an-io-project/listing-12-22/src/lib.rs:there}} | |
13cf67c4 XL |
108 | ``` |
109 | ||
110 | <span class="caption">Listing 12-22: Calling either `search` or | |
04454e1e | 111 | `search_case_insensitive` based on the value in `config.ignore_case`</span> |
13cf67c4 XL |
112 | |
113 | Finally, we need to check for the environment variable. The functions for | |
114 | working with environment variables are in the `env` module in the standard | |
04454e1e FG |
115 | library, so we bring that module into scope at the top of *src/lib.rs*. Then |
116 | we’ll use the `var` function from the `env` module to check to see if any value | |
117 | has been set for an environment variable named `IGNORE_CASE`, as shown in | |
118 | Listing 12-23. | |
13cf67c4 XL |
119 | |
120 | <span class="filename">Filename: src/lib.rs</span> | |
121 | ||
fc512014 | 122 | ```rust,noplayground |
74b04a01 | 123 | {{#rustdoc_include ../listings/ch12-an-io-project/listing-12-23/src/lib.rs:here}} |
13cf67c4 XL |
124 | ``` |
125 | ||
04454e1e FG |
126 | <span class="caption">Listing 12-23: Checking for any value in an environment |
127 | variable named `IGNORE_CASE`</span> | |
13cf67c4 | 128 | |
04454e1e FG |
129 | Here, we create a new variable `ignore_case`. To set its value, we call the |
130 | `env::var` function and pass it the name of the `IGNORE_CASE` environment | |
131 | variable. The `env::var` function returns a `Result` that will be the | |
132 | successful `Ok` variant that contains the value of the environment variable if | |
133 | the environment variable is set to any value. It will return the `Err` variant | |
134 | if the environment variable is not set. | |
13cf67c4 | 135 | |
04454e1e FG |
136 | We’re using the `is_ok` method on the `Result` to check whether the environment |
137 | variable is set, which means the program should do a case-insensitive search. | |
138 | If the `IGNORE_CASE` environment variable isn’t set to anything, `is_ok` will | |
139 | return false and the program will perform a case-sensitive search. We don’t | |
13cf67c4 | 140 | care about the *value* of the environment variable, just whether it’s set or |
04454e1e | 141 | unset, so we’re checking `is_ok` rather than using `unwrap`, `expect`, or any |
13cf67c4 XL |
142 | of the other methods we’ve seen on `Result`. |
143 | ||
04454e1e FG |
144 | We pass the value in the `ignore_case` variable to the `Config` instance so the |
145 | `run` function can read that value and decide whether to call | |
146 | `search_case_insensitive` or `search`, as we implemented in Listing 12-22. | |
13cf67c4 XL |
147 | |
148 | Let’s give it a try! First, we’ll run our program without the environment | |
149 | variable set and with the query `to`, which should match any line that contains | |
150 | the word “to” in all lowercase: | |
151 | ||
f035d41b | 152 | ```console |
74b04a01 | 153 | {{#include ../listings/ch12-an-io-project/listing-12-23/output.txt}} |
13cf67c4 XL |
154 | ``` |
155 | ||
04454e1e | 156 | Looks like that still works! Now, let’s run the program with `IGNORE_CASE` |
13cf67c4 XL |
157 | set to `1` but with the same query `to`. |
158 | ||
04454e1e | 159 | ```console |
923072b8 | 160 | $ IGNORE_CASE=1 cargo run -- to poem.txt |
04454e1e FG |
161 | ``` |
162 | ||
163 | If you’re using PowerShell, you will need to set the environment variable and | |
164 | run the program as separate commands: | |
13cf67c4 | 165 | |
f035d41b | 166 | ```console |
923072b8 | 167 | PS> $Env:IGNORE_CASE=1; cargo run -- to poem.txt |
f035d41b XL |
168 | ``` |
169 | ||
04454e1e | 170 | This will make `IGNORE_CASE` persist for the remainder of your shell |
f035d41b XL |
171 | session. It can be unset with the `Remove-Item` cmdlet: |
172 | ||
173 | ```console | |
04454e1e | 174 | PS> Remove-Item Env:IGNORE_CASE |
13cf67c4 XL |
175 | ``` |
176 | ||
177 | We should get lines that contain “to” that might have uppercase letters: | |
178 | ||
74b04a01 XL |
179 | <!-- manual-regeneration |
180 | cd listings/ch12-an-io-project/listing-12-23 | |
923072b8 | 181 | IGNORE_CASE=1 cargo run -- to poem.txt |
74b04a01 XL |
182 | can't extract because of the environment variable |
183 | --> | |
184 | ||
f035d41b | 185 | ```console |
13cf67c4 XL |
186 | Are you nobody, too? |
187 | How dreary to be somebody! | |
188 | To tell your name the livelong day | |
189 | To an admiring bog! | |
190 | ``` | |
191 | ||
192 | Excellent, we also got lines containing “To”! Our `minigrep` program can now do | |
193 | case-insensitive searching controlled by an environment variable. Now you know | |
194 | how to manage options set using either command line arguments or environment | |
195 | variables. | |
196 | ||
197 | Some programs allow arguments *and* environment variables for the same | |
198 | configuration. In those cases, the programs decide that one or the other takes | |
04454e1e FG |
199 | precedence. For another exercise on your own, try controlling case sensitivity |
200 | through either a command line argument or an environment variable. Decide | |
201 | whether the command line argument or the environment variable should take | |
202 | precedence if the program is run with one set to case sensitive and one set to | |
203 | ignore case. | |
13cf67c4 XL |
204 | |
205 | The `std::env` module contains many more useful features for dealing with | |
206 | environment variables: check out its documentation to see what is available. |