the_dude 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +3 -0
- data/.rspec +1 -0
- data/.rvmrc +1 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +55 -0
- data/README.md +40 -0
- data/Rakefile +44 -0
- data/bin/dude +35 -0
- data/features/basic.feature +13 -0
- data/features/duderc.feature +27 -0
- data/features/step_definitions/the_dude_steps.rb +8 -0
- data/features/support/env.rb +15 -0
- data/lib/the_dude.rb +128 -0
- data/lib/the_dude/command.rb +61 -0
- data/lib/the_dude/config.rb +5 -0
- data/lib/the_dude/dsl.rb +7 -0
- data/lib/the_dude/expression.rb +66 -0
- data/lib/the_dude/http.rb +28 -0
- data/lib/the_dude/setup.rb +21 -0
- data/lib/the_dude/variable.rb +25 -0
- data/lib/the_dude/version.rb +3 -0
- data/spec/spec_helper.rb +11 -0
- data/spec/support/webmocks.rb +31 -0
- data/spec/support/webmocks/google2.html +92 -0
- data/spec/the_dude/command_spec.rb +68 -0
- data/spec/the_dude/config_spec.rb +8 -0
- data/spec/the_dude/dsl_spec.rb +41 -0
- data/spec/the_dude/expression_spec.rb +76 -0
- data/spec/the_dude/http_spec.rb +48 -0
- data/spec/the_dude/variable_spec.rb +30 -0
- data/spec/the_dude_spec.rb +68 -0
- data/the_dude.gemspec +25 -0
- metadata +212 -0
data/.gitignore
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--format d --color
|
data/.rvmrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rvm use --create 1.9.3@dude
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
the_dude (0.0.2)
|
5
|
+
colored
|
6
|
+
hirb
|
7
|
+
nokogiri
|
8
|
+
|
9
|
+
GEM
|
10
|
+
remote: https://rubygems.org/
|
11
|
+
specs:
|
12
|
+
aruba (0.5.2)
|
13
|
+
childprocess (~> 0.3.6)
|
14
|
+
cucumber (>= 1.1.1)
|
15
|
+
rspec-expectations (>= 2.7.0)
|
16
|
+
builder (3.0.4)
|
17
|
+
childprocess (0.3.9)
|
18
|
+
ffi (~> 1.0, >= 1.0.11)
|
19
|
+
colored (1.2)
|
20
|
+
cucumber (1.3.1)
|
21
|
+
builder (>= 2.1.2)
|
22
|
+
diff-lcs (>= 1.1.3)
|
23
|
+
gherkin (~> 2.12.0)
|
24
|
+
multi_json (~> 1.3)
|
25
|
+
diff-lcs (1.2.4)
|
26
|
+
fakeweb (1.3.0)
|
27
|
+
ffi (1.8.1)
|
28
|
+
gherkin (2.12.0)
|
29
|
+
multi_json (~> 1.3)
|
30
|
+
hirb (0.7.1)
|
31
|
+
json (1.7.7)
|
32
|
+
multi_json (1.7.3)
|
33
|
+
nokogiri (1.5.9)
|
34
|
+
rake (10.0.4)
|
35
|
+
rdoc (4.0.1)
|
36
|
+
json (~> 1.4)
|
37
|
+
rspec (2.13.0)
|
38
|
+
rspec-core (~> 2.13.0)
|
39
|
+
rspec-expectations (~> 2.13.0)
|
40
|
+
rspec-mocks (~> 2.13.0)
|
41
|
+
rspec-core (2.13.1)
|
42
|
+
rspec-expectations (2.13.0)
|
43
|
+
diff-lcs (>= 1.1.3, < 2.0)
|
44
|
+
rspec-mocks (2.13.1)
|
45
|
+
|
46
|
+
PLATFORMS
|
47
|
+
ruby
|
48
|
+
|
49
|
+
DEPENDENCIES
|
50
|
+
aruba
|
51
|
+
fakeweb
|
52
|
+
rake
|
53
|
+
rdoc
|
54
|
+
rspec
|
55
|
+
the_dude!
|
data/README.md
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
# The Dude
|
2
|
+
|
3
|
+
[![Code Climate](https://codeclimate.com/github/adamphillips/the_dude.png)](https://codeclimate.com/github/adamphillips/the\_dude)
|
4
|
+
|
5
|
+
The Dude is here to make your terminal life more chilled.
|
6
|
+
|
7
|
+
You preload The Dude with your commonly used commands. Then you run them.
|
8
|
+
Alternatively, The Dude already knows how to do a bunch of stuff for you.
|
9
|
+
|
10
|
+
For example:
|
11
|
+
|
12
|
+
$ dude fetch google.co.uk # opens a google.co.uk in lynx
|
13
|
+
$ dude show me pictures of cool stuff # opens a browser with a google images search for 'cool stuff'
|
14
|
+
$ dude wassup # starts top
|
15
|
+
|
16
|
+
If this sounds like fancypants bash aliases, you'd be about right. So what's the point?
|
17
|
+
|
18
|
+
Well, there's a few reasons
|
19
|
+
- a single dude command can trigger a bunch of other commands. Sure you
|
20
|
+
could write a shell script and run that using an alias but that assumes
|
21
|
+
you're happy writing shell scripts.
|
22
|
+
- for the command-line phobic, dude provides a friendly interface to the
|
23
|
+
command line.
|
24
|
+
- for those who are happy on the command line, there can still be a benefit
|
25
|
+
in reducing the cognitive overhead required to run scripts
|
26
|
+
|
27
|
+
## Talking to The Dude
|
28
|
+
|
29
|
+
The Dude is still very young and not on rubygems quite yet. For the mean
|
30
|
+
time, you will need to checkout the repo and build the gem.
|
31
|
+
|
32
|
+
The Dude comes with a 'dude' binary so you can use this to run commands
|
33
|
+
|
34
|
+
dude why? # will output 'because'
|
35
|
+
|
36
|
+
Alternatively, you can start the dude inteactively with
|
37
|
+
|
38
|
+
dude -i
|
39
|
+
|
40
|
+
Then you get a dude prompt you can enter commands straight into.
|
data/Rakefile
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'rake/clean'
|
2
|
+
require 'rubygems'
|
3
|
+
require 'rubygems/package_task'
|
4
|
+
require 'rdoc/task'
|
5
|
+
require 'cucumber'
|
6
|
+
require 'cucumber/rake/task'
|
7
|
+
Rake::RDocTask.new do |rd|
|
8
|
+
rd.main = "README.rdoc"
|
9
|
+
rd.rdoc_files.include("README.rdoc","lib/**/*.rb","bin/**/*")
|
10
|
+
rd.title = 'Your application title'
|
11
|
+
end
|
12
|
+
|
13
|
+
spec = eval(File.read('the_dude.gemspec'))
|
14
|
+
|
15
|
+
Gem::PackageTask.new(spec) do |pkg|
|
16
|
+
end
|
17
|
+
CUKE_RESULTS = 'results.html'
|
18
|
+
CLEAN << CUKE_RESULTS
|
19
|
+
desc 'Run features'
|
20
|
+
Cucumber::Rake::Task.new(:features) do |t|
|
21
|
+
opts = "features --format html -o #{CUKE_RESULTS} --format progress -x"
|
22
|
+
opts += " --tags #{ENV['TAGS']}" if ENV['TAGS']
|
23
|
+
t.cucumber_opts = opts
|
24
|
+
t.fork = false
|
25
|
+
end
|
26
|
+
|
27
|
+
desc 'Run features tagged as work-in-progress (@wip)'
|
28
|
+
Cucumber::Rake::Task.new('features:wip') do |t|
|
29
|
+
tag_opts = ' --tags ~@pending'
|
30
|
+
tag_opts = ' --tags @wip'
|
31
|
+
t.cucumber_opts = "features --format html -o #{CUKE_RESULTS} --format pretty -x -s#{tag_opts}"
|
32
|
+
t.fork = false
|
33
|
+
end
|
34
|
+
|
35
|
+
task :cucumber => :features
|
36
|
+
task 'cucumber:wip' => 'features:wip'
|
37
|
+
task :wip => 'features:wip'
|
38
|
+
require 'rake/testtask'
|
39
|
+
Rake::TestTask.new do |t|
|
40
|
+
t.libs << "test"
|
41
|
+
t.test_files = FileList['test/*_test.rb']
|
42
|
+
end
|
43
|
+
|
44
|
+
task :default => [:test,:features]
|
data/bin/dude
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'the_dude'
|
3
|
+
|
4
|
+
include TheDude::Dsl
|
5
|
+
|
6
|
+
DUDERC_PATH = "#{ENV['HOME']}/.duderc"
|
7
|
+
load DUDERC_PATH if File.exists? DUDERC_PATH
|
8
|
+
|
9
|
+
# Setup rlwrap for readline support
|
10
|
+
if ARGV.include? '-i'
|
11
|
+
interactive_mode = true
|
12
|
+
if !ENV['__REPL_WRAPPED'] && system("which rlwrap > /dev/null 2> /dev/null")
|
13
|
+
ENV['__REPL_WRAPPED'] = '0'
|
14
|
+
|
15
|
+
exec "rlwrap #{Shellwords.escape(__FILE__)} #{ARGV.join(' ')}"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
question = ARGV.join(' ')
|
20
|
+
|
21
|
+
if interactive_mode
|
22
|
+
loop do
|
23
|
+
print "dude, "
|
24
|
+
begin
|
25
|
+
question = $stdin.gets.chomp
|
26
|
+
rescue NoMethodError, Interrupt
|
27
|
+
exit
|
28
|
+
end
|
29
|
+
|
30
|
+
puts TheDude.ask question
|
31
|
+
puts ''
|
32
|
+
end
|
33
|
+
else
|
34
|
+
puts TheDude.ask question
|
35
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
Feature: Basic interaction with the Dude
|
2
|
+
|
3
|
+
Scenario: App just runs
|
4
|
+
When I get help for "dude"
|
5
|
+
Then the exit status should be 0
|
6
|
+
|
7
|
+
Scenario: Asking a question
|
8
|
+
When I run `dude why?`
|
9
|
+
Then the output should contain "because"
|
10
|
+
|
11
|
+
Scenario: Asking a question that requires running another command
|
12
|
+
When I run `dude date`
|
13
|
+
Then the output should contain today's date
|
@@ -0,0 +1,27 @@
|
|
1
|
+
Feature: Reading from ~/.duderc
|
2
|
+
It should be possible to configure the dude from a duderc file
|
3
|
+
|
4
|
+
Scenario: File doesn't exist should still work
|
5
|
+
When I run `dude`
|
6
|
+
Then the exit status should be 0
|
7
|
+
|
8
|
+
#Scenario: File containing a question definition
|
9
|
+
#Given a file named "~/.duderc" with:
|
10
|
+
#"""
|
11
|
+
#command "another question" do
|
12
|
+
#"another answer"
|
13
|
+
#end
|
14
|
+
#"""
|
15
|
+
#When I run `dude another question`
|
16
|
+
#Then the output should contain "another answer"
|
17
|
+
|
18
|
+
#Scenario: File containing a question definition
|
19
|
+
#Given a file named "~/.duderc" with:
|
20
|
+
#"""
|
21
|
+
#command "say something" do
|
22
|
+
#`echo "hello"`
|
23
|
+
#end
|
24
|
+
#"""
|
25
|
+
#When I run `dude another question`
|
26
|
+
#Then the output should contain exactly "hello"
|
27
|
+
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'aruba/cucumber'
|
2
|
+
|
3
|
+
ENV['PATH'] = "#{File.expand_path(File.dirname(__FILE__) + '/../../bin')}#{File::PATH_SEPARATOR}#{ENV['PATH']}"
|
4
|
+
LIB_DIR = File.join(File.expand_path(File.dirname(__FILE__)),'..','..','lib')
|
5
|
+
|
6
|
+
Before do
|
7
|
+
# Using "announce" causes massive warnings on 1.9.2
|
8
|
+
@puts = true
|
9
|
+
@original_rubylib = ENV['RUBYLIB']
|
10
|
+
ENV['RUBYLIB'] = LIB_DIR + File::PATH_SEPARATOR + ENV['RUBYLIB'].to_s
|
11
|
+
end
|
12
|
+
|
13
|
+
After do
|
14
|
+
ENV['RUBYLIB'] = @original_rubylib
|
15
|
+
end
|
data/lib/the_dude.rb
ADDED
@@ -0,0 +1,128 @@
|
|
1
|
+
require 'date'
|
2
|
+
require 'shellwords'
|
3
|
+
|
4
|
+
require 'hirb'
|
5
|
+
require 'colored'
|
6
|
+
|
7
|
+
require 'the_dude/command'
|
8
|
+
require 'the_dude/config'
|
9
|
+
require 'the_dude/dsl'
|
10
|
+
require 'the_dude/expression'
|
11
|
+
require 'the_dude/http'
|
12
|
+
require 'the_dude/variable'
|
13
|
+
require 'the_dude/version'
|
14
|
+
|
15
|
+
module TheDude
|
16
|
+
|
17
|
+
class << self
|
18
|
+
# Asks the dude a question
|
19
|
+
def ask question
|
20
|
+
command = find_command_for question
|
21
|
+
return "Wtf? What do you mean #{question}" unless command
|
22
|
+
arguments = arguments_for question, command
|
23
|
+
|
24
|
+
# this is a nasty way of dealing with the fact that we get question
|
25
|
+
# returned if it exactly matches the command
|
26
|
+
arguments = nil if arguments == question
|
27
|
+
|
28
|
+
command.ask *arguments
|
29
|
+
end
|
30
|
+
|
31
|
+
# @return [Hash] Returns the commands the dude knows about
|
32
|
+
def commands
|
33
|
+
@commands || {}
|
34
|
+
end
|
35
|
+
|
36
|
+
# Outputs something using angry-dude-formatting
|
37
|
+
def complain something
|
38
|
+
puts angry_dude_format something
|
39
|
+
end
|
40
|
+
|
41
|
+
# @return [Hash] Returns the variables the dude knows about
|
42
|
+
def variables
|
43
|
+
@variables || {}
|
44
|
+
end
|
45
|
+
|
46
|
+
# Registers a new command with the dude
|
47
|
+
#
|
48
|
+
# @param [TheDude::Command] command The command to register
|
49
|
+
def register_command command
|
50
|
+
@commands ||= {}
|
51
|
+
@commands[command.question] = command
|
52
|
+
end
|
53
|
+
|
54
|
+
# Registers a new variable with the dude
|
55
|
+
#
|
56
|
+
# @param [TheDude::Variable] variable The variable to register
|
57
|
+
def register_variable variable
|
58
|
+
@variables ||= {}
|
59
|
+
@variables[variable.name] = variable
|
60
|
+
end
|
61
|
+
|
62
|
+
# Resets the dude
|
63
|
+
def reset
|
64
|
+
reset_commands
|
65
|
+
reset_variables
|
66
|
+
end
|
67
|
+
|
68
|
+
# Outputs something use dude-formatting
|
69
|
+
def say something
|
70
|
+
puts dude_format something
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
# @return [String] Applies angry dude formatting for error messages
|
76
|
+
def angry_dude_format string
|
77
|
+
string
|
78
|
+
end
|
79
|
+
|
80
|
+
# Returns the arguments for a question based on the specified command
|
81
|
+
#
|
82
|
+
# @param [String] question The question
|
83
|
+
# @param [TheDude::Command] command The command
|
84
|
+
#
|
85
|
+
# @return [Array] An array of arguments
|
86
|
+
def arguments_for question, command
|
87
|
+
question.scan(command.question)[0]
|
88
|
+
end
|
89
|
+
|
90
|
+
# @return [String] Applies dude formatting
|
91
|
+
def dude_format string
|
92
|
+
string
|
93
|
+
end
|
94
|
+
|
95
|
+
# Returns the command for answering the specified question
|
96
|
+
#
|
97
|
+
# @param [String] question The question asked
|
98
|
+
#
|
99
|
+
# @return [TheDude::Command]
|
100
|
+
def find_command_for question
|
101
|
+
# if there's an exact match return that first
|
102
|
+
return commands[question] if commands[question]
|
103
|
+
|
104
|
+
commands.each do |key, val|
|
105
|
+
if key.kind_of? Regexp
|
106
|
+
return val if key =~ question
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
return false
|
111
|
+
end
|
112
|
+
|
113
|
+
# Resets the collection of commands
|
114
|
+
def reset_commands
|
115
|
+
@commands = {}
|
116
|
+
end
|
117
|
+
|
118
|
+
# Resets the collection of commands
|
119
|
+
def reset_variables
|
120
|
+
@variables = {}
|
121
|
+
end
|
122
|
+
|
123
|
+
end
|
124
|
+
|
125
|
+
class UndefinedVariableError < Exception; end
|
126
|
+
end
|
127
|
+
|
128
|
+
require 'the_dude/setup' # Load default commands and variables
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module TheDude
|
2
|
+
class Command
|
3
|
+
# [String | Regexp] The question for this command
|
4
|
+
attr_reader :question
|
5
|
+
|
6
|
+
# [String | Proc | Block] The answer for this command
|
7
|
+
attr_accessor :answer
|
8
|
+
|
9
|
+
# Initializes a new command.
|
10
|
+
#
|
11
|
+
# An instance is created to hold the details of the command and the command
|
12
|
+
# is registered in TheDude.commands using the provided key. The answer can
|
13
|
+
# be a string, proc or block. If both a string/proc and block are passed,
|
14
|
+
# the block will take precedence.
|
15
|
+
#
|
16
|
+
# @example
|
17
|
+
#
|
18
|
+
# command = TheDude::Command.new 'hello', 'hello'
|
19
|
+
# TheDude.commands['hello'] == command # true
|
20
|
+
#
|
21
|
+
# TheDude::Command.new 'hello' do
|
22
|
+
# puts 'hello'
|
23
|
+
# puts 'how are you?'
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
# @param [String | Regexp] question the question to ask
|
27
|
+
# @param [String | Proc] answer the answer to the question
|
28
|
+
#
|
29
|
+
# @return [TheDude::Command]
|
30
|
+
def initialize question, answer=nil, &block_answer
|
31
|
+
@question = Expression.new(question).to_regex
|
32
|
+
@answer = block_answer || answer
|
33
|
+
|
34
|
+
TheDude.register_command self
|
35
|
+
end
|
36
|
+
|
37
|
+
# Asks the question with the specified arguments. If the answer is a string
|
38
|
+
# it is returned. If it is a proc or block it is evaluated with the passed
|
39
|
+
# parameters.
|
40
|
+
def ask *args
|
41
|
+
begin
|
42
|
+
if answer.kind_of? Proc
|
43
|
+
instance_exec(*args, &answer)
|
44
|
+
else
|
45
|
+
answer
|
46
|
+
end
|
47
|
+
rescue SystemExit
|
48
|
+
exit
|
49
|
+
rescue Interrupt
|
50
|
+
TheDude.say 'Fine, i\'ll leave it then'
|
51
|
+
rescue Exception => e
|
52
|
+
TheDude.complain "Man, what are you doing? Look what you just did\n
|
53
|
+
#{e.class}
|
54
|
+
#{e.message}
|
55
|
+
#{e.backtrace}
|
56
|
+
At least you didn't pee on my rug I guess
|
57
|
+
"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|