gooddata 0.6.0.pre11 → 0.6.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 (116) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +12 -1
  3. data/.yardopts +2 -0
  4. data/README.md +6 -3
  5. data/Rakefile +24 -7
  6. data/gooddata +2 -2
  7. data/gooddata.gemspec +4 -3
  8. data/lib/gooddata.rb +17 -12
  9. data/lib/gooddata/bricks/base_downloader.rb +7 -7
  10. data/lib/gooddata/bricks/brick.rb +7 -8
  11. data/lib/gooddata/bricks/bricks.rb +4 -1
  12. data/lib/gooddata/bricks/middleware/base_middleware.rb +2 -2
  13. data/lib/gooddata/bricks/middleware/bench_middleware.rb +5 -6
  14. data/lib/gooddata/bricks/middleware/bulk_salesforce_middleware.rb +21 -22
  15. data/lib/gooddata/bricks/middleware/fs_upload_middleware.rb +3 -4
  16. data/lib/gooddata/bricks/middleware/gooddata_middleware.rb +14 -14
  17. data/lib/gooddata/bricks/middleware/logger_middleware.rb +6 -6
  18. data/lib/gooddata/bricks/middleware/middleware.rb +4 -1
  19. data/lib/gooddata/bricks/middleware/restforce_middleware.rb +29 -32
  20. data/lib/gooddata/bricks/middleware/stdout_middleware.rb +5 -5
  21. data/lib/gooddata/bricks/middleware/twitter_middleware.rb +6 -8
  22. data/lib/gooddata/bricks/utils.rb +3 -3
  23. data/lib/gooddata/cli/cli.rb +4 -2
  24. data/lib/gooddata/cli/commands/api_cmd.rb +6 -4
  25. data/lib/gooddata/cli/commands/auth_cmd.rb +5 -3
  26. data/lib/gooddata/cli/commands/console_cmd.rb +1 -1
  27. data/lib/gooddata/cli/commands/process_cmd.rb +6 -4
  28. data/lib/gooddata/cli/commands/profile_cmd.rb +5 -3
  29. data/lib/gooddata/cli/commands/project_cmd.rb +24 -22
  30. data/lib/gooddata/cli/commands/run_ruby_cmd.rb +12 -10
  31. data/lib/gooddata/cli/commands/scaffold_cmd.rb +8 -6
  32. data/lib/gooddata/cli/hooks.rb +4 -2
  33. data/lib/gooddata/cli/shared.rb +3 -1
  34. data/lib/gooddata/cli/terminal.rb +16 -0
  35. data/lib/gooddata/client.rb +28 -22
  36. data/lib/gooddata/commands/api.rb +43 -26
  37. data/lib/gooddata/commands/auth.rb +22 -53
  38. data/lib/gooddata/commands/base.rb +2 -0
  39. data/lib/gooddata/commands/commands.rb +3 -0
  40. data/lib/gooddata/commands/datasets.rb +39 -136
  41. data/lib/gooddata/commands/process.rb +134 -130
  42. data/lib/gooddata/commands/profile.rb +2 -0
  43. data/lib/gooddata/commands/projects.rb +91 -129
  44. data/lib/gooddata/commands/runners.rb +11 -11
  45. data/lib/gooddata/commands/scaffold.rb +28 -26
  46. data/lib/gooddata/connection.rb +61 -68
  47. data/lib/gooddata/core/core.rb +1 -2
  48. data/lib/gooddata/data/data.rb +7 -0
  49. data/lib/gooddata/data/guesser.rb +114 -0
  50. data/lib/gooddata/exceptions/command_failed.rb +7 -0
  51. data/lib/gooddata/exceptions/exceptions.rb +7 -0
  52. data/lib/gooddata/{exceptions.rb → exceptions/project_not_found.rb} +2 -2
  53. data/lib/gooddata/extensions/big_decimal.rb +5 -0
  54. data/lib/gooddata/extract.rb +2 -0
  55. data/lib/gooddata/goodzilla/goodzilla.rb +11 -12
  56. data/lib/gooddata/helpers.rb +49 -35
  57. data/lib/gooddata/models/attribute.rb +7 -5
  58. data/lib/gooddata/models/dashboard.rb +44 -45
  59. data/lib/gooddata/models/data_result.rb +10 -13
  60. data/lib/gooddata/models/data_set.rb +6 -6
  61. data/lib/gooddata/models/display_form.rb +4 -4
  62. data/lib/gooddata/models/empty_result.rb +4 -3
  63. data/lib/gooddata/models/fact.rb +5 -5
  64. data/lib/gooddata/models/links.rb +3 -1
  65. data/lib/gooddata/models/metadata.rb +34 -32
  66. data/lib/gooddata/models/metric.rb +33 -34
  67. data/lib/gooddata/models/model.rb +165 -173
  68. data/lib/gooddata/models/models.rb +3 -0
  69. data/lib/gooddata/models/process.rb +18 -17
  70. data/lib/gooddata/models/profile.rb +3 -1
  71. data/lib/gooddata/models/project.rb +107 -35
  72. data/lib/gooddata/models/project_metadata.rb +12 -12
  73. data/lib/gooddata/models/report.rb +31 -30
  74. data/lib/gooddata/models/report_data_result.rb +22 -19
  75. data/lib/gooddata/models/report_definition.rb +101 -80
  76. data/lib/gooddata/version.rb +5 -3
  77. data/lib/templates/bricks/brick.rb.erb +3 -3
  78. data/lib/templates/bricks/main.rb.erb +3 -2
  79. data/lib/templates/project/Goodfile.erb +2 -2
  80. data/lib/templates/project/model/model.rb.erb +19 -19
  81. data/spec/data/.gooddata +4 -0
  82. data/spec/helpers/blueprint_helper.rb +2 -2
  83. data/spec/helpers/cli_helper.rb +28 -0
  84. data/spec/helpers/connection_helper.rb +2 -2
  85. data/spec/integration/command_projects_spec.rb +1 -1
  86. data/spec/integration/create_from_template_spec.rb +12 -0
  87. data/spec/integration/full_project_spec.rb +2 -2
  88. data/spec/integration/partial_md_export_import_spec.rb +36 -0
  89. data/spec/logging_in_logging_out_spec.rb +1 -1
  90. data/spec/spec_helper.rb +29 -2
  91. data/spec/unit/cli/cli_spec.rb +3 -3
  92. data/spec/unit/cli/commands/cmd_api_spec.rb +21 -4
  93. data/spec/unit/cli/commands/cmd_auth_spec.rb +2 -4
  94. data/spec/unit/cli/commands/cmd_process_spec.rb +20 -4
  95. data/spec/unit/cli/commands/cmd_profile_spec.rb +9 -4
  96. data/spec/unit/cli/commands/cmd_project_spec.rb +53 -4
  97. data/spec/unit/cli/commands/cmd_run_ruby_spec.rb +2 -4
  98. data/spec/unit/cli/commands/cmd_scaffold_spec.rb +14 -4
  99. data/spec/unit/commands/command_api_spec.rb +21 -2
  100. data/spec/unit/commands/command_auth_spec.rb +62 -1
  101. data/spec/unit/commands/command_dataset_spec.rb +31 -3
  102. data/spec/unit/commands/command_process_spec.rb +75 -1
  103. data/spec/unit/commands/command_profile_spec.rb +7 -1
  104. data/spec/unit/commands/command_projects_spec.rb +1 -1
  105. data/spec/unit/commands/command_scaffold_spec.rb +46 -1
  106. data/spec/unit/core/connection_spec.rb +1 -0
  107. data/spec/unit/data/guesser_spec.rb +54 -0
  108. data/spec/unit/helpers_spec.rb +47 -0
  109. data/spec/unit/model/schema_builder_spec.rb +2 -0
  110. data/spec/unit/model/tools_spec.rb +89 -0
  111. data/test/test_upload.rb +39 -15
  112. metadata +98 -75
  113. data/test/test_commands.rb +0 -85
  114. data/test/test_guessing.rb +0 -46
  115. data/test/test_model.rb +0 -81
  116. data/test/test_rest_api_basic.rb +0 -41
@@ -0,0 +1,7 @@
1
+ # encoding: UTF-8
2
+
3
+ module GoodData
4
+ # Command Failed
5
+ class CommandFailed < RuntimeError
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ # encoding: UTF-8
2
+ require 'pathname'
3
+
4
+ base = Pathname(__FILE__).dirname.expand_path
5
+ Dir.glob(base + '*.rb').each do |file|
6
+ require file
7
+ end
@@ -1,7 +1,7 @@
1
- module GoodData
1
+ # encoding: UTF-8
2
2
 
3
+ module GoodData
3
4
  # Project Not Found
4
5
  class ProjectNotFound < RestClient::ResourceNotFound
5
6
  end
6
-
7
7
  end
@@ -0,0 +1,5 @@
1
+ class BigDecimal;
2
+ def pretty_print(p)
3
+ p.text to_s;
4
+ end
5
+ end
@@ -1,3 +1,5 @@
1
+ # encoding: UTF-8
2
+
1
3
  require 'csv'
2
4
 
3
5
  module GoodData::Extract
@@ -1,5 +1,6 @@
1
- module GoodData::SmallGoodZilla
1
+ # encoding: UTF-8
2
2
 
3
+ module GoodData::SmallGoodZilla
3
4
  # Get IDs from MAQL string
4
5
  # @param a_maql_string Input MAQL string
5
6
  # @return [Array<String>] List of IDS
@@ -42,7 +43,7 @@ module GoodData::SmallGoodZilla
42
43
  []
43
44
  else
44
45
  res = GoodData::MdObject.identifier_to_uri(*ids)
45
- fail "Not all of the identifiers were resolved" if (Array(res).size != ids.size)
46
+ fail 'Not all of the identifiers were resolved' if (Array(res).size != ids.size)
46
47
  res
47
48
  end
48
49
  end
@@ -54,20 +55,18 @@ module GoodData::SmallGoodZilla
54
55
 
55
56
  def self.interpolate_metric(metric, dictionary)
56
57
  interpolated = interpolate({
57
- :facts => GoodData::SmallGoodZilla.get_facts(metric),
58
- :attributes => GoodData::SmallGoodZilla.get_attributes(metric),
59
- :metrics => GoodData::SmallGoodZilla.get_metrics(metric)
60
- }, dictionary)
58
+ :facts => GoodData::SmallGoodZilla.get_facts(metric),
59
+ :attributes => GoodData::SmallGoodZilla.get_attributes(metric),
60
+ :metrics => GoodData::SmallGoodZilla.get_metrics(metric)
61
+ }, dictionary)
61
62
 
62
63
  ids = GoodData::SmallGoodZilla.get_ids(metric)
63
64
  interpolated_ids = ids.zip(Array(interpolate_ids(ids)))
64
65
 
65
- metric = interpolated[:facts].reduce(metric) {|memo, item| memo.sub("#\"#{item[0]}\"", "[#{item[1]}]")}
66
- metric = interpolated[:attributes].reduce(metric) {|memo, item| memo.sub("@\"#{item[0]}\"", "[#{item[1]}]")}
67
- metric = interpolated[:metrics].reduce(metric) {|memo, item| memo.sub("?\"#{item[0]}\"", "[#{item[1]}]")}
68
- metric = interpolated_ids.reduce(metric) {|memo, item| memo.sub("![#{item[0]}]", "[#{item[1]}]")}
66
+ metric = interpolated[:facts].reduce(metric) { |memo, item| memo.sub("#\"#{item[0]}\"", "[#{item[1]}]") }
67
+ metric = interpolated[:attributes].reduce(metric) { |memo, item| memo.sub("@\"#{item[0]}\"", "[#{item[1]}]") }
68
+ metric = interpolated[:metrics].reduce(metric) { |memo, item| memo.sub("?\"#{item[0]}\"", "[#{item[1]}]") }
69
+ metric = interpolated_ids.reduce(metric) { |memo, item| memo.sub("![#{item[0]}]", "[#{item[1]}]") }
69
70
  metric
70
-
71
71
  end
72
-
73
72
  end
@@ -1,44 +1,58 @@
1
- module GoodData::Helpers
2
- def self.home_directory
3
- running_on_windows? ? ENV['USERPROFILE'] : ENV['HOME']
4
- end
1
+ # encoding: UTF-8
5
2
 
6
- def self.running_on_windows?
7
- RUBY_PLATFORM =~ /mswin32|mingw32/
8
- end
3
+ require 'active_support/inflections'
4
+ require 'pathname'
9
5
 
10
- def self.running_on_a_mac?
11
- RUBY_PLATFORM =~ /-darwin\d/
12
- end
6
+ module GoodData
7
+ module Helpers
8
+ class << self
9
+ def home_directory
10
+ running_on_windows? ? ENV['USERPROFILE'] : ENV['HOME']
11
+ end
13
12
 
14
- def self.error(msg)
15
- STDERR.puts(msg)
16
- exit 1
17
- end
13
+ def running_on_windows?
14
+ RUBY_PLATFORM =~ /mswin32|mingw32/
15
+ end
18
16
 
19
- def self.find_goodfile(pwd, options={})
20
- root = Pathname(options[:root] || '/' )
21
- pwd = Pathname(pwd).expand_path
22
- begin
23
- gf = pwd + "Goodfile"
24
- if gf.exist?
25
- return gf
26
- end
27
- pwd = pwd.parent
28
- end until root == pwd
29
- nil
30
- end
17
+ def running_on_a_mac?
18
+ RUBY_PLATFORM =~ /-darwin\d/
19
+ end
20
+
21
+ def error(msg)
22
+ STDERR.puts(msg)
23
+ exit 1
24
+ end
31
25
 
32
- def self.hash_dfs(thing, &block)
33
- if !thing.is_a?(Hash) && !thing.is_a?(Array)
34
- elsif thing.is_a?(Array)
35
- thing.each do |child|
36
- hash_dfs(child, &block)
26
+ def find_goodfile(pwd=`pwd`.strip!, options={})
27
+ root = Pathname(options[:root] || '/')
28
+ pwd = Pathname(pwd).expand_path
29
+ begin
30
+ gf = pwd + 'Goodfile'
31
+ if gf.exist?
32
+ return gf
33
+ end
34
+ pwd = pwd.parent
35
+ end until root == pwd
36
+ nil
37
37
  end
38
- else
39
- thing.each do |key, val|
40
- yield(thing, key)
41
- hash_dfs(val, &block)
38
+
39
+ def hash_dfs(thing, &block)
40
+ if !thing.is_a?(Hash) && !thing.is_a?(Array)
41
+ elsif thing.is_a?(Array)
42
+ thing.each do |child|
43
+ hash_dfs(child, &block)
44
+ end
45
+ else
46
+ thing.each do |key, val|
47
+ yield(thing, key)
48
+ hash_dfs(val, &block)
49
+ end
50
+ end
51
+ end
52
+
53
+ def sanitize_string(str)
54
+ str = ActiveSupport::Inflector.transliterate(str).downcase
55
+ str.gsub(/[^a-z]/, '')
42
56
  end
43
57
  end
44
58
  end
@@ -1,23 +1,25 @@
1
- require File.join(File.dirname(__FILE__), "metadata")
1
+ # encoding: UTF-8
2
2
 
3
- module GoodData
4
- class Attribute < GoodData::MdObject
3
+ require_relative 'metadata'
5
4
 
5
+ module GoodData
6
+ class Attribute < GoodData::MdObject
6
7
  root_key :attribute
7
8
 
8
9
  class << self
9
10
  def [](id)
10
11
  if id == :all
11
12
  GoodData.get(GoodData.project.md['query'] + '/attributes/')['query']['entries']
12
- else
13
+ else
13
14
  super
14
15
  end
15
16
  end
16
17
  end
17
18
 
18
19
  def display_forms
19
- content["displayForms"].map {|df| GoodData::DisplayForm[df["meta"]["uri"]]}
20
+ content['displayForms'].map { |df| GoodData::DisplayForm[df['meta']['uri']] }
20
21
  end
22
+
21
23
  alias :labels :display_forms
22
24
 
23
25
  def is_attribute?
@@ -1,25 +1,25 @@
1
- require File.join(File.dirname(__FILE__), "metadata")
1
+ # encoding: UTF-8
2
2
 
3
- module GoodData
4
- class Dashboard < GoodData::MdObject
3
+ require_relative 'metadata'
5
4
 
5
+ module GoodData
6
+ class Dashboard < GoodData::MdObject
6
7
  root_key :projectDashboard
7
8
 
8
9
  class << self
9
10
  def [](id)
10
11
  if id == :all
11
12
  GoodData.get(GoodData.project.md['query'] + '/projectdashboards/')['query']['entries']
12
- else
13
+ else
13
14
  super
14
15
  end
15
-
16
16
  end
17
17
 
18
18
  def create_report_tab(tab)
19
19
  title = tab[:title]
20
20
  {
21
21
  :title => title,
22
- :items => tab[:items].map {|i| GoodData::Dashboard.create_report_tab_item(i)}
22
+ :items => tab[:items].map { |i| GoodData::Dashboard.create_report_tab_item(i) }
23
23
  }
24
24
  end
25
25
 
@@ -28,48 +28,47 @@ module GoodData
28
28
 
29
29
  report = GoodData::Report.find_first_by_title(title)
30
30
  {
31
- :reportItem => {
32
- :obj => report.uri,
33
- :sizeY => options[:size_y] || 200,
34
- :sizeX => options[:size_x] || 300,
35
- :style => {
36
- :displayTitle => 1,
37
- :background => {
38
- :opacity => 0
39
- }
40
- },
41
- :visualization => {
42
- :grid => {
43
- :columnWidths => []
44
- },
45
- :oneNumber => {
46
- :labels => {}
47
- }
31
+ :reportItem => {
32
+ :obj => report.uri,
33
+ :sizeY => options[:size_y] || 200,
34
+ :sizeX => options[:size_x] || 300,
35
+ :style => {
36
+ :displayTitle => 1,
37
+ :background => {
38
+ :opacity => 0
39
+ }
40
+ },
41
+ :visualization => {
42
+ :grid => {
43
+ :columnWidths => []
48
44
  },
49
- :positionY => options[:position_y] || 0,
50
- :filters => [],
51
- :positionX => options[:position_x] || 0
52
- }
45
+ :oneNumber => {
46
+ :labels => {}
47
+ }
48
+ },
49
+ :positionY => options[:position_y] || 0,
50
+ :filters => [],
51
+ :positionX => options[:position_x] || 0
52
+ }
53
53
  }
54
54
  end
55
55
 
56
56
  def create(options={})
57
57
  stuff = {
58
- "projectDashboard" => {
59
- "content" => {
60
- "tabs" => options[:tabs].map {|t| GoodData::Dashboard.create_report_tab(t)},
61
- "filters" => []
62
- },
63
- "meta" => {
64
- "tags" => options[:tags],
65
- "summary" => options[:summary],
66
- "title" => options[:title]
67
- }
68
- }
58
+ 'projectDashboard' => {
59
+ 'content' => {
60
+ 'tabs' => options[:tabs].map { |t| GoodData::Dashboard.create_report_tab(t) },
61
+ 'filters' => []
62
+ },
63
+ 'meta' => {
64
+ 'tags' => options[:tags],
65
+ 'summary' => options[:summary],
66
+ 'title' => options[:title]
67
+ }
68
+ }
69
69
  }
70
70
  Dashboard.new(stuff)
71
71
  end
72
-
73
72
  end
74
73
 
75
74
  def exportable?
@@ -79,24 +78,24 @@ module GoodData
79
78
  def export(format, options={})
80
79
  supported_formats = [:pdf]
81
80
  fail "Wrong format provied \"#{format}\". Only supports formats #{supported_formats.join(', ')}" unless supported_formats.include?(format)
82
- tab = options[:tab] || ""
81
+ tab = options[:tab] || ''
83
82
 
84
- x = GoodData.post("#{GoodData.project.uri}/clientexport", {"clientExport" => {"url" => "https://secure.gooddata.com/dashboard.html#project=#{GoodData.project.uri}&dashboard=#{uri}&tab=#{tab}&export=1", "name" => title}}, :process => false)
83
+ req_uri = "/gdc/projects/#{GoodData.project.uri}/clientexport"
84
+ x = GoodData.post(req_uri, {'clientExport' => {'url' => "https://secure.gooddata.com/dashboard.html#project=#{GoodData.project.uri}&dashboard=#{uri}&tab=#{tab}&export=1", 'name' => title}}, :process => false)
85
85
  while (x.code == 202) do
86
86
  sleep(1)
87
- uri = JSON.parse(x.body)["asyncTask"]["link"]["poll"]
87
+ uri = MultiJson.load(x.body)["asyncTask"]["link"]["poll"]
88
88
  x = GoodData.get(uri, :process => false)
89
89
  end
90
90
  x
91
91
  end
92
92
 
93
93
  def tabs
94
- content["tabs"]
94
+ content['tabs']
95
95
  end
96
96
 
97
97
  def tabs_ids
98
- tabs.map {|t| t["identifier"]}
98
+ tabs.map { |t| t['identifier'] }
99
99
  end
100
-
101
100
  end
102
101
  end
@@ -1,12 +1,11 @@
1
+ # encoding: UTF-8
2
+
1
3
  require 'active_support/all'
2
4
 
3
- # TODO: Move to some shared helper
4
- class BigDecimal; def pretty_print(p) p.text to_s; end; end
5
+ require_relative '../extensions/big_decimal'
5
6
 
6
7
  module GoodData
7
-
8
8
  class DataResult
9
-
10
9
  attr_reader :data
11
10
 
12
11
  def initialize(data)
@@ -22,18 +21,16 @@ module GoodData
22
21
  a = to_table.to_a
23
22
  data = a.transpose
24
23
  data.unshift((1..a.length).to_a) if with_indices
25
- data.each_with_index.map{|col, i|
26
- col.unshift(i.zero? ? nil : i) if with_indices # inserts row labels #
27
- w = col.map{|cell| cell.to_s.length}.max # w = "column width" #
28
- col.each_with_index.map{|cell, i|
29
- i.zero? ? cell.to_s.center(w) : cell.to_s.ljust(w)} # alligns the column #
30
- }.transpose.map{|row| "[#{row.join(' | ')}]"}.unshift("").join("\n")
24
+ data.each_with_index.map { |col, i|
25
+ col.unshift(i.zero? ? nil : i) if with_indices # inserts row labels #
26
+ w = col.map { |cell| cell.to_s.length }.max # w = "column width" #
27
+ col.each_with_index.map { |cell, i|
28
+ i.zero? ? cell.to_s.center(w) : cell.to_s.ljust(w) } # alligns the column #
29
+ }.transpose.map { |row| "[#{row.join(' | ')}]" }.unshift('').join("\n")
31
30
  end
32
31
 
33
32
  def to_table
34
- raise "Should be implemented in subclass"
33
+ raise 'Should be implemented in subclass'
35
34
  end
36
-
37
35
  end
38
-
39
36
  end
@@ -1,8 +1,9 @@
1
- require File.join(File.dirname(__FILE__), "metadata.rb")
1
+ # encoding: UTF-8
2
+
3
+ require_relative 'metadata.rb'
2
4
 
3
5
  module GoodData
4
6
  class DataSet < MdObject
5
-
6
7
  root_key :dataSet
7
8
 
8
9
  SLI_CTG = 'singleloadinterface'
@@ -13,19 +14,18 @@ module GoodData
13
14
  end
14
15
 
15
16
  def sli
16
- raise NoProjectError.new "Connect to a project before searching for an object" unless GoodData.project
17
+ raise NoProjectError.new 'Connect to a project before searching for an object' unless GoodData.project
17
18
  slis = GoodData.project.md.links(Model::LDM_CTG).links(SLI_CTG)[DS_SLI_CTG]
18
19
  uri = slis[identifier]['link']
19
20
  MdObject[uri]
20
21
  end
21
22
 
22
23
  def attributes
23
- content["attributes"].map {|a| GoodData::Attribute[a]}
24
+ content['attributes'].map { |a| GoodData::Attribute[a] }
24
25
  end
25
26
 
26
27
  def facts
27
- content["facts"].map {|a| GoodData::Attribute[a]}
28
+ content['facts'].map { |a| GoodData::Attribute[a] }
28
29
  end
29
-
30
30
  end
31
31
  end
@@ -1,9 +1,9 @@
1
- require File.join(File.dirname(__FILE__), "metadata")
1
+ # encoding: UTF-8
2
2
 
3
- module GoodData
4
- class DisplayForm < GoodData::MdObject
3
+ require_relative 'metadata'
5
4
 
5
+ module GoodData
6
+ class DisplayForm < GoodData::MdObject
6
7
  root_key :attributeDisplayForm
7
-
8
8
  end
9
9
  end