rise-cli 0.2.5 → 0.2.6

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/bin/rise CHANGED
@@ -1,102 +1,101 @@
1
- #!/usr/bin/env ruby
2
- $LOAD_PATH.unshift(File.expand_path(File.join('..', 'lib'))) unless $LOAD_PATH.include?(File.expand_path(File.join('..', 'lib')))
3
-
4
- require 'core'
5
-
6
- options = {}
7
- options[:open] = false
8
- options[:ignore_files] = nil
9
- # TODO: convert the tasks to use thor or Rex::Parser::Arguments from 'rex'
10
- OptionParser.new do |opts|
11
- opts.banner = "Usage: #{$PROGRAM_NAME} [options]\nRise version: #{Rise::Constants::VERSION}"
12
- opts.separator Paint["\nGeneral Options: ", '#95a5a6']
13
-
14
- opts.on('-d DIR', '--dir DIR', String, 'Upload files in DIR (Defaults to the current directory)') do |d|
15
- options[:directory] = d unless d.nil?
16
- end
17
-
18
- opts.on('-v', '--version', 'Show the rise version and exit') do
19
- puts "Rise version: #{Paint[Rise::Constants::VERSION, '#2ecc71']}"
20
- exit 0
21
- end
22
-
23
- opts.on('--verbose', 'Run verbosely') do |v|
24
- ENV['RISE_VERBOSE'] = 'yes'
25
- end
26
-
27
- opts.on('-i', '--ignore FILES', Array, 'Ignore the given files in the upload. These will be ignored if there is a .riseignore file.') do |a|
28
- options[:ignored_files] = a unless a.nil?
29
- puts "Reminder: You can add the files to .riseignore instead of using the -i flag"
30
- end
31
-
32
- opts.on('-o', '--open', 'Open the deployment in a browser if possible') do
33
- options[:open] = true
34
- end
35
-
36
- opts.on('-u', '--update', 'Check if rise has a newer version and install it') do
37
- Rise::Util.check_for_update!
38
- exit 0
39
- end
40
-
41
- opts.on('-h', '--help', 'Show this help message') do
42
- puts opts
43
- puts Rise::Text::TASKS_HELP
44
- exit
45
- end
46
-
47
- opts.separator Paint["\nTasks: ", '#95a5a6']
48
-
49
- end.parse!(ARGV)
50
-
51
- # Ladies and gentlemen, this is what
52
- # happens when optparse doesn't have
53
- # good subcommand/task syntax support
54
- while opt = ARGV.shift do
55
- case opt
56
- when 'init'
57
- Rise::Util.setup(false)
58
- exit 0
59
- when 'update'
60
- Rise::Util.check_for_update!
61
- exit 0
62
- end
63
- end
64
-
65
-
66
- if Rise::Util.first_run?
67
- Rise::Util.setup
68
- exit 0
69
- end
70
- Rise::Util.check_for_update!
71
- dir = options[:directory] || Dir.pwd
72
- ignored = nil
73
- result_url = ''
74
-
75
- begin
76
- ignored = File.read(File.join(dir, '.riseignore')).split("\n").map { |a| a.gsub!(' ', '')}
77
- rescue Errno::ENOENT
78
- ignored = options[:ignored_files]
79
- end
80
-
81
- uploader = Rise::Transport::Uploader.new(dir, ignored)
82
-
83
- if uploader.total_files_size > 52428800
84
- puts Paint["Max file size reached (#{uploader.total_files_size} > 50MB)", '#FF0000']
85
- exit 0
86
- end
87
-
88
- puts Paint['Thanks for using Rise! Your local source for serverless deployment!', '#95a5a6']
89
-
90
- Whirly.start(spinner: 'dots', status: "Uploading files (#{uploader.total_files} total files)") do
91
- beginning_time = Time.now
92
- result_url = uploader.upload!(options[:verbose]) # Do the file upload
93
-
94
- Whirly.status = "Done!\n"
95
- Clipboard.copy(result_url)
96
- print Paint["Your url is: #{result_url} (copied to clipboard) ", :bold]
97
- puts Paint["[#{((Time.now - beginning_time)).round(2)}s]", '#95a5a6']
98
-
99
- puts Paint['Deployment successful!', '#3498db']
100
-
101
- Rise::Util.open_deployment_in_browser(result_url) if options[:open]
102
- end
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'core'
4
+
5
+ options = {}
6
+ options[:open] = false
7
+ options[:ignore_files] = nil
8
+ # TODO: convert the tasks to use thor or Rex::Parser::Arguments from 'rex'
9
+ OptionParser.new do |opts|
10
+ opts.banner = "\nUsage: #{File.basename($PROGRAM_NAME)} [options] [task]\nRise version: #{Rise::Constants::VERSION}"
11
+ opts.separator Paint["\nGlobal Options: ", '#95a5a6']
12
+
13
+ opts.on('-d DIR', '--dir DIR', String, 'Upload files in DIR (Defaults to the current directory)') do |d|
14
+ options[:directory] = d unless d.nil?
15
+ end
16
+
17
+ opts.on('-v', '--version', 'Show the rise version and exit') do
18
+ puts "Rise version: #{Paint[Rise::Constants::VERSION, '#2ecc71']}"
19
+ exit 0
20
+ end
21
+
22
+ opts.on('--verbose', 'Run verbosely') do
23
+ ENV['RISE_VERBOSE'] = 'yes'
24
+ end
25
+
26
+ opts.on('-i', '--ignore FILES', Array, 'Ignore the given files in the upload. These will be ignored if there is a .riseignore file.') do |a|
27
+ options[:ignored_files] = a unless a.nil?
28
+ puts "Reminder: You can add the files to .riseignore instead of using the -i flag"
29
+ end
30
+
31
+ opts.on('-o', '--open', 'Open the deployment in a browser if possible') do
32
+ options[:open] = true
33
+ end
34
+
35
+ opts.on('-u', '--update', 'Check if rise has a newer version and install it') do
36
+ Rise::Util.check_for_update!
37
+ exit 0
38
+ end
39
+
40
+ opts.on('-h', '--help', 'Show this help message') do
41
+ puts opts
42
+ puts Rise::Text::TASKS_HELP
43
+ exit
44
+ end
45
+
46
+ opts.separator Paint["\nTasks: ", '#95a5a6']
47
+
48
+ end.parse!(ARGV)
49
+
50
+ # Ladies and gentlemen, this is what
51
+ # happens when optparse doesn't have
52
+ # good subcommand/task syntax support
53
+ while (opt = ARGV.shift) do
54
+ case opt
55
+ when 'init'
56
+ Rise::Util.setup(false)
57
+ exit 0
58
+ when 'update'
59
+ Rise::Util.check_for_update!
60
+ exit 0
61
+ end
62
+ end
63
+
64
+
65
+ if Rise::Util.first_run?
66
+ Rise::Util.setup
67
+ exit 0
68
+ end
69
+ Rise::Util.check_for_update!
70
+ dir = options[:directory] || Dir.pwd
71
+ ignored = nil
72
+ result_url = ''
73
+
74
+ begin
75
+ ignored = File.read(File.join(dir, '.riseignore')).split("\n").map { |a| a.gsub!(' ', '')}
76
+ rescue Errno::ENOENT
77
+ ignored = options[:ignored_files]
78
+ end
79
+
80
+ uploader = Rise::Transport::Uploader.new(dir, ignored)
81
+
82
+ if uploader.total_files_size > 52428800
83
+ puts Paint["Max file size reached (#{uploader.total_files_size} > 50MB)", '#FF0000']
84
+ exit 0
85
+ end
86
+
87
+ puts Paint['Thanks for using Rise! Your local source for serverless deployment!', '#95a5a6']
88
+
89
+ Whirly.start(spinner: 'dots', status: "Uploading files (#{uploader.total_files} total files)") do
90
+ beginning_time = Time.now
91
+ result_url = uploader.upload!(options[:verbose]) # Do the file upload
92
+
93
+ Whirly.status = "Done!\n"
94
+ Clipboard.copy(result_url)
95
+ print Paint["Your url is: #{result_url} (copied to clipboard) ", :bold]
96
+ puts Paint["[#{((Time.now - beginning_time)).round(2)}s]", '#95a5a6']
97
+
98
+ puts Paint['Deployment successful!', '#3498db']
99
+
100
+ Rise::Util.open_deployment_in_browser(result_url) if options[:open]
101
+ end
data/bin/setup CHANGED
File without changes
@@ -1,17 +1,17 @@
1
- RISE_DATA_DIR = File.join(Dir.home, '.rise')
2
- DOMAIN = 'rise.sh'.freeze
3
- AUTH_PORT = 4567
4
- UPLOAD_PORT = 8080
5
-
6
- module Rise
7
- #
8
- # Holds constants used throughout the framework
9
- #
10
- module Constants
11
- VERSION = '0.2.5'.freeze
12
- EMAIL = '0xCB@protonmail.com'.freeze
13
- AUTHORS = ['Carter Brainerd']
14
- NAME = 'rise-cli'.freeze
15
- RISE_DIR = File.join(File.dirname(__FILE__), '..', '..')
16
- end
17
- end
1
+ RISE_DATA_DIR = File.join(Dir.home, '.rise')
2
+ DOMAIN = 'rise.sh'.freeze
3
+ AUTH_PORT = 4567
4
+ UPLOAD_PORT = 8080
5
+
6
+ module Rise
7
+ #
8
+ # Holds constants used throughout the framework
9
+ #
10
+ module Constants
11
+ VERSION = '0.2.6'.freeze
12
+ EMAIL = '0xCB@protonmail.com'.freeze
13
+ AUTHORS = ['Carter Brainerd']
14
+ NAME = 'rise-cli'.freeze
15
+ RISE_DIR = File.join(File.dirname(__FILE__), '..', '..')
16
+ end
17
+ end
data/lib/core/text.rb CHANGED
@@ -1,24 +1,23 @@
1
- require 'paint'
2
- #
3
- # Text and printing utility methods
4
- #
5
- module Rise
6
- module Text
7
-
8
- TASKS_HELP =
9
- %Q{ init Reinitialize your password hash. (You will lose you old hash FOREVER)
10
- update Updates the current rise-cli installation (aliased by -u)
11
-
12
- Examples:
13
- #{Paint['$ rise init -v', '#2ecc71']} Reinitializes your password with verbose output
14
- #{Paint['$ rise -d ../my-project -o', '#2ecc71']} Will upload all files in `../my-project` and open it in a browser
15
- }
16
-
17
- #
18
- # Prints +msg+ if the +RISE_VERBOSE+ environment variable is true (set with --verbose)
19
- #
20
- def self.vputs(msg='')
21
- puts msg if ENV['RISE_VERBOSE'] == 'yes'
22
- end
23
- end
24
- end
1
+ require 'paint'
2
+ #
3
+ # Text and printing utility methods
4
+ #
5
+ module Rise
6
+ module Text
7
+ TASKS_HELP =
8
+ %Q{ init Reinitialize your password hash. (You will lose you old hash FOREVER)
9
+ update Updates the current rise-cli installation (aliased by -u)
10
+
11
+ #{Paint['Examples:', '#95a5a6']}
12
+ #{Paint['$ rise init -v', '#2ecc71']} Reinitializes your password with verbose output
13
+ #{Paint['$ rise -d ../my-project -o', '#2ecc71']} Will upload all files in `../my-project` and open it in a browser
14
+ }
15
+
16
+ #
17
+ # Prints +msg+ if the +RISE_VERBOSE+ environment variable is set to 'yes' (set with --verbose)
18
+ #
19
+ def self.vputs(msg='')
20
+ puts msg if ENV['RISE_VERBOSE'] == 'yes'
21
+ end
22
+ end
23
+ end
@@ -1,65 +1,65 @@
1
- require 'rex/text'
2
- require 'uri'
3
- require 'json'
4
- require 'http'
5
-
6
- module Rise
7
- #
8
- # Handles all communication with the rise upload server
9
- #
10
- module Transport
11
- # Handles uploading files
12
- class Uploader
13
- attr_reader :folder_path, :total_files, :include_folder
14
- attr_reader :uuid, :current_file, :total_files_size
15
- attr_accessor :files
16
-
17
- def initialize(folder_path, excluded_files = [], include_folder = true)
18
- excluded_files.map! do |a|
19
- File.join(File.absolute_path(folder_path), a)
20
- end unless excluded_files.nil?
21
- @folder_path = folder_path
22
- @files = Dir.glob("#{File.absolute_path(folder_path)}/**/*")
23
- @files -= excluded_files unless excluded_files.nil?
24
- @total_files = @files.length
25
- @total_files_size = calculate_files_size
26
- @include_folder = include_folder
27
- @uuid = "#{File.basename(File.absolute_path(folder_path)).gsub('_', '-')}-#{Rex::Text.rand_text_alphanumeric(8)}" # Structure: foldername-8RNDLTRS
28
- end
29
-
30
- #
31
- # Uploads the files from +folder_path+ to the upload server
32
- # @return String the final URL of the uploaded contents
33
- #
34
- def upload!(*)
35
- upload_uri_base = "http://rise.sh:8080/api/v1/#{@uuid}"
36
- access_uri = "https://rise.sh/#{@uuid}"
37
- uri = ''
38
-
39
- # This sorts the files by (file path) length.
40
- # It is supposed to make the server make the first layer of files
41
- # before the rest of the layers.
42
- ordered_files = files.sort_by(&:length)
43
- ordered_files.each do |f|
44
- isdir = File.directory?(f)
45
- final_path = File.absolute_path(f).gsub(
46
- File.expand_path(folder_path), '')
47
- uri = URI.parse("#{upload_uri_base}/#{final_path.gsub(' ', '')}?dir=#{isdir}")
48
- begin
49
- HTTP.put(uri.to_s, body: File.read(f))
50
- rescue Errno::EISDIR
51
- HTTP.put(uri.to_s, body: '')
52
- next
53
- end
54
- end
55
- access_uri
56
- end
57
-
58
- protected
59
-
60
- def calculate_files_size
61
- @files.inject(0){|sum, file| sum + File.size(file)}
62
- end
63
- end
64
- end
65
- end
1
+ require 'rex/text'
2
+ require 'uri'
3
+ require 'json'
4
+ require 'http'
5
+
6
+ module Rise
7
+ #
8
+ # Handles all communication with the rise upload server
9
+ #
10
+ module Transport
11
+ # Handles uploading files
12
+ class Uploader
13
+ attr_reader :folder_path, :total_files, :include_folder
14
+ attr_reader :uuid, :current_file, :total_files_size
15
+ attr_accessor :files
16
+
17
+ def initialize(folder_path, excluded_files = [], include_folder = true)
18
+ excluded_files.map! do |a|
19
+ File.join(File.absolute_path(folder_path), a)
20
+ end unless excluded_files.nil?
21
+ @folder_path = folder_path
22
+ @files = Dir.glob("#{File.absolute_path(folder_path)}/**/*")
23
+ @files -= excluded_files unless excluded_files.nil?
24
+ @total_files = @files.length
25
+ @total_files_size = calculate_files_size
26
+ @include_folder = include_folder
27
+ @uuid = "#{File.basename(File.absolute_path(folder_path)).gsub('_', '-')}-#{Rex::Text.rand_text_alphanumeric(8)}" # Structure: foldername-8RNDLTRS
28
+ end
29
+
30
+ #
31
+ # Uploads the files from +folder_path+ to the upload server
32
+ # @return String the final URL of the uploaded contents
33
+ #
34
+ def upload!(*)
35
+ upload_uri_base = "http://rise.sh:8080/api/v1/#{@uuid}"
36
+ access_uri = "https://rise.sh/#{@uuid}"
37
+ uri = ''
38
+
39
+ # This sorts the files by (file path) length.
40
+ # It is supposed to make the server make the first layer of files
41
+ # before the rest of the layers.
42
+ ordered_files = files.sort_by(&:length)
43
+ ordered_files.each do |f|
44
+ isdir = File.directory?(f)
45
+ final_path = File.absolute_path(f).gsub(
46
+ File.expand_path(folder_path), '')
47
+ uri = URI.parse("#{upload_uri_base}/#{final_path.gsub(' ', '')}?dir=#{isdir}")
48
+ begin
49
+ HTTP.put(uri.to_s, body: File.read(f))
50
+ rescue Errno::EISDIR
51
+ HTTP.put(uri.to_s, body: '')
52
+ next
53
+ end
54
+ end
55
+ access_uri
56
+ end
57
+
58
+ protected
59
+
60
+ def calculate_files_size
61
+ @files.inject(0){|sum, file| sum + File.size(file)}
62
+ end
63
+ end
64
+ end
65
+ end
data/lib/core/util.rb CHANGED
@@ -1,112 +1,115 @@
1
- require 'fileutils'
2
- require 'paint'
3
- require 'json'
4
- require 'http'
5
- require 'bcrypt'
6
- require 'io/console'
7
- require 'whirly'
8
- require 'os'
9
- require 'json'
10
- require_relative 'constants'
11
-
12
- module Rise
13
- #
14
- # Utility methods
15
- #
16
- module Util
17
- #
18
- # Checks if rise is being run for the first time
19
- #
20
- def self.first_run?
21
- !File.directory?(File.join(Dir.home, '.rise'))
22
- end
23
-
24
- #
25
- # 1 = git, 2 = gem, 3 = unknown
26
- #
27
- def self.git_or_gem
28
- gem = nil
29
- 1 if File.exist?(File.join(Rise::Constants::VERSION, '.git'))
30
- if OS.windows?
31
- gem = system('which gem > NUL')
32
- else
33
- gem = system('which gem > /dev/null')
34
- end
35
-
36
- 2 if gem == true
37
- 3
38
- end
39
-
40
- #
41
- # Check for a new version of the gem
42
- #
43
- def self.check_for_update!
44
- src = Rise::Util.git_or_gem
45
- begin
46
- if src == 2 # if the gem was downloaded from rubygems
47
- current_version = JSON.parse(HTTP.get('https://rubygems.org/api/v1/versions/rise-cli/latest.json'))['version']
48
- if current_version != Rise::Constants::VERSION
49
- Whirly.start(
50
- spinner: 'line',
51
- status: "New version available (#{Paint[Rise::Constants::VERSION, 'red']} -> #{Paint[current_version, '#3498db']}), updating..."
52
- ) do
53
- system("gem install rise-cli")
54
- puts Paint["Update complete, just run #{Paint['`rise`', '#3498db']} to deploy"]
55
- end
56
- end
57
- elsif src == 1
58
- puts "It seems you're on bleeding edge, fetching new changes..."
59
- Rise::Text.vputs("Updating from #{`git show --no-color --oneline -s`.split(' ')[0]} to #{`git rev-parse --short HEAD`}")
60
- `git pull`
61
- puts Paint["Update complete, just run #{Paint['`rise`', '#3498db']} to deploy"]
62
- end
63
- rescue StandardError => e
64
- puts "Unable to check for updates. Error: #{Paint[e.message, 'red']}"
65
- exit 1
66
- end
67
- end
68
-
69
- #
70
- # Creates all of the necessary files and login information
71
- #
72
- def self.setup(first = true)
73
- if first
74
- puts Paint['Detected first time setup, creating necessary files...', :blue]
75
- FileUtils.mkdir(RISE_DATA_DIR)
76
- FileUtils.mkdir(File.join(RISE_DATA_DIR, 'auth'))
77
- end
78
-
79
- puts Paint['Create a password to secure your uploads.', :bold]
80
- pw = Rise::Util.signup
81
- while true
82
- break if pw.length > 8
83
- puts Paint['Password not long enough,
84
- it has to be longer than 8 characters', :red]
85
- pw = Rise::Util.signup
86
- end
87
- File.open(File.join(RISE_DATA_DIR, 'auth', 'creds.json'), 'w') do |f|
88
- Rise::Text.vputs('Writing hash to creds.json...')
89
- creds_hash = { 'hash' => BCrypt::Password.create(pw) }
90
- f.puts(JSON.pretty_generate(creds_hash))
91
- end
92
- puts "\nAll done!\nPlease run the `rise` command again to upload your files."
93
- end
94
-
95
- def self.signup
96
- print 'Password: '
97
- STDIN.noecho(&:gets).chomp
98
- end
99
-
100
- #
101
- # Opens +url+ in a web browser if possible
102
- #
103
- def self.open_deployment_in_browser(url)
104
- if OS.windows?
105
- system("START \"\" \"#{url}\"")
106
- else
107
- system("open #{url}")
108
- end
109
- end
110
-
111
- end
112
- end
1
+ require 'fileutils'
2
+ require 'paint'
3
+ require 'json'
4
+ require 'http'
5
+ require 'bcrypt'
6
+ require 'io/console'
7
+ require 'whirly'
8
+ require 'os'
9
+ require 'json'
10
+ require_relative 'constants'
11
+
12
+ module Rise
13
+ #
14
+ # Utility methods
15
+ #
16
+ module Util
17
+ #
18
+ # Checks if rise is being run for the first time
19
+ #
20
+ def self.first_run?
21
+ !File.directory?(File.join(Dir.home, '.rise'))
22
+ end
23
+
24
+ #
25
+ # 1 = git, 2 = gem, 3 = unknown
26
+ #
27
+ def self.git_or_gem?
28
+ gem = nil
29
+ return 1 if File.exist?(File.join(Rise::Constants::RISE_DIR, '.git'))
30
+ if OS.windows?
31
+ gem = system('which gem > NUL')
32
+ else
33
+ gem = system('which gem > /dev/null')
34
+ end
35
+ return 2 if gem
36
+ 3
37
+ end
38
+
39
+ #
40
+ # Check for a new version of the gem
41
+ #
42
+ def self.check_for_update!
43
+ src = Rise::Util.git_or_gem?
44
+ Rise::Text.vputs("Source: #{src}")
45
+ begin
46
+ if src == 2 # if the gem was downloaded from rubygems
47
+ current_version = JSON.parse(HTTP.get('https://rubygems.org/api/v1/versions/rise-cli/latest.json'))['version']
48
+
49
+ current_version_i = current_version.gsub('.', '').to_i
50
+ puts "#{current_version_i} #{Rise::Constants::VERSION.gsub('.', '').to_i}"
51
+
52
+ if current_version_i > Rise::Constants::VERSION.gsub('.', '').to_i
53
+ Whirly.start(
54
+ spinner: 'line',
55
+ status: "New version available (#{Paint[Rise::Constants::VERSION, 'red']} -> #{Paint[current_version, '#3498db']}), updating..."
56
+ ) do
57
+ system("gem install rise-cli")
58
+ puts Paint["Update complete, just run #{Paint['`rise`', '#3498db']} to deploy"]
59
+ end
60
+ end
61
+ elsif src == 1
62
+ Rise::Text.vputs("It seems you're on bleeding edge, fetching new changes...")
63
+ Rise::Text.vputs("Updating from #{`git show --no-color --oneline -s`.split(' ')[0]} to #{`git rev-parse --short HEAD`}")
64
+ `git pull`
65
+ puts Paint["Update complete, just run #{Paint['`rise`', '#3498db']} to deploy"]
66
+ end
67
+ rescue StandardError => e
68
+ puts "Unable to check for updates. Error: #{Paint[e.message, 'red']}"
69
+ exit 1
70
+ end
71
+ end
72
+
73
+ #
74
+ # Creates all of the necessary files and login information
75
+ #
76
+ def self.setup(first = true)
77
+ if first
78
+ puts Paint['Detected first time setup, creating necessary files...', :blue]
79
+ FileUtils.mkdir(RISE_DATA_DIR)
80
+ FileUtils.mkdir(File.join(RISE_DATA_DIR, 'auth'))
81
+ end
82
+
83
+ puts Paint['Create a password to secure your uploads.', :bold]
84
+ pw = Rise::Util.signup
85
+ while pw.length < 8
86
+ puts Paint["Password not long enough,
87
+ it has to be longer than 8 characters\n", :red]
88
+ pw = Rise::Util.signup
89
+ end
90
+ File.open(File.join(RISE_DATA_DIR, 'auth', 'creds.json'), 'w') do |f|
91
+ Rise::Text.vputs("\nWriting hash to creds.json...")
92
+ creds_hash = { 'hash' => BCrypt::Password.create(pw) }
93
+ f.puts(JSON.pretty_generate(creds_hash))
94
+ end
95
+ puts "\nAll done!\nRun #{Paint['`rise`', '#3498db']} to deploy"
96
+ end
97
+
98
+ def self.signup
99
+ print 'Password: '
100
+ STDIN.noecho(&:gets).chomp
101
+ end
102
+
103
+ #
104
+ # Opens +url+ in a web browser if possible
105
+ #
106
+ def self.open_deployment_in_browser(url)
107
+ if OS.windows?
108
+ system("START \"\" \"#{url}\"")
109
+ else
110
+ system("open #{url}")
111
+ end
112
+ end
113
+
114
+ end
115
+ end