polishgeeks-dev-tools 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +22 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +6 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +193 -0
- data/README.md +112 -0
- data/Rakefile +20 -0
- data/config/haml-lint.yml +74 -0
- data/config/rubocop.yml +35 -0
- data/config/yardopts +7 -0
- data/lib/polishgeeks-dev-tools.rb +43 -0
- data/lib/polishgeeks/dev-tools/command/allowed_extensions.rb +62 -0
- data/lib/polishgeeks/dev-tools/command/base.rb +73 -0
- data/lib/polishgeeks/dev-tools/command/brakeman.rb +46 -0
- data/lib/polishgeeks/dev-tools/command/coverage.rb +43 -0
- data/lib/polishgeeks/dev-tools/command/examples_comparator.rb +75 -0
- data/lib/polishgeeks/dev-tools/command/expires_in.rb +74 -0
- data/lib/polishgeeks/dev-tools/command/haml_lint.rb +28 -0
- data/lib/polishgeeks/dev-tools/command/readme.rb +32 -0
- data/lib/polishgeeks/dev-tools/command/rspec.rb +38 -0
- data/lib/polishgeeks/dev-tools/command/rspec_files_names.rb +58 -0
- data/lib/polishgeeks/dev-tools/command/rspec_files_structure.rb +134 -0
- data/lib/polishgeeks/dev-tools/command/rubocop.rb +50 -0
- data/lib/polishgeeks/dev-tools/command/rubycritic.rb +17 -0
- data/lib/polishgeeks/dev-tools/command/simplecov.rb +32 -0
- data/lib/polishgeeks/dev-tools/command/tasks_files_names.rb +76 -0
- data/lib/polishgeeks/dev-tools/command/yard.rb +35 -0
- data/lib/polishgeeks/dev-tools/command/yml_parser.rb +85 -0
- data/lib/polishgeeks/dev-tools/config.rb +91 -0
- data/lib/polishgeeks/dev-tools/hash.rb +24 -0
- data/lib/polishgeeks/dev-tools/logger.rb +63 -0
- data/lib/polishgeeks/dev-tools/output_storer.rb +17 -0
- data/lib/polishgeeks/dev-tools/runner.rb +27 -0
- data/lib/polishgeeks/dev-tools/shell.rb +16 -0
- data/lib/polishgeeks/dev-tools/tasks/dev-tools.rake +15 -0
- data/lib/polishgeeks/dev-tools/version.rb +8 -0
- data/polishgeeks_dev_tools.gemspec +36 -0
- data/spec/lib/polishgeeks-dev-tools_spec.rb +35 -0
- data/spec/lib/polishgeeks/dev-tools/command/allowed_extensions_spec.rb +66 -0
- data/spec/lib/polishgeeks/dev-tools/command/base_spec.rb +127 -0
- data/spec/lib/polishgeeks/dev-tools/command/brakeman_spec.rb +95 -0
- data/spec/lib/polishgeeks/dev-tools/command/coverage_spec.rb +121 -0
- data/spec/lib/polishgeeks/dev-tools/command/examples_comparator_spec.rb +171 -0
- data/spec/lib/polishgeeks/dev-tools/command/expires_in_spec.rb +69 -0
- data/spec/lib/polishgeeks/dev-tools/command/haml_lint_spec.rb +79 -0
- data/spec/lib/polishgeeks/dev-tools/command/readme_spec.rb +38 -0
- data/spec/lib/polishgeeks/dev-tools/command/rspec_files_names_spec.rb +91 -0
- data/spec/lib/polishgeeks/dev-tools/command/rspec_files_structure_spec.rb +262 -0
- data/spec/lib/polishgeeks/dev-tools/command/rspec_spec.rb +63 -0
- data/spec/lib/polishgeeks/dev-tools/command/rubocop_spec.rb +127 -0
- data/spec/lib/polishgeeks/dev-tools/command/rubycritic_spec.rb +27 -0
- data/spec/lib/polishgeeks/dev-tools/command/simplecov_spec.rb +53 -0
- data/spec/lib/polishgeeks/dev-tools/command/tasks_files_names_spec.rb +108 -0
- data/spec/lib/polishgeeks/dev-tools/command/yard_spec.rb +86 -0
- data/spec/lib/polishgeeks/dev-tools/command/yml_parser_spec.rb +104 -0
- data/spec/lib/polishgeeks/dev-tools/config_spec.rb +78 -0
- data/spec/lib/polishgeeks/dev-tools/hash_spec.rb +37 -0
- data/spec/lib/polishgeeks/dev-tools/logger_spec.rb +162 -0
- data/spec/lib/polishgeeks/dev-tools/output_storer_spec.rb +20 -0
- data/spec/lib/polishgeeks/dev-tools/runner_spec.rb +57 -0
- data/spec/lib/polishgeeks/dev-tools/shell_spec.rb +13 -0
- data/spec/lib/polishgeeks/dev-tools/version_spec.rb +7 -0
- data/spec/spec_helper.rb +28 -0
- metadata +330 -0
data/config/rubocop.yml
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
AlignParameters:
|
2
|
+
Enabled: false
|
3
|
+
ClassLength:
|
4
|
+
CountComments: false
|
5
|
+
Max: 200
|
6
|
+
LineLength:
|
7
|
+
Max: 99
|
8
|
+
MethodLength:
|
9
|
+
CountComments: false
|
10
|
+
Max: 15
|
11
|
+
Metrics/AbcSize:
|
12
|
+
Max: 25
|
13
|
+
AllCops:
|
14
|
+
Exclude:
|
15
|
+
- bin/**/*
|
16
|
+
- db/**/*
|
17
|
+
- .gemspec/**/*
|
18
|
+
- .bundle/**/*
|
19
|
+
- vendor/**/*
|
20
|
+
- config/**/*
|
21
|
+
- script/**/*
|
22
|
+
- !ruby/regexp /old_and_unused\.rb$/
|
23
|
+
- !ruby/regexp /polishgeeks-[^\/]{2,}\.rb/
|
24
|
+
Include:
|
25
|
+
- '**/Rakefile'
|
26
|
+
- config.ru
|
27
|
+
- lib/tasks/**/*.rake
|
28
|
+
- lib/tasks/**/*.rb
|
29
|
+
- lib/capistrano/**/*.rb
|
30
|
+
- lib/capistrano/**/*.cap
|
31
|
+
Style/MultilineOperationIndentation:
|
32
|
+
EnforcedStyle: indented
|
33
|
+
SupportedStyles:
|
34
|
+
- aligned
|
35
|
+
- indented
|
data/config/yardopts
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'yard'
|
3
|
+
require 'pry'
|
4
|
+
require 'fileutils'
|
5
|
+
require 'timecop'
|
6
|
+
require 'faker'
|
7
|
+
require 'ostruct'
|
8
|
+
|
9
|
+
base_path = File.dirname(__FILE__) + '/polishgeeks/dev-tools/*.rb'
|
10
|
+
Dir[base_path].each { |file| require file }
|
11
|
+
|
12
|
+
module PolishGeeks
|
13
|
+
module DevTools
|
14
|
+
# This is just an alias so we can use it from DevTools directly
|
15
|
+
# @return [PolishGeeks::DevTools::Config.config]
|
16
|
+
def self.config
|
17
|
+
Config.config
|
18
|
+
end
|
19
|
+
|
20
|
+
# @return [String] root path of this gem
|
21
|
+
def self.gem_root
|
22
|
+
File.expand_path('../..', __FILE__)
|
23
|
+
end
|
24
|
+
|
25
|
+
# @return [String] app root path
|
26
|
+
def self.app_root
|
27
|
+
File.dirname(ENV['BUNDLE_GEMFILE'])
|
28
|
+
end
|
29
|
+
|
30
|
+
# Sets up the whole configuration
|
31
|
+
# @param [Block] block
|
32
|
+
def self.setup(&block)
|
33
|
+
Config.setup(&block)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
require 'polishgeeks/dev-tools/command/base'
|
39
|
+
|
40
|
+
commands_path = File.dirname(__FILE__) + '/polishgeeks/dev-tools/command/*.rb'
|
41
|
+
Dir[commands_path].each { |file| require file }
|
42
|
+
|
43
|
+
load 'polishgeeks/dev-tools/tasks/dev-tools.rake'
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module PolishGeeks
|
2
|
+
module DevTools
|
3
|
+
module Command
|
4
|
+
# Checking config directory that all files are allowed
|
5
|
+
class AllowedExtensions < Base
|
6
|
+
self.type = :validator
|
7
|
+
|
8
|
+
# List of allowed extensions of files
|
9
|
+
ALLOWED_EXTENSIONS = %w(
|
10
|
+
rb
|
11
|
+
yml
|
12
|
+
rb.example
|
13
|
+
yml.example
|
14
|
+
)
|
15
|
+
|
16
|
+
# Executes this command
|
17
|
+
# @return [Array] command output array with list of
|
18
|
+
# not allowed files in config directory
|
19
|
+
def execute
|
20
|
+
results = Dir[config_path]
|
21
|
+
|
22
|
+
@output = results
|
23
|
+
.flatten
|
24
|
+
.map { |line| line.gsub!(PolishGeeks::DevTools.app_root + '/config/', '') }
|
25
|
+
.uniq
|
26
|
+
@output.delete_if do |line|
|
27
|
+
ALLOWED_EXTENSIONS.any? { |allow| line =~ /^.*\.#{allow}$/i }
|
28
|
+
end
|
29
|
+
|
30
|
+
@output
|
31
|
+
end
|
32
|
+
|
33
|
+
# @return [String] label with this validator description
|
34
|
+
def label
|
35
|
+
'Allowed Extensions'
|
36
|
+
end
|
37
|
+
|
38
|
+
# @return [String] error message that will be displayed if something
|
39
|
+
# goes wrong
|
40
|
+
def error_message
|
41
|
+
err = 'Following files are not allowed in config directory:'
|
42
|
+
err << "\n\n"
|
43
|
+
err << @output.join("\n")
|
44
|
+
err << "\n"
|
45
|
+
end
|
46
|
+
|
47
|
+
# @return [Boolean] true if all files in config directory
|
48
|
+
# have correct extension
|
49
|
+
def valid?
|
50
|
+
@output.empty?
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
# @return [String] config path with all files included
|
56
|
+
def config_path
|
57
|
+
"#{File.expand_path(PolishGeeks::DevTools.app_root + '/config')}/*.*"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module PolishGeeks
|
2
|
+
module DevTools
|
3
|
+
# Module encapsulating all the commands that we use to check/verify code
|
4
|
+
module Command
|
5
|
+
# Base class for all the commands
|
6
|
+
# @abstract Subclass and use
|
7
|
+
class Base
|
8
|
+
# Raised when we specify a framework in which a given command can be executed
|
9
|
+
# and it is not present (not detected)
|
10
|
+
class MissingFramework < StandardError; end
|
11
|
+
|
12
|
+
attr_reader :output
|
13
|
+
# stored_output [PolishGeeks::DevTools::OutputStorer] storer with results of previous
|
14
|
+
# commands (they might use output from previous/other commands)
|
15
|
+
attr_accessor :stored_output
|
16
|
+
|
17
|
+
# Available command types. We have validators that check something
|
18
|
+
# and that should have a 'valid?' method and that check for errors, etc
|
19
|
+
# and generators that are executed to generate some stats, docs, etc
|
20
|
+
TYPES = %i( validator generator )
|
21
|
+
|
22
|
+
class << self
|
23
|
+
attr_accessor :type
|
24
|
+
attr_accessor :framework
|
25
|
+
|
26
|
+
TYPES.each do |type|
|
27
|
+
# @return [Boolean] if it is a given type command
|
28
|
+
define_method :"#{type}?" do
|
29
|
+
self.type == type
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# When we will try to use a given command, we need to check if it requires
|
35
|
+
# a given framework (Rails, Sinatra), if so, then we need to check if it is
|
36
|
+
# present, because without it a given command cannot run
|
37
|
+
def initialize
|
38
|
+
ensure_framework_if_required
|
39
|
+
end
|
40
|
+
|
41
|
+
# @raise [NotImplementedError] this should be implemented in a subclass
|
42
|
+
def execute
|
43
|
+
fail NotImplementedError
|
44
|
+
end
|
45
|
+
|
46
|
+
# @raise [NotImplementedError] this should be implemented in a subclass
|
47
|
+
# if it is a validator type (or no implementation required when
|
48
|
+
# it is a validator)
|
49
|
+
def valid?
|
50
|
+
fail NotImplementedError
|
51
|
+
end
|
52
|
+
|
53
|
+
# @return [String] what message should be printed when error occures
|
54
|
+
# @note By default the whole output of an executed command will be printed
|
55
|
+
def error_message
|
56
|
+
output
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
# Checks if a framework is required for a given command, and if so, it wont
|
62
|
+
# allow to execute this command if it is not present
|
63
|
+
# @raise [PolishGeeks::DevTools::Command::Base::MissingFramework] if req framework missing
|
64
|
+
def ensure_framework_if_required
|
65
|
+
return unless self.class.framework
|
66
|
+
return if PolishGeeks::DevTools.config.public_send(:"#{self.class.framework}?")
|
67
|
+
|
68
|
+
fail MissingFramework, self.class.framework
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module PolishGeeks
|
2
|
+
module DevTools
|
3
|
+
module Command
|
4
|
+
# A static analysis security vulnerability scanner for Ruby on Rails applications
|
5
|
+
# @see https://github.com/presidentbeef/brakeman
|
6
|
+
class Brakeman < Base
|
7
|
+
self.type = :validator
|
8
|
+
self.framework = :rails
|
9
|
+
|
10
|
+
# Regexps to get some stat info from brakeman output
|
11
|
+
REGEXPS = {
|
12
|
+
controllers: /Controller.* (\d+)/,
|
13
|
+
models: /Model.* (\d+)/,
|
14
|
+
templates: /Template.* (\d+)/,
|
15
|
+
errors: /Error.* (\d+)/,
|
16
|
+
warnings: /Warning.* (\d+)/
|
17
|
+
}
|
18
|
+
|
19
|
+
# Executes this command
|
20
|
+
# @return [String] command output
|
21
|
+
def execute
|
22
|
+
@output = Shell.new.execute('bundle exec brakeman -q')
|
23
|
+
end
|
24
|
+
|
25
|
+
# @return [Boolean] true if we didn't have any vulnerabilities detected
|
26
|
+
def valid?
|
27
|
+
warnings == 0 && errors == 0
|
28
|
+
end
|
29
|
+
|
30
|
+
# @return [String] label with details bout brakeman scan
|
31
|
+
def label
|
32
|
+
"Brakeman (#{controllers} con, #{models} mod, #{templates} temp)"
|
33
|
+
end
|
34
|
+
|
35
|
+
REGEXPS.each do |name, regexp|
|
36
|
+
# @return [Integer] number of matches for given regexp
|
37
|
+
define_method(name) do
|
38
|
+
output.scan(regexp).flatten.first.to_i
|
39
|
+
end
|
40
|
+
|
41
|
+
private name
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module PolishGeeks
|
2
|
+
module DevTools
|
3
|
+
module Command
|
4
|
+
# Command wrapper for Simple code coverage analysing
|
5
|
+
# It informs us if we didn't reach a proper code coverage level
|
6
|
+
class Coverage < Base
|
7
|
+
self.type = :validator
|
8
|
+
|
9
|
+
# Regexp used to match code coverage level
|
10
|
+
MATCH_REGEXP = /\(\d+.\d+\%\) covered/
|
11
|
+
# Regexp used to match float number from coverage
|
12
|
+
NUMBER_REGEXP = /(\d+[.]\d+)/
|
13
|
+
|
14
|
+
# @return [Float] code coverage level
|
15
|
+
def to_f
|
16
|
+
output[*NUMBER_REGEXP].to_f
|
17
|
+
end
|
18
|
+
|
19
|
+
# Executes this command
|
20
|
+
# @return [String] command output
|
21
|
+
def execute
|
22
|
+
@output = stored_output.rspec[*MATCH_REGEXP]
|
23
|
+
end
|
24
|
+
|
25
|
+
# @return [Boolean] true if code coverage level is higher or equal to expected
|
26
|
+
def valid?
|
27
|
+
to_f >= DevTools.config.simplecov_threshold
|
28
|
+
end
|
29
|
+
|
30
|
+
# @return [String] default label for this command
|
31
|
+
def label
|
32
|
+
"Coverage #{to_f}% covered - #{DevTools.config.simplecov_threshold}% required"
|
33
|
+
end
|
34
|
+
|
35
|
+
# @return [String] message that should be printed when code coverage level is not met
|
36
|
+
def error_message
|
37
|
+
threshold = DevTools.config.simplecov_threshold
|
38
|
+
"Coverage level should more or equal to #{threshold}%. was: #{to_f}"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module PolishGeeks
|
2
|
+
module DevTools
|
3
|
+
module Command
|
4
|
+
# Command wrapper for ExamplesComparator rake task
|
5
|
+
# It informs us if our .example files structure is the same as non-example once
|
6
|
+
class ExamplesComparator < Base
|
7
|
+
self.type = :validator
|
8
|
+
|
9
|
+
# Executes this command
|
10
|
+
# @return [String] command output
|
11
|
+
def execute
|
12
|
+
@output = "Comparing yaml structure of example files\n\n"
|
13
|
+
|
14
|
+
Dir[config_path].each do |example_file|
|
15
|
+
dedicated_file = example_file.gsub('.example', '')
|
16
|
+
|
17
|
+
header = compare_header(example_file, dedicated_file)
|
18
|
+
|
19
|
+
if same_key_structure?(example_file, dedicated_file)
|
20
|
+
@output << successful_compare(header)
|
21
|
+
else
|
22
|
+
@output << failed_compare(header)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# @return [Boolean] true if all the example files have the same structure
|
28
|
+
# as non-example once, false otherwise
|
29
|
+
def valid?
|
30
|
+
!output.include?('failed')
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
# @return [String] config path with all the yml.example files included
|
36
|
+
# @note This method is used in Dir[] to get all the example files
|
37
|
+
def config_path
|
38
|
+
"#{File.expand_path(PolishGeeks::DevTools.app_root + '/config')}/**/*.yml.example"
|
39
|
+
end
|
40
|
+
|
41
|
+
# @param [File] example_file which we compare with dedicated one
|
42
|
+
# @param [File] dedicated_file which is compared with example one
|
43
|
+
# @return [Boolean] true if the key structure is the same in both files
|
44
|
+
# otherwise false
|
45
|
+
def same_key_structure?(example_file, dedicated_file)
|
46
|
+
yaml1 = PolishGeeks::DevTools::Hash.new
|
47
|
+
yaml1.merge!(YAML.load_file(example_file))
|
48
|
+
yaml2 = PolishGeeks::DevTools::Hash.new
|
49
|
+
yaml2.merge!(YAML.load_file(dedicated_file))
|
50
|
+
|
51
|
+
yaml1.same_key_structure?(yaml2)
|
52
|
+
end
|
53
|
+
|
54
|
+
# @param [File] example_file which we compare with dedicated one
|
55
|
+
# @param [File] dedicated_file which is compared with example one
|
56
|
+
# @return [String] success/failure (both) message header
|
57
|
+
def compare_header(example_file, dedicated_file)
|
58
|
+
"#{File.basename(example_file)} and #{File.basename(dedicated_file)}"
|
59
|
+
end
|
60
|
+
|
61
|
+
# @param [String] compare_header output message
|
62
|
+
# @return [String] success message for single file
|
63
|
+
def successful_compare(compare_header)
|
64
|
+
"\e[32m success\e[0m - #{compare_header}\n"
|
65
|
+
end
|
66
|
+
|
67
|
+
# @param [String] compare_header output message
|
68
|
+
# @return [String] failed message for single file
|
69
|
+
def failed_compare(compare_header)
|
70
|
+
"\e[31m failed\e[0m - #{compare_header} - structure not equal\n"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module PolishGeeks
|
2
|
+
module DevTools
|
3
|
+
module Command
|
4
|
+
# Checking Rails cache options especially expires_in
|
5
|
+
class ExpiresIn < Base
|
6
|
+
self.type = :validator
|
7
|
+
|
8
|
+
# List of dirs that we will check for
|
9
|
+
CHECKED_DIRS = %w(
|
10
|
+
app
|
11
|
+
lib
|
12
|
+
)
|
13
|
+
|
14
|
+
# Regexp that we want to use to catch invalid things that occur
|
15
|
+
# instead of expires_in
|
16
|
+
CHECKED_REGEXP = 'expire_in\|expir_in'
|
17
|
+
|
18
|
+
# Executes this command
|
19
|
+
def execute
|
20
|
+
results = CHECKED_DIRS.map do |directory|
|
21
|
+
path = File.join(PolishGeeks::DevTools.app_root, directory)
|
22
|
+
shell_command(path).split("\n")
|
23
|
+
end
|
24
|
+
|
25
|
+
@output = results
|
26
|
+
.flatten
|
27
|
+
.map { |line| line.split(':').first }
|
28
|
+
.map { |line| line.gsub!(PolishGeeks::DevTools.app_root, '') }
|
29
|
+
.uniq
|
30
|
+
|
31
|
+
@output.delete_if do |line|
|
32
|
+
excludes.any? { |exclude| line =~ /#{exclude}/ }
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# @return [String] label with this validator description
|
37
|
+
def label
|
38
|
+
'Expires in'
|
39
|
+
end
|
40
|
+
|
41
|
+
# @return [String] error message
|
42
|
+
def error_message
|
43
|
+
err = 'Following files use expire_in instead of expires_in:'
|
44
|
+
err << "\n\n"
|
45
|
+
err << @output.join("\n")
|
46
|
+
err << "\n"
|
47
|
+
end
|
48
|
+
|
49
|
+
# @return [Boolean] true if not find expire_in
|
50
|
+
def valid?
|
51
|
+
@output.empty?
|
52
|
+
end
|
53
|
+
|
54
|
+
# @return [Array<String>] list of files/directories that should be excluded from checking
|
55
|
+
# @note This should be set in the initializer for this gem in the
|
56
|
+
# place where it is going to be used
|
57
|
+
def excludes
|
58
|
+
DevTools.config.expires_in_files_ignored || []
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
# @param [String] path in which we want to search
|
64
|
+
# @return [String] grep command that should be executed
|
65
|
+
# to find what we search for
|
66
|
+
# @note Not every path must exist, thats why we redirect errors to /dev/null, that way
|
67
|
+
# we can skip all the errors
|
68
|
+
def shell_command(path)
|
69
|
+
`grep -R \"#{CHECKED_REGEXP}\" #{path}/* 2>/dev/null`
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|