twb 1.0.5 → 1.9.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,48 @@
1
+ # Copyright (C) 2014, 2017 Chris Gerrard
2
+ #
3
+ # This program is free software: you can redistribute it and/or modify
4
+ # it under the terms of the GNU General Public License as published by
5
+ # the Free Software Foundation, either version 3 of the License, or
6
+ # (at your option) any later version.
7
+ #
8
+ # This program is distributed in the hope that it will be useful,
9
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
+ # GNU General Public License for more details.
12
+ #
13
+ # You should have received a copy of the GNU General Public License
14
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
15
+
16
+ module Twb
17
+ module Util
18
+
19
+ require 'net/ftp'
20
+
21
+ class FTPPublisher
22
+
23
+ attr_reader :fileName, :fileURL
24
+
25
+ def initialize
26
+ @ftp = Net::FTP.new('gerrard.net')
27
+ @ftp.login("tableautools", 'TableauT00ls!')
28
+ end
29
+
30
+ def publish fileName
31
+ @fileName = fileName
32
+ @ftp.passive = true
33
+ @ftp.puttextfile(fileName, fileName)
34
+ @fileURL = "http://gerrard.net/tableautools/#{fileName}"
35
+ end
36
+
37
+ def list
38
+ @ftp.list()
39
+ end
40
+
41
+ def close
42
+ @ftp.close unless @ftp.nil?
43
+ end
44
+
45
+ end
46
+
47
+ end # module Util
48
+ end # module Twb
@@ -0,0 +1,52 @@
1
+ # Copyright (C) 2012, 2015 Chris Gerrard
2
+ #
3
+ # This program is free software: you can redistribute it and/or modify
4
+ # it under the terms of the GNU General Public License as published by
5
+ # the Free Software Foundation, either version 3 of the License, or
6
+ # (at your option) any later version.
7
+ #
8
+ # This program is distributed in the hope that it will be useful,
9
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
+ # GNU General Public License for more details.
12
+ #
13
+ # You should have received a copy of the GNU General Public License
14
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
15
+
16
+ module Twb
17
+ module Util
18
+
19
+ class JoinTable
20
+ attr_reader :table, :datasource
21
+ def initialize(table, datasource=nil)
22
+ @table = table
23
+ @datasource = datasource
24
+ end
25
+ end # class JoinTable
26
+
27
+
28
+ class JoinTablePair
29
+ attr_reader :from, :to
30
+ def initialize(from, to)
31
+ raise ParamaterException.new("'From' cannot be nil.") if from.nil?
32
+ raise ParamaterException.new("'To' cannot be nil.") if to.nil?
33
+ @from = from
34
+ @to = to
35
+ end
36
+ end # class JoinTablePair
37
+
38
+ class JoinTree
39
+ attr_reader :root, :maxdepth
40
+ def initialize
41
+ @root = nil
42
+ @maxdepth = 0
43
+ end
44
+ def add from, to
45
+ puts "abc"
46
+ end
47
+
48
+ end # class JoinTree
49
+
50
+
51
+ end # module Util
52
+ end # module Twb
@@ -25,11 +25,12 @@ module Twb
25
25
  class Workbook
26
26
 
27
27
  attr_reader :workbooknode
28
- attr_reader :name, :dir
29
- attr_reader :modtime, :version, :build
30
- attr_reader :datasources, :datasourceNames, :datasourceUINames
31
- attr_reader :dashboards, :storyboards, :worksheets, :actions
32
- attr_reader :valid, :ndoc
28
+ attr_reader :name, :dir
29
+ attr_reader :modtime, :version, :build
30
+ attr_reader :datasources, :datasource
31
+ attr_reader :datasourceNames, :datasourceUINames, :dataSourceNamesMap
32
+ attr_reader :dashboards, :storyboards, :worksheets, :actions
33
+ attr_reader :valid, :ndoc
33
34
 
34
35
  ##
35
36
  # Creates a Workbook from its file name.
@@ -39,6 +40,9 @@ module Twb
39
40
  # The Workbook's file name, the Workbook can be a TWB or TWBX file.
40
41
  #
41
42
  def initialize twbWithDir
43
+ raise ArgumentError.new("ERROR in Workbok creation: '#{twbWithDir}' must be a String, is a #{twbWithDir.class} \n ") unless twbWithDir.is_a? String
44
+ raise ArgumentError.new("ERROR in Workbok creation: '#{twbWithDir}' must have an extension of .twb or .twbx \n ") unless twbWithDir.upcase.end_with?(".TWB", ".TWBX")
45
+ raise ArgumentError.new("ERROR in Workbok creation: '#{twbWithDir}' cannot be found, must be a Tableau Workbook file. \n ") unless File.file?(twbWithDir)
42
46
  @valid = false
43
47
  if File.file?(twbWithDir) then
44
48
  @name = File.basename(twbWithDir)
@@ -66,8 +70,7 @@ module Twb
66
70
 
67
71
  def processDoc
68
72
  @workbooknode = @ndoc.at_xpath('//workbook')
69
- @version = @ndoc.xpath('/workbook/@version')
70
- @build = @ndoc.xpath('/workbook/comment()').text.gsub(/^[^0-9]+/,'').strip
73
+ @version = @ndoc.xpath('/workbook/@version').first.text
71
74
  loaddatasources
72
75
  loadWorksheets
73
76
  loadDashboards
@@ -77,17 +80,31 @@ module Twb
77
80
  @valid = true
78
81
  end
79
82
 
83
+ def build
84
+ @build ||= loadBuild
85
+ end
86
+
87
+ def loadBuild
88
+ # - earlier Version, need to confirm when source-build began
89
+ # @build = @ndoc.xpath('/workbook/comment()').text.gsub(/^[^0-9]+/,'').strip
90
+ @build = if !@ndoc.xpath('/workbook/@source-build').nil?
91
+ @ndoc.xpath('/workbook/@source-build').first.text
92
+ else
93
+ @ndoc.xpath('/workbook/comment()').text.gsub(/^[^0-9]+/,'').strip
94
+ end
95
+ end
96
+
80
97
  def loaddatasources
81
98
  # puts "LOAD DATA SOURCES"
82
99
  # @dataSourcesNode = @ndoc.at_xpath('//workbook/datasources')
83
100
  @datasources = Set.new
84
- @datasourceNames = Set.new
85
- @datasourceUINames = Set.new
101
+ @datasourceNames = SortedSet.new
102
+ @datasourceUINames = SortedSet.new
86
103
  @dataSourceNamesMap = {}
87
104
  datasourceNodes = @ndoc.xpath('//workbook/datasources/datasource')
88
105
  # puts "DATASOURCENODES : #{@datasourceNodes.length}"
89
106
  datasourceNodes.each do |node|
90
- datasource = Twb::DataSource.new(node)
107
+ datasource = Twb::DataSource.new(node,self)
91
108
  @datasources << datasource
92
109
  @datasourceNames << datasource.name
93
110
  @datasourceNames << datasource.uiname
@@ -1,4 +1,4 @@
1
- # Copyright (C) 2014, 2015 Chris Gerrard
1
+ # Copyright (C) 2014, 2018 Chris Gerrard
2
2
  #
3
3
  # This program is free software: you can redistribute it and/or modify
4
4
  # it under the terms of the GNU General Public License as published by
@@ -18,22 +18,13 @@ require 'digest/md5'
18
18
 
19
19
  module Twb
20
20
 
21
- class WorksheetDataSource
22
- attr_reader :node, :name, :caption, :uiname
23
- def initialize node
24
- @node = node
25
- @caption = node.attr('caption')
26
- @name = node.attr('name')
27
- @uiname = if @caption.nil? || @caption == '' then @name else @caption end
28
- end
29
- end
30
-
31
21
  class Worksheet
32
22
 
33
23
  @@hasher = Digest::SHA256.new
34
24
 
35
25
  attr_reader :node, :name, :datasourcenames, :datasources
36
- attr_reader :fields, :rowFields, :colFields, :datasourceFields
26
+ attr_reader :panesCount
27
+ attr_reader :fields, :rowFields, :colFields, :paneFields, :datasourceFields
37
28
 
38
29
  def initialize sheetNode
39
30
  @node = sheetNode
@@ -61,33 +52,34 @@ module Twb
61
52
  end
62
53
 
63
54
  def datasourceFields
64
- @datasourceFields.nil? ? loadFields : @datasourceFields
55
+ @datasourceFields ||= loadFields
65
56
  end
66
57
 
67
58
  def fields
68
- @fields.nil? ? loadFields : @fields
59
+ @fields ||= loadFields
60
+ # @fields.nil? ? loadFields : @fields
69
61
  end
70
62
 
71
63
  def loadFields
72
64
  # puts "WORKSHEET loadFields"
65
+ @fields = {}
73
66
  @datasourceFields = {}
74
67
  dsNodes = @node.xpath('.//datasource-dependencies')
75
68
  dsNodes.each do |dsn|
76
- dsName = dsn.attribute('datasource').text
77
- dsfs = @datasourceFields[dsName] = []
69
+ dsName = dsn.attribute('datasource').text
70
+ dsFields = @datasourceFields[dsName] = Set.new
78
71
  fieldNodes = dsn.xpath('./column')
79
72
  fieldNodes.each do |fn|
80
- fname = fn.attribute('name')
81
- dsfs.push fname.text.gsub(/^\[/,'').gsub(/\]$/,'') unless fname.nil?
73
+ dsFields.add WorksheetField.new fn, dsName
82
74
  end
83
75
  end
76
+ @fields[:dsfields] = @datasourceFields
84
77
  # <rows>([DATA Connection (copy 2)].[none:JIRA HARVEST Correspondence Name:nk] / [DATA Connection (copy 2)].[none:CreatedDate (Issue) (DAY):ok])</rows>
85
78
  # <cols>[DATA Connection (copy 2)].[:Measure Names]</cols>
86
- @fields = {}
87
- @rowFields = pullRowColFields './/rows' # returns map of data source => (Set of) field names
88
- @fields[:rows] = @rowFields
89
- @colFields = pullRowColFields './/cols' # returns map of data source => (Set of) field names
90
- @fields[:cols] = @colFields
79
+ @rowFields ||= loadRowColFields(:rows) # returns map of data source => (Set of) field names
80
+ @fields[:rows] = @rowFields
81
+ @colFields ||= loadRowColFields(:cols) # returns map of data source => (Set of) field names
82
+ @fields[:cols] = @colFields
91
83
  end
92
84
 
93
85
  def addDSFields fields, usage
@@ -95,41 +87,142 @@ module Twb
95
87
  end
96
88
  end
97
89
 
98
- def pullRowColFields xpath
99
- dsFields = {} # data source => (Set of) fields by names
100
- node = @node.xpath(xpath) # expect only one each of <rows> & <cols> node per <worksheet node
101
- code = node.text
102
- # puts "\n CODE: '#{code}'"
103
- return dsFields if code.nil?
104
- codeFields = code.gsub(/^[(]|[)]$/,'').split(/] [\/+*] [(]?\[/)
105
- return dsFields if codeFields.nil? # - commented out, left here for logic clarity
106
- codeFields.each do |dsf|
107
- parts = dsf.split('].[')
108
- if parts.length == 2
109
- ds = parts[0].gsub(/^\[|\]$/,'')
110
-
111
- # remove prefix(es), if any
112
- f1 = parts[1].gsub(/^pcto:|^fVal:/,'').gsub(/^[a-z]+:/,'')
113
- # /^pcto:|^fVal:/ - special handling based upon actual codings
114
-
115
- # remove terminating )s
116
- f2 = f1.gsub(/[\)]+$/,'')
117
-
118
- # remove suffix(es), if any
119
- f3 = f2.gsub(/:[0-9]+[\]]?$/,'').gsub(/:[a-z]+[\]]?$|[\]]?$/,'')
120
- # /:[0-9]+[\]]?$/ - special handling based upon actual codings
121
-
122
- dsFields[ds] = Set.new unless dsFields.include? ds
123
- dsFields[ds].add(f3)
124
- end
90
+ def panesCount
91
+ @panesCount ||= @panesCount = node.xpath('.//panes/pane').length
92
+ end
93
+
94
+ def loadRowColFields(type)
95
+ fields = []
96
+ xpath = case type
97
+ when :rows then './/rows'
98
+ when :cols then './/cols'
99
+ end
100
+ return fields if xpath.nil?
101
+ node = @node.at_xpath(xpath)
102
+ RowsColsSplitter.new(node).fields.each do |fcode|
103
+ fields << CodedField.new(fcode)
125
104
  end
126
- return dsFields
105
+ return fields
106
+ end
107
+
108
+ def paneFields
109
+ @paneFields ||= loadPaneFields
110
+ end
111
+
112
+ def loadPaneFields
113
+ @paneFields = Set.new
114
+ panes = @node.xpath('.//pane')
127
115
  end
128
116
 
129
117
  def datasourcenames
130
118
  @datasources.keys
131
119
  end
132
-
133
120
  end
134
121
 
135
- end
122
+ class WorksheetDataSource
123
+ # --
124
+ attr_reader :node, :name, :caption, :uiname
125
+ # --
126
+ def initialize node
127
+ @node = node
128
+ @caption = node.attr('caption')
129
+ @name = node.attr('name').gsub(/^\[/,'').gsub(/\]$/,'')
130
+ @uiname = if @caption.nil? || @caption == '' then @name else @caption end
131
+ end
132
+ end # class WorksheetDataSource
133
+
134
+
135
+ class WorksheetField
136
+ # --
137
+ attr_reader :datasource, :name, :caption, :uiname, :node
138
+ # --
139
+ def initialize node, dsName
140
+ @node = node
141
+ @name = node.attr('name').gsub(/^\[/,'').gsub(/\]$/,'')
142
+ @caption = node.attr('caption').nil? ? nil : node.attr('caption').gsub(/^\[/,'').gsub(/\]$/,'')
143
+ @uiname = if @caption.nil? || @caption == '' then @name else @caption end
144
+ @datasource = dsName
145
+ end
146
+ # --
147
+ def to_s
148
+ "name:#{@name}|caption:#{@caption}|uiname:#{@uiname}||ds:#{datasource}"
149
+ end
150
+ end # WorksheetField
151
+
152
+
153
+ class RowsColsSplitter
154
+ include Comparable
155
+ attr_reader :fields
156
+ def initialize node
157
+ @fields = []
158
+ return fields if node.nil?
159
+ unless node.text.nil?
160
+ codes = node.text.split(/[\])] [\/+*] [(]?\[/)
161
+ codes.each {|c| @fields << c.gsub(/^[(]*[\[]*|[)\]]*$/,'')}
162
+ end
163
+ end
164
+ end # class RowsColsSplitter
165
+
166
+
167
+
168
+ class CodedField
169
+
170
+ include Comparable
171
+
172
+ attr_reader :code, :dataSource, :prefix, :name, :suffix
173
+
174
+ def initialize incode
175
+ # puts "CF: #{incode}"
176
+ @code = incode
177
+ trimCode = code.gsub(/^\[/,'').gsub(/\]$/,'')
178
+ # puts " : '#{trimCode}'"
179
+ @dataSource = nil
180
+ dsFldBits = trimCode.split('].[')
181
+ if dsFldBits.length == 2
182
+ @dataSource = dsFldBits[0]
183
+ fieldCode = dsFldBits[1]
184
+ else
185
+ fieldCode = dsFldBits[0]
186
+ end
187
+ # puts "fc: #{fieldCode}"
188
+ parts = fieldCode.split(':')
189
+ bits =
190
+ if ':Measure Names'.eql?(fieldCode)
191
+ {:prefix => nil, :name => fieldCode, :suffix => nil }
192
+ else
193
+ case parts.length
194
+ when 1
195
+ bits = {:prefix => nil, :name => parts[0], :suffix => nil}
196
+ when 3
197
+ bits = {:prefix => parts[0], :name => parts[1], :suffix => parts[2]}
198
+ when 4
199
+ if parts[-1].match(/^(\d)+$/)
200
+ bits = {:prefix => parts[0], :name => parts[1], :suffix => "#{parts[2]}:#{parts[3]}"}
201
+ else
202
+ bits = {:prefix => "#{parts[0]}:#{parts[1]}", :name => parts[2], :suffix => parts[3]}
203
+ end
204
+ when 5
205
+ bits = {:prefix => "#{parts[0]}:#{parts[1]}", :name => parts[2], :suffix => "#{parts[3]}:#{parts[4]}"}
206
+ else
207
+ bits = {:prefix => "OOPS #{parts.inspect}", :name => '', :suffix => '' }
208
+ end
209
+ end
210
+ @prefix = bits[:prefix]
211
+ @name = bits[:name]
212
+ @suffix = bits[:suffix]
213
+ # puts " l: #{parts.length} == #{bits}"
214
+ # bits.each { |k,v| puts " l: %-7s %s" % [k,v] }
215
+ end
216
+ # --
217
+ def to_s
218
+ @code
219
+ end
220
+
221
+ def <=>(other)
222
+ @code <=> other.code
223
+ end
224
+
225
+ end #class CodedField
226
+
227
+
228
+ end # module Twb
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: twb
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.5
4
+ version: 1.9.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Gerrard
@@ -24,9 +24,13 @@ files:
24
24
  - lib/twb.rb
25
25
  - lib/twb/TwbTest.rb
26
26
  - lib/twb/action.rb
27
- - lib/twb/analysis/calculatedfieldsanalyzer.rb
28
- - lib/twb/apps/X-Ray Dashboards.rb
29
- - lib/twb/countNodes.rb
27
+ - lib/twb/analysis/CalculatedFields/CSVEmitter.rb
28
+ - lib/twb/analysis/CalculatedFields/CalculatedFieldsAnalyzer.rb
29
+ - lib/twb/analysis/CalculatedFields/MarkdownEmitter.rb
30
+ - lib/twb/analysis/DataSources/DataSourceTableFieldsCSVEmitter.rb
31
+ - lib/twb/analysis/Sheets/WorksheetDataStructureCSVEmitter.rb
32
+ - lib/twb/calculatedfield.rb
33
+ - lib/twb/columnfield.rb
30
34
  - lib/twb/dashboard.rb
31
35
  - lib/twb/dashboard.txt
32
36
  - lib/twb/datasource.rb
@@ -47,11 +51,13 @@ files:
47
51
  - lib/twb/storyboard.rb
48
52
  - lib/twb/there.rb
49
53
  - lib/twb/util/dotFileRenderer.rb
54
+ - lib/twb/util/ftpPublisher.rb
50
55
  - lib/twb/util/graphedge.rb
51
56
  - lib/twb/util/graphedges.rb
52
57
  - lib/twb/util/graphnode.rb
53
58
  - lib/twb/util/hashtohtml.rb
54
59
  - lib/twb/util/htmllistcollapsible.rb
60
+ - lib/twb/util/joinUtilities.rb
55
61
  - lib/twb/util/twbDashSheetDataDotBuilder.rb
56
62
  - lib/twb/util/twbDashSheetDataDotRenderer.rb
57
63
  - lib/twb/util/twbDashesSheetDataDotBuilder.rb
@@ -90,7 +96,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
90
96
  version: '0'
91
97
  requirements: []
92
98
  rubyforge_project:
93
- rubygems_version: 2.6.11
99
+ rubygems_version: 2.6.13
94
100
  signing_key:
95
101
  specification_version: 4
96
102
  summary: Classes for accessing Tableau Workbooks and their contents.