├── .gitignore
├── CHANGELOG.md
├── LICENSE.md
├── Manifest.txt
├── README.md
├── Rakefile
├── attic
└── cocos.rb
├── lib
├── cocos.rb
└── cocos
│ ├── env.rb
│ └── version.rb
├── sandbox
├── test_blob.rb
├── test_web.rb
└── test_writer.rb
└── test
├── data
├── beer.a.csv
├── beer.csv
├── manifest.txt
├── marilyn.json
├── marilyn.png
├── marilyn.yaml
├── planet.ini
└── test.tab
├── helper.rb
├── test_env.rb
└── test_readers.rb
/.gitignore:
--------------------------------------------------------------------------------
1 | *.gem
2 | *.rbc
3 | /.config
4 | /coverage/
5 | /InstalledFiles
6 | /pkg/
7 | /spec/reports/
8 | /test/tmp/
9 | /test/version_tmp/
10 | /tmp/
11 |
12 | ## Specific to RubyMotion:
13 | .dat*
14 | .repl_history
15 | build/
16 |
17 | ## Documentation cache and generated files:
18 | /.yardoc/
19 | /_yardoc/
20 | /doc/
21 | /rdoc/
22 |
23 | ## Environment normalisation:
24 | /.bundle/
25 | /lib/bundler/man/
26 |
27 | # for a library or gem, you might want to ignore these files since the code is
28 | # intended to run in multiple environments; otherwise, check them in:
29 | # Gemfile.lock
30 | # .ruby-version
31 | # .ruby-gemset
32 |
33 | # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
34 | .rvmrc
35 |
36 |
37 | # more debugging support
38 | errors.log
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ### 0.4.0
2 | ### 0.0.1 / 2022-08-01
3 |
4 | * Everything is new. First release.
5 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | CC0 1.0 Universal
2 |
3 | Statement of Purpose
4 |
5 | The laws of most jurisdictions throughout the world automatically confer
6 | exclusive Copyright and Related Rights (defined below) upon the creator and
7 | subsequent owner(s) (each and all, an "owner") of an original work of
8 | authorship and/or a database (each, a "Work").
9 |
10 | Certain owners wish to permanently relinquish those rights to a Work for the
11 | purpose of contributing to a commons of creative, cultural and scientific
12 | works ("Commons") that the public can reliably and without fear of later
13 | claims of infringement build upon, modify, incorporate in other works, reuse
14 | and redistribute as freely as possible in any form whatsoever and for any
15 | purposes, including without limitation commercial purposes. These owners may
16 | contribute to the Commons to promote the ideal of a free culture and the
17 | further production of creative, cultural and scientific works, or to gain
18 | reputation or greater distribution for their Work in part through the use and
19 | efforts of others.
20 |
21 | For these and/or other purposes and motivations, and without any expectation
22 | of additional consideration or compensation, the person associating CC0 with a
23 | Work (the "Affirmer"), to the extent that he or she is an owner of Copyright
24 | and Related Rights in the Work, voluntarily elects to apply CC0 to the Work
25 | and publicly distribute the Work under its terms, with knowledge of his or her
26 | Copyright and Related Rights in the Work and the meaning and intended legal
27 | effect of CC0 on those rights.
28 |
29 | 1. Copyright and Related Rights. A Work made available under CC0 may be
30 | protected by copyright and related or neighboring rights ("Copyright and
31 | Related Rights"). Copyright and Related Rights include, but are not limited
32 | to, the following:
33 |
34 | i. the right to reproduce, adapt, distribute, perform, display, communicate,
35 | and translate a Work;
36 |
37 | ii. moral rights retained by the original author(s) and/or performer(s);
38 |
39 | iii. publicity and privacy rights pertaining to a person's image or likeness
40 | depicted in a Work;
41 |
42 | iv. rights protecting against unfair competition in regards to a Work,
43 | subject to the limitations in paragraph 4(a), below;
44 |
45 | v. rights protecting the extraction, dissemination, use and reuse of data in
46 | a Work;
47 |
48 | vi. database rights (such as those arising under Directive 96/9/EC of the
49 | European Parliament and of the Council of 11 March 1996 on the legal
50 | protection of databases, and under any national implementation thereof,
51 | including any amended or successor version of such directive); and
52 |
53 | vii. other similar, equivalent or corresponding rights throughout the world
54 | based on applicable law or treaty, and any national implementations thereof.
55 |
56 | 2. Waiver. To the greatest extent permitted by, but not in contravention of,
57 | applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and
58 | unconditionally waives, abandons, and surrenders all of Affirmer's Copyright
59 | and Related Rights and associated claims and causes of action, whether now
60 | known or unknown (including existing as well as future claims and causes of
61 | action), in the Work (i) in all territories worldwide, (ii) for the maximum
62 | duration provided by applicable law or treaty (including future time
63 | extensions), (iii) in any current or future medium and for any number of
64 | copies, and (iv) for any purpose whatsoever, including without limitation
65 | commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes
66 | the Waiver for the benefit of each member of the public at large and to the
67 | detriment of Affirmer's heirs and successors, fully intending that such Waiver
68 | shall not be subject to revocation, rescission, cancellation, termination, or
69 | any other legal or equitable action to disrupt the quiet enjoyment of the Work
70 | by the public as contemplated by Affirmer's express Statement of Purpose.
71 |
72 | 3. Public License Fallback. Should any part of the Waiver for any reason be
73 | judged legally invalid or ineffective under applicable law, then the Waiver
74 | shall be preserved to the maximum extent permitted taking into account
75 | Affirmer's express Statement of Purpose. In addition, to the extent the Waiver
76 | is so judged Affirmer hereby grants to each affected person a royalty-free,
77 | non transferable, non sublicensable, non exclusive, irrevocable and
78 | unconditional license to exercise Affirmer's Copyright and Related Rights in
79 | the Work (i) in all territories worldwide, (ii) for the maximum duration
80 | provided by applicable law or treaty (including future time extensions), (iii)
81 | in any current or future medium and for any number of copies, and (iv) for any
82 | purpose whatsoever, including without limitation commercial, advertising or
83 | promotional purposes (the "License"). The License shall be deemed effective as
84 | of the date CC0 was applied by Affirmer to the Work. Should any part of the
85 | License for any reason be judged legally invalid or ineffective under
86 | applicable law, such partial invalidity or ineffectiveness shall not
87 | invalidate the remainder of the License, and in such case Affirmer hereby
88 | affirms that he or she will not (i) exercise any of his or her remaining
89 | Copyright and Related Rights in the Work or (ii) assert any associated claims
90 | and causes of action with respect to the Work, in either case contrary to
91 | Affirmer's express Statement of Purpose.
92 |
93 | 4. Limitations and Disclaimers.
94 |
95 | a. No trademark or patent rights held by Affirmer are waived, abandoned,
96 | surrendered, licensed or otherwise affected by this document.
97 |
98 | b. Affirmer offers the Work as-is and makes no representations or warranties
99 | of any kind concerning the Work, express, implied, statutory or otherwise,
100 | including without limitation warranties of title, merchantability, fitness
101 | for a particular purpose, non infringement, or the absence of latent or
102 | other defects, accuracy, or the present or absence of errors, whether or not
103 | discoverable, all to the greatest extent permissible under applicable law.
104 |
105 | c. Affirmer disclaims responsibility for clearing rights of other persons
106 | that may apply to the Work or any use thereof, including without limitation
107 | any person's Copyright and Related Rights in the Work. Further, Affirmer
108 | disclaims responsibility for obtaining any necessary consents, permissions
109 | or other rights required for any use of the Work.
110 |
111 | d. Affirmer understands and acknowledges that Creative Commons is not a
112 | party to this document and has no duty or obligation with respect to this
113 | CC0 or use of the Work.
114 |
115 | For more information, please see
116 |
117 |
--------------------------------------------------------------------------------
/Manifest.txt:
--------------------------------------------------------------------------------
1 | CHANGELOG.md
2 | LICENSE.md
3 | Manifest.txt
4 | README.md
5 | Rakefile
6 | lib/cocos.rb
7 | lib/cocos/env.rb
8 | lib/cocos/version.rb
9 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # cocos (code commons) - auto-include quick-starter prelude & prolog
2 |
3 |
4 | * home :: [github.com/rubycocos/cocos](https://github.com/rubycocos/cocos)
5 | * bugs :: [github.com/rubycocos/cocos/issues](https://github.com/rubycocos/cocos/issues)
6 | * gem :: [rubygems.org/gems/cocos](https://rubygems.org/gems/cocos)
7 | * rdoc :: [rubydoc.info/gems/cocos](http://rubydoc.info/gems/cocos)
8 |
9 |
10 |
11 | ## Intro - Why?
12 |
13 |
14 | **Reason No. 1**
15 |
16 | After starting of too many scripts (hundreds?) with
17 | adding more and always repeating the same dozen modules with require e.g.:
18 |
19 | ``` ruby
20 | require 'pp'
21 | require 'time'
22 | require 'date'
23 | require 'json'
24 | require 'yaml'
25 | require 'base64'
26 | require 'fileutils'
27 |
28 | require 'uri'
29 | require 'net/http'
30 | require 'net/https'
31 |
32 | ...
33 | ```
34 |
35 | why not use a more "inclusive" prelude & prolog and
36 | replace the above with a one-liner:
37 |
38 | ``` ruby
39 | require 'cocos' # auto-include code commons quick-starter prelude & prolog
40 | ```
41 |
42 |
43 | **Reason No. 2**
44 |
45 | After reading too many text files in utf-8 and always repeating the same open / read and code block dance e.g.:
46 |
47 | ``` ruby
48 | txt = File.read( "history.txt" )
49 | # sorry - will NOT guarantee unicode utf8-encoding
50 | # (e.g. on microsoft windows it is ISO Code Page (CP-1252
51 | # or something - depending on your locale/culture/language)
52 |
53 | txt = File.open( "history.txt", "r:utf-8" ) do |f|
54 | f.read
55 | end
56 | ```
57 |
58 | Or after reading and parsing too many json files
59 | (by default always required utf-8 encoding)
60 | and always repeating the same open / read and code block dance
61 | again and again e.g.:
62 |
63 | ``` ruby
64 | txt = File.open( "history.json", "r:utf-8" ) do |f|
65 | f.read
66 | end
67 | data = JSON.parse( txt )
68 | ```
69 |
70 | Why not use read convenience / short-cut helpers such as:
71 |
72 | ``` ruby
73 | txt = read_txt( "history.txt" )
74 | data = read_json( "history.json" )
75 | ```
76 |
77 |
78 | And so on.
79 |
80 |
81 |
82 |
83 |
84 | ## Usage
85 |
86 | ### Read / Parse
87 |
88 | _Read / parse convenience short-cut helpers_
89 |
90 | `read_blob( path )`
91 |
92 |
93 | `read_text( path )`
94 | also known as `read_txt`
95 |
96 |
97 | `read_lines( path )`
98 |
99 |
100 | `read_json( path )` / `parse_json( str )`
101 |
102 |
103 | `read_yaml( path )` / `parse_yaml( str )`
104 | also known as `read_yml` / `parse_yml`
105 |
106 |
107 | `read_csv( path )` / `parse_csv( str )`
108 |
109 | note: comma-separated values (.csv) reading & parsing service
110 | brought to you by the [**csvreader library / gem »**](https://github.com/rubycocos/csvreader/tree/master/csvreader)
111 |
112 |
113 | `read_data( path )` / `parse_data( str )`
114 |
115 | note: alternate csv reader / parser; reads data WITHOUT headers, that is, named columns - returns data array not named hash (table)
116 |
117 |
118 |
119 | `read_tab( path )` / `parse_tab( str )`
120 |
121 | note: tabulator (`\t`)-separated values (.tab) reading & parsing service
122 | brought to you by the [**tabreader library / gem »**](https://github.com/rubycocos/csvreader/tree/master/tabreader)
123 |
124 |
125 |
126 | `read_ini( path )` / `parse_ini( str )`
127 | also known as `read_conf / parse_conf`
128 |
129 | note: ini / conf(ig) reading & parsing service
130 | brought to you by the [**iniparser library / gem »**](https://github.com/rubycocos/core/tree/master/iniparser)
131 |
132 |
133 |
134 | That's it for now.
135 |
136 |
137 |
138 | ## License
139 |
140 | The `cocos` scripts are dedicated to the public domain.
141 | Use it as you please with no restrictions whatsoever.
142 |
143 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | require 'hoe'
2 | require './lib/cocos/version.rb'
3 |
4 |
5 | Hoe.spec 'cocos' do
6 |
7 | self.version = Cocos::VERSION
8 |
9 | self.summary = "cocos (code commons) - auto-include quick-starter prelude & prolog"
10 | self.description = summary
11 |
12 | self.urls = { home: 'https://github.com/rubycocos/cocos' }
13 |
14 | self.author = 'Gerald Bauer'
15 | self.email = 'gerald.bauer@gmail.com'
16 |
17 | # switch extension to .markdown for gihub formatting
18 | self.readme_file = 'README.md'
19 | self.history_file = 'CHANGELOG.md'
20 |
21 | self.licenses = ['Public Domain']
22 |
23 | self.extra_deps = [
24 | ['csvreader', '>= 1.2.5'],
25 | ['tabreader', '>= 1.0.1'],
26 | ['iniparser', '>= 1.0.1'],
27 | ['webclient', '>= 0.2.2'],
28 | ]
29 |
30 | self.spec_extras = {
31 | required_ruby_version: '>= 2.2.2'
32 | }
33 | end
34 |
--------------------------------------------------------------------------------
/attic/cocos.rb:
--------------------------------------------------------------------------------
1 | ################
2 | # private helpers - keep along here - why? why not?
3 |
4 | ##### check if path starts with http:// or https://
5 | ## if yes, assume it's a download
6 | DOWNLOAD_RX = %r{^https?://}i
7 |
8 | ## note: hack - use !! to force nil (no match) to false
9 | ## and matchdata to true
10 | def _download?( path )
11 | !! DOWNLOAD_RX.match( path )
12 | end
13 |
14 |
15 | ## todo: add symbolize options a la read_json
16 | ## add sep options
17 | def read_csv( path, headers: true )
18 |
19 | if _download?( path )
20 | parse_csv( _wget!( path ).text,
21 | headers: headers )
22 | else
23 | if headers
24 | CsvHash.read( path )
25 | else
26 | Csv.read( path )
27 | end
28 | end
29 | end
30 |
31 | def parse_csv( str, headers: true )
32 | if headers
33 | CsvHash.parse( str )
34 | else
35 | Csv.parse( str )
36 | end
37 | end
38 |
39 |
40 |
--------------------------------------------------------------------------------
/lib/cocos.rb:
--------------------------------------------------------------------------------
1 | ##
2 | ## "prelude / prolog " add some common used stdlibs
3 | ## add more - why? why not?
4 | require 'pp'
5 | require 'time'
6 | require 'date'
7 | require 'json'
8 | require 'yaml'
9 | require 'base64' ## e.g. Base64.decode64,Base64.encode64,...
10 | require 'fileutils'
11 |
12 | require 'uri'
13 | require 'net/http'
14 | require 'net/https'
15 | require 'cgi'
16 |
17 | require 'optparse' ## used by monofile (built-in test/debug) command line tool
18 |
19 |
20 | ###
21 | # 3rd party gems
22 | require 'csvreader'
23 | require 'tabreader'
24 | require 'iniparser'
25 |
26 | require 'webclient'
27 |
28 |
29 |
30 | #####################
31 | # our own code
32 | require_relative 'cocos/version' # note: let version always go first
33 | require_relative 'cocos/env' ## e.g. EnvParser
34 |
35 |
36 | ###
37 | ## read/parse convenience/helper shortcuts
38 |
39 |
40 | module Kernel
41 |
42 |
43 |
44 | ################
45 | # private helpers - keep along here - why? why not?
46 |
47 |
48 | ## todo: add symbolize options a la read_json? - why? why not?
49 | ## add sep options
50 |
51 | def read_csv( path, sep: nil )
52 | opts = {}
53 | opts[:sep] = sep if sep
54 |
55 | CsvHash.read( path, **opts )
56 | end
57 |
58 | def parse_csv( str, sep: nil )
59 | opts = {}
60 | opts[:sep] = sep if sep
61 |
62 | CsvHash.parse( str, **opts )
63 | end
64 |
65 |
66 | ## note - use explicit download for now
67 | ##
68 | def download_csv( url, sep: nil )
69 | opts = {}
70 | opts[:sep] = sep if sep
71 |
72 | parse_csv( download_text( url ),
73 | **opts )
74 | end
75 |
76 |
77 |
78 | ### note: use read_data / parse_data
79 | ## for alternate shortcut for read_csv / parse_csv w/ headers: false
80 | ## returning arrays of strings
81 | def read_data( path )
82 | Csv.read( path )
83 | end
84 |
85 | def parse_data( str )
86 | Csv.parse( str )
87 | end
88 |
89 | def download_data( url )
90 | parse_data( download_text( url ))
91 | end
92 |
93 |
94 |
95 | def read_tab( path )
96 | Tab.read( path )
97 | end
98 |
99 | def parse_tab( str )
100 | Tab.parse( str )
101 | end
102 |
103 | def download_tab( url )
104 | parse_tab( download_text( url ))
105 | end
106 |
107 |
108 |
109 | ## todo: add symbolize options ???
110 | def read_json( path )
111 | parse_json( read_text( path ))
112 | end
113 |
114 | def parse_json( str )
115 | JSON.parse( str )
116 | end
117 |
118 | def download_json( url )
119 | parse_json( download_text( url ))
120 | end
121 |
122 |
123 | ### todo/check: use parse_safeyaml or such? (is default anyway?) - why? why not?
124 | def read_yaml( path )
125 | parse_yaml( read_text( path ))
126 | end
127 |
128 | def parse_yaml( str )
129 | YAML.load( str )
130 | end
131 |
132 | def download_yaml( url )
133 | parse_yaml( download_text( url ))
134 | end
135 |
136 | ## keep yml alias - why? why not?
137 | alias_method :read_yml, :read_yaml
138 | alias_method :parse_yml, :parse_yaml
139 | alias_method :download_yml, :download_yaml
140 |
141 |
142 | def read_ini( path )
143 | parse_ini( read_text( path ))
144 | end
145 |
146 | def parse_ini( str )
147 | INI.load( str )
148 | end
149 |
150 | def download_ini( url )
151 | parse_ini( download_text( url ))
152 | end
153 |
154 | alias_method :read_conf, :read_ini
155 | alias_method :parse_conf, :parse_ini
156 | alias_method :download_conf, :download_ini
157 |
158 |
159 |
160 |
161 | def read_text( path )
162 | ## todo/check: add universal newline mode or such?
163 | ## e.g. will always convert all
164 | ## newline variants (\n|\r|\n\r) to "universal" \n only
165 | ##
166 | ## add r:bom - why? why not?
167 | File.open( path, 'r:utf-8' ) do |f|
168 | f.read
169 | end
170 | end
171 |
172 | def download_text( url )
173 | wget!( url ).text
174 | end
175 |
176 | alias_method :read_txt, :read_text
177 | alias_method :download_txt, :download_text
178 |
179 |
180 |
181 | def read_blob( path )
182 | File.open( path, 'rb' ) do |f|
183 | f.read
184 | end
185 | end
186 | ## alias_method :read_binary, :read_blob
187 | ## alias_method :read_bin, :read_blob
188 |
189 | def download_blob( url )
190 | wget!( url ).blob
191 | end
192 | ## alias_method :download_binary, :download_blob
193 | ## alias_method :download_bin, :download_blob
194 |
195 |
196 |
197 | ## todo/check: remove \n (or\r or \r\n) from line
198 | ## ruby (by default) keeps the newline - follow tradition? why? why not?
199 | ## add/offer chomp: true/false option or such - why? why not?
200 | ## see String.lines in rdoc
201 | ##
202 | def read_lines( path )
203 | read_text( path ).lines
204 | end
205 |
206 | def parse_lines( str )
207 | str.lines
208 | end
209 |
210 | def download_lines( url )
211 | parse_lines( download_text( url ))
212 | end
213 |
214 |
215 |
216 | def read_env( path )
217 | parse_env( read_text( path ))
218 | end
219 |
220 | def parse_env( str )
221 | EnvParser.load( str )
222 | end
223 |
224 |
225 | ##
226 | ## todo/check - change path to *paths=['./.env']
227 | ## and support more files - why? why not?
228 | def load_env( path='./.env' )
229 | if File.exist?( path )
230 | puts "==> loading .env settings..."
231 | env = read_env( path )
232 | puts " applying .env settings... (merging into ENV)"
233 | pp env
234 | ## note: will only add .env setting if NOT present in ENV!!!
235 | env.each do |k,v|
236 | ENV[k] ||= v
237 | end
238 | end
239 | end
240 |
241 |
242 |
243 |
244 | ######
245 | # add writers
246 |
247 | def write_json( path, data )
248 | ###
249 | ## todo/check: check if data is Webclient.Response?
250 | ## if yes use res.json - why? why not?
251 |
252 | dirname = File.dirname( path )
253 | FileUtils.mkdir_p( dirname ) unless Dir.exist?( dirname )
254 |
255 | ## note: pretty print/reformat json
256 | File.open( path, 'w:utf-8' ) do |f|
257 | f.write( JSON.pretty_generate( data ))
258 | end
259 | end
260 |
261 |
262 | def write_blob( path, blob )
263 | ###
264 | ## todo/check: check if data is Webclient.Response?
265 | ## if yes use res.blob/body - why? why not?
266 |
267 | dirname = File.dirname( path )
268 | FileUtils.mkdir_p( dirname ) unless Dir.exist?( dirname )
269 |
270 | File.open( path, 'wb' ) do |f|
271 | f.write( blob )
272 | end
273 | end
274 | # alias_method :write_binary, :write_blob
275 | # alias_method :write_bin, :write_blob
276 |
277 |
278 | def write_text( path, text )
279 | ###
280 | ## todo/check: check if data is Webclient.Response?
281 | ## if yes use res.text - why? why not?
282 |
283 | dirname = File.dirname( path )
284 | FileUtils.mkdir_p( dirname ) unless Dir.exist?( dirname )
285 |
286 | File.open( path, 'w:utf-8' ) do |f|
287 | f.write( text )
288 | end
289 | end
290 | alias_method :write_txt, :write_text
291 |
292 |
293 |
294 | #
295 | # note:
296 | # for now write_csv expects array of string arrays
297 | # does NOT support array of hashes for now
298 |
299 | def write_csv( path, recs, headers: nil )
300 | dirname = File.dirname( path )
301 | FileUtils.mkdir_p( dirname ) unless Dir.exist?( dirname )
302 |
303 | File.open( path, 'w:utf-8' ) do |f|
304 | if headers
305 | f.write( headers.join(',')) ## e.g. Date,Team 1,FT,HT,Team 2
306 | f.write( "\n" )
307 | end
308 |
309 | recs.each do |values|
310 | ## quote values that incl. a comma
311 | ## todo/fix - add more escape/quote checks - why? why not?
312 | ## check how other csv libs handle value generation
313 | buf = values.map do |value|
314 | if value.index(',')
315 | %Q{"#{value}"}
316 | else
317 | value
318 | end
319 | end.join( ',' )
320 |
321 | f.write( buf )
322 | f.write( "\n" )
323 | end
324 | end
325 | end
326 |
327 |
328 |
329 | ######
330 | # world wide web (www) support
331 |
332 | def wget( url, **opts )
333 | Webclient.get( url, **opts )
334 | end
335 | ## add alias www_get or web_get - why? why not?
336 |
337 | def wget!( url, **opts )
338 | res = Webclient.get( url, **opts )
339 |
340 | ## check/todo - use a different exception/error - keep RuntimeError - why? why not?
341 | raise RuntimeError, "HTTP #{res.status.code} - #{res.status.message}" if res.status.nok?
342 |
343 | res
344 | end
345 |
346 |
347 | end # module Kernel
348 |
349 |
350 |
351 |
352 |
353 | ####
354 | # convenience alias (use plural or singual)
355 | Coco = Cocos
356 |
357 |
358 | puts Cocos.banner ## say hello
359 |
360 |
--------------------------------------------------------------------------------
/lib/cocos/env.rb:
--------------------------------------------------------------------------------
1 | ###
2 | # simple read_env, load_env machinery
3 | ## inspired by
4 | ## dotenv gem -> https://github.com/bkeepers/dotenv
5 | ## figaro -> https://github.com/laserlemon/figaro
6 | ## and others
7 |
8 |
9 | ## todo/check:
10 | ## move to its own (standandaloen) envparser gem - why? why not?
11 |
12 | module EnvParser
13 | # returns a hash
14 | # (compatible structure - works like YAML.load_file)
15 | #
16 | # change to .read(path) and parse( text) - why? why not?
17 | def self.load_file( path )
18 | text = File.open( path, 'r:utf-8' ) { |f| f.read }
19 | parse( text )
20 | end
21 | def self.load( text ) parse( text ); end
22 |
23 |
24 | class Error < StandardError; end
25 |
26 | ## todo/check - what is JSON and YAML returning Parser/ParseError something else?
27 | ## YAML uses ParseError and JSON uses ParserError
28 | class ParseError < Error; end
29 |
30 |
31 | ## todo/check - if support for empty values e.g. abc= is required/possible???
32 | ## todo/ addd support for quoted values - why? why not?
33 | ## add support for "inline" end of line comments - why? why not?
34 | ## add support for escapes and multi-line values - why? why not?
35 | LINE_RX = /\A(?[A-Za-z][A-Za-z0-9_-]*)
36 | [ ]*
37 | =
38 | [ ]*
39 | (?.+?) ## non-greedy
40 | \z
41 | /x
42 |
43 | ## use a parser class - why? why not?
44 | def self.parse( text )
45 | h = {}
46 |
47 | lineno = 0
48 | text.each_line do |line|
49 | lineno += 1 ## track line numbers for (parse) error reporting
50 |
51 | line = line.strip ## check: use strip (or be more strict) - why? why not?
52 | ## skip empty and comment lines
53 | next if line.empty? || line.start_with?( '#' )
54 |
55 | if m=LINE_RX.match(line)
56 | key = m[:key]
57 | value = m[:value]
58 |
59 | ## todo/check - check/warn about duplicates - why? why not?
60 | h[key] = value
61 | else
62 | raise ParseError, "line #{lineno} - unknown line type; cannot parse >#{line}<"
63 | end
64 | end
65 | h
66 | end # methdod self.parse
67 | end # module EnvParser
68 |
69 |
70 |
71 | __END__
72 |
73 | # parser regex from dotenv
74 |
75 | LINE = /
76 | (?:^|\A) # beginning of line
77 | \s* # leading whitespace
78 | (?:export\s+)? # optional export
79 | ([\w.]+) # key
80 | (?:\s*=\s*?|:\s+?) # separator
81 | ( # optional value begin
82 | \s*'(?:\\'|[^'])*' # single quoted value
83 | | # or
84 | \s*"(?:\\"|[^"])*" # double quoted value
85 | | # or
86 | [^\#\r\n]+ # unquoted value
87 | )? # value end
88 | \s* # trailing whitespace
89 | (?:\#.*)? # optional comment
90 | (?:$|\z) # end of line
91 | /x
92 |
--------------------------------------------------------------------------------
/lib/cocos/version.rb:
--------------------------------------------------------------------------------
1 |
2 | module Cocos
3 | MAJOR = 0 ## todo: namespace inside version or something - why? why not??
4 | MINOR = 4
5 | PATCH = 0
6 | VERSION = [MAJOR,MINOR,PATCH].join('.')
7 |
8 | def self.version
9 | VERSION
10 | end
11 |
12 | def self.banner
13 | "cocos/#{VERSION} on Ruby #{RUBY_VERSION} (#{RUBY_RELEASE_DATE}) [#{RUBY_PLATFORM}] in (#{root})"
14 | end
15 |
16 | def self.root
17 | File.expand_path( File.dirname(File.dirname(File.dirname(__FILE__) )))
18 | end
19 | end # module Cocos
20 |
--------------------------------------------------------------------------------
/sandbox/test_blob.rb:
--------------------------------------------------------------------------------
1 | require 'cocos'
2 |
3 |
4 | ## prepare test for read_blob/binary/bin
5 |
6 |
7 | blob = read_blob( "./test/data/marilyn.png" )
8 |
9 | puts
10 | puts Base64.encode64( blob )
11 | #=>
12 |
13 | blob2 = Base64.decode64( < 'hello' } )
9 |
10 |
11 | recs = [['a1', 'b1'],
12 | ['a2', 'b2'],
13 | ['a3,x','b3'],
14 | ['a4', 'b4,y']]
15 | write_csv( "./tmp/one.csv", recs )
16 | write_csv( "./tmp/two.csv", recs, headers: ['a', 'b'] )
17 |
18 | puts "bye"
19 |
20 |
--------------------------------------------------------------------------------
/test/data/beer.a.csv:
--------------------------------------------------------------------------------
1 | Andechser Klosterbrauerei,Andechs,Doppelbock Dunkel,7%
2 | Augustiner Bräu München,München,Edelstoff,5.6%
3 | Bayerische Staatsbrauerei Weihenstephan,Freising,Hefe Weissbier,5.4%
4 | Brauerei Spezial,Bamberg,Rauchbier Märzen,5.1%
5 | Hacker-Pschorr Bräu,München,Münchner Dunkel,5.0%
6 | Staatliches Hofbräuhaus München,München,Hofbräu Oktoberfestbier,6.3%
7 |
--------------------------------------------------------------------------------
/test/data/beer.csv:
--------------------------------------------------------------------------------
1 | Brewery,City,Name,Abv
2 | Andechser Klosterbrauerei,Andechs,Doppelbock Dunkel,7%
3 | Augustiner Bräu München,München,Edelstoff,5.6%
4 | Bayerische Staatsbrauerei Weihenstephan,Freising,Hefe Weissbier,5.4%
5 | Brauerei Spezial,Bamberg,Rauchbier Märzen,5.1%
6 | Hacker-Pschorr Bräu,München,Münchner Dunkel,5.0%
7 | Staatliches Hofbräuhaus München,München,Hofbräu Oktoberfestbier,6.3%
8 |
--------------------------------------------------------------------------------
/test/data/manifest.txt:
--------------------------------------------------------------------------------
1 | CHANGELOG.md
2 | LICENSE.md
3 | Manifest.txt
4 | README.md
5 | Rakefile
6 | lib/cocos.rb
7 | lib/cocos/version.rb
8 |
--------------------------------------------------------------------------------
/test/data/marilyn.json:
--------------------------------------------------------------------------------
1 | {
2 | "attributes": [
3 | {"name": "Hair", "value": "Wild Blonde"},
4 | {"name": "Blemish", "value": "Mole"},
5 | {"name": "Expression", "value": "Smile"}
6 | ]
7 | }
--------------------------------------------------------------------------------
/test/data/marilyn.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rubycocos/cocos/5ae85ba802a99b0d97125bd20f28003e577b9443/test/data/marilyn.png
--------------------------------------------------------------------------------
/test/data/marilyn.yaml:
--------------------------------------------------------------------------------
1 | attributes:
2 | - name: Hair
3 | value: Wild Blonde
4 | - name: Blemish
5 | value: Mole
6 | - name: Expression
7 | value: Smile
8 |
--------------------------------------------------------------------------------
/test/data/planet.ini:
--------------------------------------------------------------------------------
1 | title = Planet Open Data News
2 |
3 | [osm]
4 | title = Open Street Map (OSM) News
5 | link = https://blog.openstreetmap.org
6 | feed = https://blog.openstreetmap.org/feed/
--------------------------------------------------------------------------------
/test/data/test.tab:
--------------------------------------------------------------------------------
1 | a b c
2 | 1 2 3
3 | 4 5 6
4 |
--------------------------------------------------------------------------------
/test/helper.rb:
--------------------------------------------------------------------------------
1 | # minitest setup
2 | require 'minitest/autorun'
3 |
4 |
5 | ## our own code
6 | require 'cocos'
7 |
8 |
--------------------------------------------------------------------------------
/test/test_env.rb:
--------------------------------------------------------------------------------
1 | ###
2 | # to run use
3 | # ruby -I ./lib -I ./test test/test_env.rb
4 |
5 |
6 | require_relative 'helper'
7 |
8 |
9 | class TestEnv < Minitest::Test
10 |
11 | ENV_TXT =< '123',
19 | 'def' => '456'
20 | }
21 |
22 | def test_env
23 | assert_equal ENV_HASH, parse_env( ENV_TXT )
24 | end
25 |
26 | end # class TestEnv
27 |
--------------------------------------------------------------------------------
/test/test_readers.rb:
--------------------------------------------------------------------------------
1 | ###
2 | # to run use
3 | # ruby -I ./lib -I ./test test/test_readers.rb
4 |
5 |
6 | require_relative 'helper'
7 |
8 |
9 | class TestReaders < Minitest::Test
10 |
11 | MANIFEST_TXT =<"Andechser Klosterbrauerei",
52 | "City"=>"Andechs",
53 | "Name"=>"Doppelbock Dunkel",
54 | "Abv"=>"7%"},
55 | {"Brewery"=>"Augustiner Bräu München",
56 | "City"=>"München",
57 | "Name"=>"Edelstoff",
58 | "Abv"=>"5.6%"},
59 | {"Brewery"=>"Bayerische Staatsbrauerei Weihenstephan",
60 | "City"=>"Freising",
61 | "Name"=>"Hefe Weissbier",
62 | "Abv"=>"5.4%"},
63 | {"Brewery"=>"Brauerei Spezial",
64 | "City"=>"Bamberg",
65 | "Name"=>"Rauchbier Märzen",
66 | "Abv"=>"5.1%"},
67 | {"Brewery"=>"Hacker-Pschorr Bräu",
68 | "City"=>"München",
69 | "Name"=>"Münchner Dunkel",
70 | "Abv"=>"5.0%"},
71 | {"Brewery"=>"Staatliches Hofbräuhaus München",
72 | "City"=>"München",
73 | "Name"=>"Hofbräu Oktoberfestbier",
74 | "Abv"=>"6.3%"}]
75 |
76 | def test_csv
77 | assert_equal BEER_ARY, read_data( "./test/data/beer.a.csv" )
78 |
79 | assert_equal BEER_HASH, read_csv( "./test/data/beer.csv" )
80 |
81 | assert_equal BEER_ARY, parse_data( < [
125 | {"name"=>"Hair", "value"=>"Wild Blonde"},
126 | {"name"=>"Blemish", "value"=>"Mole"},
127 | {"name"=>"Expression", "value"=>"Smile"}]}
128 |
129 | def test_json
130 | assert_equal MARILYN_HASH, read_json( "./test/data/marilyn.json" )
131 | assert_equal MARILYN_HASH, parse_json( <"Planet Open Data News",
157 | "osm"=> {"title"=>"Open Street Map (OSM) News",
158 | "link"=>"https://blog.openstreetmap.org",
159 | "feed"=>"https://blog.openstreetmap.org/feed/"}}
160 |
161 | def test_ini
162 | assert_equal PLANET_HASH, read_ini( "./test/data/planet.ini" )
163 | assert_equal PLANET_HASH, parse_ini( <