twb 1.0.5 → 1.9.1

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,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.