logstash-core 2.2.4.snapshot1
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.
Potentially problematic release.
This version of logstash-core might be problematic. Click here for more details.
- checksums.yaml +7 -0
- data/lib/logstash-core.rb +1 -0
- data/lib/logstash-core/logstash-core.rb +3 -0
- data/lib/logstash-core/version.rb +8 -0
- data/lib/logstash/agent.rb +391 -0
- data/lib/logstash/codecs/base.rb +50 -0
- data/lib/logstash/config/config_ast.rb +550 -0
- data/lib/logstash/config/cpu_core_strategy.rb +32 -0
- data/lib/logstash/config/defaults.rb +12 -0
- data/lib/logstash/config/file.rb +39 -0
- data/lib/logstash/config/grammar.rb +3503 -0
- data/lib/logstash/config/mixin.rb +518 -0
- data/lib/logstash/config/registry.rb +13 -0
- data/lib/logstash/environment.rb +98 -0
- data/lib/logstash/errors.rb +12 -0
- data/lib/logstash/filters/base.rb +205 -0
- data/lib/logstash/inputs/base.rb +116 -0
- data/lib/logstash/inputs/threadable.rb +18 -0
- data/lib/logstash/java_integration.rb +116 -0
- data/lib/logstash/json.rb +61 -0
- data/lib/logstash/logging.rb +91 -0
- data/lib/logstash/namespace.rb +13 -0
- data/lib/logstash/output_delegator.rb +172 -0
- data/lib/logstash/outputs/base.rb +91 -0
- data/lib/logstash/patches.rb +5 -0
- data/lib/logstash/patches/bugfix_jruby_2558.rb +51 -0
- data/lib/logstash/patches/cabin.rb +35 -0
- data/lib/logstash/patches/profile_require_calls.rb +47 -0
- data/lib/logstash/patches/rubygems.rb +38 -0
- data/lib/logstash/patches/stronger_openssl_defaults.rb +68 -0
- data/lib/logstash/pipeline.rb +499 -0
- data/lib/logstash/pipeline_reporter.rb +114 -0
- data/lib/logstash/plugin.rb +120 -0
- data/lib/logstash/program.rb +14 -0
- data/lib/logstash/runner.rb +124 -0
- data/lib/logstash/shutdown_watcher.rb +100 -0
- data/lib/logstash/util.rb +203 -0
- data/lib/logstash/util/buftok.rb +139 -0
- data/lib/logstash/util/charset.rb +35 -0
- data/lib/logstash/util/decorators.rb +52 -0
- data/lib/logstash/util/defaults_printer.rb +31 -0
- data/lib/logstash/util/filetools.rb +186 -0
- data/lib/logstash/util/java_version.rb +66 -0
- data/lib/logstash/util/password.rb +25 -0
- data/lib/logstash/util/plugin_version.rb +56 -0
- data/lib/logstash/util/prctl.rb +10 -0
- data/lib/logstash/util/retryable.rb +40 -0
- data/lib/logstash/util/socket_peer.rb +7 -0
- data/lib/logstash/util/unicode_trimmer.rb +81 -0
- data/lib/logstash/util/worker_threads_default_printer.rb +29 -0
- data/lib/logstash/util/wrapped_synchronous_queue.rb +41 -0
- data/lib/logstash/version.rb +14 -0
- data/locales/en.yml +204 -0
- data/logstash-core.gemspec +58 -0
- data/spec/conditionals_spec.rb +429 -0
- data/spec/logstash/agent_spec.rb +85 -0
- data/spec/logstash/config/config_ast_spec.rb +146 -0
- data/spec/logstash/config/cpu_core_strategy_spec.rb +123 -0
- data/spec/logstash/config/defaults_spec.rb +10 -0
- data/spec/logstash/config/mixin_spec.rb +158 -0
- data/spec/logstash/environment_spec.rb +56 -0
- data/spec/logstash/filters/base_spec.rb +251 -0
- data/spec/logstash/inputs/base_spec.rb +74 -0
- data/spec/logstash/java_integration_spec.rb +304 -0
- data/spec/logstash/json_spec.rb +96 -0
- data/spec/logstash/output_delegator_spec.rb +144 -0
- data/spec/logstash/outputs/base_spec.rb +40 -0
- data/spec/logstash/patches_spec.rb +90 -0
- data/spec/logstash/pipeline_reporter_spec.rb +85 -0
- data/spec/logstash/pipeline_spec.rb +455 -0
- data/spec/logstash/plugin_spec.rb +169 -0
- data/spec/logstash/runner_spec.rb +68 -0
- data/spec/logstash/shutdown_watcher_spec.rb +113 -0
- data/spec/logstash/util/buftok_spec.rb +31 -0
- data/spec/logstash/util/charset_spec.rb +74 -0
- data/spec/logstash/util/defaults_printer_spec.rb +50 -0
- data/spec/logstash/util/java_version_spec.rb +79 -0
- data/spec/logstash/util/plugin_version_spec.rb +64 -0
- data/spec/logstash/util/unicode_trimmer_spec.rb +55 -0
- data/spec/logstash/util/worker_threads_default_printer_spec.rb +45 -0
- data/spec/logstash/util/wrapped_synchronous_queue_spec.rb +28 -0
- data/spec/logstash/util_spec.rb +35 -0
- metadata +364 -0
@@ -0,0 +1,186 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require "net/http"
|
3
|
+
require "uri"
|
4
|
+
require "digest/sha1"
|
5
|
+
require 'fileutils'
|
6
|
+
|
7
|
+
module LogStash::Util::FileTools
|
8
|
+
extend self
|
9
|
+
|
10
|
+
def fetch(url, sha1, output)
|
11
|
+
|
12
|
+
puts "Downloading #{url}"
|
13
|
+
actual_sha1 = download(url, output)
|
14
|
+
|
15
|
+
if actual_sha1 != sha1
|
16
|
+
fail "SHA1 does not match (expected '#{sha1}' but got '#{actual_sha1}')"
|
17
|
+
end
|
18
|
+
end # def fetch
|
19
|
+
|
20
|
+
def file_fetch(url, sha1, target)
|
21
|
+
filename = File.basename( URI(url).path )
|
22
|
+
output = "#{target}/#{filename}"
|
23
|
+
begin
|
24
|
+
actual_sha1 = file_sha1(output)
|
25
|
+
if actual_sha1 != sha1
|
26
|
+
fetch(url, sha1, output)
|
27
|
+
end
|
28
|
+
rescue Errno::ENOENT
|
29
|
+
fetch(url, sha1, output)
|
30
|
+
end
|
31
|
+
return output
|
32
|
+
end
|
33
|
+
|
34
|
+
def file_sha1(path)
|
35
|
+
digest = Digest::SHA1.new
|
36
|
+
fd = File.new(path, "r")
|
37
|
+
while true
|
38
|
+
begin
|
39
|
+
digest << fd.sysread(16384)
|
40
|
+
rescue EOFError
|
41
|
+
break
|
42
|
+
end
|
43
|
+
end
|
44
|
+
return digest.hexdigest
|
45
|
+
ensure
|
46
|
+
fd.close if fd
|
47
|
+
end
|
48
|
+
|
49
|
+
def download(url, output)
|
50
|
+
uri = URI(url)
|
51
|
+
digest = Digest::SHA1.new
|
52
|
+
tmp = "#{output}.tmp"
|
53
|
+
Net::HTTP.start(uri.host, uri.port, :use_ssl => (uri.scheme == "https")) do |http|
|
54
|
+
request = Net::HTTP::Get.new(uri.path)
|
55
|
+
http.request(request) do |response|
|
56
|
+
fail "HTTP fetch failed for #{url}. #{response}" if [200, 301].include?(response.code)
|
57
|
+
size = (response["content-length"].to_i || -1).to_f
|
58
|
+
count = 0
|
59
|
+
File.open(tmp, "w") do |fd|
|
60
|
+
response.read_body do |chunk|
|
61
|
+
fd.write(chunk)
|
62
|
+
digest << chunk
|
63
|
+
if size > 0 && $stdout.tty?
|
64
|
+
count += chunk.bytesize
|
65
|
+
$stdout.write(sprintf("\r%0.2f%%", count/size * 100))
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
$stdout.write("\r \r") if $stdout.tty?
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
File.rename(tmp, output)
|
74
|
+
|
75
|
+
return digest.hexdigest
|
76
|
+
rescue SocketError => e
|
77
|
+
puts "Failure while downloading #{url}: #{e}"
|
78
|
+
raise
|
79
|
+
ensure
|
80
|
+
File.unlink(tmp) if File.exist?(tmp)
|
81
|
+
end # def download
|
82
|
+
|
83
|
+
def untar(tarball, &block)
|
84
|
+
require "archive/tar/minitar"
|
85
|
+
tgz = Zlib::GzipReader.new(File.open(tarball))
|
86
|
+
# Pull out typesdb
|
87
|
+
tar = Archive::Tar::Minitar::Input.open(tgz)
|
88
|
+
tar.each do |entry|
|
89
|
+
path = block.call(entry)
|
90
|
+
next if path.nil?
|
91
|
+
parent = File.dirname(path)
|
92
|
+
|
93
|
+
FileUtils.mkdir_p(parent) unless File.directory?(parent)
|
94
|
+
|
95
|
+
# Skip this file if the output file is the same size
|
96
|
+
if entry.directory?
|
97
|
+
FileUtils.mkdir_p(path) unless File.directory?(path)
|
98
|
+
else
|
99
|
+
entry_mode = entry.instance_eval { @mode } & 0777
|
100
|
+
if File.exists?(path)
|
101
|
+
stat = File.stat(path)
|
102
|
+
# TODO(sissel): Submit a patch to archive-tar-minitar upstream to
|
103
|
+
# expose headers in the entry.
|
104
|
+
entry_size = entry.instance_eval { @size }
|
105
|
+
# If file sizes are same, skip writing.
|
106
|
+
next if stat.size == entry_size && (stat.mode & 0777) == entry_mode
|
107
|
+
end
|
108
|
+
puts "Extracting #{entry.full_name} from #{tarball} #{entry_mode.to_s(8)}"
|
109
|
+
File.open(path, "w") do |fd|
|
110
|
+
# eof? check lets us skip empty files. Necessary because the API provided by
|
111
|
+
# Archive::Tar::Minitar::Reader::EntryStream only mostly acts like an
|
112
|
+
# IO object. Something about empty files in this EntryStream causes
|
113
|
+
# IO.copy_stream to throw "can't convert nil into String" on JRuby
|
114
|
+
# TODO(sissel): File a bug about this.
|
115
|
+
while !entry.eof?
|
116
|
+
chunk = entry.read(16384)
|
117
|
+
fd.write(chunk)
|
118
|
+
end
|
119
|
+
#IO.copy_stream(entry, fd)
|
120
|
+
end
|
121
|
+
File.chmod(entry_mode, path)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
tar.close
|
125
|
+
File.unlink(tarball) if File.file?(tarball)
|
126
|
+
end # def untar
|
127
|
+
|
128
|
+
def do_ungz(file)
|
129
|
+
|
130
|
+
outpath = file.gsub('.gz', '')
|
131
|
+
tgz = Zlib::GzipReader.new(File.open(file))
|
132
|
+
begin
|
133
|
+
File.open(outpath, "w") do |out|
|
134
|
+
IO::copy_stream(tgz, out)
|
135
|
+
end
|
136
|
+
File.unlink(file)
|
137
|
+
rescue
|
138
|
+
File.unlink(outpath) if File.file?(outpath)
|
139
|
+
raise
|
140
|
+
end
|
141
|
+
tgz.close
|
142
|
+
end
|
143
|
+
|
144
|
+
def eval_file(entry, files, prefix)
|
145
|
+
return false if entry.full_name =~ /PaxHeaders/
|
146
|
+
if !files.nil?
|
147
|
+
if files.is_a?(Array)
|
148
|
+
return false unless files.include?(entry.full_name.gsub(prefix, ''))
|
149
|
+
entry.full_name.split("/").last
|
150
|
+
elsif files.is_a?(String)
|
151
|
+
return false unless entry.full_name =~ Regexp.new(files)
|
152
|
+
entry.full_name.split("/").last
|
153
|
+
end
|
154
|
+
else
|
155
|
+
entry.full_name.gsub(prefix, '')
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
def process_downloads(files,target='')
|
160
|
+
|
161
|
+
FileUtils.mkdir_p(target) unless File.directory?(target)
|
162
|
+
|
163
|
+
files.each do |file|
|
164
|
+
download = file_fetch(file['url'], file['sha1'],target)
|
165
|
+
|
166
|
+
if download =~ /.tar.gz/
|
167
|
+
prefix = download.gsub('.tar.gz', '').gsub("#{target}/", '')
|
168
|
+
untar(download) do |entry|
|
169
|
+
next unless out = eval_file(entry, file['files'], prefix)
|
170
|
+
File.join(target, out)
|
171
|
+
end
|
172
|
+
|
173
|
+
elsif download =~ /.tgz/
|
174
|
+
prefix = download.gsub('.tgz', '').gsub("#{target}/", '')
|
175
|
+
untar(download) do |entry|
|
176
|
+
next unless out = eval_file(entry, file['files'], prefix)
|
177
|
+
File.join(target, out)
|
178
|
+
end
|
179
|
+
|
180
|
+
elsif download =~ /.gz/
|
181
|
+
do_ungz(download)
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'cabin'
|
3
|
+
|
4
|
+
module LogStash::Util::JavaVersion
|
5
|
+
def self.logger
|
6
|
+
@logger ||= Cabin::Channel.get(LogStash)
|
7
|
+
end
|
8
|
+
|
9
|
+
# Print a warning if we're on a bad version of java
|
10
|
+
def self.warn_on_bad_java_version
|
11
|
+
if self.bad_java_version?(self.version)
|
12
|
+
msg = "!!! Please upgrade your java version, the current version '#{self.version}' may cause problems. We recommend a minimum version of 1.7.0_51"
|
13
|
+
STDERR.puts(msg)
|
14
|
+
logger.warn(msg)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# Return the current java version string. Returns nil if this is a non-java platform (e.g. MRI).
|
19
|
+
def self.version
|
20
|
+
return nil unless LogStash::Environment.jruby?
|
21
|
+
java.lang.System.getProperty("java.runtime.version")
|
22
|
+
end
|
23
|
+
|
24
|
+
# Takes a string of a java version ex: "1.8.0_24-beta"
|
25
|
+
# and returns a parsed map of the components.
|
26
|
+
# nil inputs will be returned as nil.
|
27
|
+
def self.parse_java_version(version_string)
|
28
|
+
return nil if version_string.nil?
|
29
|
+
|
30
|
+
# Crazy java versioning rules @ http://www.oracle.com/technetwork/java/javase/versioning-naming-139433.html
|
31
|
+
# The regex below parses this all correctly http://rubular.com/r/sInQc3Nc7f
|
32
|
+
|
33
|
+
match = version_string.match(/\A(\d+)\.(\d+)\.(\d+)(_(\d+))?(-(.+))?\Z/)
|
34
|
+
|
35
|
+
return nil unless match
|
36
|
+
|
37
|
+
major, minor, patch, ufull, update, bfull, build = match.captures
|
38
|
+
|
39
|
+
{
|
40
|
+
:full => version_string,
|
41
|
+
:major => major.to_i,
|
42
|
+
:minor => minor.to_i,
|
43
|
+
:patch => patch.to_i,
|
44
|
+
:update => update.to_i, # this is always coerced to an int (a nil will be zero) to make comparisons easier
|
45
|
+
:build => build # not an integer, could be b06 for instance!,
|
46
|
+
}
|
47
|
+
end
|
48
|
+
|
49
|
+
# Determine if the given java version string is a bad version of java
|
50
|
+
# If it is, return true, if it isn't return false.
|
51
|
+
# Accepts nil, returning nil.
|
52
|
+
def self.bad_java_version?(version_string)
|
53
|
+
return nil if version_string.nil?
|
54
|
+
|
55
|
+
parsed = parse_java_version(version_string)
|
56
|
+
return false unless parsed
|
57
|
+
|
58
|
+
if parsed[:major] == 1 && parsed[:minor] == 7 && parsed[:patch] == 0 && parsed[:update] < 51
|
59
|
+
true
|
60
|
+
elsif parsed[:major] == 1 && parsed[:minor] < 7
|
61
|
+
true
|
62
|
+
else
|
63
|
+
false
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require "logstash/namespace"
|
3
|
+
require "logstash/util"
|
4
|
+
|
5
|
+
# This class exists to quietly wrap a password string so that, when printed or
|
6
|
+
# logged, you don't accidentally print the password itself.
|
7
|
+
class LogStash::Util::Password
|
8
|
+
attr_reader :value
|
9
|
+
|
10
|
+
public
|
11
|
+
def initialize(password)
|
12
|
+
@value = password
|
13
|
+
end # def initialize
|
14
|
+
|
15
|
+
public
|
16
|
+
def to_s
|
17
|
+
return "<password>"
|
18
|
+
end # def to_s
|
19
|
+
|
20
|
+
public
|
21
|
+
def inspect
|
22
|
+
return to_s
|
23
|
+
end # def inspect
|
24
|
+
end # class LogStash::Util::Password
|
25
|
+
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'logstash/errors'
|
3
|
+
require 'rubygems/version'
|
4
|
+
require 'forwardable'
|
5
|
+
|
6
|
+
module LogStash::Util
|
7
|
+
class PluginVersion
|
8
|
+
extend Forwardable
|
9
|
+
include Comparable
|
10
|
+
|
11
|
+
GEM_NAME_PREFIX = 'logstash'
|
12
|
+
|
13
|
+
def_delegators :@version, :to_s
|
14
|
+
attr_reader :version
|
15
|
+
|
16
|
+
def initialize(*options)
|
17
|
+
if options.size == 1 && options.first.is_a?(Gem::Version)
|
18
|
+
@version = options.first
|
19
|
+
else
|
20
|
+
@version = Gem::Version.new(options.join('.'))
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.find_version!(name)
|
25
|
+
begin
|
26
|
+
spec = Gem::Specification.find_by_name(name)
|
27
|
+
if spec.nil?
|
28
|
+
# Checking for nil? is a workaround for situations where find_by_name
|
29
|
+
# is not able to find the real spec, as for example with pre releases
|
30
|
+
# of plugins
|
31
|
+
spec = Gem::Specification.find_all_by_name(name).first
|
32
|
+
end
|
33
|
+
new(spec.version)
|
34
|
+
rescue Gem::LoadError
|
35
|
+
# Rescuing the LoadError and raise a Logstash specific error.
|
36
|
+
# Likely we can't find the gem in the current GEM_PATH
|
37
|
+
raise LogStash::PluginNoVersionError
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.find_plugin_version!(type, name)
|
42
|
+
plugin_name = [GEM_NAME_PREFIX, type, name].join('-')
|
43
|
+
find_version!(plugin_name)
|
44
|
+
end
|
45
|
+
|
46
|
+
def <=>(other)
|
47
|
+
version <=> other.version
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def self.build_from_spec(spec)
|
53
|
+
new(spec.version)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module LogStash
|
3
|
+
module Retryable
|
4
|
+
# execute retryable code block
|
5
|
+
# @param [Hash] options retryable options
|
6
|
+
# @option options [Fixnum] :tries retries to perform, default 1, set to 0 for infite retries. 1 means that upon exception the block will be retried once
|
7
|
+
# @option options [Fixnum] :base_sleep seconds to sleep on first retry, default 1
|
8
|
+
# @option options [Fixnum] :max_sleep max seconds to sleep upon exponential backoff, default 1
|
9
|
+
# @option options [Exception] :rescue exception class list to retry on, defaults is Exception, which retries on any Exception.
|
10
|
+
# @option options [Proc] :on_retry call the given Proc/lambda before each retry with the raised exception as parameter
|
11
|
+
def retryable(options = {}, &block)
|
12
|
+
options = {
|
13
|
+
:tries => 1,
|
14
|
+
:rescue => Exception,
|
15
|
+
:on_retry => nil,
|
16
|
+
:base_sleep => 1,
|
17
|
+
:max_sleep => 1,
|
18
|
+
}.merge(options)
|
19
|
+
|
20
|
+
rescue_classes = Array(options[:rescue])
|
21
|
+
max_sleep_retry = Math.log2(options[:max_sleep] / options[:base_sleep])
|
22
|
+
retry_count = 0
|
23
|
+
|
24
|
+
begin
|
25
|
+
return yield(retry_count)
|
26
|
+
rescue *rescue_classes => e
|
27
|
+
raise e if options[:tries] > 0 && retry_count >= options[:tries]
|
28
|
+
|
29
|
+
options[:on_retry].call(retry_count + 1, e) if options[:on_retry]
|
30
|
+
|
31
|
+
# dont compute and maybe overflow exponent on too big a retry count
|
32
|
+
seconds = retry_count < max_sleep_retry ? options[:base_sleep] * (2 ** retry_count) : options[:max_sleep]
|
33
|
+
sleep(seconds)
|
34
|
+
|
35
|
+
retry_count += 1
|
36
|
+
retry
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module LogStash::Util::UnicodeTrimmer
|
3
|
+
# The largest possible unicode chars are 4 bytes
|
4
|
+
# http://stackoverflow.com/questions/9533258/what-is-the-maximum-number-of-bytes-for-a-utf-8-encoded-character
|
5
|
+
# http://tools.ietf.org/html/rfc3629
|
6
|
+
MAX_CHAR_BYTES = 4
|
7
|
+
|
8
|
+
# Takes a unicode string and makes sure it fits in a max of `desired_bytes`
|
9
|
+
# This aims to be somewhat efficient about this for the average case and get as close to
|
10
|
+
# O(1) as possible. Given certain distributions of multi-byte characters it'll be slower
|
11
|
+
# It tries to find the point the truncation *should* happen based on the average byte size.
|
12
|
+
# If that snips it in the wrong place it'll try to add or remove chars to get it to the right
|
13
|
+
# spot and preserve as much data as possible.
|
14
|
+
public
|
15
|
+
def self.trim_bytes(orig_str, desired_bytes)
|
16
|
+
return orig_str if orig_str.bytesize <= desired_bytes
|
17
|
+
|
18
|
+
pre_shortened = pre_shorten(orig_str, desired_bytes)
|
19
|
+
|
20
|
+
case pre_shortened.bytesize <=> desired_bytes
|
21
|
+
when 0
|
22
|
+
pre_shortened
|
23
|
+
when 1
|
24
|
+
shrink_bytes(pre_shortened, orig_str, desired_bytes)
|
25
|
+
when -1
|
26
|
+
grow_bytes(pre_shortened, orig_str, desired_bytes)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
# Try to cut the string at the right place based on the avg. byte size
|
32
|
+
def self.pre_shorten(orig_str, desired_bytes)
|
33
|
+
# Compute the average size to get an idea of where should chop
|
34
|
+
orig_len = orig_str.length
|
35
|
+
orig_bs = orig_str.bytesize
|
36
|
+
avg_size = (orig_bs.to_f / orig_len.to_f)
|
37
|
+
|
38
|
+
# Try to do an initial shortening based on the average char size
|
39
|
+
# The goal here is to get us somewhere above or below the boundary quickly
|
40
|
+
orig_extra_bytes = orig_bs - desired_bytes
|
41
|
+
pre_shorten_by = (orig_extra_bytes / avg_size).to_i
|
42
|
+
orig_str.slice(0, orig_len - pre_shorten_by)
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
def self.grow_bytes(pre_shortened, orig_str, desired_bytes)
|
47
|
+
res_str = pre_shortened.clone()
|
48
|
+
|
49
|
+
loop do
|
50
|
+
bs = res_str.bytesize
|
51
|
+
deficit = desired_bytes - bs
|
52
|
+
lengthen_by = deficit / MAX_CHAR_BYTES
|
53
|
+
lengthen_by = 1 if lengthen_by < 1
|
54
|
+
append = orig_str.slice(res_str.length, lengthen_by)
|
55
|
+
|
56
|
+
break if (bs + append.bytesize) > desired_bytes
|
57
|
+
|
58
|
+
res_str << append
|
59
|
+
end
|
60
|
+
|
61
|
+
res_str
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
def self.shrink_bytes(pre_shortened, orig_str, desired_bytes)
|
66
|
+
res_str = pre_shortened.clone()
|
67
|
+
|
68
|
+
loop do
|
69
|
+
bs = res_str.bytesize
|
70
|
+
break if bs <= desired_bytes
|
71
|
+
|
72
|
+
extra = bs - desired_bytes
|
73
|
+
shorten_by = extra / MAX_CHAR_BYTES
|
74
|
+
shorten_by = 1 if shorten_by < 1
|
75
|
+
|
76
|
+
res_str.slice!(res_str.length - shorten_by)
|
77
|
+
end
|
78
|
+
|
79
|
+
res_str
|
80
|
+
end
|
81
|
+
end
|