Skip to content

Instantly share code, notes, and snippets.

@bayleedev
Last active August 29, 2015 13:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bayleedev/9003529 to your computer and use it in GitHub Desktop.
Save bayleedev/9003529 to your computer and use it in GitHub Desktop.
Parses/transforms terminal (specifically git) commands using boolean logic.
require 'parslet'
require 'pry'
class Command < Struct.new(:cmd, :args)
def initializer(cmd, args = [])
@cmd = cmd
@args = args
end
def execute
p String(cmd)
true
end
end
module Mini
class Transformer < Parslet::Transform
rule(:multi => { :left => subtree(:left), :right => subtree(:right) }) do
[[left.flatten + right.flatten]]
end
rule(git_cmd: simple(:cmd)) do |context|
[[Command.new(context[:cmd])]]
end
rule(git_cmd: simple(:cmd), args: subtree(:args)) do |context|
[[Command.new(context[:cmd], context[:args])]]
end
rule(arg: subtree(:parts)) do
parts.map { |p| p[:literal] }.join
end
rule(:or => { :left => subtree(:left), :right => subtree(:right) }) do
left + right
end
rule(:and => { :left => subtree(:left), :right => subtree(:right) }) do
res = []
left.each do |l|
right.each do |r|
res << (l + r)
end
end
res
end
end
class Parser < Parslet::Parser
root(:or_operation)
# Pretty
rule(:space) do
match('\s').repeat(1)
end
# Operators
rule(:and_operator) do
str("&&") >> space.maybe
end
rule(:or_operator) do
str("||") >> space.maybe
end
rule(:semicolon) do
str(";") >> space.maybe
end
# Operations
rule(:and_operation) do
(
multi_command.as(:left) >>
and_operator >>
and_operation.as(:right)
).as(:and) | multi_command
end
rule(:or_operation) do
(
and_operation.as(:left) >>
or_operator >>
or_operation.as(:right)
).as(:or) | and_operation
end
# Command
rule(:multi_command) do
(
command.as(:left) >>
semicolon >>
multi_command.as(:right)
).as(:multi) | command
end
rule(:command) do
space.maybe >> command_identifier >> argument_list.maybe >> space.maybe
end
# Sub-command
rule(:command_identifier) do
(str(':') >> identifier.as(:internal_cmd)) | identifier.as(:git_cmd)
end
rule(:identifier) do
match('[a-z]') >> match('[a-z0-9.-]').repeat(0)
end
# Arguments
rule(:argument_list) do
(space >> argument).repeat(1).as(:args)
end
rule(:argument) do
(soft_string | hard_string | unquoted_string).repeat(1).as(:arg)
end
rule(:unquoted_string) do
( variable | match(%q([^\s'"&|;])).as(:literal) ).repeat(1)
end
rule(:soft_string) do
str('"') >> (
(str('\\') >> match('[$"\\\]').as(:literal)) |
variable |
(str('"').absent? >> any).as(:literal)
).repeat(0) >> str('"')
end
rule(:hard_string) do
str("'") >> (str("'").absent? >> any).as(:literal).repeat(0) >> str("'")
end
# Meh
rule(:variable) do
str('$') >> (
str('{') >> identifier.as(:var) >> str('}') |
identifier.as(:var)
)
end
end
class CommandRunner
def initialize(tree)
@tree = tree
end
# Executes all the OR logic breaking on the first `true`-ish value
def each(&block)
@tree.each do |and_operation|
break if and_block(and_operation, &block)
end
end
protected
# Executes all the && logic breaking on the first `false`-ish value
def and_block(and_operation, &block)
and_operation.inject(true) do |memo, commands|
break(false) unless execute_commands(commands, &block)
true
end
end
# Executes commands or series of commands (separated by semicolons)
def execute_commands(commands, &block)
Array([commands]).flatten.inject(true) do |memo, command|
block.call(command)
end
end
end
end
tree = Mini::Parser.new.parse("add -p && commit -v || reset --soft HEAD")
runner = Mini::CommandRunner.new(Mini::Transformer.new.apply(tree))
runner.each do |command|
command.execute
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment