codefumes 0.1.10 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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