Module: Mmsh

Defined in:
lib/mmsh.rb

Defined Under Namespace

Classes: Cmd

Class Method Summary collapse

Class Method Details

.args(parts) ⇒ Object

Takes an array of the parts of a single command string and returns the

arguments portion.

See ::parts_from.


180
181
182
183
184
185
186
187
188
# File 'lib/mmsh.rb', line 180

def self.args(parts)
  end_index = [
    (parts.index('<') || parts.length + 1),
    (parts.index('>') || parts.length + 1),
    parts.length + 1
  ].compact.min - 1

  parts[1..end_index].join(' ')
end

.cmd_from(single_cmd_str) ⇒ Object

Takes a command string containing a single command, and returns a Cmd struct containing the parts in the command.

Ex:

 > Parser.cmd_from('baz < fizz.txt')
=> #<struct Cmd
      id="6ac8aa1c-fdc8-4a63-9b4b-8cd185bd0f40",
      name="baz",
      args="",
      input="fizz.txt",
      output=nil
    >


109
110
111
112
113
114
115
116
117
118
119
# File 'lib/mmsh.rb', line 109

def self.cmd_from(single_cmd_str)
  parts = parts_from(single_cmd_str)

  Cmd.new(
    SecureRandom.uuid,
    name(parts),
    args(parts),
    input(parts),
    output(parts)
  )
end

.input(parts) ⇒ Object

Takes an array of the parts of a single command string and returns the input redirection portion.

See ::parts_from.



195
196
197
198
# File 'lib/mmsh.rb', line 195

def self.input(parts)
  return nil unless parts.index('<')
  parts[parts.index('<') + 1]
end

.io_connect(cmd_list) ⇒ Object

Takes an array of Cmd structs and does two things with them:

For every Cmd:

  • Sets the Cmd’s #output attribute

  • Looks for uses of the pipe (‘|’) command, and where found, connect the output of the Cmd preceding the pipe to the input of the Cmd following the pipe.

The command string:

'foo | bar'

… will be split into Cmd structs that represent ‘foo’, ‘|’, and ‘bar’. This method notices that a pipe (‘|’) Cmd is being used, and connects the output of ‘foo’ to the input of ‘bar’.



137
138
139
140
141
142
143
144
145
# File 'lib/mmsh.rb', line 137

def self.io_connect(cmd_list)
  cmd_list[0][:output] ||= cmd_list[0][:id]
  cmd_list[1..-1].each.with_index do |c, i|
    c[:output] ||= c[:id]
    c[:input] ||= cmd_list[i - 1][:id] if cmd_list[i][:name].eql?('|')
  end

  cmd_list
end

.min_strip(cmd) ⇒ Object



55
56
57
58
59
# File 'lib/mmsh.rb', line 55

def self.min_strip(cmd)
  rpad = cmd.end_with?(' ') ? ' ' : ''

  "#{cmd.strip}#{rpad}"
end

.minimize(cmd) ⇒ Object

Takes a command string that may contain leading and/or trailing whitespace, as well as the line continuation character (‘'), and produces a single string with all of that stripped out. Multiple lines get combined, with continuation removed.

This:

>   foo \
> bar

Becomes:

'foo bar'

This:

>   foo\
>bar

Becomes:

'foobar'


43
44
45
46
47
# File 'lib/mmsh.rb', line 43

def self.minimize(cmd)
  min_strip(
    strip_continuation(cmd)
  )
end

.name(parts) ⇒ Object

Takes an array of the parts of a single command string and returns the name portion.

See ::parts_from.



171
172
173
# File 'lib/mmsh.rb', line 171

def self.name(parts)
  parts[0]
end

.output(parts) ⇒ Object

Takes an array of the parts of a single command string and returns the output redirection portion.

See ::parts_from.



205
206
207
208
# File 'lib/mmsh.rb', line 205

def self.output(parts)
  return nil unless parts.index('>')
  parts[parts.index('>') + 1]
end

.parse(multi_cmd_str) ⇒ Object

Takes a string captured from a CLI containing one or more commands, and returns an array of Cmd structs, fully connected and ready to be executed.

Ex:

 > Parser.parse('foo | bar; baz < fizz.txt')
=> [
     <Cmd name='foo', ... >,
     <Cmd name='|', ... >,
     <Cmd name='bar' ... >,
     <Cmd name=';' ... >,
     <Cmd name='baz', ... >
   ]


76
77
78
79
80
# File 'lib/mmsh.rb', line 76

def self.parse(multi_cmd_str)
  io_connect(
    subcmds(multi_cmd_str).map{|c| cmd_from(c) }
  )
end

.parts_from(single_cmd_str) ⇒ Object

Takes a single command string and returns the component parts as an array of strings.

Ex:

 > Parser.parts_from('foo bar1 bar2 bar3 < baz > fizz')
=> ["foo", "bar1", "bar2", "bar3", "<", "baz", ">", "fizz"]
    name   |----- arguments -----| |- input -| |- output -|

From this list, other supporting methods can extract the name, arguments, input redirection, and output redirection.



158
159
160
161
162
163
164
# File 'lib/mmsh.rb', line 158

def self.parts_from(single_cmd_str)
  single_cmd_str
    .split(/(<|>)/)
    .map{|p| p.strip }
    .map{|p| p.split }
    .flatten
end

.read(prompt) ⇒ Object



7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# File 'lib/mmsh.rb', line 7

def self.read(prompt)
  cmd_lines = []

  loop do
    cmd = Readline.readline("#{prompt} ", false).rstrip
    Readline::HISTORY.push(cmd) unless cmd.empty?
    cmd_lines << minimize(cmd)

    if cmd.end_with?('\\')
      next
    else
      break
    end
  end

  cmd_lines.join
end

.strip_continuation(cmd) ⇒ Object

Takes a command that may or may not contain line continuation, and removes it if it’s present.



51
52
53
# File 'lib/mmsh.rb', line 51

def self.strip_continuation(cmd)
  cmd.rstrip.end_with?('\\') ? cmd.rstrip[0..-2] : cmd.rstrip
end

.subcmds(multi_cmd_str) ⇒ Object

Takes a string captured from a CLI containing one or more commands, and returns an array of the commands within that string. Basically this splits on tokens that appear between commands.

Ex:

 > Parser.subcmds('foo | bar; baz < fizz.txt')
=> ["foo", "|", "bar", ";", "baz < fizz.txt"]


90
91
92
93
94
# File 'lib/mmsh.rb', line 90

def self.subcmds(multi_cmd_str)
  multi_cmd_str
    .split(/(&&|\*|\||;)/)
    .map{|cmd| cmd.strip }
end