mortar 0.1.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.
Files changed (61) hide show
  1. data/README.md +36 -0
  2. data/bin/mortar +13 -0
  3. data/lib/mortar.rb +23 -0
  4. data/lib/mortar/auth.rb +312 -0
  5. data/lib/mortar/cli.rb +54 -0
  6. data/lib/mortar/command.rb +267 -0
  7. data/lib/mortar/command/auth.rb +96 -0
  8. data/lib/mortar/command/base.rb +319 -0
  9. data/lib/mortar/command/clusters.rb +41 -0
  10. data/lib/mortar/command/describe.rb +97 -0
  11. data/lib/mortar/command/generate.rb +121 -0
  12. data/lib/mortar/command/help.rb +166 -0
  13. data/lib/mortar/command/illustrate.rb +97 -0
  14. data/lib/mortar/command/jobs.rb +174 -0
  15. data/lib/mortar/command/pigscripts.rb +45 -0
  16. data/lib/mortar/command/projects.rb +128 -0
  17. data/lib/mortar/command/validate.rb +94 -0
  18. data/lib/mortar/command/version.rb +42 -0
  19. data/lib/mortar/errors.rb +24 -0
  20. data/lib/mortar/generators/generator_base.rb +107 -0
  21. data/lib/mortar/generators/macro_generator.rb +37 -0
  22. data/lib/mortar/generators/pigscript_generator.rb +40 -0
  23. data/lib/mortar/generators/project_generator.rb +67 -0
  24. data/lib/mortar/generators/udf_generator.rb +28 -0
  25. data/lib/mortar/git.rb +233 -0
  26. data/lib/mortar/helpers.rb +488 -0
  27. data/lib/mortar/project.rb +156 -0
  28. data/lib/mortar/snapshot.rb +39 -0
  29. data/lib/mortar/templates/macro/macro.pig +14 -0
  30. data/lib/mortar/templates/pigscript/pigscript.pig +38 -0
  31. data/lib/mortar/templates/pigscript/python_udf.py +13 -0
  32. data/lib/mortar/templates/project/Gemfile +3 -0
  33. data/lib/mortar/templates/project/README.md +8 -0
  34. data/lib/mortar/templates/project/gitignore +4 -0
  35. data/lib/mortar/templates/project/macros/gitkeep +0 -0
  36. data/lib/mortar/templates/project/pigscripts/pigscript.pig +35 -0
  37. data/lib/mortar/templates/project/udfs/python/python_udf.py +13 -0
  38. data/lib/mortar/templates/udf/python_udf.py +13 -0
  39. data/lib/mortar/version.rb +20 -0
  40. data/lib/vendor/mortar/okjson.rb +598 -0
  41. data/lib/vendor/mortar/uuid.rb +312 -0
  42. data/spec/mortar/auth_spec.rb +156 -0
  43. data/spec/mortar/command/auth_spec.rb +46 -0
  44. data/spec/mortar/command/base_spec.rb +82 -0
  45. data/spec/mortar/command/clusters_spec.rb +61 -0
  46. data/spec/mortar/command/describe_spec.rb +135 -0
  47. data/spec/mortar/command/generate_spec.rb +139 -0
  48. data/spec/mortar/command/illustrate_spec.rb +140 -0
  49. data/spec/mortar/command/jobs_spec.rb +364 -0
  50. data/spec/mortar/command/pigscripts_spec.rb +70 -0
  51. data/spec/mortar/command/projects_spec.rb +165 -0
  52. data/spec/mortar/command/validate_spec.rb +119 -0
  53. data/spec/mortar/command_spec.rb +122 -0
  54. data/spec/mortar/git_spec.rb +278 -0
  55. data/spec/mortar/helpers_spec.rb +82 -0
  56. data/spec/mortar/project_spec.rb +76 -0
  57. data/spec/mortar/snapshot_spec.rb +46 -0
  58. data/spec/spec.opts +1 -0
  59. data/spec/spec_helper.rb +278 -0
  60. data/spec/support/display_message_matcher.rb +68 -0
  61. metadata +259 -0
@@ -0,0 +1,24 @@
1
+ #
2
+ # Copyright 2012 Mortar Data Inc.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+
17
+ module Mortar
18
+ class CLI
19
+ module Errors
20
+ class InvalidGithubUsername < StandardError; end
21
+
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,107 @@
1
+ #
2
+ # Copyright 2012 Mortar Data Inc.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+
17
+ require "erb"
18
+ require "fileutils"
19
+
20
+ module Mortar
21
+ module Generators
22
+ class Base
23
+ include Mortar::Helpers
24
+
25
+ def initialize()
26
+ # This is a really ugly way to turn the subclass name 'Mortar::Generators::ProjectGenerator'
27
+ # into 'project' so we can use it to find the appropriate templates folder
28
+
29
+ generator_type = self.class.name.split("::")[2].match(/.*(?=([A-Z]))/)[0].downcase
30
+ @src_path = File.expand_path("../../templates/#{generator_type}", __FILE__)
31
+ @dest_path = Dir.pwd
32
+ @rel_path = ""
33
+ @binding_variables = {}
34
+ end
35
+
36
+ def inside(folder)
37
+ rel_backup = @rel_path
38
+ @rel_path = File.join(@rel_path, folder)
39
+ yield
40
+ @rel_path = rel_backup
41
+ end
42
+
43
+ def copy_file(src_file, dest_file, options={ :recursive => false })
44
+ src_path = File.join(@src_path, @rel_path, src_file)
45
+ dest_path = File.join(@dest_path, @rel_path, dest_file)
46
+ msg = File.join(@rel_path, dest_file)[1..-1]
47
+
48
+ if File.exists?(dest_path)
49
+ if FileUtils.compare_file(src_path, dest_path)
50
+ display_identical(msg)
51
+ else
52
+ display_conflict(msg)
53
+ end
54
+ else
55
+ display_create(msg)
56
+ FileUtils.mkdir_p(File.dirname(dest_path)) if options[:recursive]
57
+ FileUtils.cp(src_path, dest_path)
58
+ end
59
+ end
60
+
61
+ def mkdir(folder, options={ :verbose => true })
62
+ dest_path = File.join(@dest_path, @rel_path, folder)
63
+ msg = File.join(@rel_path, folder)[1..-1]
64
+
65
+ if File.exists?(dest_path)
66
+ display_exists(options[:verbose] ? msg : "")
67
+ else
68
+ display_create(options[:verbose] ? msg : "")
69
+ FileUtils.mkdir(dest_path)
70
+ end
71
+ end
72
+
73
+ def generate_file(src_file, dest_file, options={ :recursive => false })
74
+ src_path = File.join(@src_path, @rel_path, src_file)
75
+ dest_path = File.join(@dest_path, @rel_path, dest_file)
76
+ msg = File.join(@rel_path, dest_file)[1..-1]
77
+
78
+ erb = ERB.new(File.read(src_path), 0, "%<>")
79
+
80
+ result = erb.result(@script_binding)
81
+
82
+
83
+ if File.exists?(dest_path)
84
+ if result == File.read(dest_path)
85
+ display_identical(msg)
86
+ else
87
+ display_conflict(msg)
88
+ end
89
+ else
90
+ FileUtils.mkdir_p(File.dirname(dest_path)) if options[:recursive]
91
+ file = File.new(dest_path, "w")
92
+ file.write(result)
93
+ file.close
94
+ display_create(msg)
95
+ end
96
+ end
97
+
98
+ protected
99
+
100
+ def set_script_binding(options)
101
+ options = options
102
+ binding
103
+ end
104
+
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,37 @@
1
+ #
2
+ # Copyright 2012 Mortar Data Inc.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+
17
+ require "mortar/generators/generator_base"
18
+ module Mortar
19
+ module Generators
20
+ class MacroGenerator < Base
21
+
22
+ def generate_macro(macro_name, project, options)
23
+ set_script_binding(macro_name, options)
24
+ generate_file "macro.pig", "macros/#{macro_name}.pig", :recursive => true
25
+ end
26
+
27
+ protected
28
+
29
+ def set_script_binding(macro_name, options)
30
+ options = options
31
+ macro_name = macro_name
32
+ @script_binding = binding
33
+ end
34
+
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,40 @@
1
+ #
2
+ # Copyright 2012 Mortar Data Inc.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+
17
+ require "mortar/generators/generator_base"
18
+ module Mortar
19
+ module Generators
20
+ class PigscriptGenerator < Base
21
+
22
+ def generate_pigscript(script_name, project, options)
23
+ set_script_binding(script_name, options)
24
+
25
+
26
+ generate_file "pigscript.pig", "pigscripts/#{script_name}.pig", :recursive => true
27
+ copy_file "python_udf.py", "udfs/python/#{script_name}.py", :recursive => true if not options[:skip_udf]
28
+ end
29
+
30
+ protected
31
+
32
+ def set_script_binding(script_name, options)
33
+ options = options
34
+ script_name = script_name
35
+ @script_binding = binding
36
+ end
37
+
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,67 @@
1
+ #
2
+ # Copyright 2012 Mortar Data Inc.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+
17
+ require "mortar/generators/generator_base"
18
+ module Mortar
19
+ module Generators
20
+ class ProjectGenerator < Base
21
+
22
+ def generate_project(project_name, options)
23
+
24
+ set_script_binding(project_name, options)
25
+ mkdir project_name, :verbose => false
26
+ @dest_path = File.join(@dest_path, project_name)
27
+
28
+ copy_file "README.md", "README.md"
29
+ copy_file "gitignore", ".gitignore"
30
+ copy_file "Gemfile", "Gemfile"
31
+
32
+ mkdir "pigscripts"
33
+
34
+ inside "pigscripts" do
35
+ generate_file "pigscript.pig", "#{project_name}.pig"
36
+ end
37
+
38
+ mkdir "macros"
39
+
40
+ inside "macros" do
41
+ copy_file "gitkeep", ".gitkeep"
42
+ end
43
+
44
+ mkdir "udfs"
45
+
46
+ inside "udfs" do
47
+ mkdir "python"
48
+ inside "python" do
49
+ copy_file "python_udf.py", "#{project_name}.py"
50
+ end
51
+ end
52
+
53
+ display_run("bundle install")
54
+ `cd #{project_name} && bundle install && cd ..`
55
+
56
+ end
57
+
58
+ protected
59
+
60
+ def set_script_binding(project_name, options)
61
+ options = options
62
+ project_name = project_name
63
+ @script_binding = binding
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,28 @@
1
+ #
2
+ # Copyright 2012 Mortar Data Inc.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+
17
+ require "mortar/generators/generator_base"
18
+ module Mortar
19
+ module Generators
20
+ class UDFGenerator < Base
21
+
22
+ def generate_python_udf(udf_name, project, options)
23
+ copy_file "python_udf.py", "udfs/python/#{udf_name}.py", :recursive => true
24
+ end
25
+
26
+ end
27
+ end
28
+ end
data/lib/mortar/git.rb ADDED
@@ -0,0 +1,233 @@
1
+ #
2
+ # Copyright 2012 Mortar Data Inc.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+
17
+ require "vendor/mortar/uuid"
18
+ require "set"
19
+
20
+ module Mortar
21
+ module Git
22
+
23
+ class GitError < RuntimeError; end
24
+
25
+ class Git
26
+
27
+ #
28
+ # core commands
29
+ #
30
+
31
+ def has_git?
32
+ %x{ git --version }
33
+ $?.success?
34
+ end
35
+
36
+ def has_dot_git?
37
+ File.directory?(".git")
38
+ end
39
+
40
+ def git(args, check_success=true, check_git_directory=true)
41
+ unless has_git?
42
+ raise GitError, "git must be installed"
43
+ end
44
+
45
+ if check_git_directory && !has_dot_git?
46
+ raise GitError, "No .git directory found"
47
+ end
48
+
49
+ flattened_args = [args].flatten.compact.join(" ")
50
+ output = %x{ git #{flattened_args} 2>&1 }.strip
51
+ success = $?.success?
52
+ if check_success && (! success)
53
+ raise GitError, "Error executing 'git #{flattened_args}':\n#{output}"
54
+ end
55
+ output
56
+ end
57
+
58
+ #
59
+ # snapshot
60
+ #
61
+
62
+ def create_snapshot_branch
63
+ # TODO: handle Ctrl-C in the middle
64
+ # TODO: can we do the equivalent of stash without changing the working directory
65
+ unless has_commits?
66
+ raise GitError, "No commits found in repository. You must do an initial commit to initialize the repository."
67
+ end
68
+
69
+ starting_branch = current_branch
70
+ snapshot_branch = "mortar-snapshot-#{Mortar::UUID.create_random.to_s}"
71
+ did_stash_changes = stash_working_dir(snapshot_branch)
72
+ begin
73
+ # checkout a new branch
74
+ git("checkout -b #{snapshot_branch}")
75
+
76
+ if did_stash_changes
77
+ # apply the topmost stash that we just created
78
+ git("stash apply stash@{0}")
79
+ end
80
+
81
+ add_untracked_files()
82
+
83
+ # commit the changes if there are any
84
+ if ! is_clean_working_directory?
85
+ git("commit -a -m \"mortar development snapshot commit\"")
86
+ end
87
+
88
+ ensure
89
+
90
+ # return to the starting branch
91
+ git("checkout #{starting_branch}")
92
+
93
+ # rebuild the original state of the working set
94
+ if did_stash_changes
95
+ git("stash pop stash@{0}")
96
+ end
97
+ end
98
+
99
+ snapshot_branch
100
+ end
101
+
102
+ #
103
+ # add
104
+ #
105
+
106
+ def add(path)
107
+ git("add #{path}")
108
+ end
109
+
110
+ def add_untracked_files
111
+ untracked_files.each do |untracked_file|
112
+ add untracked_file
113
+ end
114
+ end
115
+
116
+ #
117
+ # branch
118
+ #
119
+
120
+ def branches
121
+ git("branch")
122
+ end
123
+
124
+ def current_branch
125
+ branches.split("\n").each do |branch_listing|
126
+
127
+ # current branch will be the one that starts with *, e.g.
128
+ # not_my_current_branch
129
+ # * my_current_branch
130
+ if branch_listing =~ /^\*\s(\S*)/
131
+ return $1
132
+ end
133
+ end
134
+ raise GitError, "Unable to find current branch in list #{branches}"
135
+ end
136
+
137
+ def branch_delete(branch_name)
138
+ git("branch -D #{branch_name}")
139
+ end
140
+
141
+ #
142
+ # push
143
+ #
144
+
145
+ def push(remote_name, ref)
146
+ git("push #{remote_name} #{ref}")
147
+ end
148
+
149
+
150
+ #
151
+ # remotes
152
+ #
153
+
154
+ def remotes(git_organization)
155
+ # returns {git_remote_name => project_name}
156
+ remotes = {}
157
+ git("remote -v").split("\n").each do |remote|
158
+ name, url, method = remote.split(/\s/)
159
+ if url =~ /^git@([\w\d\.]+):#{git_organization}\/[a-zA-Z0-9]+_([\w\d-]+)\.git$$/
160
+ remotes[name] = $2
161
+ end
162
+ end
163
+
164
+ remotes
165
+ end
166
+
167
+ def remote_add(name, url)
168
+ git("remote add #{name} #{url}")
169
+ end
170
+
171
+ #
172
+ # rev-parse
173
+ #
174
+ def git_ref(refname)
175
+ git("rev-parse --verify --quiet #{refname}")
176
+ end
177
+
178
+ #
179
+ # stash
180
+ #
181
+
182
+ def stash_working_dir(stash_description)
183
+ stash_output = git("stash save --include-untracked #{stash_description}")
184
+ did_stash_changes? stash_output
185
+ end
186
+
187
+ def did_stash_changes?(stash_message)
188
+ ! (stash_message.include? "No local changes to save")
189
+ end
190
+
191
+ #
192
+ # status
193
+ #
194
+
195
+ def status
196
+ git('status --porcelain')
197
+ end
198
+
199
+
200
+ def has_commits?
201
+ # see http://stackoverflow.com/a/5492347
202
+ %x{ git rev-parse --verify --quiet HEAD }
203
+ $?.success?
204
+ end
205
+
206
+ def is_clean_working_directory?
207
+ status.empty?
208
+ end
209
+
210
+ # see https://www.kernel.org/pub/software/scm/git/docs/git-status.html#_output
211
+ GIT_STATUS_CODES__CONFLICT = Set.new ["DD", "AU", "UD", "UA", "DU", "AA", "UU"]
212
+ def has_conflicts?
213
+ def status_code(status_str)
214
+ status_str[0,2]
215
+ end
216
+
217
+ status_codes = status.split("\n").collect{|s| status_code(s)}
218
+ ! GIT_STATUS_CODES__CONFLICT.intersection(status_codes).empty?
219
+ end
220
+
221
+ def untracked_files
222
+ git("ls-files -o --exclude-standard").split("\n")
223
+ end
224
+
225
+ #
226
+ # clone
227
+ #
228
+ def clone(git_url, path="")
229
+ git("clone %s \"%s\"" % [git_url, path], true, false)
230
+ end
231
+ end
232
+ end
233
+ end