twb 0.5.3 → 1.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.
- checksums.yaml +7 -0
- data/lib/twb/action.rb +121 -0
- data/lib/twb/countNodes.rb +98 -0
- data/lib/twb/dashboard.rb +5 -1
- data/lib/twb/datasource.rb +196 -6
- data/lib/twb/fieldcalculation.rb +54 -18
- data/lib/twb/findDSFields.rb +153 -0
- data/lib/twb/graph.rb +53 -0
- data/lib/twb/graphedges.rb +36 -0
- data/lib/twb/graphnode.rb +53 -0
- data/lib/twb/identifyFields.rb +103 -0
- data/lib/twb/localfield.rb +7 -6
- data/lib/twb/util/graphedge.rb +69 -0
- data/lib/twb/util/graphedges.rb +38 -0
- data/lib/twb/util/graphnode.rb +94 -0
- data/lib/twb/workbook.rb +60 -40
- data/lib/twb/worksheet.rb +69 -1
- data/lib/twb.rb +6 -1
- metadata +25 -17
@@ -0,0 +1,153 @@
|
|
1
|
+
require 'nokogiri'
|
2
|
+
require 'csv'
|
3
|
+
require 'twb'
|
4
|
+
|
5
|
+
def init
|
6
|
+
system 'cls'
|
7
|
+
$pFile = File.open('identifyFields.txt','w')
|
8
|
+
|
9
|
+
sqiCSV = 'TWBFieldsByType.csv'
|
10
|
+
$fieldsCSV = CSV.open(sqiCSV, 'w')
|
11
|
+
$fieldsCSV << [
|
12
|
+
'TWB',
|
13
|
+
'Data Connection - UI',
|
14
|
+
'Field Name',
|
15
|
+
'TWB Field Type',
|
16
|
+
'Data Type',
|
17
|
+
'Calc Field?',
|
18
|
+
]
|
19
|
+
$path = if ARGV.empty? then '**/*.twb' else ARGV[0] end
|
20
|
+
emit " "
|
21
|
+
emit " Identifying fields in Tableau Workbook Data Connections (TWDCs).\n "
|
22
|
+
emit " Looking for Workbooks matching: '#{$path}' - from: #{ARGV[0]}\n\n "
|
23
|
+
end
|
24
|
+
|
25
|
+
$localEmit = true
|
26
|
+
def emit stuff
|
27
|
+
$pFile.puts stuff
|
28
|
+
puts stuff if $localEmit
|
29
|
+
end
|
30
|
+
|
31
|
+
$paths = {
|
32
|
+
'Relation Column' => { 'fieldXPath' => './connection/relation/columns/column',
|
33
|
+
'nameXPath' => './@name'
|
34
|
+
},
|
35
|
+
'Metadata Record' => { 'fieldXPath' => './connection/metadata-records/metadata-record[@class="column"]',
|
36
|
+
'nameXPath' => './remote-name[text()]'
|
37
|
+
},
|
38
|
+
'Columns' => { 'fieldXPath' => './column',
|
39
|
+
'nameXPath' => './@name'
|
40
|
+
},
|
41
|
+
}
|
42
|
+
|
43
|
+
# different connection classes have
|
44
|
+
# different paths & attributes to the Relation fields
|
45
|
+
$relClassPaths = {
|
46
|
+
'dataengine' => { 'fieldXPath' => './connection/relation/columns/map',
|
47
|
+
'nameXPath' => './@name'
|
48
|
+
},
|
49
|
+
'excel-direct' => { 'fieldXPath' => './connection/relation/columns/map',
|
50
|
+
'nameXPath' => './@name'
|
51
|
+
},
|
52
|
+
'textscan' => { 'fieldXPath' => './connection/relation/columns/map',
|
53
|
+
'nameXPath' => './@name'
|
54
|
+
},
|
55
|
+
}
|
56
|
+
|
57
|
+
def relationFields dataSource, connClass
|
58
|
+
emit "\t relationFields connClass: '%s'" % [connClass]
|
59
|
+
xPaths = $relClassPaths[connClass]
|
60
|
+
emit "\t xPaths: #{xPaths}"
|
61
|
+
fnodesPath = xPaths['fieldXPath']
|
62
|
+
fnamePath = xPaths['nameXPath']
|
63
|
+
fieldNodes = dataSource.xpath(fnodesPath)
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
def metaDataRecords dataSource
|
68
|
+
emit "\t metaDataRecords " #connClass: '%s'" % [connClass]
|
69
|
+
mdNodes = dataSource.xpath('./connection/metadata-records/metadata-record[@class="column"]')
|
70
|
+
emit " mdNodes.size: #{mdNodes.size}"
|
71
|
+
fields = {}
|
72
|
+
mdNodes.each do |node|
|
73
|
+
name = node.xpath('./local-name').text.gsub(/^\[/,'').gsub(/\]$/,'')
|
74
|
+
type = node.xpath('./local-type').text
|
75
|
+
fields[name] = {'type' => type}
|
76
|
+
end
|
77
|
+
return fields
|
78
|
+
end
|
79
|
+
|
80
|
+
def localColumns dataSource
|
81
|
+
emit "\t localColumns " #connClass: '%s'" % [connClass]
|
82
|
+
colNodes = dataSource.xpath('./column')
|
83
|
+
emit " colNodes.size: #{colNodes.size}"
|
84
|
+
fields = {}
|
85
|
+
colNodes.each do |node|
|
86
|
+
calcField = !node.at_xpath('./calculation').nil?
|
87
|
+
caption = node.attribute('caption')
|
88
|
+
name = node.attribute('name').text.gsub(/^\[/,'').gsub(/\]$/,'')
|
89
|
+
type = node.attribute('datatype')
|
90
|
+
emit " \t name: %s\n \t type: %s\n \t caption: %s" % [name, type, caption]
|
91
|
+
caption = name if caption.nil?
|
92
|
+
emit " \t caption: %s \n " % [caption]
|
93
|
+
fields[caption] = {'type' => type, 'calcField' => calcField}
|
94
|
+
end
|
95
|
+
return fields
|
96
|
+
end
|
97
|
+
|
98
|
+
def process file
|
99
|
+
emit "\n == #{file}"
|
100
|
+
doc = Nokogiri::XML(open(file))
|
101
|
+
dataSourcesNodes = doc.xpath('//workbook/datasources/datasource')
|
102
|
+
dataSourcesNodes.each do |ds|
|
103
|
+
dsCaption = ds.attribute('caption')
|
104
|
+
dsName = ds.attribute('name')
|
105
|
+
emit "\n dsCapt: #{dsCaption}"
|
106
|
+
emit " dsName: '#{dsName}'\n ---"
|
107
|
+
connClass = ds.xpath('./connection/@class')
|
108
|
+
emit "dsCClass: '#{connClass}'\n ---"
|
109
|
+
if dsName != 'Parameters' then
|
110
|
+
# relationFields ds, connClass
|
111
|
+
mdFields = metaDataRecords ds
|
112
|
+
mdFields.each do |fname, payload|
|
113
|
+
emit "\t "
|
114
|
+
$fieldsCSV << [file, dsCaption, fname, 'metadata', payload['type'] ]
|
115
|
+
emit [file, dsCaption, fname, 'metadata', payload['type'] ]
|
116
|
+
end
|
117
|
+
colFields = localColumns ds
|
118
|
+
colFields.each do |fname, payload|
|
119
|
+
emit "\t "
|
120
|
+
$fieldsCSV << [file, dsCaption, fname, 'column', payload['type'], payload['calcField'] ]
|
121
|
+
emit [file, dsCaption, fname, 'column', payload['type'], payload['calcField'] ]
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
# $paths.each do |type, paths|
|
126
|
+
# emit " type: #{type}"
|
127
|
+
# emit paths['fieldXPath']
|
128
|
+
# nodes = ds.xpath(paths['fieldXPath']).to_a
|
129
|
+
# nodes.each do |node|
|
130
|
+
# name = node.xpath(paths['nameXPath']).text
|
131
|
+
# emit " fname: #{name}"
|
132
|
+
# end
|
133
|
+
# end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
init
|
138
|
+
|
139
|
+
def showPaths
|
140
|
+
$paths.each do |type, paths|
|
141
|
+
emit " #{type}"
|
142
|
+
paths.each do |key, value|
|
143
|
+
emit " : %10s => %-s" % [key, value]
|
144
|
+
end
|
145
|
+
puts " "
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
emit "XXX \n#{$relClassPaths}"
|
150
|
+
|
151
|
+
Dir.glob($path) {|twb| process twb }
|
152
|
+
|
153
|
+
emit "\n\n== Done ==\n "
|
data/lib/twb/graph.rb
ADDED
@@ -0,0 +1,53 @@
|
|
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
|
+
|
18
|
+
class Graph
|
19
|
+
|
20
|
+
# @root - the visible name
|
21
|
+
# @id - the technical identifier, used to distinquish the node from similarly named nodes
|
22
|
+
# @type - useful for categorizing the node
|
23
|
+
attr_reader :name, :id, :type
|
24
|
+
attr_accessor :properties
|
25
|
+
|
26
|
+
|
27
|
+
def initialize (name:, id:, type:, properties: {})
|
28
|
+
@name = name
|
29
|
+
@id = id
|
30
|
+
@type = type
|
31
|
+
@properties = properties
|
32
|
+
end
|
33
|
+
|
34
|
+
def dotLabel
|
35
|
+
# "JIRA 1::JIRA 1.csv" [label="JIRA 1.csv"]
|
36
|
+
"\"%s\" [label=\"%s\"]" % [id, name]
|
37
|
+
end
|
38
|
+
|
39
|
+
def eql? other
|
40
|
+
@name == other.name && @id == other.id && @type == other.type && @properties == other.properties
|
41
|
+
end
|
42
|
+
|
43
|
+
def hash
|
44
|
+
[@name, @id, @type, @properties].hash
|
45
|
+
end
|
46
|
+
|
47
|
+
def to_s
|
48
|
+
"name:'%s' id:'%s' t:'%s' p:'%s'" % [@name, @id, @type, @properties.to_s]
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
@@ -0,0 +1,36 @@
|
|
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
|
+
|
18
|
+
class Graphedges
|
19
|
+
|
20
|
+
# @root - the graph's root node
|
21
|
+
# @edges - the collection of the graph's edges
|
22
|
+
attr_reader :root
|
23
|
+
attr_accessor :edges
|
24
|
+
|
25
|
+
def initialize (root:)
|
26
|
+
@root = root if root.instance_of? Twb::Graphnode
|
27
|
+
@edges = []
|
28
|
+
end
|
29
|
+
|
30
|
+
def to_s
|
31
|
+
"Root:'%s' edges::%s::" % [@root.to_s, @edges.to_s]
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
@@ -0,0 +1,53 @@
|
|
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
|
+
|
18
|
+
class Graphnode
|
19
|
+
|
20
|
+
# @name - the visible name
|
21
|
+
# @id - the technical identifier, used to distinquish the node from similarly named nodes
|
22
|
+
# @type - useful for categorizing the node
|
23
|
+
attr_reader :name, :id, :type
|
24
|
+
attr_accessor :properties
|
25
|
+
|
26
|
+
|
27
|
+
def initialize (name:, id:, type:, properties: {})
|
28
|
+
@name = name
|
29
|
+
@id = id
|
30
|
+
@type = type
|
31
|
+
@properties = properties
|
32
|
+
end
|
33
|
+
|
34
|
+
def dotLabel
|
35
|
+
# "JIRA 1::JIRA 1.csv" [label="JIRA 1.csv"]
|
36
|
+
"\"%s\" [label=\"%s\"]" % [id, name]
|
37
|
+
end
|
38
|
+
|
39
|
+
def eql? other
|
40
|
+
@name == other.name && @id == other.id && @type == other.type && @properties == other.properties
|
41
|
+
end
|
42
|
+
|
43
|
+
def hash
|
44
|
+
[@name, @id, @type, @properties].hash
|
45
|
+
end
|
46
|
+
|
47
|
+
def to_s
|
48
|
+
"name:'%s' id:'%s' t:'%s' p:'%s'" % [@name, @id, @type, @properties.to_s]
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
require 'nokogiri'
|
2
|
+
require 'csv'
|
3
|
+
require 'twb'
|
4
|
+
|
5
|
+
def init
|
6
|
+
system 'cls'
|
7
|
+
$pFile = File.open('identifyFields.txt','w')
|
8
|
+
|
9
|
+
sqiCSV = 'TWBFieldsByType.csv'
|
10
|
+
$fieldsCSV = CSV.open(sqiCSV, 'w')
|
11
|
+
$fieldsCSV << [
|
12
|
+
'TWB',
|
13
|
+
'Data Connection - UI',
|
14
|
+
'Field Name',
|
15
|
+
'Field Type',
|
16
|
+
]
|
17
|
+
$path = if ARGV.empty? then '**/*.twb' else ARGV[0] end
|
18
|
+
emit " "
|
19
|
+
emit " Identifying fields in Tableau Workbook Data Connections (TWDCs).\n "
|
20
|
+
emit " Looking for Workbooks matching: '#{$path}' - from: #{ARGV[0]}\n\n "
|
21
|
+
end
|
22
|
+
|
23
|
+
def method_name
|
24
|
+
datasources = {}
|
25
|
+
dataSourcesNode = doc.at_xpath('//workbook/datasources')
|
26
|
+
dataSourcesNodes = doc.xpath('//workbook/datasources/datasource').to_a
|
27
|
+
puts " dsn: #{dataSourcesNodes}"
|
28
|
+
datasourceNodes.each do |node|
|
29
|
+
datasource = Twb::DataSource.new(node)
|
30
|
+
datasources[datasource.name] = datasource
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
$localEmit = true
|
35
|
+
def emit stuff
|
36
|
+
$pFile.puts stuff
|
37
|
+
puts stuff if $localEmit
|
38
|
+
end
|
39
|
+
|
40
|
+
$paths = {
|
41
|
+
'Relation Column' => { 'fieldXPath' => './connection/relation/columns/column',
|
42
|
+
'nameXPath' => '@name'
|
43
|
+
},
|
44
|
+
'Metadata Record' => { 'fieldXPath' => './connection/metadata-records/metadata-record[@class="column"]',
|
45
|
+
'nameXPath' => './remote-name'
|
46
|
+
},
|
47
|
+
'Columns' => { 'fieldXPath' => './column',
|
48
|
+
'nameXPath' => '@name'
|
49
|
+
},
|
50
|
+
}
|
51
|
+
|
52
|
+
def proc file
|
53
|
+
emit "\n == #{file}"
|
54
|
+
end
|
55
|
+
|
56
|
+
def process file
|
57
|
+
emit "\n == #{file}"
|
58
|
+
doc = Nokogiri::XML(open(file))
|
59
|
+
dataSourcesNodes = doc.xpath('//workbook/datasources/datasource')
|
60
|
+
dataSourcesNodes.each do |ds|
|
61
|
+
emit "\n dc: #{ds.attribute('caption')}"
|
62
|
+
emit " dn: #{ds.attribute('name')}\n ---"
|
63
|
+
typeCounts = {}
|
64
|
+
$paths.each do |type, path|
|
65
|
+
nodes = ds.xpath(path).to_a
|
66
|
+
# emit " : %3i %-17s %-s" % [nodes.size, type, path]
|
67
|
+
typeCnt = nodes.size
|
68
|
+
nodes.each do |n|
|
69
|
+
# $fieldsCSV << [file,ds.attribute('name'),n.attribute('name'),type]
|
70
|
+
end
|
71
|
+
if type == 'Columns' then
|
72
|
+
calcCnt = 0
|
73
|
+
nodes.each do |n|
|
74
|
+
calc = n.xpath('./calculation')
|
75
|
+
# emit " c: #{calc.size} "
|
76
|
+
calcCnt += calc.size
|
77
|
+
end
|
78
|
+
# emit ' ---'
|
79
|
+
# emit " c#: %3i " % [calcCnt]
|
80
|
+
# emit " !c#: %3i " % [nodes.size - calcCnt]
|
81
|
+
typeCounts['Columns'] = nodes.size
|
82
|
+
typeCounts['Columns - calc'] = calcCnt
|
83
|
+
typeCounts['Columns - not calc'] = nodes.size - calcCnt
|
84
|
+
else
|
85
|
+
typeCounts[type] = typeCnt
|
86
|
+
end
|
87
|
+
end
|
88
|
+
# emit ' ---'
|
89
|
+
typeCounts.each do |t,c|
|
90
|
+
emit " tc: %3i %-s" % [c, t]
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
init
|
96
|
+
|
97
|
+
$paths.each do |path|
|
98
|
+
emit " p: #{path}"
|
99
|
+
end
|
100
|
+
|
101
|
+
Dir.glob($path) {|twb| process twb }
|
102
|
+
|
103
|
+
emit "\n\n== Done ==\n "
|
data/lib/twb/localfield.rb
CHANGED
@@ -21,12 +21,13 @@ module Twb
|
|
21
21
|
|
22
22
|
class LocalField
|
23
23
|
|
24
|
-
attr_reader :type, :node, :
|
24
|
+
attr_reader :type, :node, :tableauname, :dbname, :datatype, :role, :type, :hidden, :caption, :aggregation, :uiname, :calculation, :comments
|
25
25
|
|
26
26
|
def initialize fieldNode
|
27
27
|
@node = fieldNode
|
28
28
|
@type = 'local'
|
29
|
-
@
|
29
|
+
@tableauname = @node.attr('name')
|
30
|
+
@dbname = @node.attr('name').gsub(/^\[/,'').gsub(/\]$/,'')
|
30
31
|
@datatype = @node.attr('datatype')
|
31
32
|
@role = @node.attr('role')
|
32
33
|
@type = @node.attr('type')
|
@@ -35,15 +36,15 @@ module Twb
|
|
35
36
|
@aggregation = @node.attr('aggregation')
|
36
37
|
@calculation = getCalculation
|
37
38
|
@comments = getComments
|
38
|
-
@uiname = if @caption.nil? || @caption == '' then @
|
39
|
+
@uiname = if @caption.nil? || @caption == '' then @dbname else @caption end
|
39
40
|
return self
|
40
41
|
end
|
41
|
-
|
42
|
+
|
42
43
|
def getCalculation
|
43
44
|
calcNode = @node.at_xpath("./calculation")
|
44
45
|
FieldCalculation.new(calcNode) unless calcNode.nil?
|
45
46
|
end
|
46
|
-
|
47
|
+
|
47
48
|
def getComments
|
48
49
|
comments = ''
|
49
50
|
runs = node.xpath('./desc/formatted-text/run')
|
@@ -54,7 +55,7 @@ module Twb
|
|
54
55
|
end
|
55
56
|
return comments
|
56
57
|
end
|
57
|
-
|
58
|
+
|
58
59
|
def remove_attribute attribute
|
59
60
|
@node.remove_attribute(attribute)
|
60
61
|
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# Copyright (C) 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
|
+
class Graphedge
|
20
|
+
|
21
|
+
# @from - the origin node
|
22
|
+
# @to - the destination node
|
23
|
+
# @relationship - useful for categorizing the edge
|
24
|
+
# @properties - useful for categorizing the edge
|
25
|
+
attr_reader :from, :to, :relationship
|
26
|
+
attr_accessor :properties
|
27
|
+
attr_reader :cypherCreate
|
28
|
+
|
29
|
+
# Neo4J cypher variable quote character: `
|
30
|
+
|
31
|
+
def initialize (from:, to:, relationship:, properties: {})
|
32
|
+
raise ArgumentError.new("from: parameter must be a Graphnode, is a '#{from.class}'") unless from.is_a? Twb::Util::Graphnode
|
33
|
+
raise ArgumentError.new( "to: parameter must be a Graphnode, is a '#{to.class}'" ) unless to.is_a? Twb::Util::Graphnode
|
34
|
+
@from = from
|
35
|
+
@to = to
|
36
|
+
@relationship = relationship
|
37
|
+
@properties = properties
|
38
|
+
@cypherCreate = "CREATE #{cypher_s}"
|
39
|
+
end
|
40
|
+
|
41
|
+
def eql? other
|
42
|
+
@from == other.from && @to == other.to && @relationship == other.relationship && @properties == other.properties
|
43
|
+
end
|
44
|
+
|
45
|
+
def hash
|
46
|
+
[@from.hash, @to.hash, @relationship, @properties].hash
|
47
|
+
end
|
48
|
+
|
49
|
+
def to_s
|
50
|
+
"'#{@from.name}//{@from.id}' --#{@relationship}--> '#{@to.name}//#{@to.id}'"
|
51
|
+
end
|
52
|
+
|
53
|
+
def dot
|
54
|
+
"%s -> %s" % [dotquote(from.id), dotquote(to.id)]
|
55
|
+
end
|
56
|
+
|
57
|
+
def dotquote str
|
58
|
+
ns = str.gsub(/(["])/,'\\"')
|
59
|
+
return "\"#{ns}\""
|
60
|
+
end
|
61
|
+
|
62
|
+
def cypher_s
|
63
|
+
"(%s)-[:`%s`]->(%s)" % [@from.cypherID,@relationship,@to.cypherID]
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
end # module Util
|
69
|
+
end # module Twb
|
@@ -0,0 +1,38 @@
|
|
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
|
+
class Graphedges
|
20
|
+
|
21
|
+
# @root - the graph's root node
|
22
|
+
# @edges - the collection of the graph's edges
|
23
|
+
attr_reader :root
|
24
|
+
attr_accessor :edges
|
25
|
+
|
26
|
+
def initialize (root = nil)
|
27
|
+
@root = root if root.instance_of? Twb::Util::Graphnode
|
28
|
+
@edges = []
|
29
|
+
end
|
30
|
+
|
31
|
+
def to_s
|
32
|
+
"Root:'%s' edges::%s::" % [@root.to_s, @edges.to_s]
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
end # module Util
|
38
|
+
end # module Twb
|
@@ -0,0 +1,94 @@
|
|
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
|
+
class Graphnode
|
20
|
+
|
21
|
+
@@stripChars = /[.: %-\-\(\)=]/
|
22
|
+
@@replChar = '_'
|
23
|
+
|
24
|
+
|
25
|
+
|
26
|
+
# @name - the visible name
|
27
|
+
# @id - the technical identifier, used to distinquish the node from similarly named nodes
|
28
|
+
# @type - useful for categorizing the node
|
29
|
+
attr_reader :id, :type, :name
|
30
|
+
attr_reader :cypherID, :cypherCreate
|
31
|
+
# attr_reader :cypherName, :cypherID, :cypherNodeID, :cypherType, :cypherCreate
|
32
|
+
attr_accessor :properties
|
33
|
+
|
34
|
+
# Neo4J cypher variable quote character: `
|
35
|
+
|
36
|
+
def initialize (name:, id:, type:, properties: {})
|
37
|
+
@id = id
|
38
|
+
@type = type
|
39
|
+
@name = name
|
40
|
+
@properties = properties
|
41
|
+
# --
|
42
|
+
@cypherID = '`' + id + '`'
|
43
|
+
# @cypherType = type
|
44
|
+
# @cypherName = name
|
45
|
+
@properties['name'] = name unless @properties.key? 'name'
|
46
|
+
@cypherCreate = "CREATE (%s:`%s` {%s})" % [@cypherID,type,props_s]
|
47
|
+
end
|
48
|
+
|
49
|
+
|
50
|
+
def cypherize str
|
51
|
+
enquote str.gsub(@@stripChars,@@replChar)
|
52
|
+
end
|
53
|
+
|
54
|
+
def enquote str
|
55
|
+
str.gsub("'","\\\\'")
|
56
|
+
end
|
57
|
+
|
58
|
+
|
59
|
+
def dotLabel
|
60
|
+
# "JIRA 1::JIRA 1.csv" [label="JIRA 1.csv"]
|
61
|
+
filled = @type =~ /Data Source|DB Table|Database Field/ ? "style=filled" : ''
|
62
|
+
"\"%s\" [label=\"%s\" %s]" % [id, name, filled]
|
63
|
+
end
|
64
|
+
|
65
|
+
|
66
|
+
def eql? other
|
67
|
+
@name == other.name && @id == other.id && @type == other.type && @properties == other.properties
|
68
|
+
end
|
69
|
+
|
70
|
+
|
71
|
+
def hash
|
72
|
+
[@name, @id, @type, @properties].hash
|
73
|
+
end
|
74
|
+
|
75
|
+
|
76
|
+
def to_s
|
77
|
+
"name:'%s' id:'%s' t:'%s' p:%s" % [@name, @id, @type, @properties.to_s]
|
78
|
+
end
|
79
|
+
|
80
|
+
|
81
|
+
def cypher_s
|
82
|
+
"name:%s id:%s t:%s p:{%s}" % [name, @cypherID, @type, props_s]
|
83
|
+
end
|
84
|
+
|
85
|
+
|
86
|
+
def props_s
|
87
|
+
@properties.map{|k,v| "#{k}: #{v.inspect}"}.join(', ')
|
88
|
+
end
|
89
|
+
|
90
|
+
|
91
|
+
end
|
92
|
+
|
93
|
+
end # module Util
|
94
|
+
end # module Twb
|