relish 0.0.5 → 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,7 +1,4 @@
1
1
  $:.unshift(File.dirname(__FILE__) + '/../../lib')
2
2
  require 'relish/command'
3
3
 
4
- Relish::Command::Base.class_eval do
5
- remove_const(:GLOBAL_OPTIONS_FILE)
6
- const_set(:GLOBAL_OPTIONS_FILE, '~/.relish')
7
- end
4
+ Relish.global_options_file = '~/.relish'
@@ -1,3 +1,4 @@
1
+ require 'relish'
1
2
  require 'relish/commands/base'
2
3
  require 'relish/commands/push'
3
4
  require 'relish/commands/config'
@@ -1,54 +1,72 @@
1
1
  require 'yaml'
2
+ require 'relish/ui'
3
+ require 'relish/options_file'
4
+ require 'relish/commands/dsl'
2
5
 
3
6
  module Relish
4
7
  module Command
5
8
  class Base
6
- DEFAULT_HOST = 'relishapp.com'
7
- GLOBAL_OPTIONS_FILE = File.join(File.expand_path('~'), '.relish')
8
- LOCAL_OPTIONS_FILE = '.relish'
9
-
10
- attr_accessor :args
9
+ extend Dsl
11
10
 
11
+ attr_writer :args
12
+ attr_reader :cli_options
13
+
12
14
  def initialize(args = [])
13
- @args = args
15
+ @args = clean_args(args)
14
16
  @param = get_param
15
- @options = get_options
17
+ @cli_options = Hash[*@args]
16
18
  end
17
19
 
18
- def organization
19
- @options['--organization'] || @options['-o'] || parsed_options_file['organization']
20
+ def url
21
+ "http://#{host}/api"
20
22
  end
21
23
 
22
- def project
23
- @options['--project'] || @options['-p'] || parsed_options_file['project']
24
+ def get_param
25
+ @args.shift if @args.size.odd?
24
26
  end
27
+
28
+ private
25
29
 
26
- def url
27
- "http://#{@options['--host'] || DEFAULT_HOST}/api"
28
- end
30
+ option :organization
31
+ option :project
32
+ option :api_token, :default => lambda { get_and_store_api_token }
33
+ option :host, :default => lambda { Relish.default_host }
29
34
 
30
- def resource
31
- RestClient::Resource.new(url)
35
+ def get_and_store_api_token
36
+ api_token = get_api_token
37
+ global_options_file.store('api_token' => api_token)
38
+ api_token
32
39
  end
33
40
 
34
- def api_token
35
- parsed_options_file['api_token']
41
+ def get_api_token
42
+ email, password = ui.get_credentials
43
+
44
+ raw_response = resource(:user => email, :password => password)['token'].get
45
+ String.new(raw_response.to_s)
36
46
  end
37
47
 
38
- def get_param
39
- args.shift if args.size.odd?
48
+ def resource(options = {})
49
+ RestClient::Resource.new(url, options)
50
+ end
51
+
52
+ def clean_args(args)
53
+ cleaned = []
54
+ args.each do |arg|
55
+ cleaned << arg.sub('--', '')
56
+ end
57
+ cleaned
40
58
  end
41
59
 
42
- def get_options
43
- parsed_options_file.merge(Hash[*args])
60
+ def global_options_file
61
+ @global_options ||= OptionsFile.new(Relish.global_options_file)
62
+ end
63
+
64
+ def local_options_file
65
+ @local_options ||= OptionsFile.new(Relish.local_options_file)
44
66
  end
45
67
 
46
- def parsed_options_file
47
- @parsed_options_file ||= {}.tap do |parsed_options|
48
- [GLOBAL_OPTIONS_FILE, LOCAL_OPTIONS_FILE].each do |options_file|
49
- parsed_options.merge!(YAML.load_file(options_file)) if File.exist?(options_file)
50
- end
51
- end
68
+ def ui
69
+ @ui ||= Ui.new
52
70
  end
53
71
 
54
72
  end
@@ -7,13 +7,19 @@ module Relish
7
7
  end
8
8
 
9
9
  def show
10
- puts(if File.exists?(LOCAL_OPTIONS_FILE)
11
- IO.read(LOCAL_OPTIONS_FILE)
10
+ puts(if File.exists?(Relish.local_options_file)
11
+ IO.read(Relish.local_options_file)
12
12
  else
13
- "No #{LOCAL_OPTIONS_FILE} file exists"
13
+ "No #{Relish.local_options_file} file exists"
14
14
  end)
15
15
  end
16
16
 
17
+ def add
18
+ File.open(Relish.local_options_file, 'a') do |f|
19
+ f.write(YAML::dump(Hash[*@args]))
20
+ end
21
+ end
22
+
17
23
  end
18
24
  end
19
25
  end
@@ -0,0 +1,17 @@
1
+ module Relish
2
+ module Command
3
+ module Dsl
4
+
5
+ def option(name, options = {})
6
+ default_proc = options[:default] || lambda {}
7
+ define_method(name) do
8
+ cli_options[name.to_s] ||
9
+ local_options_file[name.to_s] ||
10
+ global_options_file[name.to_s] ||
11
+ instance_exec(&default_proc)
12
+ end
13
+ end
14
+
15
+ end
16
+ end
17
+ end
@@ -5,20 +5,33 @@ module Relish
5
5
  module Command
6
6
  class Projects < Base
7
7
 
8
- def default
9
- list
10
- end
8
+ def default; list end
11
9
 
12
10
  def list
13
11
  response = resource['projects'].get(
14
12
  :params => {:api_token => api_token}, :accept => :json
15
13
  )
16
14
  puts format(response)
15
+ rescue RestClient::Exception => exception
16
+ warn exception.response
17
+ exit exception.http_code
18
+ end
19
+
20
+ def add
21
+ puts resource['projects'].post(:api_token => api_token, :handle => @param)
22
+ rescue RestClient::Exception => exception
23
+ warn exception.response
24
+ exit 1
25
+ end
26
+
27
+ def remove
28
+ puts resource["projects/#{@param}?api_token=#{api_token}"].delete
17
29
  rescue RestClient::Exception => exception
18
30
  warn exception.response
19
31
  exit 1
20
32
  end
21
33
 
34
+ private
22
35
  def format(response)
23
36
  json = JSON.parse(response)
24
37
  json.map do |hash|
@@ -11,16 +11,17 @@ module Relish
11
11
 
12
12
  class Push < Base
13
13
 
14
- def default
15
- run
16
- end
14
+ option :version
15
+
16
+ def default; run end
17
17
 
18
18
  def run
19
19
  post files_as_tar_gz
20
20
  end
21
21
 
22
22
  def post(tar_gz_data)
23
- resource[parameters].post(tar_gz_data, :content_type => 'application/x-gzip')
23
+ resource["pushes?#{parameters}"].post(tar_gz_data,
24
+ :content_type => 'application/x-gzip')
24
25
  puts "sent:\n#{files.join("\n")}"
25
26
  rescue RestClient::Exception => exception
26
27
  warn exception.response
@@ -29,7 +30,6 @@ module Relish
29
30
 
30
31
  def parameters
31
32
  "".tap do |str|
32
- str << "pushes?"
33
33
  str << "creator_id=#{organization}&" if organization
34
34
  str << "project_id=#{project}&"
35
35
  str << "version_id=#{version}&" if version
@@ -37,10 +37,6 @@ module Relish
37
37
  end
38
38
  end
39
39
 
40
- def version
41
- @options['--version'] || @options['-v']
42
- end
43
-
44
40
  def files_as_tar_gz
45
41
  stream = StringIO.new
46
42
  begin
@@ -0,0 +1,45 @@
1
+ require 'relish/commands/base'
2
+ require 'yaml'
3
+
4
+ module Relish
5
+
6
+ # Represents a .relish file
7
+ class OptionsFile
8
+
9
+ def initialize(path)
10
+ @path = path
11
+ end
12
+
13
+ # Store the given options into the file. Existing options with the same keys
14
+ # will be overwritten.
15
+ def store(options)
16
+ new_options = self.options.merge(options)
17
+ FileUtils.touch(@path)
18
+ File.open(@path, 'w') do |file|
19
+ YAML.dump(new_options, file)
20
+ end
21
+ end
22
+
23
+ def [](key)
24
+ options[key]
25
+ end
26
+
27
+ def == (other)
28
+ options == other
29
+ end
30
+
31
+ # Stored options as a hash
32
+ def options
33
+ @options ||= current_options
34
+ end
35
+
36
+ private
37
+
38
+ def current_options
39
+ return {} unless File.exist?(@path)
40
+ YAML.load_file(@path)
41
+ end
42
+
43
+ end
44
+
45
+ end
data/lib/relish/ui.rb ADDED
@@ -0,0 +1,60 @@
1
+ module Relish
2
+ class Ui
3
+ def get_credentials
4
+ puts "Please enter your Relish credentials."
5
+
6
+ print "Email: "
7
+ user = ask
8
+
9
+ print "Password: "
10
+ password = running_on_windows? ? ask_for_password_on_windows : ask_for_password
11
+
12
+ [ user, password ]
13
+ end
14
+
15
+ private
16
+
17
+ def running_on_windows?
18
+ RUBY_PLATFORM =~ /mswin32|mingw32/
19
+ end
20
+
21
+ def ask_for_password_on_windows
22
+ require "Win32API"
23
+ char = nil
24
+ password = ''
25
+
26
+ while char = Win32API.new("crtdll", "_getch", [ ], "L").Call do
27
+ break if char == 10 || char == 13 # received carriage return or newline
28
+ if char == 127 || char == 8 # backspace and delete
29
+ password.slice!(-1, 1)
30
+ else
31
+ # windows might throw a -1 at us so make sure to handle RangeError
32
+ (password << char.chr) rescue RangeError
33
+ end
34
+ end
35
+ puts
36
+ return password
37
+ end
38
+
39
+ def ask_for_password
40
+ echo_off
41
+ password = ask
42
+ puts
43
+ echo_on
44
+ return password
45
+ end
46
+
47
+ def echo_off
48
+ system "stty -echo"
49
+ end
50
+
51
+ def echo_on
52
+ system "stty echo"
53
+ end
54
+
55
+ def ask
56
+ gets.strip
57
+ end
58
+
59
+ end
60
+ end
data/lib/relish.rb ADDED
@@ -0,0 +1,19 @@
1
+ module Relish
2
+ class << self
3
+
4
+ def self.setting(name, value)
5
+ attr_writer name
6
+ class_eval %{
7
+ def #{name} # def global_options_file
8
+ @#{name.to_s} ||= # @global_options_file ||=
9
+ ENV['RELISH_#{name.to_s.upcase}'] || # ENV['RELISH_GLOBAL_OPTIONS_FILE'] ||
10
+ '#{value}' # '~/.relish'
11
+ end # end
12
+ }
13
+ end
14
+
15
+ setting :global_options_file, File.join(File.expand_path('~'), '.relish')
16
+ setting :local_options_file, '.relish'
17
+ setting :default_host, 'relishapp.com'
18
+ end
19
+ end
data/relish.gemspec CHANGED
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = "relish"
3
- s.version = "0.0.5"
3
+ s.version = "0.0.6"
4
4
 
5
5
  s.required_rubygems_version = '>= 1.3.5'
6
6
  s.authors = ["Matt Wynne", "Justin Ko"]
@@ -6,7 +6,7 @@ module Relish
6
6
 
7
7
  {:organization => 'rspec', :project => 'rspec-core'}.each do |meth, name|
8
8
  describe "##{meth}" do
9
- context 'passed in command line as full arg' do
9
+ context 'passed in command line' do
10
10
  let(:base) { described_class.new(["--#{meth}", name]) }
11
11
 
12
12
  it 'returns the value' do
@@ -14,20 +14,11 @@ module Relish
14
14
  end
15
15
  end
16
16
 
17
- context 'passed in command line as short arg' do
18
- let(:short_arg) { meth.to_s[0,1] }
19
- let(:base) { described_class.new(["-#{short_arg}", name]) }
20
-
21
- it 'returns the value' do
22
- base.send(meth).should eq(name)
23
- end
24
- end
25
-
26
- context 'contained in the options file' do
17
+ context 'contained in the local options file' do
27
18
  let(:base) { described_class.new }
28
19
 
29
20
  before do
30
- base.stub(:parsed_options_file).and_return({meth.to_s => name})
21
+ OptionsFile.stub(:new).with(Relish.local_options_file).and_return({meth.to_s => name})
31
22
  end
32
23
 
33
24
  it 'returns the value' do
@@ -57,33 +48,60 @@ module Relish
57
48
  let(:base) { described_class.new }
58
49
 
59
50
  it 'returns the default host' do
60
- base.url.should eq("http://#{Base::DEFAULT_HOST}/api")
51
+ base.url.should eq("http://#{Relish.default_host}/api")
61
52
  end
62
53
  end
63
54
  end
64
55
 
65
- describe '#resource' do
66
- let(:base) { described_class.new }
67
-
68
- before do
69
- base.should_receive(:url).and_return('url')
70
- RestClient::Resource.should_receive(:new).with('url')
71
- end
72
-
73
- specify { base.resource }
74
- end
75
-
76
56
  describe '#api_token' do
77
57
  let(:base) { described_class.new }
78
- let(:options) { {'api_token' => '12345'} }
79
-
58
+ let(:ui) { double(Ui) }
59
+
80
60
  before do
81
- base.should_receive(:parsed_options_file).and_return(options)
61
+ Ui.stub(:new).and_return(ui)
62
+ OptionsFile.stub(:new => options)
82
63
  end
83
64
 
84
- it 'parses the api token' do
85
- base.api_token.should eq('12345')
65
+ context "when the token is stored locally" do
66
+ let(:options) { {'api_token' => '12345'} }
67
+
68
+ it 'parses the api token' do
69
+ base.api_token.should eq('12345')
70
+ end
71
+ end
72
+
73
+ context "when the token is not stored locally" do
74
+ let(:options) { {} }
75
+ let(:api_token) { 'abasfawer23123' }
76
+ let(:credentials) { ['testuser', 'testpassword'] }
77
+
78
+ let(:global_options) do
79
+ double = double(OptionsFile, :[] => nil)
80
+ OptionsFile.stub(:new => double)
81
+ double
82
+ end
83
+
84
+ def api_endpoint(name, credentials)
85
+ user, password = *credentials
86
+ endpoint = double
87
+
88
+ RestClient::Resource.stub(:new).
89
+ with(anything, :user => user, :password => password).
90
+ and_return name => endpoint
91
+
92
+ endpoint
93
+ end
94
+
95
+ it "asks the user for their credentials and send them to the server" do
96
+ ui.should_receive(:get_credentials).and_return(credentials)
97
+ api_endpoint('token', credentials).should_receive(:get).and_return(api_token)
98
+ global_options.should_receive(:store).with 'api_token' => api_token
99
+
100
+ base.api_token
101
+ end
102
+
86
103
  end
104
+
87
105
  end
88
106
 
89
107
  describe '#get_param' do
@@ -113,54 +131,6 @@ module Relish
113
131
  end
114
132
  end
115
133
 
116
- describe '#get_options' do
117
- let(:options) { {'project' => 'rspec-core'} }
118
- let(:base) { described_class.new(['--project', 'rspec-core']) }
119
-
120
- before do
121
- base.should_receive(:parsed_options_file).and_return(options)
122
- end
123
-
124
- it 'combines the args and options file' do
125
- base.get_options.should eq(
126
- {'project' => 'rspec-core', '--project' => 'rspec-core'}
127
- )
128
- end
129
- end
130
-
131
- describe '#parsed_options_file' do
132
- let(:base) { described_class.new }
133
-
134
- context 'with options file that exists' do
135
- let(:options) do
136
- {'organization' => 'rspec', 'project' => 'rspec-core'}
137
- end
138
-
139
- before do
140
- File.should_receive(:exist?).twice.and_return(true)
141
- YAML.should_receive(:load_file).twice.and_return(options)
142
- end
143
-
144
- it 'parses the organization' do
145
- base.parsed_options_file['organization'].should eq('rspec')
146
- end
147
-
148
- it 'parses the project' do
149
- base.parsed_options_file['project'].should eq('rspec-core')
150
- end
151
- end
152
-
153
- context 'with options file that does not exist' do
154
- before do
155
- File.stub(:exist?).and_return(false)
156
- end
157
-
158
- it 'returns an empty hash' do
159
- base.parsed_options_file.should eq({})
160
- end
161
- end
162
- end
163
-
164
134
  end
165
135
  end
166
136
  end
@@ -11,13 +11,7 @@ module Relish
11
11
  projects.should_receive(:list)
12
12
  projects.default
13
13
  end
14
- end
15
-
16
- # describe '#url' do
17
- # context 'given a name' do
18
- # let(:projects) { described_class.new(['rspec-core']) }
19
-
20
-
14
+ end
21
15
 
22
16
  end
23
17
  end
@@ -24,7 +24,7 @@ module Relish
24
24
 
25
25
  specify do
26
26
  push.parameters.should eq(
27
- "pushes?project_id=rspec&api_token=abc"
27
+ "project_id=rspec&api_token=abc"
28
28
  )
29
29
  end
30
30
  end
@@ -34,7 +34,7 @@ module Relish
34
34
 
35
35
  specify do
36
36
  push.parameters.should eq(
37
- "pushes?project_id=rspec&version_id=one&api_token=abc"
37
+ "project_id=rspec&version_id=one&api_token=abc"
38
38
  )
39
39
  end
40
40
  end
@@ -0,0 +1,65 @@
1
+ require 'spec_helper'
2
+
3
+ module Relish
4
+ describe OptionsFile do
5
+ let(:path) { Dir.tmpdir + '/.relish' }
6
+ let(:global_options) { described_class.new(path) }
7
+
8
+ after { FileUtils.rm_rf(path) }
9
+
10
+ describe '#[]' do
11
+
12
+ context 'with options file that exists' do
13
+ let(:options) do
14
+ {'organization' => 'rspec', 'project' => 'rspec-core'}
15
+ end
16
+
17
+ before do
18
+ File.open(path, 'w') { |f| YAML.dump(options, f) }
19
+ end
20
+
21
+ it 'parses the organization' do
22
+ global_options['organization'].should eq('rspec')
23
+ end
24
+
25
+ it 'parses the project' do
26
+ global_options['project'].should eq('rspec-core')
27
+ end
28
+ end
29
+
30
+ context 'with options file that does not exist' do
31
+ before do
32
+ FileUtils.rm_rf(path)
33
+ end
34
+
35
+ it 'returns an empty hash' do
36
+ global_options.should eq({})
37
+ end
38
+ end
39
+ end
40
+
41
+ describe '#store' do
42
+ context 'with options file that exists' do
43
+
44
+ let(:options) do
45
+ {'organization' => 'rspec', 'project' => 'rspec-core'}
46
+ end
47
+
48
+ before do
49
+ File.open(path, 'w') { |f| YAML.dump(options, f) }
50
+ global_options.store('organization' => 'relish')
51
+ end
52
+
53
+ it "over-writes existing values" do
54
+ OptionsFile.new(path).options['organization'].should == 'relish'
55
+ end
56
+
57
+ it 'leaves existing options alone' do
58
+ OptionsFile.new(path).options['project'].should == 'rspec-core'
59
+ end
60
+
61
+ end
62
+ end
63
+
64
+ end
65
+ end
@@ -0,0 +1,12 @@
1
+ require 'spec_helper'
2
+
3
+ describe Relish do
4
+ it "has a .global_options_file setting" do
5
+ Relish.global_options_file.should_not be_nil
6
+ end
7
+
8
+ it "allows global_options_file to be overwritten" do
9
+ Relish.global_options_file = 'foo'
10
+ Relish.global_options_file.should == 'foo'
11
+ end
12
+ end
data/spec/spec_helper.rb CHANGED
@@ -6,13 +6,5 @@ require 'relish/command'
6
6
 
7
7
  RSpec.configure do |config|
8
8
  config.color_enabled = true
9
-
10
- config.before(:suite) do
11
-
12
- Relish::Command::Base.class_eval do
13
- remove_const(:GLOBAL_OPTIONS_FILE)
14
- const_set(:GLOBAL_OPTIONS_FILE, '~/.relish')
15
- end
16
-
17
- end
9
+
18
10
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: relish
3
3
  version: !ruby/object:Gem::Version
4
- hash: 21
4
+ hash: 19
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
8
  - 0
9
- - 5
10
- version: 0.0.5
9
+ - 6
10
+ version: 0.0.6
11
11
  platform: ruby
12
12
  authors:
13
13
  - Matt Wynne
@@ -185,18 +185,24 @@ files:
185
185
  - features/step_definitions/fake_web_steps.rb
186
186
  - features/step_definitions/relish_steps.rb
187
187
  - features/support/env.rb
188
+ - lib/relish.rb
188
189
  - lib/relish/command.rb
189
190
  - lib/relish/commands/base.rb
190
191
  - lib/relish/commands/config.rb
192
+ - lib/relish/commands/dsl.rb
191
193
  - lib/relish/commands/help.rb
192
194
  - lib/relish/commands/projects.rb
193
195
  - lib/relish/commands/push.rb
196
+ - lib/relish/options_file.rb
197
+ - lib/relish/ui.rb
194
198
  - relish.gemspec
195
199
  - spec/relish/command_spec.rb
196
200
  - spec/relish/commands/base_spec.rb
197
201
  - spec/relish/commands/config_spec.rb
198
202
  - spec/relish/commands/projects_spec.rb
199
203
  - spec/relish/commands/push_spec.rb
204
+ - spec/relish/options_file_spec.rb
205
+ - spec/relish_spec.rb
200
206
  - spec/spec_helper.rb
201
207
  has_rdoc: true
202
208
  homepage: http://relishapp.com
@@ -246,4 +252,6 @@ test_files:
246
252
  - spec/relish/commands/config_spec.rb
247
253
  - spec/relish/commands/projects_spec.rb
248
254
  - spec/relish/commands/push_spec.rb
255
+ - spec/relish/options_file_spec.rb
256
+ - spec/relish_spec.rb
249
257
  - spec/spec_helper.rb