Yuki & Moto Press

Gem Series ++ Web Services (HTTP JSON APIs) the Modern Micro Way (Book Edition)

by Gerald Bauer, et al

1. almost-sinatra - Build Your Own (Micro) Webframework From Scratch with Rack and Tilt in Less Than Ten Lines

2. webservice - Script (Micro) Web Services (HTTP JSON APIs); Load (Micro) Web Services At-Runtime and More

3. worlddb - (Micro) Web Services (HTTP JSON APIs) Case Study - Seven Continents and the Countries of the World

4. beerdb - (Micro) Web Services (HTTP JSON APIs) Case Study - Cheers, Prost, Kampai, Na zdravi, Salute, 乾杯, Skål, Egészségedre!

5. sportdb - (Micro) Web Services (HTTP JSON APIs) Case Study - The World's Biggest Event - Russia 2018 - Football World Cup

6. feedparser - Meet Jason Feed - The Universal Content (Micro) Web Service (HTTP JSON API) - The Future of Publishing 'n' Online News

7. json-next - Comments, Please! - JSON 1.0, JSON 1.1, JSON What's Next?

8. feedtxt - TXT is the new JSON Case Study - Feeds in Text (Unicode) - Publish 'n' Share Posts, Articles, Podcasts, 'n' More Contents

1. almost-sinatra - Build Your Own (Micro) Webframework From Scratch with Rack and Tilt in Less Than Ten Lines Six lines of almost sinatra “unobfuscated” and bundled up for easy (re)use github: rubylibs/almost-sinatra, rubygems: almost-sinatra, rdoc: almost-sinatra ++ more: comments on reddit, please! What’s Sinatra? Simple (yet powerful and flexible) micro webframework. require 'sinatra' get '/' do 'Hallo Vienna! Servus Wien!' end Trivia Quiz - Q: How Many Lines of Ruby Code? [A] 20 Lines

[B] 200 Lines

[C] 2 000 Lines

[D] 20 000 Lines What’s Almost Sinatra? Sinatra refactored, only six lines now. Library dependencies: Tilt and Rack (like Sinatra).

A hack by Konstantin Haase. %w.rack tilt backports date INT TERM. . map { | l | trap ( l ){ $r . stop } rescue require l }; $u = Date ; $z = ( $u . new . year + 145 ). abs ; puts "== Almost Sinatra/No Version has taken the stage on #$z for development with backup from Webrick" $n = Sinatra = Module . new { a , D , S , q = Rack :: Builder . new , Object . method ( :define_method ), /@@ *([^

]+)

(((?!@@)[^

]*

)*)/m %w[get post put delete] . map { | m | D . ( m ){ | u , & b | a . map ( u ){ run -> ( e ){[ 200 ,{ "Content-Type" => "text/html" },[ a . instance_eval ( & b )]]}}}} Tilt . default_mapping . lazy_map . map { | k , v | D . ( k ){ | n , * o | $t ||= ( h = $u . _jisx0301 ( "hash, please" ); File . read ( caller [ 0 ][ /^[^:]+/ ]). scan ( S ){ | a , b | h [ a ] = b }; h ); Kernel . const_get ( v [ 0 ][ 0 ]). new ( * o ){ n == " #{ n } " ? n : $t [ n . to_s ]}. render ( a , o [ 0 ]. try ( :[] , :locals ) || {})}} %w[set enable disable configure helpers use register] . map { | m | D . ( m ){ |* _ , & b | b . try :[] }}; END { Rack :: Handler . get ( "webrick" ). run ( a , Port : $z ){ | s | $r = s }} %w[params session] . map { | m | D . ( m ){ q . send m }}; a . use Rack :: Session :: Cookie ; a . use Rack :: Lock ; D . ( :before ){ |& b | a . use Rack :: Config , & b }; before { | e | q = Rack :: Request . new e ; q . params . dup . map { | k , v | params [ k . to_sym ] = v }}} (Source: almost_sinatra.rb) Almost Sinatra - A Breakdown Line by Line Line 1 %w.rack tilt date INT TERM. . map { | l | trap ( l ){ $r . stop } rescue require l }; $u = Date ; $z = ( $u . new . year + 145 ). abs ; puts "== Almost Sinatra/No Version has taken the stage on #$z for development with backup from Webrick" Breakdown: require 'rack' require 'tilt' trap ( 'INT' ) { $server . stop } # rename $r to $server trap ( 'TERM' ) { $server . stop } $port = 4567 # rename $z to $port puts "== Almost Sinatra has taken the stage on #{ $port } for development with backup from Webrick" Aside - What’s rack? Lets you mix ‘n’ match servers and apps. Lets you stack apps inside apps inside apps inside apps inside apps. Good News: A Sinatra app is a Rack app. Learn more about Rack @ rack.github.io . Aside - What’s tilt? Tilt offers a standard “generic” interface for template engines. Let’s check-up what formats and template engines tilt includes out-of-the-box: require 'tilt' Tilt . mappings . each do | ext , engines | puts " #{ ext . ljust ( 12 ) } : #{ engines . inspect } " end Will result in: str : [ Tilt :: StringTemplate ] erb : [ Tilt :: ErubisTemplate , Tilt :: ERBTemplate ] rhtml : [ Tilt :: ErubisTemplate , Tilt :: ERBTemplate ] erubis : [ Tilt :: ErubisTemplate ] etn : [ Tilt :: EtanniTemplate ] etanni : [ Tilt :: EtanniTemplate ] haml : [ Tilt :: HamlTemplate ] sass : [ Tilt :: SassTemplate ] scss : [ Tilt :: ScssTemplate ] less : [ Tilt :: LessTemplate ] rcsv : [ Tilt :: CSVTemplate ] coffee : [ Tilt :: CoffeeScriptTemplate ] nokogiri : [ Tilt :: NokogiriTemplate ] builder : [ Tilt :: BuilderTemplate ] mab : [ Tilt :: MarkabyTemplate ] liquid : [ Tilt :: LiquidTemplate ] radius : [ Tilt :: RadiusTemplate ] markdown : [ Tilt :: RedcarpetTemplate , Tilt :: RedcarpetTemplate :: Redcarpet2 , Tilt :: RedcarpetTemplate :: Redcarpet1 , Tilt :: RDiscountTemplate , Tilt :: BlueClothTemplate , Tilt :: KramdownTemplate , Tilt :: MarukuTemplate ] mkd : [ Tilt :: RedcarpetTemplate , Tilt :: RedcarpetTemplate :: Redcarpet2 , Tilt :: RedcarpetTemplate :: Redcarpet1 , Tilt :: RDiscountTemplate , Tilt :: BlueClothTemplate , Tilt :: KramdownTemplate , Tilt :: MarukuTemplate ] md : [ Tilt :: RedcarpetTemplate , Tilt :: RedcarpetTemplate :: Redcarpet2 , Tilt :: RedcarpetTemplate :: Redcarpet1 , Tilt :: RDiscountTemplate , Tilt :: BlueClothTemplate , Tilt :: KramdownTemplate , Tilt :: MarukuTemplate ] textile : [ Tilt :: RedClothTemplate ] rdoc : [ Tilt :: RDocTemplate ] wiki : [ Tilt :: WikiClothTemplate , Tilt :: CreoleTemplate ] creole : [ Tilt :: CreoleTemplate ] mediawiki : [ Tilt :: WikiClothTemplate ] mw : [ Tilt :: WikiClothTemplate ] yajl : [ Tilt :: YajlTemplate ] ad : [ Tilt :: AsciidoctorTemplate ] adoc : [ Tilt :: AsciidoctorTemplate ] asciidoc : [ Tilt :: AsciidoctorTemplate ] html : [ Tilt :: PlainTemplate ] Line 2 $n = Module . new { extend Rack ; a , D , S , q = Rack :: Builder . new , Object . method ( :define_method ), /@@ *([^

]+)

(((?!@@)[^

]*

)*)/m Breakdown: $n = Module . new do app = Rack :: Builder . new # rename a to app req = nil # rename q to req Line 3 %w[get post put delete] . map { | m | D . ( m ){ | u , & b | a . map ( u ){ run -> ( e ){[ 200 ,{ "Content-Type" => "text/html" },[ a . instance_eval ( & b )]]}}}} Breakdown: [ 'get' , 'post' , 'put' , 'delete' ]. each do | method | define_method method do | path , & block | app . map ( path ) do run -> ( env ){ [ 200 , { 'Content-Type' => 'text/html' }, [ app . instance_eval ( & block )]]} end end end Line 4 Tilt . mappings . map { | k , v | D . ( k ){ | n , * o | $t ||= ( h = $u . _jisx0301 ( "hash, please" ); File . read ( caller [ 0 ][ /^[^:]+/ ]). scan ( S ){ | a , b | h [ a ] = b }; h ); v [ 0 ]. new ( * o ){ n == " #{ n } " ? n : $t [ n . to_s ]}. render ( a , o [ 0 ]. try ( :[] , :locals ) || {})}} Breakdown: Tilt . mappings . each do | ext , engines | # rename k to ext and v to engines define_method ext do | text , * args | # rename n to text and o to args template = engines [ 0 ]. new ( * args ) do text end locals = ( args [ 0 ]. respond_to? ( :[] ) ? args [ 0 ][: locals ] : nil ) || {} # was o[0].try(:[],:locals)||{} template . render ( app , locals ) end end Commentary: Almost Sinatra will define a method for every format so you can use, for example: markdown "Strong emphasis, aka bold, with **asterisks** or __underscores__." or erb "Hello <%= name %>!" , locals: { name: params [ 'name' ] } Line 5 %w[set enable disable configure helpers use register] . map { | m | D . ( m ){ |* _ , & b | b . try :[] }}; END { Rack :: Handler . get ( "webrick" ). run ( a , Port : $z ){ | s | $r = s }} Breakdown: # was END { ... }; change to run! method define_method 'run!' do Rack :: Handler . get ( 'webrick' ). run ( app , Port : $port ) { | server | $server = server } end Line 6 %w[params session] . map { | m | D . ( m ){ q . send m }}; a . use Rack :: Session :: Cookie ; a . use Rack :: Lock ; D . ( :before ){ |& b | a . use Rack :: Config , & b }; before { | e | q = Rack :: Request . new e ; q . params . dup . map { | k , v | params [ k . to_sym ] = v }}} Breakdown: [ 'params' , 'session' ]. each do | method | define_method method do req . send method end end app . use Rack :: Session :: Cookie app . use Rack :: Lock app . use Rack :: Config do | env | req = Rack :: Request . new ( env ) end end # Module.new The Proof of the Pudding - hello.rb samples/hello.rb : require 'almost-sinatra' include $n # include "anonymous" Almost Sinatra DSL module get '/hello' do erb "Hello <%= name %>!" , locals: { name: params [ 'name' ] } end get '/' do markdown << EOS ## Welcome to Almost Sinatra A six line ruby hack by Konstantin Haase. Try: - [Say hello!](/hello?name=Nancy) Powered by Almost Sinatra ( #{ Time . now } ) EOS end run! Use $ ruby -I ./lib ./samples/hello.rb Got Inspired? Build Your Own Microframework New York, New York

Nancy

Rum

Cuba

Roda

and many more Real World Case Study - webservice library - (Yet Another) HTTP JSON API (Web Service) Builder Micro “framework” for building HTTP JSON APIs (about 100 lines of code). Example: get '/beer/:key' do Beer . find_by_key! params [ :key ] end get '/brewery/:key' do Brewery . find_by_key! params [ :key ] end Decoding Almost Sinatra by Rob Miller

Code Safari: Almost Sinatra, Almost Readable by Xavier Shay

tilt library - let’s build (yet another) micro web framework in less than 33 lines of code by Gerald Bauer Bonus: Fun Almost Sinatra Obfuscation Hacks “Calculate” the Sinatra Port 4567: ( Date . new . year + 145 ). abs # Date.new.year always returns -4712, the default value for years # => 4567 “Get” empty Hash (e.g. {} ): Date . _jisx0301 ( "hash, please" ) # => {}

2. webservice - Script (Micro) Web Services (HTTP JSON APIs); Load (Micro) Web Services At-Runtime and More Get a free wiener lager, welsh red ale or kriek lambic beer delivered to your home (computer) in JSON and much much more github: rubylibs/webservice, rubygems: webservice, rdoc: webservice ++ more: comments on reddit, please! What’s the webservice library? The webservice library lets you script HTTP JSON APIs also known as web services or microservices in classy Sinatra 2.0-style get / post methods with Mustermann 1.0 route / url pattern matching. Dynamic Example You can load web services at-runtime from files using Webservice.load_file . Example: # service.rb get '/' do 'Hello, world!' end and # server.rb require 'webservice' App = Webservice . load_file ( './service.rb' ) App . run! and to run type $ ruby ./server.rb Classic Example # server.rb require 'webservice' class App < Webservice :: Base get '/' do 'Hello, world!' end end App . run! and to run type $ ruby ./server.rb Rackup Example Use config.ru and rackup . Example: # config.ru require `webservice` class App < Webservice :: Base get '/' do 'Hello, world!' end end run App and to run type $ rackup # will (auto-)load config.ru Note: config.ru is a shortcut (inline) version of Rack::Builder.new do ... end : # server.rb require 'webservice' class App < Webservice :: Base get '/' do 'Hello, world!' end end builder = Rack :: Builder . new do run App end Rack :: Server . start builder . to_app and to run type $ ruby ./server.rb Bonus - “Real World” Examples See beerkit / beer.db.service - beer.db HTTP JSON API (web service) scripts e.g. get '/beer/(r|rnd|rand|random)' do # special keys for random beer Beer . rnd end get '/beer/:key' Beer . find_by! key: params [ 'key' ] end get '/brewery/(r|rnd|rand|random)' do # special keys for random brewery Brewery . rnd end get '/brewery/:key' Brewery . find_by! key: params [ 'key' ] end ... worlddb / world.db.service - world.db HTTP JSON API (web service) scripts get '/countries(.:format)?' do Country . by_key . all # sort/order by key end get '/cities(.:format)?' do City . by_key . all # sort/order by key end get '/tag/:slug(.:format)?' do # e.g. /tag/north_america.csv Tag . find_by! ( slug: params [ 'slug' ] ). countries end ... sportdb / sport.db.service - sport.db (football.db) HTTP JSON API (web service) scripts # to be done ...

3. worlddb - (Micro) Web Services (HTTP JSON APIs) Case Study - Seven Continents and the Countries of the World Usage Models Class Model Diagrams Everything is a place. Country Model - Example: at = Country . find_by! key: 'at' at . name # => 'Austria' at . pop # => 8_414_638 at . area # => 83_871 at . states . count # => 9 at . states # => [ 'Wien', 'Niederösterreich', 'Oberösterreich', ... ] at . cities . by_pop # => [ 'Wien', 'Graz', 'Linz', 'Salzburg', 'Innsbruck' ... ] City Model - Example: c = City . find_by! key: 'wien' c . name # => 'Wien' c . country . name # => 'Austria' c . country . continent . name # => 'Europe' la = City . find_by! key: 'losangeles' la . name # => 'Los Angeles' la . state . name # => 'California' la . state . key # => 'ca' la . country . name # => 'United States' la . country . key # => 'us' la . country . continent . name # => 'North America' Tag Model - Example: euro = Tag . find_by! key: 'euro' euro . countries . count # => 17 euro . countries # => ['Austria, 'Belgium', 'Cyprus', ... ] flanders = Tag . find_by! key: 'flanders' flanders . states . count # => 5 flanders . states # => ['Antwerpen', 'Brabant Wallon', 'Limburg', 'Oost-Vlaanderen', 'West-Vlaanderen'] flanders . states . first . country . name # => 'Belgium' and so on. world.db Web Service (HTTP JSON API) Starter Sample The worlddb web service starter sample lets you build your own HTTP JSON API using the world.db . Example: class StarterApp < Webservice :: Base ##################### # Models include WorldDb :: Models # e.g. Continent, Country, State, City, etc. ############################################## # Controllers / Routing / Request Handlers get '/countries(.:format)?' do Country . by_key . all # sort/order by key end get '/cities(.:format)?' do City . by_key . all # sort/order by key end get '/tag/:slug(.:format)?' do # e.g. /tag/north_america.csv Tag . find_by! ( slug: params [ 'slug' ] ). countries end ... end # class StarterApp (Source: app.rb ) Getting Started Step 1: Install all libraries (Ruby gems) using bundler. Type: $ bundle install Step 2: Copy an SQLite database e.g. world.db into your folder. Step 3: Startup the web service (HTTP JSON API). Type: $ ruby ./server.rb That’s it. Open your web browser and try some services running on your machine on port 9292 (e.g. localhost:9292 ). Example: List all the world countries (in JSON - the default format): http://localhost:9292/countries List all the world countries (in CSV): http://localhost:9292/countries.csv List all the world countries (in HTML w/ simple table): http://localhost:9292/countries.html List all cities (in JSON): http://localhost:9292/cities List all cities (in CSV): http://localhost:9292/cities.csv List all countries tagged with north_america (in JSON): http://localhost:9292/tag/north_america List all countries tagged with north_america (in CSV): http://localhost:9292/tag/north_america.csv And so on. Now change the app.rb script to fit your needs. Be bold. Enjoy. world.db HTTP JSON API (web service) scripts world.db HTTP JSON API (web service) scripts Usage You can run any of the scripts using the worlddb command line tool. By default the serve command will look for a script named Service or service.rb (in the working folder, that is, ./ ). Example: $ worlddb serve To run any other script - copy the script into the working folder and pass it along as an argument. Example: $ worlddb serve starter # note: will (auto-)add the .rb extension or $ worlddb serve starter.rb starter.rb - Starter world.db HTTP JSON API get '/countries(.:format)?' do Country . by_key . all # sort/order by key end get '/cities(.:format)?' do City . by_key . all # sort/order by key end get '/tag/:slug(.:format)?' do # e.g. /tag/north_america.csv Tag . find_by! ( slug: params [ 'slug' ] ). countries end (Source: world.db.service/starter.rb) service.rb - Service world.db HTTP JSON API get '/countries(.:format)?' do Country . by_key . all # sort/order by key end get '/cities(.:format)?' do City . by_key . all # sort/order by key end get '/continents(.:format)?' do Continent . all end get '/tag/:slug(.:format)?' do # e.g. /tag/north_america.csv Tag . find_by! ( slug: params [ 'slug' ] ). countries end (Source: world.db.service/service.rb)

4. beerdb - (Micro) Web Services (HTTP JSON APIs) Case Study - Cheers, Prost, Kampai, Na zdravi, Salute, 乾杯, Skål, Egészségedre! Cheers, Prost, Kampai, Na zdravi, Salute, 乾杯, Skål, Egészségedre! github: beerkit/beer.db, rubygems: beerdb, rdoc: beerdb ++ more: comments on reddit, please! What’s the beerdb library? The beerdb library offers a ready-to-use database schema (in SQL) and models such as - surprise, surprise - Beer , Brewery , Brand and friends (using the ActiveRecord object-relational mapper machinery). Example: Let’s try the brewery model: by = Brewery . find_by ( key: 'guinness' ) by . title #=> 'St. James's Gate Brewery / Guinness Brewery' by . country . key #=> 'ie' by . country . title #=> 'Ireland' by . city . title #=> 'Dublin' by . beers . first #=> 'Guinness', 4.2 ... Or let’s try the beer model: b = Beer . find_by ( key: 'guinness' ) b . title #=> 'Guinness' b . abv # that is, alcohol by volume (abv) #=> 4.2 b . tags #=> 'irish_dry_stout', 'dry_stout', 'stout' b . brewery . title #=> 'St. James's Gate Brewery / Guinness Brewery' ... What’s it good for? Good question. Let’s build an HTTP JSON service that serves up a Guinness Irish Stout or a Bamberg Aecht Schlenkerla Rauchbier Märzen as JSON? Example - GET /beer/guinness : { "key" : "guinness" , "title" : "Guinness" , "synonyms" : "Guinness Draught" , "abv" : "4.2" , "srm" : null , "og" : null , "tags" : [ "irish_dry_stout" , "dry_stout" , "stout" ], "brewery" : { "key" : "guinness" , "title" : "St. James's Gate Brewery / Guinness Brewery" }, "country" : { "key" : "ie" , "title" : "Irland" } } Let’s use the Sinatra-like webservice library that offers a mini language, that is, domain-specific language (DSL) that lets you define routes, that is, HTTP methods paired with an URL-matching pattern and much more. For example, you can code the GET /beer/guinness route in the webservice library as get '/beer/guinness' . To make it into a route for any beer lets replace the guinness beer key with a placeholder, thus, resulting in get '/beer/:key' . Let’s run it: service.rb : class BeerService < Webservice :: Base include BeerDb :: Models # lets (re)use the Beer, Brewery, etc. models get '/beer/:key' do Beer . find_by! ( key: params [ :key ] ) end end That’s it. Ready to serve. Let’s boot-up the beer service with a web server (e.g. Thin) using a Rack handler. Example: boot.rb : require 'webservice' # note: webservice will pull in web server machinery (e.g. rack, thin, etc.) require 'beerdb/models' # note: beerdb will pull in database access machinery (e.g. activerecord, etc.) # database setup 'n' config ActiveRecord :: Base . establish_connection ( adapter: 'sqlite3' , database: './beer.db' ) require './service' Rack :: Handler :: Thin . run BeerService , :Port => 9292 Try: $ ruby ./boot.rb Open up your browser and try http://localhost:9292/beer/guinness . Voila. Enjoy your Guinness irish stout responsibly. Bonus: Let’s add brewery details and more Let’s add brewery details to the beer service and lets add a new GET /brewery route. Example: get '/beer/:key' do beer = Beer . find_by! ( key: params [ :key ] ) brewery = {} if beer . brewery . present? brewery = { key: beer . brewery . key , title: beer . brewery . title } end tags = [] if beer . tags . present? beer . tags . each { | tag | tags << tag . key } end { key: beer . key , title: beer . title , synonyms: beer . synonyms , abv: beer . abv , srm: beer . srm , og: beer . og , tags: tags , brewery: brewery , country: { key: beer . country . key , title: beer . country . title } } end get '/brewery/:key' do brewery = Brewery . find_by! ( key: params [ :key ] ) beers = [] brewery . beers . each do | b | beers << { key: b . key , title: b . title } end tags = [] if brewery . tags . present? brewery . tags . each { | tag | tags << tag . key } end { key: brewery . key , title: brewery . title , synonyms: brewery . synonyms , since: brewery . since , address: brewery . address , web: brewery . web , tags: tags , beers: beers , country: { key: brewery . country . key , title: brewery . country . title } } end beer.db HTTP JSON API (web service) scripts beer.db HTTP JSON API (web service) scripts Usage You can run any of the scripts using the beerdb command line tool. By default the serve command will look for a script named Service or service.rb (in the working folder, that is, ./ ). Example: $ beerdb serve To run any other script - copy the script into the working folder and pass it along as an argument. Example: $ beerdb serve starter # note: will (auto-)add the .rb extension or $ beerdb serve starter.rb starter.rb - Starter beer.db HTTP JSON API get '/beer/(r|rnd|rand|random)' do ## special keys for random beer Beer . rnd end get '/beer/:key' do Beer . find_by! key: params [ 'key' ] end get '/brewery/(r|rnd|rand|random)' do ## special keys for random brewery Brewery . rnd end get '/brewery/:key' do Brewery . find_by! key: params [ 'key' ] end (Source: beer.db.service/starter.rb) service.rb - Service beer.db HTTP JSON API get '/' do ## self-docu in json data = { endpoints: { get_beer: { doc: 'get beer by key' , url: '/beer/:key' }, } } end get '/notes/:key' do | key | puts " handle GET /notes/:key" if [ 'l' , 'latest' ]. include? ( key ) # get latest tasting notes (w/ ratings) notes = Note . order ( 'updated_at DESC' ). limit ( 10 ). all elsif [ 'h' , 'hot' ]. include? ( key ) # get latest tasting notes (w/ ratings) # fix: use log algo for "hotness" - for now same as latest notes = Note . order ( 'updated_at DESC' ). limit ( 10 ). all elsif [ 't' , 'top' ]. include? ( key ) notes = Note . order ( 'rating DESC, updated_at DESC' ). limit ( 10 ). all else ### todo: move to /u/:key/notes ?? # assume it's a user key user = User . find_by_key! ( key ) notes = Note . order ( 'rating DESC, updated_at DESC' ). where ( user_id: user . id ). all end data = [] notes . each do | note | data << { beer: { title: note . beer . title , key: note . beer . key }, user: { name: note . user . name , key: note . user . key }, rating: note . rating , comments: note . comments , place: note . place , created_at: note . created_at , updated_at: note . updated_at } end data end get '/notes' do if params [ :method ] == 'post' puts " handle GET /notes?method=post" user = User . find_by_key! ( params [ :user ] ) beer = Beer . find_by_key! ( params [ :beer ] ) rating = params [ :rating ]. to_i place = params [ :place ] # assumes for now a string or nil / pass through as is attribs = { user_id: user . id , beer_id: beer . id , rating: rating , place: place } note = Note . new note . update_attributes! ( attribs ) end { status: 'ok' } end get '/drinks/:key' do | key | puts " handle GET /drinks/:key" if [ 'l' , 'latest' ]. include? ( key ) # get latest +1 drinks ## todo: order by drunk_at?? drinks = Drink . order ( 'updated_at DESC' ). limit ( 10 ). all else ### todo: move to /u/:key/drinks ?? # assume it's a user key user = User . find_by_key! ( key ) drinks = Drink . order ( 'updated_at DESC' ). where ( user_id: user . id ). all end data = [] drinks . each do | drink | data << { beer: { title: drink . beer . title , key: drink . beer . key }, user: { name: drink . user . name , key: drink . user . key }, place: drink . place , drunk_at: drink . drunk_at , created_at: drink . created_at , updated_at: drink . updated_at } end data end get '/drinks' do if params [ :method ] == 'post' puts " handle GET /drinks?method=post" user = User . find_by_key! ( params [ :user ] ) beer = Beer . find_by_key! ( params [ :beer ] ) place = params [ :place ] # assumes for now a string or nil / pass through as is attribs = { user_id: user . id , beer_id: beer . id , place: place } drink = Drink . new drink . update_attributes! ( attribs ) end { status: 'ok' } end get '/beer/:key' do | key | if [ 'r' , 'rnd' , 'rand' , 'random' ]. include? ( key ) # special key for random beer # Note: use .first (otherwise will get ActiveRelation not Model) beer = Beer . rnd . first else beer = Beer . find_by_key! ( key ) end end get '/brewery/:key' do | key | if [ 'r' , 'rnd' , 'rand' , 'random' ]. include? ( key ) # special key for random brewery # Note: use .first (otherwise will get ActiveRelation not Model) brewery = Brewery . rnd . first else brewery = Brewery . find_by_key! ( key ) end end (Source: beer.db.service/service.rb)

5. sportdb - (Micro) Web Services (HTTP JSON APIs) Case Study - The World's Biggest Event - Russia 2018 - Football World Cup World Cup - English Premier League - Spanish La Liga - Austrian Bundesliga :-) And Much More github: sportdb/sport.db, rubygems: sportdb, rdoc: sportdb ++ more: comments on reddit, please! What’s sportdb? A library and command line tool that ships with an ready-to-use sport.db SQL schema and ActiveRecord models and (structured) text-to-data readers (parsers). Usage Models Quick Starter Sample - Kick Off Let’s get started with the Quick Starter Sample. Welcome to the Mauritius Premier Football League. Lets create your own (structured) text datasets from scratch and read it all into your SQL database of choice (e.g. SQLite, PostgreSQL, etc.) with a single command e.g.: $ sportdb build Let’s get started. Follow along these six steps: Step 1: Add all leagues

Step 2: Add all clubs

Step 3: Add all match fixtures and results

Step 4: Add the league season “front matter” settings

Step 5: Add a setups file list (also known as manifest)

Step 6: Add a datafile build script - That’s it. Done. Using a file structure like: ├── 2014-15 # 2014-15 season folder | ├── league-i.txt # match fixtures / results - matchdays 1-18 | ├── league-ii.txt # - matchdays 19-36 | └── league.yml # "front matter" settings ├── setups | └── all.txt # file list (manifest) ├── leagues.txt # all leagues ├── clubs.txt # all clubs └── Datafile # build script Step 1: Add all leagues Example - leagues.txt : mu, Mauritius Premier League Step 2: Add all clubs The Mauritius Premier League includes ten clubs. Example - clubs.txt : joachim, Cercle de Joachim|Cercle de Joachim SC|Joachim, CDJ chamarel, Chamarel|Chamarel SC, CHA curepipesc, Curepipe Starlight|Curepipe SC|Starlight, CUR entente, Entente Boulet Rouge|Entente Boulet Rouge-Riche Mare Rovers, EBR lacure, La Cure Sylvester|La Cure, LCS pamplemousses, Pamplemousses|Pamplemousses SC, PPM petiteriv, Petite Rivière Noire|Petite Rivière, PRN aspl, AS Port-Louis 2000|ASPL 2000, APL qbornes, AS Quatre Bornes|Quatre Bornes, AQB rempart, AS Rivière du Rempart|Rivière du Rempart, ARR Note: Use the pipe ( | ) to list alternative names. Step 3: Add all match fixtures and results Example - 2014-15/league-i.txt : Matchday 1 [Wed Nov/5] Curepipe Starlight 1-3 Petite Rivière Noire AS Quatre Bornes 1-0 La Cure Sylvester Pamplemousses 0-1 Rivière du Rempart AS Port-Louis 2000 5-1 Entente Boulet Rouge Chamarel FC 2-3 Cercle de Joachim Matchday 2 [Sun Nov/9] Curepipe Starlight 2-1 AS Quatre Bornes Entente Boulet Rouge 1-2 Chamarel FC Rivière du Rempart 1-1 AS Port-Louis 2000 La Cure Sylvester 1-2 Pamplemousses Petite Rivière Noire 2-0 Cercle de Joachim Matchday 3 [Wed Nov/12] Chamarel FC 1-1 Rivière du Rempart AS Port-Louis 2000 1-0 La Cure Sylvester Cercle de Joachim 2-2 Entente Boulet Rouge Pamplemousses 0-4 Curepipe Starlight AS Quatre Bornes 1-2 Petite Rivière Noire Matchday 4 [Sun Nov/16] Petite Rivière Noire 4-1 Entente Boulet Rouge Rivière du Rempart 1-1 Cercle de Joachim La Cure Sylvester 0-0 Chamarel FC Curepipe Starlight 0-0 AS Port-Louis 2000 AS Quatre Bornes 1-0 Pamplemousses ... Step 4: Add the league season “front matter” settings Example - 2014-15/league.yml : league : mu season : 2014/15 start_at : 2014-11-05 fixtures : - league-i - league-ii 10 teams : - Cercle de Joachim - AS Port-Louis 2000 - Pamplemousses - Curepipe Starlight - Petite Rivière Noire - Rivière du Rempart - AS Quatre Bornes - Chamarel SC - La Cure Sylvester - Entente Boulet Rouge Step 5: Add a setups file list (also known as manifest) Example - setups/all.txt : mu-mauritius!/leagues mu-mauritius!/clubs mu-mauritius!/2014-15/league Step 6: Add a datafile build script - That’s it. Done. Example - Datafile : ## a) Add country e.g. Mauritius inline do Country . parse 'mu' , 'Mauritius' , 'MUS' , '2_040 km²' , '1_261_200' end ## b) Read in all football datasets in ./mu-mauritius (defaults to setups/all.txt) football 'mu-mauritius' Now try in your working folder: $ sportdb build This will read in the ./Datafile and setup a new single-file SQLite database e.g. ./sport.db

read in all plain text datasets That’s it. Try: $ sqlite3 sport.db SQLite version 3.7.16 Enter ".help" for instructions Enter SQL statements terminated with a ";" sqlite> .tables alltime_standing_entries events_grounds names alltime_standings events_teams parts assocs games persons assocs_assocs goals places badges grounds props cities group_standing_entries rosters continents group_standings rounds counties groups seasons countries groups_teams states country_codes langs taggings districts leagues tags event_standing_entries logs teams event_standings metros usages events munis zones sqlite> select * from countries; 1|Mauritius|mauritius|mu|1|MUS|||1261200|2040|||f|t|f|f| sqlite> select * from teams; 1|joachim|Cercle de Joachim||CDJ|Cercle de Joachim SC|Joachim||t|||||f| 2|chamarel|Chamarel SC||CHA|Chamarel|Chamarel Sport Club||t|||||f| 3|curepipesc|Curepipe Starlight||CUR|Curepipe Starlight SC||t|||||f| 4|entente|Entente Boulet Rouge||EBR|Entente Boulet Rouge SC|Entente Boulet Rouge-Riche Mare Rovers||t|||||f| 5|lacure|La Cure Sylvester||LCS|La Cure Sylvester SC|La Cure||t|||||f| 6|pamplemousses|Pamplemousses||PPM|Pamplemousses SC||t|||||f| 7|petiteriv|Petite Rivière Noire||PRN|Petite Rivière Noire SC|Petite Rivière||t|||||f| 8|aspl|AS Port-Louis 2000||APL|ASPL 2000|Port-Louis 2000|Association Sportive Port-Louis 2000||t|||||f| 9|qbornes|AS Quatre Bornes||AQB|ASQB|Quatre Bornes||t|||||f| 10|rempart|Rivière du Rempart||ARR|AS Rivière du Rempart||t|||||f| 11|pointauxsables|Pointe-aux-Sables Mates|||||t|||||f| 12|savanne|Savanne SC|||Savanne Sporting Club||t|||||f| And so on. In the openfootball/world-cup you find ready-to-use (free, public domain) datasets for all world cups including the upcoming world cup 2018 in Russia. Example - 2018–russia/cup.txt: ############################ # World Cup 2018 Russia Group A | Russia Saudi Arabia Egypt Uruguay Group B | Portugal Spain Morocco Iran Group C | France Australia Peru Denmark Group D | Argentina Iceland Croatia Nigeria Group E | Brazil Switzerland Costa Rica Serbia Group F | Germany Mexico Sweden South Korea Group G | Belgium Panama Tunisia England Group H | Poland Senegal Colombia Japan Matchday 1 | Thu Jun/14 Matchday 2 | Fri Jun/15 Matchday 3 | Sat Jun/16 ... Group A: (1) Thu Jun/14 18:00 Russia - Saudi Arabia @ Luzhniki Stadium, Moscow (UTC+3) (2) Fri Jun/15 17:00 Egypt - Uruguay @ Central Stadium, Yekaterinburg (UTC+5) ...

6. feedparser - Meet Jason Feed - The Universal Content (Micro) Web Service (HTTP JSON API) - The Future of Publishing 'n' Online News The Future of Online News - The Future of Facebook & Co - Web Feeds, Web Feeds, Web Feeds github: feedparser/feedparser, rubygems: feedparser, rdoc: feedparser ++ more: comments on reddit, please! What’s a web feed? A web feed (or news feed) is a (simple) document/text format that: (1) lets you publish a list of: status updates, blog postings, articles, pictures, cartoons, recordings, etc. and that (2) lets others subscribe to your updates. Example: { "version" : "https://jsonfeed.org/version/1" , "title" : "Jason Fried's Microblog" , "home_page_url" : "https://micro.blog/jasonfried/" , "feed_url" : "https://micro.blog/jasonfried/feed.json" , "author" : { "name" : "Jason Fried" , "url" : "https://micro.blog/jasonfried/" , "avatar" : "https://micro.blog/jasonfried/avatar.png" }, "items" : [ { "id" : "865767227416612864" , "url" : "https://micro.blog/jasonfried/status/865767227416612864" , "content_text" : "JSON Feed? I know that guy." , "date_published" : "2017-05-19T20:12:00-00:00" } ] } Aside: The Wonders of RSS - What’s RSS!? Triva Quiz - Q: What’s RSS? [A] RDF Site Summary

[B] Rich Site Summary

[C] Really Simple Syndication

[D] Really Simple, Stupid

[E] Rapid Syndicaton Solution RDF = Resource Description Framework Trivia Quiz - Find the Content - Q: What’s your favorite way to add content in hypertext to RSS 2.0? [A] <description>

[B] <content:encoded> from RDF/RSS 1.0 content module extension

from RDF/RSS 1.0 content module extension [C] <media:content> from Yahoo! search extension

from Yahoo! search extension [D] Other? Please, tell! Bonus: Is your content in plain text, in html, in xhtml, in html escaped? Is your content a summary? or full text? The State of Web Feed Formats in 2017 - XML, JSON, YAML, HTML, TXT Let’s celebrate diversity! Live and let live! Web feed formats today in 2017 include: RSS 2.0 (0.91, 0.92) a.k.a. Really Simple Syndication - in XML

RSS 1.0 a.k.a. RDF Site Summary - in RDF/XML

Atom - in XML

JSON Feed - in - surprise, surprise - JSON

Microformats (h-feed/h-entry) - in HTML

Feed.TXT - in plain text; metadata in (simplified) YAML or JSON; Markdown And some more. What’s the feedparser library? One library to rule them all! All your base are blong to feedparser. In the end all formats are just 0 and 1s or: feed.title

feed.url

feed.items[0].title

feed.items[0].url

feed.items[0].published

feed.items[0].content_html or feed.items[0].content

or feed.items[0].content_text

feed.items[0].summary

etc. => Let your computer handle the reading of web feeds ;-). Read Feed Example require 'open-uri' require 'feedparser' txt = open ( 'http://openfootball.github.io/feed.xml' ). read feed = FeedParser :: Parser . parse ( txt ) puts feed . title # => "football.db - Open Football Data" puts feed . url # => "http://openfootball.github.io/" puts feed . items [ 0 ]. title # => "football.db - League Quick Starter Sample - Mauritius Premier League - Create Your Own Repo/League(s) from Scratch" puts feed . items [ 0 ]. url # => "http://openfootball.github.io/2015/08/30/league-quick-starter.html" puts feed . items [ 0 ]. updated # => Sun, 30 Aug 2015 00:00:00 +0000 puts feed . items [ 0 ]. content # => "Added a new quick starter sample using the Mauritius Premier League to get you started..." ... or reading a feed in the new JSON Feed format in - surprise, surprise - JSON; note: nothing changes :-) txt = open ( 'http://openfootball.github.io/feed.json' ). read feed = FeedParser :: Parser . parse ( txt ) puts feed . title # => "football.db - Open Football Data" puts feed . url # => "http://openfootball.github.io/" puts feed . items [ 0 ]. title # => "football.db - League Quick Starter Sample - Mauritius Premier League - Create Your Own Repo/League(s) from Scratch" puts feed . items [ 0 ]. url # => "http://openfootball.github.io/2015/08/30/league-quick-starter.html" puts feed . items [ 0 ]. updated # => Sun, 30 Aug 2015 00:00:00 +0000 puts feed . items [ 0 ]. content_text # => "Added a new quick starter sample using the Mauritius Premier League to get you started..." ... Microformats Microformats let you mark up feeds and posts in HTML with h-entry , h-feed , and friends. Note: Microformats support in feedparser is optional. Install and require the the microformats library to read feeds in HTML with Microformats. require 'microformats' text =<< HTML <article class="h-entry"> <h1 class="p-name">Microformats are amazing</h1> <p>Published by <a class="p-author h-card" href="http://example.com">W. Developer</a> on <time class="dt-published" datetime="2013-06-13 12:00:00">13<sup>th</sup> June 2013</time> <p class="p-summary">In which I extoll the virtues of using microformats.</p> <div class="e-content"> <p>Blah blah blah</p> </div> </article> HTML feed = FeedParser :: Parser . parse ( text ) puts feed . format # => "html" puts feed . items . size # => 1 puts feed . items [ 0 ]. authors . size # => 1 puts feed . items [ 0 ]. content_html # => "<p>Blah blah blah</p>" puts feed . items [ 0 ]. content_text # => "Blah blah blah" puts feed . items [ 0 ]. title # => "Microformats are amazing" puts feed . items [ 0 ]. summary # => "In which I extoll the virtues of using microformats." puts feed . items [ 0 ]. published # => 2013-06-13 12:00:00 puts feed . items [ 0 ]. authors [ 0 ]. name # => "W. Developer" ... Samples Feed Reader Planet Feed Reader in 20 Lines of Ruby planet.rb : require 'open-uri' require 'feedparser' require 'erb' # step 1) read a list of web feeds FEED_URLS = [ 'http://vienna-rb.at/atom.xml' , 'http://weblog.rubyonrails.org/feed/atom.xml' , 'http://www.ruby-lang.org/en/feeds/news.rss' , 'http://openfootball.github.io/feed.json' , ] items = [] FEED_URLS . each do | url | feed = FeedParser :: Parser . parse ( open ( url ). read ) items += feed . items end # step 2) mix up all postings in a new page FEED_ITEM_TEMPLATE = << EOS <% items.each do |item| %> <div class="item"> <h2><a href="<%= item.url %>"><%= item.title %></a></h2> <div><%= item.content %></div> </div> <% end %> EOS puts ERB . new ( FEED_ITEM_TEMPLATE ). result Run the script: $ ruby ./planet.rb Prints: <div class="item"> <h2><a href="http://vienna-rb.at/blog/2017/11/06/picks/">Picks / what the vienna.rb team thinks is worth sharing this week</a></h2> <div> <h3>6/11 Picks!!</h3> <p>In a series on this website we'll entertain YOU with our picks... ... Real World Usage See the Planet Pluto feed reader family: Planet Pluto - static planet website generator

Planet Pluto Live - dynamic (live) planet web apps (using Sinatra, Rails, etc.)

7. json-next - Comments, Please! - JSON 1.0, JSON 1.1, JSON What's Next? HANSON.parse, SON.parse, JSONX.parse github: json-next/json-next, rubygems: json-next, rdoc: json-next ++ more: comments on reddit, please! What’s missing in JSON? Comments, Comments, Comments Unquoted Keys Multi-Line Strings a) Folded – Folds Newlines

b) Unfolded Trailing Commas in Arrays and Objects More: Date/DateTime/Timestamp Type

Optional Commas

Optional Unquoted String Values

“Raw” String (e.g. '' instead of "" ) No need to escape \ or " etc. To escape ' use ''' e.g. ''''Henry's Themes'''

instead of ) More Data Types ( set , map , symbol , etc.)

, , , etc.) And much more Discussion Fixing JSON - Comments, Please! We can easily agree on what’s wrong with JSON, and I can’t help wondering if it’d be worth fixing it. – Tim Bray (Fixing JSON) XML already does everything JSON does! And there’s no way to differentiate between nodes and attributes! And there are no namespaces! And no schemas! What’s the point of JSON? – Anonymous We need to fix engineers that try to ‘fix JSON’, absolutely nothing is broken with JSON. – Anonymous What’s the json-next library? The json-next library lets you convert and read (parse) next generation json versions including: HanSON e.g. HANSON.parse , SON e.g. SON.parse , JSONX e.g. JSONX.parse . HanSON HanSON - JSON for Humans by Tim Jansen et al HanSON is an extension of JSON with a few simple additions to the spec: quotes for strings are optional if they follow JavaScript identifier rules.

you can alternatively use backticks, as in ES6’s template string literal, as quotes for strings. A backtick-quoted string may span several lines and you are not required to escape regular quote characters, only backticks. Backslashes still need to be escaped, and all other backslash-escape sequences work like in regular JSON.

for single-line strings, single quotes ( '' ) are supported in addition to double quotes ( "" )

) are supported in addition to double quotes ( ) you can use JavaScript comments, both single line ( // ) and multi-line comments ( /* */ ), in all places where JSON allows whitespace.

) and multi-line comments ( ), in all places where JSON allows whitespace. Commas after the last list element or object property will be ignored. Example: { listName : "Sesame Street Monsters" , // note that listName needs no quotes content : [ { name : "Cookie Monster" , /* Note the template quotes and unescaped regular quotes in the next string */ background : `Cookie Monster used to be a monster that ate everything, especially cookies. These days he is forced to eat "healthy" food.` }, { // You can single-quote strings too: name : 'Herry Monster' , background : `Herry Monster is a furry blue monster with a purple nose. He's mostly retired today.` }, // don't worry, the trailing comma will be ignored ] } Use HANSON.convert to convert HanSON text to ye old’ JSON text: { "listName" : "Sesame Street Monsters" , "content" : [ { "name" : "Cookie Monster" , "background" : "Cookie Monster used to be a

... to eat \" healthy \" food." }, { "name" : "Herry Monster" , "background" : "Herry Monster is a furry blue monster with a purple nose.

... today." } ] } Use HANSON.parse instead of JSON.parse to parse text to ruby hash / array / etc.: { "listName" => "Sesame Street Monsters" , "content" => [ { "name" => "Cookie Monster" , "background" => "Cookie Monster used to be a

... to eat \" healthy \" food." }, { "name" => "Herry Monster" , "background" => "Herry Monster is a furry blue monster with a purple nose.

... today." } ] } SON SON - Simple Object Notation by Aleksander Gurin et al Simple data format similar to JSON, but with some minor changes: comments starts with # sign and ends with newline (

)

sign and ends with newline ( ) comma after an object key-value pair is optional

comma after an array item is optional JSON is compatible with SON in a sense that JSON data is also SON data, but not vise versa. Example: { # Personal information "name": "Alexander Grothendieck" "fields": "mathematics" "main_topics": [ "Etale cohomology" "Motives" "Topos theory" "Schemes" ] "numbers": [1 2 3 4] "mixed": [1.1 -2 true false null] } Use SON.convert to convert SON text to ye old’ JSON text: { "name" : "Alexander Grothendieck" , "fields" : "mathematics" , "main_topics" : [ "Etale cohomology" , "Motives" , "Topos theory" , "Schemes" ], "numbers" : [ 1 , 2 , 3 , 4 ], "mixed" : [ 1.1 , -2 , true , false , null ] } Use SON.parse instead of JSON.parse to parse text to ruby hash / array / etc.: { "name" => "Alexander Grothendieck" , "fields" => "mathematics" , "main_topics" => [ "Etale cohomology" , "Motives" , "Topos theory" , "Schemes" ], "numbers" => [ 1 , 2 , 3 , 4 ], "mixed" => [ 1.1 , - 2 , true , false , nil ] } JSONX JSON with Extensions or JSON v1.1 (a.k.a. JSON11 or JSON XI or JSON II) Includes all JSON extensions from HanSON: quotes for strings are optional if they follow JavaScript identifier rules.

you can alternatively use backticks, as in ES6’s template string literal, as quotes for strings. A backtick-quoted string may span several lines and you are not required to escape regular quote characters, only backticks. Backslashes still need to be escaped, and all other backslash-escape sequences work like in regular JSON.

for single-line strings, single quotes ( '' ) are supported in addition to double quotes ( "" )

) are supported in addition to double quotes ( ) you can use JavaScript comments, both single line ( // ) and multi-line comments ( /* */ ), in all places where JSON allows whitespace.

) and multi-line comments ( ), in all places where JSON allows whitespace. Commas after the last list element or object property will be ignored. Plus all JSON extensions from SON: comments starts with # sign and ends with newline (

)

sign and ends with newline ( ) comma after an object key-value pair is optional

comma after an array item is optional Plus some more extra JSON extensions: unquoted strings following the JavaScript identifier rules can use the dash ( - ) too e.g. allows common keys such as core-js , babel-preset-es2015 , eslint-config-jquery and others Example: { # use shell-like (or ruby-like) comments listName: "Sesame Street Monsters" # note: comments after key-value pairs are optional content: [ { name: "Cookie Monster" // note: the template quotes and unescaped regular quotes in the next string background: `Cookie Monster used to be a monster that ate everything, especially cookies. These days he is forced to eat "healthy" food.` }, { // You can single-quote strings too: name: 'Herry Monster', background: `Herry Monster is a furry blue monster with a purple nose. He's mostly retired today.` }, /* don't worry, the trailing comma will be ignored */ ] } Use JSONX.convert (or JSONXI.convert or JSON11.convert or JSONII.convert ) to convert JSONX text to ye old’ JSON text: { "listName" : "Sesame Street Monsters" , "content" : [ { "name" : "Cookie Monster" , "background" : "Cookie Monster used to be a

... to eat \" healthy \" food." }, { "name" : "Herry Monster" , "background" : "Herry Monster is a furry blue monster with a purple nose.

... today." } ] } Use JSONX.parse (or JSONXI.parse or JSON11.parse or JSONII.parse ) instead of JSON.parse to parse text to ruby hash / array / etc.: { "listName" => "Sesame Street Monsters" , "content" => [ { "name" => "Cookie Monster" , "background" => "Cookie Monster used to be a

... to eat \" healthy \" food." }, { "name" => "Herry Monster" , "background" => "Herry Monster is a furry blue monster with a purple nose.

... today." } ] } Live Examples require 'json/next' text1 =<< TXT { listName: "Sesame Street Monsters", // note that listName needs no quotes content: [ { name: "Cookie Monster", /* Note the template quotes and unescaped regular quotes in the next string */ background: `Cookie Monster used to be a monster that ate everything, especially cookies. These days he is forced to eat "healthy" food.` }, { // You can single-quote strings too: name: 'Herry Monster', background: `Herry Monster is a furry blue monster with a purple nose. He's mostly retired today.` }, // don't worry, the trailing comma will be ignored ] } TXT pp HANSON . parse ( text1 ) # note: is the same as JSON.parse( HANSON.convert( text )) resulting in: { "listName" => "Sesame Street Monsters" , "content" => [ { "name" => "Cookie Monster" , "background" => "Cookie Monster used to be a

... to eat \" healthy \" food." }, { "name" => "Herry Monster" , "background" => "Herry Monster is a furry blue monster with a purple nose.

... today." } ] } and text2 =<< TXT { # Personal information "name": "Alexander Grothendieck" "fields": "mathematics" "main_topics": [ "Etale cohomology" "Motives" "Topos theory" "Schemes" ] "numbers": [1 2 3 4] "mixed": [1.1 -2 true false null] } TXT pp SON . parse ( text2 ) # note: is the same as JSON.parse( SON.convert( text )) resulting in: { "name" => "Alexander Grothendieck" , "fields" => "mathematics" , "main_topics" => [ "Etale cohomology" , "Motives" , "Topos theory" , "Schemes" ], "numbers" => [ 1 , 2 , 3 , 4 ], "mixed" => [ 1.1 , - 2 , true , false , nil ] } and text3 =<< TXT { # use shell-like (or ruby-like) comments listName: "Sesame Street Monsters" # note: comments after key-value pairs are optional content: [ { name: "Cookie Monster" // note: the template quotes and unescaped regular quotes in the next string background: `Cookie Monster used to be a monster that ate everything, especially cookies. These days he is forced to eat "healthy" food.` }, { // You can single-quote strings too: name: 'Herry Monster', background: `Herry Monster is a furry blue monster with a purple nose. He's mostly retired today.` }, /* don't worry, the trailing comma will be ignored */ ] } TXT pp JSONX . parse ( text3 ) # note: is the same as JSON.parse( JSONX.convert( text )) pp JSONXI . parse ( text3 ) # note: is the same as JSON.parse( JSONXI.convert( text )) pp JSON11 . parse ( text3 ) # note: is the same as JSON.parse( JSON11.convert( text )) pp JSONII . parse ( text3 ) # note: is the same as JSON.parse( JSONII.convert( text )) resulting in: { "listName" => "Sesame Street Monsters" , "content" => [ { "name" => "Cookie Monster" , "background" => "Cookie Monster used to be a

... to eat \" healthy \" food." }, { "name" => "Herry Monster" , "background" => "Herry Monster is a furry blue monster with a purple nose.

... today." } ] } Bonus: More JSON Formats See the Awesome JSON (What’s Next?) collection / page.


Santiago South Super Cup

The Santiago South Super Cup (Portuguese: Super Taça de Santiago Sul) is a regional super cup competition played during the season in the south of Santiago, Cape Verde consisting the municipalities of Praia, Ribeira Grande de Santiago and São Domingos. The competition is organized by the Santiago South Regional Football Association (Associação Regional de Futebol de Santiago Sul de Futebol, ARFSS). Its current champions is Sporting Praia who won their third and recent title. The regional champion competes with the cup champion. If a champion also has a cup title, a cup club who is runner-up qualifies. The upcoming edition will feature the champion Académica da Praia and the cup winner Sporting Praia.

Several cancellations occurred including in 2011, 2014, the time when Pico do Fogo on the nearby island to the west was erupting and the 2016 edition, as the cup competition took place on April 15, 2017, the super cup competition which was slanted for May or June 2017 may have been canceled.

Winners

1Runner up in the cup final as the regional cup winner was also the regional champion that season

Performance By Club

See also
Sports in Santiago, Cape Verde
Santiago South Cup
Santiago South Premier Division
Santiago South Association Cup

Notes

References

Category:Sport in Santiago, Cape Verde
Category:Football cup competitions in Cape Verde
Category:2007 establishments in Cape Verde


////////////////////////////////////////////////////////////
//
// DAGON - An Adventure Game Engine
// Copyright (c) 2011-2014 Senscape s.r.l.
// All rights reserved.
//
// This Source Code Form is subject to the terms of the
// Mozilla Public License, v. 2.0. If a copy of the MPL was
// not distributed with this file, You can obtain one at
// http://mozilla.org/MPL/2.0/.
//
////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////
// Headers
////////////////////////////////////////////////////////////

#include <cassert>
#include <cstring>
#include <sstream>

#include "Audio.h"
#include "Language.h"
#include "Log.h"

namespace dagon {

////////////////////////////////////////////////////////////
// Implementation - Constructor
////////////////////////////////////////////////////////////

Audio::Audio() :
config(Config::instance()),
log(Log::instance())
{
  _doesAutoplay = true;
  _isLoaded = false;
  _isLoopable = false;
  _isMatched = false;
  _isVarying = false;
  _state = kAudioInitial;
  _oggCallbacks.read_func = _oggRead;
  _oggCallbacks.seek_func = _oggSeek;
  _oggCallbacks.close_func = _oggClose;
  _oggCallbacks.tell_func = _oggTell;
  this->setType(kObjectAudio);
  _mutex = SDL_CreateMutex();
  if (!_mutex)
    log.error(kModAudio, "%s", kString18001);
}

////////////////////////////////////////////////////////////
// Implementation - Destructor
////////////////////////////////////////////////////////////

Audio::~Audio() {
  SDL_DestroyMutex(_mutex);
}

////////////////////////////////////////////////////////////
// Implementation - Checks
////////////////////////////////////////////////////////////

bool Audio::doesAutoplay() {
  return _doesAutoplay;
}

bool Audio::isLoopable() {
  return _isLoopable;
}

bool Audio::isPlaying() {
  bool value = false;
  if (SDL_LockMutex(_mutex) == 0) {
    value = (_state == kAudioPlaying);
    SDL_UnlockMutex(_mutex);
  } else {
    log.error(kModAudio, "%s", kString18002);
  }
  return value;
}

bool Audio::isVarying() {
  return _isVarying;
}

////////////////////////////////////////////////////////////
// Implementation - Gets
////////////////////////////////////////////////////////////

double Audio::cursor() {
  return ov_time_tell(&_oggStream);
}

int Audio::state() {
  return _state;
}

AssetID_t Audio::filename() const {
  return _filename;
}

std::string Audio::audioName() const {
  return _audioName;
}

////////////////////////////////////////////////////////////
// Implementation - Sets
////////////////////////////////////////////////////////////

void Audio::setAutoplay(bool autoplay) {
  _doesAutoplay = autoplay;
}

void Audio::setLoopable(bool loopable) {
  _isLoopable = loopable;
}

void Audio::setPosition(unsigned int face, Point origin) {
  if (SDL_LockMutex(_mutex) == 0) {
    if (_isLoaded) {
      float x = origin.x / kDefTexSize;
      float y = origin.y / kDefTexSize;

      switch (face) {
        case kNorth: {
          alSource3f(_alSource, AL_POSITION, x, y, -1.0f);
          break;
        }
        case kEast: {
          alSource3f(_alSource, AL_POSITION, 1.0f, y, x);
          break;
        }
        case kSouth: {
          alSource3f(_alSource, AL_POSITION, -x, y, 1.0f);
          break;
        }
        case kWest: {
          alSource3f(_alSource, AL_POSITION, -1.0f, y, -x);
          break;
        }
        case kUp: {
          alSource3f(_alSource, AL_POSITION, 0.0f, 1.0f, 0.0f);
          break;
        }
        case kDown: {
          alSource3f(_alSource, AL_POSITION, 0.0f, -1.0f, 0.0f);
          break;
        }
        default: {
          assert(false);
        }
      }

      _verifyError("position");
	}
    SDL_UnlockMutex(_mutex);
  } else {
    log.error(kModAudio, "%s", kString18002);
  }
}

void Audio::setVarying(bool varying) {
  _isVarying = varying;
}

void Audio::setAudioName(const std::string& audioName) {
  _audioName = audioName;
  _filename = _randomizeFile(audioName);
}

////////////////////////////////////////////////////////////
// Implementation - State changes
////////////////////////////////////////////////////////////

void Audio::match(Audio* audioToMatch) {
  _matchedAudio = audioToMatch;
  _isMatched = true;
}

void Audio::play() {
  if (SDL_LockMutex(_mutex) == 0) {
    _asset->load();
    if (!_asset->loaded()) {
      log.error(kModAudio, "%s: %s", kString16008, _filename.c_str());
      SDL_UnlockMutex(_mutex);
      return;
    }

    if (!_isLoaded) {
      _load();
    }

    if (_isLoaded && (_state != kAudioPlaying)) {
      if (_isMatched)
        ov_time_seek(&_oggStream, _matchedAudio->cursor());

      if (_isVarying) {
        float p = ((rand() % 20) + 90) / 100.0f;
        alSourcef(_alSource, AL_PITCH, p);
      }
      alSourcePlay(_alSource);
      _state = kAudioPlaying;
      _verifyError("play");
    }
    SDL_UnlockMutex(_mutex);
  } else {
    log.error(kModAudio, "%s", kString18002);
  }
}

void Audio::pause() {
  if (SDL_LockMutex(_mutex) == 0) {
    if (_state == kAudioPlaying) {
      alSourceStop(_alSource);
      _state = kAudioPaused;
      _verifyError("pause");
    }
    SDL_UnlockMutex(_mutex);
  } else {
    log.error(kModAudio, "%s", kString18002);
  }
}

void Audio::stop() {
  if (SDL_LockMutex(_mutex) == 0) {
    if ((_state == kAudioPlaying) || (_state == kAudioPaused)) {
      alSourceStop(_alSource);
      ov_raw_seek(&_oggStream, 0);
      _state = kAudioStopped;
      _verifyError("stop");
    }
    SDL_UnlockMutex(_mutex);
  } else {
    log.error(kModAudio, "%s", kString18002);
  }
}

void Audio::update() {
  if (SDL_LockMutex(_mutex) == 0) {
    if (_state == kAudioPlaying) {
      int processed;
      alGetSourcei(_alSource, AL_BUFFERS_PROCESSED, &processed);
      while (processed--) {
        ALuint buffer;

        alSourceUnqueueBuffers(_alSource, 1, &buffer);
        _fillBuffer(&buffer);
        alSourceQueueBuffers(_alSource, 1, &buffer);
      }

      // Run fade operations
      this->updateFade();

      // FIXME: Not very elegant as we're doing this check every time
      if (config.mute) {
        alSourcef(_alSource, AL_GAIN, 0.0f);
      } else {
        // Finally check the current volume. If it's zero, let the manager know
        // that we're done with this audio.
        if (this->fadeLevel() > 0.0) {
          alSourcef(_alSource, AL_GAIN, this->fadeLevel());
        } else {
          alSourceStop(_alSource);
          _state = kAudioPaused;
        }
      }
    }
    SDL_UnlockMutex(_mutex);
  } else {
    log.error(kModAudio, "%s", kString18002);
  }
}

////////////////////////////////////////////////////////////
// Implementation - Private methods
////////////////////////////////////////////////////////////

void Audio::_load() {
  _dataRead = 0;

  if (ov_open_callbacks(this, &_oggStream, NULL, 0, _oggCallbacks) < 0) {
    log.error(kModAudio, "%s", kString16010);
  }

  // Get file info
  vorbis_info* info = ov_info(&_oggStream, -1);
  _channels = info->channels;
  _rate = (ALsizei)info->rate;

  if (_channels == 1) {
    _alFormat = AL_FORMAT_MONO16;

  }
  else if (_channels == 2) {
    _alFormat = AL_FORMAT_STEREO16;
  }
  else {
    // Invalid number of channels
    log.error(kModAudio, "%s: %s", kString16009, _filename.c_str());
  }

  alGenBuffers(config.numOfAudioBuffers, _alBuffers);
  alGenSources(1, &_alSource);
  alSource3f(_alSource, AL_POSITION, 0.0f, 0.0f, 0.0f);
  alSource3f(_alSource, AL_VELOCITY, 0.0f, 0.0f, 0.0f);
  alSource3f(_alSource, AL_DIRECTION, 0.0f, 0.0f, 0.0f);

  int buffersRead = 0;
  for (buffersRead = 0; buffersRead < config.numOfAudioBuffers; buffersRead++) {
    if (_fillBuffer(&_alBuffers[buffersRead]) == kAudioStreamEOF) {
      break;
    }
  }
  alSourceQueueBuffers(_alSource, buffersRead, _alBuffers);
  _verifyError("prebuffer");
  if (config.mute || this->fadeLevel() < 0.0) {
    alSourcef(_alSource, AL_GAIN, 0.0f);
  }
  else {
    alSourcef(_alSource, AL_GAIN, this->fadeLevel());
  }

  _isLoaded = true;
  _verifyError("load");
}

void Audio::_unload() {
  if (SDL_LockMutex(_mutex) == 0) {
    if (_isLoaded) {
      if (_state == kAudioPlaying) {
        alSourceStop(_alSource);
        ov_raw_seek(&_oggStream, 0);
        _state = kAudioStopped;
      }

      _emptyBuffers();
      alDeleteSources(1, &_alSource);
      alDeleteBuffers(config.numOfAudioBuffers, _alBuffers);
      ov_clear(&_oggStream);
      _isLoaded = false;
      _verifyError("unload");
    }

    SDL_UnlockMutex(_mutex);
  }
  else {
    log.error(kModAudio, "%s", kString18002);
  }
}

int Audio::_fillBuffer(ALuint* buffer) {
  // This is a failsafe; if this is true, we won't attempt to stream anymore
  static bool _hasStreamingError = false;

  if (_hasStreamingError) {
    return kAudioGenericError;
  }

  if (!_asset->loaded()) {
    log.error(kModAudio, "%s: %s", kString16011, _filename.c_str());
    _hasStreamingError = true;
    return kAudioGenericError;
  }

  // Prevent audio cuts if file size too small
  int bufferSize = config.audioBuffer;
  if (static_cast<int>(_asset->size()) < bufferSize) {
    bufferSize = static_cast<int>(_asset->size());
  }

  char* data;
  data = new char[bufferSize];
  int size = 0;
  while (size < bufferSize) {
    int section;
    long result = ov_read(&_oggStream, data + size, bufferSize - size,
      0, 2, 1, &section);
    if (result > 0) {
      size += static_cast<int>(result);
    }
    else if (result == 0) {
      // EOF
      if (_isLoopable) {
        ov_raw_seek(&_oggStream, 0);
      }
      else {
        alSourceStop(_alSource);
        ov_raw_seek(&_oggStream, 0);
        _state = kAudioStopped;
      }
      return kAudioStreamEOF;
    }
    else if (result == OV_HOLE) {
      // May return OV_HOLE after we rewind the stream, so we just re-loop.
      continue;
    }
    else if (result < 0) {
      // Error
      log.error(kModAudio, "%s: %s", kString16007, _filename.c_str());
      _hasStreamingError = true;
      return kAudioStreamError;
    }
  }
  alBufferData(*buffer, _alFormat, data, size, _rate);
  delete[] data;
  return kAudioStreamOK;
}

void Audio::_emptyBuffers() {
  ALint alState;
  alGetSourcei(_alSource, AL_SOURCE_STATE, &alState);

  if (alState != AL_PLAYING) {
    int queued;
    alGetSourcei(_alSource, AL_BUFFERS_PROCESSED, &queued);

    while (queued--) {
      ALuint buffer;
      alSourceUnqueueBuffers(_alSource, 1, &buffer);
      _verifyError("unqueue");
    }
  }
}

std::string Audio::_randomizeFile(const std::string &fileName) {
  // Was extension specified?
  if (fileName.find(".ogg") != std::string::npos ) {
    // Then return as-is
    return fileName;
  } else {
    // If not, randomize
    int index = (rand() % 6) + 1; // TODO: Allow to configure this value

    std::stringstream fileToLoad;
    // TODO: Also configure number of zeros
    fileToLoad << fileName.c_str() << "00" << index << ".ogg";
    return fileToLoad.str();
  }
}

ALboolean Audio::_verifyError(const std::string &operation) {
  ALint error = alGetError();

  if (error != AL_NO_ERROR) {
    log.error(kModAudio, "%s: %s: %s (%d)", kString16006,
              _filename.c_str(), operation.c_str(), error);
    return AL_FALSE;
  }
  return AL_TRUE;
}

// And now... The Vorbisfile callbacks

std::size_t Audio::_oggRead(void* ptr, std::size_t size, std::size_t nmemb,
                            void* datasource) {
  Audio* audio = static_cast<Audio*>(datasource);
  if (!audio->_asset->loaded()) {
    Log::instance().error(kModAudio, "%s: %s", kString16011, audio->_filename.c_str());
    return 0;
  }

  std::size_t nSize = size * nmemb;

  if ((audio->_dataRead + nSize) > audio->_asset->size())
    nSize = audio->_asset->size() - audio->_dataRead;

  std::memcpy(ptr, audio->_asset->data() + audio->_dataRead, nSize);
  audio->_dataRead += nSize;
  return nSize;
}

int Audio::_oggSeek(void* datasource, ogg_int64_t offset, int whence) {
  Audio* audio = static_cast<Audio*>(datasource);
  if (!audio->_asset->loaded()) {
    Log::instance().error(kModAudio, "%s: %s", kString16011, audio->_filename.c_str());
    return -1;
  }

  switch (whence) {
    case SEEK_SET: {
      audio->_dataRead = offset;
      break;
    }
    case SEEK_CUR: {
      audio->_dataRead += offset;
      break;
    }
    case SEEK_END: {
      audio->_dataRead = audio->_asset->size() - offset;
      break;
    }
    default: {
      assert(false);
    }
  }

  if (audio->_dataRead > audio->_asset->size()) {
    audio->_dataRead = 0;
    return -1;
  }

  return 0;
}

int Audio::_oggClose(void* datasource) {
  Audio* audio = static_cast<Audio*>(datasource);
  audio->_dataRead = 0;
  return 0;
}

long Audio::_oggTell(void* datasource) {
  Audio* audio = static_cast<Audio*>(datasource);
  return audio->_dataRead;
}

}


Q:

how to use this IsolatedStorageFile isoStore = IsolatedStorageFile.GetUserStoreForApplication(); in WP 8.1

IsolatedStorageFile isoStore = IsolatedStorageFile.GetUserStoreForApplication();

I had modified this as Storage File isoStore=StorageFile.________________(); but I can't find a method like GetUserStoreForApplication in Windows Phone 8.1. How to get this done?

A:

You can get the local storage folder with ApplicationData.LocalFolder or the roaming folder with ApplicationData.RoamingFolder.
Windows.Storage.StorageFolder localFolder = Windows.Storage.ApplicationData.Current.LocalFolder;
Windows.Storage.StorageFolder roamingFolder = Windows.Storage.ApplicationData.Current.RoamingFolder;

See Accessing app data with the Windows Runtime (Windows Runtime apps) for more info on using application data and settings.


Q:

Splitting a string on to several different lines in Java

I have a very long string consisting of words punctuation and spaces. I am trying to modify the string to add a new line (\n) after every x characters but while preserving whole words. For example:

An evil human sprayed innocent students in the face with pepper spray

With x = 20 would output like this:
An evil human sprayed      21 chars
innocent students in       20 chars
the face with pepper       20 chars
spray                      end of string

If words would be split by inserting a \n after x characters, then the word should be allowed to stay on the line, while a new line would be inserted at the end of the word.
Is there a catch-all way of doing this without a whole lot of complicated code?

A:

I would write:
s = s.replaceAll("(.{20,}?)\\s+", "$1\n");

This will find every instance of at least twenty non-newline characters, followed by one or more whitespace characters, and replace the whitespace with a newline. Or, to make x decidable at run-time (rather than hard-coded as 20), I'd write:
s = s.replaceAll("(.{" + x + ",}?)\\s+", "$1\n");


Daredevil To Walk Across Niagara Falls On Steel Cable

(Niagara Falls, NY) — Daredevil Nik Wallenda’s death-defying wire-walk over Niagara Falls takes place this evening. Crowds have been gathering at the Falls all week as workers set up the steel cable that Wallenda will walk across over the Niagara Gorge from the American side of the Falls to the Canadian side. The wire-walk will be broadcast on ABC beginning at 8 p.m. Eastern.

The 33-year-old Sarasota, Florida-born Nik Wallenda is a member of the famed Flying Wallendas. The patriarch of the family of stunt performers, Karl Wallenda, lost his life in 1978 during a high-wire walk in Puerto Rico. He was 73 years old.


Famotidine is a well-known histamine H2-receptor antagonist and gastric acid secretion inhibitor. A key intermediate for the preparation of this compound is N-4-(cyanoethylthiomethyl)-2-thiazolyl!guanidine (also known as N-4-(2-cyanoethyl)-thio!methyl!-2-thiazoyl!guanidine). To prepare this intermediate U.S. Pat. Nos. 4,562,261 and 4,609,737 describe a route based on N-4-(chloromethyl)-4,5-dihydro-4-hydroxy-2-thiazolyl!-guanidine hydrochloride as the starting material. The synthesis involves preparing this starting material by portionwise addition of (aminoiminomethyl)thiourea to dichloroacetone in acetone at temperatures of about -5.degree. to -7.degree. C. and stirring the resultant mixture for 5 days at below 0.degree. C. The resultant N-4-(chloromethyl)-4,5-dihydro-4-hydroxy-2-thiazolyl!-guanidine hydrochloride is isolated, purified and converted to N-4-(2-aminoiminomethyl)thio!methyl!-2-thiazoyl!guanidine) dihydrochloride by reaction with thiourea. Without isolating this intermediate, the reaction mixture is cooled below 10.degree. C. and to the solution are added 3-chloropropionitrile and isopropyl alcohol, followed by dropwise addition of aqueous NaOH whereby crystals of N-4-(cyanoethylthiomethyl)-2-thiazolyl!guanidine are formed.


Q:

Which one to use; Datatable.Select() or LINQ?

If I have a DataTable with a small or large amount of data, to select data from the datatable I can use the Datatable.Select() method or go for LINQ. Which one is faster and efficient?

A:

Which one is best for your circumstance? Or, more importantly, does the speed difference for the amount of data you're querying make it worthwhile to choose one over the other?
LINQ is generally easier to read, on my opinion, than pretty much any other form of data filtering and has the advantage over using DataTable.Select of being, at least partially, strongly-typed making it harder to make mistakes.
// Using DataTable.Select()
DataRow[] records = myTable.Select("(MyIntField > 30 AND MyStringField == 'StringValue') OR AnotherField > 70");

// Using LINQ
var records = from record in myTable.AsEnumerable()
              where (record.Field<int>("MyIntField") > 30
                     && Record.Field<string>("StringValue") == "StringValue")
                    ||
                    (record.Field<int>("AnotherField") > 70)
              select record;

The LINQ query is bigger, but personally I think it's more readable


Grace always wore a hat to school.

Even in 90°F weather, she had that knitted, black beanie pulled down to her eyes. Rain or shine, hot or cold — always the hat.

It wasn’t long before the rumors started. Since she was new, the rumors were especially cruel. “I bet she’s bald,” Marie said, watching Grace sit alone at the lunch table.

“No, I’ve seen some blonde hair poke out,” I said. “She’s not blonde.”

“Maybe it’s a style thing. Like goth or something,” Marie replied.

“Hey. Don’t bring us into this,” Cara shouted from the end of the table, twirling a lock of dyed-black hair.

“Maybe her head just gets really cold. She could have a medical condition –”

“No. I’ve got it.” Lara, the nerd of our little group, leaned forward. Her brown eyes gleamed with excitement behind her glasses. “It’s a psychological experiment. This is exactly what she wants us to do. Focus on it, form theories about it, obsess over it. It’s brilliant, actually. I bet she got Mr. Hernandez to sign off on it.” Her fist slammed into the table. “Dammit, she’s totally going to get an A in psych –”

“Stop it,” Marie said, rolling her eyes. “Not everyone is obsessed with grades like you.”

After lunch was Algebra 2. It went terribly, as usual, and I made a fool out of myself when Mr. Giordano called on me. I walked back to my locker in a foul mood, when I heard a voice call out behind me.

“Hey, Clara!”

It was Grace.

I’d never really seen her up close before. She was pretty, in a delicate sort of way; pale skin, pale blue eyes, tufts of blonde hair sticking out from underneath her hat. She wore lipstick, but no other makeup – a refreshing look, compared to the rest of the girls at school.

“I saw you get that question wrong,” she started. “Sine is opposite over hypotenuse –”

“So?” I asked, bristling.

“Sorry, I didn’t mean – I just wanted to help.” She passed me a piece of paper. “This mnemonic helped me a lot. I thought it might help you with the test.”

She gave me a small smile and walked away.

I watched as she disappeared down the hallway, and felt a pang of dread. Something about the shape of her head looked… wrong, somehow. The contours, and the shadows they created looked out of place.

That’s when I realized the truth.

She wears the hat because she has some sort of deformity. And here we are — laughing at her, mocking her. She must feel awful.

I felt rotten inside.

After that, I never talked about Grace at the lunch table. She was a nice person. Not only did she help me, but she also ignored the derisive laughs, the pointed fingers, when any other teenager would have fired right back.

We should have looked up to her, not made fun of her.

Days, then weeks, went by. Every day, Grace wore that black beanie on her head. But each time, it grew a little less shocking, a little more normal. The others slowly got used to it. We didn’t talk about it anymore. Things were getting back to normal.

Then it all went to shit when we got the substitute teacher.

“I’m Mrs. Chang.” The chalk scraped against the board as she wrote her name in fine cursive. “I’m your substitute teacher for U.S. History. We’ll be starting with World War II, so please open your textbooks to page 264.” She turned around and faced the class.

Her eyes fell on Grace.

“No hats in class,” she said.

Grace’s eyes widened. She went pale. The rest of the class broke into hushed whispers. All the other teachers had just grown used to it. Or were sympathetic to the fact she was the new kid, and let it slide.

Not Mrs. Chang. “Well, what are you waiting for? Take it off,” she said, with an annoyed chuckle. “Now.”

“I can’t,” Grace replied, in a small voice.

My heart pounded in my chest. Grace didn’t deserve this. Never.

“You can’t take off your hat, huh?” Mrs. Chang paced through the rows of desks until she was right in front of Grace. “Why not?”

Grace just shook her head in silence. Tears welled in her eyes, threatening to roll down her cheeks.

“This is awful,” I whispered to Marie.

“Yeah, it kind of is.”

“Mrs. Chang,” I started, stuttering, “Grace always wears that hat. Mrs. Suresh allows it, and I think –”

“Silence!” she snapped, glaring at me. She turned back to Grace. Taking her silence for defiance, not fear, she continued: “You’re being disrespectful, holding up the entire class. Now, please — take the hat off.”

Grace brought her eyes up to Mrs. Chang’s. “I can’t,” she said, again. Her voice quavered.

“You can’t? Or you won’t?”

“I can’t!”

Mrs. Chang became enraged. Her nostrils flared; her eyes grew wide. She reached forward. Grabbed the hat. Yanked.

It popped off.

For a moment, there was silence.

Then the room erupted into chaos. Screams. Vomit. Terror.

The back of Grace’s head was blown open. Jagged bits of skull gave way to blood, brains, darkness. A small, matching hole sat on her forehead, near her hairline.

She’d been shot in the head.

Mrs. Chang stood in front of her — pale, frozen, terrified. Grace got up, snatched the hat back from her. For a second, she locked eyes with Mrs. Chang. As if she was going to slap her. Attack her. The classroom collectively held its breath.

Then Grace ran out of the room, sobbing.

We never saw Grace after that day. She stopped coming to school. I still don’t fully understand what happened, and we don’t talk about it. We’re too scared to. No one can explain exactly what we saw within the walls of that history classroom.

Well, we didn’t talk about it, until senior year started a few weeks ago.

There’s a new kid in our class.

Who always wears a scarf.


It's not a stretch to say that BEM is the most sought-after naming convention for CSS, yet despite its popularity it can still be misconstrued by even the most seasoned of developers. It's not perfect, granted, but it's a methodology that in the right hands can see a project's CSS blossom into a garden of delightfully scoped components.

In a recent post, I wrote about how you can utilise BEM to create a responsive timeline. I touched on the idea of "grandchild elements", an area that I've seen a lot of developers struggle with, and I wanted to delve deeper into that subject.

Setting the scene

At a base level, BEM provides us with the following practices for CSS selectors, each correlating to a different letter in its name:

B: Block - A standalone entity that is meaningful on its own.

E: Element - A part of a block that has no standalone meaning and is semantically tied to its block.

M: Modifier - A flag on a block or element. Use them to change appearance or behaviour. getbem.com

and here's how those correlate to actual classes:

.block { } .block__element { } .block--modifier { }

Notice that everything is tied to the 'block'? Well, grandchildren kind of break that rule.

Grandchild elements are the idea that a component may have elements within elements. That's to say, an element may be tied to another element, rather than the block itself.

If we did require that functionality, then we'd need to extend the BEM methodology to allow for this. Or, perhaps there are better resolutions? It's a tough one to answer and there's no one rule fits all.

To demonstrate, here's a post from Assortment's home page:

A typical blog post excerpt on Assortment

and if I were to mark some of that up in HTML:

< article class = " post " > < div class = " post__meta " > ... </ div > < h1 class = " post__heading " > Lorem ipsum dolor sit amet </ h1 > < p class = " post__excerpt " > Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. </ p > </ article >

Going back to the image you'll notice that on mobile the category label and date are centered at the top of the component, whereas on desktop they're positioned on the top left. To allow for this, I wrap them both in a .post__meta element which handles the positioning thanks to some media query goodness.

What gets interesting is how we should then mark up the contents of .post__meta , that being the category and date . There are a few approaches we can take, each having their own pros and cons. Let's dive in.

Flattening grandchildren

< article class = " post " > < div class = " post__meta " > < div class = " post__category " > ... </ div > < div class = " post__date " > ... </ div > </ div > ... </ article >

Our first option is to flatten our grandchildren. Flattening would mean treating them as though they have no relationship with their parent element, despite the tree representing this. Essentially disregarding the notion of grandchild elements altogether.

Whilst a simpler approach, it does decrease the readability of the component moving forward. In the example above, there would be no indication to another developer coming onto your project that the post__date element is to always be wrapped in post__meta , and so could lead to misuse of the component unless external documentation is available. This, in turn, would increase the time it takes for a developer to consume the component correctly, as they now have to the read documentation beforehand.

In an ideal world, we would want the hierarchy to the component to be understood from the CSS selectors alone, which would not be the case here. That being said, there are occasions where this approach could be preferable.

Pattern libraries

If a component were part of a wider design system where documentation is already required for consumption, then it's probably not a big deal for developers to take the extra few seconds to read a few lines on what should be nested within what.

Hell, it may even be more time efficient to do so.

Multi-use grandchildren

In circumstances where a grandchild has the ability to be in multiple places within the component, depending on the design requirements on that page, it can be better to ignore a potential relationship with its parent.

To give an example, what if there were multiple instances of a post's date? We've already established one instance on the home page, but perhaps there's another variation on the post's individual page, that's styled ever so differently. At that point, your element now has multiple ways to be consumed and there's no easy way of explaining that to a developer through any naming convention. At that point, I'd probably go with creating a whole new block.

Creating new blocks

< article class = " post " > < div class = " post__meta " > < div class = " category post__category " > ... </ div > < div class = " date post__date " > ... </ div > </ div > ... </ article >

As with Flattening, creating a new block doesn't actually achieve any relationship between elements and again dismisses the idea of grandchildren, however, it does provide you with a more robust platform to start from. Whether that's needed or not is the main question.

Many developers I've spoken to go with this option as in their eyes if you're required to have a relationship between elements (grandchildren), then your component is probably too complex. Instead, you should separate your concerns and use external documentation as required to explain any relationships of how a component links with another.

In our example above, any category styling would be part of the category namespace, whereas positioning in the post would be done by post__category . This also has the added benefit of ensuring your component isn't tied to any positioning on the post , which is useful if you need to reuse this somewhere else on your website (as I mentioned above).

However, you're not going to want to create a new block for every single element that could do with a small bit of upfront information to developers that it's nested within another element.

Extending the BEM naming convention

If forgetting about grandchildren doesn’t suffice then the only other option is to go rogue, you little cow[boy/girl] you.

Heres a few strategies to choose from.

Option 1: Continued double underscores

< article class = " post " > < div class = " post__meta " > < div class = " post__meta__category " > ... </ div > < div class = " post__meta__date " > ... </ div > </ div > ... </ article >

One of the more popular examples of grandchild elements I see is extending the element syntax ( __element ) a step further to include it's children.

This nicely ticks the box of allowing the structure to be described through the HTML and more importantly seems the be the first solution a lot of developers come up with so it potentially has some innate learning built in.

That being said, it’s still an extension to the BEM convention and so has a slight overhead for developers coming onto a project with this in place.

Option 2: Hyphen spaced

< article class = " post " > < div class = " post__meta " > < div class = " post__meta-category " > ... </ div > < div class = " post__meta-date " > ... </ div > </ div > ... </ article >

The hyphenated approach could be considered both an extension of the naming convention and flattening the idea of grandchildren at the same time.

In the BEM specification, it explains that names with multiple words should be written using a hyphen to represent the space. So you could break .post__meta-category down to:

post being the block;

being the block; and meta-category as a double-barreled element.

New developers (who’re only familiar with the traditional BEM syntax) won’t be confused and it still conforms to the specification whilst providing the ability to create a nested structure of elements in our selectors.

Of course, we know the true intentions of meta-category , but that's OK. It'll provide us with all the functionality we're looking for without confusing new developers coming onto the project.

The only real downside I've noticed when using this approach is if you start mixing double-barreled element names with grandchildren and how you distinguish between them both. Though to be honest, I can't remember the last time I had a double-barreled name for an element.

Option 3: A completely new syntax

< article class = " post " > < div class = " post__meta " > < div class = " post__meta_category " > ... </ div > < div class = " post__meta_date " > ... </ div > </ div > ... </ article >

If all else fails, there's no harm in extending the BEM syntax altogether.

In the example above I use a single underscore to describe a grandchild element, however, there's no reason it couldn't be three hyphens or a completely new character altogether. (Just keep in mind that if you use a special character you'll need to escape it in your CSS.)

Again, this approach would introduce an initial learning curve for your developers, but going back to the idea of a project which requires external documentation from the get-go, you may not mind a little overhead.

What should you do?

That's the real question, isn't it? I've laid out a few options, but which should you choose? The truth is, I don't know. Like everything in web development, it completely depends on your project and use case.

What I will say is this. Try to limit the times you're extending BEM to a minimum to avoid unnecessary complications to your naming convention. With the times that you do have to extend, ensure your entire team is onboard with a single strategy you're going to use throughout the project, and stick to it. Consistency is more important in my opinion. There's nothing worse than a codebase that doesn't conform to the original methodologies agreed.

Oh, and remember, nobody's going to judge you because you're not conforming with a specification to the tee. Most specifications are built on the extensions of others so if you have a use case then embrace it and look for a solution that suits yours and your project's needs.

If you have any other ideas on how you'd tackle grandchildren in BEM, I'd love to hear about them.

Until next time 👶


1906–07 Columbia men's ice hockey season

The 1906–07 Columbia men's ice hockey season was the 11th season of play for the program.

Season
Coolican served as coach while former player Rudolph Von Bernuth acted as his assistant. H. T. Applington served as team manager.

Note: Columbia University adopted the Lion as its mascot in 1910.

Roster

Standings

Schedule and Results

|-
!colspan=12 style=";" | Regular Season

References

Category:Columbia Lions men's ice hockey seasons
Columbia
Columbia
Category:1906 in sports in New York (state)


Grey ladies winter fashion felt hat buy online in Australia F569G

$151.17 NZDor 4 payments of $37.80 with Afterpay

GREY

GREY

GREY

GREY

This elegant grey ladies winter felt hat features unusual shape with large contrast black flower and veiling. It will make you look equally stunning at autumn and winter racing events or when you casually wear it every day.
Only the best and unique designs from best online ladies hats and fascinators store in Australia!