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.
- data/.gitignore +3 -0
- data/.infinity_test +8 -0
- data/Gemfile +2 -0
- data/LICENSE +1 -1
- data/README.rdoc +5 -3
- data/Rakefile +1 -3
- data/lib/sql_tree.rb +7 -29
- data/lib/sql_tree/node.rb +19 -7
- data/lib/sql_tree/node/begin_statement.rb +12 -0
- data/lib/sql_tree/node/commit_statement.rb +12 -0
- data/lib/sql_tree/node/expression.rb +42 -9
- data/lib/sql_tree/node/join.rb +8 -0
- data/lib/sql_tree/node/rollback_statement.rb +12 -0
- data/lib/sql_tree/node/select_declaration.rb +68 -2
- data/lib/sql_tree/node/set_query.rb +38 -0
- data/lib/sql_tree/parser.rb +8 -4
- data/lib/sql_tree/token.rb +3 -1
- data/lib/sql_tree/tokenizer.rb +12 -0
- data/spec/{lib → helpers}/matchers.rb +4 -4
- data/spec/integration/api_spec.rb +1 -1
- data/spec/integration/parse_and_generate_spec.rb +28 -1
- data/spec/spec_helper.rb +5 -21
- data/spec/unit/control_statements_spec.rb +17 -0
- data/spec/unit/delete_query_spec.rb +1 -1
- data/spec/unit/expression_node_spec.rb +39 -1
- data/spec/unit/insert_query_spec.rb +1 -1
- data/spec/unit/leaf_node_spec.rb +1 -1
- data/spec/unit/select_query_spec.rb +5 -1
- data/spec/unit/set_query_spec.rb +11 -0
- data/spec/unit/tokenizer_spec.rb +17 -1
- data/spec/unit/update_query_spec.rb +3 -3
- data/sql_tree.gemspec +7 -4
- data/tasks/{github-gem.rake → github-gem.rb} +104 -62
- metadata +82 -38
data/lib/sql_tree/token.rb
CHANGED
@@ -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
|
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)) }
|
data/lib/sql_tree/tokenizer.rb
CHANGED
@@ -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
|
7
|
-
when String
|
8
|
-
when Numeric
|
9
|
-
when Symbol
|
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
|
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
|
data/spec/spec_helper.rb
CHANGED
@@ -1,26 +1,10 @@
|
|
1
|
-
$:.reject! { |e| e.include? 'TextMate' }
|
2
|
-
$: << File.join(File.dirname(__FILE__), '..', 'lib')
|
3
|
-
|
4
1
|
require 'rubygems'
|
5
|
-
require '
|
2
|
+
require 'bundler/setup'
|
6
3
|
require 'sql_tree'
|
7
4
|
|
8
|
-
|
9
|
-
|
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
|
-
|
16
|
-
|
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
|
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
|
data/spec/unit/leaf_node_spec.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
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
|
data/spec/unit/tokenizer_spec.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
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
|
1
|
+
require 'spec_helper'
|
2
2
|
|
3
3
|
describe SQLTree::Node::UpdateQuery do
|
4
4
|
|
5
|
-
it "should parse
|
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
|
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
|
data/sql_tree.gemspec
CHANGED
@@ -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.
|
7
|
-
s.date = "
|
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(
|
26
|
-
s.test_files = %w(spec/
|
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 '
|
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
|
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 '
|
74
|
+
require 'rspec/core/rake_task'
|
72
75
|
|
73
76
|
namespace(:spec) do
|
74
77
|
desc "Verify all RSpec examples for #{gemspec.name}"
|
75
|
-
|
76
|
-
t.
|
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
|
-
|
81
|
-
t.
|
82
|
-
t.
|
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
|
-
|
87
|
-
t.
|
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
|
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
|
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
|
-
#
|
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
|
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.
|
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
|
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
|
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
|
-
|
203
|
-
|
204
|
-
|
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
|
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
|
263
|
+
sh git, 'push', '--tags', remote, remote_branch
|
216
264
|
end
|
217
265
|
|
218
|
-
|
219
|
-
|
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/
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
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
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
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
|
-
|
361
|
+
raise "Download failed with HTTP status #{response.code}!"
|
319
362
|
end
|
320
363
|
end
|
321
|
-
|
322
364
|
end
|
323
365
|
end
|