MangUpdate 0.0.1
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 +4 -0
- data/Gemfile +4 -0
- data/MangUpdate.gemspec +26 -0
- data/README.md +27 -0
- data/Rakefile +1 -0
- data/bin/mangupdate +4 -0
- data/lib/MangUpdate/cli.rb +66 -0
- data/lib/MangUpdate/dsl.rb +168 -0
- data/lib/MangUpdate/dumpfile.rb +44 -0
- data/lib/MangUpdate/job.rb +54 -0
- data/lib/MangUpdate/list.rb +1 -0
- data/lib/MangUpdate/model.rb +38 -0
- data/lib/MangUpdate/models/common.rb +14 -0
- data/lib/MangUpdate/models/inline_sql.rb +23 -0
- data/lib/MangUpdate/models/main_core.rb +19 -0
- data/lib/MangUpdate/models/ytdb_core.rb +20 -0
- data/lib/MangUpdate/path.rb +1 -0
- data/lib/MangUpdate/templates/Mupfile.erb +32 -0
- data/lib/MangUpdate/templating.rb +23 -0
- data/lib/MangUpdate/ui.rb +182 -0
- data/lib/MangUpdate/version.rb +3 -0
- data/lib/MangUpdate.rb +161 -0
- metadata +91 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/MangUpdate.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "MangUpdate/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "MangUpdate"
|
7
|
+
s.version = MangUpdate::VERSION
|
8
|
+
s.authors = ["MOZGIII"]
|
9
|
+
s.email = ["mike-n@narod.ru"]
|
10
|
+
s.homepage = ""
|
11
|
+
s.summary = %q{Use Mupfiles to update your MaNGOS server database.}
|
12
|
+
s.description = %q{This is a small gem that will help you with updating your MaNGOS server. It uses Mupfile to keep all database update logic simple.}
|
13
|
+
|
14
|
+
s.rubyforge_project = "MangUpdate"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
# specify any dependencies here; for example:
|
22
|
+
# s.add_development_dependency "rspec"
|
23
|
+
# s.add_runtime_dependency "rest-client"
|
24
|
+
s.add_runtime_dependency "thor"
|
25
|
+
s.add_runtime_dependency "activesupport"
|
26
|
+
end
|
data/README.md
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
MangUpdate
|
2
|
+
==========
|
3
|
+
|
4
|
+
Installation
|
5
|
+
------------
|
6
|
+
|
7
|
+
gem install mangupdate
|
8
|
+
|
9
|
+
Usage
|
10
|
+
-----
|
11
|
+
|
12
|
+
### WARNING!!!
|
13
|
+
**Always check you config with `mangupdate -nl` (dry run and list updates) before using `mangupdate`!**
|
14
|
+
|
15
|
+
- Create `Mupfile` with `mangupdate init`.
|
16
|
+
- Edit and configure `Mupfile` as you need.
|
17
|
+
- Run `mangupdate -nl` to check that lists and jobs are configured.
|
18
|
+
- Check `Mupfile.dump` file and change numbers there to your server's current revisions.
|
19
|
+
- Use `mangupdate` to run the update.
|
20
|
+
|
21
|
+
Feel free to use `mangupdate -h update` to see all the options available.
|
22
|
+
|
23
|
+
|
24
|
+
Mupfile examples
|
25
|
+
----------------
|
26
|
+
|
27
|
+
Just use `mangupdate init` to get a basic `Mupfile`.
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/bin/mangupdate
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'thor'
|
2
|
+
require 'MangUpdate/version'
|
3
|
+
|
4
|
+
module MangUpdate
|
5
|
+
|
6
|
+
class CLI < Thor
|
7
|
+
|
8
|
+
default_task :update
|
9
|
+
|
10
|
+
desc 'update', 'Runs the update'
|
11
|
+
|
12
|
+
method_option :debug,
|
13
|
+
:type => :boolean,
|
14
|
+
:default => false,
|
15
|
+
:aliases => '-d',
|
16
|
+
:banner => 'Print debug messages'
|
17
|
+
|
18
|
+
method_option :mupfile,
|
19
|
+
:type => :string,
|
20
|
+
:aliases => '-M',
|
21
|
+
:banner => 'Specify a Mupfile'
|
22
|
+
|
23
|
+
method_option :dry_run,
|
24
|
+
:type => :boolean,
|
25
|
+
:default => false,
|
26
|
+
:aliases => '-n',
|
27
|
+
:banner => 'Do not upload SQL files'
|
28
|
+
|
29
|
+
method_option :list,
|
30
|
+
:type => :boolean,
|
31
|
+
:default => false,
|
32
|
+
:aliases => '-l',
|
33
|
+
:banner => 'Print SQL filenames'
|
34
|
+
|
35
|
+
method_option :nodump,
|
36
|
+
:type => :boolean,
|
37
|
+
:default => false,
|
38
|
+
:banner => 'Do not dump update info'
|
39
|
+
|
40
|
+
def update
|
41
|
+
::MangUpdate.run(options)
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
desc 'version', 'Shows the MangUpdate version'
|
46
|
+
|
47
|
+
map %w(-v --version) => :version
|
48
|
+
|
49
|
+
def version
|
50
|
+
::MangUpdate::UI.info "MangUpdate version #{ MangUpdate::VERSION }"
|
51
|
+
end
|
52
|
+
|
53
|
+
|
54
|
+
desc 'init', 'Generates a Mupfile at the current working directory'
|
55
|
+
|
56
|
+
method_option :mupfile,
|
57
|
+
:type => :string,
|
58
|
+
:aliases => '-M',
|
59
|
+
:banner => 'Specify a Mupfile path'
|
60
|
+
|
61
|
+
def init
|
62
|
+
::MangUpdate.initialize_template(options)
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,168 @@
|
|
1
|
+
module MangUpdate
|
2
|
+
class Dsl
|
3
|
+
|
4
|
+
class << self
|
5
|
+
|
6
|
+
@@options = nil
|
7
|
+
@@configs = nil
|
8
|
+
|
9
|
+
# Evaluate the DSL methods in the `Mupfile`.
|
10
|
+
#
|
11
|
+
def evaluate_mupfile(options = {})
|
12
|
+
raise ArgumentError.new('No option hash passed to evaluate_mupfile!') unless options.is_a?(Hash)
|
13
|
+
|
14
|
+
@@options = options.dup
|
15
|
+
@@configs = {}
|
16
|
+
|
17
|
+
fetch_mupfile_contents
|
18
|
+
instance_eval_mupfile(mupfile_contents)
|
19
|
+
|
20
|
+
@@configs.each do |key, value|
|
21
|
+
@@options[key] = value unless @@options.key?(key)
|
22
|
+
end
|
23
|
+
@@options
|
24
|
+
end
|
25
|
+
|
26
|
+
def instance_eval_mupfile(contents)
|
27
|
+
new.instance_eval(contents, @@options[:mupfile_path], 1)
|
28
|
+
rescue
|
29
|
+
UI.error "Invalid Mupfile, original error is:\n#{ $! }"
|
30
|
+
raise if @@options[:debug]
|
31
|
+
exit 1
|
32
|
+
end
|
33
|
+
|
34
|
+
def read_mupfile(mupfile_path)
|
35
|
+
@@options[:mupfile_path] = mupfile_path
|
36
|
+
@@options[:mupfile_contents] = File.read(mupfile_path)
|
37
|
+
rescue
|
38
|
+
UI.error("Error reading file #{ mupfile_path }")
|
39
|
+
exit 1
|
40
|
+
end
|
41
|
+
|
42
|
+
# Get the content to evaluate and stores it into
|
43
|
+
# the options as `:mupfile_contents`.
|
44
|
+
#
|
45
|
+
def fetch_mupfile_contents
|
46
|
+
if @@options[:mupfile_contents]
|
47
|
+
UI.info 'Using inline Mupfile.'
|
48
|
+
@@options[:mupfile_path] = 'Inline Mupfile'
|
49
|
+
|
50
|
+
elsif @@options[:mupfile]
|
51
|
+
if File.exist?(@@options[:mupfile])
|
52
|
+
read_mupfile(@@options[:mupfile])
|
53
|
+
UI.info "Using Mupfile at #{ @@options[:mupfile] }."
|
54
|
+
else
|
55
|
+
UI.error "No Mupfile exists at #{ @@options[:mupfile] }."
|
56
|
+
exit 1
|
57
|
+
end
|
58
|
+
|
59
|
+
else
|
60
|
+
if File.exist?(mupfile_default_path)
|
61
|
+
read_mupfile(mupfile_default_path)
|
62
|
+
else
|
63
|
+
UI.error 'No Mupfile found, please create one with `mangupdate init`.'
|
64
|
+
exit 1
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
if mupfile_contents.empty?
|
69
|
+
UI.error "The command file(#{ @@options[:mupfile] }) seems to be empty."
|
70
|
+
exit 1
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def mupfile_contents
|
75
|
+
@@options ? @@options[:mupfile_contents] : ''
|
76
|
+
end
|
77
|
+
|
78
|
+
def mupfile_path
|
79
|
+
@@options ? @@options[:mupfile_path] : ''
|
80
|
+
end
|
81
|
+
|
82
|
+
def mupfile_default_path
|
83
|
+
File.join(Dir.pwd, 'Mupfile')
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
def initialize
|
89
|
+
@tmp = {}
|
90
|
+
end
|
91
|
+
|
92
|
+
def tmp_block(merge_options = {})
|
93
|
+
return unless block_given?
|
94
|
+
preserve_tmp = @tmp
|
95
|
+
@tmp = {} if @tmp.nil? && merge_options
|
96
|
+
@tmp = @tmp.merge(merge_options) if @tmp.respond_to?(:merge)
|
97
|
+
val = yield
|
98
|
+
@tmp = preserve_tmp
|
99
|
+
val
|
100
|
+
end
|
101
|
+
|
102
|
+
def list(name)
|
103
|
+
tmp_block({ :type => :list, :paths => [] }) do
|
104
|
+
yield if block_given?
|
105
|
+
MangUpdate.add_list(name, @tmp[:paths])
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def path(path, options = {})
|
110
|
+
raise "path must be called inside of a list block" unless @tmp[:type] == :list
|
111
|
+
@tmp[:paths] << [path, options]
|
112
|
+
end
|
113
|
+
|
114
|
+
def job(name)
|
115
|
+
# jobs are just for some extra groupping
|
116
|
+
tmp_block({ :type => :job, :applies => [] }) do
|
117
|
+
yield if block_given?
|
118
|
+
MangUpdate.add_job(name, @tmp[:applies])
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def filtered(options = { :clear => false })
|
123
|
+
raise "filtered must be called inside of a job block" unless [:job, :filtered].member?(@tmp[:type])
|
124
|
+
filters_new = []
|
125
|
+
filters_new = @tmp[:filters] if @tmp[:filters] && !options[:clear]
|
126
|
+
tmp_block({ :type => :filtered, :filters => filters_new, :jobname => @tmp[:jobname] }) do
|
127
|
+
yield if block_given?
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def filter(&block)
|
132
|
+
raise "filter must be called inside of a filtered block" unless @tmp[:type] == :filtered
|
133
|
+
@tmp[:filters] << block
|
134
|
+
end
|
135
|
+
|
136
|
+
def apply(options = {})
|
137
|
+
raise "apply must be called inside of a job block" unless [:job, :filtered].member?(@tmp[:type])
|
138
|
+
@tmp[:applies] << [:list, options, @tmp[:filters] || []]
|
139
|
+
end
|
140
|
+
|
141
|
+
def config(*args)
|
142
|
+
name = args.shift
|
143
|
+
case args.size
|
144
|
+
when 0
|
145
|
+
@@configs[name.downcase.to_sym]
|
146
|
+
when 1
|
147
|
+
@@configs[name.downcase.to_sym] = args[0]
|
148
|
+
else
|
149
|
+
raise ArgumentError, "Wrong number of arguments!"
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def build_lists
|
154
|
+
MangUpdate.build_lists
|
155
|
+
end
|
156
|
+
|
157
|
+
def upload_file(filename, database, rev = nil)
|
158
|
+
raise "upload_file must be called inside of a job block" unless @tmp[:type] == :job
|
159
|
+
@tmp[:applies] << [:file, filename, database, rev]
|
160
|
+
end
|
161
|
+
|
162
|
+
def upload_sql_string(sql, database, rev = nil)
|
163
|
+
raise "upload_sql_string must be called inside of a job block" unless @tmp[:type] == :job
|
164
|
+
@tmp[:applies] << [:inline, sql, database, rev]
|
165
|
+
end
|
166
|
+
|
167
|
+
end
|
168
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
module MangUpdate
|
4
|
+
class Dumpfile
|
5
|
+
|
6
|
+
class << self
|
7
|
+
def load(path = "Mupfile.dump")
|
8
|
+
from_hash(YAML.load(File.read(path)))
|
9
|
+
rescue
|
10
|
+
nil
|
11
|
+
end
|
12
|
+
|
13
|
+
def write(dump, path = "Mupfile.dump")
|
14
|
+
File.open(path, "wb") do |f|
|
15
|
+
f.write dump.to_hash.to_yaml
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def from_hash(hash)
|
20
|
+
dumpfile = self.new
|
21
|
+
dumpfile.revs = hash[:revs]
|
22
|
+
dumpfile
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
attr_accessor :revs
|
27
|
+
def initialize(revs = {})
|
28
|
+
@revs = revs
|
29
|
+
end
|
30
|
+
|
31
|
+
def update_revs(revs_new = {})
|
32
|
+
revs_new.each do |key, value|
|
33
|
+
@revs[key] = value if !@revs.key?(key) || @revs[key] < value
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def to_hash
|
38
|
+
{
|
39
|
+
:revs => revs
|
40
|
+
}
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module MangUpdate
|
2
|
+
class Job
|
3
|
+
attr_reader :name, :updates, :latest_revs
|
4
|
+
|
5
|
+
def initialize(name, applies)
|
6
|
+
@name = name
|
7
|
+
@applies = applies
|
8
|
+
@updates = nil
|
9
|
+
@latest_revs = {}
|
10
|
+
end
|
11
|
+
|
12
|
+
def collect_updates
|
13
|
+
@updates = []
|
14
|
+
@applies.each do |apply|
|
15
|
+
type = apply.shift
|
16
|
+
case type
|
17
|
+
when :list
|
18
|
+
options, filters = apply
|
19
|
+
|
20
|
+
updates = []
|
21
|
+
MangUpdate.lists(options).each do |list|
|
22
|
+
next unless list.built?
|
23
|
+
|
24
|
+
list.updates.each do |update|
|
25
|
+
selected = filters.all? do |filter|
|
26
|
+
filter.call(update, list)
|
27
|
+
end
|
28
|
+
|
29
|
+
if selected
|
30
|
+
@latest_revs[list.name] = update.rev
|
31
|
+
updates << update
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
@updates << updates
|
38
|
+
when :file
|
39
|
+
filename, database, rev = apply
|
40
|
+
model = Model::Common.new(filename, database, rev || 0)
|
41
|
+
@updates << [ model ]
|
42
|
+
|
43
|
+
when :inline
|
44
|
+
sql, database, rev = apply
|
45
|
+
model = Model::InlineSql.new(nil, database, rev || 0, { :sql => sql })
|
46
|
+
@updates << [ model ]
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
module MangUpdate
|
2
|
class List
|
1
3
|
|
2
4
|
attr_reader :name, :updates
|
3
5
|
attr_accessor :current_rev
|
4
6
|
|
5
7
|
def initialize(name, paths)
|
6
8
|
@name = name
|
7
9
|
@paths = []
|
8
10
|
@updates = []
|
9
11
|
@built = false
|
10
12
|
@current_rev = 0
|
11
13
|
add_paths(paths)
|
12
14
|
end
|
13
15
|
|
14
16
|
def add_paths(paths)
|
15
17
|
paths.each do |path|
|
16
18
|
_path, options = path
|
17
19
|
@paths << Path.new(_path, options)
|
18
20
|
end
|
19
21
|
end
|
20
22
|
|
21
23
|
def build!
|
22
24
|
@built = true
|
23
25
|
@paths.each do |path|
|
24
26
|
path.scan do |update|
|
25
27
|
@updates << update
|
26
28
|
end
|
27
29
|
end
|
28
30
|
sort_list!(@updates)
|
29
31
|
rescue
|
30
32
|
@built = false
|
31
33
|
raise
|
32
34
|
end
|
33
35
|
|
34
36
|
def built?
|
35
37
|
@built
|
36
38
|
end
|
37
39
|
|
38
40
|
private
|
39
41
|
|
40
42
|
# This sorts the array of updates by it's sort_data field
|
41
43
|
def sort_list!(list)
|
42
44
|
list.sort! do |a,b|
|
43
45
|
a, b = a.sort_data, b.sort_data
|
44
46
|
i = 0
|
45
47
|
i += 1 while a[i] == b[i] && (a[i+1] || b[i+1])
|
46
48
|
if a[i] && b[i]
|
47
49
|
a[i] <=> b[i]
|
48
50
|
else
|
49
51
|
a[i] ? 1 : -1
|
50
52
|
end
|
51
53
|
end
|
52
54
|
list
|
53
55
|
end
|
54
56
|
|
55
57
|
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module MangUpdate
|
2
|
+
module Model
|
3
|
+
|
4
|
+
autoload :Common, 'MangUpdate/models/common'
|
5
|
+
autoload :InlineSql, 'MangUpdate/models/inline_sql'
|
6
|
+
|
7
|
+
class Base
|
8
|
+
|
9
|
+
attr_reader :filename, :database, :rev, :info
|
10
|
+
attr_accessor :applied
|
11
|
+
|
12
|
+
def self.build(filename, options = {})
|
13
|
+
raise "Do not know how to build #{name}"
|
14
|
+
|
15
|
+
# Should be overwritten by subclasses with something like self.new ...
|
16
|
+
end
|
17
|
+
|
18
|
+
def initialize(filename, database, rev, info = {})
|
19
|
+
@filename = filename
|
20
|
+
@database = database
|
21
|
+
@rev = rev
|
22
|
+
@info = info
|
23
|
+
end
|
24
|
+
|
25
|
+
def sort_data
|
26
|
+
[rev]
|
27
|
+
end
|
28
|
+
|
29
|
+
def to_s
|
30
|
+
"Rev#{rev} [#{self.class.name}:#{filename}]"
|
31
|
+
end
|
32
|
+
|
33
|
+
def upload
|
34
|
+
MangUpdate.upload_file_to_db(filename, database)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module MangUpdate
|
2
|
+
module Model
|
3
|
+
|
4
|
+
class InlineSql < Base
|
5
|
+
attr_reader :sql
|
6
|
+
|
7
|
+
def self.build(filename, options = {})
|
8
|
+
raise "#{name} can not be used as a model"
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(filename, database, rev, info = {})
|
12
|
+
super("<Inline SQL>", database, rev, info)
|
13
|
+
@sql = @info[:sql]
|
14
|
+
end
|
15
|
+
|
16
|
+
def upload
|
17
|
+
MangUpdate.upload_inline_sql(sql, database)
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module MangUpdate
|
2
|
+
module Model
|
3
|
+
|
4
|
+
class MainCore < Base
|
5
|
+
APPLY_PRIORITY = 1
|
6
|
+
|
7
|
+
def self.build(filename, options = {})
|
8
|
+
data = File.basename(filename, '.sql').split('_')
|
9
|
+
self.new filename, data[2], data[0].to_i, :num => data[1].to_i
|
10
|
+
end
|
11
|
+
|
12
|
+
def sort_data
|
13
|
+
[rev, APPLY_PRIORITY, info[:num]]
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module MangUpdate
|
2
|
+
module Model
|
3
|
+
|
4
|
+
class YtdbCore < Base
|
5
|
+
APPLY_PRIORITY = 2
|
6
|
+
|
7
|
+
def self.build(filename, options = {})
|
8
|
+
data = File.basename(filename, '.sql').split('_')
|
9
|
+
return if data[1] == 'corepatch' # skip all corepatches...
|
10
|
+
self.new filename, data[1], data[3].gsub(/[^0-9]/, '').to_i
|
11
|
+
end
|
12
|
+
|
13
|
+
def sort_data
|
14
|
+
[rev, APPLY_PRIORITY]
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'active_support/inflector'
|
2
|
class Path
|
1
3
|
|
2
4
|
def initialize(path, options = {})
|
3
5
|
@path = path
|
4
6
|
@options = options
|
5
7
|
|
6
8
|
@model_class = get_model_class(options[:model] || :common)
|
7
9
|
raise "Unable to load model!" unless @model_class
|
8
10
|
end
|
9
11
|
|
10
12
|
def scan
|
11
13
|
Dir.glob(@path).each do |file|
|
12
14
|
model = @model_class.build(file, @options)
|
13
15
|
yield(model) if model
|
14
16
|
end
|
15
17
|
end
|
16
18
|
|
17
19
|
private
|
18
20
|
|
19
21
|
def get_model_class(name)
|
20
22
|
name = name.to_s
|
21
23
|
file_name = name.underscore
|
22
24
|
const_name = name.camelize
|
23
25
|
try_require = false
|
24
26
|
begin
|
25
27
|
require "MangUpdate/models/#{ file_name }" if try_require
|
26
28
|
::MangUpdate::Model.const_get(::MangUpdate::Model.constants.find { |c| c.to_s.downcase == const_name.downcase })
|
27
29
|
rescue TypeError
|
28
30
|
unless try_require
|
29
31
|
try_require = true
|
30
32
|
retry
|
31
33
|
else
|
32
34
|
UI.error "Could not find class MangUpdate::Model::#{ const_name }"
|
33
35
|
end
|
34
36
|
rescue LoadError => loadError
|
35
37
|
UI.error "Could not load 'MangUpdate/models/#{ file_name }' or find class MangUpdate::Model::#{ const_name }"
|
36
38
|
UI.error loadError.to_s
|
37
39
|
end
|
38
40
|
end
|
39
41
|
|
40
42
|
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
base_path = "."
|
2
|
+
mangos_path = base_path + "/mangos"
|
3
|
+
scriptdev2_path = base_path + "/scriptdev2"
|
4
|
+
|
5
|
+
config :mysql_user, "mangos"
|
6
|
+
config :mysql_pass, "mangos"
|
7
|
+
|
8
|
+
list("Main Core") do
|
9
|
+
path "#{mangos_path}/sql/updates/*.sql", :model => :main_core
|
10
|
+
end
|
11
|
+
|
12
|
+
list("Main SD2") do
|
13
|
+
path "#{scriptdev2_path}/sql/updates/r*.sql"
|
14
|
+
end
|
15
|
+
|
16
|
+
job("Update all") do
|
17
|
+
filtered do
|
18
|
+
|
19
|
+
# only apply updates that have revision number
|
20
|
+
# greater than currently installed revision
|
21
|
+
filter do |update, list|
|
22
|
+
update.rev > list.current_rev
|
23
|
+
end
|
24
|
+
|
25
|
+
apply
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
#job("Custom SQL") do
|
31
|
+
# upload_file "#{base_path}/mangos_custom.sql", "mangos"
|
32
|
+
#end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'erb'
|
2
|
+
|
3
|
+
module MangUpdate
|
4
|
+
module Templating
|
5
|
+
class << self
|
6
|
+
|
7
|
+
def init_template(options = {})
|
8
|
+
b = binding
|
9
|
+
template_filename = File.join(File.dirname(__FILE__), "templates", "Mupfile.erb")
|
10
|
+
|
11
|
+
template = ERB.new(File.read(template_filename))
|
12
|
+
template.filename = template_filename
|
13
|
+
|
14
|
+
result = template.result(b)
|
15
|
+
|
16
|
+
File.open(options[:mupfile], 'wb') do |f|
|
17
|
+
f.puts(result)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,182 @@
|
|
1
|
+
module MangUpdate
|
2
|
+
module UI
|
3
|
+
class << self
|
4
|
+
|
5
|
+
color_enabled = nil
|
6
|
+
|
7
|
+
# Show an info message.
|
8
|
+
#
|
9
|
+
# @param [String] message the message to show
|
10
|
+
# @option options [Boolean] reset whether to clean the output before
|
11
|
+
#
|
12
|
+
def info(message, options = { })
|
13
|
+
unless in_test_mode?
|
14
|
+
reset_line if options[:reset]
|
15
|
+
STDERR.puts color(message) if message != ''
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# Show a red error message that is prefixed with ERROR.
|
20
|
+
#
|
21
|
+
# @param [String] message the message to show
|
22
|
+
# @option options [Boolean] reset whether to clean the output before
|
23
|
+
#
|
24
|
+
def error(message, options = { })
|
25
|
+
unless in_test_mode?
|
26
|
+
reset_line if options[:reset]
|
27
|
+
STDERR.puts color('ERROR: ', :red) + message
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# Show a red deprecation message that is prefixed with DEPRECATION.
|
32
|
+
#
|
33
|
+
# @param [String] message the message to show
|
34
|
+
# @option options [Boolean] reset whether to clean the output before
|
35
|
+
#
|
36
|
+
def deprecation(message, options = { })
|
37
|
+
unless in_test_mode?
|
38
|
+
reset_line if options[:reset]
|
39
|
+
STDERR.puts color('DEPRECATION: ', :red) + message
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Show a debug message that is prefixed with DEBUG and a timestamp.
|
44
|
+
#
|
45
|
+
# @param [String] message the message to show
|
46
|
+
# @option options [Boolean] reset whether to clean the output before
|
47
|
+
#
|
48
|
+
def debug(message, options = { })
|
49
|
+
unless in_test_mode?
|
50
|
+
reset_line if options[:reset]
|
51
|
+
STDERR.puts color("DEBUG (#{Time.now.strftime('%T')}): ", :yellow) + message if ::MangUpdate.options && ::MangUpdate.options[:debug]
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# Reset a line.
|
56
|
+
#
|
57
|
+
def reset_line
|
58
|
+
STDERR.print(color_enabled? ? "\r\e[0m" : "\r\n")
|
59
|
+
end
|
60
|
+
|
61
|
+
# Clear the output.
|
62
|
+
#
|
63
|
+
def clear
|
64
|
+
system('clear;')
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
def in_test_mode?
|
70
|
+
ENV['MUP_ENV'] == 'test'
|
71
|
+
end
|
72
|
+
|
73
|
+
# Reset a color sequence.
|
74
|
+
#
|
75
|
+
# @deprecated
|
76
|
+
# @param [String] text the text
|
77
|
+
#
|
78
|
+
def reset_color(text)
|
79
|
+
deprecation('UI.reset_color(text) is deprecated, please use color(text, ' ') instead.')
|
80
|
+
color(text, '')
|
81
|
+
end
|
82
|
+
|
83
|
+
# Checks if color output can be enabled.
|
84
|
+
#
|
85
|
+
# @return [Boolean] whether color is enabled or not
|
86
|
+
#
|
87
|
+
def color_enabled?
|
88
|
+
if @color_enabled.nil?
|
89
|
+
if RbConfig::CONFIG['target_os'] =~ /mswin|mingw/i
|
90
|
+
if ENV['ANSICON']
|
91
|
+
@color_enabled = true
|
92
|
+
else
|
93
|
+
@color_enabled = false
|
94
|
+
end
|
95
|
+
else
|
96
|
+
@color_enabled = true
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
@color_enabled
|
101
|
+
end
|
102
|
+
|
103
|
+
# Colorizes a text message. See the constant in the UI class for possible
|
104
|
+
# color_options parameters. You can pass optionally :bright, a foreground
|
105
|
+
# color and a background color.
|
106
|
+
#
|
107
|
+
# @example
|
108
|
+
#
|
109
|
+
# color('Hello World', :red, :bright)
|
110
|
+
#
|
111
|
+
# @param [String] the text to colorize
|
112
|
+
# @param [Array] color_options the color options
|
113
|
+
#
|
114
|
+
def color(text, *color_options)
|
115
|
+
color_code = ''
|
116
|
+
color_options.each do |color_option|
|
117
|
+
color_option = color_option.to_s
|
118
|
+
if color_option != ''
|
119
|
+
if !(color_option =~ /\d+/)
|
120
|
+
color_option = const_get("ANSI_ESCAPE_#{ color_option.upcase }")
|
121
|
+
end
|
122
|
+
color_code += ';' + color_option
|
123
|
+
end
|
124
|
+
end
|
125
|
+
color_enabled? ? "\e[0#{ color_code }m#{ text }\e[0m" : text
|
126
|
+
end
|
127
|
+
|
128
|
+
end
|
129
|
+
|
130
|
+
# Brighten the color
|
131
|
+
ANSI_ESCAPE_BRIGHT = '1'
|
132
|
+
|
133
|
+
# Black foreground color
|
134
|
+
ANSI_ESCAPE_BLACK = '30'
|
135
|
+
|
136
|
+
# Red foreground color
|
137
|
+
ANSI_ESCAPE_RED = '31'
|
138
|
+
|
139
|
+
# Green foreground color
|
140
|
+
ANSI_ESCAPE_GREEN = '32'
|
141
|
+
|
142
|
+
# Yellow foreground color
|
143
|
+
ANSI_ESCAPE_YELLOW = '33'
|
144
|
+
|
145
|
+
# Blue foreground color
|
146
|
+
ANSI_ESCAPE_BLUE = '34'
|
147
|
+
|
148
|
+
# Magenta foreground color
|
149
|
+
ANSI_ESCAPE_MAGENTA = '35'
|
150
|
+
|
151
|
+
# Cyan foreground color
|
152
|
+
ANSI_ESCAPE_CYAN = '36'
|
153
|
+
|
154
|
+
# White foreground color
|
155
|
+
ANSI_ESCAPE_WHITE = '37'
|
156
|
+
|
157
|
+
# Black background color
|
158
|
+
ANSI_ESCAPE_BGBLACK = '40'
|
159
|
+
|
160
|
+
# Red background color
|
161
|
+
ANSI_ESCAPE_BGRED = '41'
|
162
|
+
|
163
|
+
# Green background color
|
164
|
+
ANSI_ESCAPE_BGGREEN = '42'
|
165
|
+
|
166
|
+
# Yellow background color
|
167
|
+
ANSI_ESCAPE_BGYELLOW = '43'
|
168
|
+
|
169
|
+
# Blue background color
|
170
|
+
ANSI_ESCAPE_BGBLUE = '44'
|
171
|
+
|
172
|
+
# Magenta background color
|
173
|
+
ANSI_ESCAPE_BGMAGENTA = '45'
|
174
|
+
|
175
|
+
# Cyan background color
|
176
|
+
ANSI_ESCAPE_BGCYAN = '46'
|
177
|
+
|
178
|
+
# White background color
|
179
|
+
ANSI_ESCAPE_BGWHITE = '47'
|
180
|
+
|
181
|
+
end
|
182
|
+
end
|
data/lib/MangUpdate.rb
ADDED
@@ -0,0 +1,161 @@
|
|
1
|
+
module MangUpdate
|
2
|
+
|
3
|
+
autoload :Dsl, 'MangUpdate/dsl'
|
4
|
+
autoload :UI, 'MangUpdate/ui'
|
5
|
+
autoload :Templating, 'MangUpdate/templating'
|
6
|
+
autoload :List, 'MangUpdate/list'
|
7
|
+
autoload :Path, 'MangUpdate/path'
|
8
|
+
autoload :Model, 'MangUpdate/model'
|
9
|
+
autoload :Job, 'MangUpdate/job'
|
10
|
+
autoload :Dumpfile, 'MangUpdate/dumpfile'
|
11
|
+
|
12
|
+
class << self
|
13
|
+
attr_accessor :options
|
14
|
+
|
15
|
+
def run(options = {})
|
16
|
+
@lists = []
|
17
|
+
@jobs = []
|
18
|
+
@loaded_dumpfile = nil
|
19
|
+
@options = Dsl.evaluate_mupfile(options)
|
20
|
+
|
21
|
+
build_lists
|
22
|
+
|
23
|
+
load_dumpfile
|
24
|
+
|
25
|
+
unless check_database_auth_info
|
26
|
+
UI.error "Database user and password must be supplied"
|
27
|
+
exit 1
|
28
|
+
end
|
29
|
+
|
30
|
+
new_dumpfile = Dumpfile.new
|
31
|
+
execute_jobs(new_dumpfile)
|
32
|
+
|
33
|
+
dump_write(new_dumpfile) unless @options[:nodump]
|
34
|
+
end
|
35
|
+
|
36
|
+
def initialize_template(options = {})
|
37
|
+
options = { :mupfile => "Mupfile" }.merge(options)
|
38
|
+
if File.exist?(options[:mupfile])
|
39
|
+
UI.error "Mupfile already exists at #{ File.expand_path(options[:mupfile]) }"
|
40
|
+
exit 1
|
41
|
+
end
|
42
|
+
|
43
|
+
Templating.init_template({ :mupfile => "Mupfile" }.merge(options))
|
44
|
+
UI.info "Mupfile generated, feel free to edit it"
|
45
|
+
end
|
46
|
+
|
47
|
+
def lists(filter = nil)
|
48
|
+
case filter
|
49
|
+
when Hash
|
50
|
+
if filter[:except]
|
51
|
+
array = filter[:except].kind_of?(Array) ? filter[:except] : [ filter[:except] ]
|
52
|
+
@lists.find_all { |list| !array.member?(list.name) }
|
53
|
+
|
54
|
+
elsif filter[:only]
|
55
|
+
array = filter[:only].kind_of?(Array) ? filter[:only] : [ filter[:only] ]
|
56
|
+
@lists.find_all { |list| array.member?(list.name) }
|
57
|
+
|
58
|
+
else
|
59
|
+
@lists
|
60
|
+
|
61
|
+
end
|
62
|
+
when String, Symbol
|
63
|
+
@lists.find { |list| list.name == filter }
|
64
|
+
when Regexp
|
65
|
+
@lists.find_all { |list| list.name.to_s =~ filter }
|
66
|
+
when Proc
|
67
|
+
@lists.find_all { |list| filter.call(list) }
|
68
|
+
else
|
69
|
+
@lists
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def add_list(name, paths)
|
74
|
+
list = lists(name)
|
75
|
+
if list.nil?
|
76
|
+
list = List.new(name, paths)
|
77
|
+
@lists << list
|
78
|
+
else
|
79
|
+
unless list.built?
|
80
|
+
list.add_paths(paths)
|
81
|
+
else
|
82
|
+
raise "List #{name} was already built, you can't add more paths to it!"
|
83
|
+
end
|
84
|
+
end
|
85
|
+
list
|
86
|
+
end
|
87
|
+
|
88
|
+
def add_job(name, uploads)
|
89
|
+
job = Job.new(name, uploads)
|
90
|
+
@jobs << job
|
91
|
+
end
|
92
|
+
|
93
|
+
def build_lists
|
94
|
+
fully_built = lists.inject(true) do |valid, list|
|
95
|
+
list.build!
|
96
|
+
valid &&= list.built?
|
97
|
+
end
|
98
|
+
|
99
|
+
raise "Some lists are not fully built!" unless fully_built
|
100
|
+
end
|
101
|
+
|
102
|
+
def execute_jobs(dumpfile)
|
103
|
+
lists.each do |list|
|
104
|
+
dumpfile.update_revs({ list.name => list.current_rev })
|
105
|
+
end
|
106
|
+
|
107
|
+
@jobs.each do |job|
|
108
|
+
UI.info "Running job: #{job.name}"
|
109
|
+
|
110
|
+
job.collect_updates
|
111
|
+
|
112
|
+
dumpfile.update_revs(job.latest_revs)
|
113
|
+
|
114
|
+
job.updates.flatten.each do |update|
|
115
|
+
UI.info update.filename if @options[:list]
|
116
|
+
update.upload unless @options[:dry_run]
|
117
|
+
end
|
118
|
+
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def check_database_auth_info
|
123
|
+
[ :mysql_user, :mysql_pass ].all? do |param|
|
124
|
+
@options[param] && !@options[param].empty?
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def upload_file_to_db(path, database)
|
129
|
+
user = @options[:mysql_user]
|
130
|
+
pass = @options[:mysql_pass]
|
131
|
+
command = %Q[mysql -u"#{user}" -p"#{pass}" -D"#{database}" < "#{path}"]
|
132
|
+
UI.debug "SYSTEM: #{command}"
|
133
|
+
system command
|
134
|
+
end
|
135
|
+
|
136
|
+
def upload_inline_sql(sql, database = nil)
|
137
|
+
user = @options[:mysql_user]
|
138
|
+
pass = @options[:mysql_pass]
|
139
|
+
|
140
|
+
command = %Q[mysql -u"#{user}" -p"#{pass}" -D"#{database}" -B -s -e "#{sql.gsub('"', '\"')}"]
|
141
|
+
UI.debug "SYSTEM: #{command}"
|
142
|
+
system command
|
143
|
+
end
|
144
|
+
|
145
|
+
def load_dumpfile
|
146
|
+
@loaded_dumpfile = Dumpfile.load
|
147
|
+
|
148
|
+
return unless @loaded_dumpfile
|
149
|
+
|
150
|
+
@loaded_dumpfile.revs.each do |key, value|
|
151
|
+
list = lists(key)
|
152
|
+
list.current_rev = value if list
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def dump_write(dump)
|
157
|
+
Dumpfile.write(dump)
|
158
|
+
end
|
159
|
+
|
160
|
+
end
|
161
|
+
end
|
metadata
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: MangUpdate
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- MOZGIII
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-11-06 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: thor
|
16
|
+
requirement: &28246692 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *28246692
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: activesupport
|
27
|
+
requirement: &28245336 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *28245336
|
36
|
+
description: This is a small gem that will help you with updating your MaNGOS server.
|
37
|
+
It uses Mupfile to keep all database update logic simple.
|
38
|
+
email:
|
39
|
+
- mike-n@narod.ru
|
40
|
+
executables:
|
41
|
+
- mangupdate
|
42
|
+
extensions: []
|
43
|
+
extra_rdoc_files: []
|
44
|
+
files:
|
45
|
+
- .gitignore
|
46
|
+
- Gemfile
|
47
|
+
- MangUpdate.gemspec
|
48
|
+
- README.md
|
49
|
+
- Rakefile
|
50
|
+
- bin/mangupdate
|
51
|
+
- lib/MangUpdate.rb
|
52
|
+
- lib/MangUpdate/cli.rb
|
53
|
+
- lib/MangUpdate/dsl.rb
|
54
|
+
- lib/MangUpdate/dumpfile.rb
|
55
|
+
- lib/MangUpdate/job.rb
|
56
|
+
- lib/MangUpdate/list.rb
|
57
|
+
- lib/MangUpdate/model.rb
|
58
|
+
- lib/MangUpdate/models/common.rb
|
59
|
+
- lib/MangUpdate/models/inline_sql.rb
|
60
|
+
- lib/MangUpdate/models/main_core.rb
|
61
|
+
- lib/MangUpdate/models/ytdb_core.rb
|
62
|
+
- lib/MangUpdate/path.rb
|
63
|
+
- lib/MangUpdate/templates/Mupfile.erb
|
64
|
+
- lib/MangUpdate/templating.rb
|
65
|
+
- lib/MangUpdate/ui.rb
|
66
|
+
- lib/MangUpdate/version.rb
|
67
|
+
homepage: ''
|
68
|
+
licenses: []
|
69
|
+
post_install_message:
|
70
|
+
rdoc_options: []
|
71
|
+
require_paths:
|
72
|
+
- lib
|
73
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
74
|
+
none: false
|
75
|
+
requirements:
|
76
|
+
- - ! '>='
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
version: '0'
|
79
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
80
|
+
none: false
|
81
|
+
requirements:
|
82
|
+
- - ! '>='
|
83
|
+
- !ruby/object:Gem::Version
|
84
|
+
version: '0'
|
85
|
+
requirements: []
|
86
|
+
rubyforge_project: MangUpdate
|
87
|
+
rubygems_version: 1.8.10
|
88
|
+
signing_key:
|
89
|
+
specification_version: 3
|
90
|
+
summary: Use Mupfiles to update your MaNGOS server database.
|
91
|
+
test_files: []
|