persia_generator 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,171 @@
1
+ class PersiaScaffoldingSandbox
2
+ include ActionView::Helpers::ActiveRecordHelper
3
+
4
+ attr_accessor :form_action, :singular_name, :suffix, :model_instance
5
+
6
+ def sandbox_binding
7
+ binding
8
+ end
9
+
10
+ def default_input_block
11
+ Proc.new { |record, column| "<p><label for=\"#{record}_#{column.name}\">#{column.human_name}</label><br/>\n#{input(record, column.name)}</p>\n" }
12
+ end
13
+
14
+ end
15
+
16
+ class ActionView::Helpers::InstanceTag
17
+ def to_input_field_tag(field_type, options={})
18
+ field_meth = "#{field_type}_field"
19
+ "<%= #{field_meth} '#{@object_name}', '#{@method_name}' #{options.empty? ? '' : ', '+options.inspect} %>"
20
+ end
21
+
22
+ def to_text_area_tag(options = {})
23
+ "<%= text_area '#{@object_name}', '#{@method_name}' #{options.empty? ? '' : ', '+ options.inspect} %>"
24
+ end
25
+
26
+ def to_date_select_tag(options = {})
27
+ "<%= date_select '#{@object_name}', '#{@method_name}' #{options.empty? ? '' : ', '+ options.inspect} %>"
28
+ end
29
+
30
+ def to_datetime_select_tag(options = {})
31
+ "<%= datetime_select '#{@object_name}', '#{@method_name}' #{options.empty? ? '' : ', '+ options.inspect} %>"
32
+ end
33
+ end
34
+
35
+ class PersiaScaffoldGenerator < Rails::Generator::NamedBase
36
+ attr_reader :controller_name,
37
+ :controller_class_path,
38
+ :controller_file_path,
39
+ :controller_class_nesting,
40
+ :controller_class_nesting_depth,
41
+ :controller_class_name,
42
+ :controller_singular_name,
43
+ :controller_plural_name
44
+ alias_method :controller_file_name, :controller_singular_name
45
+ alias_method :controller_table_name, :controller_plural_name
46
+
47
+ def initialize(runtime_args, runtime_options = {})
48
+ super
49
+
50
+ # Take controller name from the next argument. Default to the pluralized model name.
51
+ @controller_name = args.shift
52
+ @controller_name ||= ActiveRecord::Base.pluralize_table_names ? @name.pluralize : @name
53
+
54
+ base_name, @controller_class_path, @controller_file_path, @controller_class_nesting, @controller_class_nesting_depth = extract_modules(@controller_name)
55
+ @controller_class_name_without_nesting, @controller_singular_name, @controller_plural_name = inflect_names(base_name)
56
+
57
+ if @controller_class_nesting.empty?
58
+ @controller_class_name = @controller_class_name_without_nesting
59
+ else
60
+ @controller_class_name = "#{@controller_class_nesting}::#{@controller_class_name_without_nesting}"
61
+ end
62
+ end
63
+
64
+ def manifest
65
+ record do |m|
66
+ # Check for class naming collisions.
67
+ m.class_collisions controller_class_path, "#{controller_class_name}Controller", "#{controller_class_name}ControllerTest", "#{controller_class_name}Helper"
68
+
69
+ # Controller, helper, views, and test directories.
70
+ m.directory File.join('app/controllers', controller_class_path)
71
+ m.directory File.join('app/helpers', controller_class_path)
72
+ m.directory File.join('app/views', controller_class_path, controller_file_name)
73
+ m.directory File.join('test/functional', controller_class_path)
74
+
75
+ # Depend on model generator but skip if the model exists.
76
+ m.dependency 'model', [singular_name], :collision => :skip, :skip_migration => true
77
+
78
+ # Scaffolded views.
79
+ scaffold_views.each do |action|
80
+ m.template "view_#{action}.rhtml",
81
+ File.join('app/views',
82
+ controller_class_path,
83
+ controller_file_name,
84
+ "#{action}.mdml"),
85
+ :assigns => { :action => action }
86
+ end
87
+
88
+ # Controller class, functional test, helper, and views.
89
+ m.template 'controller.rb',
90
+ File.join('app/controllers',
91
+ controller_class_path,
92
+ "#{controller_file_name}_controller.rb")
93
+
94
+ m.template 'functional_test.rb',
95
+ File.join('test/functional',
96
+ controller_class_path,
97
+ "#{controller_file_name}_controller_test.rb")
98
+
99
+ # Controller class, functional test, helper, and views.
100
+ m.template 'view.rb',
101
+ File.join("app/views/#{controller_file_name}",
102
+ controller_class_path,
103
+ "view.rb")
104
+
105
+ # Layout and stylesheet.
106
+ m.template 'view_layout.rhtml', "app/views/#{controller_file_name}/layout.mdml"
107
+ m.template 'style.css', 'public/stylesheets/scaffold.css'
108
+
109
+ # Unscaffolded views.
110
+ unscaffolded_actions.each do |action|
111
+ path = File.join('app/views',
112
+ controller_class_path,
113
+ controller_file_name,
114
+ "#{action}.rhtml")
115
+ m.template "controller:view.rhtml", path,
116
+ :assigns => { :action => action, :path => path}
117
+ end
118
+ end
119
+ end
120
+
121
+ protected
122
+ # Override with your own usage banner.
123
+ def banner
124
+ "Usage: #{$0} persia_scaffold ModelName [ControllerName] [action, ...]"
125
+ end
126
+
127
+ def scaffold_views
128
+ %w(list show new edit)
129
+ end
130
+
131
+ def scaffold_actions
132
+ scaffold_views + %w(index create update destroy)
133
+ end
134
+
135
+ def model_name
136
+ class_name.demodulize
137
+ end
138
+
139
+ def unscaffolded_actions
140
+ args - scaffold_actions
141
+ end
142
+
143
+ def suffix
144
+ "_#{singular_name}" if options[:suffix]
145
+ end
146
+
147
+ def create_sandbox
148
+ sandbox = PersiaScaffoldingSandbox.new
149
+ sandbox.singular_name = singular_name
150
+ begin
151
+ sandbox.model_instance = model_instance
152
+ sandbox.instance_variable_set("@#{singular_name}", sandbox.model_instance)
153
+ rescue ActiveRecord::StatementInvalid => e
154
+ logger.error "Before updating scaffolding from new DB schema, try creating a table for your model (#{class_name})"
155
+ raise SystemExit
156
+ end
157
+ sandbox.suffix = suffix
158
+ sandbox
159
+ end
160
+
161
+ def model_instance
162
+ base = class_nesting.split('::').inject(Object) do |base, nested|
163
+ break base.const_get(nested) if base.const_defined?(nested)
164
+ base.const_set(nested, Module.new)
165
+ end
166
+ unless base.const_defined?(@class_name_without_nesting)
167
+ base.const_set(@class_name_without_nesting, Class.new(ActiveRecord::Base))
168
+ end
169
+ class_name.constantize.new
170
+ end
171
+ end
@@ -0,0 +1,58 @@
1
+ class <%= controller_class_name %>Controller < ApplicationController
2
+ <% unless suffix -%>
3
+ def index
4
+ list
5
+ render :action => 'list'
6
+ end
7
+ <% end -%>
8
+
9
+ <% for action in unscaffolded_actions -%>
10
+ def <%= action %><%= suffix %>
11
+ end
12
+
13
+ <% end -%>
14
+ # GETs should be safe (see http://www.w3.org/2001/tag/doc/whenToUseGet.html)
15
+ verify :method => :post, :only => [ :destroy<%= suffix %>, :create<%= suffix %>, :update<%= suffix %> ],
16
+ :redirect_to => { :action => :list<%= suffix %> }
17
+
18
+ def list<%= suffix %>
19
+ @<%= singular_name %>_pages, @<%= plural_name %> = paginate :<%= plural_name %>, :per_page => 10
20
+ end
21
+
22
+ def show<%= suffix %>
23
+ @<%= singular_name %> = <%= model_name %>.find(params[:id])
24
+ end
25
+
26
+ def new<%= suffix %>
27
+ @<%= singular_name %> = <%= model_name %>.new
28
+ end
29
+
30
+ def create<%= suffix %>
31
+ @<%= singular_name %> = <%= model_name %>.new(params[:<%= singular_name %>])
32
+ if @<%= singular_name %>.save
33
+ flash[:notice] = '<%= model_name %> was successfully created.'
34
+ redirect_to :action => 'list<%= suffix %>'
35
+ else
36
+ render :action => 'new<%= suffix %>'
37
+ end
38
+ end
39
+
40
+ def edit<%= suffix %>
41
+ @<%= singular_name %> = <%= model_name %>.find(params[:id])
42
+ end
43
+
44
+ def update
45
+ @<%= singular_name %> = <%= model_name %>.find(params[:id])
46
+ if @<%= singular_name %>.update_attributes(params[:<%= singular_name %>])
47
+ flash[:notice] = '<%= model_name %> was successfully updated.'
48
+ redirect_to :action => 'show<%= suffix %>', :id => @<%= singular_name %>
49
+ else
50
+ render :action => 'edit<%= suffix %>'
51
+ end
52
+ end
53
+
54
+ def destroy<%= suffix %>
55
+ <%= model_name %>.find(params[:id]).destroy
56
+ redirect_to :action => 'list<%= suffix %>'
57
+ end
58
+ end
@@ -0,0 +1,98 @@
1
+ require File.dirname(__FILE__) + '<%= "/.." * controller_class_nesting_depth %>/../test_helper'
2
+ require '<%= controller_file_path %>_controller'
3
+
4
+ # Re-raise errors caught by the controller.
5
+ class <%= controller_class_name %>Controller; def rescue_action(e) raise e end; end
6
+
7
+ class <%= controller_class_name %>ControllerTest < Test::Unit::TestCase
8
+ fixtures :<%= table_name %>
9
+
10
+ def setup
11
+ @controller = <%= controller_class_name %>Controller.new
12
+ @request = ActionController::TestRequest.new
13
+ @response = ActionController::TestResponse.new
14
+ end
15
+
16
+ <% for action in unscaffolded_actions -%>
17
+ def test_<%= action %>
18
+ get :<%= action %>
19
+ assert_response :success
20
+ assert_template '<%= action %>'
21
+ end
22
+
23
+ <% end -%>
24
+ <% unless suffix -%>
25
+ def test_index
26
+ get :index
27
+ assert_response :success
28
+ assert_template 'list'
29
+ end
30
+
31
+ <% end -%>
32
+ def test_list<%= suffix %>
33
+ get :list<%= suffix %>
34
+
35
+ assert_response :success
36
+ assert_template 'list<%= suffix %>'
37
+
38
+ assert_not_nil assigns(:<%= plural_name %>)
39
+ end
40
+
41
+ def test_show<%= suffix %>
42
+ get :show<%= suffix %>, :id => 1
43
+
44
+ assert_response :success
45
+ assert_template 'show'
46
+
47
+ assert_not_nil assigns(:<%= singular_name %>)
48
+ assert assigns(:<%= singular_name %>).valid?
49
+ end
50
+
51
+ def test_new<%= suffix %>
52
+ get :new<%= suffix %>
53
+
54
+ assert_response :success
55
+ assert_template 'new<%= suffix %>'
56
+
57
+ assert_not_nil assigns(:<%= singular_name %>)
58
+ end
59
+
60
+ def test_create
61
+ num_<%= plural_name %> = <%= model_name %>.count
62
+
63
+ post :create<%= suffix %>, :<%= singular_name %> => {}
64
+
65
+ assert_response :redirect
66
+ assert_redirected_to :action => 'list<%= suffix %>'
67
+
68
+ assert_equal num_<%= plural_name %> + 1, <%= model_name %>.count
69
+ end
70
+
71
+ def test_edit<%= suffix %>
72
+ get :edit<%= suffix %>, :id => 1
73
+
74
+ assert_response :success
75
+ assert_template 'edit<%= suffix %>'
76
+
77
+ assert_not_nil assigns(:<%= singular_name %>)
78
+ assert assigns(:<%= singular_name %>).valid?
79
+ end
80
+
81
+ def test_update<%= suffix %>
82
+ post :update<%= suffix %>, :id => 1
83
+ assert_response :redirect
84
+ assert_redirected_to :action => 'show<%= suffix %>', :id => 1
85
+ end
86
+
87
+ def test_destroy<%= suffix %>
88
+ assert_not_nil <%= model_name %>.find(1)
89
+
90
+ post :destroy, :id => 1
91
+ assert_response :redirect
92
+ assert_redirected_to :action => 'list<%= suffix %>'
93
+
94
+ assert_raise(ActiveRecord::RecordNotFound) {
95
+ <%= model_name %>.find(1)
96
+ }
97
+ end
98
+ end
@@ -0,0 +1,74 @@
1
+ body { background-color: #fff; color: #333; }
2
+
3
+ body, p, ol, ul, td {
4
+ font-family: verdana, arial, helvetica, sans-serif;
5
+ font-size: 13px;
6
+ line-height: 18px;
7
+ }
8
+
9
+ pre {
10
+ background-color: #eee;
11
+ padding: 10px;
12
+ font-size: 11px;
13
+ }
14
+
15
+ a { color: #000; }
16
+ a:visited { color: #666; }
17
+ a:hover { color: #fff; background-color:#000; }
18
+
19
+ .fieldWithErrors {
20
+ padding: 2px;
21
+ background-color: red;
22
+ display: table;
23
+ }
24
+
25
+ #errorExplanation {
26
+ width: 400px;
27
+ border: 2px solid red;
28
+ padding: 7px;
29
+ padding-bottom: 12px;
30
+ margin-bottom: 20px;
31
+ background-color: #f0f0f0;
32
+ }
33
+
34
+ #errorExplanation h2 {
35
+ text-align: left;
36
+ font-weight: bold;
37
+ padding: 5px 5px 5px 15px;
38
+ font-size: 12px;
39
+ margin: -7px;
40
+ background-color: #c00;
41
+ color: #fff;
42
+ }
43
+
44
+ #errorExplanation p {
45
+ color: #333;
46
+ margin-bottom: 0;
47
+ padding: 5px;
48
+ }
49
+
50
+ #errorExplanation ul li {
51
+ font-size: 12px;
52
+ list-style: square;
53
+ }
54
+
55
+ div.uploadStatus {
56
+ margin: 5px;
57
+ }
58
+
59
+ div.progressBar {
60
+ margin: 5px;
61
+ }
62
+
63
+ div.progressBar div.border {
64
+ background-color: #fff;
65
+ border: 1px solid grey;
66
+ width: 100%;
67
+ }
68
+
69
+ div.progressBar div.background {
70
+ background-color: #333;
71
+ height: 18px;
72
+ width: 0%;
73
+ }
74
+
@@ -0,0 +1,51 @@
1
+ class <%= controller_class_name %>View < ActionView::PersiaView
2
+ define_resources :layout => 'app/views/layouts/layout.mdml'
3
+
4
+ def list(cursor)
5
+ cursor.replace! :document, :hole => :content_list
6
+ current = @<%= singular_name %>_pages.current
7
+ # fill th elements
8
+ cursor.id![:header].times_and_swap!(<%= model_name %>.content_columns) { |c, i| c[] <= i.human_name }
9
+
10
+ # fill table
11
+ cursor.id![:rows].times_and_swap!(@<%= plural_name %>) do |e, i|
12
+ # e is tr element
13
+ e.td / [
14
+ macro.times_and_swap!(<%= model_name %>.content_columns) { |e2, col| e2[] <= i.send(col.name) },
15
+ macro.a ^ { :action => 'show', :id => i },
16
+ macro.a ^ { :action => 'edit', :id => i },
17
+ macro.a ^ { :action => 'destroy', :id => i }
18
+ ]
19
+ end
20
+
21
+ # previous/next page links
22
+ cursor.set_urls! :new => { :action => 'new' },
23
+ :prev => [current.previous, { :page => current.previous }],
24
+ :next => [current.next, { :page => current.next }]
25
+ end
26
+
27
+ def show(cursor)
28
+ cursor.replace! :document, :hole => :content_show
29
+ cursor.id![:data_column].times_and_swap!(<%= model_name %>.content_columns) do |e, column|
30
+ e.b[] <= column.human_name + ":"
31
+ e.span[] <= ERB::Util.h(@<%= singular_name %>.send(column.name))
32
+ end
33
+ cursor.set_urls! :edit => { :action => 'edit', :id => @<%= singular_name %> },
34
+ :back => { :action => 'list' }
35
+ end
36
+
37
+ def new(cursor)
38
+ cursor.replace! :document, :hole => :content_new
39
+ cursor.set_urls! :new_form => { :action => 'create' },
40
+ :list => { :action => 'list' }
41
+ cursor.id![:fields] <= all_input_tags(@<%= singular_name %>, '<%= singular_name %>', {})
42
+ end
43
+
44
+ def edit(cursor)
45
+ cursor.replace! :document, :hole => :content_edit
46
+ cursor.set_urls! :show => { :action => 'show', :id => @<%= singular_name %> },
47
+ :edit_form => { :action => 'update', :id => @<%= singular_name %> },
48
+ :list => { :action => 'list' }
49
+ cursor.id![:fields] <= all_input_tags(@<%= singular_name %>, '<%= singular_name %>', {})
50
+ end
51
+ end
@@ -0,0 +1,25 @@
1
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
2
+ <html xmlns:persia="http://persia.rubyforge.org/specs">
3
+ <head>
4
+ <title>Title</title>
5
+ </head>
6
+ <body>
7
+
8
+ <p style="color: green">flash</p>
9
+
10
+ <div persia:id="content_edit">
11
+ <h1>Editing <%= singular_name %></h1>
12
+
13
+ <form persia:id="edit_form" method="post" action="/<%= @controller_name %>/update">
14
+ <div persia:skip="fields">
15
+ <input name="foo" />
16
+ </div>
17
+ <input name="commit" type="submit" value="Edit" />
18
+ </form>
19
+
20
+ <a persia:id="show" href="">Show</a>
21
+ <a persia:id="list" href="">Back</a>
22
+ </div>
23
+
24
+ </body>
25
+ </html>
@@ -0,0 +1,36 @@
1
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
2
+ <html xmlns:persia="http://persia.rubyforge.org/specs" persia:id="document">
3
+ <head persia:id="head">
4
+ <title persia:id="title">Standard title - change me</title>
5
+ </head>
6
+ <body>
7
+ <p style="color: green" persia:id="flash"></p>
8
+
9
+ <div persia:skip="hole">
10
+ <h1>Manipulate model</h1>
11
+
12
+ <table>
13
+ <tr>
14
+ <th>Name</th>
15
+ </tr>
16
+
17
+ <tr>
18
+ <td>Mijn data</td>
19
+ <td><a href="#">Show</a></td>
20
+ <td><a href="#">Edit</a></td>
21
+ <td>Destroy</td>
22
+ </tr>
23
+ </table>
24
+
25
+ <a href="#">Previous page</a>
26
+ <a href="#">Next page</a>
27
+
28
+ <br />
29
+
30
+ <a href="#">New user</a>
31
+
32
+ </div>
33
+
34
+ </body>
35
+ </html>
36
+
@@ -0,0 +1,39 @@
1
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
2
+ <html xmlns:persia="http://persia.rubyforge.org/specs">
3
+ <head>
4
+ <title>Title</title>
5
+ </head>
6
+ <body>
7
+
8
+ <p style="color: green">flash</p>
9
+
10
+ <div persia:id="content_list">
11
+ <h1>Listing <%= plural_name %></h1>
12
+
13
+ <table>
14
+ <tr>
15
+ <th persia:id="header">Name</th>
16
+ </tr>
17
+
18
+ <tr persia:id="rows">
19
+ <td>Mijn data</td>
20
+ <td><a href="">Show</a></td>
21
+ <td><a href="">Edit</a></td>
22
+ <td>
23
+ <a href="" onclick='if (confirm(&apos;Are you sure?&apos;)) { var f = document.createElement(&apos;form&apos;); this.parentNode.appendChild(f); f.method = &apos;POST&apos;; f.action = this.href; f.submit(); };return false;'>
24
+ Destroy
25
+ </a>
26
+ </td>
27
+ </tr>
28
+ </table>
29
+
30
+ <a persia:id="prev" href="">Previous page</a>
31
+ <a persia:id="next" href="">Next page</a>
32
+
33
+ <br />
34
+
35
+ <a persia:id="new" href="">New <%= singular_name %></a>
36
+
37
+ </div>
38
+ </body>
39
+ </html>
@@ -0,0 +1,23 @@
1
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
2
+ <html xmlns:persia="http://persia.rubyforge.org/specs">
3
+ <head>
4
+ <title>Title</title>
5
+ </head>
6
+ <body>
7
+
8
+ <p style="color: green"></p>
9
+
10
+ <div persia:id="content_new">
11
+ <h1>New <%= singular_name %></h1>
12
+
13
+ <form persia:id="new_form" method="post" action="/<%= @controller_name %>/create">
14
+ <div persia:skip="fields">
15
+ <input name="foo" />
16
+ </div>
17
+ <input name="commit" type="submit" value="Create" />
18
+ </form>
19
+
20
+ <a persia:id="list" href="">Back</a>
21
+ </div>
22
+ </body>
23
+ </html>
@@ -0,0 +1,20 @@
1
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
2
+ <html xmlns:persia="http://persia.rubyforge.org/specs">
3
+ <head>
4
+ <title>Title</title>
5
+ </head>
6
+ <body>
7
+
8
+ <p style="color: green">hehe</p>
9
+
10
+ <div persia:id="content_show">
11
+ <p persia:id="data_column">
12
+ <b>Name:</b>
13
+ <span>Kees</span>
14
+ </p>
15
+
16
+ <a persia:id="edit" href="">Edit</a> |
17
+ <a persia:id="back" href="">Back</a>
18
+ </div>
19
+ </body>
20
+ </html>
metadata ADDED
@@ -0,0 +1,62 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.8.11
3
+ specification_version: 1
4
+ name: persia_generator
5
+ version: !ruby/object:Gem::Version
6
+ version: 0.1.0
7
+ date: 2006-09-12 00:00:00 +02:00
8
+ summary: Scaffold generator for persia
9
+ require_paths:
10
+ - lib
11
+ email: michiel@finalist.com
12
+ homepage: http://persia.rubyforge.org/
13
+ rubyforge_project:
14
+ description:
15
+ autorequire:
16
+ default_executable:
17
+ bindir: bin
18
+ has_rdoc: false
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ - - ">"
22
+ - !ruby/object:Gem::Version
23
+ version: 0.0.0
24
+ version:
25
+ platform: ruby
26
+ signing_key:
27
+ cert_chain:
28
+ authors:
29
+ - Michiel de Mare
30
+ files:
31
+ - persia_scaffold_generator.rb
32
+ - templates/controller.rb
33
+ - templates/functional_test.rb
34
+ - templates/style.css
35
+ - templates/view.rb
36
+ - templates/view_edit.rhtml
37
+ - templates/view_layout.rhtml
38
+ - templates/view_list.rhtml
39
+ - templates/view_new.rhtml
40
+ - templates/view_show.rhtml
41
+ test_files: []
42
+
43
+ rdoc_options: []
44
+
45
+ extra_rdoc_files: []
46
+
47
+ executables: []
48
+
49
+ extensions: []
50
+
51
+ requirements: []
52
+
53
+ dependencies:
54
+ - !ruby/object:Gem::Dependency
55
+ name: rails
56
+ version_requirement:
57
+ version_requirements: !ruby/object:Gem::Version::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: 1.1.6
62
+ version: