ruby-graphviz_c 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (184) hide show
  1. checksums.yaml +7 -0
  2. data/.gemrc +0 -0
  3. data/.gitignore +9 -0
  4. data/.travis.yml +7 -0
  5. data/AUTHORS.rdoc +33 -0
  6. data/CHANGELOG.rdoc +287 -0
  7. data/COPYING.rdoc +133 -0
  8. data/Gemfile +4 -0
  9. data/README.rdoc +206 -0
  10. data/Rakefile +71 -0
  11. data/bin/dot2ruby +91 -0
  12. data/bin/gem2gv +165 -0
  13. data/bin/git2gv +167 -0
  14. data/bin/ruby2gv +234 -0
  15. data/bin/xml2gv +96 -0
  16. data/examples/dot/JSP.dot +52 -0
  17. data/examples/dot/balanced.dot +36 -0
  18. data/examples/dot/cluster.dot +30 -0
  19. data/examples/dot/dotgraph.dot +28 -0
  20. data/examples/dot/fsm.dot +20 -0
  21. data/examples/dot/genetic.dot +118 -0
  22. data/examples/dot/hello.dot +1 -0
  23. data/examples/dot/hello_test.rb +33 -0
  24. data/examples/dot/lion_share.dot +103 -0
  25. data/examples/dot/prof.dot +150 -0
  26. data/examples/dot/psg.dot +28 -0
  27. data/examples/dot/rank.dot +6 -0
  28. data/examples/dot/sdh.dot +284 -0
  29. data/examples/dot/siblings.dot +492 -0
  30. data/examples/dot/so-sample001.gv +30 -0
  31. data/examples/dot/so-sample002.gv +33 -0
  32. data/examples/dot/so-sample003.gv +45 -0
  33. data/examples/dot/test.dot +17 -0
  34. data/examples/dot/test_parse.rb +13 -0
  35. data/examples/dot/this_crach_with_dot_2.20.dot +24 -0
  36. data/examples/dot/unix.dot +104 -0
  37. data/examples/graphml/attributes.ext.graphml +12 -0
  38. data/examples/graphml/attributes.graphml +40 -0
  39. data/examples/graphml/cluster.graphml +75 -0
  40. data/examples/graphml/failed_graph.graphml +461 -0
  41. data/examples/graphml/hyper.graphml +29 -0
  42. data/examples/graphml/nested.graphml +54 -0
  43. data/examples/graphml/port.graphml +32 -0
  44. data/examples/graphml/simple.graphml +30 -0
  45. data/examples/hello.png +0 -0
  46. data/examples/rgv/rgv.ps +125 -0
  47. data/examples/rgv/test_rgv.rb +12 -0
  48. data/examples/sample01.rb +32 -0
  49. data/examples/sample02.rb +42 -0
  50. data/examples/sample03.rb +31 -0
  51. data/examples/sample04.rb +22 -0
  52. data/examples/sample05.rb +32 -0
  53. data/examples/sample06.rb +46 -0
  54. data/examples/sample07.rb +23 -0
  55. data/examples/sample08.rb +34 -0
  56. data/examples/sample09.rb +50 -0
  57. data/examples/sample10.rb +50 -0
  58. data/examples/sample11.rb +42 -0
  59. data/examples/sample12.rb +55 -0
  60. data/examples/sample13.rb +48 -0
  61. data/examples/sample14.rb +44 -0
  62. data/examples/sample15.rb +25 -0
  63. data/examples/sample16.rb +8 -0
  64. data/examples/sample17.rb +92 -0
  65. data/examples/sample18.rb +24 -0
  66. data/examples/sample19.rb +59 -0
  67. data/examples/sample20.rb +47 -0
  68. data/examples/sample21.rb +12 -0
  69. data/examples/sample22.rb +10 -0
  70. data/examples/sample23.rb +11 -0
  71. data/examples/sample24.rb +11 -0
  72. data/examples/sample25.rb +11 -0
  73. data/examples/sample26.rb +8 -0
  74. data/examples/sample27.rb +8 -0
  75. data/examples/sample28.rb +12 -0
  76. data/examples/sample29.rb +8 -0
  77. data/examples/sample30.rb +12 -0
  78. data/examples/sample31.rb +10 -0
  79. data/examples/sample32.rb +14 -0
  80. data/examples/sample33.rb +43 -0
  81. data/examples/sample34.rb +29 -0
  82. data/examples/sample35.rb +43 -0
  83. data/examples/sample36.rb +35 -0
  84. data/examples/sample37.rb +87 -0
  85. data/examples/sample38.rb +12 -0
  86. data/examples/sample39.rb +11 -0
  87. data/examples/sample40.rb +17 -0
  88. data/examples/sample41.rb +8 -0
  89. data/examples/sample42.rb +35 -0
  90. data/examples/sample43.rb +26 -0
  91. data/examples/sample44.rb +97 -0
  92. data/examples/sample45.rb +24 -0
  93. data/examples/sample46.rb +43 -0
  94. data/examples/sample47.rb +7 -0
  95. data/examples/sample48.rb +62 -0
  96. data/examples/sample49.rb +10 -0
  97. data/examples/sample50.rb +215 -0
  98. data/examples/sample51.rb +37 -0
  99. data/examples/sample52.rb +62 -0
  100. data/examples/sample53.rb +26 -0
  101. data/examples/sample54.rb +26 -0
  102. data/examples/sample55.rb +9 -0
  103. data/examples/sample56.rb +10 -0
  104. data/examples/sample57.rb +8 -0
  105. data/examples/sample58.rb +33 -0
  106. data/examples/sample59.rb +14 -0
  107. data/examples/sample60.rb +12 -0
  108. data/examples/sample61.rb +12 -0
  109. data/examples/sample62.rb +24 -0
  110. data/examples/sample63.rb +32 -0
  111. data/examples/sample64.rb +31 -0
  112. data/examples/sample65.rb +9 -0
  113. data/examples/sample66.rb +4 -0
  114. data/examples/sample67.rb +10 -0
  115. data/examples/sample68.rb +27 -0
  116. data/examples/sample69.rb +23 -0
  117. data/examples/sample70.rb +9 -0
  118. data/examples/sample99.rb +70 -0
  119. data/examples/sdlshapes/README +2 -0
  120. data/examples/sdlshapes/sdl.ps +655 -0
  121. data/examples/sdlshapes/sdlshapes.dot +78 -0
  122. data/examples/test.xml +26 -0
  123. data/examples/theory/pert.rb +47 -0
  124. data/examples/theory/tests.rb +87 -0
  125. data/lib/ext/gvpr/dot2ruby.g +185 -0
  126. data/lib/graphviz/attrs.rb +73 -0
  127. data/lib/graphviz/constants.rb +294 -0
  128. data/lib/graphviz/core_ext.rb +64 -0
  129. data/lib/graphviz/dot2ruby.rb +59 -0
  130. data/lib/graphviz/dot_script.rb +109 -0
  131. data/lib/graphviz/dsl.rb +67 -0
  132. data/lib/graphviz/edge.rb +197 -0
  133. data/lib/graphviz/elements.rb +39 -0
  134. data/lib/graphviz/ext.rb +17 -0
  135. data/lib/graphviz/family_tree/couple.rb +63 -0
  136. data/lib/graphviz/family_tree/generation.rb +39 -0
  137. data/lib/graphviz/family_tree/person.rb +120 -0
  138. data/lib/graphviz/family_tree/sibling.rb +13 -0
  139. data/lib/graphviz/family_tree.rb +118 -0
  140. data/lib/graphviz/graphml.rb +268 -0
  141. data/lib/graphviz/math/matrix.rb +221 -0
  142. data/lib/graphviz/node.rb +160 -0
  143. data/lib/graphviz/nothugly/nothugly.xsl +321 -0
  144. data/lib/graphviz/nothugly.rb +63 -0
  145. data/lib/graphviz/theory.rb +321 -0
  146. data/lib/graphviz/types/arrow_type.rb +32 -0
  147. data/lib/graphviz/types/color.rb +58 -0
  148. data/lib/graphviz/types/color_list.rb +24 -0
  149. data/lib/graphviz/types/esc_string.rb +20 -0
  150. data/lib/graphviz/types/gv_bool.rb +49 -0
  151. data/lib/graphviz/types/gv_double.rb +32 -0
  152. data/lib/graphviz/types/html_string.rb +18 -0
  153. data/lib/graphviz/types/lbl_string.rb +22 -0
  154. data/lib/graphviz/types/rect.rb +35 -0
  155. data/lib/graphviz/types/spline_type.rb +77 -0
  156. data/lib/graphviz/types.rb +22 -0
  157. data/lib/graphviz/utils/colors.rb +1018 -0
  158. data/lib/graphviz/utils.rb +70 -0
  159. data/lib/graphviz/xml.rb +119 -0
  160. data/lib/graphviz.rb +967 -0
  161. data/lib/ruby-graphviz.rb +1 -0
  162. data/man/dot2ruby.1 +66 -0
  163. data/man/dot2ruby.1.ronn +55 -0
  164. data/man/gem2gv.1 +60 -0
  165. data/man/gem2gv.1.ronn +47 -0
  166. data/man/git2gv.1 +48 -0
  167. data/man/git2gv.1.ronn +40 -0
  168. data/man/ruby2gv.1 +60 -0
  169. data/man/ruby2gv.1.ronn +47 -0
  170. data/man/xml2gv.1 +48 -0
  171. data/man/xml2gv.1.ronn +39 -0
  172. data/ruby-graphviz.gemspec +47 -0
  173. data/setup.rb +1585 -0
  174. data/test/helper.rb +13 -0
  175. data/test/support.rb +95 -0
  176. data/test/test_dot_script.rb +47 -0
  177. data/test/test_examples.rb +151 -0
  178. data/test/test_graph.rb +115 -0
  179. data/test/test_search.rb +29 -0
  180. data/test/test_subgraph.rb +27 -0
  181. data/test/test_theory.rb +98 -0
  182. data/test/test_types.rb +65 -0
  183. data/test/test_utils_colors.rb +52 -0
  184. metadata +301 -0
@@ -0,0 +1,70 @@
1
+ require 'rbconfig'
2
+
3
+ class GraphViz
4
+ module Utils
5
+ # Since this code is an adaptation of Launchy::Application#find_executable
6
+ # (http://copiousfreetime.rubyforge.org/launchy/Launchy/Application.html)
7
+ # it follow is licence :
8
+ #
9
+ # Permission to use, copy, modify, and/or distribute this software for any
10
+ # purpose with or without fee is hereby granted, provided that the above
11
+ # copyright notice and this permission notice appear in all copies.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED AS IS AND THE AUTHOR DISCLAIMS ALL WARRANTIES
14
+ # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
15
+ # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
16
+ # SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
17
+ # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
18
+ # OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
19
+ # CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
20
+ def find_executable(bin, custom_paths) #:nodoc:
21
+ system_path = ENV['PATH']
22
+ user_given_path = Array(custom_paths).join(File::PATH_SEPARATOR)
23
+ search_path = system_path + File::PATH_SEPARATOR + user_given_path
24
+
25
+ search_path.split(File::PATH_SEPARATOR).each do |path|
26
+ file_path = File.join(path,bin)
27
+ return file_path if File.executable?(file_path) and File.file?(file_path)
28
+
29
+ if RbConfig::CONFIG['host_os'] =~ /mswin|mingw/ # WAS: elsif RUBY_PLATFORM =~ /mswin|mingw/
30
+ found_ext = (ENV['PATHEXT'] || '.exe;.bat;.com').split(";").find {|ext| File.executable?(file_path + ext) }
31
+ return file_path + found_ext if found_ext
32
+ end
33
+ end
34
+ return nil
35
+ end
36
+
37
+ def output_and_errors_from_command(cmd) #:nodoc:
38
+ unless defined? Open3
39
+ begin
40
+ require 'open3'
41
+ require 'win32/open3'
42
+ rescue LoadError
43
+ end
44
+ end
45
+ begin
46
+ Open3.popen3( *cmd ) do |stdin, stdout, stderr|
47
+ stdin.close
48
+ stdout.binmode
49
+ [stdout.read, stderr.read]
50
+ end
51
+ rescue NotImplementedError, NoMethodError
52
+ IO.popen( *cmd ) do |stdout|
53
+ stdout.binmode
54
+ [stdout.read, nil]
55
+ end
56
+ end
57
+ end
58
+
59
+ def output_from_command(cmd) #:nodoc:
60
+ output, errors = output_and_errors_from_command(cmd)
61
+ if errors.nil? || errors.strip.empty?
62
+ output
63
+ else
64
+ raise "Error from #{cmd}:\n#{errors}"
65
+ end
66
+ end
67
+
68
+ end
69
+ end
70
+
@@ -0,0 +1,119 @@
1
+ # Copyright (C) 2004 - 2012 Gregoire Lejeune <gregoire.lejeune@free.fr>
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 2 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, write to the Free Software
15
+ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
16
+
17
+ require 'graphviz'
18
+ require 'rexml/document'
19
+
20
+ class GraphViz
21
+ class XML
22
+
23
+ # The GraphViz object
24
+ attr_accessor :graph
25
+
26
+ #
27
+ # Generate the graph
28
+ #
29
+ # THIS METHOD IS DEPRECATED, PLEASE USE GraphViz::XML.graph.output
30
+ #
31
+ def output( *options )
32
+ warn "GraphViz::XML.output is deprecated, use GraphViz::XML.graph.output"
33
+ @graph.output( *options )
34
+ end
35
+
36
+ private
37
+
38
+ #
39
+ # Create a graph from a XML file
40
+ #
41
+ # In:
42
+ # * xml_file : XML File
43
+ # * *options : Graph options:
44
+ # * :text : show text nodes (default true)
45
+ # * :attrs : show XML attributes (default true)
46
+ #
47
+ def initialize( xml_file, *options )
48
+ @node_name = "00000"
49
+ @show_text = true
50
+ @show_attributes = true
51
+
52
+ if options and options[0]
53
+ options[0].each do |xKey, xValue|
54
+ case xKey.to_s
55
+ when "text"
56
+ @show_text = xValue
57
+ options[0].delete( xKey )
58
+ when "attrs"
59
+ @show_attributes = xValue
60
+ options[0].delete( xKey )
61
+ end
62
+ end
63
+ end
64
+
65
+ @rexml_document = REXML::Document::new( File::new( xml_file ) )
66
+ @graph = GraphViz::new( "XML", *options )
67
+ parse_xml_node( @rexml_document.root() )
68
+ end
69
+
70
+ def parse_xml_node( xml_node ) #:nodoc:
71
+ local_node_name = @node_name.clone
72
+ @node_name.succ!
73
+
74
+ label = xml_node.name
75
+ if xml_node.has_attributes? and @show_attributes
76
+ label = "{ " + xml_node.name
77
+
78
+ xml_node.attributes.each do |xName, xValue|
79
+ label << "| { #{xName} | #{xValue} } "
80
+ end
81
+
82
+ label << "}"
83
+ end
84
+ @graph.add_nodes( local_node_name, "label" => label, "color" => "blue", "shape" => "record" )
85
+
86
+ ## Act: Search and add Text nodes
87
+ if xml_node.has_text? and @show_text
88
+ text_node_name = local_node_name.clone
89
+ text_node_name << "111"
90
+
91
+ xText = ""
92
+ xSep = ""
93
+ xml_node.texts().each do |l|
94
+ x = l.value.chomp.strip
95
+ if x.length > 0
96
+ xText << xSep << x
97
+ xSep = "\n"
98
+ end
99
+ end
100
+
101
+ if xText.length > 0
102
+ @graph.add_nodes( text_node_name, "label" => xText, "color" => "black", "shape" => "ellipse" )
103
+ @graph.add_edges( local_node_name, text_node_name )
104
+ end
105
+ end
106
+
107
+ ## Act: Search and add attributes
108
+ ## TODO
109
+
110
+ xml_node.each_element( ) do |xml_child_node|
111
+ child_node_name = parse_xml_node( xml_child_node )
112
+ @graph.add_edges( local_node_name, child_node_name )
113
+ end
114
+
115
+ return( local_node_name )
116
+ end
117
+
118
+ end
119
+ end
data/lib/graphviz.rb ADDED
@@ -0,0 +1,967 @@
1
+ # Copyright (C) 2003 - 2012 Gregoire Lejeune <gregoire.lejeune@free.fr>
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 2 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, write to the Free Software
15
+ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
16
+
17
+
18
+ IS_JRUBY = (defined?( JRUBY_VERSION ) != nil)
19
+ IS_CYGWIN = ((RUBY_PLATFORM =~ /cygwin/) != nil)
20
+
21
+ require 'tempfile'
22
+
23
+ require 'graphviz/utils'
24
+ require 'graphviz/node'
25
+ require 'graphviz/edge'
26
+ require 'graphviz/attrs'
27
+ require 'graphviz/constants'
28
+ require 'graphviz/elements'
29
+ require 'graphviz/dot_script'
30
+
31
+ require 'graphviz/dot2ruby'
32
+ require 'graphviz/types'
33
+ require 'graphviz/core_ext'
34
+
35
+ if /^1.8/.match RUBY_VERSION
36
+ $KCODE = "UTF8"
37
+ end
38
+
39
+ class GraphViz
40
+ include GraphViz::Constants
41
+ include GraphViz::Utils
42
+
43
+ public
44
+
45
+ ## Var: Output format (dot, png, jpeg, ...)
46
+ @@format = nil #"canon"
47
+ @format = nil
48
+ ## Var: Output file name
49
+ @filename = nil
50
+ ## Var: Output format and file
51
+ @output = nil
52
+ ## Var: program to use (dot|twopi)
53
+ @@prog = "dot"
54
+ @prog = nil
55
+ ## Var: program path
56
+ @@path = []
57
+ @path = nil
58
+ ## Var: Error level
59
+ @@errors = 1
60
+ @errors = nil
61
+ ## Var: External libraries
62
+ @@extlibs = []
63
+ @extlibs = nil
64
+
65
+ ## Var: Graph name
66
+ @name = nil
67
+
68
+ ## Var: defined attributes
69
+ @graph = nil
70
+ @node = nil
71
+ @edge = nil
72
+
73
+ # This accessor allow you to set global graph attributes
74
+ attr_accessor :graph
75
+ alias_method :graph_attrs, :graph
76
+
77
+ # This accessor allow you to set global nodes attributes
78
+ attr_accessor :node
79
+ alias_method :node_attrs, :node
80
+
81
+ # This accessor allow you to set global edges attributes
82
+ attr_accessor :edge
83
+ alias_method :edge_attrs, :edge
84
+
85
+ @elements_order = nil
86
+
87
+ def add_node( xNodeName, hOpts = {} )
88
+ if xNodeName.kind_of? Array
89
+ raise ArgumentError, "use `add_nodes' to add several nodes at the same time"
90
+ end
91
+ return add_nodes(xNodeName, hOpts)
92
+ end
93
+
94
+ # Create a new node
95
+ #
96
+ # In:
97
+ # * xNodeName : Name of the new node
98
+ # * hOpts : Node attributes
99
+ #
100
+ # Return the GraphViz::Node object created
101
+ def add_nodes(node_name, options = {})
102
+ if node_name.kind_of? Array
103
+ node_name.each { |n| add_nodes(n, options.clone) }
104
+ else
105
+ node = @hoNodes[node_name]
106
+
107
+ if node.nil?
108
+ @hoNodes[node_name] = GraphViz::Node::new( node_name, self )
109
+ @hoNodes[node_name].index = @elements_order.size_of( "node" )
110
+
111
+ unless options.keys.include?(:label) or options.keys.include?("label")
112
+ options[:label] = node_name
113
+ end
114
+
115
+ @elements_order.push( {
116
+ "type" => "node",
117
+ "name" => node_name,
118
+ "value" => @hoNodes[node_name]
119
+ } )
120
+
121
+ node = @hoNodes[node_name]
122
+ end
123
+
124
+ options.each do |xKey, xValue|
125
+ @hoNodes[node_name][xKey.to_s] = xValue
126
+ end
127
+
128
+ return node
129
+ end
130
+ end
131
+
132
+ # Return the node object for the given name (or nil) in the current graph
133
+ def get_node( xNodeName, &block )
134
+ node = @hoNodes[xNodeName] || nil
135
+
136
+ yield( node ) if( block and node )
137
+
138
+ return node
139
+ end
140
+
141
+ # Returns the first node found in the entire graph, starting from the root graph
142
+ def find_node(name)
143
+ root = root_graph
144
+ return root.search_node(name)
145
+ end
146
+
147
+ # Return the first node found in the current graph, and it subgraphs
148
+ def search_node(name)
149
+ n = get_node(name)
150
+ return n unless n.nil?
151
+ each_graph { |_, g|
152
+ n = g.search_node(name)
153
+ return n unless n.nil?
154
+ }
155
+ return nil
156
+ end
157
+
158
+ #
159
+ # Return the node object for the given index
160
+ #
161
+ def get_node_at_index( index )
162
+ element = @elements_order[index, "node"]
163
+ (element.nil?) ? nil : element["value"]
164
+ end
165
+
166
+ #
167
+ # Allow you to traverse nodes
168
+ #
169
+ def each_node( &block )
170
+ if block_given?
171
+ @hoNodes.each do |name, node|
172
+ yield( name, node )
173
+ end
174
+ else
175
+ return( @hoNodes )
176
+ end
177
+ end
178
+
179
+ # Get the number of nodes
180
+ def node_count
181
+ @hoNodes.size
182
+ end
183
+
184
+ def add_edge( oNodeOne, oNodeTwo, hOpts = {} )
185
+ if oNodeTwo.kind_of? Array or oNodeOne.kind_of? Array
186
+ raise ArgumentError, "use `add_edges' to add several edges at the same time"
187
+ end
188
+ add_edges(oNodeOne, oNodeTwo, hOpts)
189
+ end
190
+
191
+ # Create a new edge
192
+ #
193
+ # In:
194
+ # * node_one : First node (or node list)
195
+ # * node_two : Second Node (or node list)
196
+ # * options : Edge attributes
197
+ def add_edges( node_one, node_two, options = {} )
198
+
199
+ if( node_one.class == Array )
200
+ node_one.each do |no|
201
+ add_edges( no, node_two, options )
202
+ end
203
+ else
204
+ if( node_two.class == Array )
205
+ node_two.each do |nt|
206
+ add_edges( node_one, nt, options )
207
+ end
208
+ else
209
+ edge = GraphViz::Edge::new( node_one, node_two, self )
210
+ edge.index = @elements_order.size_of( "edge" )
211
+
212
+ options.each do |xKey, xValue|
213
+ edge[xKey.to_s] = xValue
214
+ end
215
+
216
+ @elements_order.push( {
217
+ "type" => "edge",
218
+ "value" => edge
219
+ } )
220
+ @loEdges.push( edge )
221
+
222
+ return( edge )
223
+ end
224
+ end
225
+ end
226
+
227
+ #
228
+ # Allow you to traverse edges
229
+ #
230
+ def each_edge( &block )
231
+ if block_given?
232
+ @loEdges.each do |edge|
233
+ yield(edge)
234
+ end
235
+ else
236
+ return @loEdges
237
+ end
238
+ end
239
+
240
+ #
241
+ # Get the number of edges
242
+ #
243
+ def edge_count
244
+ @loEdges.size
245
+ end
246
+
247
+ #
248
+ # Return the edge object for the given index
249
+ #
250
+ def get_edge_at_index( index )
251
+ element = @elements_order[index, "edge"]
252
+ (element.nil?) ? nil : element["value"]
253
+ end
254
+
255
+ #
256
+ # Create a new graph
257
+ #
258
+ # In:
259
+ # * xGraphName : Graph name
260
+ # * hOpts : Graph attributes
261
+ #
262
+ def add_graph( xGraphName = nil, hOpts = {}, &block )
263
+ if xGraphName.kind_of?(GraphViz)
264
+ xGraphID = xGraphName.id
265
+ @hoGraphs[xGraphID] = xGraphName.clone
266
+ @hoGraphs[xGraphID].type = @oGraphType
267
+ @hoGraphs[xGraphID].pg = self
268
+ xGraphName = xGraphID
269
+ else
270
+ if xGraphName.kind_of?(Hash)
271
+ hOpts = xGraphName
272
+ xGraphName = nil
273
+ end
274
+
275
+ if xGraphName.nil?
276
+ xGraphID = String.random(11)
277
+ xGraphName = ""
278
+ else
279
+ xGraphID = xGraphName
280
+ end
281
+
282
+ @hoGraphs[xGraphID] = GraphViz::new( xGraphName, {:parent => self, :type => @oGraphType}, &block )
283
+
284
+ hOpts.each do |xKey, xValue|
285
+ @hoGraphs[xGraphID][xKey.to_s] = xValue
286
+ end
287
+ end
288
+
289
+ @elements_order.push( {
290
+ "type" => "graph",
291
+ "name" => xGraphName,
292
+ "value" => @hoGraphs[xGraphID]
293
+ } )
294
+
295
+ return( @hoGraphs[xGraphID] )
296
+ end
297
+ alias :subgraph :add_graph
298
+ #
299
+ # Return the graph object for the given name (or nil)
300
+ #
301
+ def get_graph( xGraphName, &block )
302
+ graph = @hoGraphs[xGraphName] || nil
303
+
304
+ yield( graph ) if( block and graph )
305
+
306
+ return graph
307
+ end
308
+
309
+ #
310
+ # Allow you to traverse graphs
311
+ #
312
+ def each_graph( &block )
313
+ if block_given?
314
+ @hoGraphs.each do |name, graph|
315
+ yield( name, graph )
316
+ end
317
+ else
318
+ return @hoGraphs
319
+ end
320
+ end
321
+
322
+ #
323
+ # Add nodes and edges defined by a Hash
324
+ #
325
+ def add(h)
326
+ if h.kind_of? Hash
327
+ h.each do |node, data|
328
+ add_hash_edge(node, data)
329
+ end
330
+ end
331
+ end
332
+
333
+ #
334
+ # Return the graph type (graph digraph)
335
+ #
336
+ def type
337
+ @oGraphType
338
+ end
339
+ def type=(x) #:nodoc:
340
+ @oGraphType = x
341
+ end
342
+
343
+ #
344
+ # Get the number of graphs
345
+ #
346
+ def graph_count
347
+ @hoGraphs.size
348
+ end
349
+
350
+ def method_missing( idName, *args, &block ) #:nodoc:
351
+ xName = idName.id2name
352
+
353
+ rCod = nil
354
+
355
+ if block
356
+ # Creating a cluster named '#{xName}'
357
+ rCod = add_graph( xName, args[0]||{} )
358
+ yield( rCod )
359
+ else
360
+ # Create a node named '#{xName}' or search for a node, edge or cluster
361
+ if @hoNodes.keys.include?( xName )
362
+ if( args[0] )
363
+ return { xName => args[0] }
364
+ else
365
+ return( @hoNodes[xName] )
366
+ end
367
+ end
368
+ return( @hoGraphs[xName] ) if @hoGraphs.keys.include?( xName )
369
+
370
+ rCod = add_nodes( xName, args[0]||{} )
371
+ end
372
+
373
+ return rCod
374
+ end
375
+
376
+ #
377
+ # Set value +xValue+ to the graph attribute +xAttrName+
378
+ #
379
+ def []=( xAttrName, xValue )
380
+ xValue = xValue.to_s if xValue.class == Symbol
381
+ @graph[xAttrName] = xValue
382
+ end
383
+
384
+ #
385
+ # Get the value of the graph attribute +xAttrName+
386
+ #
387
+ def []( xAttrName )
388
+ if Hash === xAttrName
389
+ xAttrName.each do |key, value|
390
+ self[key] = value
391
+ end
392
+ else
393
+ attr = @graph[xAttrName]
394
+ if attr.nil?
395
+ return nil
396
+ else
397
+ return( @graph[xAttrName].clone )
398
+ end
399
+ end
400
+ end
401
+
402
+ #
403
+ # Calls block once for each attribute of the graph, passing the name and value to the
404
+ # block as a two-element array.
405
+ #
406
+ def each_attribute(&b)
407
+ @graph.each do |k,v|
408
+ yield(k,v)
409
+ end
410
+ end
411
+ def each_attribut(&b)
412
+ warn "`GraphViz#each_attribut` is deprecated, please use `GraphViz#each_attribute`"
413
+ each_attribute(&b)
414
+ end
415
+
416
+ # Create a new graph from the current subgraph
417
+ def to_graph
418
+ graph = self.clone
419
+ graph.pg = nil
420
+ return graph
421
+ end
422
+
423
+ #
424
+ # Generate the graph
425
+ #
426
+ # Options :
427
+ # * :output : Output format (GraphViz::Constants::FORMATS)
428
+ # * :file : Output file name
429
+ # * :use : Program to use (GraphViz::Constants::PROGRAMS)
430
+ # * :path : Program PATH
431
+ # * :<format> => <file> : <file> can be
432
+ # * a file name
433
+ # * nil, then the output will be printed to STDOUT
434
+ # * String, then the output will be returned as a String
435
+ # * :errors : DOT error level (default 1)
436
+ # * 0 = Error + Warning
437
+ # * 1 = Error
438
+ # * 2 = none
439
+ #
440
+ def output( hOpts = {} )
441
+ xDOTScript = DOTScript.new
442
+ lNotHugly = []
443
+
444
+ append_attributes_and_types(xDOTScript)
445
+
446
+ xDOTScript << "}"
447
+
448
+ if has_parent_graph?
449
+ xDOTScript.make_subgraph("#{GraphViz.escape(@name, :unquote_empty => true)}")
450
+ else
451
+ hOutput = {}
452
+
453
+ hOpts.each do |xKey, xValue|
454
+ xValue = xValue.to_s unless xValue.nil? or [Class, TrueClass, FalseClass].include?(xValue.class)
455
+ case xKey.to_s
456
+ when "use"
457
+ if PROGRAMS.index( xValue ).nil?
458
+ raise ArgumentError, "can't use '#{xValue}'"
459
+ end
460
+ @prog = xValue
461
+ when "path"
462
+ @path = xValue && xValue.split( "," ).map{ |x| x.strip }
463
+ when "errors"
464
+ @errors = xValue
465
+ when "extlib"
466
+ @extlibs = xValue.split( "," ).map{ |x| x.strip }
467
+ when "scale"
468
+ # Scale input by 'v' (=72)
469
+ @scale = xValue
470
+ when "inverty"
471
+ # Invert y coordinate in output
472
+ @inverty = xValue
473
+ when "no_layout"
474
+ # No layout mode 'v' (=1)
475
+ @no_layout = xValue
476
+ when "reduce"
477
+ # Reduce graph
478
+ @reduce_graph = xValue
479
+ when "Lg"
480
+ # Don't use grid
481
+ @Lg = xValue
482
+ when "LO"
483
+ # Use old attractive force
484
+ @LO = xValue
485
+ when "Ln"
486
+ # Set number of iterations to i
487
+ @Ln = xValue
488
+ when "LU"
489
+ # Set unscaled factor to i
490
+ @LU = xValue
491
+ when "LC"
492
+ # Set overlap expansion factor to v
493
+ @LC = xValue
494
+ when "LT"
495
+ # Set temperature (temperature factor) to v
496
+ @LT = xValue
497
+ when "nothugly"
498
+ begin
499
+ require 'graphviz/nothugly'
500
+ @nothugly = true
501
+ rescue LoadError
502
+ warn "You must install ruby-xslt or libxslt-ruby to use nothugly option!"
503
+ @nothugly = false
504
+ end
505
+ else
506
+ if FORMATS.index( xKey.to_s ).nil?
507
+ raise ArgumentError, "output format '#{xValue}' invalid"
508
+ end
509
+ hOutput[xKey.to_s] = xValue
510
+ end
511
+ end
512
+
513
+ @output = hOutput if hOutput.size > 0
514
+
515
+ xStict = ((@strict && @oGraphType == "digraph") ? "strict " : "")
516
+ xDOTScript.prepend(
517
+ "#{xStict}#{@oGraphType} #{GraphViz.escape(@name, :unquote_empty => true)} {"
518
+ )
519
+
520
+ xOutputString = (@filename == String ||
521
+ @output.any? {|format, file| file == String })
522
+
523
+ xOutput = ""
524
+ if @format.to_s == "none" or @output.any? {|fmt, fn| fmt.to_s == "none"}
525
+ if xOutputString
526
+ xOutput << xDOTScript
527
+ else
528
+ xFileName = @output["none"] || @filename
529
+ open( xFileName, "w" ) do |h|
530
+ h.puts xDOTScript
531
+ end
532
+ end
533
+ end
534
+
535
+ if (@format.to_s != "none" and not @format.nil?) or (@output.any? {|format, file| format != "none" } and @output.size > 0)
536
+ ## Act: Save script and send it to dot
537
+ t = Tempfile::open( File.basename(__FILE__) )
538
+ t.print( xDOTScript )
539
+ t.close
540
+
541
+ cmd = find_executable( @prog, @path )
542
+ if cmd == nil
543
+ raise StandardError, "GraphViz not installed or #{@prog} not in PATH. Install GraphViz or use the 'path' option"
544
+ end
545
+
546
+ xOutputWithFile = []
547
+ xOutputWithoutFile = []
548
+ unless @format.nil? or @format == "none"
549
+ lNotHugly << @filename if @format.to_s == "svg" and @nothugly
550
+ if @filename.nil? or @filename == String
551
+ xOutputWithoutFile = ["-T#{@format}"]
552
+ else
553
+ xOutputWithFile = ["-T#{@format}", "-o#{@filename}"]
554
+ end
555
+ end
556
+ @output.each_except( :key => ["none"] ) do |format, file|
557
+ lNotHugly << file if format.to_s == "svg" and @nothugly
558
+ if file.nil? or file == String
559
+ xOutputWithoutFile += ["-T#{format}"]
560
+ else
561
+ xOutputWithFile += ["-T#{format}", "-o#{file}"]
562
+ end
563
+ end
564
+
565
+ xExternalLibraries = @extlibs.map { |lib| "-l#{lib}" }
566
+
567
+ xOtherOptions = []
568
+ xOtherOptions << "-s#{@scale}" if @scale
569
+ xOtherOptions << "-y" if @inverty
570
+ xOtherOptions << "-n#{@no_layout}" if @no_layout
571
+ xOtherOptions << "-x" if @reduce_graph
572
+ xOtherOptions << "-Lg" if @Lg
573
+ xOtherOptions << "-LO" if @LO
574
+ xOtherOptions << "-Ln#{@Ln}" if @Ln
575
+ xOtherOptions << "-LU#{@LU}" if @LU
576
+ xOtherOptions << "-LC#{@LC}" if @LC
577
+ xOtherOptions << "-LT#{@LT}" if @LT
578
+
579
+ tmpPath = if IS_JRUBY
580
+ t.path
581
+ elsif IS_CYGWIN
582
+ begin
583
+ IO.popen("cygpath", "-w", t.path).chomp
584
+ rescue
585
+ warn "cygpath is not installed!"
586
+ t.path
587
+ end
588
+ else
589
+ t.path
590
+ end
591
+
592
+ xCmd = [cmd, "-q#{@errors}"] +
593
+ xExternalLibraries +
594
+ xOtherOptions +
595
+ xOutputWithFile +
596
+ xOutputWithoutFile +
597
+ [tmpPath]
598
+
599
+ xOutput << output_from_command( xCmd )
600
+ end
601
+
602
+ # Not Hugly
603
+ lNotHugly.each do |f|
604
+ if f.nil? or f == String
605
+ xOutput = GraphViz.nothugly( xOutput, false )
606
+ else
607
+ GraphViz.nothugly( f, true )
608
+ end
609
+ end
610
+
611
+ if xOutputString
612
+ xOutput
613
+ else
614
+ print xOutput
615
+ end
616
+ end
617
+ end
618
+ alias :save :output
619
+
620
+ def append_attributes_and_types(script)
621
+ script_data = DOTScriptData.new
622
+
623
+ @elements_order.each { |kElement|
624
+ is_new_type = script_data.type != kElement["type"]
625
+ if is_new_type
626
+ script << script_data unless script_data.type.nil? or script_data.empty?
627
+ script_data = DOTScriptData.new(kElement["type"])
628
+ end
629
+
630
+ # Modified by Brandon Coleman verify value is NOT NULL
631
+ kElement["value"] or raise ArgumentError, "#{kElement["name"]} is nil!"
632
+
633
+ case kElement["type"]
634
+ when "graph_attr", "node_attr", "edge_attr"
635
+ script_data.add_attribute(kElement["name"], kElement["value"].to_gv)
636
+ when "node", "graph"
637
+ script << kElement["value"].output()
638
+ when "edge"
639
+ script << " " + kElement["value"].output( @oGraphType )
640
+ else
641
+ raise ArgumentError,
642
+ "Don't know what to do with element type '#{kElement['type']}'"
643
+ end
644
+ }
645
+ script << script_data unless script_data.type.nil? or script_data.empty?
646
+ end
647
+
648
+ def to_s
649
+ self.output(:none => String)
650
+ end
651
+
652
+ #
653
+ # Get the graph name
654
+ #
655
+ def name
656
+ @name.clone
657
+ end
658
+ alias :id :name
659
+
660
+ #
661
+ # Create an edge between the current cluster and the node or cluster +oNode+
662
+ #
663
+ def <<( oNode )
664
+ raise( ArgumentError, "Edge between root graph and node or cluster not allowed!" ) if self.pg.nil?
665
+
666
+ if( oNode.class == Array )
667
+ oNode.each do |no|
668
+ self << no
669
+ end
670
+ else
671
+ return GraphViz::commonGraph( oNode, self ).add_edges( self, oNode )
672
+ end
673
+ end
674
+ alias :> :<<
675
+ alias :- :<<
676
+ alias :>> :<<
677
+
678
+ def pg #:nodoc:
679
+ @oParentGraph
680
+ end
681
+ def pg=(x) #:nodoc:
682
+ @oParentGraph = x
683
+ end
684
+
685
+ #
686
+ # Return the root graph
687
+ #
688
+ def root_graph
689
+ return( (self.pg.nil?) ? self : self.pg.root_graph )
690
+ end
691
+
692
+ def self.commonGraph( o1, o2 ) #:nodoc:
693
+ g1 = o1.pg
694
+ g2 = o2.pg
695
+
696
+ return o1 if g1.nil?
697
+ return o2 if g2.nil?
698
+
699
+ return g1 if g1.object_id == g2.object_id
700
+
701
+ return GraphViz::commonGraph( g1, g2 )
702
+ end
703
+
704
+ def set_position( xType, xKey, xValue ) #:nodoc:
705
+ @elements_order.push( {
706
+ "type" => "#{xType}_attr",
707
+ "name" => xKey,
708
+ "value" => xValue
709
+ } )
710
+ end
711
+
712
+ ## ----------------------------------------------------------------------------
713
+
714
+ #
715
+ # Change default options (:use, :path, :errors and :output)
716
+ #
717
+ def self.default( hOpts )
718
+ hOpts.each do |k, v|
719
+ case k.to_s
720
+ when "use"
721
+ @@prog = v
722
+ when "path"
723
+ @@path = v.split( "," ).map{ |x| x.strip }
724
+ when "errors"
725
+ @@errors = v
726
+ when "extlibs"
727
+ @@extlibs = v.split( "," ).map{ |x| x.strip }
728
+ else
729
+ warn "Invalid option #{k}!"
730
+ end
731
+ end
732
+ end
733
+
734
+ def self.options( hOpts )
735
+ GraphViz::default( hOpts )
736
+ end
737
+
738
+ ## ----------------------------------------------------------------------------
739
+
740
+ # Create a new graph from a GraphViz File
741
+ #
742
+ # Options :
743
+ # * :output : Output format (GraphViz::Constants::FORMATS) (default : dot)
744
+ # * :file : Output file name (default : none)
745
+ # * :use : Program to use (GraphViz::Constants::PROGRAMS) (default : dot)
746
+ # * :path : Program PATH
747
+ #
748
+ def self.parse( xFile, hOpts = {}, &block )
749
+ graph = Dot2Ruby::new( hOpts[:path], nil, nil ).eval( xFile )
750
+ yield( graph ) if( block and graph )
751
+ return graph
752
+ end
753
+
754
+ # Create a new graph from a GraphViz File
755
+ #
756
+ # Options :
757
+ # * :output : Output format (GraphViz::Constants::FORMATS) (default : dot)
758
+ # * :file : Output file name (default : none)
759
+ # * :use : Program to use (GraphViz::Constants::PROGRAMS) (default : dot)
760
+ # * :path : Program PATH
761
+ #
762
+ def self.parse_string( str, hOpts = {}, &block )
763
+ graph = Dot2Ruby::new( hOpts[:path], nil, nil ).eval_string( str )
764
+ yield( graph ) if( block and graph )
765
+ return graph
766
+ end
767
+
768
+ # Return a new completed graph
769
+ def complete
770
+ GraphViz.parse_string( root_graph.output( :dot => String ) )
771
+ end
772
+
773
+ # Complete the current graph
774
+ def complete!
775
+ # TODO: Keep options
776
+ complete
777
+ end
778
+
779
+ # Return true if the graph is directed.
780
+ def directed?
781
+ not((/digraph/ =~ "bla digraph bla").nil?)
782
+ end
783
+
784
+ def has_parent_graph?
785
+ not @oParentGraph.nil?
786
+ end
787
+ ## ----------------------------------------------------------------------------
788
+
789
+ private
790
+
791
+ ## Var: Nodes, Edges and Graphs tables
792
+ @hoNodes = nil
793
+ @loEdges = nil
794
+ @hoGraphs = nil
795
+
796
+ ## Var: Parent graph
797
+ @oParentGraph = nil
798
+
799
+ ## Var: Type de graphe (orienté ou non)
800
+ @oGraphType = nil
801
+
802
+ #
803
+ # Create a new graph object
804
+ #
805
+ # Options :
806
+ # * :output : Output format (GraphViz::Constants::FORMATS) (default : dot)
807
+ # * :file : Output file name (default : nil)
808
+ # * :use : Program to use (GraphViz::Constants::PROGRAMS) (default : dot)
809
+ # * :path : Program PATH
810
+ # * :parent : Parent graph (default : nil)
811
+ # * :type : Graph type (GraphViz::Constants::GRAPHTYPE) (default : digraph)
812
+ # * :errors : DOT error level (default 1)
813
+ # * 0 = Error + Warning
814
+ # * 1 = Error
815
+ # * 2 = none
816
+ #
817
+ def initialize( xGraphName, hOpts = {}, &block )
818
+ @filename = nil
819
+ @name = xGraphName.to_s
820
+ @format = @@format
821
+ @prog = @@prog
822
+ @path = @@path
823
+ @errors = @@errors
824
+ @extlibs = @@extlibs
825
+ @output = {}
826
+ @nothugly = false
827
+ @strict = false
828
+
829
+ @scale = nil
830
+ @inverty = nil
831
+ @no_layout = nil
832
+ @reduce_graph = nil
833
+ @Lg = nil
834
+ @LO = nil
835
+ @Ln = nil
836
+ @LU = nil
837
+ @LC = nil
838
+ @LT = nil
839
+
840
+ @elements_order = GraphViz::Elements::new()
841
+
842
+ @oParentGraph = nil
843
+ @oGraphType = "digraph"
844
+
845
+ @hoNodes = Hash::new()
846
+ @loEdges = Array::new()
847
+ @hoGraphs = Hash::new()
848
+
849
+ @node = GraphViz::Attrs::new( self, "node", NODESATTRS )
850
+ @edge = GraphViz::Attrs::new( self, "edge", EDGESATTRS )
851
+ @graph = GraphViz::Attrs::new( self, "graph", GRAPHSATTRS )
852
+
853
+ hOpts.each do |xKey, xValue|
854
+ case xKey.to_s
855
+ when "use"
856
+ if PROGRAMS.index( xValue.to_s ).nil?
857
+ raise ArgumentError, "can't use '#{xValue}'"
858
+ end
859
+ @prog = xValue.to_s
860
+ when "parent"
861
+ @oParentGraph = xValue
862
+ when "type"
863
+ if GRAPHTYPE.index( xValue.to_s ).nil?
864
+ raise ArgumentError, "graph type '#{xValue}' unknow"
865
+ end
866
+ @oGraphType = xValue.to_s
867
+ when "path"
868
+ @path = xValue.split( "," ).map{ |x| x.strip }
869
+ when "strict"
870
+ @strict = (xValue ? true : false)
871
+ when "errors"
872
+ @errors = xValue
873
+ when "extlibs"
874
+ @extlibs = xValue.split( "," ).map{ |x| x.strip }
875
+ else
876
+ self[xKey.to_s] = xValue.to_s
877
+ end
878
+ end
879
+
880
+ yield( self ) if( block )
881
+ end
882
+
883
+ # Edge between a node and a Hash
884
+ # Used by GraphViz#add
885
+ def add_hash_edge(node, hash)
886
+ if hash.kind_of? Hash
887
+ hash.each do |nt, data|
888
+ add_edges(node, nt)
889
+ add_hash_edge(nt, data)
890
+ end
891
+ else
892
+ add_edges(node, hash)
893
+ end
894
+ end
895
+
896
+ #
897
+ # Create a new undirected graph
898
+ #
899
+ # See also GraphViz::new
900
+ #
901
+ def self.graph( xGraphName, hOpts = {}, &block )
902
+ new( xGraphName, hOpts.symbolize_keys.merge( {:type => "graph"} ), &block )
903
+ end
904
+
905
+ #
906
+ # Create a new directed graph
907
+ #
908
+ # See also GraphViz::new
909
+ #
910
+ def self.digraph( xGraphName, hOpts = {}, &block )
911
+ new( xGraphName, hOpts.symbolize_keys.merge( {:type => "digraph"} ), &block )
912
+ end
913
+
914
+ # Create a new strict directed graph
915
+ #
916
+ # See also GraphViz::new
917
+ def self.strict_digraph( xGraphName, hOpts = {}, &block )
918
+ new( xGraphName, hOpts.symbolize_keys.merge( {:type => "digraph", :strict => true} ), &block )
919
+ end
920
+
921
+ # Create a random graph.
922
+ def self.generate(num_nodes, num_edges, directed = false, weight_range = (1..1))
923
+ g = nil
924
+ if directed
925
+ g = GraphViz.digraph(:G)
926
+ else
927
+ g = GraphViz.graph(:G)
928
+ end
929
+
930
+ nodes = (1..num_nodes).map{ |e| e.to_s }
931
+ g.add_nodes(nodes)
932
+
933
+ edges = []
934
+ nodes.each do |head|
935
+ nodes.each do |tail|
936
+ if (directed and head != tail) or (head < tail)
937
+ edges << {:head => head, :tail => tail, :weight => weight_range.to_a.shuffle[0]}
938
+ end
939
+ end
940
+ end
941
+ edges.shuffle!
942
+
943
+ (num_edges - 1).times do |i|
944
+ g.add_edges(edges[i][:head], edges[i][:tail], :label => edges[i][:weight].to_s, :weight => edges[i][:weight])
945
+ end
946
+
947
+ return g
948
+ end
949
+
950
+ #
951
+ # Escape a string to be acceptable as a node name in a graphviz input file
952
+ #
953
+ def self.escape(str, opts = {} ) #:nodoc:
954
+ options = {
955
+ :force => false,
956
+ :unquote_empty => false,
957
+ }.merge(opts)
958
+
959
+ if (options[:force] or str.match( /\A[a-zA-Z_]+[a-zA-Z0-9_]*\Z/ ).nil?)
960
+ unless options[:unquote_empty] and str.size == 0
961
+ '"' + str.gsub('"', '\\"').gsub("\n", '\\\\n') + '"'
962
+ end
963
+ else
964
+ str
965
+ end
966
+ end
967
+ end