travis_dpl_test 2.0.3.beta.4.ror

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 (100) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +172 -0
  3. data/CODE_OF_CONDUCT.md +74 -0
  4. data/CONTRIBUTING.md +392 -0
  5. data/Gemfile +32 -0
  6. data/Gemfile.lock +611 -0
  7. data/LICENSE +19 -0
  8. data/README.md +2744 -0
  9. data/Rakefile +210 -0
  10. data/bin/dpl +11 -0
  11. data/config/transliterate.yml +733 -0
  12. data/dpl.gemspec +23 -0
  13. data/lib/dpl/assets/atlas/install +19 -0
  14. data/lib/dpl/assets/convox/install +11 -0
  15. data/lib/dpl/assets/dpl/README.erb.md +138 -0
  16. data/lib/dpl/assets/dpl/git_ssh +8 -0
  17. data/lib/dpl/assets/git/detect_private_key +8 -0
  18. data/lib/dpl/assets/hephy/filter_log +3 -0
  19. data/lib/dpl/assets/pypi/install +4 -0
  20. data/lib/dpl/assets/scalingo/install +6 -0
  21. data/lib/dpl/cli.rb +100 -0
  22. data/lib/dpl/ctx/bash.rb +549 -0
  23. data/lib/dpl/ctx/test.rb +255 -0
  24. data/lib/dpl/ctx.rb +4 -0
  25. data/lib/dpl/helper/assets.rb +38 -0
  26. data/lib/dpl/helper/cmd.rb +169 -0
  27. data/lib/dpl/helper/config_file.rb +49 -0
  28. data/lib/dpl/helper/cookbook_site_streaming_uploader.rb +249 -0
  29. data/lib/dpl/helper/env.rb +92 -0
  30. data/lib/dpl/helper/github.rb +22 -0
  31. data/lib/dpl/helper/interpolate.rb +160 -0
  32. data/lib/dpl/helper/memoize.rb +23 -0
  33. data/lib/dpl/helper/squiggle.rb +24 -0
  34. data/lib/dpl/helper/transliterate.rb +13 -0
  35. data/lib/dpl/helper/wrap.rb +11 -0
  36. data/lib/dpl/helper/zip.rb +71 -0
  37. data/lib/dpl/provider/dsl.rb +410 -0
  38. data/lib/dpl/provider/examples.rb +132 -0
  39. data/lib/dpl/provider/status.rb +61 -0
  40. data/lib/dpl/provider.rb +651 -0
  41. data/lib/dpl/providers/anynines.rb +71 -0
  42. data/lib/dpl/providers/azure_web_apps.rb +63 -0
  43. data/lib/dpl/providers/bintray.rb +324 -0
  44. data/lib/dpl/providers/bluemixcloudfoundry.rb +98 -0
  45. data/lib/dpl/providers/boxfuse.rb +52 -0
  46. data/lib/dpl/providers/cargo.rb +32 -0
  47. data/lib/dpl/providers/chef_supermarket.rb +132 -0
  48. data/lib/dpl/providers/cloud66.rb +46 -0
  49. data/lib/dpl/providers/cloudfiles.rb +62 -0
  50. data/lib/dpl/providers/cloudformation.rb +281 -0
  51. data/lib/dpl/providers/cloudfoundry.rb +89 -0
  52. data/lib/dpl/providers/codedeploy.rb +190 -0
  53. data/lib/dpl/providers/convox.rb +130 -0
  54. data/lib/dpl/providers/datica.rb +64 -0
  55. data/lib/dpl/providers/ecr.rb +129 -0
  56. data/lib/dpl/providers/elasticbeanstalk.rb +207 -0
  57. data/lib/dpl/providers/engineyard.rb +113 -0
  58. data/lib/dpl/providers/firebase.rb +45 -0
  59. data/lib/dpl/providers/flynn.rb +35 -0
  60. data/lib/dpl/providers/gae.rb +78 -0
  61. data/lib/dpl/providers/gcs.rb +132 -0
  62. data/lib/dpl/providers/git_push.rb +273 -0
  63. data/lib/dpl/providers/gleis.rb +74 -0
  64. data/lib/dpl/providers/hackage.rb +53 -0
  65. data/lib/dpl/providers/hephy.rb +107 -0
  66. data/lib/dpl/providers/heroku/api.rb +123 -0
  67. data/lib/dpl/providers/heroku/git.rb +54 -0
  68. data/lib/dpl/providers/heroku.rb +111 -0
  69. data/lib/dpl/providers/lambda.rb +211 -0
  70. data/lib/dpl/providers/launchpad.rb +80 -0
  71. data/lib/dpl/providers/netlify.rb +38 -0
  72. data/lib/dpl/providers/npm.rb +130 -0
  73. data/lib/dpl/providers/nuget.rb +41 -0
  74. data/lib/dpl/providers/openshift.rb +52 -0
  75. data/lib/dpl/providers/opsworks.rb +146 -0
  76. data/lib/dpl/providers/packagecloud.rb_ +194 -0
  77. data/lib/dpl/providers/pages/api.rb +106 -0
  78. data/lib/dpl/providers/pages/git.rb +262 -0
  79. data/lib/dpl/providers/pages.rb +18 -0
  80. data/lib/dpl/providers/puppetforge.rb +50 -0
  81. data/lib/dpl/providers/pypi.rb +125 -0
  82. data/lib/dpl/providers/releases.rb +234 -0
  83. data/lib/dpl/providers/rubygems.rb +97 -0
  84. data/lib/dpl/providers/s3.rb +251 -0
  85. data/lib/dpl/providers/scalingo.rb +69 -0
  86. data/lib/dpl/providers/script.rb +32 -0
  87. data/lib/dpl/providers/snap.rb +68 -0
  88. data/lib/dpl/providers/surge.rb +59 -0
  89. data/lib/dpl/providers/testfairy.rb +101 -0
  90. data/lib/dpl/providers/transifex.rb +72 -0
  91. data/lib/dpl/providers.rb +48 -0
  92. data/lib/dpl/string_ext.rb +23 -0
  93. data/lib/dpl/support/aws_sdk_patch.rb +26 -0
  94. data/lib/dpl/support/gems.rb +73 -0
  95. data/lib/dpl/support/gstore_patch.rb +8 -0
  96. data/lib/dpl/support/version.rb +84 -0
  97. data/lib/dpl/version.rb +5 -0
  98. data/lib/dpl.rb +23 -0
  99. data/status.json +237 -0
  100. metadata +161 -0
@@ -0,0 +1,249 @@
1
+ #
2
+ # Author:: Stanislav Vitvitskiy
3
+ # Author:: Nuo Yan (nuo@chef.io)
4
+ # Author:: Christopher Walters (<cw@chef.io>)
5
+ # Copyright:: Copyright (c) Chef Software Inc.
6
+ # License:: Apache License, Version 2.0
7
+ #
8
+ # Licensed under the Apache License, Version 2.0 (the "License");
9
+ # you may not use this file except in compliance with the License.
10
+ # You may obtain a copy of the License at
11
+ #
12
+ # http://www.apache.org/licenses/LICENSE-2.0
13
+ #
14
+ # Unless required by applicable law or agreed to in writing, software
15
+ # distributed under the License is distributed on an "AS IS" BASIS,
16
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
+ # See the License for the specific language governing permissions and
18
+ # limitations under the License.
19
+ #
20
+
21
+ autoload :URI, "uri"
22
+ module Net
23
+ autoload :HTTP, "net/http"
24
+ end
25
+ autoload :OpenSSL, "openssl"
26
+ module Mixlib
27
+ module Authentication
28
+ autoload :SignedHeaderAuth, "mixlib/authentication/signedheaderauth"
29
+ end
30
+ end
31
+ require 'chef'
32
+ require "chef-utils/dist" unless defined?(ChefUtils::Dist)
33
+ class Chef
34
+ class Knife
35
+ module Core
36
+ # == Chef::Knife::Core::CookbookSiteStreamingUploader
37
+ # A streaming multipart HTTP upload implementation. Used to upload cookbooks
38
+ # (in tarball form) to https://supermarket.chef.io
39
+ #
40
+ # inspired by http://stanislavvitvitskiy.blogspot.com/2008/12/multipart-post-in-ruby.html
41
+ class CookbookSiteStreamingUploader
42
+
43
+ DefaultHeaders = { "accept" => "application/json", "x-chef-version" => ::Chef::VERSION }.freeze # rubocop:disable Naming/ConstantName
44
+
45
+ class << self
46
+
47
+ def create_build_dir(cookbook)
48
+ tmp_cookbook_path = Tempfile.new("#{ChefUtils::Dist::Infra::SHORT}-#{cookbook.name}-build")
49
+ tmp_cookbook_path.close
50
+ tmp_cookbook_dir = tmp_cookbook_path.path
51
+ File.unlink(tmp_cookbook_dir)
52
+ FileUtils.mkdir_p(tmp_cookbook_dir)
53
+ Chef::Log.trace("Staging at #{tmp_cookbook_dir}")
54
+ checksums_to_on_disk_paths = cookbook.checksums
55
+ cookbook.each_file do |manifest_record|
56
+ path_in_cookbook = manifest_record[:path]
57
+ on_disk_path = checksums_to_on_disk_paths[manifest_record[:checksum]]
58
+ dest = File.join(tmp_cookbook_dir, cookbook.name.to_s, path_in_cookbook)
59
+ FileUtils.mkdir_p(File.dirname(dest))
60
+ Chef::Log.trace("Staging #{on_disk_path} to #{dest}")
61
+ FileUtils.cp(on_disk_path, dest)
62
+ end
63
+
64
+ # First, generate metadata
65
+ Chef::Log.trace("Generating metadata")
66
+ kcm = Chef::Knife::CookbookMetadata.new
67
+ kcm.config[:cookbook_path] = [ tmp_cookbook_dir ]
68
+ kcm.name_args = [ cookbook.name.to_s ]
69
+ kcm.run
70
+
71
+ tmp_cookbook_dir
72
+ end
73
+
74
+ def post(to_url, user_id, secret_key_filename, params = {}, headers = {})
75
+ make_request(:post, to_url, user_id, secret_key_filename, params, headers)
76
+ end
77
+
78
+ def put(to_url, user_id, secret_key_filename, params = {}, headers = {})
79
+ make_request(:put, to_url, user_id, secret_key_filename, params, headers)
80
+ end
81
+
82
+ def make_request(http_verb, to_url, user_id, secret_key_filename, params = {}, headers = {})
83
+ boundary = "----RubyMultipartClient" + rand(1000000).to_s + "ZZZZZ"
84
+ parts = []
85
+ content_file = nil
86
+
87
+ secret_key = OpenSSL::PKey::RSA.new(File.read(secret_key_filename))
88
+
89
+ unless params.nil? || params.empty?
90
+ params.each do |key, value|
91
+ if value.is_a?(File)
92
+ content_file = value
93
+ filepath = value.path
94
+ filename = File.basename(filepath)
95
+ parts << StringPart.new( "--" + boundary + "\r\n" +
96
+ "Content-Disposition: form-data; name=\"" + key.to_s + "\"; filename=\"" + filename + "\"\r\n" +
97
+ "Content-Type: application/octet-stream\r\n\r\n")
98
+ parts << StreamPart.new(value, File.size(filepath))
99
+ parts << StringPart.new("\r\n")
100
+ else
101
+ parts << StringPart.new( "--" + boundary + "\r\n" +
102
+ "Content-Disposition: form-data; name=\"" + key.to_s + "\"\r\n\r\n")
103
+ parts << StringPart.new(value.to_s + "\r\n")
104
+ end
105
+ end
106
+ parts << StringPart.new("--" + boundary + "--\r\n")
107
+ end
108
+
109
+ body_stream = MultipartStream.new(parts)
110
+
111
+ timestamp = Time.now.utc.iso8601
112
+
113
+ url = URI.parse(to_url)
114
+
115
+ Chef::Log.logger.debug("Signing: method: #{http_verb}, url: #{url}, file: #{content_file}, User-id: #{user_id}, Timestamp: #{timestamp}")
116
+
117
+ # We use the body for signing the request if the file parameter
118
+ # wasn't a valid file or wasn't included. Extract the body (with
119
+ # multi-part delimiters intact) to sign the request.
120
+ # TODO: tim: 2009-12-28: It'd be nice to remove this special case, and
121
+ # always hash the entire request body. In the file case it would just be
122
+ # expanded multipart text - the entire body of the POST.
123
+ content_body = parts.inject("") { |result, part| result + part.read(0, part.size) }
124
+ content_file.rewind if content_file # we consumed the file for the above operation, so rewind it.
125
+
126
+ signing_options = {
127
+ http_method: http_verb,
128
+ path: url.path,
129
+ user_id: user_id,
130
+ timestamp: timestamp }
131
+ (content_file && signing_options[:file] = content_file) || (signing_options[:body] = (content_body || ""))
132
+
133
+ headers.merge!(Mixlib::Authentication::SignedHeaderAuth.signing_object(signing_options).sign(secret_key))
134
+
135
+ content_file.rewind if content_file
136
+
137
+ # net/http doesn't like symbols for header keys, so we'll to_s each one just in case
138
+ headers = DefaultHeaders.merge(Hash[*headers.map { |k, v| [k.to_s, v] }.flatten])
139
+
140
+ req = case http_verb
141
+ when :put
142
+ Net::HTTP::Put.new(url.path, headers)
143
+ when :post
144
+ Net::HTTP::Post.new(url.path, headers)
145
+ end
146
+ req.content_length = body_stream.size
147
+ req.content_type = "multipart/form-data; boundary=" + boundary unless parts.empty?
148
+ req.body_stream = body_stream
149
+
150
+ http = Chef::HTTP::BasicClient.new(url).http_client
151
+ res = http.request(req)
152
+
153
+ # alias status to code and to_s to body for test purposes
154
+ # TODO: stop the following madness!
155
+ class << res
156
+ alias :to_s :body
157
+
158
+ # BUG this makes the response compatible with what response_steps expects to test headers (response.headers[] -> response[])
159
+ def headers # rubocop:disable Lint/NestedMethodDefinition
160
+ self
161
+ end
162
+
163
+ def status # rubocop:disable Lint/NestedMethodDefinition
164
+ code.to_i
165
+ end
166
+ end
167
+ res
168
+ end
169
+
170
+ end
171
+
172
+ class StreamPart
173
+ def initialize(stream, size)
174
+ @stream, @size = stream, size
175
+ end
176
+
177
+ def size
178
+ @size
179
+ end
180
+
181
+ # read the specified amount from the stream
182
+ def read(offset, how_much)
183
+ @stream.read(how_much)
184
+ end
185
+ end
186
+
187
+ class StringPart
188
+ def initialize(str)
189
+ @str = str
190
+ end
191
+
192
+ def size
193
+ @str.length
194
+ end
195
+
196
+ # read the specified amount from the string starting at the offset
197
+ def read(offset, how_much)
198
+ @str[offset, how_much]
199
+ end
200
+ end
201
+
202
+ class MultipartStream
203
+ def initialize(parts)
204
+ @parts = parts
205
+ @part_no = 0
206
+ @part_offset = 0
207
+ end
208
+
209
+ def size
210
+ @parts.inject(0) { |size, part| size + part.size }
211
+ end
212
+
213
+ def read(how_much, dst_buf = nil)
214
+ if @part_no >= @parts.size
215
+ dst_buf.replace("") if dst_buf
216
+ return dst_buf
217
+ end
218
+
219
+ how_much_current_part = @parts[@part_no].size - @part_offset
220
+
221
+ how_much_current_part = if how_much_current_part > how_much
222
+ how_much
223
+ else
224
+ how_much_current_part
225
+ end
226
+
227
+ how_much_next_part = how_much - how_much_current_part
228
+
229
+ current_part = @parts[@part_no].read(@part_offset, how_much_current_part)
230
+
231
+ # recurse into the next part if the current one was not large enough
232
+ if how_much_next_part > 0
233
+ @part_no += 1
234
+ @part_offset = 0
235
+ next_part = read(how_much_next_part)
236
+ result = current_part + (next_part || "")
237
+ else
238
+ @part_offset += how_much_current_part
239
+ result = current_part
240
+ end
241
+ dst_buf ? dst_buf.replace(result || "") : result
242
+ end
243
+ end
244
+
245
+ end
246
+ end
247
+ end
248
+ end
249
+
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dpl/helper/memoize'
4
+
5
+ module Dpl
6
+ module Env
7
+ def self.included(base)
8
+ base.extend(ClassMethods)
9
+ end
10
+
11
+ class Env
12
+ include Memoize
13
+ # opts[:allow_skip_underscore] allows unconventional ENV vars such as GOOGLECLOUDKEYFILE
14
+
15
+ attr_reader :cmd, :env, :strs, :keys, :opts
16
+
17
+ def initialize(env, args)
18
+ @env = env
19
+ @opts = args.last.is_a?(Hash) ? args.pop : {}
20
+ @strs = args.map(&:to_s).map(&:upcase)
21
+ end
22
+
23
+ def env(cmd)
24
+ @cmd = cmd
25
+ env = @env.select { |key, _| keys.include?(key) }
26
+ env = env.transform_keys { |key| unprefix(key).downcase.to_sym }
27
+ env.transform_keys { |key| dealias(key) }
28
+ end
29
+
30
+ def description(cmd)
31
+ strs = self.strs.map { |str| "#{str}_" }
32
+ strs += self.strs if opts[:allow_skip_underscore]
33
+ strs = strs.size > 1 ? "[#{strs.sort.join('|')}]" : strs.join
34
+ "Options can be given via env vars if prefixed with `#{strs}`. #{example(cmd)}"
35
+ end
36
+
37
+ def example(cmd)
38
+ return unless opt = cmd.opts.detect { |option| option.secret? }
39
+
40
+ env = strs.map { |str| "`#{str}_#{opt.name.upcase}=<#{opt.name}>`" }
41
+ env += strs.map { |str| "`#{str}#{opt.name.upcase}=<#{opt.name}>`" } if opts[:allow_skip_underscore]
42
+ "E.g. the option `--#{opt.name}` can be given as #{sentence(env)}."
43
+ end
44
+
45
+ def sentence(strs)
46
+ return strs.join if strs.size == 1
47
+
48
+ [strs[0..-2].join(', '), strs[-1]].join(' or ')
49
+ end
50
+
51
+ private
52
+
53
+ def dealias(key)
54
+ opt = cmd.opts.detect { |option| option.aliases.include?(key) }
55
+ opt ? opt.name : key
56
+ end
57
+
58
+ def unprefix(key)
59
+ strs.inject(key) { |key, str| key.sub(/^#{str}_?/, '') }
60
+ end
61
+
62
+ def keys
63
+ keys = cmd.opts.map(&:name) + cmd.opts.map(&:aliases).flatten
64
+ strs.map { |str| keys.map { |key| keys_for(str, key) } }.flatten
65
+ end
66
+ memoize :keys
67
+
68
+ def keys_for(str, key)
69
+ keys = [["#{str}_", key.upcase].join]
70
+ keys << [str, key.upcase].join if opts[:allow_skip_underscore]
71
+ keys
72
+ end
73
+ end
74
+
75
+ # should this sit in Cl?
76
+ module ClassMethods
77
+ def env(*strs)
78
+ if strs.any?
79
+ @env = Env.new(ENV, strs)
80
+ elsif env = @env || superclass.instance_variable_get(:@env)
81
+ env.env(self)
82
+ else
83
+ {}
84
+ end
85
+ end
86
+ end
87
+
88
+ def opts
89
+ @opts ||= self.class.env.merge(super)
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dpl/helper/transliterate'
4
+
5
+ # I18n.load_path << File.expand_path('config/transliterate.yml')
6
+ # I18n.eager_load!
7
+ # I18n.config.available_locales_set << :en # seems really wrong, but ¯\_(ツ)_/¯
8
+
9
+ module Dpl
10
+ module Github
11
+ include Transliterate
12
+
13
+ def normalize_filename(str)
14
+ str = File.basename(str)
15
+ str = str.split(' ').first
16
+ str = transliterate(str)
17
+ str.gsub(/[^\w@+\-_]/, '.')
18
+ end
19
+
20
+ extend self
21
+ end
22
+ end
@@ -0,0 +1,160 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'uri'
4
+
5
+ module Dpl
6
+ module Interpolate
7
+ # Interpolates variables in the given string.
8
+ #
9
+ # Variables can be contained in scripts, shell commands, and messages.
10
+ # They have the syntax `%{name}` or `%s` (or any other identifier supported
11
+ # by [Kernel#sprintf](https://ruby-doc.org/core-2.6.3/Kernel.html#method-i-format)).
12
+ #
13
+ # This supports two styles of interpolation:
14
+ #
15
+ # * Named variables `%{name}` and
16
+ # * Positional variables.
17
+ #
18
+ # Named variable names need to match constants on the provider class, or
19
+ # methods on the provider instance, which will be called in order to
20
+ # evaluate the value to be interpolated.
21
+ #
22
+ # Positional variables can be used if no corresponding method exists, e.g.
23
+ # if the value that needs to be interpolated is an argument passed to a
24
+ # local method.
25
+ #
26
+ # For example, using named variables:
27
+ #
28
+ # ```ruby
29
+ # def upload_file
30
+ # interpolate('Uploading file %{file} to %{target}')
31
+ # end
32
+ #
33
+ # def file
34
+ # './file_name'
35
+ # end
36
+ #
37
+ # def target
38
+ # 'target host'
39
+ # end
40
+ # ```
41
+ #
42
+ # Using positional variables:
43
+ #
44
+ # ```ruby
45
+ # def upload_file(file, target)
46
+ # interpolate('Uploading file %s to %s', file, target)
47
+ # end
48
+ # ```
49
+ #
50
+ # Implementors are encouraged to use named variables when possible, but
51
+ # are free to choose according to their needs.
52
+ def interpolate(str, args = [], opts = {})
53
+ args = args.shift if args.is_a?(Array) && args.first.is_a?(Hash)
54
+ Interpolator.new(str, self, args || {}, opts).apply
55
+ end
56
+
57
+ # Interpolation variables as declared by the provider.
58
+ #
59
+ # By default this contains string option names, but additional
60
+ # methods can be added using Provider::Dsl#vars.
61
+ def vars
62
+ self.class.vars
63
+ end
64
+
65
+ # Obfuscates the given string.
66
+ #
67
+ # Replaces all but the first N characters with asterisks, and paddes
68
+ # the string to a standard length of 20 characters. N depends on the
69
+ # length of the original string.
70
+ def obfuscate(str, opts = {})
71
+ return str if opts[:secure] || !str.blacklisted?
72
+
73
+ keep = (str.length / (4.0 + str.length / 5).round).round
74
+ keep = 1 if keep.zero?
75
+ str[0, keep] + '*' * (20 - keep)
76
+ end
77
+
78
+ class Interpolator < Struct.new(:str, :obj, :args, :opts)
79
+ include Interpolate
80
+
81
+ MODIFIER = %i[obfuscate escape quote].freeze
82
+ PATTERN = /%\{(\$?[\w]+)\}/
83
+ ENV_VAR = /^\$[A-Z_]+$/
84
+ UPCASE = /^[A-Z_]+$/
85
+ UNKNOWN = '[unknown variable: %s]'
86
+
87
+ def apply
88
+ str = interpolate(self.str.to_s)
89
+ str = obfuscate(str) unless opts[:secure]
90
+ str = str.gsub(' ', ' ') if str.lines.size == 1
91
+ str
92
+ end
93
+
94
+ def interpolate(str)
95
+ str = str % args if args.is_a?(Array) && args.any?
96
+ @blacklist_result = false
97
+ str = str.to_s.gsub(PATTERN) do
98
+ @blacklist_result = true
99
+ normalize(lookup(::Regexp.last_match(1).to_sym))
100
+ end
101
+ @blacklist_result || (args.is_a?(Array) && args.any? { |arg| arg.is_a?(String) && arg.blacklisted? }) ? str.blacklist : str
102
+ end
103
+
104
+ def obfuscate(str)
105
+ secrets(str).inject(str) do |str, secret|
106
+ secret = secret.dup if secret.frozen?
107
+ secret.blacklist if str.blacklisted?
108
+ str.gsub(secret, super(secret))
109
+ end
110
+ end
111
+
112
+ def secrets(str)
113
+ return [] unless str.is_a?(String) && str.blacklisted?
114
+
115
+ opts = obj.class.opts.select(&:secret?)
116
+ secrets = opts.map { |opt| obj.opts[opt.name] }.compact
117
+ secrets.select { |secret| str.include?(secret) }
118
+ end
119
+
120
+ def normalize(obj)
121
+ obj.is_a?(Array) ? obj.join(' ') : obj.to_s
122
+ end
123
+
124
+ def lookup(key)
125
+ if vars? && !var?(key)
126
+ UNKNOWN % key
127
+ elsif mod = modifier(key)
128
+ key = key.to_s.sub("#{mod}d_", '')
129
+ obj.send(mod, lookup(key))
130
+ elsif key.to_s =~ ENV_VAR
131
+ ENV[key.to_s.sub('$', '')]
132
+ elsif key.to_s =~ UPCASE && obj.class.const_defined?(key)
133
+ obj.class.const_get(key)
134
+ elsif args.is_a?(Hash) && args.key?(key)
135
+ args[key]
136
+ elsif obj.respond_to?(key, true)
137
+ obj.send(key)
138
+ else
139
+ raise KeyError, key
140
+ end
141
+ end
142
+
143
+ def modifier(key)
144
+ MODIFIER.detect { |mod| key.to_s.start_with?("#{mod}d_") }
145
+ end
146
+
147
+ def var?(key)
148
+ vars.include?(key)
149
+ end
150
+
151
+ def vars
152
+ opts[:vars]
153
+ end
154
+
155
+ def vars?
156
+ !!vars
157
+ end
158
+ end
159
+ end
160
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Memoize
4
+ class ArgsError < StandardError; end
5
+
6
+ module ClassMethods
7
+ def memoize(name)
8
+ ivar = :"@#{name.to_s.sub('?', '_predicate')}"
9
+ prepend Module.new {
10
+ define_method(name) do |*args|
11
+ raise ArgsError, 'cannot pass arguments to memoized method %p' % name unless args.empty?
12
+ return instance_variable_get(ivar) if instance_variable_defined?(ivar)
13
+
14
+ instance_variable_set(ivar, super())
15
+ end
16
+ }
17
+ end
18
+ end
19
+
20
+ def self.included(base)
21
+ base.extend(ClassMethods)
22
+ end
23
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Beloved squiggly heredocs did not exist in Ruby 2.1, which we still want to
4
+ # support, so let's give kudos with a method `sq`.
5
+ module Squiggle
6
+ # Outdents each line on a multiline string by the number of leading
7
+ # whitespace characters on the first line.
8
+ #
9
+ # This method exists so we can unindet heredoc strings the same way that
10
+ # Ruby 2.2's squiggly heredocs work, but still support Ruby 2.1 for the
11
+ # time being.
12
+ #
13
+ # For example:
14
+ #
15
+ # str = sq(<<-str)
16
+ # This multiline string will be outdented by two characters,
17
+ # so the extra indentation on this line will be kept,
18
+ # while this line sits on the same level as the first line.
19
+ # str
20
+ def sq(str)
21
+ width = str =~ /( *)\S/ && ::Regexp.last_match(1).size
22
+ str.lines.map { |line| line.gsub(/^ {#{width}}/, '') }.join
23
+ end
24
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dpl
4
+ module Transliterate
5
+ APPROXIMATIONS = YAML.load(File.read(File.expand_path('../../../config/transliterate.yml', __dir__)))
6
+
7
+ def transliterate(string, replacement = '.')
8
+ string.gsub(/[^\x00-\x7f]/u) do |char|
9
+ APPROXIMATIONS[char] || replacement
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Wrap
4
+ module_function
5
+
6
+ def wrap(str, width = 80)
7
+ str.lines.map do |line|
8
+ line.size > width ? line.gsub(/(.{1,#{width}})(\s+|$)/, "\\1\n").strip : line
9
+ end.join("\n")
10
+ end
11
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'tempfile'
4
+
5
+ module Dpl
6
+ class Zip < Struct.new(:src, :dest, :opts)
7
+ ZIP_EXT = %w[.zip .jar].freeze
8
+
9
+ def initialize(*)
10
+ require 'zip'
11
+ super
12
+ end
13
+
14
+ def zip
15
+ if zip_file?
16
+ File.new(src)
17
+ elsif dir?
18
+ zip_dir
19
+ else
20
+ zip_file
21
+ end
22
+ end
23
+
24
+ def zip_dir
25
+ create(Dir.glob(*glob).reject { |path| dir?(path) })
26
+ end
27
+
28
+ def zip_file
29
+ create([src])
30
+ end
31
+
32
+ def create(files)
33
+ ::Zip::File.open(dest, ::Zip::File::CREATE) do |zip|
34
+ files.each do |file|
35
+ zip.add(file.sub("#{src}/", ''), file)
36
+ end
37
+ end
38
+ File.new(dest)
39
+ end
40
+
41
+ def zip_file?
42
+ exts.include?(File.extname(src))
43
+ end
44
+
45
+ def dir?(path = src)
46
+ File.directory?(path)
47
+ end
48
+
49
+ def copy
50
+ FileUtils.cp(src, dest)
51
+ end
52
+
53
+ def glob
54
+ glob = ["#{src}/**/*"]
55
+ glob << File::FNM_DOTMATCH if dot_match?
56
+ glob
57
+ end
58
+
59
+ def dot_match?
60
+ opts[:dot_match]
61
+ end
62
+
63
+ def exts
64
+ opts[:exts] ||= ZIP_EXT
65
+ end
66
+
67
+ def opts
68
+ super || {}
69
+ end
70
+ end
71
+ end