yard-chef 1.0.0 → 2.0.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.
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>