Merge lp:~vila/bzr/bool-config-option into lp:~bzr/bzr/trunk-old
- bool-config-option
- Merge into trunk-old
Status: | Merged |
---|---|
Approved by: | Ian Clatworthy |
Approved revision: | no longer in the source branch. |
Merged at revision: | not available |
Proposed branch: | lp:~vila/bzr/bool-config-option |
Merge into: | lp:~bzr/bzr/trunk-old |
Diff against target: | 312 lines |
To merge this branch: | bzr merge lp:~vila/bzr/bool-config-option |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Ian Clatworthy | Approve | ||
Review via email: mp+8180@code.launchpad.net |
This proposal supersedes a proposal from 2009-07-02.
Commit message
Description of the change
Vincent Ladeuil (vila) wrote : Posted in a previous version of this proposal | # |
John A Meinel (jameinel) wrote : Posted in a previous version of this proposal | # |
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1
Vincent Ladeuil wrote:
> Vincent Ladeuil has proposed merging lp:~vila/bzr/bool-config-option into lp:bzr.
>
> Requested reviews:
> bzr-core (bzr-core)
>
> This patch adds the ability to interpret a configuration as a boolean.
>
> It can be viewed (or reviewed :) as:
> - add a bool_from_string() function,
> - use that function to define config.
> - use that function in ui.get_boolean
> - use config.
>
> Regarding the discussion about defining a new method when we add a new configuration variable,
> my opinion is that it's not always worth it.
>
> The --strict option for push and send can be defined anywhere, I prefer to handle the
> default value in the commands themselves because:
> - that's the only place they are used,
> - that's the place that is targeted by the tests
>
Though it also makes it hard to know the full list of config items that
one could possibly set without using "grep -rnI get_user_option bzrlib"
Anyway, this patch would probably be fine if lp:mad wasn't crazy.
review resubmit
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (Cygwin)
Comment: Using GnuPG with Mozilla - http://
iEYEARECAAYFAkp
idgAn3xS0cIKF70
=O5+R
-----END PGP SIGNATURE-----
Ian Clatworthy (ian-clatworthy) wrote : | # |
Looks all ok to me. I'd like to suggest though that the rstrip('\n') call in ui/__init__.py omit the parameter and default to stripping all trailing whitespace. That might work better on Windows? (Either way, I think trailing whitespace is harmless.)
Preview Diff
1 | === modified file 'bzrlib/builtins.py' |
2 | --- bzrlib/builtins.py 2009-07-02 21:17:35 +0000 |
3 | +++ bzrlib/builtins.py 2009-07-09 07:35:50 +0000 |
4 | @@ -1096,24 +1096,15 @@ |
5 | (tree, br_from, |
6 | _unused) = bzrdir.BzrDir.open_containing_tree_or_branch(directory) |
7 | if strict is None: |
8 | - strict = br_from.get_config().get_user_option('push_strict') |
9 | - if strict is not None: |
10 | - # FIXME: This should be better supported by config |
11 | - # -- vila 20090611 |
12 | - bools = dict(yes=True, no=False, on=True, off=False, |
13 | - true=True, false=False) |
14 | - try: |
15 | - strict = bools[strict.lower()] |
16 | - except KeyError: |
17 | - strict = None |
18 | + strict = br_from.get_config().get_user_option_as_bool('push_strict') |
19 | + if strict is None: strict = True # default value |
20 | # Get the tip's revision_id |
21 | revision = _get_one_revision('push', revision) |
22 | if revision is not None: |
23 | revision_id = revision.in_history(br_from).rev_id |
24 | else: |
25 | revision_id = None |
26 | - if (tree is not None and revision_id is None |
27 | - and (strict is None or strict)): # Default to True: |
28 | + if strict and tree is not None and revision_id is None: |
29 | changes = tree.changes_from(tree.basis_tree()) |
30 | if changes.has_changed() or len(tree.get_parent_ids()) > 1: |
31 | raise errors.UncommittedChanges( |
32 | |
33 | === modified file 'bzrlib/config.py' |
34 | --- bzrlib/config.py 2009-06-11 06:49:21 +0000 |
35 | +++ bzrlib/config.py 2009-07-09 07:35:50 +0000 |
36 | @@ -146,6 +146,9 @@ |
37 | class Config(object): |
38 | """A configuration policy - what username, editor, gpg needs etc.""" |
39 | |
40 | + def __init__(self): |
41 | + super(Config, self).__init__() |
42 | + |
43 | def get_editor(self): |
44 | """Get the users pop up editor.""" |
45 | raise NotImplementedError |
46 | @@ -174,6 +177,15 @@ |
47 | """Get a generic option - no special process, no default.""" |
48 | return self._get_user_option(option_name) |
49 | |
50 | + def get_user_option_as_bool(self, option_name): |
51 | + """Get a generic option as a boolean - no special process, no default. |
52 | + |
53 | + :return None if the option doesn't exist or its value can't be |
54 | + interpreted as a boolean. Returns True or False ortherwise. |
55 | + """ |
56 | + s = self._get_user_option(option_name) |
57 | + return ui.bool_from_string(s) |
58 | + |
59 | def gpg_signing_command(self): |
60 | """What program should be used to sign signatures?""" |
61 | result = self._gpg_signing_command() |
62 | @@ -196,9 +208,6 @@ |
63 | """See log_format().""" |
64 | return None |
65 | |
66 | - def __init__(self): |
67 | - super(Config, self).__init__() |
68 | - |
69 | def post_commit(self): |
70 | """An ordered list of python functions to call. |
71 | |
72 | @@ -299,6 +308,11 @@ |
73 | class IniBasedConfig(Config): |
74 | """A configuration policy that draws from ini files.""" |
75 | |
76 | + def __init__(self, get_filename): |
77 | + super(IniBasedConfig, self).__init__() |
78 | + self._get_filename = get_filename |
79 | + self._parser = None |
80 | + |
81 | def _get_parser(self, file=None): |
82 | if self._parser is not None: |
83 | return self._parser |
84 | @@ -381,11 +395,6 @@ |
85 | """See Config.log_format.""" |
86 | return self._get_user_option('log_format') |
87 | |
88 | - def __init__(self, get_filename): |
89 | - super(IniBasedConfig, self).__init__() |
90 | - self._get_filename = get_filename |
91 | - self._parser = None |
92 | - |
93 | def _post_commit(self): |
94 | """See Config.post_commit.""" |
95 | return self._get_user_option('post_commit') |
96 | |
97 | === modified file 'bzrlib/send.py' |
98 | --- bzrlib/send.py 2009-07-01 15:17:33 +0000 |
99 | +++ bzrlib/send.py 2009-07-09 07:35:50 +0000 |
100 | @@ -108,17 +108,10 @@ |
101 | base_revision_id = revision[0].as_revision_id(branch) |
102 | if revision_id is None: |
103 | if strict is None: |
104 | - strict = branch.get_config().get_user_option('send_strict') |
105 | - if strict is not None: |
106 | - # FIXME: This should be better supported by config |
107 | - # -- vila 20090626 |
108 | - bools = dict(yes=True, no=False, on=True, off=False, |
109 | - true=True, false=False) |
110 | - try: |
111 | - strict = bools[strict.lower()] |
112 | - except KeyError: |
113 | - strict = None |
114 | - if tree is not None and (strict is None or strict): |
115 | + strict = branch.get_config( |
116 | + ).get_user_option_as_bool('send_strict') |
117 | + if strict is None: strict = True # default value |
118 | + if strict and tree is not None: |
119 | changes = tree.changes_from(tree.basis_tree()) |
120 | if changes.has_changed() or len(tree.get_parent_ids()) > 1: |
121 | raise errors.UncommittedChanges( |
122 | |
123 | === modified file 'bzrlib/tests/test_config.py' |
124 | --- bzrlib/tests/test_config.py 2009-06-29 12:14:09 +0000 |
125 | +++ bzrlib/tests/test_config.py 2009-07-09 07:35:50 +0000 |
126 | @@ -367,6 +367,20 @@ |
127 | parser = my_config._get_parser(file=config_file) |
128 | self.failUnless(my_config._get_parser() is parser) |
129 | |
130 | + def test_get_user_option_as_bool(self): |
131 | + config_file = StringIO(""" |
132 | +a_true_bool = true |
133 | +a_false_bool = 0 |
134 | +an_invalid_bool = maybe |
135 | +a_list = hmm, who knows ? # This interpreted as a list ! |
136 | +""".encode('utf-8')) |
137 | + my_config = config.IniBasedConfig(None) |
138 | + parser = my_config._get_parser(file=config_file) |
139 | + get_option = my_config.get_user_option_as_bool |
140 | + self.assertEqual(True, get_option('a_true_bool')) |
141 | + self.assertEqual(False, get_option('a_false_bool')) |
142 | + self.assertIs(None, get_option('an_invalid_bool')) |
143 | + self.assertIs(None, get_option('not_defined_in_this_config')) |
144 | |
145 | class TestGetConfig(tests.TestCase): |
146 | |
147 | |
148 | === modified file 'bzrlib/tests/test_ui.py' |
149 | --- bzrlib/tests/test_ui.py 2009-06-30 05:34:47 +0000 |
150 | +++ bzrlib/tests/test_ui.py 2009-07-09 07:35:50 +0000 |
151 | @@ -174,21 +174,22 @@ |
152 | pb1.finished() |
153 | |
154 | def assert_get_bool_acceptance_of_user_input(self, factory): |
155 | - factory.stdin = StringIO("y\nyes with garbage\n" |
156 | - "yes\nn\nnot an answer\n" |
157 | - "no\n" |
158 | - "N\nY\n" |
159 | - "foo\n" |
160 | - ) |
161 | + factory.stdin = StringIO("y\n" # True |
162 | + "n\n" # False |
163 | + "yes with garbage\nY\n" # True |
164 | + "not an answer\nno\n" # False |
165 | + "I'm sure!\nyes\n" # True |
166 | + "NO\n" # False |
167 | + "foo\n") |
168 | factory.stdout = StringIO() |
169 | factory.stderr = StringIO() |
170 | # there is no output from the base factory |
171 | self.assertEqual(True, factory.get_boolean("")) |
172 | - self.assertEqual(True, factory.get_boolean("")) |
173 | - self.assertEqual(False, factory.get_boolean("")) |
174 | - self.assertEqual(False, factory.get_boolean("")) |
175 | - self.assertEqual(False, factory.get_boolean("")) |
176 | - self.assertEqual(True, factory.get_boolean("")) |
177 | + self.assertEqual(False, factory.get_boolean("")) |
178 | + self.assertEqual(True, factory.get_boolean("")) |
179 | + self.assertEqual(False, factory.get_boolean("")) |
180 | + self.assertEqual(True, factory.get_boolean("")) |
181 | + self.assertEqual(False, factory.get_boolean("")) |
182 | self.assertEqual("foo\n", factory.stdin.read()) |
183 | # stdin should be empty |
184 | self.assertEqual('', factory.stdin.readline()) |
185 | @@ -355,3 +356,58 @@ |
186 | r'[####| ] a:b:c 1/2' |
187 | , uif._progress_view._render_line()) |
188 | |
189 | + |
190 | +class TestBoolFromString(tests.TestCase): |
191 | + |
192 | + def assertIsTrue(self, s, accepted_values=None): |
193 | + res = _mod_ui.bool_from_string(s, accepted_values=accepted_values) |
194 | + self.assertEquals(True, res) |
195 | + |
196 | + def assertIsFalse(self, s, accepted_values=None): |
197 | + res = _mod_ui.bool_from_string(s, accepted_values=accepted_values) |
198 | + self.assertEquals(False, res) |
199 | + |
200 | + def assertIsNone(self, s, accepted_values=None): |
201 | + res = _mod_ui.bool_from_string(s, accepted_values=accepted_values) |
202 | + self.assertIs(None, res) |
203 | + |
204 | + def test_know_valid_values(self): |
205 | + self.assertIsTrue('true') |
206 | + self.assertIsFalse('false') |
207 | + self.assertIsTrue('1') |
208 | + self.assertIsFalse('0') |
209 | + self.assertIsTrue('on') |
210 | + self.assertIsFalse('off') |
211 | + self.assertIsTrue('yes') |
212 | + self.assertIsFalse('no') |
213 | + self.assertIsTrue('y') |
214 | + self.assertIsFalse('n') |
215 | + # Also try some case variations |
216 | + self.assertIsTrue('True') |
217 | + self.assertIsFalse('False') |
218 | + self.assertIsTrue('On') |
219 | + self.assertIsFalse('Off') |
220 | + self.assertIsTrue('ON') |
221 | + self.assertIsFalse('OFF') |
222 | + self.assertIsTrue('oN') |
223 | + self.assertIsFalse('oFf') |
224 | + |
225 | + def test_invalid_values(self): |
226 | + self.assertIsNone(None) |
227 | + self.assertIsNone('doubt') |
228 | + self.assertIsNone('frue') |
229 | + self.assertIsNone('talse') |
230 | + self.assertIsNone('42') |
231 | + |
232 | + def test_provided_values(self): |
233 | + av = dict(y=True, n=False, yes=True, no=False) |
234 | + self.assertIsTrue('y', av) |
235 | + self.assertIsTrue('Y', av) |
236 | + self.assertIsTrue('Yes', av) |
237 | + self.assertIsFalse('n', av) |
238 | + self.assertIsFalse('N', av) |
239 | + self.assertIsFalse('No', av) |
240 | + self.assertIsNone('1', av) |
241 | + self.assertIsNone('0', av) |
242 | + self.assertIsNone('on', av) |
243 | + self.assertIsNone('off', av) |
244 | |
245 | === modified file 'bzrlib/ui/__init__.py' |
246 | --- bzrlib/ui/__init__.py 2009-06-30 05:34:47 +0000 |
247 | +++ bzrlib/ui/__init__.py 2009-07-09 07:35:50 +0000 |
248 | @@ -43,6 +43,42 @@ |
249 | """) |
250 | |
251 | |
252 | +_valid_boolean_strings = dict(yes=True, no=False, |
253 | + y=True, n=False, |
254 | + on=True, off=False, |
255 | + true=True, false=False) |
256 | +_valid_boolean_strings['1'] = True |
257 | +_valid_boolean_strings['0'] = False |
258 | + |
259 | + |
260 | +def bool_from_string(s, accepted_values=None): |
261 | + """Returns a boolean if the string can be interpreted as such. |
262 | + |
263 | + Interpret case insensitive strings as booleans. The default values |
264 | + includes: 'yes', 'no, 'y', 'n', 'true', 'false', '0', '1', 'on', |
265 | + 'off'. Alternative values can be provided with the 'accepted_values' |
266 | + parameter. |
267 | + |
268 | + :param s: A string that should be interpreted as a boolean. It should be of |
269 | + type string or unicode. |
270 | + |
271 | + :param accepted_values: An optional dict with accepted strings as keys and |
272 | + True/False as values. The strings will be tested against a lowered |
273 | + version of 's'. |
274 | + |
275 | + :return: True or False for accepted strings, None otherwise. |
276 | + """ |
277 | + if accepted_values is None: |
278 | + accepted_values = _valid_boolean_strings |
279 | + val = None |
280 | + if type(s) in (str, unicode): |
281 | + try: |
282 | + val = accepted_values[s.lower()] |
283 | + except KeyError: |
284 | + pass |
285 | + return val |
286 | + |
287 | + |
288 | class UIFactory(object): |
289 | """UI abstraction. |
290 | |
291 | @@ -156,15 +192,16 @@ |
292 | self.stdout = stdout or sys.stdout |
293 | self.stderr = stderr or sys.stderr |
294 | |
295 | + _accepted_boolean_strings = dict(y=True, n=False, yes=True, no=False) |
296 | + |
297 | def get_boolean(self, prompt): |
298 | while True: |
299 | self.prompt(prompt + "? [y/n]: ") |
300 | line = self.stdin.readline() |
301 | - line = line.lower() |
302 | - if line in ('y\n', 'yes\n'): |
303 | - return True |
304 | - if line in ('n\n', 'no\n'): |
305 | - return False |
306 | + line = line.rstrip('\n') |
307 | + val = bool_from_string(line, self._accepted_boolean_strings) |
308 | + if val is not None: |
309 | + return val |
310 | |
311 | def get_non_echoed_password(self): |
312 | isatty = getattr(self.stdin, 'isatty', None) |
This patch adds the ability to interpret a configuration as a boolean.
It can be viewed (or reviewed :) as: get_user_ option_ as_bool( ) get_user_ option_ as_bool( ) where needed
- add a bool_from_string() function,
- use that function to define config.
- use that function in ui.get_boolean
- use config.
Regarding the discussion about defining a new method when we add a new configuration variable,
my opinion is that it's not always worth it.
The --strict option for push and send can be defined anywhere, I prefer to handle the
default value in the commands themselves because:
- that's the only place they are used,
- that's the place that is targeted by the tests