fronde 0.3.4 → 0.5.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.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/bin/fronde +15 -30
  3. data/lib/ext/nil_time.rb +25 -0
  4. data/lib/ext/r18n.rb +37 -0
  5. data/lib/ext/time.rb +39 -0
  6. data/lib/ext/time_no_time.rb +23 -0
  7. data/lib/fronde/cli/commands.rb +97 -104
  8. data/lib/fronde/cli/data/Rakefile +8 -0
  9. data/lib/fronde/cli/data/config.yml +13 -0
  10. data/lib/fronde/cli/data/gitignore +6 -0
  11. data/lib/fronde/cli/data/zsh_completion +37 -0
  12. data/lib/fronde/cli/helpers.rb +55 -0
  13. data/lib/fronde/cli/opt_parse.rb +140 -0
  14. data/lib/fronde/cli/throbber.rb +110 -0
  15. data/lib/fronde/cli.rb +42 -42
  16. data/lib/fronde/config/data/org-config.el +25 -0
  17. data/lib/fronde/config/data/ox-fronde.el +158 -0
  18. data/lib/fronde/config/data/themes/umaneti/css/htmlize.css +364 -0
  19. data/lib/fronde/config/data/themes/umaneti/css/style.css +250 -0
  20. data/lib/fronde/config/data/themes/umaneti/img/bottom.png +0 -0
  21. data/lib/fronde/config/data/themes/umaneti/img/content.png +0 -0
  22. data/lib/fronde/config/data/themes/umaneti/img/tic.png +0 -0
  23. data/lib/fronde/config/data/themes/umaneti/img/top.png +0 -0
  24. data/lib/fronde/config/helpers.rb +62 -0
  25. data/lib/fronde/config/lisp.rb +80 -0
  26. data/lib/fronde/config.rb +148 -98
  27. data/lib/fronde/emacs.rb +23 -20
  28. data/lib/fronde/index/atom_generator.rb +55 -66
  29. data/lib/fronde/index/data/all_tags.org +19 -0
  30. data/lib/fronde/index/data/template.org +26 -0
  31. data/lib/fronde/index/data/template.xml +37 -0
  32. data/lib/fronde/index/org_generator.rb +72 -88
  33. data/lib/fronde/index.rb +57 -86
  34. data/lib/fronde/org/file.rb +299 -0
  35. data/lib/fronde/org/file_extracter.rb +101 -0
  36. data/lib/fronde/org.rb +105 -0
  37. data/lib/fronde/preview.rb +43 -39
  38. data/lib/fronde/slug.rb +54 -0
  39. data/lib/fronde/source/gemini.rb +34 -0
  40. data/lib/fronde/source/html.rb +67 -0
  41. data/lib/fronde/source.rb +209 -0
  42. data/lib/fronde/sync/neocities.rb +220 -0
  43. data/lib/fronde/sync/rsync.rb +46 -0
  44. data/lib/fronde/sync.rb +32 -0
  45. data/lib/fronde/templater.rb +101 -71
  46. data/lib/fronde/version.rb +1 -1
  47. data/lib/tasks/cli.rake +33 -0
  48. data/lib/tasks/org.rake +58 -43
  49. data/lib/tasks/site.rake +66 -31
  50. data/lib/tasks/sync.rake +37 -40
  51. data/lib/tasks/tags.rake +11 -7
  52. data/locales/en.yml +61 -14
  53. data/locales/fr.yml +69 -14
  54. metadata +77 -95
  55. data/lib/fronde/config/lisp_config.rb +0 -340
  56. data/lib/fronde/config/org-config.el +0 -19
  57. data/lib/fronde/config/ox-fronde.el +0 -121
  58. data/lib/fronde/org_file/class_methods.rb +0 -72
  59. data/lib/fronde/org_file/extracter.rb +0 -72
  60. data/lib/fronde/org_file/htmlizer.rb +0 -43
  61. data/lib/fronde/org_file.rb +0 -298
  62. data/lib/fronde/utils.rb +0 -229
@@ -1,298 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'time'
4
- require 'fileutils'
5
- # fronde/config is required by htmlizer
6
- require 'fronde/org_file/htmlizer'
7
- require 'fronde/org_file/extracter'
8
- require 'fronde/org_file/class_methods'
9
- require 'fronde/index'
10
- require 'fronde/version'
11
-
12
- module Fronde
13
- # Handles org files.
14
- #
15
- # This class is responsible for reading or writing existing or new org
16
- # files, and formating their content to be used on the generated
17
- # website.
18
- class OrgFile # rubocop:disable Metrics/ClassLength
19
- # @return [String] the title of the current org document, taken from
20
- # the ~#+title:~ header.
21
- attr_reader :title
22
-
23
- # @return [String] the subtitle of the current org document, taken
24
- # from the ~#+subtitle:~ header.
25
- attr_reader :subtitle
26
-
27
- # @return [DateTime] the date and time of the current org document,
28
- # taken from the ~#+date:~ header.
29
- attr_reader :date
30
-
31
- # @return [Boolean] wether a time has been extracted from the
32
- # current org document ~#+date:~ header.
33
- attr_reader :notime
34
-
35
- # The author of the current org document, taken from the ~#+author:~
36
- # header.
37
- #
38
- # If the current document doesn't have any authorship information,
39
- # the one from the ~config.yml~ file will be used instead
40
- #
41
- # @return [String] the author name
42
- attr_reader :author
43
-
44
- # @return [Array] the keywords list of the current org document,
45
- # taken from the ~#+keywords:~ header.
46
- attr_reader :keywords
47
-
48
- # @return [String] the description of this org document, taken from
49
- # the ~#+description:~ header.
50
- attr_reader :excerpt
51
-
52
- # The locale of the current org document, taken from the
53
- # ~#+language:~ header.
54
- #
55
- # If the current document doesn't have any language information, the
56
- # one from the ~config.yml~ file will be used instead, or "en" by
57
- # default.
58
- #
59
- # @return [String] the document lang
60
- attr_reader :lang
61
-
62
- # @return [String] the relative path to the source of this document.
63
- attr_reader :file
64
-
65
- # @return [String] the relative path to the generated html file of
66
- # this document.
67
- attr_reader :html_file
68
-
69
- # @return [String] the url of this document, build from the ~domain~
70
- # settings and the above {#html_file @html_file} attribute.
71
- attr_reader :url
72
-
73
- # @return [String] the project owning this document.
74
- attr_reader :project
75
-
76
- extend Fronde::OrgFileClassMethods
77
-
78
- include Fronde::OrgFileExtracter
79
- include Fronde::OrgFileHtmlizer
80
-
81
- # Prepares the file named by ~file_name~ for read and write
82
- # operations.
83
- #
84
- # If the file ~file_name~ does not exist, the new instance may be
85
- # populated by data given in the ~opts~ parameter.
86
- #
87
- # @example
88
- # File.exist? './test.org'
89
- # => true
90
- # o = Fronde::OrgFile.new('./test.org')
91
- # => #<Fronde::OrgFile @file='./test.org'...>
92
- # o.title
93
- # => "This is an existing test file"
94
- # File.exist? '/tmp/does_not_exist.org'
95
- # => false
96
- # o = Fronde::OrgFile.new('/tmp/does_not_exist.org')
97
- # => #<Fronde::OrgFile @file='/tmp/does_not_exist.org'...>
98
- # o.title
99
- # => ""
100
- # File.exist? '/tmp/other.org'
101
- # => false
102
- # o = Fronde::OrgFile.new('/tmp/other.org', title: 'New file')
103
- # => #<Fronde::OrgFile @file='/tmp/other.org'...>
104
- # o.title
105
- # => "New file"
106
- #
107
- # @param file_name [String] path to the corresponding Org file
108
- # @param opts [Hash] optional data to initialize new Org file
109
- # @option opts [String] title ('') the title of the new Org file
110
- # @option opts [String] author (system user or '') the author of the
111
- # document
112
- # @option opts [String] project the project owning this file
113
- # must be stored
114
- # @return [Fronde::OrgFile] the new instance of Fronde::OrgFile
115
- def initialize(file_name, opts = {})
116
- file_name = nil if file_name == ''
117
- @file = file_name
118
- @html_file = nil
119
- @url = nil
120
- @project = opts.delete :project
121
- @options = opts
122
- build_html_file_and_url
123
- if @file && File.exist?(@file)
124
- extract_data
125
- else
126
- init_empty_file
127
- end
128
- end
129
-
130
- # Returns a String representation of the document date, which aims
131
- # to be used to sort several OrgFiles.
132
- #
133
- # The format used for the key is ~%Y%m%d%H%M%S~. If the current
134
- # OrgFile instance does not have a date, this mehod return
135
- # ~00000000000000~. If the current OrgFile instance does not have
136
- # time information, the date is padded with zeros.
137
- #
138
- # @example with the org header ~#+date: <2019-07-03 Wed 20:52:49>~
139
- # org_file.date
140
- # => #<DateTime: 2019-07-03T20:52:49+02:00...>
141
- # org_file.timekey
142
- # => "20190703205349"
143
- #
144
- # @example with the org header ~#+date: <2019-07-03 Wed>~
145
- # org_file.date
146
- # => #<DateTime: 2019-07-03T00:00:00+02:00...>
147
- # org_file.timekey
148
- # => "20190703000000"
149
- #
150
- # @example with no date header in the org file
151
- # org_file.date
152
- # => nil
153
- # org_file.timekey
154
- # => "00000000000000"
155
- #
156
- # @return [String] the document key
157
- def timekey
158
- return '00000000000000' if @date.nil?
159
- @date.strftime('%Y%m%d%H%M%S')
160
- end
161
-
162
- # Returns the current OrgFile instance DateTime as a String.
163
- #
164
- # This method accepts three values for the ~dateformat~ parameter:
165
- #
166
- # - ~:full~ (or ~:long~) :: outputs a complete date and time
167
- # representation, localized through R18n;
168
- # - ~:short~ :: outputs a short date representation (without time),
169
- # localized with R18n;
170
- # - ~:rfc3339~ :: outputs the RFC 3339 date and time representation,
171
- # used in atom feed.
172
- #
173
- # @param dateformat [Symbol] the format to use to convert DateTime
174
- # into String
175
- # @param year [Boolean] wether or not the ~:full~ format must
176
- # contain the year
177
- # @return [String] the document DateTime string representation
178
- def datestring(dateformat = :full, year: true)
179
- return '' if @date.nil?
180
- return R18n.l @date.to_date if dateformat == :short
181
- return @date.rfc3339 if dateformat == :rfc3339
182
- locale = R18n.get.locale
183
- long_fmt = R18n.t.fronde.index.full_date_format(
184
- date: locale.format_date_full(@date, year: year)
185
- )
186
- unless @notime
187
- long_fmt = R18n.t.fronde.index.full_date_with_time_format(
188
- date: long_fmt, time: locale.time_format.delete('_').strip
189
- )
190
- end
191
- locale.strftime(@date, long_fmt)
192
- end
193
-
194
- # Formats given ~string~ with values of the current OrgFile.
195
- #
196
- # This method expects to find percent-tags in the given ~string~ and
197
- # replace them by their corresponding value.
198
- #
199
- # It reuses the same tags than the ~org-html-format-spec~ method.
200
- #
201
- # *** Format:
202
- #
203
- # - %a :: the raw author name
204
- # - %A :: the HTML rendering of the author name, equivalent to
205
- # ~<span class="author">%a</span>~
206
- # - %d :: the ~:short~ date HTML representation, equivalent
207
- # to ~<time datetime="%I">%i</time>~
208
- # - %D :: the ~:full~ date and time HTML representation
209
- # - %i :: the raw ~:short~ date and time
210
- # - %I :: the raw ~:rfc3339~ date and time
211
- # - %k :: the keywords separated by a comma
212
- # - %K :: the HTML list rendering of the keywords
213
- # - %l :: the lang of the document
214
- # - %L :: the license information, taken from the
215
- # {Fronde::Config#settings}
216
- # - %n :: the Fronde name and version
217
- # - %N :: the Fronde name and version with a link to the project
218
- # home on the name
219
- # - %s :: the subtitle of the document
220
- # - %t :: the title of the document
221
- # - %u :: the web path to the related published HTML document
222
- # - %x :: the raw description (eXcerpt)
223
- # - %X :: the description, enclosed in an HTML ~p~ tag, equivalent
224
- # to ~<p>%x</p>~
225
- #
226
- # @example
227
- # org_file.format("Article written by %a the %d")
228
- # => "Article written by Alice Smith the Wednesday 3rd July"
229
- #
230
- # @return [String] the given ~string~ after replacement occurs
231
- # rubocop:disable Metrics/MethodLength
232
- # rubocop:disable Layout/LineLength
233
- def format(string)
234
- string.gsub('%a', @author)
235
- .gsub('%A', author_to_html)
236
- .gsub('%d', date_to_html(:short))
237
- .gsub('%D', date_to_html)
238
- .gsub('%i', datestring(:short))
239
- .gsub('%I', datestring(:rfc3339))
240
- .gsub('%k', @keywords.join(', '))
241
- .gsub('%K', keywords_to_html)
242
- .gsub('%l', @lang)
243
- .gsub('%L', Fronde::Config.get('license', '').gsub(/\s+/, ' ').strip)
244
- .gsub('%n', "Fronde #{Fronde::VERSION}")
245
- .gsub('%N', "<a href=\"https://git.umaneti.net/fronde/about/\">Fronde</a> #{Fronde::VERSION}")
246
- .gsub('%s', @subtitle)
247
- .gsub('%t', @title)
248
- .gsub('%u', @html_file || '')
249
- .gsub('%x', @excerpt)
250
- .gsub('%X', "<p>#{@excerpt}</p>")
251
- end
252
- # rubocop:enable Layout/LineLength
253
- # rubocop:enable Metrics/MethodLength
254
-
255
- # Writes the current OrgFile content to the underlying file.
256
- #
257
- # The intermediate parent folders are created if necessary.
258
- #
259
- # @return [Integer] the length written (as returned by the
260
- # underlying ~File.write~ method call)
261
- def write
262
- raise TypeError, 'no conversion from nil file name to path.' if @file.nil?
263
- file_dir = File.dirname @file
264
- FileUtils.mkdir_p file_dir unless Dir.exist? file_dir
265
- File.write @file, @content
266
- end
267
-
268
- private
269
-
270
- def build_html_file_and_url
271
- return if @file.nil?
272
- @html_file = Fronde::OrgFile.target_for_source(
273
- @file, @project, with_public_folder: false
274
- )
275
- @url = "#{Fronde::Config.get('domain')}/#{@html_file}"
276
- end
277
-
278
- def init_empty_file
279
- @title = @options[:title] || ''
280
- @subtitle = ''
281
- @date = DateTime.now
282
- @notime = false
283
- @author = @options[:author] || Fronde::Config.get('author')
284
- @keywords = []
285
- @lang = @options[:lang] || Fronde::Config.get('lang')
286
- @excerpt = ''
287
- body = @options[:content] || ''
288
- @content = @options[:raw_content] || <<~ORG
289
- #+title: #{@title}
290
- #+date: <#{@date.strftime('%Y-%m-%d %a. %H:%M:%S')}>
291
- #+author: #{@author}
292
- #+language: #{@lang}
293
-
294
- #{body}
295
- ORG
296
- end
297
- end
298
- end
data/lib/fronde/utils.rb DELETED
@@ -1,229 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'uri'
4
- require 'rainbow'
5
- require 'net/http'
6
- require 'r18n-core'
7
- require 'fronde/config'
8
-
9
- module Fronde
10
- # Default Error, which may be raised by fronde code
11
- class Error < ::StandardError; end
12
-
13
- # Embeds usefull methods, mainly used in rake tasks.
14
- module Utils
15
- # @return [Hash] the possible throbber themes
16
- THROBBER_FRAMES = {
17
- 'basic' => '-\|/',
18
- 'basicdots' => '⋯⋱⋮⋰',
19
- 'moon' => '🌑🌒🌓🌔🌕🌖🌗🌘',
20
- 'clock' => '🕛🕐🕑🕒🕓🕔🕕🕖🕗🕘🕙🕚',
21
- 'bricks' => '⣷⣯⣟⡿⢿⣻⣽⣾',
22
- 'points' => '·⁘∷⁛∷⁘',
23
- 'quadrant2' => '▙▛▜▟',
24
- 'default' => ['⠁ ⠂ ⠄ ⡀ ⠄ ⠂ ⠁', '⠂ ⠁ ⠂ ⠄ ⡀ ⠄ ⠂', '⠄ ⠂ ⠁ ⠂ ⠄ ⡀ ⠄',
25
- '⡀ ⠄ ⠂ ⠁ ⠂ ⠄ ⡀', '⠄ ⡀ ⠄ ⠂ ⠁ ⠂ ⠄', '⠂ ⠄ ⡀ ⠄ ⠂ ⠁ ⠂']
26
- }.freeze
27
-
28
- # @return [Hash] the possible ~fronde~ options and their
29
- # configuration
30
- FRONDE_OPTIONS = {
31
- '-a' => { long: 'author' },
32
- '-f' => { long: 'force', boolean: true },
33
- '-h' => { long: 'help', boolean: true, meth: :on_tail },
34
- '-l' => { long: 'lang', keyword: 'LOCALE' },
35
- '-t' => { long: 'title' },
36
- '-v' => { long: 'verbose', boolean: true, meth: :on_tail },
37
- '-V' => { long: 'version', boolean: true, meth: :on_tail }
38
- }.freeze
39
-
40
- # @return [Hash] the possible ~fronde~ subcommands and their
41
- # configuration
42
- FRONDE_COMMANDS = {
43
- 'init' => { opts: ['-a', '-h', '-l', '-t', '-v'] },
44
- 'update' => { opts: ['-a', '-h', '-l', '-t', '-v'] },
45
- 'config' => { alias: 'update' },
46
- 'preview' => { opts: ['-h'] },
47
- 'open' => { opts: ['-a', '-h', '-l', '-t', '-v'] },
48
- 'edit' => { alias: 'open' },
49
- 'build' => { opts: ['-f', '-h'] },
50
- 'publish' => { opts: ['-h'] },
51
- 'help' => { opts: ['-h'] },
52
- 'basic' => { opts: ['-h', '-V'], label: '<command>' }
53
- }.freeze
54
-
55
- class << self
56
- # Animates strings in the user console to alert him that something
57
- # is running in the background.
58
- #
59
- # The animation is chosen among a bunch of themes, with the
60
- # configuration option ~throbber~ (retrieved via
61
- # {Fronde::Config#settings}).
62
- #
63
- # @example
64
- # long_stuff = Thread.new { very_long_operation }
65
- # Fronde::Utils.throbber(long_stuff, 'Computing hard stuff:')
66
- #
67
- # @param thread [Thread] the long-running operation to decorate
68
- # @param message [String] the message to display before the throbber
69
- # @return [void]
70
- def throbber(thread, message)
71
- frames = select_throbber_frames
72
- begin
73
- run_and_decorate_thread thread, message, frames
74
- rescue RuntimeError => e
75
- throbber_error message
76
- raise e
77
- else
78
- done = Rainbow('done'.ljust(frames[0].length)).green
79
- puts "#{message} #{done}"
80
- end
81
- end
82
-
83
- # Returns the short and long options specification for a given
84
- # short option.
85
- #
86
- # This method use the {Fronde::Utils::FRONDE_OPTIONS} Hash to
87
- # retrieve corresponding values.
88
- #
89
- # @example
90
- # spec = Fronde::Utils.decorate_option('-a')
91
- # => ['-a AUTHOR', '--author AUTHOR']
92
- #
93
- # @param short [String] the short option to decorate
94
- # @return [Array] the short and long specification for an option
95
- def decorate_option(short)
96
- opt = Fronde::Utils::FRONDE_OPTIONS[short]
97
- long = "--#{opt[:long]}"
98
- return [short, long] if opt[:boolean]
99
- key = opt[:keyword] || opt[:long].upcase
100
- [short + key, format('%<long>s %<key>s', long: long, key: key)]
101
- end
102
-
103
- # Returns the ~fronde~ help summary for a given command.
104
- #
105
- # @param command [String] the command for which a summary
106
- # should be given
107
- # @return [String]
108
- def summarize_command(command)
109
- Fronde::Utils::FRONDE_COMMANDS[command][:opts].map do |k|
110
- short, long = Fronde::Utils.decorate_option(k)
111
- opt = Fronde::Utils::FRONDE_OPTIONS[k]
112
- label = [short, long].join(', ')
113
- line = [format(' %<opt>s', opt: label).ljust(30)]
114
- if R18n.t.fronde.bin.options[opt[:long]].translated?
115
- line << R18n.t.fronde.bin.options[opt[:long]]
116
- end
117
- line.join(' ')
118
- end.join("\n")
119
- end
120
-
121
- # Returns a formatted list of available commands for ~fronde~.
122
- #
123
- # @return [String]
124
- def list_commands
125
- lines = []
126
- Fronde::Utils::FRONDE_COMMANDS.each do |cmd, opt|
127
- next if cmd == 'basic'
128
- line = [' ', cmd.ljust(10)]
129
- if opt.has_key? :alias
130
- line << R18n.t.fronde.bin.commands.alias(opt[:alias])
131
- else
132
- line << R18n.t.fronde.bin.commands[cmd]
133
- end
134
- lines << line.join(' ')
135
- end
136
- lines.join("\n")
137
- end
138
-
139
- # Returns the real command name for a given command, which may be
140
- # an alias.
141
- #
142
- # @param command [String] the command to resolve
143
- # @return [String]
144
- def resolve_possible_alias(command)
145
- return 'basic' unless Fronde::Utils::FRONDE_COMMANDS.include?(command)
146
- cmd_opt = Fronde::Utils::FRONDE_COMMANDS[command]
147
- return cmd_opt[:alias] if cmd_opt.has_key?(:alias)
148
- command
149
- end
150
-
151
- # Returns the given command options.
152
- #
153
- # This method will first try to resolve command alias, if any.
154
- #
155
- # @param command [String] the command, which options should be returned
156
- # @return [Hash] the command options
157
- def command_options(command)
158
- cmd = resolve_possible_alias command
159
- FRONDE_COMMANDS[cmd].merge(name: cmd)
160
- end
161
-
162
- # Try to discover the current host operating system.
163
- #
164
- # @return [String] either apple, windows or linux (default)
165
- # :nocov:
166
- def current_os
167
- if ENV['OS'] == 'Windows_NT' || RUBY_PLATFORM.include?('cygwin')
168
- return 'windows'
169
- end
170
- return 'apple' if RUBY_PLATFORM.include?('darwin')
171
- 'linux'
172
- end
173
- # :nocov:
174
-
175
- # Download latest org-mode tarball.
176
- #
177
- # @param destination [String] where to save the org-mode tarball
178
- # @return [String] the downloaded org-mode version
179
- def download_org(destination = 'var/tmp')
180
- # :nocov:
181
- return if Fronde::Config.org_last_version.nil?
182
- # :nocov:
183
- # Remove version number in dest file to allow easy rake file
184
- # task naming
185
- dest_file = File.expand_path('org.tar.gz', destination)
186
- return if File.exist?(dest_file)
187
- tarball = "org-mode-release_#{Fronde::Config.org_last_version}.tar.gz"
188
- uri = URI("https://git.savannah.gnu.org/cgit/emacs/org-mode.git/snapshot/#{tarball}")
189
- # Will crash on purpose if anything goes wrong
190
- Net::HTTP.start(uri.host, uri.port, :use_ssl => true) do |http|
191
- http.request(Net::HTTP::Get.new(uri)) do |response|
192
- File.open(dest_file, 'w') do |io|
193
- response.read_body { |chunk| io.write chunk }
194
- end
195
- end
196
- end
197
- end
198
-
199
- private
200
-
201
- def throbber_error(message)
202
- warn(
203
- format(
204
- "%<message>s %<label>s\n%<explanation>s",
205
- message: message,
206
- label: Rainbow(R18n.t.fronde.error.label).bold.red,
207
- explanation: Rainbow(R18n.t.fronde.error.explanation).bold
208
- )
209
- )
210
- end
211
-
212
- def select_throbber_frames
213
- model = Fronde::Config.settings['throbber'] || 'default'
214
- model = 'default' unless Fronde::Utils::THROBBER_FRAMES.has_key?(model)
215
- Fronde::Utils::THROBBER_FRAMES[model]
216
- end
217
-
218
- def run_and_decorate_thread(thread, message, frames)
219
- thread.abort_on_exception = true
220
- current = 0
221
- while thread.alive?
222
- sleep 0.1
223
- print "#{message} #{frames[current % frames.length]}\r"
224
- current += 1
225
- end
226
- end
227
- end
228
- end
229
- end