mortar 0.9.1 → 0.9.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -66,12 +66,48 @@ class Mortar::Command::Local < Mortar::Command::Base
66
66
  error("No such directory #{project_root}")
67
67
  end
68
68
  Dir.chdir(project_root)
69
-
70
69
  script = validate_script!(script_name)
71
70
  ctrl = Mortar::Local::Controller.new
72
71
  ctrl.run(script, pig_parameters)
73
72
  end
74
73
 
74
+ # local:characterize
75
+ #
76
+ # Characterize will inspect your input data, inferring a schema and
77
+ # generating keys, if needed. It will output CSV containing various
78
+ # statistics about your data (most common values, percent null, etc.)
79
+ #
80
+ # -f, --param-file PARAMFILE # Load pig parameter values from a file
81
+ #
82
+ # Load some data and emit statistics.
83
+ # PARAMFILE:
84
+ # LOADER=<full class path of loader function>
85
+ # INPUT_SRC=<Location of the input data>
86
+ # OUTPUT_PATH=<Relative path from project root for output>
87
+ # INFER_TYPES=<when true, recursively infers types for input data>
88
+ #
89
+ # Example paramfile:
90
+ # LOADER=org.apache.pig.piggybank.storage.JsonLoader()
91
+ # INPUT_SRC=s3n://twitter-gardenhose-mortar/example
92
+ # OUTPUT_PATH=twitter_char
93
+ # INFER_TYPES=true
94
+ #
95
+ def characterize
96
+ validate_arguments!
97
+
98
+ #cd into the project root
99
+ project_root = options[:project_root] ||= Dir.getwd
100
+ unless File.directory?(project_root)
101
+ error("No such directory #{project_root}")
102
+ end
103
+ Dir.chdir(project_root)
104
+
105
+ controlscript_name = "controlscripts/lib/characterize_control.py"
106
+ script = validate_script!(controlscript_name)
107
+ ctrl = Mortar::Local::Controller.new
108
+ ctrl.run(script, pig_parameters)
109
+ end
110
+
75
111
  # local:illustrate PIGSCRIPT [ALIAS]
76
112
  #
77
113
  # Locally illustrate the effects and output of a pigscript.
@@ -46,7 +46,7 @@ module Mortar::Command
46
46
  plugin.install
47
47
  Mortar::Plugin.load_plugin(plugin.name)
48
48
  rescue StandardError => e
49
- error e
49
+ error e.message
50
50
  end
51
51
  end
52
52
  end
@@ -68,7 +68,7 @@ module Mortar::Command
68
68
  begin
69
69
  plugin.uninstall
70
70
  rescue Mortar::Plugin::ErrorPluginNotFound => e
71
- error e
71
+ error e.message
72
72
  end
73
73
  end
74
74
  end
@@ -101,7 +101,7 @@ module Mortar::Command
101
101
  status "skipped symlink"
102
102
  rescue StandardError => e
103
103
  status "error"
104
- display e
104
+ display e.message
105
105
  end
106
106
  end
107
107
  end
@@ -47,19 +47,31 @@ class Mortar::Command::Projects < Mortar::Command::Base
47
47
  error("Usage: mortar projects:delete PROJECTNAME\nMust specify PROJECTNAME.")
48
48
  end
49
49
  validate_arguments!
50
-
51
- # delete embedded project mirror if one exists
52
- mirror_dir = "#{git.mortar_mirrors_dir()}/#{name}"
53
- if File.directory? mirror_dir
54
- FileUtils.rm_r mirror_dir
50
+ projects = api.get_projects().body['projects']
51
+ project_id = nil
52
+ if projects.any?
53
+ projects.each do |project|
54
+ if project['name'] == name
55
+ project_id = project['project_id']
56
+ end
57
+ end
55
58
  end
56
59
 
57
- # delete Mortar remote
58
- project_id = nil
59
- action("Sending request to delete project: #{name}") do
60
- api.delete_project(name).body["project_id"]
60
+ if project_id.nil?
61
+ display "\nNo project with name: #{name}"
62
+ else
63
+ # delete embedded project mirror if one exists
64
+ mirror_dir = "#{git.mortar_mirrors_dir()}/#{name}"
65
+ if File.directory? mirror_dir
66
+ FileUtils.rm_r mirror_dir
67
+ end
68
+
69
+ # delete Mortar remote
70
+ action("Sending request to delete project: #{name}") do
71
+ api.delete_project(project_id).body['project_id']
72
+ end
73
+ display "\nYour project has been deleted."
61
74
  end
62
- display "\nYour project has been deleted."
63
75
 
64
76
  end
65
77
 
@@ -38,6 +38,7 @@ module Mortar
38
38
 
39
39
  inside "pigscripts" do
40
40
  generate_file "pigscript.pig", "#{project_name}.pig"
41
+ copy_file "characterize.pig", "characterize.pig"
41
42
  end
42
43
 
43
44
  mkdir "controlscripts"
@@ -46,6 +47,7 @@ module Mortar
46
47
  mkdir "lib"
47
48
  inside "lib" do
48
49
  copy_file "__init__.py", "__init__.py"
50
+ copy_file "characterize_control.py", "characterize_control.py"
49
51
  end
50
52
  end
51
53
 
@@ -53,6 +55,7 @@ module Mortar
53
55
 
54
56
  inside "macros" do
55
57
  copy_file "gitkeep", ".gitkeep"
58
+ copy_file "characterize_macro.pig", "characterize_macro.pig"
56
59
  end
57
60
 
58
61
  mkdir "fixtures"
@@ -72,6 +75,7 @@ module Mortar
72
75
  mkdir "jython"
73
76
  inside "jython" do
74
77
  copy_file "gitkeep", ".gitkeep"
78
+ copy_file "top_5_tuple.py", "top_5_tuple.py"
75
79
  end
76
80
 
77
81
  mkdir "java"
@@ -142,4 +146,4 @@ module Mortar
142
146
  end
143
147
  end
144
148
  end
145
- end
149
+ end
data/lib/mortar/plugin.rb CHANGED
@@ -1,4 +1,7 @@
1
1
  # based on the Rails Plugin
2
+
3
+ require "stringio"
4
+
2
5
  module Mortar
3
6
  class Plugin
4
7
  include Mortar::Helpers
@@ -41,7 +44,40 @@ module Mortar
41
44
  end
42
45
 
43
46
  def self.install_bundle
44
- system("bundle install --standalone --without development >> ./../plugin_install.log")
47
+ # TODO: Deal with the bundler as a runtime dependency issue
48
+ # before moving these require statements to the top.
49
+ begin
50
+ require 'bundler/cli'
51
+ require 'bundler/friendly_errors'
52
+ rescue LoadError => e
53
+ raise <<-ERROR
54
+ Unable to install this plugin. Make sure you have bundler installed:
55
+
56
+ $ gem install bundler
57
+
58
+ ERROR
59
+ end
60
+
61
+ out = StringIO.new
62
+ $stdout = out
63
+ begin
64
+ bundle_def = Bundler.definition({})
65
+ Bundler.ui = Bundler::UI::Shell.new({})
66
+ Bundler.ui.level = "silent"
67
+ Bundler.settings[:path] = "bundle"
68
+ Bundler::Installer.install(Bundler.root, bundle_def, {
69
+ :standalone => [],
70
+ })
71
+ result = true
72
+ rescue StandardError => e
73
+ out.write e.message
74
+ result = false
75
+ end
76
+ open("#{Plugin.directory}/plugin_install.log", 'a') do |f|
77
+ f.puts out.string
78
+ end
79
+ $stdout = STDOUT
80
+ return result
45
81
  end
46
82
 
47
83
  def self.load!
@@ -111,8 +147,7 @@ ERROR
111
147
  Mortar::Plugin.without_bundler_env do
112
148
  ENV["BUNDLE_GEMFILE"] = File.expand_path("Gemfile", path)
113
149
  if File.exists? ENV["BUNDLE_GEMFILE"]
114
- Mortar::Plugin.install_bundle
115
- unless $?.success?
150
+ unless Mortar::Plugin.install_bundle
116
151
  FileUtils.rm_rf path
117
152
  raise Mortar::Plugin::ErrorInstallingDependencies, <<-ERROR
118
153
  Unable to install dependencies for #{name}.
@@ -0,0 +1,23 @@
1
+ from org.apache.pig.scripting import Pig
2
+ import os
3
+
4
+ if __name__ == "__main__":
5
+ params = Pig.getParameters()
6
+ loader = params["LOADER"]
7
+ input_source = params["INPUT_SRC"]
8
+ output_path = params["OUTPUT_PATH"]
9
+ infer_types = params["INFER_TYPES"]
10
+
11
+ Pig.compileFromFile("../pigscripts/characterize.pig").bind({
12
+ "LOADER" : loader,
13
+ "INPUT_SRC" : input_source,
14
+ "OUTPUT_PATH" : output_path,
15
+ "INFER_TYPES" : infer_types
16
+ }).runSingle()
17
+
18
+ for root, _, files in os.walk("../%s" % output_path):
19
+ for f in files:
20
+ if f[0] != '.':
21
+ fullpath = os.path.join(root, f)
22
+ copypath = os.path.join(root, f + '.csv')
23
+ os.system ("cp %s %s" % (fullpath, copypath))
@@ -0,0 +1,129 @@
1
+ /*
2
+ Copyright 2013 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
+ /*
18
+ * A pig macro to characterize some data.
19
+ * Take an alias to some pre-loaded data and return a relation
20
+ * containing statistics about that data in a bag.
21
+ * Each field is listed up to five times (with their five most common
22
+ * example values) as a tuple of the following form:
23
+ * (
24
+ * Field Name (embedded fields have their parent's field name prepended),
25
+ * Number of distinct values associated with the field,
26
+ * Total number of values,
27
+ * Deciles,
28
+ * Number of null appearences,
29
+ * Percentage null,
30
+ * Minimum value,
31
+ * Maximum value,
32
+ * Range of values (Max - Min),
33
+ * Type,
34
+ * Values (tuple of top 5 values, bags/chararrays/tuples are converted to length)
35
+ * Value Counts (tuple of corresponding number of value occurrences)
36
+ * Original Values (tuple of corresponding original values)
37
+ * )
38
+ *
39
+ * data: {anything}
40
+ * inferTypes: when true, characterize will infer numeric types for values
41
+ * without an explicit schema (e.g. values in a complex map object)
42
+ *
43
+ * Example:
44
+ * data = LOAD '/path/to/data' USING JsonLoader();
45
+ * characterized = Characterize(data, 'true')
46
+ */
47
+
48
+ REGISTER 's3://mhc-software-mirror/datafu/datafu-0.0.10.jar';
49
+ REGISTER 's3://mhc-software-mirror/bacon-bits/udfs/java/bacon-bits-0.1.0.jar';
50
+ REGISTER '../udfs/jython/top_5_tuple.py' USING jython AS top5;
51
+
52
+ DEFINE Deciles datafu.pig.stats.StreamingQuantile('11');
53
+
54
+ DEFINE Characterize(data, inferTypes)
55
+ RETURNS out {
56
+
57
+ DEFINE Characterize__ExtractFields com.mortardata.pig.ExtractFields('$inferTypes');
58
+
59
+ raw_fields = FOREACH $data
60
+ GENERATE FLATTEN(Characterize__ExtractFields(*)) as
61
+ (keyname:chararray, type:chararray, val:double, orig:chararray);
62
+
63
+ --Group the rows by field name and find the number of unique values for each field in the collection
64
+ key_groups = GROUP raw_fields BY (keyname);
65
+ unique_vals = FOREACH key_groups {
66
+ v = raw_fields.val;
67
+ null_fields = filter raw_fields by val is null;
68
+ unique_v = distinct v;
69
+ GENERATE flatten(group) as keyname:chararray,
70
+ COUNT(unique_v) as num_distinct_vals_count:long, COUNT(v) as num_vals:long, COUNT(null_fields) as num_null:long,
71
+ (double) COUNT(null_fields) / (double) COUNT(raw_fields) as percentage_null:double,
72
+ MIN(unique_v.val) as min_val:double, MAX(unique_v.val) as max_val:double;
73
+ }
74
+
75
+ -- calculate decile tuples for each key (filtering out null values to make datafu happy)
76
+ no_nulls = filter raw_fields by val is not null;
77
+ no_null_groups = GROUP no_nulls BY keyname;
78
+ key_deciles = FOREACH no_null_groups {
79
+ GENERATE flatten(group) as keyname:chararray, Deciles(no_nulls.val) as deciles:tuple();
80
+ }
81
+
82
+ -- Find the number of times each value occurs for each field
83
+ key_val_groups = GROUP raw_fields BY (keyname, type, val, orig);
84
+ key_val_groups_with_counts = FOREACH key_val_groups
85
+ GENERATE flatten(group),
86
+ COUNT($1) as val_count:long;
87
+
88
+ -- Find the top 5 most common values for each field
89
+ key_vals = GROUP key_val_groups_with_counts BY (keyname);
90
+ top_5_vals = FOREACH key_vals {
91
+ ordered_vals = ORDER key_val_groups_with_counts BY val_count DESC;
92
+ limited_vals = LIMIT ordered_vals 5;
93
+ GENERATE flatten(limited_vals);
94
+ }
95
+
96
+ cogroup_result = COGROUP unique_vals BY keyname,
97
+ top_5_vals BY keyname;
98
+
99
+ flat_vals = FOREACH cogroup_result {
100
+ top_vals = top5.key_bag_to_tuple(top_5_vals);
101
+ GENERATE flatten(unique_vals) as
102
+ (keyname:chararray, num_distinct_vals_count:long, num_vals:long,
103
+ num_null:long, percent_null:double, min_val:double, max_val:double), top_vals.vals as vals,
104
+ top_vals.type as type:chararray, top_vals.orig as orig:tuple(), top_vals.val_counts as val_counts:tuple();
105
+ }
106
+
107
+ join_result = JOIN flat_vals BY keyname,
108
+ key_deciles BY keyname;
109
+
110
+ -- Clean up columns (remove duplicate keyname field)
111
+ result = FOREACH join_result
112
+ GENERATE flat_vals::keyname as Key,
113
+ num_distinct_vals_count as NDistinct,
114
+ num_vals as NVals,
115
+ deciles as Deciles,
116
+ num_null as NNull,
117
+ percent_null as PctNull,
118
+ min_val as Min,
119
+ max_val as Max,
120
+ max_val - min_val as Range,
121
+ type as Type,
122
+ vals as Vals,
123
+ val_counts as ValCounts,
124
+ orig as OrigVals;
125
+
126
+ -- -- Sort by field name and number of values
127
+ $out = ORDER result BY Key;
128
+
129
+ };
@@ -0,0 +1,6 @@
1
+ IMPORT '../macros/characterize_macro.pig';
2
+ data = LOAD '$INPUT_SRC'
3
+ USING $LOADER;
4
+ characterize_data = Characterize(data, $INFER_TYPES);
5
+ STORE characterize_data INTO '../$OUTPUT_PATH'
6
+ USING org.apache.pig.piggybank.storage.CSVExcelStorage(',', 'YES_MULTILINE', 'UNIX', 'WRITE_OUTPUT_HEADER');
@@ -0,0 +1,27 @@
1
+ @outputSchema("five:tuple(vals:tuple(v1:double, v2:double, v3:double, v4:double, v5:double), type:chararray, orig:tuple(v1:chararray, v2:chararray, v3:chararray, v4:chararray, v5:chararray), val_counts:tuple(v1:long, v2:long, v3:long, v4:long, v5:long))")
2
+ def key_bag_to_tuple(input_bag):
3
+ output = []
4
+ #sort by val_count
5
+ input_bag = sorted(input_bag, key=lambda tup: tup[4], reverse=True)
6
+ vals = [None, None, None, None, None]
7
+ val_counts = [None, None, None, None, None]
8
+ orig_vals = [None, None, None, None, None]
9
+ for idx, t in enumerate(input_bag):
10
+ vals[idx] = t[2]
11
+ orig_vals[idx] = t[3]
12
+ val_counts[idx] = t[4]
13
+ val_tup = tuple(vals)
14
+ val_count_tup = tuple(val_counts)
15
+ orig_val_tup = tuple(orig_vals)
16
+ output.append(val_tup)
17
+ if all(i[1] == "NULL" for i in input_bag):
18
+ output.append("NULL")
19
+ else:
20
+ for t in input_bag:
21
+ if t[1] != "NULL":
22
+ output.append(t[1])
23
+ break
24
+ output.append(orig_val_tup)
25
+ output.append(val_count_tup)
26
+ return tuple(output)
27
+
@@ -16,5 +16,5 @@
16
16
 
17
17
  module Mortar
18
18
  # see http://semver.org/
19
- VERSION = "0.9.1"
19
+ VERSION = "0.9.2"
20
20
  end
@@ -36,9 +36,11 @@ module Mortar::Command
36
36
 
37
37
  project1 = {'name' => "Project1",
38
38
  'status' => Mortar::API::Projects::STATUS_ACTIVE,
39
+ 'project_id' => 'abcd1234',
39
40
  'git_url' => "git@github.com:mortarcode-dev/Project1"}
40
41
  project2 = {'name' => "Project2",
41
42
  'status' => Mortar::API::Projects::STATUS_ACTIVE,
43
+ 'project_id' => 'defg5678',
42
44
  'git_url' => "git@github.com:mortarcode-dev/Project2"}
43
45
 
44
46
  context("index") do
@@ -75,12 +77,13 @@ STDERR
75
77
  end
76
78
 
77
79
  it "deletes project" do
78
- project_name = "COMMANDO"
80
+ project_id = "abcd1234"
79
81
 
80
- mock(Mortar::Auth.api).delete_project(project_name).returns(Excon::Response.new(:body => {}, :status => 200 ))
81
- stderr, stdout = execute("projects:delete COMMANDO")
82
+ mock(Mortar::Auth.api).get_projects().returns(Excon::Response.new(:body => {"projects" => [project1, project2]}))
83
+ mock(Mortar::Auth.api).delete_project(project_id).returns(Excon::Response.new(:body => {}, :status => 200 ))
84
+ stderr, stdout = execute("projects:delete Project1")
82
85
  stdout.should == <<-STDOUT
83
- Sending request to delete project: COMMANDO... done
86
+ Sending request to delete project: Project1... done
84
87
 
85
88
  Your project has been deleted.
86
89
  STDOUT
@@ -127,11 +130,14 @@ STDOUT
127
130
  \e[1;32m create\e[0m .gitignore
128
131
  \e[1;32m create\e[0m pigscripts
129
132
  \e[1;32m create\e[0m pigscripts/some_new_project.pig
133
+ \e[1;32m create\e[0m pigscripts/characterize.pig
130
134
  \e[1;32m create\e[0m controlscripts
131
135
  \e[1;32m create\e[0m controlscripts/lib
132
136
  \e[1;32m create\e[0m controlscripts/lib/__init__.py
137
+ \e[1;32m create\e[0m controlscripts/lib/characterize_control.py
133
138
  \e[1;32m create\e[0m macros
134
139
  \e[1;32m create\e[0m macros/.gitkeep
140
+ \e[1;32m create\e[0m macros/characterize_macro.pig
135
141
  \e[1;32m create\e[0m fixtures
136
142
  \e[1;32m create\e[0m fixtures/.gitkeep
137
143
  \e[1;32m create\e[0m udfs
@@ -139,6 +145,7 @@ STDOUT
139
145
  \e[1;32m create\e[0m udfs/python/some_new_project.py
140
146
  \e[1;32m create\e[0m udfs/jython
141
147
  \e[1;32m create\e[0m udfs/jython/.gitkeep
148
+ \e[1;32m create\e[0m udfs/jython/top_5_tuple.py
142
149
  \e[1;32m create\e[0m udfs/java
143
150
  \e[1;32m create\e[0m udfs/java/.gitkeep
144
151
  \e[1;32m create\e[0m vendor
@@ -426,4 +433,4 @@ STDERR
426
433
 
427
434
  end
428
435
  end
429
- end
436
+ end
@@ -100,15 +100,30 @@ module Mortar
100
100
 
101
101
  describe "installing plugins with dependencies" do
102
102
  it "should install plugin dependencies" do
103
+ ## Setup Fake Gem
104
+ mortar_fake_gem_folder = create_fake_gem("/tmp")
103
105
  plugin_folder = "/tmp/mortar_plugin"
104
106
  FileUtils.mkdir_p(plugin_folder)
105
- File.open(plugin_folder + '/Gemfile', 'w') { |f| f.write "# dummy content" }
107
+ File.open(plugin_folder + '/Gemfile', 'w') { |f|
108
+ f.write "gem 'mortar_fake_gem', :path => '#{mortar_fake_gem_folder}'"
109
+ }
110
+ File.open(plugin_folder + '/init.rb', 'w') { |f|
111
+ f.write <<-EOS
112
+ require File.join(File.dirname(__FILE__), "bundle/bundler/setup")
113
+ require "mortar_fake_gem"
114
+
115
+ PluginTest = MortarFakeGem::WhoIs.awesome?
116
+ EOS
117
+ }
106
118
  `cd #{plugin_folder} && git init && echo 'test' > README && git add . && git commit -m 'my plugin'`
107
119
  Plugin.new(plugin_folder).install
108
120
  File.directory?("#{@sandbox}/mortar_plugin").should be_true
109
121
  File.directory?("#{@sandbox}/mortar_plugin/bundle").should be_true
110
122
  File.exist?("#{@sandbox}/mortar_plugin/Gemfile").should be_true
111
123
  File.read("#{@sandbox}/mortar_plugin/README").should == "test\n"
124
+
125
+ Plugin.load!
126
+ PluginTest.should be_true
112
127
  end
113
128
 
114
129
  it "should fail to install plugin with bad dependencies" do
data/spec/spec_helper.rb CHANGED
@@ -249,6 +249,44 @@ def git_create_conflict(git, project)
249
249
  filename
250
250
  end
251
251
 
252
+ def create_fake_gem(path)
253
+ mortar_fake_gem = <<-EOS
254
+ Gem::Specification.new do |s|
255
+ s.name = "mortar_fake_gem"
256
+ s.version = "0.0.1"
257
+ s.summary = "mortar_fake_gem is the best"
258
+ s.files = [
259
+ "lib/mortar_fake_gem.rb"
260
+ ]
261
+ s.require_paths = ["lib"]
262
+ end
263
+ EOS
264
+
265
+ mortar_fake_gem_rb = <<-EOS
266
+ module MortarFakeGem
267
+ class WhoIs
268
+ def self.awesome?
269
+ return true
270
+ end
271
+ end
272
+ end
273
+ EOS
274
+
275
+ files = {
276
+ "mortar_fake_gem.gemspec" => mortar_fake_gem,
277
+ "lib/mortar_fake_gem.rb" => mortar_fake_gem_rb,
278
+ }
279
+
280
+ FileUtils.mkdir_p(File.join(path, "mortar_fake_gem/lib"))
281
+ files.each { |file_name, contents|
282
+ File.open(File.join(path, "/mortar_fake_gem/#{file_name}"), 'w') do |f|
283
+ f.puts contents
284
+ end
285
+ }
286
+
287
+ return File.join(path, "mortar_fake_gem")
288
+
289
+ end
252
290
 
253
291
  def git_add_file(git, project)
254
292
  # add a new file
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mortar
3
3
  version: !ruby/object:Gem::Version
4
- hash: 57
4
+ hash: 63
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 9
9
- - 1
10
- version: 0.9.1
9
+ - 2
10
+ version: 0.9.2
11
11
  platform: ruby
12
12
  authors:
13
13
  - Mortar Data
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2013-07-08 00:00:00 Z
18
+ date: 2013-07-16 00:00:00 Z
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
21
21
  name: mortar-api-ruby
@@ -210,12 +210,16 @@ files:
210
210
  - lib/mortar/templates/pigscript/python_udf.py
211
211
  - lib/mortar/templates/project/README.md
212
212
  - lib/mortar/templates/project/controlscripts/lib/__init__.py
213
+ - lib/mortar/templates/project/controlscripts/lib/characterize_control.py
213
214
  - lib/mortar/templates/project/fixtures/gitkeep
214
215
  - lib/mortar/templates/project/gitignore
216
+ - lib/mortar/templates/project/macros/characterize_macro.pig
215
217
  - lib/mortar/templates/project/macros/gitkeep
218
+ - lib/mortar/templates/project/pigscripts/characterize.pig
216
219
  - lib/mortar/templates/project/pigscripts/pigscript.pig
217
220
  - lib/mortar/templates/project/udfs/java/gitkeep
218
221
  - lib/mortar/templates/project/udfs/jython/gitkeep
222
+ - lib/mortar/templates/project/udfs/jython/top_5_tuple.py
219
223
  - lib/mortar/templates/project/udfs/python/python_udf.py
220
224
  - lib/mortar/templates/project/vendor/controlscripts/lib/__init__.py
221
225
  - lib/mortar/templates/project/vendor/macros/gitkeep
@@ -289,7 +293,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
289
293
  requirements: []
290
294
 
291
295
  rubyforge_project:
292
- rubygems_version: 1.8.24
296
+ rubygems_version: 1.8.25
293
297
  signing_key:
294
298
  specification_version: 3
295
299
  summary: Client library and CLI to interact with the Mortar service.