├── .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( <