sql_tree 0.1.1 → 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.
@@ -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