twb 0.5.3 → 1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|