berkeley_library-tind 0.4.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 (162) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/build.yml +18 -0
  3. data/.gitignore +388 -0
  4. data/.idea/inspectionProfiles/Project_Default.xml +20 -0
  5. data/.idea/misc.xml +4 -0
  6. data/.idea/modules.xml +8 -0
  7. data/.idea/tind.iml +138 -0
  8. data/.idea/vcs.xml +6 -0
  9. data/.rubocop.yml +334 -0
  10. data/.ruby-version +1 -0
  11. data/.simplecov +8 -0
  12. data/.yardopts +1 -0
  13. data/CHANGES.md +58 -0
  14. data/Dockerfile +57 -0
  15. data/Gemfile +3 -0
  16. data/Jenkinsfile +18 -0
  17. data/LICENSE.md +21 -0
  18. data/README.md +73 -0
  19. data/Rakefile +20 -0
  20. data/berkeley_library-tind.gemspec +50 -0
  21. data/bin/tind-export +14 -0
  22. data/docker-compose.yml +15 -0
  23. data/lib/berkeley_library/tind.rb +3 -0
  24. data/lib/berkeley_library/tind/api.rb +1 -0
  25. data/lib/berkeley_library/tind/api/api.rb +132 -0
  26. data/lib/berkeley_library/tind/api/api_exception.rb +131 -0
  27. data/lib/berkeley_library/tind/api/collection.rb +82 -0
  28. data/lib/berkeley_library/tind/api/date_range.rb +67 -0
  29. data/lib/berkeley_library/tind/api/format.rb +32 -0
  30. data/lib/berkeley_library/tind/api/search.rb +100 -0
  31. data/lib/berkeley_library/tind/config.rb +103 -0
  32. data/lib/berkeley_library/tind/export.rb +1 -0
  33. data/lib/berkeley_library/tind/export/column.rb +54 -0
  34. data/lib/berkeley_library/tind/export/column_group.rb +144 -0
  35. data/lib/berkeley_library/tind/export/column_group_list.rb +131 -0
  36. data/lib/berkeley_library/tind/export/column_width_calculator.rb +76 -0
  37. data/lib/berkeley_library/tind/export/config.rb +154 -0
  38. data/lib/berkeley_library/tind/export/csv_exporter.rb +29 -0
  39. data/lib/berkeley_library/tind/export/export.rb +47 -0
  40. data/lib/berkeley_library/tind/export/export_command.rb +168 -0
  41. data/lib/berkeley_library/tind/export/export_exception.rb +8 -0
  42. data/lib/berkeley_library/tind/export/export_format.rb +67 -0
  43. data/lib/berkeley_library/tind/export/exporter.rb +105 -0
  44. data/lib/berkeley_library/tind/export/filter.rb +52 -0
  45. data/lib/berkeley_library/tind/export/no_results_error.rb +7 -0
  46. data/lib/berkeley_library/tind/export/ods_exporter.rb +138 -0
  47. data/lib/berkeley_library/tind/export/row.rb +24 -0
  48. data/lib/berkeley_library/tind/export/row_metrics.rb +18 -0
  49. data/lib/berkeley_library/tind/export/table.rb +175 -0
  50. data/lib/berkeley_library/tind/export/table_metrics.rb +116 -0
  51. data/lib/berkeley_library/tind/marc.rb +1 -0
  52. data/lib/berkeley_library/tind/marc/xml_reader.rb +144 -0
  53. data/lib/berkeley_library/tind/module_info.rb +14 -0
  54. data/lib/berkeley_library/util/arrays.rb +178 -0
  55. data/lib/berkeley_library/util/logging.rb +1 -0
  56. data/lib/berkeley_library/util/ods/spreadsheet.rb +170 -0
  57. data/lib/berkeley_library/util/ods/xml/content_doc.rb +26 -0
  58. data/lib/berkeley_library/util/ods/xml/document_node.rb +57 -0
  59. data/lib/berkeley_library/util/ods/xml/element_node.rb +106 -0
  60. data/lib/berkeley_library/util/ods/xml/loext/table_protection.rb +26 -0
  61. data/lib/berkeley_library/util/ods/xml/manifest/file_entry.rb +42 -0
  62. data/lib/berkeley_library/util/ods/xml/manifest/manifest.rb +73 -0
  63. data/lib/berkeley_library/util/ods/xml/manifest_doc.rb +26 -0
  64. data/lib/berkeley_library/util/ods/xml/namespace.rb +46 -0
  65. data/lib/berkeley_library/util/ods/xml/office/automatic_styles.rb +181 -0
  66. data/lib/berkeley_library/util/ods/xml/office/body.rb +17 -0
  67. data/lib/berkeley_library/util/ods/xml/office/document_content.rb +98 -0
  68. data/lib/berkeley_library/util/ods/xml/office/document_styles.rb +39 -0
  69. data/lib/berkeley_library/util/ods/xml/office/font_face_decls.rb +30 -0
  70. data/lib/berkeley_library/util/ods/xml/office/scripts.rb +17 -0
  71. data/lib/berkeley_library/util/ods/xml/office/spreadsheet.rb +37 -0
  72. data/lib/berkeley_library/util/ods/xml/office/styles.rb +39 -0
  73. data/lib/berkeley_library/util/ods/xml/style/cell_style.rb +58 -0
  74. data/lib/berkeley_library/util/ods/xml/style/column_style.rb +36 -0
  75. data/lib/berkeley_library/util/ods/xml/style/default_style.rb +31 -0
  76. data/lib/berkeley_library/util/ods/xml/style/family.rb +85 -0
  77. data/lib/berkeley_library/util/ods/xml/style/font_face.rb +46 -0
  78. data/lib/berkeley_library/util/ods/xml/style/paragraph_properties.rb +30 -0
  79. data/lib/berkeley_library/util/ods/xml/style/row_style.rb +37 -0
  80. data/lib/berkeley_library/util/ods/xml/style/style.rb +44 -0
  81. data/lib/berkeley_library/util/ods/xml/style/table_cell_properties.rb +40 -0
  82. data/lib/berkeley_library/util/ods/xml/style/table_column_properties.rb +30 -0
  83. data/lib/berkeley_library/util/ods/xml/style/table_properties.rb +25 -0
  84. data/lib/berkeley_library/util/ods/xml/style/table_row_properties.rb +28 -0
  85. data/lib/berkeley_library/util/ods/xml/style/table_style.rb +27 -0
  86. data/lib/berkeley_library/util/ods/xml/style/text_properties.rb +52 -0
  87. data/lib/berkeley_library/util/ods/xml/styles_doc.rb +26 -0
  88. data/lib/berkeley_library/util/ods/xml/table/named_expressions.rb +17 -0
  89. data/lib/berkeley_library/util/ods/xml/table/repeatable.rb +38 -0
  90. data/lib/berkeley_library/util/ods/xml/table/table.rb +193 -0
  91. data/lib/berkeley_library/util/ods/xml/table/table_cell.rb +46 -0
  92. data/lib/berkeley_library/util/ods/xml/table/table_column.rb +43 -0
  93. data/lib/berkeley_library/util/ods/xml/table/table_row.rb +136 -0
  94. data/lib/berkeley_library/util/ods/xml/text/p.rb +118 -0
  95. data/lib/berkeley_library/util/paths.rb +111 -0
  96. data/lib/berkeley_library/util/stringios.rb +30 -0
  97. data/lib/berkeley_library/util/strings.rb +42 -0
  98. data/lib/berkeley_library/util/sys_exits.rb +15 -0
  99. data/lib/berkeley_library/util/times.rb +22 -0
  100. data/lib/berkeley_library/util/uris.rb +44 -0
  101. data/lib/berkeley_library/util/uris/appender.rb +162 -0
  102. data/lib/berkeley_library/util/uris/requester.rb +62 -0
  103. data/lib/berkeley_library/util/uris/validator.rb +32 -0
  104. data/rakelib/bundle.rake +8 -0
  105. data/rakelib/coverage.rake +11 -0
  106. data/rakelib/gem.rake +54 -0
  107. data/rakelib/rubocop.rake +18 -0
  108. data/rakelib/spec.rake +2 -0
  109. data/spec/.rubocop.yml +40 -0
  110. data/spec/berkeley_library/tind/api/api_exception_spec.rb +91 -0
  111. data/spec/berkeley_library/tind/api/api_spec.rb +143 -0
  112. data/spec/berkeley_library/tind/api/collection_spec.rb +74 -0
  113. data/spec/berkeley_library/tind/api/date_range_spec.rb +110 -0
  114. data/spec/berkeley_library/tind/api/format_spec.rb +54 -0
  115. data/spec/berkeley_library/tind/api/search_spec.rb +364 -0
  116. data/spec/berkeley_library/tind/config_spec.rb +86 -0
  117. data/spec/berkeley_library/tind/export/column_group_spec.rb +29 -0
  118. data/spec/berkeley_library/tind/export/column_spec.rb +43 -0
  119. data/spec/berkeley_library/tind/export/config_spec.rb +206 -0
  120. data/spec/berkeley_library/tind/export/export_command_spec.rb +169 -0
  121. data/spec/berkeley_library/tind/export/export_format_spec.rb +59 -0
  122. data/spec/berkeley_library/tind/export/export_matcher.rb +112 -0
  123. data/spec/berkeley_library/tind/export/export_spec.rb +150 -0
  124. data/spec/berkeley_library/tind/export/exporter_spec.rb +125 -0
  125. data/spec/berkeley_library/tind/export/row_spec.rb +118 -0
  126. data/spec/berkeley_library/tind/export/table_spec.rb +322 -0
  127. data/spec/berkeley_library/tind/marc/xml_reader_spec.rb +93 -0
  128. data/spec/berkeley_library/util/arrays_spec.rb +340 -0
  129. data/spec/berkeley_library/util/ods/spreadsheet_spec.rb +124 -0
  130. data/spec/berkeley_library/util/ods/xml/content_doc_spec.rb +121 -0
  131. data/spec/berkeley_library/util/ods/xml/manifest/file_entry_spec.rb +27 -0
  132. data/spec/berkeley_library/util/ods/xml/manifest/manifest_spec.rb +33 -0
  133. data/spec/berkeley_library/util/ods/xml/office/document_content_spec.rb +60 -0
  134. data/spec/berkeley_library/util/ods/xml/style/automatic_styles_spec.rb +37 -0
  135. data/spec/berkeley_library/util/ods/xml/style/family_spec.rb +57 -0
  136. data/spec/berkeley_library/util/ods/xml/table/table_row_spec.rb +179 -0
  137. data/spec/berkeley_library/util/ods/xml/table/table_spec.rb +218 -0
  138. data/spec/berkeley_library/util/paths_spec.rb +90 -0
  139. data/spec/berkeley_library/util/stringios_spec.rb +34 -0
  140. data/spec/berkeley_library/util/strings_spec.rb +27 -0
  141. data/spec/berkeley_library/util/times_spec.rb +39 -0
  142. data/spec/berkeley_library/util/uris_spec.rb +118 -0
  143. data/spec/data/collection-names.txt +438 -0
  144. data/spec/data/collections.json +4827 -0
  145. data/spec/data/disjoint-records.xml +187 -0
  146. data/spec/data/record-184453.xml +58 -0
  147. data/spec/data/record-184458.xml +63 -0
  148. data/spec/data/record-187888.xml +78 -0
  149. data/spec/data/records-api-search-cjk-p1.xml +6381 -0
  150. data/spec/data/records-api-search-cjk-p2.xml +5 -0
  151. data/spec/data/records-api-search-p1.xml +4506 -0
  152. data/spec/data/records-api-search-p2.xml +4509 -0
  153. data/spec/data/records-api-search-p3.xml +4506 -0
  154. data/spec/data/records-api-search-p4.xml +4509 -0
  155. data/spec/data/records-api-search-p5.xml +4506 -0
  156. data/spec/data/records-api-search-p6.xml +2436 -0
  157. data/spec/data/records-api-search-p7.xml +5 -0
  158. data/spec/data/records-api-search.xml +234 -0
  159. data/spec/data/records-manual-search.xml +547 -0
  160. data/spec/spec_helper.rb +30 -0
  161. data/test/profile/table_from_records_profile.rb +46 -0
  162. metadata +585 -0
@@ -0,0 +1,218 @@
1
+ require 'spec_helper'
2
+
3
+ module BerkeleyLibrary
4
+ module Util
5
+ module ODS
6
+ module XML
7
+ module Table
8
+ describe Table do
9
+ let(:content) { ContentDoc.new }
10
+ let(:doc_content) { content.document_content }
11
+ let(:table_name) { "Table for #{File.basename(__FILE__, '.rb')}" }
12
+
13
+ attr_reader :table
14
+
15
+ before(:each) do
16
+ @table = doc_content.add_table(table_name)
17
+ end
18
+
19
+ # TODO: move to some kind of helper & share w/table_spec
20
+ def doc_content_xml
21
+ xml_str = content.to_xml
22
+ # File.open("tmp/content-#{Time.now.to_i}.xml", 'wb') { |f| f.write(xml_str) }
23
+ rexml_doc = REXML::Document.new(xml_str)
24
+ rexml_doc.root
25
+ end
26
+
27
+ # TODO: move to some kind of helper & share w/table_spec
28
+ def find_table_xml(dc_xml = doc_content_xml, table_index = 1)
29
+ body = dc_xml.elements[4]
30
+ expect(body).to be_a(REXML::Element) # just to be sure
31
+
32
+ spreadsheet = body.elements[1]
33
+ expect(spreadsheet).to be_a(REXML::Element) # just to be sure
34
+
35
+ spreadsheet.elements[table_index]
36
+ end
37
+
38
+ describe :add_column do
39
+ attr_reader :header_str
40
+ attr_reader :width
41
+ attr_reader :col
42
+
43
+ before(:each) do
44
+ timestamp = Time.now.to_i
45
+ @header_str = "Header #{timestamp}"
46
+ @width = "#{timestamp % 10}.#{timestamp % 100}.in"
47
+ @col = table.add_column(header_str, width)
48
+ end
49
+
50
+ it 'adds a column' do
51
+ expect(col).to be_a(TableColumn)
52
+ end
53
+
54
+ it 'sets the header' do
55
+ expect(table.get_value_at(0, 0)).to eq(header_str)
56
+ end
57
+
58
+ it 'creates an associated style' do
59
+ styles = doc_content.automatic_styles
60
+ col_style = styles.find_column_style(width)
61
+ expect(col_style).to be_a(Style::ColumnStyle)
62
+
63
+ expect(col.column_style).to be(col_style)
64
+
65
+ dc_xml = doc_content_xml
66
+ styles_xml = dc_xml.elements[1, 'automatic-styles']
67
+ expect(styles_xml).to be_a(REXML::Element) # just to be sure
68
+
69
+ style_xml = styles_xml.elements.find do |elem|
70
+ elem.prefix == 'style' &&
71
+ elem.name == 'style' &&
72
+ elem['style:family'] == 'table-column' &&
73
+ elem['style:name'] == col_style.style_name
74
+ end
75
+ expect(style_xml).to be_a(REXML::Element)
76
+
77
+ props_xml = style_xml.elements[1]
78
+ expect(props_xml.prefix).to eq('style')
79
+ expect(props_xml.name).to eq('table-column-properties')
80
+ expect(props_xml['style:column-width']).to eq(width)
81
+ end
82
+
83
+ it 'writes the column to XML' do
84
+ col_style = doc_content.automatic_styles.find_column_style(width)
85
+
86
+ table_xml = find_table_xml
87
+ col_xml = table_xml.elements[1, 'table-column']
88
+ expect(col_xml).to be_a(REXML::Element)
89
+ expect(col_xml.prefix).to eq('table')
90
+ expect(col_xml['table:style-name']).to eq(col_style.style_name)
91
+ end
92
+
93
+ describe 'protected' do
94
+ attr_reader :pcol
95
+
96
+ before(:each) do
97
+ timestamp = Time.now.to_i
98
+ header_str = "Protected #{timestamp}"
99
+ @pcol = table.add_column(header_str, protected: true)
100
+ end
101
+
102
+ it 'adds a column' do
103
+ expect(pcol).to be_a(TableColumn)
104
+ end
105
+
106
+ it 'adds a protected cell style' do
107
+ col_style = pcol.column_style
108
+
109
+ default_cell_style = pcol.default_cell_style
110
+ expect(default_cell_style.protected?).to eq(true)
111
+
112
+ table_xml = find_table_xml
113
+ col_xml = table_xml.elements[2, 'table-column']
114
+ expect(col_xml['table:style-name']).to eq(col_style.style_name)
115
+ expect(col_xml['table:default-cell-style-name']).to eq(default_cell_style.style_name)
116
+ end
117
+ end
118
+ end
119
+
120
+ describe :to_xml do
121
+ let(:columns_added) { 15 }
122
+ let(:expected_dummy_cols) { Table::MIN_COLUMNS - columns_added }
123
+
124
+ describe 'column repeats' do
125
+
126
+ it 'adds dummy columns as needed' do
127
+ expected_dummy_cols = Table::MIN_COLUMNS - columns_added
128
+ columns_added.times { |col| table.add_column("Column #{col}", "1.#{col}in") }
129
+
130
+ table_xml = find_table_xml
131
+ columns_xml = table_xml.elements.select { |e| e.name == 'table-column' }
132
+ expect(columns_xml.size).to eq(columns_added + 1)
133
+
134
+ dummy_column_xml = columns_xml.last
135
+ expect(dummy_column_xml['table:number-columns-repeated']).to eq(expected_dummy_cols.to_s)
136
+
137
+ header_row_xml = table_xml.elements[1, 'table-row']
138
+ header_cells_xml = header_row_xml.elements.select { |e| e.name == 'table-cell' }
139
+ expect(header_cells_xml.size).to eq(columns_added + 1)
140
+
141
+ columns_added.times do |col|
142
+ expected_header = "Column #{col}"
143
+ expect(table.get_value_at(0, col)).to eq(expected_header) # just to be sure
144
+
145
+ header_cell_xml = header_cells_xml[col]
146
+ header_p = header_cell_xml.elements[1, 'p']
147
+ header_texts = header_p.texts
148
+ expect(header_texts.size).to eq(1)
149
+
150
+ expect(header_texts[0].value).to eq(expected_header)
151
+ end
152
+
153
+ dummy_cell_xml = header_cells_xml.last
154
+ expect(dummy_cell_xml['table:number-columns-repeated']).to eq(expected_dummy_cols.to_s)
155
+ end
156
+
157
+ it 'uses repeats for identical columns' do
158
+ columns_added.times { |col| table.add_column("Column #{col}") }
159
+
160
+ table_xml = find_table_xml
161
+ columns_xml = table_xml.elements.select { |e| e.name == 'table-column' }
162
+ expect(columns_xml.size).to eq(2)
163
+
164
+ repeated_column_xml = columns_xml[0]
165
+ expect(repeated_column_xml['table:number-columns-repeated']).to eq(columns_added.to_s)
166
+
167
+ dummy_column_xml = columns_xml.last
168
+ expect(dummy_column_xml['table:number-columns-repeated']).to eq(expected_dummy_cols.to_s)
169
+ end
170
+
171
+ end
172
+
173
+ describe 'dummy rows' do
174
+ let(:rows_added) { 15 }
175
+
176
+ it 'adds dummy rows as needed' do
177
+ columns_added.times { |col| table.add_column("Column #{col}") }
178
+ rows_added.times do |r|
179
+ row = table.add_row
180
+ columns_added.times do |c|
181
+ value = "value for (#{r + 1}, c)"
182
+ row.set_value_at(c, value)
183
+ end
184
+ end
185
+
186
+ columns_added.times do |c|
187
+ expect(table.get_value_at(0, c)).to eq("Column #{c}")
188
+ rows_added.times do |r|
189
+ value = "value for (#{r + 1}, c)" # skip header row
190
+ expect(table.get_value_at(r + 1, c)).to eq(value)
191
+ end
192
+ end
193
+
194
+ table_xml = find_table_xml
195
+ rows_xml = table_xml.elements.select { |e| e.name == 'table-row' }
196
+ expect(rows_xml.size).to eq(2 + rows_added)
197
+
198
+ # TODO: do we need a final unrepeated row?
199
+ expected_dummy_rows = Table::MIN_ROWS - (1 + rows_added)
200
+ dummy_row_xml = rows_xml.last
201
+ expect(dummy_row_xml['table:number-rows-repeated']).to eq(expected_dummy_rows.to_s)
202
+
203
+ dummy_cells_xml = dummy_row_xml.elements.select { |e| e.name == 'table-cell' }
204
+ expect(dummy_cells_xml.size).to eq(1)
205
+
206
+ dummy_cell_xml = dummy_cells_xml[0]
207
+ expect(dummy_cell_xml['table:number-columns-repeated']).to eq(Table::MIN_COLUMNS.to_s)
208
+ end
209
+
210
+ end
211
+ end
212
+
213
+ end
214
+ end
215
+ end
216
+ end
217
+ end
218
+ end
@@ -0,0 +1,90 @@
1
+ require 'spec_helper'
2
+ require 'berkeley_library/util/paths'
3
+
4
+ module BerkeleyLibrary::Util
5
+ describe Paths do
6
+ describe :clean do
7
+ {
8
+ # nil
9
+ nil => nil,
10
+
11
+ # Already clean
12
+ '' => '.',
13
+ 'abc' => 'abc',
14
+ 'abc/def' => 'abc/def',
15
+ 'a/b/c' => 'a/b/c',
16
+ '.' => '.',
17
+ '..' => '..',
18
+ '../..' => '../..',
19
+ '../../abc' => '../../abc',
20
+ '/abc' => '/abc',
21
+ '/' => '/',
22
+
23
+ # Remove trailing slash
24
+ 'abc/' => 'abc',
25
+ 'abc/def/' => 'abc/def',
26
+ 'a/b/c/' => 'a/b/c',
27
+ './' => '.',
28
+ '../' => '..',
29
+ '../../' => '../..',
30
+ '/abc/' => '/abc',
31
+
32
+ # Remove doubled slash
33
+ 'abc//def//ghi' => 'abc/def/ghi',
34
+ '//abc' => '/abc',
35
+ '///abc' => '/abc',
36
+ '//abc//' => '/abc',
37
+ 'abc//' => 'abc',
38
+
39
+ # Remove . elements
40
+ 'abc/./def' => 'abc/def',
41
+ '/./abc/def' => '/abc/def',
42
+ 'abc/.' => 'abc',
43
+
44
+ # Remove .. elements
45
+ 'abc/def/ghi/../jkl' => 'abc/def/jkl',
46
+ 'abc/def/../ghi/../jkl' => 'abc/jkl',
47
+ 'abc/def/..' => 'abc',
48
+ 'abc/def/../..' => '.',
49
+ '/abc/def/../..' => '/',
50
+ 'abc/def/../../..' => '..',
51
+ '/abc/def/../../..' => '/',
52
+ 'abc/def/../../../ghi/jkl/../../../mno' => '../../mno',
53
+
54
+ # Combinations
55
+ 'abc/./../def' => 'def',
56
+ 'abc//./../def' => 'def',
57
+ 'abc/../../././../def' => '../../def'
58
+ }.each do |orig, expected|
59
+ it "clean(#{orig.inspect}) -> #{expected.inspect}" do
60
+ expect(Paths.clean(orig)).to eq(expected)
61
+ end
62
+ end
63
+ end
64
+
65
+ describe :join do
66
+ {
67
+ # zero parameters
68
+ [] => '',
69
+
70
+ # one parameter
71
+ [''] => '',
72
+ ['a'] => 'a',
73
+
74
+ # two parameters
75
+ ['a', 'b'] => 'a/b',
76
+ ['a', ''] => 'a',
77
+ ['', 'b'] => 'b',
78
+ ['/', 'a'] => '/a',
79
+ ['/', ''] => '/',
80
+ ['a/', 'b'] => 'a/b',
81
+ ['a/', ''] => 'a',
82
+ ['', ''] => ''
83
+ }.each do |orig, expected|
84
+ it "join(#{orig.map(&:inspect).join(', ')}) -> #{expected.inspect}" do
85
+ expect(Paths.join(*orig)).to eq(expected)
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,34 @@
1
+ require 'spec_helper'
2
+
3
+ require 'berkeley_library/util/stringios'
4
+
5
+ module BerkeleyLibrary
6
+ module Util
7
+ describe StringIOs do
8
+ describe :getbyte do
9
+ let(:s) { '祇園精舎の鐘の声、諸行無常の響きあり。' }
10
+ let(:bytes) { s.bytes }
11
+ let(:sio) { StringIO.new(s) }
12
+
13
+ it 'gets the byte at the specified byte index' do
14
+ bytes.each_with_index do |b, i|
15
+ expect(StringIOs.getbyte(sio, i)).to eq(b)
16
+ end
17
+ end
18
+
19
+ it 'resets the current offset' do
20
+ StringIOs.getbyte(sio, bytes.size / 2)
21
+ expect(sio.pos).to eq(0)
22
+ end
23
+
24
+ it 'returns nil for a too-large positive offset' do
25
+ expect(StringIOs.getbyte(s, bytes.size)).to be_nil
26
+ end
27
+
28
+ it 'returns nil for a too-large negative offset' do
29
+ expect(StringIOs.getbyte(s, -(1 + bytes.size))).to be_nil
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,27 @@
1
+ require 'spec_helper'
2
+
3
+ module BerkeleyLibrary
4
+ module Util
5
+ describe Strings do
6
+ describe :diff_index do
7
+ it 'returns nil for identical strings' do
8
+ s = 'elvis'
9
+ expect(Strings.diff_index(s, s)).to be_nil
10
+ end
11
+
12
+ it 'returns the index for different strings' do
13
+ s1 = 'elvis aaron presley'
14
+ s2 = 'elvis nikita presley'
15
+ expect(Strings.diff_index(s1, s2)).to eq(6)
16
+ end
17
+
18
+ it 'returns the length of the shorter string for prefixes' do
19
+ s1 = 'elvis'
20
+ s2 = 'elvis aaron presley'
21
+ expect(Strings.diff_index(s1, s2)).to eq(5)
22
+ expect(Strings.diff_index(s2, s1)).to eq(5)
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,39 @@
1
+ require 'spec_helper'
2
+ require 'berkeley_library/util/times'
3
+
4
+ module BerkeleyLibrary
5
+ module Util
6
+ describe Times do
7
+ describe :ensure_utc do
8
+ it 'returns a UTC time unchanged' do
9
+ time = Time.parse('2021-02-05 16:19:11.37707 -0800')
10
+ utc_time = time.getutc
11
+ expect(Times.ensure_utc(utc_time)).to be(utc_time)
12
+ end
13
+
14
+ it 'converts a non-UTC time' do
15
+ time = Time.parse('2021-02-06 08:19:11.37707 +0800')
16
+ expect(Times.ensure_utc(time)).to eq(time.getutc)
17
+ expect(time.gmt_offset).to eq(28_800), 'Times.ensure_utc() should not modify its argument'
18
+ end
19
+
20
+ it 'accepts a Date' do
21
+ date = Date.new(2021, 2, 6)
22
+ utc_time = Time.new(2021, 2, 6).getutc
23
+ expect(Times.ensure_utc(date)).to eq(utc_time)
24
+ end
25
+
26
+ it 'accepts a Datetime' do
27
+ datetime = DateTime.parse('2021-02-05 16:19:11.37707 -0800')
28
+ utc_time = Time.parse('2021-02-06 00:19:11.37707 UTC')
29
+ expect(Times.ensure_utc(datetime)).to eq(utc_time)
30
+ end
31
+
32
+ it 'rejects non-date/time objects' do
33
+ # noinspection RubyYardParamTypeMatch
34
+ expect { Times.ensure_utc(Object.new) }.to raise_error(ArgumentError)
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,118 @@
1
+ require 'spec_helper'
2
+
3
+ module BerkeleyLibrary::Util
4
+ describe URIs do
5
+ describe :append do
6
+ it 'appends paths' do
7
+ original_uri = URI('https://example.org/foo/bar')
8
+ new_uri = URIs.append(original_uri, 'qux', 'corge', 'garply')
9
+ expect(new_uri).to eq(URI('https://example.org/foo/bar/qux/corge/garply'))
10
+ end
11
+
12
+ it 'does not modify the original URI' do
13
+ original_uri = URI('https://example.org/foo/bar')
14
+ original_url = original_uri.to_s
15
+ new_uri = URIs.append(original_uri, 'qux', 'corge', 'garply')
16
+ expect(new_uri).not_to be(original_uri)
17
+ expect(original_uri.to_s).to eq(original_url)
18
+ end
19
+
20
+ it 'removes extraneous slashes' do
21
+ original_uri = URI('https://example.org/foo/bar')
22
+ new_uri = URIs.append(original_uri, '/qux', '/corge/', '//garply')
23
+ expect(new_uri).to eq(URI('https://example.org/foo/bar/qux/corge/garply'))
24
+ end
25
+
26
+ it 'preserves queries' do
27
+ original_uri = URI('https://example.org/foo/bar?baz=qux')
28
+ new_uri = URIs.append(original_uri, '/qux', '/corge/', '//garply')
29
+ expect(new_uri).to eq(URI('https://example.org/foo/bar/qux/corge/garply?baz=qux'))
30
+ end
31
+
32
+ it 'preserves fragments' do
33
+ original_uri = URI('https://example.org/foo/bar#baz')
34
+ new_uri = URIs.append(original_uri, '/qux', '/corge/', '//garply')
35
+ expect(new_uri).to eq(URI('https://example.org/foo/bar/qux/corge/garply#baz'))
36
+ end
37
+
38
+ it 'accepts a query string if no previous query present' do
39
+ original_uri = URI('https://example.org/foo/bar#baz')
40
+ new_uri = URIs.append(original_uri, '/qux', '/corge/', '//garply?grault=xyzzy')
41
+ expect(new_uri).to eq(URI('https://example.org/foo/bar/qux/corge/garply?grault=xyzzy#baz'))
42
+ end
43
+
44
+ it 'allows the ? to be passed separately' do
45
+ original_uri = URI('https://example.org/foo/bar#baz')
46
+ new_uri = URIs.append(original_uri, '/qux', '/corge/', '//garply', '?', 'grault=xyzzy')
47
+ expect(new_uri).to eq(URI('https://example.org/foo/bar/qux/corge/garply?grault=xyzzy#baz'))
48
+ end
49
+
50
+ it 'appends query parameters with &' do
51
+ original_uri = URI('https://example.org/foo/bar#baz')
52
+ new_uri = URIs.append(original_uri, '/qux', '/corge/', '//garply?grault=xyzzy', '&plugh=flob')
53
+ expect(new_uri).to eq(URI('https://example.org/foo/bar/qux/corge/garply?grault=xyzzy&plugh=flob#baz'))
54
+ end
55
+
56
+ it 'appends query parameters to original URI query' do
57
+ original_uri = URI('https://example.org/foo/bar/qux/corge/garply?grault=xyzzy')
58
+ new_uri = URIs.append(original_uri, '&plugh=flob')
59
+ expect(new_uri).to eq(URI('https://example.org/foo/bar/qux/corge/garply?grault=xyzzy&plugh=flob'))
60
+ end
61
+
62
+ it 'treats & as a path element if no query is present' do
63
+ original_uri = URI('https://example.org/foo/bar#baz')
64
+ new_uri = URIs.append(original_uri, '/qux', '/corge/', 'garply', '&plugh=flob')
65
+ expect(new_uri).to eq(URI('https://example.org/foo/bar/qux/corge/garply/&plugh=flob#baz'))
66
+ end
67
+
68
+ it 'accepts a fragment if no previous fragment present' do
69
+ original_uri = URI('https://example.org/foo/bar?baz=qux')
70
+ new_uri = URIs.append(original_uri, '/qux', '/corge/', '//garply#grault')
71
+ expect(new_uri).to eq(URI('https://example.org/foo/bar/qux/corge/garply?baz=qux#grault'))
72
+ end
73
+
74
+ it 'rejects a query string if the original URI already has one' do
75
+ original_uri = URI('https://example.org/foo/bar?baz=qux')
76
+ expect { URIs.append(original_uri, '/qux?corge') }.to raise_error(URI::InvalidComponentError)
77
+ end
78
+
79
+ it 'rejects a fragment if the original URI already has one' do
80
+ original_uri = URI('https://example.org/foo/bar#baz')
81
+ expect { URIs.append(original_uri, '/qux#corge') }.to raise_error(URI::InvalidComponentError)
82
+ end
83
+
84
+ it 'rejects appending multiple queries' do
85
+ original_uri = URI('https://example.org/foo/bar')
86
+ expect { URIs.append(original_uri, 'baz?qux=corge', 'grault?plugh=xyzzy') }.to raise_error(URI::InvalidComponentError)
87
+ end
88
+
89
+ it 'rejects appending multiple fragments' do
90
+ original_uri = URI('https://example.org/foo/bar')
91
+ expect { URIs.append(original_uri, 'baz#qux', 'grault#plugh') }.to raise_error(URI::InvalidComponentError)
92
+ end
93
+
94
+ it 'rejects queries after fragments' do
95
+ original_uri = URI('https://example.org/foo/bar')
96
+ expect { URIs.append(original_uri, 'baz#qux', '?grault=plugh') }.to raise_error(URI::InvalidComponentError)
97
+ end
98
+
99
+ it 'correctly handles fragments in mid-path-segment' do
100
+ original_uri = URI('https://example.org/foo/bar')
101
+ new_uri = URIs.append(original_uri, 'qux#corge')
102
+ expect(new_uri).to eq(URI('https://example.org/foo/bar/qux#corge'))
103
+ end
104
+
105
+ it 'correctly handles fragments in query start' do
106
+ original_uri = URI('https://example.org/foo/bar')
107
+ new_uri = URIs.append(original_uri, '?qux=corge&grault=plugh#xyzzy')
108
+ expect(new_uri).to eq(URI('https://example.org/foo/bar?qux=corge&grault=plugh#xyzzy'))
109
+ end
110
+
111
+ it 'correctly handles fragments in mid-query' do
112
+ original_uri = URI('https://example.org/foo/bar')
113
+ new_uri = URIs.append(original_uri, '?qux=corge', '&grault=plugh#xyzzy')
114
+ expect(new_uri).to eq(URI('https://example.org/foo/bar?qux=corge&grault=plugh#xyzzy'))
115
+ end
116
+ end
117
+ end
118
+ end