crosstest-core 0.0.1
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.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.rubocop.yml +1 -0
- data/.rubocop_todo.yml +30 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +4 -0
- data/Rakefile +12 -0
- data/crosstest-core.gemspec +29 -0
- data/lib/crosstest/core.rb +16 -0
- data/lib/crosstest/core/cli.rb +41 -0
- data/lib/crosstest/core/color.rb +42 -0
- data/lib/crosstest/core/file_system.rb +60 -0
- data/lib/crosstest/core/logger.rb +19 -0
- data/lib/crosstest/core/logging.rb +125 -0
- data/lib/crosstest/core/util.rb +204 -0
- data/lib/crosstest/core/version.rb +5 -0
- metadata +192 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: c0116950bbc38ceb7c21d1a532ea1816de54f29e
|
4
|
+
data.tar.gz: 353480f78e749159b8d3f337486650a935ef34b5
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 03837f8ea547880d2cb8d84c780670dac0dc34d708d35287c358bd6e7d50f676ee64e16524c390ef5ab7f3803752e47f9e54c4706713903cae75c4ff49a3179d
|
7
|
+
data.tar.gz: 97dffaf921fac6dcb26a31700a66c71ab8dc1f95dd18d6eb8ea7aa9b8bfb3c890c66cf8d9e48f2a563f5decffae5d5dc34654cca1609047b7a33a417094c4cbe
|
data/.gitignore
ADDED
data/.rubocop.yml
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
inherit_from: .rubocop_todo.yml
|
data/.rubocop_todo.yml
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# This configuration was generated by `rubocop --auto-gen-config`
|
2
|
+
# on 2015-01-20 16:57:55 -0500 using RuboCop version 0.27.0.
|
3
|
+
# The point is for the user to remove these configuration records
|
4
|
+
# one by one as the offenses are removed from the code base.
|
5
|
+
# Note that changes in the inspected code, or installation of new
|
6
|
+
# versions of RuboCop, may require this file to be generated again.
|
7
|
+
|
8
|
+
# Offense count: 1
|
9
|
+
Metrics/AbcSize:
|
10
|
+
Max: 18
|
11
|
+
|
12
|
+
# Offense count: 10
|
13
|
+
# Configuration parameters: AllowURI, URISchemes.
|
14
|
+
Metrics/LineLength:
|
15
|
+
Max: 105
|
16
|
+
|
17
|
+
# Offense count: 4
|
18
|
+
# Configuration parameters: CountComments.
|
19
|
+
Metrics/MethodLength:
|
20
|
+
Max: 13
|
21
|
+
|
22
|
+
# Offense count: 14
|
23
|
+
Style/Documentation:
|
24
|
+
Enabled: false
|
25
|
+
|
26
|
+
# Offense count: 2
|
27
|
+
# Configuration parameters: MaxSlashes.
|
28
|
+
Style/RegexpLiteral:
|
29
|
+
Enabled: false
|
30
|
+
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2015 Max Lincoln
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
require 'rubocop/rake_task'
|
3
|
+
require 'rake/notes/rake_task'
|
4
|
+
require 'rspec/core/rake_task'
|
5
|
+
|
6
|
+
task default: [:spec, :rubocop, :notes]
|
7
|
+
|
8
|
+
RSpec::Core::RakeTask.new('spec')
|
9
|
+
RuboCop::RakeTask.new(:rubocop) do |task|
|
10
|
+
# abort rake on failure
|
11
|
+
task.fail_on_error = true
|
12
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'crosstest/core/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'crosstest-core'
|
8
|
+
spec.version = Crosstest::Core::VERSION
|
9
|
+
spec.authors = ['Max Lincoln']
|
10
|
+
spec.email = ['max@devopsy.com']
|
11
|
+
spec.summary = 'Shared code for crosstest projects.'
|
12
|
+
spec.homepage = ''
|
13
|
+
spec.license = 'MIT'
|
14
|
+
|
15
|
+
spec.files = `git ls-files -z`.split("\x0")
|
16
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
17
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
18
|
+
spec.require_paths = ['lib']
|
19
|
+
|
20
|
+
spec.add_dependency 'thor', '~> 0.19'
|
21
|
+
spec.add_development_dependency 'bundler', '~> 1.5'
|
22
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
23
|
+
spec.add_development_dependency 'rake-notes'
|
24
|
+
spec.add_development_dependency 'simplecov'
|
25
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
26
|
+
spec.add_development_dependency 'rubocop', '~> 0.18', '<= 0.27'
|
27
|
+
spec.add_development_dependency 'rubocop-rspec', '~> 1.2'
|
28
|
+
spec.add_development_dependency 'aruba'
|
29
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'crosstest/core/version'
|
2
|
+
require 'logger'
|
3
|
+
|
4
|
+
module Crosstest
|
5
|
+
module Core
|
6
|
+
autoload :Util, 'crosstest/core/util'
|
7
|
+
autoload :FileSystem, 'crosstest/core/file_system'
|
8
|
+
autoload :CLI, 'crosstest/core/cli'
|
9
|
+
autoload :Logger, 'crosstest/core/logger'
|
10
|
+
autoload :Logging, 'crosstest/core/logging'
|
11
|
+
autoload :DefaultLogger, 'crosstest/core/logging'
|
12
|
+
autoload :StdoutLogger, 'crosstest/core/logging'
|
13
|
+
autoload :LogdevLogger, 'crosstest/core/logging'
|
14
|
+
autoload :Color, 'crosstest/core/color'
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'English'
|
2
|
+
require 'thor'
|
3
|
+
|
4
|
+
module Crosstest
|
5
|
+
module Core
|
6
|
+
class CLI < Thor
|
7
|
+
BUILT_IN_TASKS = %w(bootstrap)
|
8
|
+
|
9
|
+
class << self
|
10
|
+
# Override Thor's start to strip extra_args from ARGV before it's processed
|
11
|
+
attr_accessor :extra_args
|
12
|
+
|
13
|
+
def start(given_args = ARGV, config = {})
|
14
|
+
if given_args && (split_pos = given_args.index('--'))
|
15
|
+
@extra_args = given_args.slice(split_pos + 1, given_args.length).map do | arg |
|
16
|
+
# Restore quotes
|
17
|
+
arg.match(/\=/) ? restore_quotes(arg) : arg
|
18
|
+
end
|
19
|
+
given_args = given_args.slice(0, split_pos)
|
20
|
+
end
|
21
|
+
super given_args, config
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def restore_quotes(arg)
|
27
|
+
lhs, rhs = arg.split('=')
|
28
|
+
lhs = "\"#{lhs}\"" if lhs.match(/\s/)
|
29
|
+
rhs = "\"#{rhs}\"" if rhs.match(/\s/)
|
30
|
+
[lhs, rhs].join('=')
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
no_commands do
|
35
|
+
def extra_args
|
36
|
+
self.class.extra_args
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Crosstest
|
2
|
+
module Core
|
3
|
+
module Color
|
4
|
+
ANSI = {
|
5
|
+
reset: 0, black: 30, red: 31, green: 32, yellow: 33,
|
6
|
+
blue: 34, magenta: 35, cyan: 36, white: 37,
|
7
|
+
bright_black: 90, bright_red: 91, bright_green: 92,
|
8
|
+
bright_yellow: 93, bright_blue: 94, bright_magenta: 95,
|
9
|
+
bright_cyan: 96, bright_white: 97
|
10
|
+
}.freeze
|
11
|
+
|
12
|
+
COLORS = %w(
|
13
|
+
cyan yellow green magenta blue bright_cyan bright_yellow
|
14
|
+
bright_green bright_magenta bright_blue
|
15
|
+
).freeze
|
16
|
+
|
17
|
+
# Returns an ansi escaped string representing a color control sequence.
|
18
|
+
#
|
19
|
+
# @param name [Symbol] a valid color representation, taken from
|
20
|
+
# Crosstest::Color::ANSI
|
21
|
+
# @return [String] an ansi escaped string if the color is valid and an
|
22
|
+
# empty string otherwise
|
23
|
+
def self.escape(name)
|
24
|
+
return '' if name.nil?
|
25
|
+
return '' unless ANSI[name]
|
26
|
+
"\e[#{ANSI[name]}m"
|
27
|
+
end
|
28
|
+
|
29
|
+
# Returns a colorized ansi escaped string with the given color.
|
30
|
+
#
|
31
|
+
# @param str [String] a string to colorize
|
32
|
+
# @param name [Symbol] a valid color representation, taken from
|
33
|
+
# Crosstest::Color::ANSI
|
34
|
+
# @return [String] an ansi escaped string if the color is valid and an
|
35
|
+
# unescaped string otherwise
|
36
|
+
def self.colorize(str, name)
|
37
|
+
color = escape(name)
|
38
|
+
color.empty? ? str : "#{color}#{str}#{escape(:reset)}"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module Crosstest
|
2
|
+
module Core
|
3
|
+
module FileSystem
|
4
|
+
include Util::String
|
5
|
+
|
6
|
+
class << self
|
7
|
+
# Finds a file by loosely matching the file name to a scenario name
|
8
|
+
def find_file(search_path, scenario_name, ignored_patterns = nil)
|
9
|
+
ignored_patterns ||= read_gitignore(search_path)
|
10
|
+
glob_string = "#{search_path}/**/*#{slugify(scenario_name)}.*"
|
11
|
+
potential_files = Dir.glob(glob_string, File::FNM_CASEFOLD)
|
12
|
+
potential_files.concat Dir.glob(glob_string.gsub('_', '-'), File::FNM_CASEFOLD)
|
13
|
+
potential_files.concat Dir.glob(glob_string.gsub('_', ''), File::FNM_CASEFOLD)
|
14
|
+
|
15
|
+
# Filter out ignored filesFind the first file, not including generated files
|
16
|
+
files = potential_files.select do |f|
|
17
|
+
!ignored? ignored_patterns, search_path, f
|
18
|
+
end
|
19
|
+
|
20
|
+
# Select the shortest path, likely the best match
|
21
|
+
file = files.min_by(&:length)
|
22
|
+
|
23
|
+
fail Errno::ENOENT, "No file was found for #{scenario_name} within #{search_path}" if file.nil?
|
24
|
+
Pathname.new file
|
25
|
+
end
|
26
|
+
|
27
|
+
def relativize(file, base_path)
|
28
|
+
absolute_file = File.absolute_path(file)
|
29
|
+
absolute_base_path = File.absolute_path(base_path)
|
30
|
+
Pathname.new(absolute_file).relative_path_from Pathname.new(absolute_base_path)
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
# @api private
|
36
|
+
def read_gitignore(dir)
|
37
|
+
gitignore_file = "#{dir}/.gitignore"
|
38
|
+
File.read(gitignore_file)
|
39
|
+
rescue
|
40
|
+
''
|
41
|
+
end
|
42
|
+
|
43
|
+
# @api private
|
44
|
+
def ignored?(ignored_patterns, base_path, target_file)
|
45
|
+
# Trying to match the git ignore rules but there's some discrepencies.
|
46
|
+
ignored_patterns.split.find do |pattern|
|
47
|
+
# if git ignores a folder, we should ignore all files it contains
|
48
|
+
pattern = "#{pattern}**" if pattern[-1] == '/'
|
49
|
+
started_with_slash = pattern.start_with? '/'
|
50
|
+
|
51
|
+
pattern.gsub!(/\A\//, '') # remove leading slashes since we're searching from root
|
52
|
+
file = relativize(target_file, base_path)
|
53
|
+
ignored = file.fnmatch? pattern
|
54
|
+
ignored || (file.fnmatch? "**/#{pattern}" unless started_with_slash)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
module Crosstest
|
4
|
+
module Core
|
5
|
+
module Logger
|
6
|
+
def logger
|
7
|
+
@logger ||= new_logger
|
8
|
+
end
|
9
|
+
|
10
|
+
def new_logger(_io = $stdout, _level = :debug)
|
11
|
+
::Logger.new(STDOUT)
|
12
|
+
end
|
13
|
+
|
14
|
+
def log_level=(level)
|
15
|
+
logger.level = level
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
module Crosstest
|
2
|
+
module Core
|
3
|
+
module DefaultLogger
|
4
|
+
module ClassMethods
|
5
|
+
def logger
|
6
|
+
@logger ||= Crosstest.configuration.default_logger
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.included(base)
|
11
|
+
base.extend(ClassMethods)
|
12
|
+
end
|
13
|
+
|
14
|
+
include ClassMethods
|
15
|
+
end
|
16
|
+
|
17
|
+
module Logging
|
18
|
+
class << self
|
19
|
+
private
|
20
|
+
|
21
|
+
def logger_method(meth)
|
22
|
+
define_method(meth) do |*args|
|
23
|
+
logger.public_send(meth, *args)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
logger_method :banner
|
29
|
+
logger_method :debug
|
30
|
+
logger_method :info
|
31
|
+
logger_method :warn
|
32
|
+
logger_method :error
|
33
|
+
logger_method :fatal
|
34
|
+
end
|
35
|
+
|
36
|
+
# Internal class which adds a #banner method call that displays the
|
37
|
+
# message with a callout arrow.
|
38
|
+
class LogdevLogger < ::Logger
|
39
|
+
alias_method :super_info, :info
|
40
|
+
|
41
|
+
# Dump one or more messages to info.
|
42
|
+
#
|
43
|
+
# @param msg [String] a message
|
44
|
+
def <<(msg)
|
45
|
+
@buffer ||= ''
|
46
|
+
lines, _, remainder = msg.rpartition("\n")
|
47
|
+
if lines.empty?
|
48
|
+
@buffer << remainder
|
49
|
+
else
|
50
|
+
lines.insert(0, @buffer)
|
51
|
+
lines.split("\n").each { |l| format_line(l.chomp) }
|
52
|
+
@buffer = ''
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# Log a banner message.
|
57
|
+
#
|
58
|
+
# @param msg [String] a message
|
59
|
+
def banner(msg = nil, &block)
|
60
|
+
super_info("-----> #{msg}", &block)
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
# Reformat a line if it already contains log formatting.
|
66
|
+
#
|
67
|
+
# @param line [String] a message line
|
68
|
+
# @api private
|
69
|
+
def format_line(line)
|
70
|
+
case line
|
71
|
+
when /^-----> / then banner(line.gsub(/^[ >-]{6} /, ''))
|
72
|
+
when /^>>>>>> / then error(line.gsub(/^[ >-]{6} /, ''))
|
73
|
+
when /^ / then info(line.gsub(/^[ >-]{6} /, ''))
|
74
|
+
else info(line)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# Internal class which reformats logging methods for display as console
|
80
|
+
# output.
|
81
|
+
class StdoutLogger < LogdevLogger
|
82
|
+
# Log a debug message
|
83
|
+
#
|
84
|
+
# @param msg [String] a message
|
85
|
+
def debug(msg = nil, &block)
|
86
|
+
super("D #{msg}", &block)
|
87
|
+
end
|
88
|
+
|
89
|
+
# Log an info message
|
90
|
+
#
|
91
|
+
# @param msg [String] a message
|
92
|
+
def info(msg = nil, &block)
|
93
|
+
super(" #{msg}", &block)
|
94
|
+
end
|
95
|
+
|
96
|
+
# Log a warn message
|
97
|
+
#
|
98
|
+
# @param msg [String] a message
|
99
|
+
def warn(msg = nil, &block)
|
100
|
+
super("$$$$$$ #{msg}", &block)
|
101
|
+
end
|
102
|
+
|
103
|
+
# Log an error message
|
104
|
+
#
|
105
|
+
# @param msg [String] a message
|
106
|
+
def error(msg = nil, &block)
|
107
|
+
super(">>>>>> #{msg}", &block)
|
108
|
+
end
|
109
|
+
|
110
|
+
# Log a fatal message
|
111
|
+
#
|
112
|
+
# @param msg [String] a message
|
113
|
+
def fatal(msg = nil, &block)
|
114
|
+
super("!!!!!! #{msg}", &block)
|
115
|
+
end
|
116
|
+
|
117
|
+
# Log an unknown message
|
118
|
+
#
|
119
|
+
# @param msg [String] a message
|
120
|
+
def unknown(msg = nil, &block)
|
121
|
+
super("?????? #{msg}", &block)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
@@ -0,0 +1,204 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
#
|
3
|
+
# Much of this code has been adapted from Fletcher Nichol (<fnichol@nichol.ca>)
|
4
|
+
# work on test-kitchen.
|
5
|
+
|
6
|
+
module Crosstest
|
7
|
+
module Core
|
8
|
+
# Stateless utility methods used in different contexts. Essentially a mini
|
9
|
+
# PassiveSupport library.
|
10
|
+
module Util
|
11
|
+
module Hashable
|
12
|
+
def to_hash
|
13
|
+
instance_variables.each_with_object({}) do |var, hash|
|
14
|
+
hash[var.to_s.delete('@')] = instance_variable_get(var)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# Returns the standard library Logger level constants for a given symbol
|
20
|
+
# representation.
|
21
|
+
#
|
22
|
+
# @param symbol [Symbol] symbol representation of a logger level (:debug,
|
23
|
+
# :info, :warn, :error, :fatal)
|
24
|
+
# @return [Integer] Logger::Severity constant value or nil if input is not
|
25
|
+
# valid
|
26
|
+
def self.to_logger_level(symbol)
|
27
|
+
return nil unless [:debug, :info, :warn, :error, :fatal].include?(symbol)
|
28
|
+
|
29
|
+
::Logger.const_get(symbol.to_s.upcase)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Returns the symbol represenation of a logging levels for a given
|
33
|
+
# standard library Logger::Severity constant.
|
34
|
+
#
|
35
|
+
# @param const [Integer] Logger::Severity constant value for a logging
|
36
|
+
# level (Logger::DEBUG, Logger::INFO, Logger::WARN, Logger::ERROR,
|
37
|
+
# Logger::FATAL)
|
38
|
+
# @return [Symbol] symbol representation of the logging level
|
39
|
+
def self.from_logger_level(const)
|
40
|
+
case const
|
41
|
+
when ::Logger::DEBUG then :debug
|
42
|
+
when ::Logger::INFO then :info
|
43
|
+
when ::Logger::WARN then :warn
|
44
|
+
when ::Logger::ERROR then :error
|
45
|
+
else :fatal
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Returns a new Hash with all key values coerced to symbols. All keys
|
50
|
+
# within a Hash are coerced by calling #to_sym and hashes within arrays
|
51
|
+
# and other hashes are traversed.
|
52
|
+
#
|
53
|
+
# @param obj [Object] the hash to be processed. While intended for
|
54
|
+
# hashes, this method safely processes arbitrary objects
|
55
|
+
# @return [Object] a converted hash with all keys as symbols
|
56
|
+
def self.symbolized_hash(obj)
|
57
|
+
if obj.is_a?(Hash)
|
58
|
+
obj.each_with_object({}) do |(k, v), h|
|
59
|
+
h[k.to_sym] = symbolized_hash(v)
|
60
|
+
end
|
61
|
+
elsif obj.is_a?(Array)
|
62
|
+
obj.each_with_object([]) do |e, a|
|
63
|
+
a << symbolized_hash(e)
|
64
|
+
end
|
65
|
+
else
|
66
|
+
obj
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# Returns a new Hash with all key values coerced to strings. All keys
|
71
|
+
# within a Hash are coerced by calling #to_s and hashes with arrays
|
72
|
+
# and other hashes are traversed.
|
73
|
+
#
|
74
|
+
# @param obj [Object] the hash to be processed. While intended for
|
75
|
+
# hashes, this method safely processes arbitrary objects
|
76
|
+
# @return [Object] a converted hash with all keys as strings
|
77
|
+
def self.stringified_hash(obj)
|
78
|
+
if obj.is_a?(Hash)
|
79
|
+
obj.each_with_object({}) do |(k, v), h|
|
80
|
+
h[k.to_s] = stringified_hash(v)
|
81
|
+
end
|
82
|
+
elsif obj.is_a?(Array)
|
83
|
+
obj.each_with_object([]) do |e, a|
|
84
|
+
a << stringified_hash(e)
|
85
|
+
end
|
86
|
+
else
|
87
|
+
obj
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
# Returns a formatted string representing a duration in seconds.
|
92
|
+
#
|
93
|
+
# @param total [Integer] the total number of seconds
|
94
|
+
# @return [String] a formatted string of the form (XmYY.00s)
|
95
|
+
def self.duration(total)
|
96
|
+
total = 0 if total.nil?
|
97
|
+
minutes = (total / 60).to_i
|
98
|
+
seconds = (total - (minutes * 60))
|
99
|
+
format('(%dm%.2fs)', minutes, seconds)
|
100
|
+
end
|
101
|
+
|
102
|
+
module String
|
103
|
+
module ClassMethods
|
104
|
+
def slugify(*labels)
|
105
|
+
labels.map do |label|
|
106
|
+
label.downcase.gsub(/[\.\s-]/, '_')
|
107
|
+
end.join('-')
|
108
|
+
end
|
109
|
+
|
110
|
+
def ansi2html(text)
|
111
|
+
HTML.from_ansi(text)
|
112
|
+
end
|
113
|
+
|
114
|
+
def escape_html(text)
|
115
|
+
HTML.escape_html(text)
|
116
|
+
end
|
117
|
+
alias_method :h, :escape_html
|
118
|
+
|
119
|
+
def highlight(source, opts = {})
|
120
|
+
return nil if source.nil?
|
121
|
+
|
122
|
+
opts[:language] ||= 'ruby'
|
123
|
+
opts[:formatter] ||= 'terminal256'
|
124
|
+
Highlight.new(opts).highlight(source)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def self.included(base)
|
129
|
+
base.extend(ClassMethods)
|
130
|
+
end
|
131
|
+
|
132
|
+
include ClassMethods
|
133
|
+
end
|
134
|
+
|
135
|
+
class Highlight
|
136
|
+
def initialize(opts)
|
137
|
+
@lexer = Rouge::Lexer.find(opts[:language]) || Rouge::Lexer.guess_by_filename(opts[:filename])
|
138
|
+
@formatter = opts[:formatter]
|
139
|
+
end
|
140
|
+
|
141
|
+
def highlight(source)
|
142
|
+
Rouge.highlight(source, @lexer, @formatter)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
class HTML
|
147
|
+
ANSICODES = {
|
148
|
+
'1' => 'bold',
|
149
|
+
'4' => 'underline',
|
150
|
+
'30' => 'black',
|
151
|
+
'31' => 'red',
|
152
|
+
'32' => 'green',
|
153
|
+
'33' => 'yellow',
|
154
|
+
'34' => 'blue',
|
155
|
+
'35' => 'magenta',
|
156
|
+
'36' => 'cyan',
|
157
|
+
'37' => 'white',
|
158
|
+
'40' => 'bg-black',
|
159
|
+
'41' => 'bg-red',
|
160
|
+
'42' => 'bg-green',
|
161
|
+
'43' => 'bg-yellow',
|
162
|
+
'44' => 'bg-blue',
|
163
|
+
'45' => 'bg-magenta',
|
164
|
+
'46' => 'bg-cyan',
|
165
|
+
'47' => 'bg-white'
|
166
|
+
}
|
167
|
+
|
168
|
+
def self.from_ansi(text)
|
169
|
+
ansi = StringScanner.new(text)
|
170
|
+
html = StringIO.new
|
171
|
+
until ansi.eos?
|
172
|
+
if ansi.scan(/\e\[0?m/)
|
173
|
+
html.print(%(</span>))
|
174
|
+
elsif ansi.scan(/\e\[0?(\d+)m/)
|
175
|
+
# use class instead of style?
|
176
|
+
style = ANSICODES[ansi[1]] || 'text-reset'
|
177
|
+
html.print(%(<span class="#{style}">))
|
178
|
+
else
|
179
|
+
html.print(ansi.scan(/./m))
|
180
|
+
end
|
181
|
+
end
|
182
|
+
html.string
|
183
|
+
end
|
184
|
+
|
185
|
+
# From Rack
|
186
|
+
|
187
|
+
ESCAPE_HTML = {
|
188
|
+
'&' => '&',
|
189
|
+
'<' => '<',
|
190
|
+
'>' => '>',
|
191
|
+
"'" => ''',
|
192
|
+
'"' => '"',
|
193
|
+
'/' => '/'
|
194
|
+
}
|
195
|
+
ESCAPE_HTML_PATTERN = Regexp.union(*ESCAPE_HTML.keys)
|
196
|
+
|
197
|
+
# Escape ampersands, brackets and quotes to their HTML/XML entities.
|
198
|
+
def self.escape_html(string)
|
199
|
+
string.to_s.gsub(ESCAPE_HTML_PATTERN) { |c| ESCAPE_HTML[c] }
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
metadata
ADDED
@@ -0,0 +1,192 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: crosstest-core
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Max Lincoln
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-01-20 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: thor
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.19'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0.19'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.5'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.5'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '10.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '10.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake-notes
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: simplecov
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rspec
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '3.0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '3.0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: rubocop
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0.18'
|
104
|
+
- - "<="
|
105
|
+
- !ruby/object:Gem::Version
|
106
|
+
version: '0.27'
|
107
|
+
type: :development
|
108
|
+
prerelease: false
|
109
|
+
version_requirements: !ruby/object:Gem::Requirement
|
110
|
+
requirements:
|
111
|
+
- - "~>"
|
112
|
+
- !ruby/object:Gem::Version
|
113
|
+
version: '0.18'
|
114
|
+
- - "<="
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
version: '0.27'
|
117
|
+
- !ruby/object:Gem::Dependency
|
118
|
+
name: rubocop-rspec
|
119
|
+
requirement: !ruby/object:Gem::Requirement
|
120
|
+
requirements:
|
121
|
+
- - "~>"
|
122
|
+
- !ruby/object:Gem::Version
|
123
|
+
version: '1.2'
|
124
|
+
type: :development
|
125
|
+
prerelease: false
|
126
|
+
version_requirements: !ruby/object:Gem::Requirement
|
127
|
+
requirements:
|
128
|
+
- - "~>"
|
129
|
+
- !ruby/object:Gem::Version
|
130
|
+
version: '1.2'
|
131
|
+
- !ruby/object:Gem::Dependency
|
132
|
+
name: aruba
|
133
|
+
requirement: !ruby/object:Gem::Requirement
|
134
|
+
requirements:
|
135
|
+
- - ">="
|
136
|
+
- !ruby/object:Gem::Version
|
137
|
+
version: '0'
|
138
|
+
type: :development
|
139
|
+
prerelease: false
|
140
|
+
version_requirements: !ruby/object:Gem::Requirement
|
141
|
+
requirements:
|
142
|
+
- - ">="
|
143
|
+
- !ruby/object:Gem::Version
|
144
|
+
version: '0'
|
145
|
+
description:
|
146
|
+
email:
|
147
|
+
- max@devopsy.com
|
148
|
+
executables: []
|
149
|
+
extensions: []
|
150
|
+
extra_rdoc_files: []
|
151
|
+
files:
|
152
|
+
- ".gitignore"
|
153
|
+
- ".rubocop.yml"
|
154
|
+
- ".rubocop_todo.yml"
|
155
|
+
- Gemfile
|
156
|
+
- LICENSE.txt
|
157
|
+
- README.md
|
158
|
+
- Rakefile
|
159
|
+
- crosstest-core.gemspec
|
160
|
+
- lib/crosstest/core.rb
|
161
|
+
- lib/crosstest/core/cli.rb
|
162
|
+
- lib/crosstest/core/color.rb
|
163
|
+
- lib/crosstest/core/file_system.rb
|
164
|
+
- lib/crosstest/core/logger.rb
|
165
|
+
- lib/crosstest/core/logging.rb
|
166
|
+
- lib/crosstest/core/util.rb
|
167
|
+
- lib/crosstest/core/version.rb
|
168
|
+
homepage: ''
|
169
|
+
licenses:
|
170
|
+
- MIT
|
171
|
+
metadata: {}
|
172
|
+
post_install_message:
|
173
|
+
rdoc_options: []
|
174
|
+
require_paths:
|
175
|
+
- lib
|
176
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
177
|
+
requirements:
|
178
|
+
- - ">="
|
179
|
+
- !ruby/object:Gem::Version
|
180
|
+
version: '0'
|
181
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
182
|
+
requirements:
|
183
|
+
- - ">="
|
184
|
+
- !ruby/object:Gem::Version
|
185
|
+
version: '0'
|
186
|
+
requirements: []
|
187
|
+
rubyforge_project:
|
188
|
+
rubygems_version: 2.4.2
|
189
|
+
signing_key:
|
190
|
+
specification_version: 4
|
191
|
+
summary: Shared code for crosstest projects.
|
192
|
+
test_files: []
|