cheatr 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +173 -0
- data/Rakefile +1 -0
- data/bin/cheatr +108 -0
- data/cheatr.gemspec +37 -0
- data/lib/cheatr.rb +6 -0
- data/lib/cheatr/client.rb +133 -0
- data/lib/cheatr/client/sheet.rb +137 -0
- data/lib/cheatr/error.rb +7 -0
- data/lib/cheatr/server.rb +22 -0
- data/lib/cheatr/server/app.rb +80 -0
- data/lib/cheatr/server/helpers.rb +47 -0
- data/lib/cheatr/server/public/404.html +157 -0
- data/lib/cheatr/server/public/css/main.css +300 -0
- data/lib/cheatr/server/public/css/normalize.css +533 -0
- data/lib/cheatr/server/public/favicon.ico +0 -0
- data/lib/cheatr/server/public/humans.txt +13 -0
- data/lib/cheatr/server/public/robots.txt +3 -0
- data/lib/cheatr/server/sheet.rb +139 -0
- data/lib/cheatr/server/views/index.md.erb +5 -0
- data/lib/cheatr/server/views/layout.html.erb +24 -0
- data/lib/cheatr/server/views/sheet.md.erb +1 -0
- data/lib/cheatr/version.rb +3 -0
- metadata +239 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Ernesto Garcia
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,173 @@
|
|
1
|
+
# cheatr
|
2
|
+
|
3
|
+
Cheatr is a simple command line utility to access cheat sheets from an online
|
4
|
+
repository.
|
5
|
+
|
6
|
+
Generally speaking, cheatr is a tool that allows to host an online collection
|
7
|
+
of cheat sheets, and lets users query and maintain this repository remotely via
|
8
|
+
a command line client utility.
|
9
|
+
|
10
|
+
### What is a cheat sheet?
|
11
|
+
|
12
|
+
In the context of cheatr, a cheat sheet is a small document with quick tips and
|
13
|
+
hints on a concrete and specific topic. Typically it contains notes intended
|
14
|
+
to aid one's memory, like listing common and useful shortcuts used on
|
15
|
+
a program, quick and easy tips on how to use a library, or the command line
|
16
|
+
options of a shell command, for instance.
|
17
|
+
|
18
|
+
Cheatr however enforces very little on the contents of the cheat sheets in
|
19
|
+
a repository. The above specification is just a guideline on the intended use
|
20
|
+
of this tool, but in practice you can use it to store and maintain any
|
21
|
+
collection of text documents in it.
|
22
|
+
|
23
|
+
Further down this document you'll find more information about the format of
|
24
|
+
[cheat sheet contents](#cheat-sheet-contents).
|
25
|
+
|
26
|
+
## Installation
|
27
|
+
|
28
|
+
$ gem install cheatr
|
29
|
+
|
30
|
+
## Usage
|
31
|
+
|
32
|
+
Cheatr retrieves cheat sheets from a remote server, so we need to tell
|
33
|
+
cheatr where to look.
|
34
|
+
|
35
|
+
$ cheatr config server cheatr.gnapse.com
|
36
|
+
|
37
|
+
This generates a configuration file in `~/.cheatr/config.yml`. Now we can
|
38
|
+
start querying the remote server for cheat sheets, or start creating new ones.
|
39
|
+
|
40
|
+
$ cheatr show "ruby*" # Cheat sheets with names starting with 'ruby'
|
41
|
+
ruby.strings
|
42
|
+
ruby.blocks
|
43
|
+
rubygems
|
44
|
+
|
45
|
+
$ cheatr search ruby # With this command you can ommit the wildcard
|
46
|
+
ruby.strings
|
47
|
+
ruby.blocks
|
48
|
+
rubygems
|
49
|
+
|
50
|
+
$ cheatr all # Lists all cheat sheets available
|
51
|
+
ack
|
52
|
+
bash
|
53
|
+
cplusplus
|
54
|
+
...
|
55
|
+
ruby
|
56
|
+
vim
|
57
|
+
zsh
|
58
|
+
|
59
|
+
Tell cheatr to show a given cheat sheet.
|
60
|
+
|
61
|
+
$ cheatr show ruby.blocks
|
62
|
+
Ruby blocks are an awesome language feature!
|
63
|
+
|
64
|
+
The contents of a cheat sheet are cached once it has been fetched. You can
|
65
|
+
force cheatr to fetch remote contents.
|
66
|
+
|
67
|
+
$ cheatr show ruby.blocks -r
|
68
|
+
|
69
|
+
You can edit existing cheat sheets, or create new ones.
|
70
|
+
|
71
|
+
$ cheatr edit ruby.blocks
|
72
|
+
|
73
|
+
This will launch you preferred `$EDITOR` with the current contents of the
|
74
|
+
specified cheat sheet. After you save the file and quit the editor, `cheatr`
|
75
|
+
will update the cheat sheet contents.
|
76
|
+
|
77
|
+
Cheatr commands provide help, so you can discover all its features and
|
78
|
+
possibilities. Type `cheatr --help` or `cheatr <command> --help` for details.
|
79
|
+
|
80
|
+
## Web server
|
81
|
+
|
82
|
+
This gem includes the server side component as well, so anyone can host their
|
83
|
+
own cheat sheets service. This server offers the basic API-like calls to
|
84
|
+
query, retrieve and modify cheat sheet contents. You can start a cheatr
|
85
|
+
server with the following command:
|
86
|
+
|
87
|
+
$ cheatr server /path/to/cheatr/repository
|
88
|
+
|
89
|
+
The repository path refers to the location where the cheats repository can be
|
90
|
+
found. A cheatr repository is nothing more than a git repository holding the
|
91
|
+
cheat sheets as files in it. If omitted, the current working directory is
|
92
|
+
assummed.
|
93
|
+
|
94
|
+
A cheatr server is a [sinatra][] app, so it can also be started using [rack][].
|
95
|
+
Every cheatr repository, when created, is initialized with a standard
|
96
|
+
`config.ru` file. So while placed in the repository's root folder, the server
|
97
|
+
can be started with the following command:
|
98
|
+
|
99
|
+
$ rackup
|
100
|
+
|
101
|
+
Also try `rackup --help` for more options.
|
102
|
+
|
103
|
+
[sinatra]: http://www.sinatrarb.com
|
104
|
+
[rack]: https://github.com/rack/rack
|
105
|
+
|
106
|
+
### Web interface
|
107
|
+
|
108
|
+
Additionally, cheatr servers can be accessed directly on a browser, where
|
109
|
+
content is delivered as traditional hyperlinked web pages instead of mere plain
|
110
|
+
text, with the possibility to search for cheat sheets, or [link between
|
111
|
+
them](#cheat-sheet-hyperlinks), converting cheatr in a simple but powerful
|
112
|
+
cheat sheets wiki.
|
113
|
+
|
114
|
+
To try this, type the following command:
|
115
|
+
|
116
|
+
$ cheatr browse ruby.blocks
|
117
|
+
|
118
|
+
It will open the contents of the specified cheat sheet in your preferred
|
119
|
+
browser. Or you can always type the URLs in your browser yourself, provided
|
120
|
+
you know the cheatr server you want to access.
|
121
|
+
|
122
|
+
## Cheat sheet contents
|
123
|
+
|
124
|
+
Although cheatr does not enforce any format to the contents of cheat sheets,
|
125
|
+
it assumes they consist of markdown text. Thus is highly beneficial to adhere
|
126
|
+
to this convention, and embrace it.
|
127
|
+
|
128
|
+
### Cheat sheet hyperlinks
|
129
|
+
|
130
|
+
In addition to standard markdown syntax, cheatr supports a minor custom
|
131
|
+
extension, that becomes useful when browsing a cheatr server in a web
|
132
|
+
browser. This is related to linking in between cheat sheets, as hinted in the
|
133
|
+
previous section.
|
134
|
+
|
135
|
+
This is best shown with an example.
|
136
|
+
|
137
|
+
```
|
138
|
+
Dynamic languages, like {{ruby}} and {{python}}, are more versatile than
|
139
|
+
{{c++|cplusplus}} in many situations.
|
140
|
+
```
|
141
|
+
|
142
|
+
Noticed the slices of text surrounded by doble braces? These are cheatr
|
143
|
+
hyperlinks. When showing pages through the web interface in a browser, these
|
144
|
+
will be converted to links to the appropriate cheat sheet.
|
145
|
+
|
146
|
+
Note also the link to the `cplusplus` cheat sheet. The text before the
|
147
|
+
vertical bar will be used as the link text, whereas the text after it is the
|
148
|
+
name of the cheat sheet to link to.
|
149
|
+
|
150
|
+
In the command line, these "links" will be shown unmodified, still serving the
|
151
|
+
purpose of discovery, because the user can identify these "links" and discover
|
152
|
+
new related topics to continue "browsing" and learning.
|
153
|
+
|
154
|
+
## Contributing
|
155
|
+
|
156
|
+
Feel free to dive into the code to understand more about how cheatr works, or
|
157
|
+
just for fun. Please note that this is a very young project with very rough
|
158
|
+
edges still. Issues reports and pull requests are welcome!
|
159
|
+
|
160
|
+
In case you decide to step in:
|
161
|
+
|
162
|
+
1. Fork the repo (`git clone https://github.com/gnapse/cheatr.git`)
|
163
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
164
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
165
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
166
|
+
5. Create new Pull Request
|
167
|
+
|
168
|
+
## Thanks
|
169
|
+
|
170
|
+
Thanks to [defunkt][] for [cheat][], which provided the idea for this project.
|
171
|
+
|
172
|
+
[defunkt]: https://github.com/defunkt
|
173
|
+
[cheat]: https://github.com/defunkt/cheat
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/bin/cheatr
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "slop"
|
4
|
+
require "cheatr"
|
5
|
+
|
6
|
+
def check(condition, message)
|
7
|
+
unless condition
|
8
|
+
$stderr.puts message
|
9
|
+
exit
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
unless ARGV.first == 'config'
|
14
|
+
check Cheatr::Client.server, "Run 'cheatr config server <location>' to set the remote cheatr server."
|
15
|
+
end
|
16
|
+
|
17
|
+
Slop.parse help: true do
|
18
|
+
|
19
|
+
command :server do
|
20
|
+
banner 'Usage: cheatr server [options] [repository-path]'
|
21
|
+
on 'p', 'port=', 'The port where the server will listen to.'
|
22
|
+
run do |opts, args|
|
23
|
+
check args.length <= 1, opts
|
24
|
+
repository = args.first || Dir.pwd
|
25
|
+
Cheatr::Server.run(repository, opts)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
command :show do
|
30
|
+
banner 'Usage: cheatr show [options] <sheet-name>'
|
31
|
+
on 'r', 'remote', 'Forces retrieving contents from remote, ignoring the cache, if any.'
|
32
|
+
run do |opts, args|
|
33
|
+
check args.length == 1, opts
|
34
|
+
name = args.first
|
35
|
+
if name.include?('*')
|
36
|
+
Cheatr::Client.search_sheets(name)
|
37
|
+
else
|
38
|
+
Cheatr::Client.display_sheet(name, ignore_cache: opts[:remote])
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
command :browse do
|
44
|
+
banner 'Usage: cheatr browse [query]'
|
45
|
+
run do |opts, args|
|
46
|
+
check args.length <= 1, opts
|
47
|
+
Cheatr::Client.browse(args.first)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
command :search do
|
52
|
+
banner 'Usage: cheatr search <query>'
|
53
|
+
run do |opts, args|
|
54
|
+
check args.length == 1, opts
|
55
|
+
Cheatr::Client.search_sheets(args.first)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
command :all do
|
60
|
+
banner 'Usage: cheatr all'
|
61
|
+
run do |opts, args|
|
62
|
+
check args.length == 0, opts
|
63
|
+
Cheatr::Client.search_sheets
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
command :edit do
|
68
|
+
banner 'Usage: cheatr edit [options] <sheet-name>'
|
69
|
+
on 'y', 'yes', 'Do not ask for confirmation after editing.'
|
70
|
+
run do |opts, args|
|
71
|
+
check args.length == 1, opts
|
72
|
+
Cheatr::Client.edit_sheet(args.first, skip_confirmation: opts[:yes])
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
command :fetch do
|
77
|
+
banner 'Usage: cheatr fetch [options] <sheet-name>+'
|
78
|
+
on 'q', 'quiet', 'Do not print anything to standard output. Overrides --errors.'
|
79
|
+
on 'e', 'errors', 'Print error messages only.'
|
80
|
+
run do |opts, args|
|
81
|
+
Cheatr::Client.fetch_sheets(args, opts)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
command :clear do
|
86
|
+
banner 'Usage: cheatr clear [options] [sheet-name]'
|
87
|
+
on 'a', 'all', 'Clear all cached cheat sheets. Requires confirmation by default.'
|
88
|
+
on 'y', 'yes', 'Do not ask for confirmation for clearing the cache completely.'
|
89
|
+
on 'q', 'quiet', 'Do not print anything to standard output. Implies --yes, skipping confirmation.'
|
90
|
+
run do |opts, args|
|
91
|
+
if opts[:all]
|
92
|
+
Cheatr::Client.clear_cache(:all, skip_confirmation: opts[:yes], quiet: opts[:quiet])
|
93
|
+
else
|
94
|
+
check(args.length == 1, opts)
|
95
|
+
Cheatr::Client.clear_cache(args.first, quiet: opts[:quiet])
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
command :config do
|
101
|
+
banner 'Usage: cheatr config <param> <value>'
|
102
|
+
run do |opts, args|
|
103
|
+
check args.length == 2, opts
|
104
|
+
Cheatr::Client.set_config(args.first => args.last)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
end
|
data/cheatr.gemspec
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'cheatr/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "cheatr"
|
8
|
+
spec.version = Cheatr::VERSION
|
9
|
+
spec.authors = ["Ernesto García"]
|
10
|
+
spec.email = ["gnapse@gmail.com"]
|
11
|
+
spec.description = %q{Display cheat sheets for a variety of programs, tools and libraries, right on the command line.}
|
12
|
+
spec.summary = %q{Display cheat sheets right on the command line.}
|
13
|
+
spec.homepage = ""
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_dependency "slop"
|
22
|
+
|
23
|
+
# server dependencies
|
24
|
+
spec.add_dependency "git"
|
25
|
+
spec.add_dependency "sinatra"
|
26
|
+
spec.add_dependency "activemodel"
|
27
|
+
spec.add_dependency "redcarpet"
|
28
|
+
|
29
|
+
# client dependencies
|
30
|
+
spec.add_dependency "rest-client"
|
31
|
+
spec.add_dependency "pager"
|
32
|
+
|
33
|
+
# test dependencies
|
34
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
35
|
+
spec.add_development_dependency "rake"
|
36
|
+
spec.add_development_dependency "rspec"
|
37
|
+
end
|
data/lib/cheatr.rb
ADDED
@@ -0,0 +1,133 @@
|
|
1
|
+
require "yaml"
|
2
|
+
require "pager"
|
3
|
+
require "cheatr/client/sheet"
|
4
|
+
|
5
|
+
module Cheatr
|
6
|
+
module Client
|
7
|
+
extend Pager
|
8
|
+
|
9
|
+
# Actions
|
10
|
+
|
11
|
+
def self.search_sheets(query = nil)
|
12
|
+
query += '*' unless query.nil? || query.include?('*')
|
13
|
+
page
|
14
|
+
puts Sheet.all(query)
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.display_sheet(name, opts = {})
|
18
|
+
sheet = Sheet.new(name, opts)
|
19
|
+
if sheet.contents
|
20
|
+
page
|
21
|
+
puts "(cached version)" unless sheet.remote?
|
22
|
+
puts sheet.contents
|
23
|
+
else
|
24
|
+
puts sheet.errors.first
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.edit_sheet(name, opts = {})
|
29
|
+
sheet = Sheet.new(name, ignore_cache: true)
|
30
|
+
|
31
|
+
file = Tempfile.new([name, '.md'])
|
32
|
+
file.write(sheet.contents) if sheet.contents
|
33
|
+
file.close
|
34
|
+
system "#{editor} #{file.path}"
|
35
|
+
file.open
|
36
|
+
contents = file.read
|
37
|
+
file.close
|
38
|
+
|
39
|
+
if contents.strip.empty?
|
40
|
+
puts "Edited sheet contents were empty so nothing was saved."
|
41
|
+
return
|
42
|
+
end
|
43
|
+
|
44
|
+
if opts[:skip_confirmation] || confirm("Do you want to update the '#{name}' cheat sheet?")
|
45
|
+
sheet.contents = contents
|
46
|
+
if sheet.save
|
47
|
+
puts "Sheet '#{name}' was updated successfully."
|
48
|
+
else
|
49
|
+
puts "Sheet '#{name}' could not be updated."
|
50
|
+
$stderr.puts "Error: #{sheet.errors.first}" if sheet.errors
|
51
|
+
end
|
52
|
+
else
|
53
|
+
puts "Sheet '#{name}' was left unchanged."
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.browse(query = nil)
|
58
|
+
open_cmd = `uname` =~ /Darwin/ ? 'open' : 'xdg-open'
|
59
|
+
uri = "http://#{server}/#{query}"
|
60
|
+
exec "#{open_cmd} #{uri}"
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.fetch_sheets(arr, opts = {})
|
64
|
+
arr.each do |name|
|
65
|
+
sheet = Sheet.new(name, ignore_cache: true)
|
66
|
+
if sheet.contents
|
67
|
+
puts "Sheet '#{name}' fetched successfully." unless opts[:quiet] || opts[:errors]
|
68
|
+
else
|
69
|
+
message = sheet.errors.first || "Sheet '#{name}' could not be fetched."
|
70
|
+
puts message unless opts[:quiet]
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def self.clear_cache(name = :all, opts = {})
|
76
|
+
if name == :all
|
77
|
+
if opts[:quiet] || opts[:skip_confirmation] || confirm("Are you sure you want clear all cheat sheets cache?")
|
78
|
+
FileUtils.rm Dir.glob("#{cache_dir}/*.md")
|
79
|
+
puts "All cached cheat sheets have been removed." unless opts[:quiet]
|
80
|
+
else
|
81
|
+
puts "Cached cheat sheets were not removed." unless opts[:quiet]
|
82
|
+
end
|
83
|
+
else
|
84
|
+
FileUtils.rm "#{cache_dir}/#{name}.md", force: true
|
85
|
+
puts "Removed cached version of '#{name}'." unless opts[:quiet]
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# Configuration settings
|
90
|
+
|
91
|
+
def self.mkdir(dir)
|
92
|
+
FileUtils.mkdir_p dir
|
93
|
+
dir
|
94
|
+
end
|
95
|
+
|
96
|
+
def self.cheatr_dir
|
97
|
+
@@cheatr_dir ||= mkdir File.join(File.expand_path("~"), ".cheatr")
|
98
|
+
end
|
99
|
+
|
100
|
+
def self.cache_dir
|
101
|
+
@@cache_dir ||= mkdir File.join(cheatr_dir, 'cache', server.gsub(/:\d+\z/, ''))
|
102
|
+
end
|
103
|
+
|
104
|
+
def self.server
|
105
|
+
@@sever ||= config['server']
|
106
|
+
end
|
107
|
+
|
108
|
+
def self.editor
|
109
|
+
@@editor ||= ENV['EDITOR']
|
110
|
+
end
|
111
|
+
|
112
|
+
def self.config_file
|
113
|
+
@@config_file ||= File.join(cheatr_dir, 'config.yml')
|
114
|
+
end
|
115
|
+
|
116
|
+
def self.config
|
117
|
+
@@config ||= YAML.load_file(config_file) || {} rescue {}
|
118
|
+
end
|
119
|
+
|
120
|
+
def self.set_config(options)
|
121
|
+
config.merge!(options)
|
122
|
+
File.open(config_file, 'w') { |f| f.write config.to_yaml }
|
123
|
+
end
|
124
|
+
|
125
|
+
private
|
126
|
+
|
127
|
+
def self.confirm(message)
|
128
|
+
print "#{message} (yes/no) "
|
129
|
+
answer = $stdin.gets.chomp
|
130
|
+
%w(yes Yes YES y Y).include? answer
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|