├── .rspec ├── .gitignore ├── .rvmrc ├── config.ru ├── .autotest ├── spec ├── spec_helper.rb └── olpc_spec.rb ├── views ├── delete.erb ├── layout.erb ├── edit.erb └── home.erb ├── Gemfile ├── Rakefile ├── README.markdown ├── olpc-tasks.rb ├── public ├── style.css └── reset.css └── Gemfile.lock /.rspec: -------------------------------------------------------------------------------- 1 | --format nested 2 | --color 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.db 2 | *.swo 3 | *.swp 4 | -------------------------------------------------------------------------------- /.rvmrc: -------------------------------------------------------------------------------- 1 | rvm use --create 1.9.3@sinatra 2 | -------------------------------------------------------------------------------- /config.ru: -------------------------------------------------------------------------------- 1 | require './olpc-tasks.rb' 2 | run Sinatra::Application 3 | -------------------------------------------------------------------------------- /.autotest: -------------------------------------------------------------------------------- 1 | require 'autotest/fsevent' 2 | require 'autotest/growl' 3 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require_relative '../olpc-tasks.rb' 2 | require 'rack/test' 3 | 4 | set :environment, :test 5 | 6 | def app 7 | Sinatra::Application 8 | end 9 | 10 | -------------------------------------------------------------------------------- /views/delete.erb: -------------------------------------------------------------------------------- 1 | <% if @task %> 2 |

Are you sure you want to delete the following task: "<%= @task.title %>"?

3 |
4 | 5 | Cancel 6 |
7 | <% else %> 8 |

Task not found.

9 | <% end %> 10 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source :gemcutter 2 | #source "http://ruby.taobao.com" 3 | 4 | gem 'rake' 5 | gem 'sinatra' 6 | gem 'datamapper' 7 | gem 'heroku' 8 | 9 | group :production do 10 | gem "pg" 11 | gem "dm-postgres-adapter" 12 | end 13 | 14 | group :development, :test do 15 | gem "sqlite3" 16 | gem 'dm-sqlite-adapter' 17 | end 18 | 19 | group :test do 20 | gem 'rack-test' 21 | gem 'rspec' 22 | gem 'autotest' 23 | end 24 | 25 | 26 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rspec/core/rake_task' 2 | 3 | desc 'Default: run specs.' 4 | task :default => :spec 5 | 6 | task :spec do 7 | desc "TDD" 8 | RSpec::Core::RakeTask.new do |t| 9 | #t.rspec_opts = %w{--colour --format progress --loadby mtime} 10 | t.pattern = "./spec/**/*_spec.rb" 11 | end 12 | 13 | desc "Generate code coverage" 14 | RSpec::Core::RakeTask.new(:coverage) do |t| 15 | t.pattern = "./spec/**/*_spec.rb" # don't need this, it's default. 16 | t.rcov = true 17 | t.rcov_opts = ['--exclude', 'spec'] 18 | end 19 | end 20 | 21 | -------------------------------------------------------------------------------- /views/layout.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | <%= @title + "| OLPC volunteers Tasks" %> 6 | 7 | 8 | 9 | 10 |
11 |
12 |

Tasks

13 |

Tasks for China OLPC volunteers

14 |
15 |
16 | 17 |
18 | <%= yield %> 19 |
20 | 21 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /views/edit.erb: -------------------------------------------------------------------------------- 1 | <% if @task %> 2 |
3 | 4 | 7 | 8 | 9 | 10 |
11 |

Delete

12 | <% else %> 13 |

Task not found.

14 | <% end %> 15 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | # api 2 | 3 | sinatra 4 | rspec 5 | 6 | # shell 7 | 8 | ruby olpc-tasks.rb 9 | rake spec 10 | 11 | # heroku 12 | 13 | heroku create olpc-tasks 14 | heroku addons:add shared-database:5mb 15 | heroku config 16 | git push heroku master 17 | 18 | # database 19 | sudo -u postgres createdb olpc-tasks 20 | sudo -u postgres psql 21 | sudo -u postgres psql 22 | postgres=# \password $USER 23 | 24 | heroku db:push postgres://postgres:postgres@localhost/olpc-tasks 25 | heroku pg:info 26 | 27 | # debug 28 | 29 | heroku console 30 | >> ENV["DATABASE_URL"] 31 | 32 | # ci 33 | travis-ci 34 | -------------------------------------------------------------------------------- /views/home.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 | 6 |
7 |
8 | 9 | <% # display tasks %> 10 | <% @tasks.each do |task| %> 11 | <% task_status ="running" if !task.volunteer.empty? %> 12 | <% task_status ="complete" if task.complete %> 13 |
14 |

15 |

<%= task.title %> 16 | [edit] 17 |

18 |

19 |

20 | volunteer: <%= task.volunteer %> 21 |

22 |

23 | <%= task.url %> 24 |

25 | 28 |
29 | <% end %> 30 | -------------------------------------------------------------------------------- /olpc-tasks.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'sinatra' 3 | require 'data_mapper' 4 | 5 | #DataMapper.setup(:default, ENV['DATABASE_URL'] || "sqlite3://#{Dir.pwd}/olpc-tasks.db") 6 | DataMapper.setup(:default, ENV['DATABASE_URL'] || "postgres://postgres:postgres@localhost/olpc-tasks") 7 | 8 | class Task 9 | include DataMapper::Resource 10 | property :id, Serial 11 | property :title, Text, :required => true 12 | property :url, Text, :required => true 13 | property :complete, Boolean, :required => true, :default => false 14 | property :volunteer, String, :default => "" 15 | end 16 | 17 | # automatically create the post table 18 | Task.auto_migrate! unless Task.storage_exists? 19 | 20 | configure :development do 21 | use Rack::Reloader 22 | end 23 | 24 | get '/' do 25 | @tasks = Task.all :order => :id.desc 26 | @title = 'All tasks' 27 | erb :home 28 | end 29 | 30 | post '/create' do 31 | t = Task.new 32 | t.title = params[:title] 33 | t.url = params[:url] 34 | t.save 35 | redirect '/' 36 | end 37 | 38 | get '/:id' do 39 | @task = Task.get params[:id] 40 | @title = "Edit task ##{params[:id]}" 41 | erb :edit 42 | end 43 | 44 | post '/:id/update' do 45 | t = Task.get params[:id] 46 | t.title = params[:title] 47 | t.url = params[:url] 48 | t.volunteer = params[:volunteer] 49 | t.save 50 | redirect "/" 51 | end 52 | 53 | get '/:id/delete' do 54 | @task = Task.get params[:id] 55 | @title = "Confirm deletion of task ##{params[:id]}" 56 | erb :delete 57 | end 58 | 59 | post '/:id/destroy' do 60 | t = Task.get params[:id] 61 | t.destroy 62 | redirect '/' 63 | end 64 | 65 | get '/:id/complete' do 66 | t = Task.get params[:id] 67 | t.complete = t.complete ? 0 : 1 # flip it 68 | t.save 69 | redirect '/' 70 | end 71 | -------------------------------------------------------------------------------- /public/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 35px auto; 3 | width: 640px; 4 | } 5 | 6 | header { 7 | text-align: center; 8 | margin: 0 0 20px; 9 | } 10 | 11 | header h1 { 12 | display: inline; 13 | font-size: 32px; 14 | } 15 | 16 | header h1 a:link, header h1 a:visited { 17 | color: #444; 18 | text-decoration: none; 19 | } 20 | 21 | header h2 { 22 | font-size: 16px; 23 | font-style: italic; 24 | color: #999; 25 | } 26 | 27 | 28 | #main { 29 | margin: 0 0 20px; 30 | } 31 | 32 | #add { 33 | margin: 0 0 20px; 34 | } 35 | 36 | #add textarea { 37 | height: 20px; 38 | width: 160px; 39 | padding: 10px; 40 | border: 1px solid #ddd; 41 | } 42 | 43 | #add textarea[name="url"] { 44 | height: 20px; 45 | width: 320px; 46 | padding: 10px; 47 | border: 1px solid #ddd; 48 | } 49 | 50 | #add input[type=submit] { 51 | height: 30px; 52 | width: 100px; 53 | margin: -50px 0 0; 54 | border: 1px solid #ddd; 55 | background: white; 56 | } 57 | 58 | #edit textarea { 59 | height: 20px; 60 | width: 360px; 61 | padding: 10px; 62 | border: 1px solid #ddd; 63 | } 64 | 65 | #edit textarea[name="url"] { 66 | height: 20px; 67 | width: 560px; 68 | padding: 10px; 69 | border: 1px solid #ddd; 70 | } 71 | 72 | #edit input[type=submit] { 73 | height: 30px; 74 | width: 100px; 75 | border: 1px solid #ddd; 76 | background: white; 77 | } 78 | 79 | #edit input[type=checkbox] { 80 | height: 20px; 81 | width: 26px; 82 | } 83 | 84 | article { 85 | border: 1px solid #eee; 86 | border-top: none; 87 | padding: 15px 10px; 88 | } 89 | 90 | article:first-of-type { 91 | border: 1px solid #eee; 92 | } 93 | 94 | article:nth-child(even) { 95 | background: #fafafa; 96 | } 97 | 98 | article.complete { 99 | background: #99CC32; 100 | } 101 | 102 | article.running { 103 | background: #FF7F00; 104 | } 105 | 106 | article h2 { 107 | font-size: 16px; 108 | font-style: italic; 109 | } 110 | 111 | article span { 112 | font-size: 0.8em; 113 | } 114 | 115 | p { 116 | margin: 0 0 5px; 117 | } 118 | 119 | .meta { 120 | font-size: 0.8em; 121 | font-style: italic; 122 | color: #888; 123 | } 124 | 125 | .links { 126 | font-size: 1.8em; 127 | line-height: 0.8em; 128 | float: right; 129 | margin: -10px 0 0; 130 | } 131 | 132 | .links a { 133 | display: block; 134 | text-decoration: none; 135 | } 136 | -------------------------------------------------------------------------------- /spec/olpc_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative 'spec_helper.rb' 2 | 3 | describe 'Home' do 4 | include Rack::Test::Methods 5 | 6 | it "should load the home page" do 7 | get '/' 8 | last_response.should be_ok 9 | last_response.body.should include('All tasks') 10 | end 11 | 12 | it "should return to home with the created task" do 13 | title = "title-#{Time.now}" 14 | url = "http://www.google.com/#{Time.now}" 15 | post '/create', params = { :title => title, :url => url} 16 | last_response.should be_redirect; follow_redirect! 17 | last_request.url.should include("/") 18 | last_response.body.should include(title) 19 | end 20 | 21 | end 22 | 23 | describe 'Edit task' do 24 | include Rack::Test::Methods 25 | 26 | before do 27 | @task = Task.first 28 | end 29 | 30 | it "should load task to edit" do 31 | get "/#{@task.id}" 32 | last_response.should be_ok 33 | last_response.body.should include('Edit task') 34 | end 35 | 36 | it "should return to home with the updated task" do 37 | title = "title-#{Time.now}" 38 | url = "http://www.google.com/#{Time.now}" 39 | volunteer = "volunteer-#{Time.now}" 40 | post "/#{@task.id}/update", params = { :title => title, :url => url, :volunteer => volunteer} 41 | last_response.should be_redirect; follow_redirect! 42 | last_request.url.should include("/") 43 | last_response.body.should include(volunteer) 44 | end 45 | 46 | end 47 | 48 | describe 'Delete task' do 49 | include Rack::Test::Methods 50 | 51 | before do 52 | @task = Task.first 53 | end 54 | 55 | it "should let user confirm the delete operation" do 56 | get "/#{@task.id}/delete" 57 | last_response.should be_ok 58 | last_response.body.should include('Confirm deletion of task') 59 | end 60 | 61 | it "should return to home without the deleted task" do 62 | post "/#{@task.id}/destroy" 63 | last_response.should be_redirect; follow_redirect! 64 | last_request.url.should include("/") 65 | end 66 | 67 | end 68 | 69 | describe 'Complete task' do 70 | include Rack::Test::Methods 71 | 72 | before do 73 | @task = Task.first 74 | end 75 | 76 | it "should return to home with the completed task" do 77 | get "/#{@task.id}/complete" 78 | last_response.should be_redirect; follow_redirect! 79 | last_request.url.should include("/") 80 | last_response.should be_ok 81 | end 82 | 83 | end 84 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: http://rubygems.org/ 3 | specs: 4 | ZenTest (4.6.2) 5 | addressable (2.2.7) 6 | autotest (4.4.6) 7 | ZenTest (>= 4.4.1) 8 | bcrypt-ruby (3.0.1) 9 | data_objects (0.10.8) 10 | addressable (~> 2.1) 11 | datamapper (1.2.0) 12 | dm-aggregates (~> 1.2.0) 13 | dm-constraints (~> 1.2.0) 14 | dm-core (~> 1.2.0) 15 | dm-migrations (~> 1.2.0) 16 | dm-serializer (~> 1.2.0) 17 | dm-timestamps (~> 1.2.0) 18 | dm-transactions (~> 1.2.0) 19 | dm-types (~> 1.2.0) 20 | dm-validations (~> 1.2.0) 21 | diff-lcs (1.1.3) 22 | dm-aggregates (1.2.0) 23 | dm-core (~> 1.2.0) 24 | dm-constraints (1.2.0) 25 | dm-core (~> 1.2.0) 26 | dm-core (1.2.0) 27 | addressable (~> 2.2.6) 28 | dm-do-adapter (1.2.0) 29 | data_objects (~> 0.10.6) 30 | dm-core (~> 1.2.0) 31 | dm-migrations (1.2.0) 32 | dm-core (~> 1.2.0) 33 | dm-postgres-adapter (1.2.0) 34 | dm-do-adapter (~> 1.2.0) 35 | do_postgres (~> 0.10.6) 36 | dm-serializer (1.2.1) 37 | dm-core (~> 1.2.0) 38 | fastercsv (~> 1.5.4) 39 | json (~> 1.6.1) 40 | json_pure (~> 1.6.1) 41 | multi_json (~> 1.0.3) 42 | dm-sqlite-adapter (1.2.0) 43 | dm-do-adapter (~> 1.2.0) 44 | do_sqlite3 (~> 0.10.6) 45 | dm-timestamps (1.2.0) 46 | dm-core (~> 1.2.0) 47 | dm-transactions (1.2.0) 48 | dm-core (~> 1.2.0) 49 | dm-types (1.2.1) 50 | bcrypt-ruby (~> 3.0.0) 51 | dm-core (~> 1.2.0) 52 | fastercsv (~> 1.5.4) 53 | json (~> 1.6.1) 54 | multi_json (~> 1.0.3) 55 | stringex (~> 1.3.0) 56 | uuidtools (~> 2.1.2) 57 | dm-validations (1.2.0) 58 | dm-core (~> 1.2.0) 59 | do_postgres (0.10.8) 60 | data_objects (= 0.10.8) 61 | do_sqlite3 (0.10.8) 62 | data_objects (= 0.10.8) 63 | fastercsv (1.5.4) 64 | heroku (2.20.1) 65 | launchy (>= 0.3.2) 66 | rest-client (~> 1.6.1) 67 | rubyzip 68 | term-ansicolor (~> 1.0.5) 69 | json (1.6.5) 70 | json_pure (1.6.5) 71 | launchy (2.0.5) 72 | addressable (~> 2.2.6) 73 | mime-types (1.17.2) 74 | multi_json (1.0.4) 75 | pg (0.13.2) 76 | rack (1.4.1) 77 | rack-protection (1.2.0) 78 | rack 79 | rack-test (0.6.1) 80 | rack (>= 1.0) 81 | rake (0.9.2.2) 82 | rest-client (1.6.7) 83 | mime-types (>= 1.16) 84 | rspec (2.8.0) 85 | rspec-core (~> 2.8.0) 86 | rspec-expectations (~> 2.8.0) 87 | rspec-mocks (~> 2.8.0) 88 | rspec-core (2.8.0) 89 | rspec-expectations (2.8.0) 90 | diff-lcs (~> 1.1.2) 91 | rspec-mocks (2.8.0) 92 | rubyzip (0.9.6.1) 93 | sinatra (1.3.2) 94 | rack (~> 1.3, >= 1.3.6) 95 | rack-protection (~> 1.2) 96 | tilt (~> 1.3, >= 1.3.3) 97 | sqlite3 (1.3.5) 98 | stringex (1.3.2) 99 | term-ansicolor (1.0.7) 100 | tilt (1.3.3) 101 | uuidtools (2.1.2) 102 | 103 | PLATFORMS 104 | ruby 105 | 106 | DEPENDENCIES 107 | autotest 108 | datamapper 109 | dm-postgres-adapter 110 | dm-sqlite-adapter 111 | heroku 112 | pg 113 | rack-test 114 | rake 115 | rspec 116 | sinatra 117 | sqlite3 118 | -------------------------------------------------------------------------------- /public/reset.css: -------------------------------------------------------------------------------- 1 | /* 2 | HTML5 ✰ Boilerplate 3 | 4 | style.css contains a reset, font normalization and some base styles. 5 | 6 | credit is left where credit is due. 7 | much inspiration was taken from these projects: 8 | yui.yahooapis.com/2.8.1/build/base/base.css 9 | camendesign.com/design/ 10 | praegnanz.de/weblog/htmlcssjs-kickstart 11 | */ 12 | 13 | /* 14 | html5doctor.com Reset Stylesheet (Eric Meyer's Reset Reloaded + HTML5 baseline) 15 | v1.6.1 2010-09-17 | Authors: Eric Meyer & Richard Clark 16 | html5doctor.com/html-5-reset-stylesheet/ 17 | */ 18 | 19 | html, body, div, span, object, iframe, 20 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 21 | abbr, address, cite, code, del, dfn, em, img, ins, kbd, q, samp, 22 | small, strong, sub, sup, var, b, i, dl, dt, dd, ol, ul, li, 23 | fieldset, form, label, legend, 24 | table, caption, tbody, tfoot, thead, tr, th, td, 25 | article, aside, canvas, details, figcaption, figure, 26 | footer, header, hgroup, menu, nav, section, summary, 27 | time, mark, audio, video { 28 | margin:0; 29 | padding:0; 30 | border:0; 31 | font-size:100%; 32 | font: inherit; 33 | vertical-align:baseline; 34 | } 35 | 36 | article, aside, details, figcaption, figure, 37 | footer, header, hgroup, menu, nav, section { 38 | display:block; 39 | } 40 | 41 | blockquote, q { quotes:none; } 42 | 43 | blockquote:before, blockquote:after, 44 | q:before, q:after { content:''; content:none; } 45 | 46 | ins { background-color:#ff9; color:#000; text-decoration:none; } 47 | 48 | mark { background-color:#ff9; color:#000; font-style:italic; font-weight:bold; } 49 | 50 | del { text-decoration: line-through; } 51 | 52 | abbr[title], dfn[title] { border-bottom:1px dotted; cursor:help; } 53 | 54 | table { border-collapse:collapse; border-spacing:0; } 55 | 56 | hr { display:block; height:1px; border:0; border-top:1px solid #ccc; margin:1em 0; padding:0; } 57 | 58 | input, select { vertical-align:middle; } 59 | 60 | /* END RESET CSS */ 61 | 62 | /* font normalization inspired by from the YUI Library's fonts.css: developer.yahoo.com/yui/ */ 63 | body { font:13px/1.231 sans-serif; *font-size:small; } /* hack retained to preserve specificity */ 64 | select, input, textarea, button { font:99% sans-serif; } 65 | 66 | /* normalize monospace sizing 67 | * en.wikipedia.org/wiki/MediaWiki_talk:Common.css/Archive_11#Teletype_style_fix_for_Chrome */ 68 | pre, code, kbd, samp { font-family: monospace, sans-serif; } 69 | 70 | /* 71 | * minimal base styles 72 | */ 73 | 74 | body, select, input, textarea { 75 | /* #444 looks better than black: twitter.com/H_FJ/statuses/11800719859 */ 76 | color: #444; 77 | /* set your base font here, to apply evenly */ 78 | /* font-family: Georgia, serif; */ 79 | } 80 | 81 | /* headers (h1,h2,etc) have no default font-size or margin. define those yourself. */ 82 | h1,h2,h3,h4,h5,h6 { font-weight: bold; } 83 | 84 | /* always force a scrollbar in non-IE: */ 85 | html { overflow-y: scroll; } 86 | 87 | /* accessible focus treatment: people.opera.com/patrickl/experiments/keyboard/test */ 88 | a:hover, a:active { outline: none; } 89 | 90 | a, a:active, a:visited { color: #607890; } 91 | a:hover { color: #036; } 92 | 93 | ul, ol { margin-left: 2em; } 94 | ol { list-style-type: decimal; } 95 | 96 | /* remove margins for navigation lists */ 97 | nav ul, nav li { margin: 0; list-style:none; list-style-image: none; } 98 | 99 | small { font-size: 85%; } 100 | strong, th { font-weight: bold; } 101 | 102 | td { vertical-align: top; } 103 | 104 | /* set sub, sup without affecting line-height: gist.github.com/413930 */ 105 | sub, sup { font-size: 75%; line-height: 0; position: relative; } 106 | sup { top: -0.5em; } 107 | sub { bottom: -0.25em; } 108 | 109 | pre { 110 | /* www.pathf.com/blogs/2008/05/formatting-quoted-code-in-blog-posts-css21-white-space-pre-wrap/ */ 111 | white-space: pre; white-space: pre-wrap; white-space: pre-line; word-wrap: break-word; 112 | padding: 15px; 113 | } 114 | 115 | textarea { overflow: auto; } /* www.sitepoint.com/blogs/2010/08/20/ie-remove-textarea-scrollbars/ */ 116 | 117 | .ie6 legend, .ie7 legend { margin-left: -7px; } /* thnx ivannikolic! */ 118 | 119 | /* align checkboxes, radios, text inputs with their label by: Thierry Koblentz tjkdesign.com/ez-css/css/base.css */ 120 | input[type="radio"] { vertical-align: text-bottom; } 121 | input[type="checkbox"] { vertical-align: bottom; } 122 | .ie7 input[type="checkbox"] { vertical-align: baseline; } 123 | .ie6 input { vertical-align: text-bottom; } 124 | 125 | /* hand cursor on clickable input elements */ 126 | label, input[type="button"], input[type="submit"], input[type="image"], button { cursor: pointer; } 127 | 128 | /* webkit browsers add a 2px margin outside the chrome of form elements */ 129 | button, input, select, textarea { margin: 0; } 130 | 131 | /* colors for form validity */ 132 | input:valid, textarea:valid { } 133 | input:invalid, textarea:invalid { 134 | border-radius: 1px; -moz-box-shadow: 0px 0px 5px red; -webkit-box-shadow: 0px 0px 5px red; box-shadow: 0px 0px 5px red; 135 | } 136 | .no-boxshadow input:invalid, .no-boxshadow textarea:invalid { background-color: #f0dddd; } 137 | 138 | /* These selection declarations have to be separate. 139 | No text-shadow: twitter.com/miketaylr/status/12228805301 140 | Also: hot pink. */ 141 | ::-moz-selection{ background: #FF5E99; color:#fff; text-shadow: none; } 142 | ::selection { background:#FF5E99; color:#fff; text-shadow: none; } 143 | 144 | /* j.mp/webkit-tap-highlight-color */ 145 | a:link { -webkit-tap-highlight-color: #FF5E99; } 146 | 147 | /* make buttons play nice in IE: 148 | www.viget.com/inspire/styling-the-button-element-in-internet-explorer/ */ 149 | button { width: auto; overflow: visible; } 150 | 151 | /* bicubic resizing for non-native sized IMG: 152 | code.flickr.com/blog/2008/11/12/on-ui-quality-the-little-things-client-side-image-resizing/ */ 153 | .ie7 img { -ms-interpolation-mode: bicubic; } 154 | --------------------------------------------------------------------------------