cocos 0.3.1 → 0.4.1

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cbe9c2c739403b5996adda6684d175a3853afbd76bffe81adf44c8a31cbe0c8d
4
- data.tar.gz: 4ba46909cd0722c656aaa1be7145dc5430e681bf5f4722e2b03b3af6928d9de8
3
+ metadata.gz: 51832aaf3ccd7164304a1c4652108b89d7651d2d7358e80c8f0d24fa46f59f69
4
+ data.tar.gz: 3236f78ad3ced02d1ac42a047a0ce11eaf60017063b61c2c1480786e3513ddc0
5
5
  SHA512:
6
- metadata.gz: 5416f8ad167ba3f614cb85483b372bd1b9a8ccf7c60a69fcdbcfac954c1ae3c586f582e1fa3d7abf6dc6cf4c3f95c1da0f8e2bbfaae12b716d77290f90450715
7
- data.tar.gz: e7aa4a8514cf702b9b35b526e60011743de51ebdf1cbb2e8e0861e722225b5e3e76b113304dbcb8c58c377be17e340fd0e961a64a8a3a568768d01fe17a6de3e
6
+ metadata.gz: 11206d11694da8a98bf33b2c444975ec3e195e4c29348f761945fba044fe582d5ab885bb8a5d3057f3ce5589ba288bc14b9d2b456e0003fa74537ac5efa3d0a1
7
+ data.tar.gz: 8f9e09293599531c4fd4482295fc75e0d7aeea95ffe5de62f7c2bc4d2e84a4e8b643109682accdb98b3295e30e89265f0aa6b36dbcaf70e25adfd8488ea6ba8c
data/CHANGELOG.md CHANGED
@@ -1,4 +1,5 @@
1
- ### 0.3.1
1
+ ### 0.4.1
2
+ ### 0.4.0
2
3
  ### 0.0.1 / 2022-08-01
3
4
 
4
5
  * Everything is new. First release.
data/Manifest.txt CHANGED
@@ -5,4 +5,5 @@ README.md
5
5
  Rakefile
6
6
  lib/cocos.rb
7
7
  lib/cocos/env.rb
8
+ lib/cocos/find_file.rb
8
9
  lib/cocos/version.rb
data/README.md CHANGED
@@ -87,8 +87,7 @@ And so on.
87
87
 
88
88
  _Read / parse convenience short-cut helpers_
89
89
 
90
- `read_blob( path )` <br>
91
- also known as `read_binary` or `read_bin`
90
+ `read_blob( path )`
92
91
 
93
92
 
94
93
  `read_text( path )` <br>
@@ -98,14 +97,14 @@ also known as `read_txt`
98
97
  `read_lines( path )`
99
98
 
100
99
 
101
-
102
100
  `read_json( path )` / `parse_json( str )`
103
101
 
104
102
 
105
- `read_yaml( path )` / `parse_yaml( str )`
103
+ `read_yaml( path )` / `parse_yaml( str )` <br>
104
+ also known as `read_yml` / `parse_yml`
106
105
 
107
106
 
108
- `read_csv( path, headers: true )` / `parse_csv( str, headers: true )`
107
+ `read_csv( path )` / `parse_csv( str )`
109
108
 
110
109
  note: comma-separated values (.csv) reading & parsing service
111
110
  brought to you by the [**csvreader library / gem »**](https://github.com/rubycocos/csvreader/tree/master/csvreader)
@@ -113,8 +112,7 @@ brought to you by the [**csvreader library / gem »**](https://github.com/rubyco
113
112
 
114
113
  `read_data( path )` / `parse_data( str )`
115
114
 
116
- note: alternate shortcut / alias for `read_csv( path, headers: false )` / `parse_csv( str, headers: false )`
117
-
115
+ note: alternate csv reader / parser; reads data WITHOUT headers, that is, named columns - returns data array not named hash (table)
118
116
 
119
117
 
120
118
 
@@ -125,7 +123,6 @@ brought to you by the [**tabreader library / gem »**](https://github.com/rubyco
125
123
 
126
124
 
127
125
 
128
-
129
126
  `read_ini( path )` / `parse_ini( str )` <br>
130
127
  also known as `read_conf / parse_conf`
131
128
 
data/Rakefile CHANGED
@@ -12,7 +12,7 @@ Hoe.spec 'cocos' do
12
12
  self.urls = { home: 'https://github.com/rubycocos/cocos' }
13
13
 
14
14
  self.author = 'Gerald Bauer'
15
- self.email = 'opensport@googlegroups.com'
15
+ self.email = 'gerald.bauer@gmail.com'
16
16
 
17
17
  # switch extension to .markdown for gihub formatting
18
18
  self.readme_file = 'README.md'
data/lib/cocos/env.rb CHANGED
@@ -2,7 +2,7 @@
2
2
  # simple read_env, load_env machinery
3
3
  ## inspired by
4
4
  ## dotenv gem -> https://github.com/bkeepers/dotenv
5
- ## figaro -> https://github.com/laserlemon/figaro
5
+ ## figaro -> https://github.com/laserlemon/figaro
6
6
  ## and others
7
7
 
8
8
 
@@ -19,54 +19,59 @@ module EnvParser
19
19
  parse( text )
20
20
  end
21
21
  def self.load( text ) parse( text ); end
22
-
23
-
22
+
23
+
24
24
  class Error < StandardError; end
25
-
25
+
26
26
  ## todo/check - what is JSON and YAML returning Parser/ParseError something else?
27
27
  ## YAML uses ParseError and JSON uses ParserError
28
28
  class ParseError < Error; end
29
-
29
+
30
30
 
31
31
  ## todo/check - if support for empty values e.g. abc= is required/possible???
32
32
  ## todo/ addd support for quoted values - why? why not?
33
33
  ## add support for "inline" end of line comments - why? why not?
34
34
  ## add support for escapes and multi-line values - why? why not?
35
- LINE_RX = /\A(?<key>[A-Za-z][A-Za-z0-9_-]*)
35
+ LINE_RE = /\A
36
+ [ ]*
37
+ (?<key> [A-Za-z][A-Za-z0-9_-]*)
36
38
  [ ]*
37
39
  =
38
40
  [ ]*
39
41
  (?<value>.+?) ## non-greedy
42
+ [ ]*
40
43
  \z
41
44
  /x
42
45
 
43
- ## use a parser class - why? why not?
44
- def self.parse( text )
45
- h = {}
46
+ ## use a parser class - why? why not?
47
+ def self.parse( text )
48
+ h = {}
46
49
 
47
- lineno = 0
50
+ lineno = 0
48
51
  text.each_line do |line|
49
52
  lineno += 1 ## track line numbers for (parse) error reporting
50
-
51
53
  line = line.strip ## check: use strip (or be more strict) - why? why not?
54
+
52
55
  ## skip empty and comment lines
53
- next if line.empty? || line.start_with?( '#' )
56
+ next if line.empty? || line.start_with?( '#' )
57
+ ## support __END__ marker for inline comments
58
+ break if line == '__END__'
54
59
 
55
- if m=LINE_RX.match(line)
60
+ if m=LINE_RE.match(line)
56
61
  key = m[:key]
57
62
  value = m[:value]
58
-
63
+
59
64
  ## todo/check - check/warn about duplicates - why? why not?
60
65
  h[key] = value
61
- else
66
+ else
62
67
  raise ParseError, "line #{lineno} - unknown line type; cannot parse >#{line}<"
63
- end
68
+ end
64
69
  end
65
70
  h
66
71
  end # methdod self.parse
67
72
  end # module EnvParser
68
73
 
69
-
74
+
70
75
 
71
76
  __END__
72
77
 
@@ -0,0 +1,45 @@
1
+
2
+
3
+ ##
4
+ ## note - use File.file? instead of File.exist?
5
+ ## (checks if file exists AND file is a file NOT a directory)
6
+ ##
7
+ ##
8
+ ## add option - raise_on_error: false - why? why not?
9
+ ## def find_file! - find_file( raise_on_error: false )
10
+ ##
11
+ ## todo/check
12
+ ## golang lookup_path or such
13
+ ## always return absolute (expanded) path - why? why not?
14
+
15
+
16
+ module Kernel
17
+
18
+ def find_file!( name, path: )
19
+ filepath = find_file( name, path: path )
20
+ raise Errorno::ENOENT, "file <#{name}> not found; looking in path #{path.inspect}" if filepath.nil?
21
+ filepath
22
+ end
23
+
24
+ ##
25
+ ## note - find_file will NOT find directories!!!
26
+ ## File.file? will only check if a file (not directory) exits!!
27
+
28
+
29
+ ##
30
+ ## todo/check - expand path and use File.realpath too?
31
+ ## or keep "simple" File.join ?
32
+
33
+ def find_file( name, path: )
34
+ return name if File.file?( name )
35
+
36
+ path.each do |dir|
37
+ filepath = File.join( dir, name )
38
+ return filepath if File.file?( filepath )
39
+ end
40
+
41
+ nil ## return nil if not found
42
+ end
43
+
44
+
45
+ end # module Kernel
data/lib/cocos/version.rb CHANGED
@@ -1,7 +1,7 @@
1
1
 
2
2
  module Cocos
3
3
  MAJOR = 0 ## todo: namespace inside version or something - why? why not??
4
- MINOR = 3
4
+ MINOR = 4
5
5
  PATCH = 1
6
6
  VERSION = [MAJOR,MINOR,PATCH].join('.')
7
7
 
@@ -14,8 +14,6 @@ module Cocos
14
14
  end
15
15
 
16
16
  def self.root
17
- File.expand_path( File.dirname(File.dirname(__FILE__) ))
17
+ File.expand_path( File.dirname(File.dirname(File.dirname(__FILE__) )))
18
18
  end
19
-
20
19
  end # module Cocos
21
-
data/lib/cocos.rb CHANGED
@@ -29,8 +29,12 @@ require 'webclient'
29
29
 
30
30
  #####################
31
31
  # our own code
32
- require 'cocos/version' # note: let version always go first
33
- require 'cocos/env' ## e.g. EnvParser
32
+ require_relative 'cocos/version' # note: let version always go first
33
+ require_relative 'cocos/env' ## e.g. EnvParser
34
+
35
+
36
+ require_relative 'cocos/find_file'
37
+
34
38
 
35
39
  ###
36
40
  ## read/parse convenience/helper shortcuts
@@ -43,138 +47,153 @@ module Kernel
43
47
  ################
44
48
  # private helpers - keep along here - why? why not?
45
49
 
46
- ##### check if path starts with http:// or https://
47
- ## if yes, assume it's a download
48
- DOWNLOAD_RX = %r{^https?://}i
49
50
 
50
- ## note: hack - use !! to force nil (no match) to false
51
- ## and matchdata to true
52
- def _download?( path )
53
- !! DOWNLOAD_RX.match( path )
54
- end
51
+ ## todo: add symbolize options a la read_json? - why? why not?
52
+ ## add sep options
55
53
 
54
+ def read_csv( path, sep: nil )
55
+ opts = {}
56
+ opts[:sep] = sep if sep
56
57
 
58
+ CsvHash.read( path, **opts )
59
+ end
57
60
 
58
- ## todo: add symbolize options a la read_json
59
- ## add sep options
60
- def read_csv( path, headers: true )
61
-
62
- if _download?( path )
63
- parse_csv( _wget!( path ).text,
64
- headers: headers )
65
- else
66
- if headers
67
- CsvHash.read( path )
68
- else
69
- Csv.read( path )
70
- end
71
- end
61
+ def parse_csv( str, sep: nil )
62
+ opts = {}
63
+ opts[:sep] = sep if sep
64
+
65
+ CsvHash.parse( str, **opts )
72
66
  end
73
67
 
74
- def parse_csv( str, headers: true )
75
- if headers
76
- CsvHash.parse( str )
77
- else
78
- Csv.parse( str )
79
- end
68
+
69
+ ## note - use explicit download for now
70
+ ##
71
+ def download_csv( url, sep: nil )
72
+ opts = {}
73
+ opts[:sep] = sep if sep
74
+
75
+ parse_csv( download_text( url ),
76
+ **opts )
80
77
  end
81
78
 
82
79
 
80
+
83
81
  ### note: use read_data / parse_data
84
82
  ## for alternate shortcut for read_csv / parse_csv w/ headers: false
85
83
  ## returning arrays of strings
86
84
  def read_data( path )
87
- if _download?( path )
88
- read_data( _wget!( path ).text )
89
- else
90
- Csv.read( path )
91
- end
85
+ Csv.read( path )
92
86
  end
93
87
 
94
88
  def parse_data( str )
95
89
  Csv.parse( str )
96
90
  end
97
91
 
92
+ def download_data( url )
93
+ parse_data( download_text( url ))
94
+ end
95
+
98
96
 
99
97
 
100
98
  def read_tab( path )
101
- if _download?( path )
102
- parse_tab( _wget!( path ).text )
103
- else
104
- Tab.read( path )
105
- end
99
+ Tab.read( path )
106
100
  end
107
101
 
108
102
  def parse_tab( str )
109
103
  Tab.parse( str )
110
104
  end
111
105
 
106
+ def download_tab( url )
107
+ parse_tab( download_text( url ))
108
+ end
109
+
110
+
112
111
 
113
112
  ## todo: add symbolize options ???
114
113
  def read_json( path )
115
- JSON.parse( read_text( path ))
114
+ parse_json( read_text( path ))
116
115
  end
117
116
 
118
117
  def parse_json( str )
119
118
  JSON.parse( str )
120
119
  end
121
120
 
121
+ def download_json( url )
122
+ parse_json( download_text( url ))
123
+ end
124
+
122
125
 
123
126
  ### todo/check: use parse_safeyaml or such? (is default anyway?) - why? why not?
124
127
  def read_yaml( path )
125
- YAML.load( read_text( path ))
128
+ parse_yaml( read_text( path ))
126
129
  end
127
130
 
128
131
  def parse_yaml( str )
129
132
  YAML.load( str )
130
133
  end
131
134
 
135
+ def download_yaml( url )
136
+ parse_yaml( download_text( url ))
137
+ end
138
+
139
+ ## keep yml alias - why? why not?
140
+ alias_method :read_yml, :read_yaml
141
+ alias_method :parse_yml, :parse_yaml
142
+ alias_method :download_yml, :download_yaml
143
+
132
144
 
133
145
  def read_ini( path )
134
- INI.load( read_text( path ))
146
+ parse_ini( read_text( path ))
135
147
  end
136
148
 
137
149
  def parse_ini( str )
138
150
  INI.load( str )
139
151
  end
140
152
 
141
- alias_method :read_conf, :read_ini
142
- alias_method :parse_conf, :parse_ini
153
+ def download_ini( url )
154
+ parse_ini( download_text( url ))
155
+ end
156
+
157
+ alias_method :read_conf, :read_ini
158
+ alias_method :parse_conf, :parse_ini
159
+ alias_method :download_conf, :download_ini
143
160
 
144
161
 
145
162
 
146
163
 
147
164
  def read_text( path )
148
- if _download?( path )
149
- _wget!( path ).text
150
- else
151
165
  ## todo/check: add universal newline mode or such?
152
166
  ## e.g. will always convert all
153
167
  ## newline variants (\n|\r|\n\r) to "universal" \n only
154
168
  ##
155
169
  ## add r:bom - why? why not?
156
- txt = File.open( path, 'r:utf-8' ) do |f|
157
- f.read
158
- end
159
- txt
160
- end
170
+ File.open( path, 'r:utf-8' ) do |f|
171
+ f.read
172
+ end
161
173
  end
162
- alias_method :read_txt, :read_text
174
+
175
+ def download_text( url )
176
+ wget!( url ).text
177
+ end
178
+
179
+ alias_method :read_txt, :read_text
180
+ alias_method :download_txt, :download_text
181
+
163
182
 
164
183
 
165
184
  def read_blob( path )
166
- if _download?( path )
167
- _wget!( path ).blob
168
- else
169
- blob = File.open( path, 'rb' ) do |f|
170
- f.read
171
- end
172
- blob
173
- end
185
+ File.open( path, 'rb' ) do |f|
186
+ f.read
187
+ end
174
188
  end
175
- alias_method :read_binary, :read_blob
176
- alias_method :read_bin, :read_blob
189
+ ## alias_method :read_binary, :read_blob
190
+ ## alias_method :read_bin, :read_blob
177
191
 
192
+ def download_blob( url )
193
+ wget!( url ).blob
194
+ end
195
+ ## alias_method :download_binary, :download_blob
196
+ ## alias_method :download_bin, :download_blob
178
197
 
179
198
 
180
199
 
@@ -191,10 +210,14 @@ def parse_lines( str )
191
210
  str.lines
192
211
  end
193
212
 
213
+ def download_lines( url )
214
+ parse_lines( download_text( url ))
215
+ end
216
+
194
217
 
195
218
 
196
219
  def read_env( path )
197
- EnvParser.load( read_text( path ))
220
+ parse_env( read_text( path ))
198
221
  end
199
222
 
200
223
  def parse_env( str )
@@ -202,11 +225,17 @@ def parse_env( str )
202
225
  end
203
226
 
204
227
 
228
+
229
+
205
230
  ##
206
231
  ## todo/check - change path to *paths=['./.env']
207
232
  ## and support more files - why? why not?
233
+ ##
234
+ ## note - use File.file? instead of File.exist?
235
+ ## will avoid matching directories!
236
+
208
237
  def load_env( path='./.env' )
209
- if File.exist?( path )
238
+ if File.file?( path )
210
239
  puts "==> loading .env settings..."
211
240
  env = read_env( path )
212
241
  puts " applying .env settings... (merging into ENV)"
@@ -251,8 +280,8 @@ def write_blob( path, blob )
251
280
  f.write( blob )
252
281
  end
253
282
  end
254
- alias_method :write_binary, :write_blob
255
- alias_method :write_bin, :write_blob
283
+ # alias_method :write_binary, :write_blob
284
+ # alias_method :write_bin, :write_blob
256
285
 
257
286
 
258
287
  def write_text( path, text )
@@ -271,54 +300,90 @@ alias_method :write_txt, :write_text
271
300
 
272
301
 
273
302
 
274
- #
303
+
275
304
  # note:
276
305
  # for now write_csv expects array of string arrays
277
- # does NOT support array of hashes for now
306
+ # does NOT support array of hashes for now
278
307
 
279
308
  def write_csv( path, recs, headers: nil )
280
309
  dirname = File.dirname( path )
281
310
  FileUtils.mkdir_p( dirname ) unless Dir.exist?( dirname )
282
311
 
283
312
  File.open( path, 'w:utf-8' ) do |f|
284
- if headers
285
- f.write( headers.join(',')) ## e.g. Date,Team 1,FT,HT,Team 2
313
+ if headers ## e.g. Date,Team 1,FT,HT,Team 2
314
+ f.write( headers.map do |header|
315
+ _escape_csv( header )
316
+ end.join(',') )
286
317
  f.write( "\n" )
287
- end
318
+ end
288
319
 
289
320
  recs.each do |values|
290
- ## quote values that incl. a comma
291
- ## todo/fix - add more escape/quote checks - why? why not?
292
- ## check how other csv libs handle value generation
293
321
  buf = values.map do |value|
294
- if value.index(',')
295
- %Q{"#{value}"}
296
- else
297
- value
298
- end
322
+ _escape_csv( value )
299
323
  end.join( ',' )
300
-
324
+
301
325
  f.write( buf )
302
326
  f.write( "\n" )
303
- end
327
+ end
304
328
  end
305
329
  end
306
330
 
307
331
 
308
332
 
333
+ ## quote values that incl. a comma
334
+ ## todo/fix - add more escape/quote checks - why? why not?
335
+ ## check how other csv libs handle value generation
336
+ ##
337
+ ## If a field contains
338
+ ## - a comma (,)
339
+ ## - a double quote (")
340
+ ## - a newline (\r\n)
341
+ ## then wrap the field in quotes
342
+ ## Inside quoted fields, double every double quote (" → "")
343
+
344
+ def _escape_csv(value)
345
+ ## auto-convert to string or let code fail on nil or such?
346
+ value = value.to_s
347
+
348
+ ## note - double double quotes (") for now only
349
+ ## check
350
+ ## - escape newline (lf) as \n or keep it literal - why? why not?
351
+ ## what about \r carriage return (cr)
352
+ ##
353
+ ## add value.match?(/\A\s|\s\z/) ||
354
+ ## to preserve leading/trailing spaces in value ???
355
+ ## e.g. _a_ becomes "_a_" written out
356
+ ##
357
+ ## quote empty strings or keep them empty
358
+ ## what about nil - for now empty string too
359
+ ## add nil_value option e.g. 'n/a' or such
360
+ ## and quote_empty true|false - why? why not?
361
+ if value.include?(',') ||
362
+ value.include?('"') ||
363
+ value.include?("\n") ||
364
+ value.include?("\r")
365
+ '"' + value.gsub('"', '""') + '"'
366
+ else
367
+ value
368
+ end
369
+ end
370
+
371
+
372
+
373
+
374
+
375
+
376
+
309
377
  ######
310
378
  # world wide web (www) support
311
379
 
312
- def wget( url, **kwargs )
313
- Webclient.get( url, **kwargs )
380
+ def wget( url, **opts )
381
+ Webclient.get( url, **opts )
314
382
  end
315
383
  ## add alias www_get or web_get - why? why not?
316
384
 
317
-
318
-
319
- ## private helper - make public -why? why not?
320
- def _wget!( url, **kwargs )
321
- res = Webclient.get( url, **kwargs )
385
+ def wget!( url, **opts )
386
+ res = Webclient.get( url, **opts )
322
387
 
323
388
  ## check/todo - use a different exception/error - keep RuntimeError - why? why not?
324
389
  raise RuntimeError, "HTTP #{res.status.code} - #{res.status.message}" if res.status.nok?
@@ -339,4 +404,3 @@ Coco = Cocos
339
404
 
340
405
 
341
406
  puts Cocos.banner ## say hello
342
-
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cocos
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.4.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gerald Bauer
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-06-29 00:00:00.000000000 Z
11
+ date: 2026-05-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: csvreader
@@ -92,16 +92,16 @@ dependencies:
92
92
  requirements:
93
93
  - - "~>"
94
94
  - !ruby/object:Gem::Version
95
- version: '4.1'
95
+ version: '4.2'
96
96
  type: :development
97
97
  prerelease: false
98
98
  version_requirements: !ruby/object:Gem::Requirement
99
99
  requirements:
100
100
  - - "~>"
101
101
  - !ruby/object:Gem::Version
102
- version: '4.1'
102
+ version: '4.2'
103
103
  description: cocos (code commons) - auto-include quick-starter prelude & prolog
104
- email: opensport@googlegroups.com
104
+ email: gerald.bauer@gmail.com
105
105
  executables: []
106
106
  extensions: []
107
107
  extra_rdoc_files:
@@ -117,6 +117,7 @@ files:
117
117
  - Rakefile
118
118
  - lib/cocos.rb
119
119
  - lib/cocos/env.rb
120
+ - lib/cocos/find_file.rb
120
121
  - lib/cocos/version.rb
121
122
  homepage: https://github.com/rubycocos/cocos
122
123
  licenses:
@@ -139,7 +140,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
139
140
  - !ruby/object:Gem::Version
140
141
  version: '0'
141
142
  requirements: []
142
- rubygems_version: 3.4.10
143
+ rubygems_version: 3.5.22
143
144
  signing_key:
144
145
  specification_version: 4
145
146
  summary: cocos (code commons) - auto-include quick-starter prelude & prolog