telegrator 0.2.0.rc1 → 0.2.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.
- checksums.yaml +4 -4
- data/.rubocop.yml +5 -0
- data/.rubocop_todo.yml +10 -0
- data/.travis.yml +4 -0
- data/Rakefile +5 -5
- data/bin/console +4 -4
- data/lib/telegrator/cli.rb +6 -2
- data/lib/telegrator/generators/bot/generator.rb +28 -11
- data/lib/telegrator/generators/bot/templates/Capfile.tt +18 -0
- data/lib/telegrator/generators/bot/templates/Rakefile.tt +2 -0
- data/lib/telegrator/generators/bot/templates/app/commands/base.rb.tt +3 -1
- data/lib/telegrator/generators/bot/templates/bin/{bot.tt → bot} +0 -0
- data/lib/telegrator/generators/bot/templates/config/deploy.rb.tt +21 -0
- data/lib/telegrator/generators/bot/templates/config/deploy/production.rb.tt +61 -0
- data/lib/telegrator/generators/bot/templates/config/deploy/staging.rb.tt +1 -0
- data/lib/telegrator/generators/bot/templates/config/initializers/sequel.rb.tt +3 -0
- data/lib/telegrator/generators/bot/templates/db/migrate/001_create_users.rb.tt +16 -0
- data/lib/telegrator/generators/bot/templates/env.tt +14 -0
- data/lib/telegrator/generators/bot/templates/gitignore.tt +4 -0
- data/lib/telegrator/generators/bot/templates/lib/capistrano/tasks/bot.rake.tt +8 -0
- data/lib/telegrator/generators/bot/templates/lib/tasks/bot.rake.tt +29 -0
- data/lib/telegrator/generators/bot/templates/lib/tasks/db.rake.tt +31 -0
- data/lib/telegrator/version.rb +1 -1
- data/telegrator.gemspec +2 -0
- metadata +33 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1570bb5bb5f21daeb0442224c24fbd454de61540
|
4
|
+
data.tar.gz: a4e20acf9ea74b51ffdae4ebb9febceb1e853c44
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 68bdb6495e650a711fad8435149da2b35e8dd7e9a9371a3b223f42b038d1fc651bfc67a4780a5e2f93b6fdf46dc958561704f6401c4beb8719007850352a04ab
|
7
|
+
data.tar.gz: 6a92a1a93d1b65e149283576abedee0bed95bf399c54989c9e4d7ffffde9d4a3f96b8337a208f9f074687c37d0d8f9d52bf8db10a202230139956bd29c336d43
|
data/.rubocop.yml
ADDED
data/.rubocop_todo.yml
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
# Offense count: 5
|
2
|
+
Style/Documentation:
|
3
|
+
Exclude:
|
4
|
+
- 'spec/**/*'
|
5
|
+
- 'test/**/*'
|
6
|
+
- 'lib/telegrator.rb'
|
7
|
+
- 'lib/telegrator/cli.rb'
|
8
|
+
- 'lib/telegrator/generators.rb'
|
9
|
+
- 'lib/telegrator/generators/base.rb'
|
10
|
+
- 'lib/telegrator/generators/bot/generator.rb'
|
data/.travis.yml
CHANGED
data/Rakefile
CHANGED
@@ -1,10 +1,10 @@
|
|
1
|
-
require
|
2
|
-
require
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
require 'rake/testtask'
|
3
3
|
|
4
4
|
Rake::TestTask.new(:test) do |t|
|
5
|
-
t.libs <<
|
6
|
-
t.libs <<
|
5
|
+
t.libs << 'test'
|
6
|
+
t.libs << 'lib'
|
7
7
|
t.test_files = FileList['test/**/*_test.rb']
|
8
8
|
end
|
9
9
|
|
10
|
-
task :
|
10
|
+
task default: :test
|
data/bin/console
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'telegrator'
|
5
5
|
|
6
6
|
# You can add fixtures and/or initialization code here to make experimenting
|
7
7
|
# with your gem easier. You can also use a different console, if you like.
|
8
8
|
|
9
9
|
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
-
# require
|
10
|
+
# require 'pry'
|
11
11
|
# Pry.start
|
12
12
|
|
13
|
-
require
|
13
|
+
require 'irb'
|
14
14
|
IRB.start(__FILE__)
|
data/lib/telegrator/cli.rb
CHANGED
@@ -2,7 +2,11 @@ require 'thor'
|
|
2
2
|
|
3
3
|
module Telegrator
|
4
4
|
class CLI < Thor
|
5
|
-
|
6
|
-
|
5
|
+
register(
|
6
|
+
Generators::Bot,
|
7
|
+
'bot',
|
8
|
+
'bot',
|
9
|
+
'Generate a skeleton for creating a new bot'
|
10
|
+
)
|
7
11
|
end
|
8
12
|
end
|
@@ -3,7 +3,7 @@ module Telegrator
|
|
3
3
|
class Bot < Base
|
4
4
|
include Thor::Actions
|
5
5
|
|
6
|
-
DATABASES = %w[postgresql mongodb]
|
6
|
+
DATABASES = %w[postgresql mongodb].freeze
|
7
7
|
|
8
8
|
# TODO: move to Base class
|
9
9
|
def self.source_root
|
@@ -35,6 +35,18 @@ module Telegrator
|
|
35
35
|
|
36
36
|
argument :name
|
37
37
|
|
38
|
+
def create_root_files
|
39
|
+
template 'gitignore.tt', '.gitignore'
|
40
|
+
inside { run 'git init' }
|
41
|
+
|
42
|
+
template 'env.tt', '.env.sample'
|
43
|
+
template 'env.tt', '.env'
|
44
|
+
|
45
|
+
template 'Gemfile.tt'
|
46
|
+
template 'Rakefile.tt'
|
47
|
+
template 'Capfile.tt' unless options[:skip_capistrano]
|
48
|
+
end
|
49
|
+
|
38
50
|
# === app/ directory ===
|
39
51
|
def create_app_dir
|
40
52
|
empty_directory 'app'
|
@@ -65,17 +77,30 @@ module Telegrator
|
|
65
77
|
|
66
78
|
# === bin/ directory ===
|
67
79
|
def create_bin_dir
|
68
|
-
directory 'bin'
|
80
|
+
directory 'bin', mode: :preserve
|
69
81
|
end
|
70
82
|
|
71
83
|
# === config/ directory ===
|
72
84
|
def create_config_dir
|
73
85
|
directory 'config'
|
86
|
+
|
87
|
+
if options[:skip_capistrano]
|
88
|
+
remove_file 'config/deploy/'
|
89
|
+
remove_file 'config/deploy.rb'
|
90
|
+
end
|
91
|
+
remove_file 'config/initializers/sequel.rb' if mongodb?
|
92
|
+
end
|
93
|
+
|
94
|
+
# === db/ directory ===
|
95
|
+
def create_db_dir
|
96
|
+
return if mongodb?
|
97
|
+
directory 'db'
|
74
98
|
end
|
75
99
|
|
76
100
|
# === lib/ directory ===
|
77
101
|
def create_lib_dir
|
78
|
-
|
102
|
+
directory 'lib'
|
103
|
+
remove_file 'lib/tasks/db.rake' if mongodb?
|
79
104
|
end
|
80
105
|
|
81
106
|
# === log/ directory ===
|
@@ -84,14 +109,6 @@ module Telegrator
|
|
84
109
|
create_file 'log/.keep'
|
85
110
|
end
|
86
111
|
|
87
|
-
def create_gemfile
|
88
|
-
template 'Gemfile.tt'
|
89
|
-
end
|
90
|
-
|
91
|
-
def create_rakefile
|
92
|
-
template 'Rakefile.tt'
|
93
|
-
end
|
94
|
-
|
95
112
|
private
|
96
113
|
|
97
114
|
DATABASES.each do |db|
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# Load DSL and set up stages
|
2
|
+
require 'capistrano/setup'
|
3
|
+
|
4
|
+
# Include default deployment tasks
|
5
|
+
require 'capistrano/deploy'
|
6
|
+
require 'capistrano/scm/git'
|
7
|
+
install_plugin Capistrano::SCM::Git
|
8
|
+
|
9
|
+
# Include tasks from other gems included in your Gemfile
|
10
|
+
require 'capistrano/rbenv'
|
11
|
+
require 'capistrano/bundler'
|
12
|
+
require 'capistrano/sidekiq'
|
13
|
+
|
14
|
+
require 'capistrano/puma'
|
15
|
+
install_plugin Capistrano::Puma
|
16
|
+
|
17
|
+
# Load custom tasks from `lib/capistrano/tasks` if you have any defined
|
18
|
+
Dir.glob('lib/capistrano/tasks/*.rake').each { |r| import r }
|
File without changes
|
@@ -0,0 +1,21 @@
|
|
1
|
+
lock '3.8.1'
|
2
|
+
|
3
|
+
require 'dotenv'
|
4
|
+
Dotenv.load
|
5
|
+
|
6
|
+
set :application, '<%= name %>'
|
7
|
+
set :deploy_to, "/home/deployer/#{fetch(:application)}"
|
8
|
+
set :repo_url, `git remote get-url origin`.chomp
|
9
|
+
ask :branch, `git rev-parse --abbrev-ref HEAD`.chomp
|
10
|
+
|
11
|
+
set :cap_host, ENV['CAP_HOST']
|
12
|
+
set :cap_user, ENV['CAP_USER']
|
13
|
+
|
14
|
+
set :linked_files, %w(.env)
|
15
|
+
set :linked_dirs, %w(log tmp/pids tmp/sockets)
|
16
|
+
|
17
|
+
set :default_env, path: '~/.rbenv/shims:~/.rbenv/bin:$PATH'
|
18
|
+
|
19
|
+
set :sidekiq_pid, File.join(shared_path, 'tmp', 'pids', 'sidekiq.pid')
|
20
|
+
set :sidekiq_config, File.join(current_path, 'config', 'sidekiq.yml')
|
21
|
+
set :sidekiq_require, File.join(current_path, 'app', 'boot.rb')
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# server-based syntax
|
2
|
+
# ======================
|
3
|
+
# Defines a single server with a list of roles and multiple properties.
|
4
|
+
# You can define all roles on a single server, or split them:
|
5
|
+
|
6
|
+
# server "example.com", user: "deploy", roles: %w{app db web}, my_property: :my_value
|
7
|
+
# server "example.com", user: "deploy", roles: %w{app web}, other_property: :other_value
|
8
|
+
# server "db.example.com", user: "deploy", roles: %w{db}
|
9
|
+
|
10
|
+
|
11
|
+
|
12
|
+
# role-based syntax
|
13
|
+
# ==================
|
14
|
+
|
15
|
+
# Defines a role with one or multiple servers. The primary server in each
|
16
|
+
# group is considered to be the first unless any hosts have the primary
|
17
|
+
# property set. Specify the username and a domain or IP for the server.
|
18
|
+
# Don't use `:all`, it's a meta role.
|
19
|
+
|
20
|
+
# role :app, %w{deploy@example.com}, my_property: :my_value
|
21
|
+
# role :web, %w{user1@primary.com user2@additional.com}, other_property: :other_value
|
22
|
+
# role :db, %w{deploy@example.com}
|
23
|
+
|
24
|
+
|
25
|
+
|
26
|
+
# Configuration
|
27
|
+
# =============
|
28
|
+
# You can set any configuration variable like in config/deploy.rb
|
29
|
+
# These variables are then only loaded and set in this stage.
|
30
|
+
# For available Capistrano configuration variables see the documentation page.
|
31
|
+
# http://capistranorb.com/documentation/getting-started/configuration/
|
32
|
+
# Feel free to add new variables to customise your setup.
|
33
|
+
|
34
|
+
|
35
|
+
|
36
|
+
# Custom SSH Options
|
37
|
+
# ==================
|
38
|
+
# You may pass any option but keep in mind that net/ssh understands a
|
39
|
+
# limited set of options, consult the Net::SSH documentation.
|
40
|
+
# http://net-ssh.github.io/net-ssh/classes/Net/SSH.html#method-c-start
|
41
|
+
#
|
42
|
+
# Global options
|
43
|
+
# --------------
|
44
|
+
# set :ssh_options, {
|
45
|
+
# keys: %w(/home/rlisowski/.ssh/id_rsa),
|
46
|
+
# forward_agent: false,
|
47
|
+
# auth_methods: %w(password)
|
48
|
+
# }
|
49
|
+
#
|
50
|
+
# The server-based syntax can be used to override options:
|
51
|
+
# ------------------------------------
|
52
|
+
# server "example.com",
|
53
|
+
# user: "user_name",
|
54
|
+
# roles: %w{web app},
|
55
|
+
# ssh_options: {
|
56
|
+
# user: "user_name", # overrides user setting above
|
57
|
+
# keys: %w(/home/user_name/.ssh/id_rsa),
|
58
|
+
# forward_agent: false,
|
59
|
+
# auth_methods: %w(publickey password)
|
60
|
+
# # password: "please use keys"
|
61
|
+
# }
|
@@ -0,0 +1 @@
|
|
1
|
+
server fetch(:cap_host), user: fetch(:cap_user), roles: %w(app db web)
|
@@ -0,0 +1,16 @@
|
|
1
|
+
Sequel.migration do
|
2
|
+
change do
|
3
|
+
create_table :users do
|
4
|
+
primary_key :id
|
5
|
+
|
6
|
+
Integer :telegram_id, null: false, unique: true
|
7
|
+
String :first_name, size: 255
|
8
|
+
String :last_name, size: 255
|
9
|
+
String :username, size: 255
|
10
|
+
TrueClass :active, null: false, default: true
|
11
|
+
|
12
|
+
DateTime :created_at, null: false
|
13
|
+
DateTime :updated_at
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
BOT_TOKEN=YYYYYYYYY:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
2
|
+
|
3
|
+
<%-
|
4
|
+
db_connection_string =
|
5
|
+
if postgresql?
|
6
|
+
'postgresql://[username[:password]@][host][:port][/database][?options]'
|
7
|
+
elsif mongodb?
|
8
|
+
'mongodb://[username:password@]host[:port][/database][?options]'
|
9
|
+
end
|
10
|
+
-%>
|
11
|
+
DATABASE_URL=<%= db_connection_string %>
|
12
|
+
|
13
|
+
CAP_HOST=xxx.xxx.xxx.xxx
|
14
|
+
CAP_USER=deployer
|
@@ -0,0 +1,29 @@
|
|
1
|
+
namespace :bot do
|
2
|
+
desc 'Set up webhook'
|
3
|
+
task :set_webhook, [:url, :cert] do |_, args|
|
4
|
+
options = {}
|
5
|
+
options[:url] = args[:url]
|
6
|
+
unless args[:cert].nil?
|
7
|
+
cert = Faraday::UploadIO.new(args[:cert], 'application/x-pem-file')
|
8
|
+
options[:certificate] = cert
|
9
|
+
end
|
10
|
+
|
11
|
+
puts BOT.api.set_webhook(options)
|
12
|
+
end
|
13
|
+
|
14
|
+
desc 'Delete webhook'
|
15
|
+
task :delete_webhook do
|
16
|
+
puts BOT.api.delete_webhook
|
17
|
+
end
|
18
|
+
|
19
|
+
desc 'Create self-signed certificate'
|
20
|
+
task :create_cert, :host do |_, args|
|
21
|
+
dir = ROOT.join('ssl')
|
22
|
+
Dir.mkdir(dir) unless Dir.exist?(dir)
|
23
|
+
|
24
|
+
sh <<-COMMAND
|
25
|
+
openssl req -x509 -nodes -days 365 -newkey rsa:2048 -sha256 -keyout ssl/bot.key -out ssl/bot.pem \
|
26
|
+
-subj '/C=RU/CN=#{args[:host]}'
|
27
|
+
COMMAND
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
namespace :db do
|
2
|
+
desc 'Show current schema version'
|
3
|
+
task :version do
|
4
|
+
version =
|
5
|
+
if DB.table_exists?(:schema_info)
|
6
|
+
DB.from(:schema_info).get(:version)
|
7
|
+
else
|
8
|
+
0
|
9
|
+
end
|
10
|
+
puts "Schema Version: #{version}"
|
11
|
+
end
|
12
|
+
|
13
|
+
desc 'Migrate the database'
|
14
|
+
task :migrate do
|
15
|
+
Sequel.extension(:migration)
|
16
|
+
Sequel::Migrator.run(DB, ROOT.join('db', 'migrate'))
|
17
|
+
end
|
18
|
+
|
19
|
+
desc 'Rollback to specified target'
|
20
|
+
task :rollback, :target do |_, args|
|
21
|
+
Sequel.extension(:migration)
|
22
|
+
Sequel::Migrator.run(DB, ROOT.join('db', 'migrate'), target: args[:target].to_i)
|
23
|
+
Rake::Task['db:version'].execute
|
24
|
+
end
|
25
|
+
|
26
|
+
desc 'Seed db with initial data'
|
27
|
+
task :seed do
|
28
|
+
Sequel.extension(:seed)
|
29
|
+
Sequel::Seeder.apply(DB, ROOT.join('db', 'seeds'))
|
30
|
+
end
|
31
|
+
end
|
data/lib/telegrator/version.rb
CHANGED
data/telegrator.gemspec
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# coding: utf-8
|
2
|
+
|
2
3
|
lib = File.expand_path('../lib', __FILE__)
|
3
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
5
|
require 'telegrator/version'
|
@@ -26,4 +27,5 @@ Gem::Specification.new do |spec|
|
|
26
27
|
spec.add_development_dependency 'bundler', '~> 1.14'
|
27
28
|
spec.add_development_dependency 'rake', '~> 12.0'
|
28
29
|
spec.add_development_dependency 'minitest', '~> 5.0'
|
30
|
+
spec.add_development_dependency 'rubocop', '~> 0.48.1'
|
29
31
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: telegrator
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.0
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Aleksey Ivanov
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-05-
|
11
|
+
date: 2017-05-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: thor
|
@@ -66,6 +66,20 @@ dependencies:
|
|
66
66
|
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '5.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rubocop
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 0.48.1
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 0.48.1
|
69
83
|
description: A generator to set up new Telegram bot easily
|
70
84
|
email:
|
71
85
|
- ialexxei@gmail.com
|
@@ -75,6 +89,8 @@ extensions: []
|
|
75
89
|
extra_rdoc_files: []
|
76
90
|
files:
|
77
91
|
- ".gitignore"
|
92
|
+
- ".rubocop.yml"
|
93
|
+
- ".rubocop_todo.yml"
|
78
94
|
- ".travis.yml"
|
79
95
|
- Gemfile
|
80
96
|
- LICENSE.txt
|
@@ -88,6 +104,7 @@ files:
|
|
88
104
|
- lib/telegrator/generators.rb
|
89
105
|
- lib/telegrator/generators/base.rb
|
90
106
|
- lib/telegrator/generators/bot/generator.rb
|
107
|
+
- lib/telegrator/generators/bot/templates/Capfile.tt
|
91
108
|
- lib/telegrator/generators/bot/templates/Gemfile.tt
|
92
109
|
- lib/telegrator/generators/bot/templates/Rakefile.tt
|
93
110
|
- lib/telegrator/generators/bot/templates/app/commands.rb.tt
|
@@ -103,11 +120,21 @@ files:
|
|
103
120
|
- lib/telegrator/generators/bot/templates/app/workers.rb.tt
|
104
121
|
- lib/telegrator/generators/bot/templates/app/workers/base.rb.tt
|
105
122
|
- lib/telegrator/generators/bot/templates/app/workers/processor.rb.tt
|
106
|
-
- lib/telegrator/generators/bot/templates/bin/bot
|
123
|
+
- lib/telegrator/generators/bot/templates/bin/bot
|
107
124
|
- lib/telegrator/generators/bot/templates/config/boot.rb.tt
|
125
|
+
- lib/telegrator/generators/bot/templates/config/deploy.rb.tt
|
126
|
+
- lib/telegrator/generators/bot/templates/config/deploy/production.rb.tt
|
127
|
+
- lib/telegrator/generators/bot/templates/config/deploy/staging.rb.tt
|
108
128
|
- lib/telegrator/generators/bot/templates/config/initializers/01_env.rb.tt
|
109
129
|
- lib/telegrator/generators/bot/templates/config/initializers/02_root.rb.tt
|
110
130
|
- lib/telegrator/generators/bot/templates/config/initializers/bot.rb.tt
|
131
|
+
- lib/telegrator/generators/bot/templates/config/initializers/sequel.rb.tt
|
132
|
+
- lib/telegrator/generators/bot/templates/db/migrate/001_create_users.rb.tt
|
133
|
+
- lib/telegrator/generators/bot/templates/env.tt
|
134
|
+
- lib/telegrator/generators/bot/templates/gitignore.tt
|
135
|
+
- lib/telegrator/generators/bot/templates/lib/capistrano/tasks/bot.rake.tt
|
136
|
+
- lib/telegrator/generators/bot/templates/lib/tasks/bot.rake.tt
|
137
|
+
- lib/telegrator/generators/bot/templates/lib/tasks/db.rake.tt
|
111
138
|
- lib/telegrator/version.rb
|
112
139
|
- telegrator.gemspec
|
113
140
|
homepage: https://github.com/ivanovaleksey/telegrator
|
@@ -125,12 +152,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
125
152
|
version: '0'
|
126
153
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
127
154
|
requirements:
|
128
|
-
- - "
|
155
|
+
- - ">="
|
129
156
|
- !ruby/object:Gem::Version
|
130
|
-
version:
|
157
|
+
version: '0'
|
131
158
|
requirements: []
|
132
159
|
rubyforge_project:
|
133
|
-
rubygems_version: 2.6.
|
160
|
+
rubygems_version: 2.6.12
|
134
161
|
signing_key:
|
135
162
|
specification_version: 4
|
136
163
|
summary: A generator to set up new Telegram bot easily
|