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.
- 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
|