yard-chef 1.0.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +7 -0
  2. data/lib/yard-chef.rb +12 -11
  3. data/lib/yard-chef/code_objects/attribute_object.rb +43 -0
  4. data/lib/yard-chef/code_objects/chef_object.rb +3 -3
  5. data/lib/yard-chef/code_objects/cookbook_object.rb +9 -1
  6. data/lib/yard-chef/code_objects/dependency_object.rb +52 -0
  7. data/lib/yard-chef/code_objects/provider_object.rb +7 -7
  8. data/lib/yard-chef/code_objects/recipe_object.rb +2 -1
  9. data/lib/yard-chef/code_objects/resource_object.rb +1 -1
  10. data/lib/yard-chef/handlers/actions.rb +2 -2
  11. data/lib/yard-chef/handlers/attribute.rb +38 -9
  12. data/lib/yard-chef/handlers/base.rb +14 -14
  13. data/lib/yard-chef/handlers/cookbook.rb +18 -8
  14. data/lib/yard-chef/handlers/dependency.rb +55 -0
  15. data/lib/yard-chef/handlers/recipe.rb +25 -13
  16. data/templates/default/action/html/action.erb +8 -0
  17. data/templates/default/action/html/setup.rb +1 -13
  18. data/templates/default/attribute/html/attribute.erb +7 -0
  19. data/templates/default/attribute/html/attribute_header.erb +1 -1
  20. data/templates/default/attribute/html/cookbook_attribute.erb +33 -0
  21. data/templates/default/attribute/html/{table.erb → resource_attribute.erb} +24 -2
  22. data/templates/default/attribute/html/setup.rb +2 -1
  23. data/templates/default/chef/html/cookbook_table.erb +323 -2
  24. data/templates/default/chef/html/setup.rb +1 -1
  25. data/templates/default/chef_tags/html/example.erb +11 -0
  26. data/templates/default/chef_tags/html/index.erb +3 -0
  27. data/templates/default/chef_tags/html/note.erb +10 -0
  28. data/templates/default/chef_tags/html/see.erb +8 -0
  29. data/templates/default/chef_tags/html/tag.erb +20 -0
  30. data/templates/default/chef_tags/setup.rb +43 -0
  31. data/templates/default/{action/html/action_summary.erb → cookbook/html/dependencies.erb} +7 -6
  32. data/templates/default/cookbook/html/libraries.erb +4 -2
  33. data/templates/default/cookbook/html/recipes.erb +1 -1
  34. data/templates/default/cookbook/html/setup.rb +18 -14
  35. data/templates/default/fulldoc/html/css/bootstrap.min.css +5 -0
  36. data/templates/default/fulldoc/html/css/common.css +146 -0
  37. data/templates/default/fulldoc/html/full_list_cookbooks.erb +6 -8
  38. data/templates/default/fulldoc/html/full_list_definitions.erb +9 -9
  39. data/templates/default/fulldoc/html/full_list_recipes.erb +5 -5
  40. data/templates/default/fulldoc/html/full_list_resources.erb +5 -5
  41. data/templates/default/fulldoc/html/js/app.js +213 -0
  42. data/templates/default/fulldoc/html/js/bootstrap.min.js +7 -0
  43. data/templates/default/fulldoc/html/js/d3.js +5 -0
  44. data/templates/default/fulldoc/html/js/jquery.js +4 -0
  45. data/templates/default/fulldoc/html/setup.rb +34 -24
  46. data/templates/default/layout/html/setup.rb +19 -9
  47. data/templates/default/provider/html/providers_summary.erb +4 -1
  48. data/templates/default/provider/html/setup.rb +2 -1
  49. data/templates/default/recipe/html/recipe_list.erb +1 -1
  50. data/templates/default/recipe/html/setup.rb +1 -1
  51. data/templates/default/resource/html/actions.erb +1 -1
  52. data/templates/default/resource/html/setup.rb +1 -1
  53. metadata +64 -55
  54. data/templates/default/action/html/action_list.erb +0 -42
  55. data/templates/default/action/html/source.erb +0 -34
  56. data/templates/default/chef/html/docstring.erb +0 -25
  57. data/templates/default/layout/html/cookbook_table.erb +0 -46
@@ -0,0 +1,55 @@
1
+ # Copyright (c) 2015 Aleksey Hariton
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining
4
+ # a copy of this software and associated documentation files (the
5
+ # 'Software'), to deal in the Software without restriction, including
6
+ # without limitation the rights to use, copy, modify, merge, publish,
7
+ # distribute, sublicense, and/or sell copies of the Software, and to
8
+ # permit persons to whom the Software is furnished to do so, subject to
9
+ # the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be
12
+ # included in all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
15
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
17
+ # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
18
+ # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
19
+ # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
20
+ # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
22
+ require 'yard'
23
+
24
+ module YARD::Handlers
25
+ module Chef
26
+ # Handles "recipes" in a cookbook.
27
+ class DependencyHandler < Base
28
+ handles method_call(:depends)
29
+
30
+ def process
31
+ path_array = statement.file.to_s.split('/')
32
+ return unless path_array.include?('metadata.rb')
33
+
34
+ # Recipe declaration in metadata.rb
35
+ dependency_obj = ChefObject.register(cookbook, name, :dependency)
36
+ dependency_obj.docstring = statement.docstring
37
+ end
38
+
39
+ # Gets the recipe name from the metadata.rb.
40
+ #
41
+ # @return [String] the recipe name
42
+ #
43
+ def name
44
+ statement.parameters.first.jump(:string_content, :ident).source
45
+ end
46
+
47
+ # Gets the docstring for the recipe. The docstring is obtained from the
48
+ # description field in the recipe.
49
+ #
50
+ # @return [YARD::Docsting] the docstring
51
+ #
52
+ def parse_docs; end
53
+ end
54
+ end
55
+ end
@@ -26,12 +26,32 @@ module YARD::Handlers
26
26
  # Handles "recipes" in a cookbook.
27
27
  class RecipeHandler < Base
28
28
  handles method_call(:recipe)
29
+ handles :comment, :void_stmt
29
30
 
30
31
  def process
31
- return unless statement.file.to_s =~ /metadata.rb/
32
+ path_array = statement.file.to_s.split('/')
32
33
 
33
- recipe_obj = ChefObject.register(cookbook, name, :recipe)
34
- recipe_obj.docstring = docstring
34
+ # Recipe declaration in metadata.rb
35
+ if path_array.include?('metadata.rb') && (statement.jump(:ident).source == 'recipe')
36
+ description = ''
37
+ recipe_obj = ChefObject.register(cookbook, name, :recipe)
38
+ # YARD builds an abstract syntax tree (AST) which we need to traverse
39
+ # to obtain the complete docstring
40
+ statement.parameters[1].traverse do |child|
41
+ description << child.jump(:string_content).source if child.type == :string_content
42
+ end
43
+ recipe_obj.short_desc = YARD::DocstringParser.new.parse(description).to_docstring
44
+ recipe_obj.docstring = statement.docstring
45
+ end
46
+
47
+ # Recipe description in the head of recipe, leading comment block
48
+ if path_array.include? 'recipes'
49
+ recipe_obj = ChefObject.register(cookbook, ::File.basename(statement.file.to_s, '.rb'), :recipe)
50
+ if statement.docstring =~ /[\s\t]*\*?Description[:]?\*?/i
51
+ recipe_obj.docstring = statement.docstring
52
+ end
53
+ end
54
+ recipe_obj
35
55
  end
36
56
 
37
57
  # Gets the recipe name from the metadata.rb.
@@ -40,7 +60,7 @@ module YARD::Handlers
40
60
  #
41
61
  def name
42
62
  recipe = statement.parameters.first.jump(:string_content, :ident).source
43
- recipe = recipe.split("::")[1] if recipe =~ /::/
63
+ recipe = recipe.split('::')[1] if recipe =~ /::/
44
64
  recipe = 'default' if recipe == cookbook.name.to_s
45
65
  recipe
46
66
  end
@@ -50,15 +70,7 @@ module YARD::Handlers
50
70
  #
51
71
  # @return [YARD::Docsting] the docstring
52
72
  #
53
- def docstring
54
- description = ""
55
- # YARD builds an abstract syntax tree (AST) which we need to traverse
56
- # to obtain the complete docstring
57
- statement.parameters[1].traverse do |child|
58
- description << child.jump(:string_content).source if child.type == :string_content
59
- end
60
- YARD::DocstringParser.new.parse(description).to_docstring
61
- end
73
+ def parse_docs; end
62
74
  end
63
75
  end
64
76
  end
@@ -0,0 +1,8 @@
1
+ <li class="action">
2
+ <span class="summary_signature">
3
+ <strong><a href="#<%= object.name %>"><%= object.name %></a></strong>
4
+ </span>
5
+ <span class="summary_desc"><%= htmlify object.docstring %></span>
6
+ </li>
7
+
8
+ <%= yieldall %>
@@ -20,17 +20,5 @@
20
20
  # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
21
 
22
22
  def init
23
- case object.type
24
- when :cookbook
25
- @providers = object.providers
26
- sections.push :action_list, [:source]
27
- when :provider
28
- @actions = object.children_by_type(:action)
29
- sections.push :action_summary
30
- end
31
- end
32
-
33
- def source
34
- return if object.source.nil?
35
- erb(:source)
23
+ sections.push :action, [T('chef_tags')] if object.type == :action
36
24
  end
@@ -0,0 +1,7 @@
1
+ <%=
2
+ if object.type == :cookbook
3
+ erb(:cookbook_attribute)
4
+ else
5
+ erb(:resource_attribute)
6
+ end
7
+ %>
@@ -23,6 +23,6 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
23
  %>
24
24
 
25
25
  <% if @attributes.size > 0 %>
26
- <%= object.type == :resource ? "<h4>Attributes</h4>" : "<h2>Cookbook Attributes</h2>"%>
26
+ <%= object.type == :resource ? '<h4>Attributes</h4>' : '<h2>Cookbook Attributes</h2>'%>
27
27
  <ul><%= yieldall %></ul>
28
28
  <% end %>
@@ -0,0 +1,33 @@
1
+ <% @attributes.each do |attribute| %>
2
+ <h3><strong><%= attribute.name %></strong></h3>
3
+ <div class="row">
4
+ <div class="col-md-12">
5
+ <p>
6
+ <strong>Description</strong><br />
7
+ </p>
8
+ <%= htmlify attribute.docstring %>
9
+ <%= yieldall :object => attribute, :owner => object %>
10
+ </div>
11
+ </div>
12
+ <div class="row">
13
+ <div class="col-md-12">
14
+ <p>
15
+ <strong>JSON and default value</strong>
16
+ </p>
17
+ <table class="table table-hover">
18
+ <tr>
19
+ <th>JSON</th>
20
+ <th>Default value</th>
21
+ </tr>
22
+ <tr>
23
+ <td>
24
+ <% if object.type == :cookbook %>
25
+ <%= htmlify attribute.to_json %>
26
+ <% end %>
27
+ </td>
28
+ <td><%= htmlify attribute.default %></td>
29
+ </tr>
30
+ </table>
31
+ </div>
32
+ </div>
33
+ <% end %>
@@ -27,12 +27,34 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27
27
  <thead>
28
28
  <th>Attribute</th>
29
29
  <th>Description</th>
30
+ <% if object.type == :resource %>
31
+ <th>Type</th>
32
+ <% end %>
33
+ <th>Default</th>
30
34
  </thead>
31
35
  <tbody>
32
36
  <% @attributes.each do |attribute| %>
33
37
  <tr class="r<%= n %>">
34
- <td><strong><%= attribute.name %></strong></td>
35
- <td><%= attribute.docstring %></td>
38
+ <td>
39
+ <strong><%= attribute.name %></strong>
40
+ <% if object.type == :cookbook %>
41
+ <br /><span>[<a href="#" onclick="return false;" class="toggleSource">View JSON</a>]</span><br /><span class="source_code"><%= htmlify attribute.to_json %></span>
42
+ <% end %>
43
+ </td>
44
+ <td>
45
+ <%= htmlify attribute.docstring %>
46
+ <%= yieldall :object => attribute, :owner => object %>
47
+ </td>
48
+ <% if object.type == :resource %>
49
+ <td><%= attribute.kind_of %></td>
50
+ <% end %>
51
+ <td>
52
+ <% if attribute.default.size > 60 %>
53
+ <span>[<a href="#" onclick="return false;" class="toggleSource">View source</a>]</span><br /><span class="source_code"><%= htmlify attribute.default %></span>
54
+ <% else %>
55
+ <%= htmlify attribute.default %>
56
+ <%end%>
57
+ </td>
36
58
  </tr>
37
59
  <% n = n == 2 ? 1 : 2 %>
38
60
  <% end %>
@@ -22,5 +22,6 @@
22
22
  def init
23
23
  @attributes = object.children_by_type(:attribute)
24
24
 
25
- sections.push :attribute_header, [:table]
25
+ sections.push :attribute_header
26
+ sections.push :attribute, [T('chef_tags')]
26
27
  end
@@ -22,7 +22,30 @@ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
22
22
  SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
23
  %>
24
24
 
25
- <h1>Cookbooks List</h1>
25
+ <h2>Cookbooks Dependencies Tree</h2>
26
+
27
+ <!-- Button trigger modal -->
28
+ <button type="button" class="btn btn-primary btn-normal" data-toggle="modal" data-target="#dependenciesTree">
29
+ Show cookbooks dependencies tree
30
+ </button>
31
+
32
+ <!-- Modal -->
33
+ <div class="modal fade" id="dependenciesTree" tabindex="-1" role="dialog" aria-labelledby="dependenciesTreeLabel">
34
+ <div class="modal-dialog modal-lg" role="document">
35
+ <div class="modal-content">
36
+ <div class="modal-header">
37
+ <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
38
+ <h4 class="modal-title" id="dependenciesTreeLabel">Cookbooks dependency tree</h4>
39
+ </div>
40
+ <div class="modal-body">
41
+ Double click on node to open cookbook documentation.
42
+ </div>
43
+ </div>
44
+ </div>
45
+ </div>
46
+
47
+
48
+ <h2>Cookbooks List</h2>
26
49
  <table>
27
50
  <thead>
28
51
  <tr>
@@ -36,10 +59,308 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
36
59
  <% @cookbooks.each do |cookbook| %>
37
60
  <tr class="r<%= n %>">
38
61
  <td><%= linkify cookbook %></td>
39
- <td><%= cookbook.short_desc %></td>
62
+ <td><%= htmlify cookbook.short_desc %></td>
40
63
  <td><%= cookbook.version %></td>
41
64
  </tr>
42
65
  <% n = n == 2 ? 1 : 2 %>
43
66
  <% end %>
44
67
  </tbody>
45
68
  </table>
69
+
70
+ <!--
71
+ <script>
72
+
73
+ // http://blog.thomsonreuters.com/index.php/mobile-patent-suits-graphic-of-the-day/
74
+ var links = [
75
+ <% @cookbooks.each do |cookbook| %>
76
+ <% cookbook.dependencies.each do |dependency|%>
77
+ {source: "<%= cookbook.name %>", target: "<%= dependency.name %>", type: "dependency"},
78
+ <% end %>
79
+ <% end %>
80
+ ];
81
+
82
+ var nodes = {};
83
+
84
+ // Compute the distinct nodes from the links.
85
+ links.forEach(function(link) {
86
+ link.source = nodes[link.source] || (nodes[link.source] = {name: link.source});
87
+ link.target = nodes[link.target] || (nodes[link.target] = {name: link.target});
88
+ });
89
+
90
+ var margin = {top: -5, right: -5, bottom: -5, left: -5},
91
+ width = "960",
92
+ height = "900";
93
+
94
+ var zoom = d3.behavior.zoom()
95
+ .scaleExtent([1, 10])
96
+ .on("zoom", zoomed);
97
+
98
+ var force = d3.layout.force()
99
+ .nodes(d3.values(nodes))
100
+ .links(links)
101
+ .size([width, height])
102
+ .linkDistance(90)
103
+ .charge(-300)
104
+ .on("tick", tick)
105
+ .start();
106
+
107
+ var drag = force.drag()
108
+ .on("dragstart", dragstart);
109
+
110
+ var svg = d3.select("span#graph").append("svg");
111
+
112
+ // define arrow markers for graph links
113
+ svg.append('svg:defs').append('svg:marker')
114
+ .attr('id', 'end-arrow')
115
+ .attr('viewBox', '0 -5 10 10')
116
+ .attr('refX', 6)
117
+ .attr('markerWidth', 3)
118
+ .attr('markerHeight', 3)
119
+ .attr('orient', 'auto')
120
+ .append('svg:path')
121
+ .attr('d', 'M0,-5L10,0L0,5')
122
+ .attr('fill', '#000');
123
+
124
+ svg.attr("width", width)
125
+ .attr("height", height)
126
+ .append("g")
127
+ .attr("transform", "translate(" + margin.left + "," + margin.right + ")")
128
+ .call(zoom)
129
+ .on("dblclick.zoom", null);
130
+
131
+ var link = svg.selectAll(".link")
132
+ .data(force.links())
133
+ .enter().append("line")
134
+ .attr("class", "link");
135
+
136
+ var node = svg.selectAll(".node")
137
+ .data(force.nodes())
138
+ .enter().append("g")
139
+ .attr("class", "node")
140
+ .on("mouseover", mouseover)
141
+ .on("mouseout", mouseout)
142
+ .on("dblclick", dblclick)
143
+ .call(drag)
144
+ .call(force.drag);
145
+
146
+ node.append("circle")
147
+ .attr("r", 8);
148
+
149
+ node.append("text")
150
+ .attr("x", 12)
151
+ .attr("dy", ".35em")
152
+ .text(function(d) { return d.name; });
153
+
154
+ function tick() {
155
+ link.attr("x1", function(d) { return d.source.x; })
156
+ .attr("y1", function(d) { return d.source.y; })
157
+ .attr("x2", function(d) { return d.target.x; })
158
+ .attr("y2", function(d) { return d.target.y; });
159
+ node.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
160
+ }
161
+
162
+ function zoomed() {
163
+ svg.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
164
+ }
165
+ function dblclick(d) {
166
+ d3.select(this).classed("fixed", d.fixed = false);
167
+ }
168
+
169
+ function mouseover() {
170
+ d3.select(this).select("circle").transition()
171
+ .duration(750)
172
+ .attr("r", 16);
173
+ }
174
+
175
+ function mouseout() {
176
+ d3.select(this).select("circle").transition()
177
+ .duration(750)
178
+ .attr("r", 8);
179
+ }
180
+
181
+ function dragstart(d) {
182
+ d3.event.sourceEvent.stopPropagation();
183
+ d3.select(this).classed("fixed", d.fixed = true);
184
+ }
185
+
186
+ </script>
187
+ -->
188
+
189
+
190
+
191
+
192
+ <%
193
+ cookbooks = @cookbooks
194
+ %>
195
+
196
+ <script>
197
+ var treeData = [
198
+ {
199
+ "name": "cookbooks",
200
+ "parent": "null",
201
+ "children": [
202
+ <% while (cookbook = cookbooks.pop) do %>
203
+ {
204
+ "name": "<%= cookbook.name %>",
205
+ "parent": "cookbooks",
206
+ <% dependencies = cookbook.dependencies.map {|d| d}
207
+ if dependencies.size > 0
208
+ %>
209
+ "children": [
210
+ <%
211
+ while(dependency = dependencies.pop)%>
212
+ {
213
+ "name": "<%= dependency.name %>",
214
+ "parent": "<%= cookbook.name %>"
215
+ }<%= ',' if dependencies.size > 0 %>
216
+ <% end %>
217
+ ]
218
+ <% end %>
219
+ }<%= ',' if cookbooks.size > 0 %>
220
+ <% end %>
221
+ ]
222
+ }
223
+ ];
224
+ // ************** Generate the tree diagram *****************
225
+ var margin = {top: 20, right: 120, bottom: 20, left: 120},
226
+ width = 1024 - margin.right - margin.left,
227
+ height = 800 - margin.top - margin.bottom;
228
+
229
+ var i = 0,
230
+ duration = 750,
231
+ root;
232
+
233
+ var tree = d3.layout.tree()
234
+ .size([height, width]);
235
+
236
+ var diagonal = d3.svg.diagonal()
237
+ .projection(function(d) { return [d.y, d.x]; });
238
+
239
+ var svg = d3.select("div.modal-body").append("svg")
240
+ .attr("width", width + margin.right + margin.left)
241
+ .attr("height", height + margin.top + margin.bottom)
242
+ .append("g")
243
+ .attr("transform", "translate(" + margin.left + "," + margin.top + ")");
244
+
245
+ root = treeData[0];
246
+ root.x0 = height / 2;
247
+ root.y0 = 0;
248
+
249
+ function collapse(d) {
250
+ if (d.children) {
251
+ d._children = d.children;
252
+ d._children.forEach(collapse);
253
+ d.children = null;
254
+ }
255
+ }
256
+
257
+ root.children.forEach(collapse);
258
+ update(root);
259
+
260
+ d3.select(self.frameElement).style("height", "800px");
261
+
262
+ function update(source) {
263
+
264
+ // Compute the new tree layout.
265
+ var nodes = tree.nodes(root).reverse(),
266
+ links = tree.links(nodes);
267
+
268
+ // Normalize for fixed-depth.
269
+ nodes.forEach(function(d) { d.y = d.depth * 180; });
270
+
271
+ // Update the nodes…
272
+ var node = svg.selectAll("g.node")
273
+ .data(nodes, function(d) { return d.id || (d.id = ++i); });
274
+
275
+ // Enter any new nodes at the parent's previous position.
276
+ var nodeEnter = node.enter().append("g")
277
+ .attr("class", "node")
278
+ .attr("transform", function(d) { return "translate(" + source.y0 + "," + source.x0 + ")"; })
279
+ .on("click", click)
280
+ .on("dblclick", cookbook_click);
281
+
282
+ nodeEnter.append("circle")
283
+ .attr("r", 1e-6)
284
+ .style("fill", function(d) { return d._children ? "lightsteelblue" : "#fff"; });
285
+
286
+ nodeEnter.append("text")
287
+ .attr("x", function(d) { return 10; })
288
+ .attr("y", function(d) { return -6; })
289
+ .attr("dy", ".35em")
290
+ .attr("text-anchor", function(d) { return "start"; })
291
+ .text(function(d) { return d.name; })
292
+ .style("fill-opacity", 1e-6);
293
+
294
+ // Transition nodes to their new position.
295
+ var nodeUpdate = node.transition()
296
+ .duration(duration)
297
+ .attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; });
298
+
299
+ nodeUpdate.select("circle")
300
+ .attr("r", 7)
301
+ .style("fill", function(d) { return d._children ? "lightsteelblue" : "#fff"; });
302
+
303
+ nodeUpdate.select("text")
304
+ .style("fill-opacity", 1);
305
+
306
+ // Transition exiting nodes to the parent's new position.
307
+ var nodeExit = node.exit().transition()
308
+ .duration(duration)
309
+ .attr("transform", function(d) { return "translate(" + source.y + "," + source.x + ")"; })
310
+ .remove();
311
+
312
+ nodeExit.select("circle")
313
+ .attr("r", 1e-6);
314
+
315
+ nodeExit.select("text")
316
+ .style("fill-opacity", 1e-6);
317
+
318
+ // Update the links…
319
+ var link = svg.selectAll("path.link")
320
+ .data(links, function(d) { return d.target.id; });
321
+
322
+ // Enter any new links at the parent's previous position.
323
+ link.enter().insert("path", "g")
324
+ .attr("class", "link")
325
+ .attr("d", function(d) {
326
+ var o = {x: source.x0, y: source.y0};
327
+ return diagonal({source: o, target: o});
328
+ });
329
+
330
+ // Transition links to their new position.
331
+ link.transition()
332
+ .duration(duration)
333
+ .attr("d", diagonal);
334
+
335
+ // Transition exiting nodes to the parent's new position.
336
+ link.exit().transition()
337
+ .duration(duration)
338
+ .attr("d", function(d) {
339
+ var o = {x: source.x, y: source.y};
340
+ return diagonal({source: o, target: o});
341
+ })
342
+ .remove();
343
+
344
+ // Stash the old positions for transition.
345
+ nodes.forEach(function(d) {
346
+ d.x0 = d.x;
347
+ d.y0 = d.y;
348
+ });
349
+ }
350
+
351
+ // Toggle children on click.
352
+ function click(d) {
353
+ if (d.children) {
354
+ d._children = d.children;
355
+ d.children = null;
356
+ } else {
357
+ d.children = d._children;
358
+ d._children = null;
359
+ }
360
+ update(d);
361
+ }
362
+ // Toggle children on click.
363
+ function cookbook_click(d) {
364
+ window.location.href = "chef/" + d.name + ".html";
365
+ }
366
+ </script>