jenkins_api_client 0.14.1 → 1.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 (40) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +3 -0
  3. data/.travis.yml +5 -23
  4. data/CHANGELOG.md +104 -35
  5. data/CONTRIBUTORS.md +1 -11
  6. data/Gemfile +4 -2
  7. data/README.md +107 -7
  8. data/Rakefile +1 -0
  9. data/Vagrantfile +6 -8
  10. data/jenkins_api_client.gemspec +30 -12
  11. data/lib/jenkins_api_client/cli/helper.rb +1 -0
  12. data/lib/jenkins_api_client/client.rb +162 -69
  13. data/lib/jenkins_api_client/exceptions.rb +26 -17
  14. data/lib/jenkins_api_client/job.rb +321 -75
  15. data/lib/jenkins_api_client/node.rb +22 -10
  16. data/lib/jenkins_api_client/plugin_manager.rb +460 -0
  17. data/lib/jenkins_api_client/urihelper.rb +17 -0
  18. data/lib/jenkins_api_client/user.rb +4 -2
  19. data/lib/jenkins_api_client/version.rb +3 -3
  20. data/lib/jenkins_api_client/view.rb +10 -7
  21. data/lib/jenkins_api_client.rb +1 -0
  22. data/scripts/login_with_pry.rb +54 -0
  23. data/spec/func_tests/client_spec.rb +3 -3
  24. data/spec/func_tests/job_spec.rb +90 -7
  25. data/spec/func_tests/{node_spec.rb → node_spec.rb.pending} +9 -9
  26. data/spec/func_tests/plugin_spec.rb +148 -0
  27. data/spec/unit_tests/client_spec.rb +108 -27
  28. data/spec/unit_tests/fake_http_response.rb +9 -0
  29. data/spec/unit_tests/fixtures/files/available_plugins.json +1 -0
  30. data/spec/unit_tests/fixtures/files/installed_plugins.json +1 -0
  31. data/spec/unit_tests/fixtures/files/updatable_plugins.json +1 -0
  32. data/spec/unit_tests/job_spec.rb +109 -6
  33. data/spec/unit_tests/node_spec.rb +18 -6
  34. data/spec/unit_tests/plugin_spec.rb +165 -0
  35. data/spec/unit_tests/spec_helper.rb +11 -1
  36. data/spec/unit_tests/system_spec.rb +2 -1
  37. data/spec/unit_tests/user_spec.rb +1 -1
  38. data/travis/hudson.model.UpdateCenter.xml +7 -0
  39. data/travis/setup.sh +2 -1
  40. metadata +76 -64
@@ -0,0 +1,165 @@
1
+ require File.expand_path('../spec_helper', __FILE__)
2
+ require "json"
3
+
4
+ describe JenkinsApi::Client::PluginManager do
5
+ context "With properly initialized Client" do
6
+ before do
7
+ mock_logger = Logger.new "/dev/null"
8
+ @client = double
9
+ @client.should_receive(:logger).and_return(mock_logger)
10
+ @plugin = JenkinsApi::Client::PluginManager.new(@client)
11
+ @installed_plugins = load_json_from_fixture("installed_plugins.json")
12
+ @available_plugins = load_json_from_fixture("available_plugins.json")
13
+ @updatable_plugins = load_json_from_fixture("updatable_plugins.json")
14
+ end
15
+
16
+ describe "InstanceMethods" do
17
+ describe "#initialize" do
18
+ it "initializes by receiving an instane of client object" do
19
+ mock_logger = Logger.new "/dev/null"
20
+ @client.should_receive(:logger).and_return(mock_logger)
21
+ expect(
22
+ lambda { JenkinsApi::Client::PluginManager.new(@client) }
23
+ ).not_to raise_error
24
+ end
25
+ end
26
+
27
+ describe "#list_installed" do
28
+ it "lists all installed plugins in jenkins" do
29
+ @client.should_receive(:api_get_request).
30
+ with("/pluginManager", "tree=plugins[shortName,version]").
31
+ and_return(@installed_plugins)
32
+ plugins = @plugin.list_installed
33
+ plugins.class.should == Hash
34
+ plugins.size.should == @installed_plugins["plugins"].size
35
+ end
36
+ supported_filters = [
37
+ :active, :bundled, :deleted, :downgradable, :enabled,
38
+ :hasUpdate, :pinned
39
+ ]
40
+ supported_filters.each do |filter|
41
+ it "lists all installed plugins matching filter '#{filter}'" do
42
+ @client.should_receive(:api_get_request).
43
+ with("/pluginManager",
44
+ "tree=plugins[shortName,version,#{filter}]"
45
+ ).and_return(@installed_plugins)
46
+ @plugin.list_installed(filter => true).class.should == Hash
47
+ end
48
+ end
49
+ it "lists all installed plugins matching multiple filters" do
50
+ @client.should_receive(:api_get_request).
51
+ with("/pluginManager",
52
+ "tree=plugins[shortName,version,bundled,deleted]").
53
+ and_return(@installed_plugins)
54
+ @plugin.list_installed(:bundled => true, :deleted => true).class.
55
+ should == Hash
56
+ end
57
+ it "raises an error if unsupported filter is specified" do
58
+ expect(
59
+ lambda { @plugin.list_installed(:unsupported => true) }
60
+ ).to raise_error(ArgumentError)
61
+ end
62
+ end
63
+
64
+ describe "#list_available" do
65
+ it "lists all available plugins in jenkins update center" do
66
+ @client.should_receive(:api_get_request).
67
+ with("/updateCenter/coreSource", "tree=availables[name,version]").
68
+ and_return(@available_plugins)
69
+ @plugin.list_available.class.should == Hash
70
+ end
71
+ end
72
+
73
+ describe "#list_updates" do
74
+ it "lists all available plugin updates in jenkins update center" do
75
+ @client.should_receive(:api_get_request).
76
+ with("/updateCenter/coreSource", "tree=updates[name,version]").
77
+ and_return(@updatable_plugins)
78
+ @plugin.list_updates.class.should == Hash
79
+ end
80
+ end
81
+
82
+ describe "#install" do
83
+ it "installs a single plugin given as a string" do
84
+ @client.should_receive(:api_post_request).
85
+ with("/pluginManager/install",
86
+ {"plugin.awesome-plugin.default" => "on"}
87
+ )
88
+ @plugin.install("awesome-plugin")
89
+ end
90
+ it "installs multiple plugins given as an array" do
91
+ @client.should_receive(:api_post_request).
92
+ with("/pluginManager/install",
93
+ {
94
+ "plugin.awesome-plugin-1.default" => "on",
95
+ "plugin.awesome-plugin-2.default" => "on",
96
+ "plugin.awesome-plugin-3.default" => "on"
97
+ }
98
+ )
99
+ @plugin.install([
100
+ "awesome-plugin-1",
101
+ "awesome-plugin-2",
102
+ "awesome-plugin-3"
103
+ ])
104
+ end
105
+ end
106
+
107
+ describe "#uninstall" do
108
+ it "uninstalls a single plugin given as a string" do
109
+ @client.should_receive(:api_post_request).
110
+ with("/pluginManager/plugin/awesome-plugin/doUninstall")
111
+ @plugin.uninstall("awesome-plugin")
112
+ end
113
+ it "uninstalls multiple plugins given as array" do
114
+ plugins = ["awesome-plugin-1", "awesome-plugin-2", "awesome-plugin-3"]
115
+ plugins.each do |plugin|
116
+ @client.should_receive(:api_post_request).
117
+ with("/pluginManager/plugin/#{plugin}/doUninstall")
118
+ end
119
+ @plugin.uninstall(plugins)
120
+ end
121
+ end
122
+
123
+ describe "#enable" do
124
+ it "enables a single plugin given as a string" do
125
+ @client.should_receive(:api_post_request).
126
+ with("/pluginManager/plugin/awesome-plugin/makeEnabled")
127
+ @plugin.enable("awesome-plugin")
128
+ end
129
+ it "enables multiple plugins given as array" do
130
+ plugins = ["awesome-plugin-1", "awesome-plugin-2", "awesome-plugin-3"]
131
+ plugins.each do |plugin|
132
+ @client.should_receive(:api_post_request).
133
+ with("/pluginManager/plugin/#{plugin}/makeEnabled")
134
+ end
135
+ @plugin.enable(plugins)
136
+ end
137
+ end
138
+
139
+ describe "#disable" do
140
+ it "disables a single plugin given as a string" do
141
+ @client.should_receive(:api_post_request).
142
+ with("/pluginManager/plugin/awesome-plugin/makeDisabled")
143
+ @plugin.disable("awesome-plugin")
144
+ end
145
+ it "disabless multiple plugins given as array" do
146
+ plugins = ["awesome-plugin-1", "awesome-plugin-2", "awesome-plugin-3"]
147
+ plugins.each do |plugin|
148
+ @client.should_receive(:api_post_request).
149
+ with("/pluginManager/plugin/#{plugin}/makeDisabled")
150
+ end
151
+ @plugin.disable(plugins)
152
+ end
153
+ end
154
+
155
+ describe "#restart_required?" do
156
+ it "checks if restart is required after plugin install/uninstall" do
157
+ @client.should_receive(:api_get_request).
158
+ with("/updateCenter", "tree=restartRequiredForCompletion").
159
+ and_return({"restartRequiredForCompletion" => true})
160
+ @plugin.restart_required?.should == true
161
+ end
162
+ end
163
+ end
164
+ end
165
+ end
@@ -1,9 +1,19 @@
1
1
  require File.expand_path('../../../lib/jenkins_api_client', __FILE__)
2
2
  require 'logger'
3
+ require 'json'
3
4
 
4
5
  RSpec.configure do |config|
5
6
  config.before(:each) do
6
7
  end
7
-
8
8
  end
9
9
 
10
+ def load_json_from_fixture(file_name)
11
+ JSON.load(
12
+ File.read(
13
+ File.expand_path(
14
+ "../fixtures/files/#{file_name}",
15
+ __FILE__
16
+ )
17
+ )
18
+ )
19
+ end
@@ -1,4 +1,5 @@
1
1
  require File.expand_path('../spec_helper', __FILE__)
2
+ require File.expand_path('../fake_http_response', __FILE__)
2
3
 
3
4
  describe JenkinsApi::Client::System do
4
5
  context "With properly initialized Client" do
@@ -65,7 +66,7 @@ describe JenkinsApi::Client::System do
65
66
 
66
67
  describe "#wait_for_ready" do
67
68
  it "exits if the response body doesn't have the wait message" do
68
- @client.should_receive(:get_root).and_return(Net::HTTP.get_response(URI('http://example.com/index.html')))
69
+ @client.should_receive(:get_root).and_return(FakeResponse.new)
69
70
  @system.wait_for_ready
70
71
  end
71
72
  end
@@ -85,7 +85,7 @@ __USERLIST
85
85
  @client.should_receive(:logger).and_return(mock_logger)
86
86
  @client.should_receive(:timeout).and_return(mock_timeout)
87
87
  @client.stub(:api_get_request).with('/asynchPeople').and_return(PEOPLE_JSON)
88
- @client.stub(:api_get_request).with('/user/Fred Flintstone').and_return(FRED_JSON)
88
+ @client.stub(:api_get_request).with('/user/Fred%20Flintstone').and_return(FRED_JSON)
89
89
  @client.stub(:api_get_request).with('/user/fred').and_return(FRED_JSON)
90
90
  @client.stub(:api_get_request).with('/user/wilma').and_return(WILMA_JSON)
91
91
  @user = JenkinsApi::Client::User.new(@client)
@@ -0,0 +1,7 @@
1
+ <?xml version='1.0' encoding='UTF-8'?>
2
+ <sites>
3
+ <site>
4
+ <id>default</id>
5
+ <url>http://updates.jenkins-ci.org/update-center.json</url>
6
+ </site>
7
+ </sites>
data/travis/setup.sh CHANGED
@@ -1,4 +1,4 @@
1
- #!/bin/bash
1
+ #!/bin/bash -x
2
2
 
3
3
  # Install Jenkins
4
4
  wget -q -O - http://pkg.jenkins-ci.org/debian/jenkins-ci.org.key | sudo apt-key add -
@@ -9,6 +9,7 @@ sudo apt-get install -qq jenkins
9
9
  # Configure Jenkins
10
10
  sudo service jenkins stop
11
11
  sudo cp -f travis/jenkins_config.xml /var/lib/jenkins/config.xml
12
+ sudo cp -f travis/hudson.model.UpdateCenter.xml /var/lib/jenkins/hudson.model.UpdateCenter.xml
12
13
  sudo mkdir -p /var/lib/jenkins/users/testuser
13
14
  sudo cp -f travis/user_config.xml /var/lib/jenkins/users/testuser/config.xml
14
15
  sudo service jenkins start
metadata CHANGED
@@ -1,181 +1,187 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jenkins_api_client
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.14.1
5
- prerelease:
4
+ version: 1.0.0
6
5
  platform: ruby
7
6
  authors:
8
7
  - Kannan Manickam
9
8
  autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2013-08-19 00:00:00.000000000 Z
11
+ date: 2014-06-23 00:00:00.000000000 Z
13
12
  dependencies:
14
13
  - !ruby/object:Gem::Dependency
15
14
  name: nokogiri
16
15
  requirement: !ruby/object:Gem::Requirement
17
- none: false
18
16
  requirements:
19
- - - ~>
17
+ - - "~>"
20
18
  - !ruby/object:Gem::Version
21
- version: 1.5.0
19
+ version: 1.6.0
22
20
  type: :runtime
23
21
  prerelease: false
24
22
  version_requirements: !ruby/object:Gem::Requirement
25
- none: false
26
23
  requirements:
27
- - - ~>
24
+ - - "~>"
28
25
  - !ruby/object:Gem::Version
29
- version: 1.5.0
26
+ version: 1.6.0
30
27
  - !ruby/object:Gem::Dependency
31
28
  name: thor
32
29
  requirement: !ruby/object:Gem::Requirement
33
- none: false
34
30
  requirements:
35
- - - ! '>='
31
+ - - ">="
36
32
  - !ruby/object:Gem::Version
37
33
  version: 0.16.0
38
34
  type: :runtime
39
35
  prerelease: false
40
36
  version_requirements: !ruby/object:Gem::Requirement
41
- none: false
42
37
  requirements:
43
- - - ! '>='
38
+ - - ">="
44
39
  - !ruby/object:Gem::Version
45
40
  version: 0.16.0
46
41
  - !ruby/object:Gem::Dependency
47
42
  name: json
48
43
  requirement: !ruby/object:Gem::Requirement
49
- none: false
50
44
  requirements:
51
- - - ! '>='
45
+ - - ">="
52
46
  - !ruby/object:Gem::Version
53
47
  version: '0'
54
48
  type: :runtime
55
49
  prerelease: false
56
50
  version_requirements: !ruby/object:Gem::Requirement
57
- none: false
58
51
  requirements:
59
- - - ! '>='
52
+ - - ">="
60
53
  - !ruby/object:Gem::Version
61
54
  version: '0'
62
55
  - !ruby/object:Gem::Dependency
63
56
  name: terminal-table
64
57
  requirement: !ruby/object:Gem::Requirement
65
- none: false
66
58
  requirements:
67
- - - ! '>='
59
+ - - ">="
68
60
  - !ruby/object:Gem::Version
69
61
  version: 1.4.0
70
62
  type: :runtime
71
63
  prerelease: false
72
64
  version_requirements: !ruby/object:Gem::Requirement
73
- none: false
74
65
  requirements:
75
- - - ! '>='
66
+ - - ">="
76
67
  - !ruby/object:Gem::Version
77
68
  version: 1.4.0
78
69
  - !ruby/object:Gem::Dependency
79
70
  name: mixlib-shellout
80
71
  requirement: !ruby/object:Gem::Requirement
81
- none: false
82
72
  requirements:
83
- - - ! '>='
73
+ - - ">="
84
74
  - !ruby/object:Gem::Version
85
75
  version: 1.1.0
86
76
  type: :runtime
87
77
  prerelease: false
88
78
  version_requirements: !ruby/object:Gem::Requirement
89
- none: false
90
79
  requirements:
91
- - - ! '>='
80
+ - - ">="
92
81
  - !ruby/object:Gem::Version
93
82
  version: 1.1.0
94
83
  - !ruby/object:Gem::Dependency
95
84
  name: bundler
96
85
  requirement: !ruby/object:Gem::Requirement
97
- none: false
98
86
  requirements:
99
- - - ! '>='
87
+ - - ">="
100
88
  - !ruby/object:Gem::Version
101
89
  version: '1.0'
102
90
  type: :development
103
91
  prerelease: false
104
92
  version_requirements: !ruby/object:Gem::Requirement
105
- none: false
106
93
  requirements:
107
- - - ! '>='
94
+ - - ">="
108
95
  - !ruby/object:Gem::Version
109
96
  version: '1.0'
110
97
  - !ruby/object:Gem::Dependency
111
98
  name: jeweler
112
99
  requirement: !ruby/object:Gem::Requirement
113
- none: false
114
100
  requirements:
115
- - - ! '>='
101
+ - - ">="
116
102
  - !ruby/object:Gem::Version
117
103
  version: 1.6.4
118
104
  type: :development
119
105
  prerelease: false
120
106
  version_requirements: !ruby/object:Gem::Requirement
121
- none: false
122
107
  requirements:
123
- - - ! '>='
108
+ - - ">="
124
109
  - !ruby/object:Gem::Version
125
110
  version: 1.6.4
126
111
  - !ruby/object:Gem::Dependency
127
112
  name: rspec
128
113
  requirement: !ruby/object:Gem::Requirement
129
- none: false
130
114
  requirements:
131
- - - ~>
115
+ - - "~>"
132
116
  - !ruby/object:Gem::Version
133
- version: 2.13.0
117
+ version: 2.14.1
134
118
  type: :development
135
119
  prerelease: false
136
120
  version_requirements: !ruby/object:Gem::Requirement
137
- none: false
138
121
  requirements:
139
- - - ~>
122
+ - - "~>"
140
123
  - !ruby/object:Gem::Version
141
- version: 2.13.0
124
+ version: 2.14.1
142
125
  - !ruby/object:Gem::Dependency
143
126
  name: simplecov
144
127
  requirement: !ruby/object:Gem::Requirement
145
- none: false
146
128
  requirements:
147
- - - ! '>='
129
+ - - ">="
148
130
  - !ruby/object:Gem::Version
149
131
  version: '0'
150
132
  type: :development
151
133
  prerelease: false
152
134
  version_requirements: !ruby/object:Gem::Requirement
153
- none: false
154
135
  requirements:
155
- - - ! '>='
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: yard-thor
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
156
151
  - !ruby/object:Gem::Version
157
152
  version: '0'
158
153
  - !ruby/object:Gem::Dependency
159
154
  name: yard
160
155
  requirement: !ruby/object:Gem::Requirement
161
- none: false
162
156
  requirements:
163
- - - ! '>='
157
+ - - ">="
158
+ - !ruby/object:Gem::Version
159
+ version: '0'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - ">="
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
167
+ - !ruby/object:Gem::Dependency
168
+ name: pry
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - ">="
164
172
  - !ruby/object:Gem::Version
165
173
  version: '0'
166
174
  type: :development
167
175
  prerelease: false
168
176
  version_requirements: !ruby/object:Gem::Requirement
169
- none: false
170
177
  requirements:
171
- - - ! '>='
178
+ - - ">="
172
179
  - !ruby/object:Gem::Version
173
180
  version: '0'
174
- description: ! '
181
+ description: |2-
175
182
 
176
183
  This is a simple and easy-to-use Jenkins Api client with features focused on
177
-
178
- automating Job configuration programaticaly and so forth'
184
+ automating Job configuration programaticaly and so forth
179
185
  email:
180
186
  - arangamani.kannan@gmail.com
181
187
  executables:
@@ -183,9 +189,9 @@ executables:
183
189
  extensions: []
184
190
  extra_rdoc_files: []
185
191
  files:
186
- - .gitignore
187
- - .jenkins.yml
188
- - .travis.yml
192
+ - ".gitignore"
193
+ - ".jenkins.yml"
194
+ - ".travis.yml"
189
195
  - CHANGELOG.md
190
196
  - CONTRIBUTORS.md
191
197
  - Gemfile
@@ -209,28 +215,38 @@ files:
209
215
  - lib/jenkins_api_client/exceptions.rb
210
216
  - lib/jenkins_api_client/job.rb
211
217
  - lib/jenkins_api_client/node.rb
218
+ - lib/jenkins_api_client/plugin_manager.rb
212
219
  - lib/jenkins_api_client/system.rb
220
+ - lib/jenkins_api_client/urihelper.rb
213
221
  - lib/jenkins_api_client/user.rb
214
222
  - lib/jenkins_api_client/version.rb
215
223
  - lib/jenkins_api_client/view.rb
216
224
  - scripts/login_with_irb.rb
225
+ - scripts/login_with_pry.rb
217
226
  - spec/func_tests/client_spec.rb
218
227
  - spec/func_tests/job_spec.rb
219
- - spec/func_tests/node_spec.rb
228
+ - spec/func_tests/node_spec.rb.pending
229
+ - spec/func_tests/plugin_spec.rb
220
230
  - spec/func_tests/spec_helper.rb
221
231
  - spec/func_tests/system_spec.rb
222
232
  - spec/func_tests/user_spec.rb
223
233
  - spec/func_tests/view_spec.rb
224
234
  - spec/unit_tests/build_queue_spec.rb
225
235
  - spec/unit_tests/client_spec.rb
236
+ - spec/unit_tests/fake_http_response.rb
237
+ - spec/unit_tests/fixtures/files/available_plugins.json
226
238
  - spec/unit_tests/fixtures/files/computer_sample.xml
239
+ - spec/unit_tests/fixtures/files/installed_plugins.json
227
240
  - spec/unit_tests/fixtures/files/job_sample.xml
241
+ - spec/unit_tests/fixtures/files/updatable_plugins.json
228
242
  - spec/unit_tests/job_spec.rb
229
243
  - spec/unit_tests/node_spec.rb
244
+ - spec/unit_tests/plugin_spec.rb
230
245
  - spec/unit_tests/spec_helper.rb
231
246
  - spec/unit_tests/system_spec.rb
232
247
  - spec/unit_tests/user_spec.rb
233
248
  - spec/unit_tests/view_spec.rb
249
+ - travis/hudson.model.UpdateCenter.xml
234
250
  - travis/jenkins_config.xml
235
251
  - travis/jenkins_config_with_crumb.xml
236
252
  - travis/setup.sh
@@ -239,29 +255,25 @@ files:
239
255
  - travis/user_config.xml
240
256
  homepage: https://github.com/arangamani/jenkins_api_client
241
257
  licenses: []
258
+ metadata: {}
242
259
  post_install_message:
243
260
  rdoc_options: []
244
261
  require_paths:
245
262
  - lib
246
263
  required_ruby_version: !ruby/object:Gem::Requirement
247
- none: false
248
264
  requirements:
249
- - - ! '>='
265
+ - - ">="
250
266
  - !ruby/object:Gem::Version
251
- version: '0'
252
- segments:
253
- - 0
254
- hash: 2979339971375720651
267
+ version: 1.9.2
255
268
  required_rubygems_version: !ruby/object:Gem::Requirement
256
- none: false
257
269
  requirements:
258
- - - ! '>='
270
+ - - ">="
259
271
  - !ruby/object:Gem::Version
260
272
  version: '0'
261
273
  requirements: []
262
274
  rubyforge_project:
263
- rubygems_version: 1.8.23
275
+ rubygems_version: 2.2.2
264
276
  signing_key:
265
- specification_version: 3
277
+ specification_version: 4
266
278
  summary: Jenkins JSON API Client
267
279
  test_files: []