mortar 0.6.2 → 0.7.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.
@@ -0,0 +1,184 @@
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/local/installutil"
18
+
19
+ class Mortar::Local::Python
20
+ include Mortar::Local::InstallUtil
21
+
22
+ PYTHON_DEFAULT_TGZ_URL = "https://s3.amazonaws.com/mortar-public-artifacts/mortar-python-osx.tgz"
23
+
24
+ # Path to the python binary that should be used
25
+ # for running UDFs
26
+ @command = nil
27
+
28
+
29
+ # Execute either an installation of python or an inspection
30
+ # of the local system to see if a usable python is available
31
+ def check_or_install
32
+ if osx?
33
+ # We currently only install python for osx
34
+ install_python_osx
35
+ else
36
+ # Otherwise we check that the system supplied python will be sufficient
37
+ check_system_python
38
+ end
39
+ end
40
+
41
+ # Performs an installation of python specific to this project, this
42
+ # install includes pip and virtualenv
43
+ def install_python_osx
44
+ @command = "#{local_install_directory}/python/bin/python"
45
+ if should_do_python_install?
46
+ FileUtils.mkdir_p(local_install_directory)
47
+ action "Installing python" do
48
+ download_file(python_archive_url, local_install_directory)
49
+ extract_tgz(local_install_directory + "/" + python_archive_file, local_install_directory)
50
+
51
+ # This has been seening coming out of the tgz w/o +x so we do
52
+ # here to be sure it has the necessary permissions
53
+ FileUtils.chmod(0755, @command)
54
+ File.delete(local_install_directory + "/" + python_archive_file)
55
+ note_install("python")
56
+ end
57
+ end
58
+ true
59
+ end
60
+
61
+ # Determines if a python install needs to occur, true if no
62
+ # python install present or a newer version is available
63
+ def should_do_python_install?
64
+ return (osx? and (not (File.exists?(python_directory))))
65
+ end
66
+
67
+
68
+ # Checks if there is a usable versionpython already installed
69
+ def check_system_python
70
+ py_cmd = path_to_local_python()
71
+ if not py_cmd
72
+ false
73
+ else
74
+ @command = py_cmd
75
+ true
76
+ end
77
+ end
78
+
79
+ # Checks if the specified python command has
80
+ # virtualenv installed
81
+ def check_virtualenv_installed(python)
82
+ `#{python} -m virtualenv --help`
83
+ if (0 != $?.to_i)
84
+ false
85
+ else
86
+ true
87
+ end
88
+ end
89
+
90
+ def path_to_local_python
91
+ # Check several python commands in decending level of desirability
92
+ [ "python#{desired_python_minor_version}", "python" ].each{ |cmd|
93
+ path_to_python = `which #{cmd}`.to_s.strip
94
+ if path_to_python != ''
95
+ # todo: check for a minimum version (in the case of 'python')
96
+ if check_virtualenv_installed(path_to_python)
97
+ return path_to_python
98
+ end
99
+ end
100
+ }
101
+ return nil
102
+ end
103
+
104
+
105
+ def desired_python_minor_version
106
+ return "2.7"
107
+ end
108
+
109
+ def pip_requirements_path
110
+ return ENV.fetch('PIP_REQ_FILE', File.join(Dir.getwd, "udfs", "python", "requirements.txt"))
111
+ end
112
+
113
+ def has_python_requirements
114
+ return File.exists?(pip_requirements_path)
115
+ end
116
+
117
+ def python_env_dir
118
+ return "#{local_install_directory}/pythonenv"
119
+ end
120
+
121
+ def python_directory
122
+ return "#{local_install_directory}/python"
123
+ end
124
+
125
+ def python_archive_url
126
+ return ENV.fetch('PYTHON_DISTRO_URL', PYTHON_DEFAULT_TGZ_URL)
127
+ end
128
+
129
+ def python_archive_file
130
+ File.basename(python_archive_url)
131
+ end
132
+
133
+ # Creates a virtualenv in a well known location and installs any packages
134
+ # necessary for the users python udf
135
+ def setup_project_python_environment
136
+ `#{@command} -m virtualenv #{python_env_dir}`
137
+ if 0 != $?.to_i
138
+ return false
139
+ end
140
+ if should_do_requirements_install
141
+ action "Installing python UDF dependencies" do
142
+ pip_output = `. #{python_env_dir}/bin/activate &&
143
+ #{python_env_dir}/bin/pip install --requirement #{pip_requirements_path}`
144
+ if 0 != $?.to_i
145
+ File.open(pip_error_log_path, 'w') { |f|
146
+ f.write(pip_output)
147
+ }
148
+ return false
149
+ end
150
+ note_install("pythonenv")
151
+ end
152
+ end
153
+ return true
154
+ end
155
+
156
+ def pip_error_log_path
157
+ return ENV.fetch('PIP_ERROR_LOG', "dependency_install.log")
158
+ end
159
+
160
+ # Whether or not we need to do a `pip install -r requirements.txt` because
161
+ # we've never done one before or the dependencies have changed
162
+ def should_do_requirements_install
163
+ if has_python_requirements
164
+ if not install_date('pythonenv')
165
+ # We've never done an install from requirements.txt before
166
+ return true
167
+ else
168
+ return (requirements_edit_date > install_date('pythonenv'))
169
+ end
170
+ else
171
+ return false
172
+ end
173
+ end
174
+
175
+ # Date of last change to the requirements file
176
+ def requirements_edit_date
177
+ if has_python_requirements
178
+ return File.mtime(pip_requirements_path).to_i
179
+ else
180
+ return nil
181
+ end
182
+ end
183
+
184
+ end
@@ -57,6 +57,19 @@ module Mortar
57
57
  ".pig")
58
58
  @pigscripts
59
59
  end
60
+
61
+ def controlscripts_path
62
+ File.join(@root_path, "controlscripts")
63
+ end
64
+
65
+ def controlscripts
66
+ @controlscripts ||= ControlScripts.new(
67
+ controlscripts_path,
68
+ "controlscripts",
69
+ ".py",
70
+ :optional => true)
71
+ @controlscripts
72
+ end
60
73
 
61
74
  def tmp_path
62
75
  path = File.join(@root_path, "tmp")
@@ -75,10 +88,11 @@ module Mortar
75
88
 
76
89
  include Enumerable
77
90
 
78
- def initialize(path, name, filename_extension)
91
+ def initialize(path, name, filename_extension, optional=false)
79
92
  @path = path
80
93
  @name = name
81
94
  @filename_extension = filename_extension
95
+ @optional = optional
82
96
  @elements = elements
83
97
  end
84
98
 
@@ -107,14 +121,15 @@ module Mortar
107
121
  end
108
122
 
109
123
  def elements
110
- unless File.directory? @path
111
- raise ProjectError, "Unable to find #{@name} directory in project"
124
+ if File.directory? @path
125
+ # get {script_name => full_path}
126
+ file_paths = Dir[File.join(@path, "**", "*#{@filename_extension}")]
127
+ file_paths_hsh = file_paths.collect{|element_path| [element_name(element_path), element(element_name(element_path), element_path)]}.flatten
128
+ return Hash[*file_paths_hsh]
129
+ else
130
+ raise ProjectError, "Unable to find #{@name} directory in project" if not @optional
112
131
  end
113
-
114
- # get {script_name => full_path}
115
- file_paths = Dir[File.join(@path, "**", "*#{@filename_extension}")]
116
- file_paths_hsh = file_paths.collect{|element_path| [element_name(element_path), element(element_name(element_path), element_path)]}.flatten
117
- Hash[*file_paths_hsh]
132
+ return Hash[]
118
133
  end
119
134
 
120
135
  def element(path)
@@ -124,7 +139,13 @@ module Mortar
124
139
 
125
140
  class PigScripts < ProjectEntity
126
141
  def element(name, path)
127
- Script.new(name, path)
142
+ PigScript.new(name, path)
143
+ end
144
+ end
145
+
146
+ class ControlScripts < ProjectEntity
147
+ def element(name, path)
148
+ ControlScript.new(name, path)
128
149
  end
129
150
  end
130
151
 
@@ -156,5 +177,10 @@ module Mortar
156
177
  end
157
178
  end
158
179
 
180
+ class ControlScript < Script
181
+ end
182
+ class PigScript < Script
183
+ end
184
+
159
185
  end
160
186
  end
@@ -2,4 +2,6 @@ Gemfile.lock
2
2
  *.pyc
3
3
  *.class
4
4
  *.log
5
- tmp
5
+ tmp
6
+ log
7
+ .mortar-local
@@ -0,0 +1,96 @@
1
+
2
+ <html>
3
+ <head>
4
+ <title>Illustrate Report</title>
5
+ <link rel="stylesheet" href="resources/css/illustrate-output.css" type="text/css" />
6
+ </head>
7
+
8
+ <body>
9
+
10
+ <% @tables.each_with_index do |table, table_index| %>
11
+
12
+ <h3 class="illustrate_alias">
13
+ <% if (table["op"] == "LOStore") %>
14
+ Store :
15
+ <% end %>
16
+ <%= table["alias"] %>
17
+ <% if (table["notices"]) %>
18
+ &nbsp;
19
+ <% end %>
20
+ </h3>
21
+
22
+ <table class="table table-bordered illustrate_table expandable">
23
+ <tbody>
24
+ <tr>
25
+ <% table["fields"].each do |field| %>
26
+ <th><%= field %></th>
27
+ <% end %>
28
+ </tr>
29
+
30
+ <% table["data"].each_with_index do |row, row_index| %>
31
+ <tr>
32
+ <% row.each_with_index do |value, item_index| %>
33
+ <% if (value) %>
34
+ <td>
35
+ <div class="mortar-table-expandable-cell-wrapper">
36
+ <div class="mortar-table-expandable-cell-container">
37
+ <div class="mortar-table-expandable-cell-preview">
38
+ <%# <%- $.mortar_data.truncate.truncate_center(value, 30) %>
39
+ <%= value[0..30] %>
40
+ </div>
41
+ <div class="mortar-table-expandable-cell-content" hidden="true">
42
+ <!-- TODO: get clipboard working -->
43
+ <!-- <div data-clipboard-target="copy-<%- table_index %>-<%- row_index %>-<%- item_index %>" class="clipboard" data->Copy to Clipboard</div> -->
44
+ <div id="copy-<%- table_index %>-<%- row_index %>-<%- item_index %>" class="illustrate_content_wrapper"><%= value %></div>
45
+ </div>
46
+ </div>
47
+ </div>
48
+ </td>
49
+ <% else %>
50
+ <td><div>&nbsp;</div></td>
51
+ <% end %>
52
+ <% end %>
53
+ </tr>
54
+ <% end %>
55
+ </tbody>
56
+ </table>
57
+
58
+ <% if (table["notices"]) %>
59
+ <% table["notices"].each do |notice| %>
60
+ <div class="alert">
61
+ <a class="close" data-dismiss="alert">&times;</a>
62
+ * <%= notice %>
63
+ </div>
64
+ <% end %>
65
+ <% end %>
66
+
67
+ <% end %>
68
+
69
+ <% if (@udf_output and (not @udf_output.empty?)) %>
70
+ <h3 class="illustrate_alias">UDF Output: </h3>
71
+ </br>
72
+ <pre id="illustrate_udf_output">
73
+ <%= @udf_output %>
74
+ </pre>
75
+ <% end %>
76
+
77
+ <script src="resources/js/jquery-1.7.1.min.js"></script>
78
+ <script src="resources/js/jquery.transit.js"></script>
79
+ <script src="resources/js/jquery.stylestack.js"></script>
80
+ <script src="resources/js/zero_clipboard.js"></script>
81
+ <script src="resources/js/mortar-table.js"></script>
82
+ <script>
83
+ $('.illustrate_table td').mortarTableExpandableCell();
84
+ // TODO: get this working
85
+ /*
86
+ $('.illustrate_table td .clipboard').each(function() {
87
+ $(this).data('clippy', new ZeroClipboard( $(this), {
88
+ moviePath: "resources/flash/zeroclipboard.swf"
89
+ }));
90
+ });
91
+ */
92
+ </script>
93
+
94
+ </body>
95
+
96
+ </html>
@@ -0,0 +1,23 @@
1
+ #!/bin/bash
2
+
3
+ set -e
4
+
5
+ export PIG_HOME=<%= @pig_home %>
6
+ export PIG_CLASSPATH=<%= @pig_classpath %>
7
+ export CLASSPATH=<%= @classpath %>
8
+ export PIG_MAIN_CLASS=com.mortardata.hawk.HawkMain
9
+ export PIG_OPTS="<% @pig_opts.each do |k,v| %>-D<%= k %>=<%= v %> <% end %>"
10
+
11
+ # UDF paths are relative to this direectory
12
+ cd <%= @project_home %>/pigscripts
13
+
14
+ # Setup python environment
15
+ source <%= @local_install_dir %>/pythonenv/bin/activate
16
+
17
+ # Run Pig
18
+ <%= @local_install_dir %>/pig/bin/pig -exectype local \
19
+ -log4jconf <%= @local_install_dir %>/pig/conf/log4j-cli-local-dev.properties \
20
+ -propertyFile <%= @local_install_dir %>/pig/conf/pig-hawk-global.properties \
21
+ -propertyFile <%= @local_install_dir %>/pig/conf/pig-cli-local-dev.properties \
22
+ -param_file <%= @pig_params_file %> \
23
+ <%= @pig_sub_command %>
@@ -16,5 +16,5 @@
16
16
 
17
17
  module Mortar
18
18
  # see http://semver.org/
19
- VERSION = "0.6.2"
19
+ VERSION = "0.7.0"
20
20
  end
@@ -152,5 +152,13 @@ module Mortar
152
152
 
153
153
  lambda { @cli.check }.should raise_error(Mortar::CLI::Errors::InvalidGithubUsername)
154
154
  end
155
+
156
+ it "encodes the user email as s3 safe" do
157
+ user_email = "myemail+dontspam@somedomain.com"
158
+ stub(@cli).user.returns(user_email)
159
+ @cli.user().should == user_email
160
+ @cli.user_s3_safe.should == 'myemail-dontspam-somedomain-com'
161
+ end
162
+
155
163
  end
156
164
  end
@@ -57,8 +57,21 @@ STDERR
57
57
  with_git_initialized_project do |p|
58
58
  stderr, stdout = execute("describe does_not_exist my_alias", p, @git)
59
59
  stderr.should == <<-STDERR
60
- ! Unable to find pigscript does_not_exist
60
+ ! Unable to find a pigscript or controlscript for does_not_exist
61
+ !
61
62
  ! No pigscripts found
63
+ !
64
+ ! No controlscripts found
65
+ STDERR
66
+ end
67
+ end
68
+
69
+ it "errors when requested with controlscript" do
70
+ with_git_initialized_project do |p|
71
+ write_file(File.join(p.controlscripts_path, "my_script.py"))
72
+ stderr, stdout = execute("describe my_script my_alias", p, @git)
73
+ stderr.should == <<-STDERR
74
+ ! Currently Mortar does not support describing control scripts
62
75
  STDERR
63
76
  end
64
77
  end
@@ -57,8 +57,21 @@ STDERR
57
57
  with_git_initialized_project do |p|
58
58
  stderr, stdout = execute("illustrate does_not_exist my_alias", p, @git)
59
59
  stderr.should == <<-STDERR
60
- ! Unable to find pigscript does_not_exist
60
+ ! Unable to find a pigscript or controlscript for does_not_exist
61
+ !
61
62
  ! No pigscripts found
63
+ !
64
+ ! No controlscripts found
65
+ STDERR
66
+ end
67
+ end
68
+
69
+ it "errors when requested with controlscript" do
70
+ with_git_initialized_project do |p|
71
+ write_file(File.join(p.controlscripts_path, "my_script.py"))
72
+ stderr, stdout = execute("illustrate my_script my_alias", p, @git)
73
+ stderr.should == <<-STDERR
74
+ ! Currently Mortar does not support illustrating control scripts
62
75
  STDERR
63
76
  end
64
77
  end