travis_dpl_test 2.0.3.beta.4.ror

Sign up to get free protection for your applications and to get access to all the features.
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