ambry 0.2.4 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/Changelog.md +9 -1
- data/README.md +1 -2
- data/extras/countries.rb +0 -2
- data/lib/ambry/active_model.rb +13 -0
- data/lib/ambry/hash_proxy.rb +7 -1
- data/lib/ambry/version.rb +2 -2
- data/spec/active_model_spec.rb +27 -0
- metadata +15 -19
- data/extras/cookie_demo.rb +0 -111
- data/lib/ambry/adapters/cookie.rb +0 -55
- data/lib/rack/ambry.rb +0 -23
- data/spec/cookie_adapter_spec.rb +0 -81
data/Changelog.md
CHANGED
@@ -1,6 +1,14 @@
|
|
1
1
|
# Ambry Changelog
|
2
2
|
|
3
|
-
## [0.
|
3
|
+
## [0.3.0](https://github.com/norman/ambry/tree/0.3.0) - 2012-03-15 ([diff](https://github.com/norman/ambry/compare/0.2.4...0.3.0))
|
4
|
+
|
5
|
+
### [Norman Clarke](https://github.com/norman)
|
6
|
+
|
7
|
+
* Don't raise from finds using hash proxy when a key has a falsy value
|
8
|
+
* Remove cookie adapter; keep Ambry focused on its core mission.
|
9
|
+
* Fixed bug which allowed invalid records to be saved with Active Model. Thanks Tute Costa for reporting.
|
10
|
+
|
11
|
+
## [0.2.4](https://github.com/norman/ambry/tree/0.2.4) - 2011-10-07 ([diff](https://github.com/norman/ambry/compare/0.2.3...0.2.4))
|
4
12
|
|
5
13
|
### [Norman Clarke](https://github.com/norman)
|
6
14
|
|
data/README.md
CHANGED
@@ -6,8 +6,7 @@ library with plain old Ruby objects that are searchable via a fast, simple
|
|
6
6
|
database-like API.
|
7
7
|
|
8
8
|
It implements Active Model and has generators to integrate nicely with Rails.
|
9
|
-
You can store your data in
|
10
|
-
cookie, or easily write your own IO adapter.
|
9
|
+
You can store your data in either YAML or dump file.
|
11
10
|
|
12
11
|
For more info, take a peek at the
|
13
12
|
[docs](http://rubydoc.info/github/ambry/ambry/master/frames), or read on for some
|
data/extras/countries.rb
CHANGED
data/lib/ambry/active_model.rb
CHANGED
@@ -49,6 +49,14 @@ module Ambry
|
|
49
49
|
new(*args).save!
|
50
50
|
end
|
51
51
|
|
52
|
+
# Create and save a model instance, returning false if any errors
|
53
|
+
# occur.
|
54
|
+
def create(*args)
|
55
|
+
new(*args).save!
|
56
|
+
rescue AmbryError
|
57
|
+
false
|
58
|
+
end
|
59
|
+
|
52
60
|
# Validate the uniqueness of a field's value in a model instance.
|
53
61
|
def validates_uniqueness_of(*attr_names)
|
54
62
|
validates_with Validations::Uniqueness, _merge_attributes(attr_names)
|
@@ -88,6 +96,7 @@ module Ambry
|
|
88
96
|
end
|
89
97
|
|
90
98
|
def save
|
99
|
+
return false unless valid?
|
91
100
|
run_callbacks(:save) do
|
92
101
|
@new_record = false
|
93
102
|
super
|
@@ -117,6 +126,10 @@ module Ambry
|
|
117
126
|
def update_attributes(*args)
|
118
127
|
run_callbacks(:save) { update(*args) }
|
119
128
|
end
|
129
|
+
|
130
|
+
def to_partial_path
|
131
|
+
"#{self.class.name.pluralize.underscore}/#{self.class.name.underscore}"
|
132
|
+
end
|
120
133
|
end
|
121
134
|
end
|
122
135
|
end
|
data/lib/ambry/hash_proxy.rb
CHANGED
@@ -12,7 +12,13 @@ module Ambry
|
|
12
12
|
|
13
13
|
# Allows accessing a hash attribute as a method.
|
14
14
|
def method_missing(symbol)
|
15
|
-
|
15
|
+
if hash.key?(symbol)
|
16
|
+
hash[symbol]
|
17
|
+
elsif hash.key?(symbol.to_s)
|
18
|
+
hash[symbol.to_s]
|
19
|
+
else
|
20
|
+
raise NoMethodError
|
21
|
+
end
|
16
22
|
end
|
17
23
|
|
18
24
|
# Allows accessing a hash attribute as hash key, either a string or symbol.
|
data/lib/ambry/version.rb
CHANGED
data/spec/active_model_spec.rb
CHANGED
@@ -59,6 +59,24 @@ describe Ambry::ActiveModel do
|
|
59
59
|
end
|
60
60
|
end
|
61
61
|
|
62
|
+
describe "#create" do
|
63
|
+
it "should not store invalid model instances" do
|
64
|
+
old_count = Book.count
|
65
|
+
Book.create({})
|
66
|
+
assert_equal old_count, Book.count
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
describe "#save" do
|
71
|
+
it "should not store invalid model instances" do
|
72
|
+
old_count = Book.count
|
73
|
+
book = Book.new
|
74
|
+
assert !book.valid?
|
75
|
+
book.save
|
76
|
+
assert_equal old_count, Book.count
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
62
80
|
describe "#to_json" do
|
63
81
|
it "should serialize" do
|
64
82
|
json = @model.to_json
|
@@ -86,7 +104,9 @@ describe Ambry::ActiveModel do
|
|
86
104
|
|
87
105
|
describe "callbacks" do
|
88
106
|
it "should fire save callbacks" do
|
107
|
+
Book.mapper.clear
|
89
108
|
book = Book.new valid_book
|
109
|
+
assert book.valid?
|
90
110
|
book.save
|
91
111
|
assert book.instance_variable_defined? :@save_callback_fired
|
92
112
|
end
|
@@ -112,4 +132,11 @@ describe Ambry::ActiveModel do
|
|
112
132
|
assert @book.valid?
|
113
133
|
end
|
114
134
|
end
|
135
|
+
|
136
|
+
describe "to_partial_path" do
|
137
|
+
it "should return something reasonable" do
|
138
|
+
assert_equal "books/book", Book.new.to_partial_path
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
115
142
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ambry
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-
|
12
|
+
date: 2012-03-15 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: ffaker
|
16
|
-
requirement: &
|
16
|
+
requirement: &70196828901600 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: '0'
|
22
22
|
type: :development
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *70196828901600
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: minitest
|
27
|
-
requirement: &
|
27
|
+
requirement: &70196828901100 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ~>
|
@@ -32,10 +32,10 @@ dependencies:
|
|
32
32
|
version: 2.2.2
|
33
33
|
type: :development
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *70196828901100
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: mocha
|
38
|
-
requirement: &
|
38
|
+
requirement: &70196828900640 !ruby/object:Gem::Requirement
|
39
39
|
none: false
|
40
40
|
requirements:
|
41
41
|
- - ! '>='
|
@@ -43,10 +43,10 @@ dependencies:
|
|
43
43
|
version: '0'
|
44
44
|
type: :development
|
45
45
|
prerelease: false
|
46
|
-
version_requirements: *
|
46
|
+
version_requirements: *70196828900640
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: activesupport
|
49
|
-
requirement: &
|
49
|
+
requirement: &70196828900020 !ruby/object:Gem::Requirement
|
50
50
|
none: false
|
51
51
|
requirements:
|
52
52
|
- - ~>
|
@@ -54,10 +54,10 @@ dependencies:
|
|
54
54
|
version: '3.0'
|
55
55
|
type: :development
|
56
56
|
prerelease: false
|
57
|
-
version_requirements: *
|
57
|
+
version_requirements: *70196828900020
|
58
58
|
- !ruby/object:Gem::Dependency
|
59
59
|
name: activemodel
|
60
|
-
requirement: &
|
60
|
+
requirement: &70196828915860 !ruby/object:Gem::Requirement
|
61
61
|
none: false
|
62
62
|
requirements:
|
63
63
|
- - ~>
|
@@ -65,10 +65,10 @@ dependencies:
|
|
65
65
|
version: '3.0'
|
66
66
|
type: :development
|
67
67
|
prerelease: false
|
68
|
-
version_requirements: *
|
68
|
+
version_requirements: *70196828915860
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: rake
|
71
|
-
requirement: &
|
71
|
+
requirement: &70196828915480 !ruby/object:Gem::Requirement
|
72
72
|
none: false
|
73
73
|
requirements:
|
74
74
|
- - ! '>='
|
@@ -76,7 +76,7 @@ dependencies:
|
|
76
76
|
version: '0'
|
77
77
|
type: :development
|
78
78
|
prerelease: false
|
79
|
-
version_requirements: *
|
79
|
+
version_requirements: *70196828915480
|
80
80
|
description: ! " Ambry is not an ORM, man! It's a database and ORM replacement
|
81
81
|
for (mostly)\n static models and small datasets. It provides ActiveModel compatibility,
|
82
82
|
and\n flexible searching and storage.\n"
|
@@ -93,13 +93,11 @@ files:
|
|
93
93
|
- Rakefile
|
94
94
|
- ambry.gemspec
|
95
95
|
- extras/bench.rb
|
96
|
-
- extras/cookie_demo.rb
|
97
96
|
- extras/countries.rb
|
98
97
|
- lib/ambry.rb
|
99
98
|
- lib/ambry/abstract_key_set.rb
|
100
99
|
- lib/ambry/active_model.rb
|
101
100
|
- lib/ambry/adapter.rb
|
102
|
-
- lib/ambry/adapters/cookie.rb
|
103
101
|
- lib/ambry/adapters/file.rb
|
104
102
|
- lib/ambry/adapters/yaml.rb
|
105
103
|
- lib/ambry/hash_proxy.rb
|
@@ -107,10 +105,8 @@ files:
|
|
107
105
|
- lib/ambry/model.rb
|
108
106
|
- lib/ambry/version.rb
|
109
107
|
- lib/generators/ambry_generator.rb
|
110
|
-
- lib/rack/ambry.rb
|
111
108
|
- spec/active_model_spec.rb
|
112
109
|
- spec/adapter_spec.rb
|
113
|
-
- spec/cookie_adapter_spec.rb
|
114
110
|
- spec/file_adapter_spec.rb
|
115
111
|
- spec/fixtures.yml
|
116
112
|
- spec/key_set_spec.rb
|
@@ -137,7 +133,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
137
133
|
version: '0'
|
138
134
|
requirements: []
|
139
135
|
rubyforge_project: ! '[none]'
|
140
|
-
rubygems_version: 1.8.
|
136
|
+
rubygems_version: 1.8.11
|
141
137
|
signing_key:
|
142
138
|
specification_version: 3
|
143
139
|
summary: An ActiveModel-compatible ORM-like library for storing model instances in
|
data/extras/cookie_demo.rb
DELETED
@@ -1,111 +0,0 @@
|
|
1
|
-
$LOAD_PATH << File.expand_path("../../lib", __FILE__)
|
2
|
-
$LOAD_PATH.uniq!
|
3
|
-
|
4
|
-
require "rubygems"
|
5
|
-
require "sinatra"
|
6
|
-
require "haml"
|
7
|
-
require "babosa"
|
8
|
-
require "date"
|
9
|
-
require "ambry"
|
10
|
-
require "ambry/adapters/cookie"
|
11
|
-
require "rack/ambry"
|
12
|
-
|
13
|
-
set :session, false
|
14
|
-
|
15
|
-
use Rack::Cookies
|
16
|
-
use Rack::Ambry, :name => :cookie, :secret => "Sssshhhh! This is a secret."
|
17
|
-
|
18
|
-
class Book
|
19
|
-
extend Ambry::Model
|
20
|
-
field :slug, :title, :author
|
21
|
-
use :cookie
|
22
|
-
|
23
|
-
def title=(value)
|
24
|
-
@slug = value.to_slug.normalize.to_s
|
25
|
-
@title = value
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
get "/" do
|
30
|
-
@header = "Books"
|
31
|
-
@books = Book.all
|
32
|
-
haml :index
|
33
|
-
end
|
34
|
-
|
35
|
-
get "/books/new" do
|
36
|
-
@header = "Add a Book"
|
37
|
-
@action = "/books"
|
38
|
-
haml :new
|
39
|
-
end
|
40
|
-
|
41
|
-
get "/books/:slug/edit" do |slug|
|
42
|
-
@book = Book.get(slug)
|
43
|
-
@action = "/books"
|
44
|
-
@header = @book.title
|
45
|
-
params[:title] = @book.title
|
46
|
-
params[:author] = @book.author
|
47
|
-
haml :edit
|
48
|
-
end
|
49
|
-
|
50
|
-
get "/books/:slug" do |slug|
|
51
|
-
@book = Book.get(slug)
|
52
|
-
@header = @book.title
|
53
|
-
haml :book
|
54
|
-
end
|
55
|
-
|
56
|
-
post "/books" do
|
57
|
-
Book.delete params[:slug] if params[:slug]
|
58
|
-
@book = Book.create params if params[:title]
|
59
|
-
redirect "/"
|
60
|
-
end
|
61
|
-
|
62
|
-
__END__
|
63
|
-
@@layout
|
64
|
-
!!! 5
|
65
|
-
%html
|
66
|
-
%head
|
67
|
-
%meta(http-equiv="Content-Type" content="text/html; charset=utf-8")
|
68
|
-
%title Ambry Cookie Adapter Demo
|
69
|
-
%body
|
70
|
-
%h2= @header
|
71
|
-
= yield
|
72
|
-
|
73
|
-
@@edit
|
74
|
-
= haml(:form, :layout => false)
|
75
|
-
|
76
|
-
@@index
|
77
|
-
%ul
|
78
|
-
- @books.each do |book|
|
79
|
-
%li= '<a href="/books/%s">%s</a>' % [book.slug, book.title, book.author]
|
80
|
-
%p.controls
|
81
|
-
<a href="/books/new">New book</a>
|
82
|
-
|
83
|
-
@@new
|
84
|
-
= haml(:form, :layout => false)
|
85
|
-
%p.controls
|
86
|
-
<a href="/">Books</a>
|
87
|
-
|
88
|
-
@@book
|
89
|
-
by #{@book.author}
|
90
|
-
%p.controls
|
91
|
-
<a href="/">Books</a>
|
92
|
-
<a href="/books/#{@book.slug}/edit">Edit</a>
|
93
|
-
|
94
|
-
@@form
|
95
|
-
%form(method="post" enctype="utf-8" action=@action)
|
96
|
-
%p
|
97
|
-
- if @book
|
98
|
-
%input#slug{:type => "hidden", :value => @book.slug, :name => "slug"}
|
99
|
-
%label(for="title") Title:
|
100
|
-
%br
|
101
|
-
%input#title{:type => "text", :value => params[:title], :name => "title"}
|
102
|
-
%p
|
103
|
-
%label(for="author") Author:
|
104
|
-
%br
|
105
|
-
%input#author{:type => "text", :value => params[:author], :name => "author"}
|
106
|
-
%p
|
107
|
-
%input(type="submit" value="save it")
|
108
|
-
- if @book
|
109
|
-
%form{:method => "post", :enctype => "utf-8", :action => "/books"}
|
110
|
-
%input#slug{:type => "hidden", :value => @book.slug, :name => "slug"}
|
111
|
-
%input(type="submit" value="or delete it")
|
@@ -1,55 +0,0 @@
|
|
1
|
-
require "active_support"
|
2
|
-
require "active_support/message_verifier"
|
3
|
-
require "zlib"
|
4
|
-
|
5
|
-
module Ambry
|
6
|
-
module Adapters
|
7
|
-
|
8
|
-
# Ambry's cookie adapter allows you to store a Ambry database inside
|
9
|
-
# a zipped and signed string suitable for setting as a cookie. This can be
|
10
|
-
# useful for modelling things like basic shopping carts or form wizards.
|
11
|
-
# Keep in mind the data is signed, so it can't be tampered with. However,
|
12
|
-
# the data is not *encrypted*, so somebody that wanted to could unzip and
|
13
|
-
# load the cookie data to see what's inside. So don't send this data
|
14
|
-
# client-side if it's at all sensitive.
|
15
|
-
class Cookie < Adapter
|
16
|
-
|
17
|
-
attr :verifier
|
18
|
-
attr_accessor :data
|
19
|
-
|
20
|
-
MAX_DATA_LENGTH = 4096
|
21
|
-
|
22
|
-
def self.max_data_length
|
23
|
-
MAX_DATA_LENGTH
|
24
|
-
end
|
25
|
-
|
26
|
-
def initialize(options)
|
27
|
-
@data = options[:data]
|
28
|
-
@verifier = ActiveSupport::MessageVerifier.new(options[:secret])
|
29
|
-
super
|
30
|
-
end
|
31
|
-
|
32
|
-
def export_data
|
33
|
-
cookie = verifier.generate(Zlib::Deflate.deflate(Marshal.dump(db)))
|
34
|
-
length = cookie.bytesize
|
35
|
-
if length > Cookie.max_data_length
|
36
|
-
raise(AmbryError, "Data is %s bytes, cannot exceed %s" % [length, Cookie.max_data_length])
|
37
|
-
end
|
38
|
-
cookie
|
39
|
-
end
|
40
|
-
|
41
|
-
def import_data
|
42
|
-
(!data || data.empty?) ? {} : Marshal.load(Zlib::Inflate.inflate(verifier.verify(data)))
|
43
|
-
end
|
44
|
-
|
45
|
-
def load_database
|
46
|
-
@db = import_data
|
47
|
-
@db.map(&:freeze)
|
48
|
-
end
|
49
|
-
|
50
|
-
def save_database
|
51
|
-
@data = export_data
|
52
|
-
end
|
53
|
-
end
|
54
|
-
end
|
55
|
-
end
|
data/lib/rack/ambry.rb
DELETED
@@ -1,23 +0,0 @@
|
|
1
|
-
require "rack/contrib"
|
2
|
-
|
3
|
-
module Rack
|
4
|
-
# Rack::Ambry is a middleware that allows you to store a Ambry datbase
|
5
|
-
# in a cookie.
|
6
|
-
# @see Ambry::Adapters::Cookie
|
7
|
-
class Ambry
|
8
|
-
def initialize(app, options = {})
|
9
|
-
@app = app
|
10
|
-
options = options.call if options.respond_to? :call
|
11
|
-
@cookie_name = options.delete(:cookie_name) || "ambry_data"
|
12
|
-
@ambry = ::Ambry::Adapters::Cookie.new(options.merge(:sync => true))
|
13
|
-
end
|
14
|
-
|
15
|
-
def call(env)
|
16
|
-
@ambry.data = env["rack.cookies"][@cookie_name]
|
17
|
-
@ambry.load_database
|
18
|
-
status, headers, body = @app.call(env)
|
19
|
-
env["rack.cookies"][@cookie_name] = @ambry.export_data
|
20
|
-
[status, headers, body]
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|
data/spec/cookie_adapter_spec.rb
DELETED
@@ -1,81 +0,0 @@
|
|
1
|
-
require File.expand_path("../spec_helper", __FILE__)
|
2
|
-
require "ambry/adapters/cookie"
|
3
|
-
|
4
|
-
class User
|
5
|
-
extend Ambry::Model
|
6
|
-
field :email, :name
|
7
|
-
end
|
8
|
-
|
9
|
-
module CookieAdapterSpecHelpers
|
10
|
-
def secret
|
11
|
-
"ssssshh... this is a secret!"
|
12
|
-
end
|
13
|
-
|
14
|
-
def sample_data
|
15
|
-
# hash = {"User" => {valid_user[:email] => valid_user}}
|
16
|
-
# p ActiveSupport::MessageVerifier.new(secret).generate(Zlib::Deflate.deflate(Marshal.dump(hash)))
|
17
|
-
"BAgiTnicY+GoZvNU4gwtTi1is2JzDQHxhLPyUx2KkzNy81P1kvNz2awZQqrZrTjzEnNTPZX4" +
|
18
|
-
"vfJTFYLBkiAJK67U3MTMHKyaAJGaGlk=--08913fe1c677e4bb0dd34ef90fb22f9027e587f4"
|
19
|
-
end
|
20
|
-
|
21
|
-
def valid_user
|
22
|
-
@valid_user ||= {:name => "Joe Schmoe", :email => "joe@schmoe.com"}
|
23
|
-
end
|
24
|
-
|
25
|
-
def load_fixtures
|
26
|
-
Ambry.adapters.clear
|
27
|
-
@adapter = Ambry::Adapters::Cookie.new \
|
28
|
-
:name => :cookie,
|
29
|
-
:secret => secret
|
30
|
-
User.use :cookie, :sync => true
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
describe Ambry::Adapters::Cookie do
|
35
|
-
|
36
|
-
include CookieAdapterSpecHelpers
|
37
|
-
|
38
|
-
before { load_fixtures }
|
39
|
-
after { Ambry.adapters.clear }
|
40
|
-
|
41
|
-
describe Ambry::Adapters::Cookie do
|
42
|
-
|
43
|
-
describe "#initialize" do
|
44
|
-
it "should decode signed data if given" do
|
45
|
-
adapter = Ambry::Adapters::Cookie.new \
|
46
|
-
:secret => secret,
|
47
|
-
:data => sample_data
|
48
|
-
assert_kind_of Hash, adapter.db["User"]
|
49
|
-
assert_equal "joe@schmoe.com", adapter.db["User"].keys.first
|
50
|
-
end
|
51
|
-
|
52
|
-
it "should load properly with nil or blank data" do
|
53
|
-
[nil, ""].each_with_index do |arg, index|
|
54
|
-
adapter = Ambry::Adapters::Cookie.new \
|
55
|
-
:secret => secret,
|
56
|
-
:data => arg,
|
57
|
-
:name => :"main_#{index}"
|
58
|
-
assert_instance_of Hash, adapter.db
|
59
|
-
end
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
|
-
describe "#export_data" do
|
64
|
-
it "should encode and sign the database" do
|
65
|
-
User.create \
|
66
|
-
:name => Faker::Name.name,
|
67
|
-
:email => Faker::Internet.email
|
68
|
-
refute_nil @adapter.export_data
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
|
-
describe "#save_database" do
|
73
|
-
it "should raise a AmbryError if signed data exceeds max data length" do
|
74
|
-
Ambry::Adapters::Cookie.stubs(:max_data_length).returns(1)
|
75
|
-
assert_raises Ambry::AmbryError do
|
76
|
-
User.create valid_user
|
77
|
-
end
|
78
|
-
end
|
79
|
-
end
|
80
|
-
end
|
81
|
-
end
|