linebook 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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