rscm 0.2.1.1404 → 0.3.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 (50) hide show
  1. data/README +34 -23
  2. data/Rakefile +24 -29
  3. data/bin/touch.exe +0 -0
  4. data/lib/rscm.rb +6 -3
  5. data/lib/rscm/annotations.rb +26 -7
  6. data/lib/rscm/{abstract_scm.rb → base.rb} +109 -71
  7. data/lib/rscm/better.rb +16 -0
  8. data/lib/rscm/logging.rb +11 -5
  9. data/lib/rscm/path_converter.rb +9 -16
  10. data/lib/rscm/revision.rb +201 -0
  11. data/lib/rscm/revision_file.rb +71 -0
  12. data/lib/rscm/scm/clearcase.rb +7 -7
  13. data/lib/rscm/scm/cvs.rb +69 -70
  14. data/lib/rscm/scm/cvs_log_parser.rb +29 -29
  15. data/lib/rscm/scm/darcs.rb +82 -34
  16. data/lib/rscm/scm/darcs_log_parser.rb +65 -0
  17. data/lib/rscm/scm/monotone.rb +249 -77
  18. data/lib/rscm/scm/monotone_log_parser.rb +57 -43
  19. data/lib/rscm/scm/mooky.rb +3 -3
  20. data/lib/rscm/scm/perforce.rb +196 -134
  21. data/lib/rscm/scm/star_team.rb +10 -10
  22. data/lib/rscm/scm/subversion.rb +106 -77
  23. data/lib/rscm/scm/subversion_log_parser.rb +76 -47
  24. data/lib/rscm/time_ext.rb +2 -116
  25. data/test/rscm/annotations_test.rb +15 -2
  26. data/test/rscm/{abstract_scm_test.rb → base_test.rb} +3 -3
  27. data/test/rscm/difftool_test.rb +9 -3
  28. data/test/rscm/generic_scm_tests.rb +195 -124
  29. data/test/rscm/revision_fixture.rb +20 -0
  30. data/test/rscm/revision_test.rb +129 -0
  31. data/test/rscm/{changesets.yaml → revisions.yaml} +10 -10
  32. data/test/rscm/scm/clearcase.log +608 -0
  33. data/test/rscm/scm/clearcase_test.rb +39 -0
  34. data/test/rscm/scm/cvs_log_parser_test.rb +73 -73
  35. data/test/rscm/scm/cvs_test.rb +1 -1
  36. data/test/rscm/scm/darcs_log_parser_test.rb +171 -0
  37. data/test/rscm/scm/monotone_log_parser_test.rb +49 -31
  38. data/test/rscm/scm/monotone_test.rb +3 -2
  39. data/test/rscm/scm/p4client_test.rb +33 -0
  40. data/test/rscm/scm/perforce_test.rb +25 -3
  41. data/test/rscm/scm/star_team.rb +9 -9
  42. data/test/rscm/scm/subversion_log_parser_test.rb +107 -47
  43. metadata +17 -13
  44. data/lib/multipart.rb +0 -95
  45. data/lib/rscm/RSS.txt +0 -41
  46. data/lib/rscm/changes.rb +0 -268
  47. data/lib/rscm/example.yaml +0 -21
  48. data/lib/rubyforge_file_publisher.rb +0 -176
  49. data/test/rscm/changes_fixture.rb +0 -20
  50. data/test/rscm/changes_test.rb +0 -129
@@ -0,0 +1,39 @@
1
+ require 'test/unit'
2
+ require 'rscm/generic_scm_tests'
3
+ require 'rscm/clearcase/clearcase'
4
+
5
+ module RSCM
6
+ class ClearCaseTest < Test::Unit::TestCase
7
+
8
+ def setup
9
+ @checkout_dir = "C:\\ClearCase_Storage\\viewroot\\icah_CorpAsstPlan_integration\\merchandising\\MerchandisingRandD"
10
+ end
11
+
12
+ def test_revisions
13
+ scm = ClearCase.new
14
+ revisions = scm.revisions(@checkout_dir, Time.utc(2005,03,03,0,0,0))
15
+ end
16
+
17
+ def test_checkout
18
+ # delete some local files (so we get some checkouts!)
19
+ build_xml = "build.xml"
20
+ actions_xml = "JavaSource/actions.xml"
21
+ File.delete("#{checkout_dir}/#{build_xml}") if File.exist?("#{checkout_dir}/#{build_xml}")
22
+ File.delete("#{checkout_dir}/#{actions_xml}") if File.exist?("#{checkout_dir}/#{actions_xml}")
23
+
24
+ scm = ClearCase.new
25
+
26
+ assert(!scm.uptodate?(@checkout_dir, Time.new.utc))
27
+ assert(!scm.uptodate?(@checkout_dir, Time.new.utc))
28
+
29
+ yielded_files = []
30
+ files = scm.checkout(@checkout_dir) do |file_name|
31
+ yielded_files << file_name
32
+ end
33
+
34
+ assert_equal(files, yielded_files)
35
+ assert_equal([build_xml, actions_xml], files)
36
+ end
37
+
38
+ end
39
+ end
@@ -22,14 +22,14 @@ module RSCM
22
22
  assert_equal(nil, CvsLogParser.new(StringIO.new("")).next_log_entry)
23
23
  end
24
24
 
25
- def test_parses_entire_log_into_changesets
25
+ def test_parses_entire_log_into_revisions
26
26
  File.open(File.dirname(__FILE__) + "/cvs-test.log") do |io|
27
27
  @parser = CvsLogParser.new(io)
28
- changesets = @parser.parse_changesets
28
+ revisions = @parser.parse_revisions
29
29
 
30
- assert_equal(24, changesets.length)
31
- assert_match(/o YAML config \(BuildBootstrapper\)/, changesets[1].message)
32
- assert_match(/failure/, changesets[8].message)
30
+ assert_equal(24, revisions.length)
31
+ assert_match(/o YAML config \(BuildBootstrapper\)/, revisions[1].message)
32
+ assert_match(/failure/, revisions[8].message)
33
33
  end
34
34
  end
35
35
 
@@ -37,35 +37,35 @@ module RSCM
37
37
  def test_jira_dc_312
38
38
  File.open(File.dirname(__FILE__) + "/cvs-dataforge.log") do |io|
39
39
  @parser = CvsLogParser.new(io)
40
- changesets = @parser.parse_changesets
40
+ revisions = @parser.parse_revisions
41
41
 
42
- assert_equal(271, changesets.length)
42
+ assert_equal(271, revisions.length)
43
43
  end
44
44
  end
45
45
 
46
- def test_parse_changes
47
- changesets = ChangeSets.new
48
- @parser.parse_changes(LOG_ENTRY, changesets)
49
- changesets.sort!
50
- assert_equal(4, changesets.length)
51
- assert_equal("src/ruby/damagecontrol/BuildExecutorTest.rb", changesets[0][0].path)
52
- assert_match(/linux-windows galore/, changesets[1][0].message)
46
+ def test_parse_files
47
+ revisions = Revisions.new
48
+ @parser.parse_files(LOG_ENTRY, revisions)
49
+ revisions.sort!
50
+ assert_equal(4, revisions.length)
51
+ assert_equal("src/ruby/damagecontrol/BuildExecutorTest.rb", revisions[0][0].path)
52
+ assert_match(/linux-windows galore/, revisions[1][0].message)
53
53
  end
54
54
 
55
- def test_sets_previous_revision_to_one_before_the_current
56
- change = @parser.parse_change(CHANGE_ENTRY)
57
- assert_equal("1.20", change.revision)
58
- assert_equal("1.19", change.previous_revision)
55
+ def test_sets_previous_native_revision_identifier_to_one_before_the_current
56
+ change = @parser.parse_file(CHANGE_ENTRY)
57
+ assert_equal("1.20", change.native_revision_identifier)
58
+ assert_equal("1.19", change.previous_native_revision_identifier)
59
59
  end
60
60
 
61
- def test_can_determine_previous_revisions_from_tricky_input
62
- assert_equal("2.2.1.1", @parser.determine_previous_revision("2.2.1.2"))
63
- assert_equal(nil, @parser.determine_previous_revision("2.2.1.1"))
61
+ def test_can_determine_previous_native_revision_identifiers_from_tricky_input
62
+ assert_equal("2.2.1.1", @parser.determine_previous_native_revision_identifier("2.2.1.2"))
63
+ assert_equal(nil, @parser.determine_previous_native_revision_identifier("2.2.1.1"))
64
64
  end
65
65
 
66
- def test_parse_change
67
- change = @parser.parse_change(CHANGE_ENTRY)
68
- assert_equal("1.20", change.revision)
66
+ def test_parse_file
67
+ change = @parser.parse_file(CHANGE_ENTRY)
68
+ assert_equal("1.20", change.native_revision_identifier)
69
69
  assert_equal(Time.utc(2003,11,9,17,53,37), change.time)
70
70
  assert_equal("tirsen", change.developer)
71
71
  assert_match(/Quiet period is configurable for each project/, change.message)
@@ -100,20 +100,20 @@ remove username check (doesn't work on beaver)
100
100
  I do really want to see the url in irc, it's very, very convenient. thank you very much ;-)
101
101
  EOF
102
102
 
103
- def test_can_parse_changes_with_deleted_file
104
- changesets = ChangeSets.new
105
- @parser.parse_changes(LOG_ENTRY_WITH_DELETED_FILE, changesets)
106
- assert_equal(1, changesets.length)
107
- assert_equal("server/damagecontrol/codehaus.rb", changesets[0][0].path)
108
- assert_equal(Change::DELETED, changesets[0][0].status)
103
+ def test_can_parse_files_with_deleted_file
104
+ revisions = Revisions.new
105
+ @parser.parse_files(LOG_ENTRY_WITH_DELETED_FILE, revisions)
106
+ assert_equal(1, revisions.length)
107
+ assert_equal("server/damagecontrol/codehaus.rb", revisions[0][0].path)
108
+ assert_equal(RevisionFile::DELETED, revisions[0][0].status)
109
109
  end
110
110
 
111
111
  def test_log_from_e2e_test
112
112
  @parser = CvsLogParser.new(StringIO.new(LOG_FROM_E2E_TEST))
113
- changesets = @parser.parse_changesets
114
- assert_equal(2, changesets.length)
115
- assert_match(/foo/, changesets[1].message)
116
- assert_match(/bar/, changesets[0].message)
113
+ revisions = @parser.parse_revisions
114
+ assert_equal(2, revisions.length)
115
+ assert_match(/foo/, revisions[1].message)
116
+ assert_match(/bar/, revisions[0].message)
117
117
  end
118
118
 
119
119
  LOG_FROM_E2E_TEST = <<-EOF
@@ -197,17 +197,17 @@ EOF
197
197
  @parser = CvsLogParser.new(StringIO.new(LOG_FROM_05_07_2004_19_41))
198
198
  assert_equal(11, @parser.split_entries(LOG_FROM_05_07_2004_19_41).size)
199
199
  assert_equal("server/damagecontrol/scm/CVS.rb", @parser.parse_path(@parser.split_entries(LOG_FROM_05_07_2004_19_41)[0]))
200
- changesets = @parser.parse_changesets
200
+ revisions = @parser.parse_revisions
201
201
 
202
- assert_equal(10, changesets.length)
203
- expected_change = Change.new
202
+ assert_equal(10, revisions.length)
203
+ expected_change = RevisionFile.new
204
204
  expected_change.path = "server/damagecontrol/scm/CVS.rb"
205
205
  expected_change.developer = "tirsen"
206
206
  expected_change.message = "fixed some stuff in the log parser"
207
- expected_change.revision = "1.19"
207
+ expected_change.native_revision_identifier = "1.19"
208
208
  expected_change.time = Time.utc(2004, 7, 5, 9, 41, 51)
209
209
 
210
- assert_equal(expected_change, changesets[9][0])
210
+ assert_equal(expected_change, revisions[9][0])
211
211
  end
212
212
 
213
213
  LOG_FROM_05_07_2004_19_41 = <<-EOF
@@ -257,7 +257,7 @@ debugging cvs timestamps
257
257
  ----------------------------
258
258
  revision 1.12
259
259
  date: 2004/07/03 19:25:19; author: rinkrank; state: Exp; lines: +20 -3
260
- support for previous_revision in modifications
260
+ support for previous_native_revision_identifier in modifications
261
261
  ----------------------------
262
262
  revision 1.11
263
263
  date: 2004/07/02
@@ -359,30 +359,30 @@ EOF
359
359
 
360
360
  def test_can_parse_LOG_WITH_DELETIONS
361
361
  @parser = CvsLogParser.new(StringIO.new(LOG_WITH_DELETIONS))
362
- changesets = @parser.parse_changesets
363
- assert_equal(2, changesets.length)
364
-
365
- changeset_delete = changesets[1]
366
- # assert_equal("MAIN:rinkrank:20031013000454", changeset_delete.revision)
367
- assert_equal(Time.utc(2003,10,13,00,04,54,0), changeset_delete.time)
368
- assert_equal("Obsolete", changeset_delete.message)
369
- assert_equal("rinkrank", changeset_delete.developer)
370
- assert_equal(1, changeset_delete.length)
371
- assert_equal("build.xml", changeset_delete[0].path)
372
- assert_equal("1.11", changeset_delete[0].revision)
373
- assert_equal("1.10", changeset_delete[0].previous_revision)
374
- assert(Change::DELETED, changeset_delete[0].status)
375
-
376
- changeset_fix_url = changesets[0]
377
- # assert_equal("MAIN:rinkrank:20030725163239", changeset_fix_url.revision)
378
- assert_equal(Time.utc(2003,07,25,16,32,39,0), changeset_fix_url.time)
379
- assert_equal("fixed broken url (NANO-8)", changeset_fix_url.message)
380
- assert_equal("rinkrank", changeset_fix_url.developer)
381
- assert_equal(1, changeset_fix_url.length)
382
- assert_equal("build.xml", changeset_fix_url[0].path)
383
- assert_equal("1.10", changeset_fix_url[0].revision)
384
- assert_equal("1.9", changeset_fix_url[0].previous_revision)
385
- assert_equal(Change::MODIFIED, changeset_fix_url[0].status)
362
+ revisions = @parser.parse_revisions
363
+ assert_equal(2, revisions.length)
364
+
365
+ revision_delete = revisions[1]
366
+ # assert_equal("MAIN:rinkrank:20031013000454", revision_delete.revision)
367
+ assert_equal(Time.utc(2003,10,13,00,04,54,0), revision_delete.time)
368
+ assert_equal("Obsolete", revision_delete.message)
369
+ assert_equal("rinkrank", revision_delete.developer)
370
+ assert_equal(1, revision_delete.length)
371
+ assert_equal("build.xml", revision_delete[0].path)
372
+ assert_equal("1.11", revision_delete[0].native_revision_identifier)
373
+ assert_equal("1.10", revision_delete[0].previous_native_revision_identifier)
374
+ assert(RevisionFile::DELETED, revision_delete[0].status)
375
+
376
+ revision_fix_url = revisions[0]
377
+ # assert_equal("MAIN:rinkrank:20030725163239", revision_fix_url.revision)
378
+ assert_equal(Time.utc(2003,07,25,16,32,39,0), revision_fix_url.time)
379
+ assert_equal("fixed broken url (NANO-8)", revision_fix_url.message)
380
+ assert_equal("rinkrank", revision_fix_url.developer)
381
+ assert_equal(1, revision_fix_url.length)
382
+ assert_equal("build.xml", revision_fix_url[0].path)
383
+ assert_equal("1.10", revision_fix_url[0].native_revision_identifier)
384
+ assert_equal("1.9", revision_fix_url[0].previous_native_revision_identifier)
385
+ assert_equal(RevisionFile::MODIFIED, revision_fix_url[0].status)
386
386
  end
387
387
 
388
388
  LOG_WITH_MISSING_ENTRIES = <<EOF
@@ -415,8 +415,8 @@ EOF
415
415
 
416
416
  def test_can_parse_LOG_WITH_MISSING_ENTRIES
417
417
  @parser = CvsLogParser.new(StringIO.new(LOG_WITH_MISSING_ENTRIES))
418
- changesets = @parser.parse_changesets
419
- assert_equal(0, changesets.length)
418
+ revisions = @parser.parse_revisions
419
+ assert_equal(0, revisions.length)
420
420
  end
421
421
 
422
422
  LOG_WITH_NEW_AND_OLD_FILE = <<EOF
@@ -461,12 +461,12 @@ EOF
461
461
 
462
462
  def test_can_distinguish_new_file_from_old_file
463
463
  @parser = CvsLogParser.new(StringIO.new(LOG_WITH_NEW_AND_OLD_FILE))
464
- changesets = @parser.parse_changesets
464
+ revisions = @parser.parse_revisions
465
465
 
466
- assert_equal(Change::ADDED, changesets[0][0].status)
467
- assert_equal(Change::MODIFIED, changesets[1][0].status)
468
- assert_equal(Change::MODIFIED, changesets[2][0].status)
469
- assert_equal(Change::ADDED, changesets[3][0].status)
466
+ assert_equal(RevisionFile::ADDED, revisions[0][0].status)
467
+ assert_equal(RevisionFile::MODIFIED, revisions[1][0].status)
468
+ assert_equal(RevisionFile::MODIFIED, revisions[2][0].status)
469
+ assert_equal(RevisionFile::ADDED, revisions[3][0].status)
470
470
  end
471
471
 
472
472
  # https://sitemesh.dev.java.net/source/browse/sitemesh/.cvsignore
@@ -566,8 +566,8 @@ EOF
566
566
 
567
567
  def test_can_parse_logs_with_cvs_and_dashes_in_commit_message
568
568
  @parser = CvsLogParser.new(StringIO.new(LOG_WITH_WEIRD_CVS_AND_MANY_DASHES))
569
- changesets = @parser.parse_changesets
570
- assert_equal(6, changesets.length)
569
+ revisions = @parser.parse_revisions
570
+ assert_equal(6, revisions.length)
571
571
  end
572
572
 
573
573
  end
@@ -24,7 +24,7 @@ module RSCM
24
24
 
25
25
  def test_should_fail_on_bad_command
26
26
  assert_raise(RuntimeError) do
27
- Cvs.new("").create
27
+ Cvs.new("").create_central
28
28
  end
29
29
  end
30
30
 
@@ -0,0 +1,171 @@
1
+ require 'test/unit'
2
+ require 'stringio'
3
+ require 'rscm'
4
+
5
+ module RSCM
6
+ class DarcsLogParserTest < Test::Unit::TestCase
7
+
8
+ CHANGESET = <<EOF
9
+ <patch author='tester@test.net' date='20050327203534' local_date='Sun Mar 27 16:35:34 AST 2005' inverted='False' hash='20050327203534-4d520-baeeafb062e7f0d72ce740e1e1f5e6a203321ab4.gz'>
10
+ <name>something nice</name>
11
+ <comment>
12
+ imported
13
+ sources
14
+ </comment>
15
+ <summary>
16
+ <add_file>
17
+ build.xml
18
+ </add_file>
19
+ <add_file>
20
+ project.xml
21
+ </add_file>
22
+ <add_directory>
23
+ src
24
+ </add_directory>
25
+ <add_directory>
26
+ src/java
27
+ </add_directory>
28
+ <add_directory>
29
+ src/java/com
30
+ </add_directory>
31
+ <add_directory>
32
+ src/java/com/thoughtworks
33
+ </add_directory>
34
+ <add_directory>
35
+ src/java/com/thoughtworks/damagecontrolled
36
+ </add_directory>
37
+ <add_file>
38
+ src/java/com/thoughtworks/damagecontrolled/Thingy.java
39
+ </add_file>
40
+ <add_directory>
41
+ src/test
42
+ </add_directory>
43
+ <add_directory>
44
+ src/test/com
45
+ </add_directory>
46
+ <add_directory>
47
+ src/test/com/thoughtworks
48
+ </add_directory>
49
+ <add_directory>
50
+ src/test/com/thoughtworks/damagecontrolled
51
+ </add_directory>
52
+ <add_file>
53
+ src/test/com/thoughtworks/damagecontrolled/ThingyTestCase.java
54
+ </add_file>
55
+ </summary>
56
+ </patch>
57
+ EOF
58
+
59
+ def test_should_parse_CHANGESET_to_revision
60
+ parser = DarcsLogParser.new
61
+ revision = parser.parse_revision(StringIO.new(CHANGESET), {})
62
+
63
+ assert_equal("imported\nsources", revision.message)
64
+ assert_equal('20050327203534-4d520-baeeafb062e7f0d72ce740e1e1f5e6a203321ab4.gz', revision.identifier)
65
+ assert_equal("tester@test.net", revision.developer)
66
+ assert_equal(Time.utc(2005,3,27,20,35,34), revision.time)
67
+ assert_equal(4, revision.length)
68
+
69
+ assert_equal("build.xml", revision[0].path)
70
+ assert_equal(RevisionFile::ADDED, revision[0].status)
71
+
72
+ assert_equal("project.xml", revision[1].path)
73
+ assert_equal(RevisionFile::ADDED, revision[1].status)
74
+
75
+ assert_equal("src/java/com/thoughtworks/damagecontrolled/Thingy.java", revision[2].path)
76
+ assert_equal(RevisionFile::ADDED, revision[2].status)
77
+
78
+ assert_equal("src/test/com/thoughtworks/damagecontrolled/ThingyTestCase.java", revision[3].path)
79
+ assert_equal(RevisionFile::ADDED, revision[3].status)
80
+ end
81
+
82
+ CHANGESETS = <<EOF
83
+ <changelog>
84
+ <patch author='tester@test.net' date='20050327204037' local_date='Sun Mar 27 16:40:37 AST 2005' inverted='False' hash='20050327204037-8fc3f-49f20f511eff452541c19cf8d4b188342f226c6c.gz'>
85
+ <name>something nice</name>
86
+ <comment>
87
+ changed
88
+ something
89
+
90
+ </comment>
91
+ <summary>
92
+ <modify_file>
93
+ build.xml<removed_lines num='1'/><added_lines num='2'/>
94
+ </modify_file>
95
+ <modify_file>
96
+ src/java/com/thoughtworks/damagecontrolled/Thingy.java<removed_lines num='1'/><added_lines num='2'/>
97
+ </modify_file>
98
+ </summary>
99
+ </patch>
100
+ <patch author='tester@test.net' date='20050327203534' local_date='Sun Mar 27 16:35:34 AST 2005' inverted='False' hash='20050327203534-4d520-baeeafb062e7f0d72ce740e1e1f5e6a203321ab4.gz'>
101
+ <name>something nice</name>
102
+ <comment>
103
+ imported
104
+ sources
105
+ </comment>
106
+ <summary>
107
+ <add_file>
108
+ build.xml
109
+ </add_file>
110
+ <add_file>
111
+ project.xml
112
+ </add_file>
113
+ <add_directory>
114
+ src
115
+ </add_directory>
116
+ <add_directory>
117
+ src/java
118
+ </add_directory>
119
+ <add_directory>
120
+ src/java/com
121
+ </add_directory>
122
+ <add_directory>
123
+ src/java/com/thoughtworks
124
+ </add_directory>
125
+ <add_directory>
126
+ src/java/com/thoughtworks/damagecontrolled
127
+ </add_directory>
128
+ <add_file>
129
+ src/java/com/thoughtworks/damagecontrolled/Thingy.java
130
+ </add_file>
131
+ <add_directory>
132
+ src/test
133
+ </add_directory>
134
+ <add_directory>
135
+ src/test/com
136
+ </add_directory>
137
+ <add_directory>
138
+ src/test/com/thoughtworks
139
+ </add_directory>
140
+ <add_directory>
141
+ src/test/com/thoughtworks/damagecontrolled
142
+ </add_directory>
143
+ <add_file>
144
+ src/test/com/thoughtworks/damagecontrolled/ThingyTestCase.java
145
+ </add_file>
146
+ </summary>
147
+ </patch>
148
+ </changelog>
149
+ EOF
150
+
151
+ def test_should_parse_CHANGESETS_to_revisions
152
+ parser = DarcsLogParser.new
153
+ revisions = parser.parse_revisions(StringIO.new(CHANGESETS))
154
+ assert_equal(2, revisions.length)
155
+ revision = revisions[0]
156
+
157
+ assert_equal("build.xml", revision[0].path)
158
+ assert_equal(RevisionFile::MODIFIED, revision[0].status)
159
+
160
+ assert_equal("src/java/com/thoughtworks/damagecontrolled/Thingy.java", revision[1].path)
161
+ assert_equal(RevisionFile::MODIFIED, revision[1].status)
162
+ end
163
+
164
+ def test_should_parse_CHANGESET_to_revisions
165
+ parser = DarcsLogParser.new
166
+ revisions = parser.parse_revisions(StringIO.new(CHANGESET))
167
+ assert_equal(1, revisions.length)
168
+ end
169
+ end
170
+ end
171
+