linebook 0.7.0 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,226 @@
1
+ # Generated by Linecook
2
+
3
+ module Linebook
4
+ module Os
5
+ module Linux
6
+ module Utilities
7
+ # Returns true if the group exists as determined by checking /etc/group.
8
+ def group?(name)
9
+ # grep "^<%= name %>:" /etc/group >/dev/null 2>&1
10
+ write "grep \"^"; write(( name ).to_s); write ":\" /etc/group >/dev/null 2>&1"
11
+ chain_proxy
12
+ end
13
+
14
+ def _group?(*args, &block) # :nodoc:
15
+ str = capture_str { group?(*args, &block) }
16
+ str.strip!
17
+ str
18
+ end
19
+
20
+ # Create a new group.
21
+ # {[Spec]}[http://refspecs.linuxfoundation.org/LSB_4.1.0/LSB-Core-generic/LSB-Core-generic/groupadd.html]
22
+ def groupadd(group, options={})
23
+ execute 'groupadd', group, options
24
+ chain_proxy
25
+ end
26
+
27
+ def _groupadd(*args, &block) # :nodoc:
28
+ str = capture_str { groupadd(*args, &block) }
29
+ str.strip!
30
+ str
31
+ end
32
+
33
+ # Delete a group.
34
+ # {[Spec]}[http://refspecs.linuxfoundation.org/LSB_4.1.0/LSB-Core-generic/LSB-Core-generic/groupdel.html]
35
+ def groupdel(group)
36
+ execute 'groupdel', group
37
+ chain_proxy
38
+ end
39
+
40
+ def _groupdel(*args, &block) # :nodoc:
41
+ str = capture_str { groupdel(*args, &block) }
42
+ str.strip!
43
+ str
44
+ end
45
+
46
+ # Modify a group.
47
+ # {[Spec]}[http://refspecs.linuxfoundation.org/LSB_4.1.0/LSB-Core-generic/LSB-Core-generic/groupmod.html]
48
+ def groupmod(group, options={})
49
+ execute 'groupmod', group, options
50
+ chain_proxy
51
+ end
52
+
53
+ def _groupmod(*args, &block) # :nodoc:
54
+ str = capture_str { groupmod(*args, &block) }
55
+ str.strip!
56
+ str
57
+ end
58
+
59
+ # Display a group.
60
+ # {[Spec]}[http://refspecs.linuxfoundation.org/LSB_4.1.0/LSB-Core-generic/LSB-Core-generic/groups.html]
61
+ def groups(user)
62
+ execute 'groups', user
63
+ chain_proxy
64
+ end
65
+
66
+ def _groups(*args, &block) # :nodoc:
67
+ str = capture_str { groups(*args, &block) }
68
+ str.strip!
69
+ str
70
+ end
71
+
72
+ # Compress or expand files.
73
+ # {[Spec]}[http://refspecs.linuxfoundation.org/LSB_4.1.0/LSB-Core-generic/LSB-Core-generic/gzip.html]
74
+ def gzip(*files)
75
+ execute 'gzip', *files
76
+ chain_proxy
77
+ end
78
+
79
+ def _gzip(*args, &block) # :nodoc:
80
+ str = capture_str { gzip(*args, &block) }
81
+ str.strip!
82
+ str
83
+ end
84
+
85
+ # Show or set the system's host name.
86
+ # {[Spec]}[http://refspecs.linuxfoundation.org/LSB_4.1.0/LSB-Core-generic/LSB-Core-generic/hostname.html]
87
+ def hostname(name=nil)
88
+ execute 'hostname', name
89
+ chain_proxy
90
+ end
91
+
92
+ def _hostname(*args, &block) # :nodoc:
93
+ str = capture_str { hostname(*args, &block) }
94
+ str.strip!
95
+ str
96
+ end
97
+
98
+ # Copy files and set attributes.
99
+ # {[Spec]}[http://refspecs.linuxfoundation.org/LSB_4.1.0/LSB-Core-generic/LSB-Core-generic/install.html]
100
+ def install(source, dest, options={})
101
+ execute 'install', source, dest, options
102
+ chain_proxy
103
+ end
104
+
105
+ def _install(*args, &block) # :nodoc:
106
+ str = capture_str { install(*args, &block) }
107
+ str.strip!
108
+ str
109
+ end
110
+
111
+ # Generate or check MD5 message digests.
112
+ # {[Spec]}[http://refspecs.linuxfoundation.org/LSB_4.1.0/LSB-Core-generic/LSB-Core-generic/md5sum.html]
113
+ def md5sum(*files)
114
+ execute 'md5sum', *files
115
+ chain_proxy
116
+ end
117
+
118
+ def _md5sum(*args, &block) # :nodoc:
119
+ str = capture_str { md5sum(*args, &block) }
120
+ str.strip!
121
+ str
122
+ end
123
+
124
+ # Make temporary file name (unique)
125
+ # {[Spec]}[http://refspecs.linuxfoundation.org/LSB_4.1.0/LSB-Core-generic/LSB-Core-generic/mktemp.html]
126
+ def mktemp(template, options={})
127
+ execute 'mktemp', template, options
128
+ chain_proxy
129
+ end
130
+
131
+ def _mktemp(*args, &block) # :nodoc:
132
+ str = capture_str { mktemp(*args, &block) }
133
+ str.strip!
134
+ str
135
+ end
136
+
137
+ # Switches to the specified user for the duration of a block. The current ENV
138
+ # and pwd are preserved.
139
+ # {[Spec]}[http://refspecs.linuxfoundation.org/LSB_4.1.0/LSB-Core-generic/LSB-Core-generic/su.html]
140
+ def su(user='root', options={})
141
+ path = capture_script(options) do
142
+ functions.each_value do |function|
143
+ writeln function
144
+ end
145
+ yield
146
+ end
147
+ execute 'su', user, path, :m => true
148
+ chain_proxy
149
+ end
150
+
151
+ def _su(*args, &block) # :nodoc:
152
+ str = capture_str { su(*args, &block) }
153
+ str.strip!
154
+ str
155
+ end
156
+
157
+ # File archiver. {[Spec]}[http://pubs.opengroup.org/onlinepubs/007908799/xcu/tar.html]
158
+ def tar(key, *files)
159
+ execute 'tar', key, files
160
+ chain_proxy
161
+ end
162
+
163
+ def _tar(*args, &block) # :nodoc:
164
+ str = capture_str { tar(*args, &block) }
165
+ str.strip!
166
+ str
167
+ end
168
+
169
+ # Returns true if the user exists as determined by id.
170
+ def user?(name)
171
+ # id <%= quote(name) %> >/dev/null 2>&1
172
+ write "id "; write(( quote(name) ).to_s); write " >/dev/null 2>&1"
173
+ chain_proxy
174
+ end
175
+
176
+ def _user?(*args, &block) # :nodoc:
177
+ str = capture_str { user?(*args, &block) }
178
+ str.strip!
179
+ str
180
+ end
181
+
182
+ # Create a new user or update default new user information.
183
+ # {[Spec]}[http://refspecs.linuxfoundation.org/LSB_4.1.0/LSB-Core-generic/LSB-Core-generic/useradd.html]
184
+ def useradd(login, options={})
185
+ execute 'useradd', login, options
186
+ chain_proxy
187
+ end
188
+
189
+ def _useradd(*args, &block) # :nodoc:
190
+ str = capture_str { useradd(*args, &block) }
191
+ str.strip!
192
+ str
193
+ end
194
+
195
+ # Delete a user account and related files.
196
+ # {[Spec]}[http://refspecs.linuxfoundation.org/LSB_4.1.0/LSB-Core-generic/LSB-Core-generic/userdel.html]
197
+ def userdel(login, options={})
198
+ # TODO - look into other things that might need to happen before:
199
+ # * kill processes belonging to user
200
+ # * remove at/cron/print jobs etc.
201
+ execute 'userdel', login, options
202
+ chain_proxy
203
+ end
204
+
205
+ def _userdel(*args, &block) # :nodoc:
206
+ str = capture_str { userdel(*args, &block) }
207
+ str.strip!
208
+ str
209
+ end
210
+
211
+ # Modify a user account.
212
+ # {[Spec]}[http://refspecs.linuxfoundation.org/LSB_4.1.0/LSB-Core-generic/LSB-Core-generic/usermod.html]
213
+ def usermod(login, options={})
214
+ execute 'usermod', login, options
215
+ chain_proxy
216
+ end
217
+
218
+ def _usermod(*args, &block) # :nodoc:
219
+ str = capture_str { usermod(*args, &block) }
220
+ str.strip!
221
+ str
222
+ end
223
+ end
224
+ end
225
+ end
226
+ end
@@ -2,26 +2,33 @@
2
2
 
3
3
  module Linebook
4
4
  module Os
5
+ # Defines POSIX-compliant functionality, based on the {IEEE 1003.1-2008
6
+ # standard }[http://pubs.opengroup.org/onlinepubs/9699919799]. See the online
7
+ # documentation for:
8
+ #
9
+ # * {POSIX Shell Command Language
10
+ # }[http://pubs.opengroup.org/onlinepubs/9699919799/utilities/xcu_chap02.html]
11
+ # * {Special Built-in Utilities
12
+ # }[http://pubs.opengroup.org/onlinepubs/9699919799/idx/sbi.html]
13
+ # * {Standard Utilties
14
+ # }[http://pubs.opengroup.org/onlinepubs/9699919799/idx/utilities.html]
15
+ #
16
+ # In addition, the {Shell Hater's Handbook}[http://shellhaters.heroku.com/]
17
+ # provides a nice index of the relevant information.
18
+ #
5
19
  module Posix
6
- # Returns true if the obj converts to a string which is whitespace or empty.
7
- def blank?(obj)
8
- # shortcut for nil...
9
- obj.nil? || obj.to_s.strip.empty?
10
- end
20
+ require 'linebook/os/posix/variable'
21
+ require 'linebook/os/posix/utilities'
22
+ include Utilities
11
23
 
12
- # Encloses the arg in quotes if the arg is not quoted and is quotable.
13
- # Stringifies arg using to_s.
14
- def quote(arg)
15
- arg = arg.to_s
16
- quoted?(arg) || !quote?(arg) ? arg : "\"#{arg}\""
24
+ # Returns "$0", the program name.
25
+ def program_name
26
+ Variable.new(0)
17
27
  end
18
28
 
19
- # Returns true if the str is not an option (ie it begins with - or +), and is
20
- # not already quoted (either by quotes or apostrophes). The intention is to
21
- # check whether a string _should_ be quoted.
22
- def quote?(str)
23
- c = str[0]
24
- c == ?- || c == ?+ || quoted?(str) ? false : true
29
+ # Encloses the arg in quotes, unless already quoted (see quoted?).
30
+ def quote(str)
31
+ quoted?(str) ? str : "\"#{str}\""
25
32
  end
26
33
 
27
34
  # Returns true if the str is quoted (either by quotes or apostrophes).
@@ -29,15 +36,27 @@ module Linebook
29
36
  str =~ /\A".*"\z/ || str =~ /\A'.*'\z/ ? true : false
30
37
  end
31
38
 
39
+ # Encloses the arg in quotes unless the arg is an option or already quoted
40
+ # (see option? and quoted?).
41
+ def option_quote(str)
42
+ option?(str) ? str : quote(str)
43
+ end
44
+
45
+ # Returns true if the str is an option (ie it begins with - or +).
46
+ def option?(str)
47
+ c = str[0]
48
+ c == ?- || c == ?+
49
+ end
50
+
32
51
  # Formats a command line command. Arguments are quoted. If the last arg is a
33
52
  # hash, then it will be formatted into options using format_options and
34
53
  # prepended to args.
35
- def format_cmd(command, *args)
54
+ def command_str(command, *args)
36
55
  opts = args.last.kind_of?(Hash) ? args.pop : {}
37
56
  args.compact!
38
- args.collect! {|arg| quote(arg) }
57
+ args.collect! {|arg| option_quote(arg.to_s) }
39
58
 
40
- args = format_options(opts) + args
59
+ args = options_str(opts) + args
41
60
  args.unshift(command)
42
61
  args.join(' ')
43
62
  end
@@ -56,7 +75,7 @@ module Linebook
56
75
  # symbols) such that underscores are converted to dashes, ie :some_key =>
57
76
  # 'some-key'. Note that options are sorted, such that short options appear
58
77
  # after long options, and so should 'win' given typical option processing.
59
- def format_options(opts)
78
+ def options_str(opts)
60
79
  options = []
61
80
 
62
81
  opts.each do |(key, value)|
@@ -82,48 +101,66 @@ module Linebook
82
101
  options.sort
83
102
  end
84
103
 
85
- # An array of functions defined for self.
104
+ # A hash of functions defined for self.
86
105
  def functions
87
- @functions ||= []
106
+ @functions ||= {}
88
107
  end
89
108
 
90
109
  # Defines a function from the block. The block content is indented and
91
- # cleaned up some to make a nice function definition. To avoid formatting,
92
- # provide the body directly.
93
- #
94
- # A body and block given together raises an error. Raises an error if the
95
- # function is already defined with a different body.
96
- def function(name, body=nil)
97
- if body && block_given?
98
- raise "define functions with body or block"
99
- end
100
-
101
- if body.nil?
102
- str = capture_str { indent { yield } }
103
- body = "\n#{str.chomp("\n")}\n"
104
- end
105
-
106
- function = "#{name}() {#{body}}"
110
+ # cleaned up some to make a nice function definition.
111
+ def function(name, method_name=name)
112
+ str = capture_str { indent { yield(*signature(Proc.new.arity)) } }
113
+ function = %{#{name}() {\n#{str.chomp("\n")}\n}}
107
114
 
108
- if current = functions.find {|func| func.index("#{name}()") == 0 }
109
- if current != function
115
+ if function?(name)
116
+ unless functions[name] == function
110
117
  raise "function already defined: #{name.inspect}"
111
118
  end
119
+ else
120
+ functions[name] = function
121
+
122
+ if method_name
123
+ instance_eval %{
124
+ def self.#{method_name}(*args)
125
+ execute '#{method_name}', *args
126
+ chain_proxy
127
+ end
128
+ }
129
+ end
112
130
  end
113
131
 
114
- functions << function
115
132
  writeln function
116
-
117
133
  name
118
134
  end
119
135
 
120
- # Returns true if the function is defined.
136
+ # Returns true if a function with the given name is defined.
121
137
  def function?(name)
122
- declaration = "#{name}()"
123
- functions.any? {|func| func.index(declaration) == 0 }
138
+ functions.has_key?(name)
124
139
  end
125
140
 
126
- CHECK_STATUS = /(\s*(?:\ncheck_status.*?\n\s*)?)\z/
141
+ # Returns an array of positional variables for use as inputs to a function
142
+ # block. Splat blocks are supported; the splat expression behaves like $*.
143
+ def signature(arity)
144
+ variables = Array.new(arity.abs) {|i| var(i+1) }
145
+
146
+ if arity < 0
147
+ # This works for defaults...
148
+ # $(shift 1; echo ${*:-NONE})
149
+ # You can't do this:
150
+ # ${$(shift 1; echo $*):-NONE}
151
+ variables[-1] = "$(shift #{arity.abs - 1}; echo $*)"
152
+ end
153
+
154
+ variables
155
+ end
156
+
157
+ def var(name)
158
+ Variable.new(name)
159
+ end
160
+
161
+ def trailer
162
+ /(\s*(?:\ncheck_status.*?\n\s*)?)\z/
163
+ end
127
164
 
128
165
  # Adds a redirect to append stdout to a file.
129
166
  def append(path=nil)
@@ -137,6 +174,22 @@ module Linebook
137
174
  str
138
175
  end
139
176
 
177
+ # Exit from for, while, or until loop.
178
+ # {[Spec]}[http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_15]
179
+ def break_()
180
+ # break
181
+ #
182
+ write "break\n"
183
+
184
+ chain_proxy
185
+ end
186
+
187
+ def _break_(*args, &block) # :nodoc:
188
+ str = capture_str { break_(*args, &block) }
189
+ str.strip!
190
+ str
191
+ end
192
+
140
193
  # Adds a check that ensures the last exit status is as indicated. Note that no
141
194
  # check will be added unless check_status_function is added beforehand.
142
195
  def check_status(expect_status=0, fail_status='$?')
@@ -157,9 +210,20 @@ module Linebook
157
210
  str
158
211
  end
159
212
 
160
- # Adds the check status function.
213
+ # Defines the check status function.
161
214
  def check_status_function()
162
- function 'check_status', ' if [ $2 -ne $1 ]; then echo "[$2] $0:${4:-?}"; exit $3; else return $2; fi '
215
+ function('check_status', nil) do |expected, actual, error, message|
216
+ message.default = '?'
217
+
218
+ if_ actual.ne(expected) do
219
+ writeln %{echo [#{actual}] #{program_name}:#{message}}
220
+ exit_ error
221
+ end
222
+
223
+ else_ do
224
+ return_ actual
225
+ end
226
+ end
163
227
  chain_proxy
164
228
  end
165
229
 
@@ -184,18 +248,80 @@ module Linebook
184
248
  str
185
249
  end
186
250
 
187
- # Executes a command and checks the output status. Quotes all non-option args
251
+ # Continue for, while, or until loop.
252
+ # {[Spec]}[http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_17]
253
+ def continue_()
254
+ # continue
255
+ #
256
+ write "continue\n"
257
+
258
+ chain_proxy
259
+ end
260
+
261
+ def _continue_(*args, &block) # :nodoc:
262
+ str = capture_str { continue_(*args, &block) }
263
+ str.strip!
264
+ str
265
+ end
266
+
267
+ # Chains to if_ to make an else-if statement.
268
+ def elif_(expression)
269
+ unless match = rewrite(/(\s+)(fi\s*)/)
270
+ raise "elif_ used outside of if_ statement"
271
+ end
272
+ # <%= match[1] %>
273
+ # elif <%= expression %>
274
+ # then
275
+ # <% indent { yield } %>
276
+ # <%= match[2] %>
277
+ write(( match[1] ).to_s)
278
+ write "elif "; write(( expression ).to_s); write "\n"
279
+ write "then\n"
280
+ indent { yield }
281
+ write(( match[2] ).to_s)
282
+ chain_proxy
283
+ end
284
+
285
+ def _elif_(*args, &block) # :nodoc:
286
+ str = capture_str { elif_(*args, &block) }
287
+ str.strip!
288
+ str
289
+ end
290
+
291
+ # Chains to if_ or unless_ to make an else statement.
292
+ def else_()
293
+ unless match = rewrite(/(\s+)(fi\s*)/)
294
+ raise "else_ used outside of if_ statement"
295
+ end
296
+ # <%= match[1] %>
297
+ # else
298
+ # <% indent { yield } %>
299
+ # <%= match[2] %>
300
+ write(( match[1] ).to_s)
301
+ write "else\n"
302
+ indent { yield }
303
+ write(( match[2] ).to_s)
304
+ chain_proxy
305
+ end
306
+
307
+ def _else_(*args, &block) # :nodoc:
308
+ str = capture_str { else_(*args, &block) }
309
+ str.strip!
310
+ str
311
+ end
312
+
313
+ # Executes a command and checks the output status. Quotes all non-option args
188
314
  # that aren't already quoted. Accepts a trailing hash which will be transformed
189
315
  # into command line options.
190
316
  def execute(command, *args)
191
317
  if chain?
192
- rewrite(CHECK_STATUS)
318
+ rewrite(trailer)
193
319
  write ' | '
194
320
  end
195
- # <%= format_cmd(command, *args) %>
321
+ # <%= command_str(command, *args) %>
196
322
  #
197
323
  # <% check_status %>
198
- write(( format_cmd(command, *args) ).to_s)
324
+ write(( command_str(command, *args) ).to_s)
199
325
  write "\n"
200
326
  check_status
201
327
  chain_proxy
@@ -207,17 +333,26 @@ module Linebook
207
333
  str
208
334
  end
209
335
 
210
- # Exports a variable.
211
- def export(key, value)
212
- # export <%= key %>=<%= quote(value) %>
336
+ # Cause the shell to exit.
337
+ # {[Spec]}[http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_21]
338
+ def exit_(status=nil)
339
+ # <% if status.nil? %>
340
+ # exit
341
+ # <% else %>
342
+ # exit <%= status %>
343
+ # <% end %>
213
344
  #
214
- write "export "; write(( key ).to_s); write "="; write(( quote(value) ).to_s); write "\n"
345
+ if status.nil?
346
+ write "exit\n"
347
+ else
348
+ write "exit "; write(( status ).to_s); write "\n"
349
+ end
215
350
 
216
351
  chain_proxy
217
352
  end
218
353
 
219
- def _export(*args, &block) # :nodoc:
220
- str = capture_str { export(*args, &block) }
354
+ def _exit_(*args, &block) # :nodoc:
355
+ str = capture_str { exit_(*args, &block) }
221
356
  str.strip!
222
357
  str
223
358
  end
@@ -240,7 +375,7 @@ module Linebook
240
375
  # outdent add '-' before the delimiter
241
376
  # quote quotes the delimiter
242
377
  def heredoc(options={})
243
- tail = chain? ? rewrite(CHECK_STATUS) {|m| write ' '; m[1].lstrip } : nil
378
+ tail = chain? ? rewrite(trailer) {|m| write ' '; m[1].lstrip } : nil
244
379
 
245
380
  unless options.kind_of?(Hash)
246
381
  options = {:delimiter => options}
@@ -255,11 +390,13 @@ module Linebook
255
390
  # <%= delimiter %><% end %>
256
391
  #
257
392
  # <%= tail %>
393
+ #
258
394
  write "<<"; write(( options[:outdent] ? '-' : ' ').to_s); write(( options[:quote] ? "\"#{delimiter}\"" : delimiter ).to_s); outdent(" # :#{delimiter}:") do ; write "\n"
259
395
  yield
260
396
  write(( delimiter ).to_s); end
261
397
  write "\n"
262
398
  write(( tail ).to_s)
399
+
263
400
  chain_proxy
264
401
  end
265
402
 
@@ -297,7 +434,7 @@ module Linebook
297
434
  source = source.nil? || source.kind_of?(Fixnum) ? source : "#{source} "
298
435
  target = target.nil? || target.kind_of?(Fixnum) ? "&#{target}" : " #{target}"
299
436
 
300
- match = chain? ? rewrite(CHECK_STATUS) : nil
437
+ match = chain? ? rewrite(trailer) : nil
301
438
  write " #{source}#{redirection}#{target}"
302
439
  write match[1] if match
303
440
  chain_proxy
@@ -309,21 +446,45 @@ module Linebook
309
446
  str
310
447
  end
311
448
 
312
- # Sets the options to on (true) or off (false) as specified.
313
- def set(options)
314
- # <% options.keys.sort_by {|opt| opt.to_s }.each do |opt| %>
315
- # set <%= options[opt] ? '-' : '+' %>o <%= opt %>
449
+ # Return from a function.
450
+ # {[Spec]}[http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_24]
451
+ def return_(status=nil)
452
+ # <% if status.nil? %>
453
+ # return
454
+ # <% else %>
455
+ # return <%= status %>
316
456
  # <% end %>
317
457
  #
318
- options.keys.sort_by {|opt| opt.to_s }.each do |opt|
319
- write "set "; write(( options[opt] ? '-' : '+' ).to_s); write "o "; write(( opt ).to_s); write "\n"
458
+ if status.nil?
459
+ write "return\n"
460
+ else
461
+ write "return "; write(( status ).to_s); write "\n"
320
462
  end
321
463
 
322
464
  chain_proxy
323
465
  end
324
466
 
325
- def _set(*args, &block) # :nodoc:
326
- str = capture_str { set(*args, &block) }
467
+ def _return_(*args, &block) # :nodoc:
468
+ str = capture_str { return_(*args, &block) }
469
+ str.strip!
470
+ str
471
+ end
472
+
473
+ # Write a comment to delimit sections. The comment takes the format:
474
+ #
475
+ # #### name ###
476
+ def section(name="")
477
+ n = (78 - name.length)/2
478
+ str = "-" * n
479
+ # #<%= str %><%= name %><%= str %><%= "-" if name.length % 2 == 1 %>
480
+ #
481
+ write "#"; write(( str ).to_s); write(( name ).to_s); write(( str ).to_s); write(( "-" if name.length % 2 == 1 ).to_s); write "\n"
482
+
483
+ chain_proxy
484
+ end
485
+
486
+ def _section(*args, &block) # :nodoc:
487
+ str = capture_str { section(*args, &block) }
327
488
  str.strip!
328
489
  str
329
490
  end
@@ -352,19 +513,25 @@ module Linebook
352
513
  str
353
514
  end
354
515
 
355
- # Unsets a list of variables.
356
- def unset(*keys)
357
- # <% keys.each do |key| %>
358
- # unset <%= key %>
359
- # <% end %>
360
- keys.each do |key|
361
- write "unset "; write(( key ).to_s); write "\n"
362
- end
516
+ # Executes the block until the expression evaluates to zero.
517
+ def until_(expression)
518
+ # until <%= expression %>
519
+ # do
520
+ # <% indent { yield } %>
521
+ # done
522
+ #
523
+ #
524
+ write "until "; write(( expression ).to_s); write "\n"
525
+ write "do\n"
526
+ indent { yield }
527
+ write "done\n"
528
+ write "\n"
529
+
363
530
  chain_proxy
364
531
  end
365
532
 
366
- def _unset(*args, &block) # :nodoc:
367
- str = capture_str { unset(*args, &block) }
533
+ def _until_(*args, &block) # :nodoc:
534
+ str = capture_str { until_(*args, &block) }
368
535
  str.strip!
369
536
  str
370
537
  end
@@ -385,6 +552,29 @@ module Linebook
385
552
  str.strip!
386
553
  str
387
554
  end
555
+
556
+ # Executes the block while the expression evaluates to zero.
557
+ def while_(expression)
558
+ # while <%= expression %>
559
+ # do
560
+ # <% indent { yield } %>
561
+ # done
562
+ #
563
+ #
564
+ write "while "; write(( expression ).to_s); write "\n"
565
+ write "do\n"
566
+ indent { yield }
567
+ write "done\n"
568
+ write "\n"
569
+
570
+ chain_proxy
571
+ end
572
+
573
+ def _while_(*args, &block) # :nodoc:
574
+ str = capture_str { while_(*args, &block) }
575
+ str.strip!
576
+ str
577
+ end
388
578
  end
389
579
  end
390
580
  end