sql_tree 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -140,10 +140,12 @@ class SQLTree::Token
140
140
  RPAREN = Class.new(SQLTree::Token).new(')')
141
141
  DOT = Class.new(SQLTree::Token).new('.')
142
142
  COMMA = Class.new(SQLTree::Token).new(',')
143
+ STRING_ESCAPE = Class.new(SQLTree::Token).new('E')
143
144
 
144
145
  # A list of all the SQL reserverd keywords.
145
146
  KEYWORDS = %w{SELECT FROM WHERE GROUP HAVING ORDER DISTINCT LEFT RIGHT INNER FULL OUTER NATURAL JOIN USING
146
- AND OR NOT AS ON IS NULL BY LIKE ILIKE BETWEEN IN ASC DESC INSERT INTO VALUES DELETE UPDATE SET}
147
+ AND OR NOT AS ON IS NULL BY LIKE ILIKE BETWEEN IN ASC DESC INSERT INTO VALUES DELETE UPDATE
148
+ SET BEGIN COMMIT ROLLBACK TO INTERVAL COUNT}
147
149
 
148
150
  # Create a token for all the reserved keywords in SQL
149
151
  KEYWORDS.each { |kw| const_set(kw, Class.new(SQLTree::Token::Keyword)) }
@@ -94,6 +94,7 @@ class SQLTree::Tokenizer
94
94
  when ','; handle_token(SQLTree::Token::COMMA, &block)
95
95
  when /\d/; tokenize_number(&block)
96
96
  when "'"; tokenize_quoted_string(&block)
97
+ when 'E'; tokenize_possible_escaped_string(&block)
97
98
  when /\w/; tokenize_keyword(&block)
98
99
  when OPERATOR_CHARS; tokenize_operator(&block)
99
100
  when SQLTree.identifier_quote_char; tokenize_quoted_identifier(&block)
@@ -106,6 +107,17 @@ class SQLTree::Tokenizer
106
107
 
107
108
  alias :each :each_token
108
109
 
110
+ # Tokenizes a something that could be a 'postgresql'-styled string (e.g.
111
+ # E'foobar'). If the very next char isn't a single quote, then it falls back
112
+ # to tokenizing a keyword.
113
+ def tokenize_possible_escaped_string(&block)
114
+ if peek_char == "'"
115
+ handle_token(SQLTree::Token::STRING_ESCAPE, &block)
116
+ else
117
+ tokenize_keyword(&block)
118
+ end
119
+ end
120
+
109
121
  # Tokenizes a eyword in the code. This can either be a reserved SQL keyword
110
122
  # or a variable. This method will yield variables directly. Keywords will be
111
123
  # yielded with a delay, because they may need to be combined with other
@@ -3,10 +3,10 @@ class TokenizeTo
3
3
  def initialize(expected_tokens)
4
4
  @expected_tokens = expected_tokens.map do |t|
5
5
  case t
6
- when SQLTree::Token then t
7
- when String then SQLTree::Token::String.new(t)
8
- when Numeric then SQLTree::Token::Number.new(t)
9
- when Symbol then SQLTree::Token.const_get(t.to_s.upcase)
6
+ when SQLTree::Token; t
7
+ when String; SQLTree::Token::String.new(t)
8
+ when Numeric; SQLTree::Token::Number.new(t)
9
+ when Symbol; SQLTree::Token.const_get(t.to_s.upcase)
10
10
  else "Cannot check for this token: #{t.inspect}!"
11
11
  end
12
12
  end
@@ -1,4 +1,4 @@
1
- require "#{File.dirname(__FILE__)}/../spec_helper"
1
+ require 'spec_helper'
2
2
 
3
3
  describe SQLTree, :API do
4
4
 
@@ -1,4 +1,4 @@
1
- require "#{File.dirname(__FILE__)}/../spec_helper"
1
+ require 'spec_helper'
2
2
 
3
3
  describe SQLTree, 'parsing and generating SQL' do
4
4
 
@@ -80,4 +80,31 @@ describe SQLTree, 'parsing and generating SQL' do
80
80
  SQLTree['UPDATE table SET field1 = 123 WHERE id = 17'].to_sql.should ==
81
81
  'UPDATE "table" SET "field1" = 123 WHERE ("id" = 17)'
82
82
  end
83
+
84
+ it "should parse and generate a SET query" do
85
+ SQLTree["SET client_min_messages TO 'panic'"].to_sql.should ==
86
+ "SET \"client_min_messages\" TO 'panic'"
87
+ end
88
+
89
+ it "should parse and generate a BEGIN statement" do
90
+ SQLTree["BEGIN"].to_sql.should == "BEGIN"
91
+ end
92
+
93
+ it "should parse and generate a COMMIT statement" do
94
+ SQLTree["COMMIT"].to_sql.should == "COMMIT"
95
+ end
96
+
97
+ it "should parse and generate a COMMIT statement" do
98
+ SQLTree["ROLLBACK"].to_sql.should == "ROLLBACK"
99
+ end
100
+
101
+ it "should parse and generate a SELECT query with a function that takes star as arg" do
102
+ SQLTree["SELECT count(*) FROM jobs"].to_sql.should ==
103
+ 'SELECT COUNT(*) FROM "jobs"'
104
+ end
105
+
106
+ it "should parse and generate a LEFT OUTER JOIN query" do
107
+ SQLTree["SELECT * FROM table_a LEFT OUTER JOIN table_b ON foo = bar"].to_sql.should ==
108
+ "SELECT * FROM \"table_a\" LEFT OUTER JOIN \"table_b\" ON (\"foo\" = \"bar\")"
109
+ end
83
110
  end
@@ -1,26 +1,10 @@
1
- $:.reject! { |e| e.include? 'TextMate' }
2
- $: << File.join(File.dirname(__FILE__), '..', 'lib')
3
-
4
1
  require 'rubygems'
5
- require 'spec'
2
+ require 'bundler/setup'
6
3
  require 'sql_tree'
7
4
 
8
- module SQLTree::Spec
9
- module NodeLoader
10
- def self.const_missing(const)
11
- SQLTree::Node.const_get(const)
12
- end
13
- end
5
+ # Load helper files.
6
+ require 'helpers/matchers'
14
7
 
15
- module TokenLoader
16
- def self.const_missing(const)
17
- SQLTree::Token.const_get(const)
18
- end
19
- end
8
+ RSpec.configure do |config|
9
+ # Nothing special going on
20
10
  end
21
-
22
- Spec::Runner.configure do |config|
23
-
24
- end
25
-
26
- require "#{File.dirname(__FILE__)}/lib/matchers"
@@ -0,0 +1,17 @@
1
+ require 'spec_helper'
2
+
3
+ describe SQLTree, 'for transaction statements' do
4
+
5
+ it "should parse a BEGIN statement correctly" do
6
+ SQLTree['BEGIN'].should be_kind_of(SQLTree::Node::BeginStatement)
7
+ end
8
+
9
+ it "should parse a COMMIT statement correctly" do
10
+ SQLTree['COMMIT'].should be_kind_of(SQLTree::Node::CommitStatement)
11
+ end
12
+
13
+ it "should parse a ROLLBACK statement correctly" do
14
+ SQLTree['ROLLBACK'].should be_kind_of(SQLTree::Node::RollbackStatement)
15
+ end
16
+
17
+ end
@@ -1,4 +1,4 @@
1
- require "#{File.dirname(__FILE__)}/../spec_helper"
1
+ require 'spec_helper'
2
2
 
3
3
  describe SQLTree::Node::DeleteQuery do
4
4
 
@@ -1,4 +1,4 @@
1
- require "#{File.dirname(__FILE__)}/../spec_helper"
1
+ require 'spec_helper'
2
2
 
3
3
  describe SQLTree::Node::Expression do
4
4
 
@@ -19,6 +19,16 @@ describe SQLTree::Node::Expression do
19
19
  function.arguments.should == [SQLTree::Node::Expression::Value.new('string')]
20
20
  end
21
21
 
22
+ it "shoud parse an escaped string correctly" do
23
+ string = SQLTree::Node::Expression["E'string'"]
24
+ string.should == SQLTree::Node::Expression::Value.new("string")
25
+ end
26
+
27
+ it "should parse a postgresql interval expression correctly" do
28
+ interval = SQLTree::Node::Expression["interval '2 hours'"]
29
+ interval.value.should == "2 hours"
30
+ end
31
+
22
32
  it "should parse a logical OR expression correctly" do
23
33
  logical = SQLTree::Node::Expression["'this' OR 'that"]
24
34
  logical.operator.should == 'OR'
@@ -101,3 +111,31 @@ describe SQLTree::Node::Expression do
101
111
  end
102
112
  end
103
113
  end
114
+
115
+ describe SQLTree::Node::SelectExpression do
116
+ describe '.parse' do
117
+ it "should parse a COUNT(*) call correctly" do
118
+ count = SQLTree::Node::SelectExpression["COUNT(*)"]
119
+ count.distinct.should be_false
120
+ count.expression.should == SQLTree::Node::ALL_FIELDS
121
+ end
122
+
123
+ it "should parse a COUNT(DISTINCT *) call correctly" do
124
+ count = SQLTree::Node::SelectExpression["COUNT(DISTINCT *)"]
125
+ count.distinct.should be_true
126
+ count.expression.should == SQLTree::Node::ALL_FIELDS
127
+ end
128
+
129
+ it "should parse a COUNT(DISTINCT(*)) call correctly" do
130
+ count = SQLTree::Node::SelectExpression["COUNT(DISTINCT(*))"]
131
+ count.distinct.should be_true
132
+ count.expression.should == SQLTree::Node::ALL_FIELDS
133
+ end
134
+
135
+ it "should parse a COUNT(field) call correctly" do
136
+ count = SQLTree::Node::SelectExpression["COUNT(field)"]
137
+ count.distinct.should be_false
138
+ count.expression.should == SQLTree::Node::Expression['field']
139
+ end
140
+ end
141
+ end
@@ -1,4 +1,4 @@
1
- require "#{File.dirname(__FILE__)}/../spec_helper"
1
+ require 'spec_helper'
2
2
 
3
3
  describe SQLTree::Node::InsertQuery do
4
4
 
@@ -1,4 +1,4 @@
1
- require "#{File.dirname(__FILE__)}/../spec_helper"
1
+ require 'spec_helper'
2
2
 
3
3
  describe SQLTree::Node::Expression::Value do
4
4
 
@@ -1,4 +1,4 @@
1
- require "#{File.dirname(__FILE__)}/../spec_helper"
1
+ require 'spec_helper'
2
2
 
3
3
  describe SQLTree::Node::SelectQuery do
4
4
 
@@ -73,6 +73,10 @@ describe SQLTree::Node::Join do
73
73
  it "should parse a table alias without AS" do
74
74
  SQLTree::Node::Join['LEFT JOIN table t ON other.field = table.field'].table_alias.should == 't'
75
75
  end
76
+
77
+ it "should parse an outer join table" do
78
+ SQLTree::Node::Join['LEFT OUTER JOIN table ON other.field = table.field'].table.should == 'table'
79
+ end
76
80
  end
77
81
 
78
82
  describe SQLTree::Node::Ordering do
@@ -0,0 +1,11 @@
1
+ require 'spec_helper'
2
+
3
+ describe SQLTree::Node::SetQuery do
4
+
5
+ it "should parse a set query correctly" do
6
+ set = SQLTree::Node::SetQuery["SET foo TO 'var'"]
7
+ set.variable.should == SQLTree::Node::Expression::Field.new("foo")
8
+ set.value.should == SQLTree::Node::Expression::Value.new("var")
9
+ end
10
+
11
+ end
@@ -1,4 +1,4 @@
1
- require "#{File.dirname(__FILE__)}/../spec_helper"
1
+ require 'spec_helper'
2
2
 
3
3
  describe SQLTree::Tokenizer do
4
4
 
@@ -11,6 +11,10 @@ describe SQLTree::Tokenizer do
11
11
  SQLTree::Tokenizer.tokenize('and').should tokenize_to(:and)
12
12
  end
13
13
 
14
+ it "should tokenize a begin SQL keyword" do
15
+ SQLTree::Tokenizer.tokenize('BEGIN').should tokenize_to(:begin)
16
+ end
17
+
14
18
  it "should tokenize muliple separate keywords" do
15
19
  SQLTree::Tokenizer.tokenize('SELECT DISTINCT').should tokenize_to(:select, :distinct)
16
20
  end
@@ -58,6 +62,14 @@ describe SQLTree::Tokenizer do
58
62
  it "should tokenize commas" do
59
63
  SQLTree::Tokenizer.tokenize('a , "b"').should tokenize_to(sql_var('a'), comma, sql_var('b'))
60
64
  end
65
+
66
+ it "should tokenize postgresql string escape token" do
67
+ SQLTree::Tokenizer.tokenize("E'foo'").should tokenize_to(:string_escape, "foo")
68
+ end
69
+
70
+ it "should tokenize postgresql interval statements" do
71
+ SQLTree::Tokenizer.tokenize("interval '2 days'").should tokenize_to(:interval, "2 days")
72
+ end
61
73
  end
62
74
 
63
75
  # # Combined tokens are disabled for now;
@@ -77,6 +89,10 @@ describe SQLTree::Tokenizer do
77
89
  it "should tokenize a function call" do
78
90
  SQLTree::Tokenizer.tokenize("MD5('test')").should tokenize_to(sql_var('MD5'), lparen, 'test', rparen)
79
91
  end
92
+
93
+ it "should tokenize a posgresql SET call" do
94
+ SQLTree::Tokenizer.tokenize("SET client_min_messages TO 'panic'").should tokenize_to(:set, sql_var('client_min_messages'), :to, 'panic')
95
+ end
80
96
  end
81
97
 
82
98
  end
@@ -1,8 +1,8 @@
1
- require "#{File.dirname(__FILE__)}/../spec_helper"
1
+ require 'spec_helper'
2
2
 
3
3
  describe SQLTree::Node::UpdateQuery do
4
4
 
5
- it "should parse a delete query without WHERE clause correctly" do
5
+ it "should parse an UPDATE query without WHERE clause correctly" do
6
6
  update = SQLTree::Node::UpdateQuery["UPDATE table SET field1 = 1, field2 = 5 - 3"]
7
7
  update.table.should == SQLTree::Node::TableReference.new("table")
8
8
  update.updates.should have(2).items
@@ -13,7 +13,7 @@ describe SQLTree::Node::UpdateQuery do
13
13
  update.where.should be_nil
14
14
  end
15
15
 
16
- it "should parse a delete query without WHERE clause correctly" do
16
+ it "should parse an UPDATE query with WHERE clause correctly" do
17
17
  update = SQLTree::Node::UpdateQuery["UPDATE table SET field = 1 WHERE id = 17"]
18
18
  update.table.should == SQLTree::Node::TableReference.new("table")
19
19
  update.updates.should have(1).item
@@ -3,8 +3,8 @@ Gem::Specification.new do |s|
3
3
 
4
4
  # Do not modify the version and date values by hand, because this will
5
5
  # automatically by them gem release script.
6
- s.version = "0.1.1"
7
- s.date = "2009-10-17"
6
+ s.version = "0.2.0"
7
+ s.date = "2011-01-30"
8
8
 
9
9
  s.summary = "A pure Ruby library to represent SQL queries with a syntax tree for inspection and modification."
10
10
  s.description = <<-EOS
@@ -17,11 +17,14 @@ Gem::Specification.new do |s|
17
17
  s.email = 'willem@vanbergen.org'
18
18
  s.homepage = 'http://wiki.github.com/wvanbergen/sql_tree'
19
19
 
20
+ s.add_development_dependency('rake')
21
+ s.add_development_dependency('rspec', '~> 2')
22
+
20
23
  s.rdoc_options << '--title' << s.name << '--main' << 'README.rdoc' << '--line-numbers' << '--inline-source'
21
24
  s.extra_rdoc_files = ['README.rdoc']
22
25
 
23
26
  # Do not modify the files and test_files values by hand, because this will
24
27
  # automatically by them gem release script.
25
- s.files = %w(spec/unit/select_query_spec.rb spec/unit/insert_query_spec.rb spec/spec_helper.rb lib/sql_tree/tokenizer.rb lib/sql_tree/node/join.rb .gitignore lib/sql_tree/node/ordering.rb LICENSE spec/lib/matchers.rb lib/sql_tree/parser.rb sql_tree.gemspec spec/unit/tokenizer_spec.rb spec/unit/expression_node_spec.rb spec/unit/delete_query_spec.rb spec/unit/leaf_node_spec.rb lib/sql_tree/token.rb lib/sql_tree/node/table_reference.rb lib/sql_tree/node/source.rb lib/sql_tree/node/insert_query.rb Rakefile tasks/github-gem.rake spec/unit/update_query_spec.rb spec/integration/parse_and_generate_spec.rb lib/sql_tree/node/select_query.rb lib/sql_tree/node.rb README.rdoc spec/integration/api_spec.rb lib/sql_tree/node/expression.rb lib/sql_tree/node/delete_query.rb lib/sql_tree/node/select_declaration.rb lib/sql_tree.rb lib/sql_tree/node/update_query.rb)
26
- s.test_files = %w(spec/unit/select_query_spec.rb spec/unit/insert_query_spec.rb spec/unit/tokenizer_spec.rb spec/unit/expression_node_spec.rb spec/unit/delete_query_spec.rb spec/unit/leaf_node_spec.rb spec/unit/update_query_spec.rb spec/integration/parse_and_generate_spec.rb spec/integration/api_spec.rb)
28
+ s.files = %w(.gitignore .infinity_test Gemfile LICENSE README.rdoc Rakefile lib/sql_tree.rb lib/sql_tree/node.rb lib/sql_tree/node/begin_statement.rb lib/sql_tree/node/commit_statement.rb lib/sql_tree/node/delete_query.rb lib/sql_tree/node/expression.rb lib/sql_tree/node/insert_query.rb lib/sql_tree/node/join.rb lib/sql_tree/node/ordering.rb lib/sql_tree/node/rollback_statement.rb lib/sql_tree/node/select_declaration.rb lib/sql_tree/node/select_query.rb lib/sql_tree/node/set_query.rb lib/sql_tree/node/source.rb lib/sql_tree/node/table_reference.rb lib/sql_tree/node/update_query.rb lib/sql_tree/parser.rb lib/sql_tree/token.rb lib/sql_tree/tokenizer.rb spec/helpers/matchers.rb spec/integration/api_spec.rb spec/integration/parse_and_generate_spec.rb spec/spec_helper.rb spec/unit/control_statements_spec.rb spec/unit/delete_query_spec.rb spec/unit/expression_node_spec.rb spec/unit/insert_query_spec.rb spec/unit/leaf_node_spec.rb spec/unit/select_query_spec.rb spec/unit/set_query_spec.rb spec/unit/tokenizer_spec.rb spec/unit/update_query_spec.rb sql_tree.gemspec tasks/github-gem.rb)
29
+ s.test_files = %w(spec/integration/api_spec.rb spec/integration/parse_and_generate_spec.rb spec/unit/control_statements_spec.rb spec/unit/delete_query_spec.rb spec/unit/expression_node_spec.rb spec/unit/insert_query_spec.rb spec/unit/leaf_node_spec.rb spec/unit/select_query_spec.rb spec/unit/set_query_spec.rb spec/unit/tokenizer_spec.rb spec/unit/update_query_spec.rb)
27
30
  end
@@ -2,7 +2,7 @@ require 'rubygems'
2
2
  require 'rake'
3
3
  require 'rake/tasklib'
4
4
  require 'date'
5
- require 'git'
5
+ require 'set'
6
6
 
7
7
  module GithubGem
8
8
 
@@ -24,7 +24,7 @@ module GithubGem
24
24
 
25
25
  class RakeTasks
26
26
 
27
- attr_reader :gemspec, :modified_files, :git
27
+ attr_reader :gemspec, :modified_files
28
28
  attr_accessor :gemspec_file, :task_namespace, :main_include, :root_dir, :spec_pattern, :test_pattern, :remote, :remote_branch, :local_branch
29
29
 
30
30
  # Initializes the settings, yields itself for configuration
@@ -33,7 +33,7 @@ module GithubGem
33
33
  @gemspec_file = GithubGem.detect_gemspec_file
34
34
  @task_namespace = task_namespace
35
35
  @main_include = GithubGem.detect_main_include
36
- @modified_files = []
36
+ @modified_files = Set.new
37
37
  @root_dir = Dir.pwd
38
38
  @test_pattern = 'test/**/*_test.rb'
39
39
  @spec_pattern = 'spec/**/*_spec.rb'
@@ -43,13 +43,16 @@ module GithubGem
43
43
 
44
44
  yield(self) if block_given?
45
45
 
46
- @git = Git.open(@root_dir)
47
46
  load_gemspec!
48
47
  define_tasks!
49
48
  end
50
49
 
51
50
  protected
52
51
 
52
+ def git
53
+ @git ||= ENV['GIT'] || 'git'
54
+ end
55
+
53
56
  # Define Unit test tasks
54
57
  def define_test_tasks!
55
58
  require 'rake/testtask'
@@ -68,23 +71,23 @@ module GithubGem
68
71
 
69
72
  # Defines RSpec tasks
70
73
  def define_rspec_tasks!
71
- require 'spec/rake/spectask'
74
+ require 'rspec/core/rake_task'
72
75
 
73
76
  namespace(:spec) do
74
77
  desc "Verify all RSpec examples for #{gemspec.name}"
75
- Spec::Rake::SpecTask.new(:basic) do |t|
76
- t.spec_files = FileList[spec_pattern]
78
+ RSpec::Core::RakeTask.new(:basic) do |t|
79
+ t.pattern = spec_pattern
77
80
  end
78
81
 
79
82
  desc "Verify all RSpec examples for #{gemspec.name} and output specdoc"
80
- Spec::Rake::SpecTask.new(:specdoc) do |t|
81
- t.spec_files = FileList[spec_pattern]
82
- t.spec_opts << '--format' << 'specdoc' << '--color'
83
+ RSpec::Core::RakeTask.new(:specdoc) do |t|
84
+ t.pattern = spec_pattern
85
+ t.rspec_opts = ['--format', 'documentation', '--color']
83
86
  end
84
87
 
85
88
  desc "Run RCov on specs for #{gemspec.name}"
86
- Spec::Rake::SpecTask.new(:rcov) do |t|
87
- t.spec_files = FileList[spec_pattern]
89
+ RSpec::Core::RakeTask.new(:rcov) do |t|
90
+ t.pattern = spec_pattern
88
91
  t.rcov = true
89
92
  t.rcov_opts = ['--exclude', '"spec/*,gems/*"', '--rails']
90
93
  end
@@ -119,23 +122,43 @@ module GithubGem
119
122
  checks = [:check_current_branch, :check_clean_status, :check_not_diverged, :check_version]
120
123
  checks.unshift('spec:basic') if has_specs?
121
124
  checks.unshift('test:basic') if has_tests?
122
- checks.push << [:check_rubyforge] if gemspec.rubyforge_project
125
+ # checks.push << [:check_rubyforge] if gemspec.rubyforge_project
123
126
 
124
127
  desc "Perform all checks that would occur before a release"
125
128
  task(:release_checks => checks)
126
129
 
127
- release_tasks = [:release_checks, :set_version, :build, :github_release]
128
- release_tasks << [:rubyforge_release] if gemspec.rubyforge_project
130
+ release_tasks = [:release_checks, :set_version, :build, :github_release, :gemcutter_release]
131
+ # release_tasks << [:rubyforge_release] if gemspec.rubyforge_project
129
132
 
130
- desc "Release a new verison of the gem"
133
+ desc "Release a new version of the gem using the VERSION environment variable"
131
134
  task(:release => release_tasks) { release_task }
135
+
136
+ namespace(:release) do
137
+ desc "Release the next version of the gem, by incrementing the last version segment by 1"
138
+ task(:next => [:next_version] + release_tasks) { release_task }
139
+
140
+ desc "Release the next version of the gem, using a patch increment (0.0.1)"
141
+ task(:patch => [:next_patch_version] + release_tasks) { release_task }
142
+
143
+ desc "Release the next version of the gem, using a minor increment (0.1.0)"
144
+ task(:minor => [:next_minor_version] + release_tasks) { release_task }
145
+
146
+ desc "Release the next version of the gem, using a major increment (1.0.0)"
147
+ task(:major => [:next_major_version] + release_tasks) { release_task }
148
+ end
132
149
 
133
- task(:check_rubyforge) { check_rubyforge_task }
134
- task(:rubyforge_release) { rubyforge_release_task }
150
+ # task(:check_rubyforge) { check_rubyforge_task }
151
+ # task(:rubyforge_release) { rubyforge_release_task }
152
+ task(:gemcutter_release) { gemcutter_release_task }
135
153
  task(:github_release => [:commit_modified_files, :tag_version]) { github_release_task }
136
154
  task(:tag_version) { tag_version_task }
137
155
  task(:commit_modified_files) { commit_modified_files_task }
138
156
 
157
+ task(:next_version) { next_version_task }
158
+ task(:next_patch_version) { next_version_task(:patch) }
159
+ task(:next_minor_version) { next_version_task(:minor) }
160
+ task(:next_major_version) { next_version_task(:major) }
161
+
139
162
  desc "Updates the gem release tasks with the latest version on Github"
140
163
  task(:update_tasks) { update_tasks_task }
141
164
  end
@@ -145,7 +168,7 @@ module GithubGem
145
168
  # in the repository and the spec/test file pattern.
146
169
  def manifest_task
147
170
  # Load all the gem's files using "git ls-files"
148
- repository_files = git.ls_files.keys
171
+ repository_files = `#{git} ls-files`.split("\n")
149
172
  test_files = Dir[test_pattern] + Dir[spec_pattern]
150
173
 
151
174
  update_gemspec(:files, repository_files)
@@ -159,6 +182,32 @@ module GithubGem
159
182
  sh "mv #{gemspec.name}-#{gemspec.version}.gem pkg/#{gemspec.name}-#{gemspec.version}.gem"
160
183
  end
161
184
 
185
+ def newest_version
186
+ `#{git} tag`.split("\n").map { |tag| tag.split('-').last }.compact.map { |v| Gem::Version.new(v) }.max || Gem::Version.new('0.0.0')
187
+ end
188
+
189
+ def next_version(increment = nil)
190
+ next_version = newest_version.segments
191
+ increment_index = case increment
192
+ when :micro then 3
193
+ when :patch then 2
194
+ when :minor then 1
195
+ when :major then 0
196
+ else next_version.length - 1
197
+ end
198
+
199
+ next_version[increment_index] ||= 0
200
+ next_version[increment_index] = next_version[increment_index].succ
201
+ ((increment_index + 1)...next_version.length).each { |i| next_version[i] = 0 }
202
+
203
+ Gem::Version.new(next_version.join('.'))
204
+ end
205
+
206
+ def next_version_task(increment = nil)
207
+ ENV['VERSION'] = next_version(increment).version
208
+ puts "Releasing version #{ENV['VERSION']}..."
209
+ end
210
+
162
211
  # Updates the version number in the gemspec file, the VERSION constant in the main
163
212
  # include file and the contents of the VERSION file.
164
213
  def version_task
@@ -171,70 +220,58 @@ module GithubGem
171
220
 
172
221
  def check_version_task
173
222
  raise "#{ENV['VERSION']} is not a valid version number!" if ENV['VERSION'] && !Gem::Version.correct?(ENV['VERSION'])
174
- proposed_version = Gem::Version.new(ENV['VERSION'] || gemspec.version)
175
- # Loads the latest version number using the created tags
176
- newest_version = git.tags.map { |tag| tag.name.split('-').last }.compact.map { |v| Gem::Version.new(v) }.max
177
- raise "This version (#{proposed_version}) is not higher than the highest tagged version (#{newest_version})" if newest_version && newest_version >= proposed_version
223
+ proposed_version = Gem::Version.new((ENV['VERSION'] || gemspec.version).dup)
224
+ raise "This version (#{proposed_version}) is not higher than the highest tagged version (#{newest_version})" if newest_version >= proposed_version
178
225
  end
179
226
 
180
227
  # Checks whether the current branch is not diverged from the remote branch
181
228
  def check_not_diverged_task
182
- raise "The current branch is diverged from the remote branch!" if git.log.between('HEAD', git.remote(remote).branch(remote_branch).gcommit).any?
229
+ raise "The current branch is diverged from the remote branch!" if `#{git} rev-list HEAD..#{remote}/#{remote_branch}`.split("\n").any?
183
230
  end
184
231
 
185
232
  # Checks whether the repository status ic clean
186
233
  def check_clean_status_task
187
- raise "The current working copy contains modifications" if git.status.changed.any?
234
+ raise "The current working copy contains modifications" if `#{git} ls-files -m`.split("\n").any?
188
235
  end
189
236
 
190
237
  # Checks whether the current branch is correct
191
238
  def check_current_branch_task
192
- raise "Currently not on #{local_branch} branch!" unless git.branch.name == local_branch.to_s
239
+ raise "Currently not on #{local_branch} branch!" unless `#{git} branch`.split("\n").detect { |b| /^\* / =~ b } == "* #{local_branch}"
193
240
  end
194
241
 
195
242
  # Fetches the latest updates from Github
196
243
  def fetch_origin_task
197
- git.fetch('origin')
244
+ sh git, 'fetch', remote
198
245
  end
199
246
 
200
247
  # Commits every file that has been changed by the release task.
201
248
  def commit_modified_files_task
202
- if modified_files.any?
203
- modified_files.each { |file| git.add(file) }
204
- git.commit("Released #{gemspec.name} gem version #{gemspec.version}")
249
+ really_modified = `#{git} ls-files -m #{modified_files.entries.join(' ')}`.split("\n")
250
+ if really_modified.any?
251
+ really_modified.each { |file| sh git, 'add', file }
252
+ sh git, 'commit', '-m', "Released #{gemspec.name} gem version #{gemspec.version}."
205
253
  end
206
254
  end
207
255
 
208
256
  # Adds a tag for the released version
209
257
  def tag_version_task
210
- git.add_tag("#{gemspec.name}-#{gemspec.version}")
258
+ sh git, 'tag', '-a', "#{gemspec.name}-#{gemspec.version}", '-m', "Released #{gemspec.name} gem version #{gemspec.version}."
211
259
  end
212
260
 
213
261
  # Pushes the changes and tag to github
214
262
  def github_release_task
215
- git.push(remote, remote_branch, true)
263
+ sh git, 'push', '--tags', remote, remote_branch
216
264
  end
217
265
 
218
- # Checks whether Rubyforge is configured properly
219
- def check_rubyforge_task
220
- # Login no longer necessary when using rubyforge 2.0.0 gem
221
- # raise "Could not login on rubyforge!" unless `rubyforge login 2>&1`.strip.empty?
222
- output = `rubyforge names`.split("\n")
223
- raise "Rubyforge group not found!" unless output.any? { |line| %r[^groups\s*\:.*\b#{Regexp.quote(gemspec.rubyforge_project)}\b.*] =~ line }
224
- raise "Rubyforge package not found!" unless output.any? { |line| %r[^packages\s*\:.*\b#{Regexp.quote(gemspec.name)}\b.*] =~ line }
225
- end
226
-
227
- # Task to release the .gem file toRubyforge.
228
- def rubyforge_release_task
229
- sh 'rubyforge', 'add_release', gemspec.rubyforge_project, gemspec.name, gemspec.version.to_s, "pkg/#{gemspec.name}-#{gemspec.version}.gem"
266
+ def gemcutter_release_task
267
+ sh "gem", 'push', "pkg/#{gemspec.name}-#{gemspec.version}.gem"
230
268
  end
231
269
 
232
270
  # Gem release task.
233
271
  # All work is done by the task's dependencies, so just display a release completed message.
234
272
  def release_task
235
273
  puts
236
- puts '------------------------------------------------------------'
237
- puts "Released #{gemspec.name} version #{gemspec.version}"
274
+ puts "Release successful."
238
275
  end
239
276
 
240
277
  private
@@ -294,30 +331,35 @@ module GithubGem
294
331
 
295
332
  # Reload the gemspec so the changes are incorporated
296
333
  load_gemspec!
334
+
335
+ # Also mark the Gemfile.lock file as changed because of the new version.
336
+ modified_files << 'Gemfile.lock' if File.exist?(File.join(root_dir, 'Gemfile.lock'))
297
337
  end
298
338
  end
299
339
 
300
340
  # Updates the tasks file using the latest file found on Github
301
341
  def update_tasks_task
302
- require 'net/http'
303
-
304
- server = 'github.com'
305
- path = '/wvanbergen/github-gem/raw/master/tasks/github-gem.rake'
306
-
307
- Net::HTTP.start(server) do |http|
308
- response = http.get(path)
342
+ require 'net/https'
343
+ require 'uri'
344
+
345
+ uri = URI.parse('https://github.com/wvanbergen/github-gem/raw/master/tasks/github-gem.rake')
346
+ http = Net::HTTP.new(uri.host, uri.port)
347
+ http.use_ssl = true
348
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
349
+ response = http.request(Net::HTTP::Get.new(uri.path))
350
+
351
+ if Net::HTTPSuccess === response
309
352
  open(__FILE__, "w") { |file| file.write(response.body) }
310
- end
311
-
312
- relative_file = File.expand_path(__FILE__).sub(%r[^#{git.dir.path}/], '')
313
- if git.status[relative_file] && git.status[relative_file].type == 'M'
314
- git.add(relative_file)
315
- git.commit("Updated to latest gem release management tasks.")
316
- puts "Updated to latest version of gem release management tasks."
353
+ relative_file = File.expand_path(__FILE__).sub(%r[^#{@root_dir}/], '')
354
+ if `#{git} ls-files -m #{relative_file}`.split("\n").any?
355
+ sh git, 'add', relative_file
356
+ sh git, 'commit', '-m', "Updated to latest gem release management tasks."
357
+ else
358
+ puts "Release managament tasks already are at the latest version."
359
+ end
317
360
  else
318
- puts "Release managament tasks already are at the latest version."
361
+ raise "Download failed with HTTP status #{response.code}!"
319
362
  end
320
363
  end
321
-
322
364
  end
323
365
  end