codefumes 0.1.10 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. data/Gemfile +19 -0
  2. data/Gemfile.lock +135 -0
  3. data/History.txt +12 -0
  4. data/LICENSE +20 -0
  5. data/Manifest.txt +40 -19
  6. data/README.txt +11 -29
  7. data/Rakefile +15 -10
  8. data/bin/fumes +214 -0
  9. data/config/website.yml +2 -0
  10. data/cucumber.yml +2 -0
  11. data/features/claiming_a_project.feature +46 -0
  12. data/features/deleting_a_project.feature +32 -0
  13. data/features/releasing_a_project.feature +50 -0
  14. data/features/step_definitions/cli_steps.rb +98 -0
  15. data/features/step_definitions/common_steps.rb +168 -0
  16. data/features/step_definitions/filesystem_steps.rb +19 -0
  17. data/features/storing_user_api_key.feature +41 -0
  18. data/features/support/common.rb +29 -0
  19. data/features/support/env.rb +24 -0
  20. data/features/support/matchers.rb +11 -0
  21. data/features/synchronizing_repository_with_project.feature +33 -0
  22. data/lib/codefumes.rb +10 -8
  23. data/lib/codefumes/api.rb +20 -11
  24. data/lib/codefumes/api/build.rb +139 -0
  25. data/lib/codefumes/api/claim.rb +74 -0
  26. data/lib/codefumes/api/commit.rb +150 -0
  27. data/lib/codefumes/api/payload.rb +93 -0
  28. data/lib/codefumes/api/project.rb +158 -0
  29. data/lib/codefumes/cli_helpers.rb +54 -0
  30. data/lib/codefumes/config_file.rb +3 -2
  31. data/lib/codefumes/errors.rb +21 -0
  32. data/lib/codefumes/exit_codes.rb +10 -0
  33. data/lib/codefumes/harvester.rb +113 -0
  34. data/lib/codefumes/quick_build.rb +43 -0
  35. data/lib/codefumes/quick_metric.rb +20 -0
  36. data/lib/codefumes/source_control.rb +137 -0
  37. data/lib/integrity_notifier/codefumes.haml +11 -0
  38. data/lib/integrity_notifier/codefumes.rb +62 -0
  39. data/spec/codefumes/{build_spec.rb → api/build_spec.rb} +14 -24
  40. data/spec/codefumes/{claim_spec.rb → api/claim_spec.rb} +42 -3
  41. data/spec/codefumes/{commit_spec.rb → api/commit_spec.rb} +34 -24
  42. data/spec/codefumes/api/payload_spec.rb +148 -0
  43. data/spec/codefumes/api/project_spec.rb +286 -0
  44. data/spec/codefumes/api_spec.rb +38 -15
  45. data/spec/codefumes/config_file_spec.rb +69 -13
  46. data/spec/codefumes/harvester_spec.rb +118 -0
  47. data/spec/codefumes/source_control_spec.rb +199 -0
  48. data/spec/codefumes_service_helpers.rb +23 -19
  49. data/spec/fixtures/sample_project_dirs/no_scm/description +4 -0
  50. data/spec/spec_helper.rb +1 -0
  51. data/tasks/cucumber.rake +11 -0
  52. metadata +145 -60
  53. data/bin/cf_claim_project +0 -9
  54. data/bin/cf_release_project +0 -10
  55. data/bin/cf_store_credentials +0 -10
  56. data/lib/cf_claim_project/cli.rb +0 -95
  57. data/lib/cf_release_project/cli.rb +0 -76
  58. data/lib/cf_store_credentials/cli.rb +0 -50
  59. data/lib/codefumes/build.rb +0 -131
  60. data/lib/codefumes/claim.rb +0 -57
  61. data/lib/codefumes/commit.rb +0 -144
  62. data/lib/codefumes/payload.rb +0 -103
  63. data/lib/codefumes/project.rb +0 -129
  64. data/spec/cf_claim_project/cli_spec.rb +0 -17
  65. data/spec/cf_release_project/cli_spec.rb +0 -41
  66. data/spec/cf_store_credentials/cli_spec.rb +0 -28
  67. data/spec/codefumes/payload_spec.rb +0 -155
  68. data/spec/codefumes/project_spec.rb +0 -274
@@ -1,144 +0,0 @@
1
- module CodeFumes
2
- # Similar to a revision control system, a Commit encompasses a set of
3
- # changes to a codebase, who made them, when said changes were applied
4
- # to the previous revision of codebase, et cetera.
5
- #
6
- # A Commit has a concept of 'standard attributes' which will always be
7
- # present in a response from CodeFumes.com[http://codefumes.com], such
8
- # as the +identifier+, +author+, and +commit_message+ (see the list of
9
- # attributes for a comprehensive listing). In addition to this, users
10
- # are able to associate 'custom attributes' to a Commit, allowing
11
- # users to link any number of attributes with a commit identifier and
12
- # easily retrieve them later.
13
- #
14
- # One thing to note about Commit objects is that they are read-only.
15
- # To associate metrics with a Commit object, a Payload object should
16
- # be created and saved. Refer to the Payload documentation for more
17
- # information.
18
- class Commit < CodeFumes::API
19
- attr_reader :identifier, :author_name, :author_email, :committer_name,
20
- :committer_email, :short_message, :message,:committed_at,
21
- :authored_at, :uploaded_at, :api_uri, :parent_identifiers,
22
- :line_additions, :line_deletions, :line_total,
23
- :affected_file_count, :custom_attributes
24
-
25
- # Instantiates a new Commit object
26
- #
27
- # Accepts a Hash of options, including:
28
- # * identifier
29
- # * author_email
30
- # * author_name
31
- # * committer_email
32
- # * committer_name
33
- # * short_message
34
- # * message
35
- # * committed_at
36
- # * authored_at
37
- # * uploaded_at
38
- # * api_uri
39
- # * parent_identifiers
40
- # * line_additions
41
- # * line_deletions
42
- # * line_total
43
- # * affected_file_count
44
- # * custom_attributes
45
- #
46
- # +custom_attributes+ should be a Hash of attribute_name/value
47
- # pairs associated with the commit. All other attributes are
48
- # expected to be String values, other than +committed_at+ and
49
- # +authored_at+, which are expected to be DateTime objects.
50
- # Technically speaking, you could pass anything you wanted into
51
- # the fields, but when using with the CodeFumes API, the attribute
52
- # values will be of the type String, DateTime, or Hash.
53
- def initialize(options)
54
- @identifier = options["identifier"]
55
- @author_email = options["author_email"]
56
- @author_name = options["author_name"]
57
- @committer_email = options["committer_email"]
58
- @committer_name = options["committer_name"]
59
- @short_message = options["short_message"]
60
- @message = options["message"]
61
- @committed_at = options["committed_at"]
62
- @authored_at = options["authored_at"]
63
- @uploaded_at = options["uploaded_at"]
64
- @api_uri = options["api_uri"]
65
- @parent_identifiers = options["parent_identifiers"]
66
- @line_additions = options["line_additions"]
67
- @line_deletions = options["line_deletions"]
68
- @line_total = options["line_total"]
69
- @affected_file_count = options["affected_file_count"]
70
- @custom_attributes = options["custom_attributes"] || {}
71
- convert_custom_attributes_keys_to_symbols
72
- end
73
-
74
- # Returns the name of the author and the email associated
75
- # with the commit in a string formatted as:
76
- # "Name [email_address]"
77
- # (ie: "John Doe [jdoe@example.com]")
78
- def author
79
- "#{author_name} [#{author_email}]"
80
- end
81
-
82
- # Returns the name of the committer and the email associated
83
- # with the commit in a string formatted as:
84
- # "Name [email_address]"
85
- # (ie: "John Doe [jdoe@example.com]")
86
- def committer
87
- "#{committer_name} [#{committer_email}]"
88
- end
89
-
90
- # Returns the Commit object associated with the supplied identifier.
91
- # Returns nil if the identifier is not found.
92
- def self.find(identifier)
93
- response = get("/commits/#{identifier}")
94
- case response.code
95
- when 200
96
- return nil if response["commit"].empty?
97
- new(response["commit"])
98
- else
99
- nil
100
- end
101
- end
102
-
103
- # Returns a collection of commits associated with the specified
104
- # Project public key.
105
- def self.all(project_public_key)
106
- response = get("/projects/#{project_public_key}/commits")
107
- case response.code
108
- when 200
109
- return [] if response["commits"].empty? || response["commits"]["commit"].nil?
110
- response["commits"]["commit"].map do |commit_data|
111
- new(commit_data)
112
- end
113
- else
114
- nil
115
- end
116
- end
117
-
118
- # Returns the most recent commit associated with the specified
119
- # Project public key.
120
- def self.latest(project_public_key)
121
- response = get("/projects/#{project_public_key}/commits/latest")
122
- case response.code
123
- when 200
124
- new(response["commit"])
125
- else
126
- nil
127
- end
128
- end
129
-
130
- # Returns the commit identifier of the most recent commit of with
131
- # the specified Project public key.
132
- def self.latest_identifier(project_public_key)
133
- latest_commit = latest(project_public_key)
134
- latest_commit.nil? ? nil : latest_commit.identifier
135
- end
136
-
137
- private
138
- def convert_custom_attributes_keys_to_symbols
139
- @custom_attributes = @custom_attributes.inject({}) do |results, key_and_value|
140
- results.merge! key_and_value.first.to_sym => key_and_value.last
141
- end
142
- end
143
- end
144
- end
@@ -1,103 +0,0 @@
1
- module CodeFumes
2
- # Payloads are intended to simplify sending up large amounts of
3
- # content at one time. For example, when sending up the entire
4
- # history of a repository, making a POST request for each commit would
5
- # require a very large number of requests. Using a Payload object
6
- # allows larger amounts of content to be saved at one time,
7
- # significantly reducing the number of requests made.
8
- class Payload < CodeFumes::API
9
- PAYLOAD_CHARACTER_LIMIT = 4000 #:nodoc:
10
-
11
- attr_reader :project_public_key, :project_private_key, :created_at
12
-
13
- # Accepts +:public_key+, +:private_key+, and :content keys.
14
- # +:content+ should also contain a key named +:commits+, with a list
15
- # of commits and associated data. An example would be:
16
- #
17
- # {:public_key => "abC3", :private_key => "some-private-key",
18
- # :content => {:commits => [{:identifier => "commit_identifer",
19
- # :files_affected => 3, :custom_attributes => {:any_metric_you_want => "value"}}]}}
20
- def initialize(options = {})
21
- @project_public_key = options[:public_key]
22
- @project_private_key = options[:private_key]
23
- @content = options[:content]
24
- end
25
-
26
- # Saves instance to CodeFumes.com. After a successful save, the
27
- # +created_at+ attribute will be populated with the timestamp the
28
- # Payload was created.
29
- #
30
- # Returns +true+ if the Payload does not contain any content to be
31
- # saved or the request was successful.
32
- #
33
- # Returns +false+ if the request failed.
34
- def save
35
- return true if empty_payload?
36
- response = self.class.post("/projects/#{@project_public_key}/payloads", :query => {:payload => @content}, :basic_auth => {:username => @project_public_key, :password => @project_private_key})
37
-
38
- case response.code
39
- when 201
40
- @created_at = response['payload']['created_at']
41
- true
42
- else
43
- false
44
- end
45
- end
46
-
47
- # +save+ requests are made with a standard POST request (not a
48
- # multi-part POST), so the request size is limited by the
49
- # application server. The current configuration on CodeFumes.com
50
- # limits requests to approximately 8,000 bytes (a little over). In
51
- # order to simplify dealing with these constraints, without
52
- # requiring a multi-part POST request, +prepare+ can be used to
53
- # "chunk" up the data into Payloads which do not exceed this limit.
54
- #
55
- # Returns collection of payload objects which fall into the
56
- # constraints of a individual payload (ie: length of raw request,
57
- # et cetera).
58
- #--
59
- # TODO: Clean up how the size of the request is constrained, this
60
- # is pretty hackish right now (basically guesses how many
61
- # characters would be added when HTTParty wraps the content in XML.
62
- def self.prepare(data = {})
63
- return [] if data.nil? || data.empty?
64
- raw_payload = data.dup
65
-
66
- public_key = raw_payload.delete(:public_key)
67
- raise ArgumentError, "No public key provided" if public_key.nil?
68
-
69
- private_key = raw_payload.delete(:private_key)
70
-
71
- if raw_payload[:content].nil? || raw_payload[:content][:commits].nil?
72
- raise ArgumentError, "No commits key provided"
73
- end
74
-
75
- content = raw_payload[:content][:commits]
76
- initial_chunks = {:on_deck => [], :prepared => []}
77
-
78
- # TODO: Clean this up
79
- chunked = content.inject(initial_chunks) do |chunks, new_commit|
80
- if chunks[:on_deck].to_s.length + new_commit.to_s.length >= PAYLOAD_CHARACTER_LIMIT
81
- chunks[:prepared] << chunks[:on_deck]
82
- chunks[:on_deck] = [new_commit]
83
- elsif new_commit == content.last
84
- chunks[:on_deck] << new_commit
85
- chunks[:prepared] << chunks[:on_deck]
86
- chunks[:on_deck] = []
87
- else
88
- chunks[:on_deck] << new_commit
89
- end
90
- chunks
91
- end
92
-
93
- chunked[:prepared].map do |raw_content|
94
- Payload.new(:public_key => public_key, :private_key => private_key, :content => {:commits => raw_content})
95
- end
96
- end
97
-
98
- private
99
- def empty_payload?
100
- @content.empty? || @content[:commits].nil? || @content[:commits].blank?
101
- end
102
- end
103
- end
@@ -1,129 +0,0 @@
1
- module CodeFumes
2
- # A Project encapsulates the concept of a project on the CodeFumes.com
3
- # website. Each project has a public key, private key, and can have a
4
- # name defined. Projects are also associated with a collection of
5
- # commits from a repository.
6
- class Project < CodeFumes::API
7
- attr_reader :private_key, :short_uri, :community_uri, :api_uri, :build_status
8
- attr_accessor :name,:public_key
9
-
10
- # Accepts Hash containing the following keys:
11
- # * :public_key
12
- # * :private_key
13
- # * :name
14
- def initialize(options = {})
15
- @public_key = options[:public_key]
16
- @private_key = options[:private_key]
17
- @name = options[:name]
18
- end
19
-
20
- # Deletes project from the website.
21
- #
22
- # Returns +true+ if the request succeeded.
23
- #
24
- # Returns +false+ if the request failed.
25
- def delete
26
- response = destroy!
27
- case response.code
28
- when 200
29
- return true
30
- else
31
- return false
32
- end
33
- end
34
-
35
- # Saves project +:public_key+ to the website. If the public key
36
- # of the project has not been reserved yet, it will attempt to do
37
- # so. If the public key of the project is already in use, it will
38
- # attempt to update it with the current values.
39
- #
40
- # Returns +true+ if the request succeeded.
41
- #
42
- # Returns +false+ if the request failed.
43
- def save
44
- response = exists? ? update : create
45
- case response.code
46
- when 201, 200
47
- reinitialize!(response['project'])
48
- true
49
- else
50
- false
51
- end
52
- end
53
-
54
- # Serializes a Project instance to a format compatible with the
55
- # CodeFumes config file.
56
- def to_config
57
- project_attributes = {:api_uri => @api_uri, :short_uri => @short_uri}
58
- project_attributes[:private_key] = @private_key if @private_key
59
- {@public_key.to_sym => project_attributes}
60
- end
61
-
62
- # Verifies existence of Project on website.
63
- #
64
- # Returns +true+ if the public key of Project is available.
65
- #
66
- # Returns +false+ if the public key of the Project is not available.
67
- def exists?
68
- return false if @public_key.nil? || @public_key.empty?
69
- !self.class.find(@public_key).nil?
70
- end
71
-
72
-
73
- # Searches website for project with the supplied public key.
74
- #
75
- # Returns a Project instance if the project exists and is available,
76
- # to the user making the request.
77
- #
78
- # Returns +nil+ in all other cases.
79
- def self.find(public_key)
80
- response = get("/projects/#{public_key}")
81
- case response.code
82
- when 200
83
- project = Project.new
84
- project.reinitialize!(response['project'])
85
- else
86
- nil
87
- end
88
- end
89
-
90
- # TODO: Make this a private method
91
- def reinitialize!(options = {}) #:nodoc:
92
- @public_key = options['public_key']
93
- @private_key = options['private_key']
94
- @short_uri = options['short_uri']
95
- @community_uri = options['community_uri']
96
- @api_uri = options['api_uri']
97
- @build_status = options['build_status'].empty? ? nil : options['build_status']
98
- @build_status && @build_status.sub!(/_build/, '')
99
- self
100
- end
101
-
102
- # Attempts to claim "ownership" of the project using the API key
103
- # defined in the "credentials" section of your CodeFumes config
104
- # file.
105
- #
106
- # If you need to claim a project for a key that is not defined in
107
- # your config file, refer to Claim#create.
108
- #
109
- # Returns true if the request is successful.
110
- #
111
- # Returns +false+ in all other cases.
112
- def claim
113
- Claim.create(self, ConfigFile.api_key)
114
- end
115
-
116
- private
117
- def update
118
- self.class.put("/projects/#{@public_key}", :query => {:project => {:name => @name}}, :basic_auth => {:username => @public_key, :password => @private_key})
119
- end
120
-
121
- def create
122
- self.class.post('/projects', :query => {:project => {:name => @name, :public_key => @public_key}})
123
- end
124
-
125
- def destroy!
126
- self.class.delete("/projects/#{@public_key}", :basic_auth => {:username => @public_key, :password => @private_key})
127
- end
128
- end
129
- end
@@ -1,17 +0,0 @@
1
- require 'spec/spec_helper'
2
- require 'lib/cf_claim_project/cli'
3
-
4
- describe CfClaimProject::CLI, "execute" do
5
- before(:each) do
6
- @project = Project.new(:public_key => "pub", :private_key => "prv_key")
7
- Project.stub!(:find).and_return(@project)
8
- ConfigFile.save_credentials("sample_credentials")
9
- ConfigFile.save_project(@project)
10
- @stdout_io = StringIO.new
11
- end
12
-
13
- it "calls Claim#create" do
14
- Claim.should_receive(:create)
15
- CfClaimProject::CLI.execute(@stdout_io, [@project.public_key])
16
- end
17
- end
@@ -1,41 +0,0 @@
1
- require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
- require 'cf_release_project/cli'
3
-
4
- def delete_config_file
5
- unless ConfigFile.path == File.expand_path('~/.codefumes_config')
6
- File.delete(ConfigFile.path) if File.exist?(ConfigFile.path)
7
- end
8
- end
9
-
10
- def execute_command(args)
11
- @stdout_io = StringIO.new
12
- CfReleaseProject::CLI.execute(@stdout_io, [args])
13
- @stdout_io.rewind
14
- @stdout = @stdout_io.read
15
- end
16
-
17
-
18
- describe CfReleaseProject::CLI, "execute" do
19
- before(:each) do
20
- @api_key = "my_credentials"
21
- @project = Project.new(:public_key => "abc", :private_key => "382")
22
- ConfigFile.save_project(@project)
23
- ConfigFile.save_credentials(@api_key)
24
- Project.stub!(:find).and_return(@project)
25
- Claim.stub!(:destroy).with(@project, @api_key)
26
- end
27
-
28
- after(:all) do
29
- delete_config_file
30
- end
31
-
32
- it "deletes the claim on the project" do
33
- Claim.should_receive(:destroy).with(@project, @api_key)
34
- execute_command(@project.public_key)
35
- end
36
-
37
- it "should print default output" do
38
- execute_command(@project.public_key)
39
- @stdout.should =~ /Done/
40
- end
41
- end
@@ -1,28 +0,0 @@
1
- require 'spec/spec_helper'
2
- require 'lib/cf_store_credentials/cli'
3
-
4
- def delete_config_file
5
- unless ConfigFile.path == File.expand_path('~/.codefumes_config')
6
- File.delete(ConfigFile.path) if File.exist?(ConfigFile.path)
7
- end
8
- end
9
-
10
- describe CfStoreCredentials::CLI, "execute" do
11
- after(:all) do
12
- delete_config_file
13
- end
14
-
15
- before(:each) do
16
- delete_config_file
17
- @api_key_value = "API_KEY#{rand(100)}"
18
- @stdout_io = StringIO.new
19
- CfStoreCredentials::CLI.execute(@stdout_io, [@api_key_value])
20
- @stdout_io.rewind
21
- @stdout = @stdout_io.read
22
- end
23
-
24
- it "stores the value supplied in the config file under the key ':api_key'" do
25
- ConfigFile.credentials.keys.should include(:api_key)
26
- ConfigFile.credentials[:api_key].should == @api_key_value
27
- end
28
- end