brutal 1.5.0 → 1.6.0.beta2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ea124053ebeef681c2e046022936f9d67ca97778733a23bd2d81781e90daa9a8
4
- data.tar.gz: 677089454fece7f60aedfc4693b53c132f057bbeeaca5e651f88ec52d026ea34
3
+ metadata.gz: 2a8a912485598e917360c280db92eee2b3acc53180dd0587b2147ea0eb26e649
4
+ data.tar.gz: 5a7a1f13b748e8deb7d5a1529ca2e11eb35b687aabab84f7b9fc9ebc58d0b174
5
5
  SHA512:
6
- metadata.gz: b1d043d4740db1987c188755e9b339da566034a3cc2a43f99ced12fb3de26a93df0f93d669b20700ba662a935d9be662a25bffba8f8ae92aae3e01deade92d9b
7
- data.tar.gz: 5abc20c46849f69b20de5b89848f8fed831b2d92641f57cfa349be4e0a975c8630c27268d16a022911693d403af8262b4f0d1099dcf468879392e9c2d7fe7ec0
6
+ metadata.gz: ec1ffa0f11a17812ff0000bc7e8b4ba8a04c826e37f9ef5e777681312fcb87962020f034b4802b617471894019f9c046598a012eeb6491c6548f0fef233af66d
7
+ data.tar.gz: 363647b4219053d2433fa198aac832b8f8f630e36d7b9282f0c36c34c2a255e5874ee91e481440be6bea2c87bb7763225ddbd04bfbf0204ad949bea98b5fbb01
data/README.md CHANGED
@@ -38,7 +38,7 @@ It is therefore the responsibility of the developer to analyze the generated beh
38
38
  Add this line to your application's Gemfile:
39
39
 
40
40
  ```ruby
41
- gem "brutal"
41
+ gem "brutal", ">= 1.6.0.beta2", require: false
42
42
  ```
43
43
 
44
44
  And then execute:
@@ -50,84 +50,84 @@ bundle install
50
50
  Or install it yourself as:
51
51
 
52
52
  ```sh
53
- gem install brutal
53
+ gem install brutal --pre
54
54
  ```
55
55
 
56
- ## Quick Start
57
-
58
- Just type `brutal` in a Ruby project's folder and watch the magic happen.
59
-
60
56
  ## Usage
61
57
 
62
- __Brutal__ needs a configuration file to know how to write your tests.
63
- Currently, only the YAML format is supported.
64
- This file is by default named `.brutal.yml` and is composed of 4 top-level sections:
58
+ ### YAML manifest
59
+
60
+ __Brutal__ needs configuration files in YAML format to know how to write tests.
61
+ Configuration file names are suffixed by `_brutal.yaml` and composed of 7 top-level sections:
65
62
 
66
- * `header` - Specifies the code to execute before generating the test suite.
63
+ * `header` - Specifies a block of lines to be executed once before all examples.
64
+ * `before` - Specifies a block of lines to be executed before each example.
67
65
  * `subject` - Specifies the template of the code to be declined across contexts.
68
66
  * `contexts` - Specifies a list of variables to populate the subject's template.
69
67
  * `actuals` - Specifies templates to challenge evaluated subjects & get results.
68
+ * `after` - Specifies a block of lines to be executed after each example.
69
+ * `footer` - Specifies a block of lines to be executed once after all examples.
70
70
 
71
- When the configuration file is present, the generation of a test suite can be done with the command:
71
+ ### Command line
72
+
73
+ The `brutal` command comes with several options you can use to customize Brutal's behavior.
74
+
75
+ For a full list of options, run the `brutal` command with the `--help` flag:
72
76
 
73
77
  ```sh
74
- brutal .brutal.yml
78
+ brutal --help
75
79
  ```
76
80
 
77
- or:
81
+ ```txt
82
+ Usage: brutal [options] [files or directories]
78
83
 
79
- ```sh
80
- brutal .
84
+ --format=FORMAT Choose "ruby" (default).
85
+ --help Display this help.
86
+ --version Display the version.
81
87
  ```
82
88
 
83
- or even:
89
+ Assuming that in the workspace there is a configuration file named `user_brutal.yaml`, the test suite can be generated via one of these commands:
84
90
 
85
91
  ```sh
86
- brutal
92
+ brutal user_brutal.yaml
87
93
  ```
88
94
 
89
- This would create a `test.rb` file containing the test suite.
90
-
91
- Configuration files can also be named differently:
95
+ or:
92
96
 
93
97
  ```sh
94
- brutal path/to/test_hello_world.yml
98
+ brutal .
95
99
  ```
96
100
 
97
- This would create a `path/to/test_hello_world.rb` file containing the test suite.
98
-
99
- To avoid accidentally overwriting a file, the `--no-force` option can be used:
101
+ or even:
100
102
 
101
103
  ```sh
102
- brutal path/to/test_hello_world.yml --no-force
104
+ brutal
103
105
  ```
104
106
 
105
- > A path/to/test_hello_world.rb file already exists!
107
+ This would create a `test_user.rb` file containing the test suite.
106
108
 
107
- ### Getting started
109
+ Assuming now that in the workspace there are a large number of configuration files named in the `spec/` folder, the complete test suite could be generated recursively via this command:
108
110
 
109
- 1. Create a `.brutal.yml` file in your application's root directory. For example: <https://github.com/fixrb/brutal/blob/v1.4.0/examples/hello_world_v1/.brutal.yml>
110
- 2. Run the `brutal` command from the same directory.
111
- 3. Read the generated `test.rb` file in the same directory: <https://github.com/fixrb/brutal/blob/v1.4.0/examples/hello_world_v1/test.rb>
111
+ ```sh
112
+ brutal spec/
113
+ ```
114
+
115
+ This would create one test file per configuration file matching `./spec/**/*_brutal.yaml` in to `./spec/**/test_*.rb`.
112
116
 
113
- ### More examples
117
+ ### Some examples
114
118
 
115
- <https://github.com/fixrb/brutal/blob/v1.4.0/examples/>
119
+ <https://github.com/fixrb/brutal/tree/main/examples/>
116
120
 
117
121
  ## Rake integration example
118
122
 
119
- A generated `test.rb` file could be matched as follows:
123
+ Generated test suite files could be matched as follows:
120
124
 
121
125
  ```ruby
122
126
  Rake::TestTask.new do |t|
123
- t.pattern = "test.rb"
127
+ t.pattern = "**/test_*.rb"
124
128
  end
125
129
  ```
126
130
 
127
- ## Test suite
128
-
129
- __Brutal__'s test set is brutally self-generated here: [./test.rb](https://github.com/fixrb/brutal/blob/main/test.rb)
130
-
131
131
  ## Contact
132
132
 
133
133
  * Source code: https://github.com/fixrb/brutal
data/bin/brutal CHANGED
@@ -1,12 +1,18 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- require "pathname"
4
+ require_relative File.join("..", "lib", "brutal", "command_line_arguments_parser")
5
+
6
+ formats, pathnames = Brutal::CommandLineArgumentsParser.new(*ARGV).call
7
+
5
8
  require_relative File.join("..", "lib", "brutal")
6
9
 
7
- path = ARGV.fetch(0, Brutal::File::DEFAULT_CONFIG_FILENAME)
8
- pathname = Pathname.new(path)
9
- pathname += Brutal::File::DEFAULT_CONFIG_FILENAME if pathname.directory?
10
- force_opt = ARGV.none?("--no-force")
10
+ formats.each do |format|
11
+ generator = Brutal.new(format)
11
12
 
12
- Brutal.generate!(pathname, force: force_opt)
13
+ pathnames.each do |pathname|
14
+ Dir.chdir(pathname.dirname) do
15
+ generator.call(pathname.basename)
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative File.join("manifest", "file", "name")
4
+
5
+ require "pathname"
6
+
7
+ class Brutal
8
+ # Accept an arbitrary number of arguments passed from the command-line.
9
+ class CommandLineArgumentsParser
10
+ MANIFEST_FILENAME_SUFFIX = Manifest::File::Name::SUFFIX
11
+ MANIFEST_FILENAME_PATTERN = ::File.join("**", "*#{MANIFEST_FILENAME_SUFFIX}")
12
+ CURRENT_DIRECTORY_CONTEXT = "."
13
+ GEM_NAME = "brutal"
14
+ HELP_OPTION = "--help"
15
+ VERSION_OPTION = "--version"
16
+ RUBY_FORMAT_OPTION = "--format=ruby"
17
+ DEFAULT_FORMAT_OPTION = RUBY_FORMAT_OPTION
18
+
19
+ FORMAT_OPTIONS = {
20
+ RUBY_FORMAT_OPTION => :Ruby
21
+ }.freeze
22
+
23
+ DEFAULT_FORMAT = FORMAT_OPTIONS.fetch(DEFAULT_FORMAT_OPTION)
24
+ DEFAULT_FORMATS = [DEFAULT_FORMAT].freeze
25
+
26
+ attr_reader :pathnames
27
+
28
+ def initialize(*args)
29
+ args.each do |arg|
30
+ help! if arg == HELP_OPTION
31
+ version! if arg == VERSION_OPTION
32
+ end
33
+
34
+ @formats = []
35
+ @pathnames = []
36
+
37
+ args << CURRENT_DIRECTORY_CONTEXT unless any_path_given?(*args)
38
+ args.each { |arg| parse!(arg) }
39
+ end
40
+
41
+ def call
42
+ [formats, pathnames]
43
+ end
44
+
45
+ private
46
+
47
+ def formats
48
+ @formats.empty? ? DEFAULT_FORMATS : @formats
49
+ end
50
+
51
+ def parse!(arg)
52
+ case arg
53
+ when RUBY_FORMAT_OPTION
54
+ @formats << FORMAT_OPTIONS.fetch(RUBY_FORMAT_OPTION)
55
+ else
56
+ load!(::Pathname.new(arg))
57
+ end
58
+ end
59
+
60
+ def help!
61
+ puts help_message
62
+ exit
63
+ end
64
+
65
+ def help_message
66
+ <<~TXT
67
+ Usage: #{$PROGRAM_NAME} [options] [files or directories]
68
+
69
+ --format=FORMAT Choose "ruby" (default).
70
+ --help Display this help.
71
+ --version Display the version.
72
+ TXT
73
+ end
74
+
75
+ def load!(pathname)
76
+ if pathname.directory?
77
+ pathname.glob(MANIFEST_FILENAME_PATTERN).each { |filename| load!(filename) }
78
+ elsif pathname.file?
79
+ load_file!(pathname)
80
+ else
81
+ raise ::ArgumentError, "#{pathname} is neither a file nor a directory."
82
+ end
83
+ end
84
+
85
+ def load_file!(pathname)
86
+ if pathname.fnmatch?(MANIFEST_FILENAME_PATTERN)
87
+ @pathnames << pathname
88
+ else
89
+ warn "Skipping #{pathname} because not matched against #{MANIFEST_FILENAME_PATTERN}."
90
+ end
91
+ end
92
+
93
+ def any_path_given?(*args)
94
+ path_args(*args).any?
95
+ end
96
+
97
+ def path_args(*args)
98
+ args - FORMAT_OPTIONS.keys
99
+ end
100
+
101
+ def version!
102
+ abort "Gem not found on the system. Unknown version." if not_loaded_spec?
103
+
104
+ puts loaded_spec.version
105
+ exit
106
+ end
107
+
108
+ def loaded_spec
109
+ ::Gem.loaded_specs[GEM_NAME]
110
+ end
111
+
112
+ def not_loaded_spec?
113
+ loaded_spec.nil?
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Brutal
4
+ module Format
5
+ class Ruby
6
+ module Filename
7
+ # Brutal Ruby format file prefix.
8
+ PREFIX = "test_"
9
+
10
+ # Brutal Ruby format file suffix.
11
+ SUFFIX = ".rb"
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,163 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative File.join("ruby", "filename")
4
+
5
+ class Brutal
6
+ module Format
7
+ # Ruby format class
8
+ class Ruby
9
+ # Whitespace character.
10
+ SPACE = " "
11
+
12
+ # Hyphen-minus character.
13
+ HYPHEN_MINUS = "-"
14
+
15
+ # Two spaces per indentation level.
16
+ INDENTATION = SPACE * 2
17
+
18
+ # Specifies templates to challenge evaluated subjects & get results.
19
+ attr_reader :actuals
20
+
21
+ # Specifies a list of variables to populate the subject's template.
22
+ attr_reader :contexts
23
+
24
+ # Specifies a block of lines to be executed once before all examples.
25
+ attr_reader :header
26
+
27
+ # Specifies a block of lines to be executed before each example.
28
+ attr_reader :before
29
+
30
+ # Specifies the template of the code to be declined across contexts.
31
+ attr_reader :subject
32
+
33
+ # Specifies a block of lines to be executed after each example.
34
+ attr_reader :after
35
+
36
+ # Specifies a block of lines to be executed once after all examples.
37
+ attr_reader :footer
38
+
39
+ # Initialize a new scaffold generator.
40
+ def initialize(header, before, subject, after, footer, *actuals, **contexts)
41
+ header = "# Brutal test suite" if header.empty?
42
+ before = "# Starting an example" if before.empty?
43
+ after = "# Finishing an example" if after.empty?
44
+ footer = "# End of the brutal test" if footer.empty?
45
+
46
+ warn("Empty subject!") if subject.empty?
47
+ warn("Empty actual values!") if actuals.empty?
48
+ warn("Empty contexts!") if contexts.empty?
49
+
50
+ @header = header
51
+ @before = before
52
+ @subject = subject
53
+ @actuals = actuals
54
+ @contexts = contexts
55
+ @after = after
56
+ @footer = footer
57
+ end
58
+
59
+ # Return a Ruby string that can be evaluated.
60
+ def inspect(object)
61
+ return object.to_s unless object.is_a?(::String)
62
+
63
+ object.strip
64
+ end
65
+
66
+ # Return a string representation.
67
+ #
68
+ # @return [String]
69
+ def to_s
70
+ eval(header) # rubocop:disable Security/Eval
71
+
72
+ ruby_lines.compact.join(separator_code)
73
+ ensure
74
+ eval(footer) # rubocop:disable Security/Eval
75
+ end
76
+
77
+ def attributes(*values)
78
+ context_names.each_with_index.inject({}) do |h, (name, i)|
79
+ h.merge(name.to_sym => inspect(values.fetch(i)))
80
+ end
81
+ end
82
+
83
+ def context_names
84
+ contexts.keys.sort
85
+ end
86
+
87
+ def contexts_values
88
+ context_names.map { |context_name| contexts.fetch(context_name) }
89
+ end
90
+
91
+ def combinations_values
92
+ Array(contexts_values[0]).product(*Array(contexts_values[1..]))
93
+ end
94
+
95
+ def ruby_lines
96
+ [header_code] + actual_codes + [footer_code]
97
+ end
98
+
99
+ def actual_codes
100
+ combinations_values.map do |values|
101
+ actual_str = format(inspect(subject), **attributes(*values))
102
+ string = actual_code(actual_str)
103
+ actual = eval(actual_str) # rubocop:disable Security/Eval, Lint/UselessAssignment
104
+
105
+ actuals.each do |actual_value|
106
+ result_str = format(actual_value, subject: "actual")
107
+ string += "raise if #{result_str} != #{eval(result_str).inspect}\n" # rubocop:disable Security/Eval
108
+ end
109
+
110
+ string
111
+ end
112
+ end
113
+
114
+ def actual_code(actual_str)
115
+ <<~CODE
116
+ #{before_code}
117
+ actual = begin
118
+ #{actual_str.gsub(/^/, INDENTATION)}
119
+ end
120
+
121
+ #{after_code}
122
+ CODE
123
+ end
124
+
125
+ def header_code
126
+ <<~CODE
127
+ #{header.chomp}
128
+ CODE
129
+ end
130
+
131
+ def before_code
132
+ <<~CODE
133
+ #{before.chomp}
134
+ CODE
135
+ end
136
+
137
+ def after_code
138
+ <<~CODE
139
+ #{after.chomp}
140
+ CODE
141
+ end
142
+
143
+ def footer_code
144
+ <<~CODE
145
+ #{footer.chomp}
146
+ CODE
147
+ end
148
+
149
+ def separator_code
150
+ <<~CODE
151
+
152
+ #{thematic_break_code}
153
+ CODE
154
+ end
155
+
156
+ def thematic_break_code
157
+ <<~CODE
158
+ # #{HYPHEN_MINUS * 78}
159
+ CODE
160
+ end
161
+ end
162
+ end
163
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ Dir[File.join(File.dirname(__FILE__), "format", "*.rb")].sort.each do |fname|
4
+ require_relative fname
5
+ end
6
+
7
+ class Brutal
8
+ # A collection of formatter classes.
9
+ module Format
10
+ end
11
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Brutal
4
+ class Manifest
5
+ class File
6
+ module Name
7
+ # Suffix for configuration file names.
8
+ SUFFIX = "_brutal.yaml"
9
+
10
+ # Suffix pattern for configuration file names.
11
+ SUFFIX_PATTERN = "*#{SUFFIX}"
12
+
13
+ # Suffix regex for configuration file names.
14
+ SUFFIX_REGEX = /#{SUFFIX}\z/.freeze
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative File.join("file", "name")
4
+
5
+ require "yaml"
6
+
7
+ class Brutal
8
+ class Manifest
9
+ # YAML manifest file parser.
10
+ class File
11
+ attr_reader :yaml
12
+
13
+ def initialize(pathname)
14
+ raise ::ArgumentError unless pathname.fnmatch?(Name::SUFFIX_PATTERN)
15
+
16
+ @yaml = pathname.read
17
+ end
18
+
19
+ def parse
20
+ ::YAML.safe_load(yaml, symbolize_names: false)
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative File.join("manifest", "file")
4
+
5
+ class Brutal
6
+ # Brutal YAML manifest file parser.
7
+ class Manifest
8
+ # The _actuals_ top-level section key.
9
+ ACTUALS_KEY = "actuals"
10
+
11
+ # The _contexts_ top-level section key.
12
+ CONTEXTS_KEY = "contexts"
13
+
14
+ # The _header_ top-level section key.
15
+ HEADER_KEY = "header"
16
+
17
+ # The _before_ top-level section key.
18
+ BEFORE_KEY = "before"
19
+
20
+ # The _subject_ top-level section key.
21
+ SUBJECT_KEY = "subject"
22
+
23
+ # The _after_ top-level section key.
24
+ AFTER_KEY = "after"
25
+
26
+ # The _footer_ top-level section key.
27
+ FOOTER_KEY = "footer"
28
+
29
+ # Default _actuals_ collection.
30
+ DEFAULT_ACTUALS = [].freeze
31
+
32
+ # Default _contexts_ collection.
33
+ DEFAULT_CONTEXTS = {}.freeze
34
+
35
+ # Default _header_ code to evaluate.
36
+ DEFAULT_HEADER = ""
37
+
38
+ # Default _before_ code to evaluate.
39
+ DEFAULT_BEFORE = ""
40
+
41
+ # Default _after_ code to evaluate.
42
+ DEFAULT_AFTER = ""
43
+
44
+ # Default _footer_ code to evaluate.
45
+ DEFAULT_FOOTER = ""
46
+
47
+ # Parse a file at `pathname`. Returns the YAML manifest instance.
48
+ def self.parse_file(pathname)
49
+ load(File.new(pathname).parse)
50
+ end
51
+
52
+ # Load the configuration parameters.
53
+ #
54
+ # @param params [Hash] Receive the 7 top-level section parameters.
55
+ def self.load(params)
56
+ new(
57
+ actuals: params.fetch(ACTUALS_KEY, DEFAULT_ACTUALS),
58
+ contexts: params.fetch(CONTEXTS_KEY, DEFAULT_CONTEXTS),
59
+ header: params.fetch(HEADER_KEY, DEFAULT_HEADER),
60
+ before: params.fetch(BEFORE_KEY, DEFAULT_BEFORE),
61
+ subject: params.fetch(SUBJECT_KEY),
62
+ after: params.fetch(AFTER_KEY, DEFAULT_AFTER),
63
+ footer: params.fetch(FOOTER_KEY, DEFAULT_FOOTER)
64
+ )
65
+ end
66
+
67
+ # Specifies templates to challenge evaluated subjects & get results.
68
+ attr_reader :actuals
69
+
70
+ # Specifies a list of variables to populate the subject's template.
71
+ attr_reader :contexts
72
+
73
+ # Specifies a block of lines to be executed once before all examples.
74
+ attr_reader :header
75
+
76
+ # Specifies a block of lines to be executed before each example.
77
+ attr_reader :before
78
+
79
+ # Specifies the template of the code to be declined across contexts.
80
+ attr_reader :subject
81
+
82
+ # Specifies a block of lines to be executed after each example.
83
+ attr_reader :after
84
+
85
+ # Specifies a block of lines to be executed once after all examples.
86
+ attr_reader :footer
87
+
88
+ # Initialize a new configuration.
89
+ def initialize(actuals:, contexts:, header:, before:, subject:, after:, footer:)
90
+ raise ::TypeError, actuals.inspect unless actuals.is_a?(::Array)
91
+ raise ::TypeError, contexts.inspect unless contexts.is_a?(::Hash)
92
+ raise ::TypeError, header.inspect unless header.is_a?(::String)
93
+ raise ::TypeError, before.inspect unless before.is_a?(::String)
94
+ raise ::TypeError, subject.inspect unless subject.is_a?(::String)
95
+ raise ::TypeError, after.inspect unless after.is_a?(::String)
96
+ raise ::TypeError, footer.inspect unless footer.is_a?(::String)
97
+
98
+ @actuals = actuals
99
+ @contexts = contexts
100
+ @header = header
101
+ @before = before
102
+ @subject = subject
103
+ @after = after
104
+ @footer = footer
105
+ end
106
+ end
107
+ end
data/lib/brutal.rb CHANGED
@@ -1,43 +1,40 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  %w[
4
- configuration
5
- file
6
- scaffold
7
- yaml
4
+ format
5
+ manifest
8
6
  ].each { |filename| require_relative(File.join("brutal", filename)) }
9
7
 
10
8
  # The Brutal namespace.
11
- module Brutal
12
- def self.generate!(pathname, force: true)
13
- hash = parse(pathname)
14
- conf = Configuration.load(hash)
9
+ class Brutal
10
+ attr_reader :engine
15
11
 
16
- ruby = Scaffold.new(conf.header,
17
- conf.subject,
18
- *conf.actuals,
19
- **conf.contexts)
12
+ def initialize(format)
13
+ @engine = Format.const_get(format)
14
+ end
20
15
 
21
- write(pathname, ruby, force: force)
16
+ def call(pathname)
17
+ manifest = Manifest.parse_file(pathname)
18
+ scaffold = brutalizer(manifest)
19
+ pathname = new_pathname(pathname)
20
+ pathname.write(scaffold)
22
21
  end
23
22
 
24
- def self.parse(pathname)
25
- return Yaml.parse(read(pathname)) if Yaml.parse?(pathname)
23
+ private
24
+
25
+ def brutalizer(conf)
26
+ engine.new(conf.header, conf.before, conf.subject, conf.after, conf.footer, *conf.actuals, **conf.contexts)
27
+ end
26
28
 
27
- raise ::ArgumentError, "Unrecognized extension. " \
28
- "Impossible to parse #{pathname.inspect}."
29
+ def new_pathname(pathname)
30
+ pathname.dirname + "#{new_prefix}#{pathname.basename.sub(Manifest::File::Name::SUFFIX_REGEX, new_suffix)}"
29
31
  end
30
- private_class_method :parse
31
32
 
32
- def self.read(pathname)
33
- File::Read.new(pathname).call
33
+ def new_prefix
34
+ engine.const_get(:Filename).const_get(:PREFIX)
34
35
  end
35
- private_class_method :read
36
36
 
37
- def self.write(pathname, ruby, force:)
38
- new_pathname = File.generated_pathname(pathname)
39
- File.override_protection(new_pathname) unless force
40
- File::Write.new(new_pathname).call(ruby)
37
+ def new_suffix
38
+ engine.const_get(:Filename).const_get(:SUFFIX)
41
39
  end
42
- private_class_method :write
43
40
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: brutal
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.5.0
4
+ version: 1.6.0.beta2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Cyril Kato
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-08-23 00:00:00.000000000 Z
11
+ date: 2022-09-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -108,20 +108,6 @@ dependencies:
108
108
  - - ">="
109
109
  - !ruby/object:Gem::Version
110
110
  version: '0'
111
- - !ruby/object:Gem::Dependency
112
- name: simplecov
113
- requirement: !ruby/object:Gem::Requirement
114
- requirements:
115
- - - ">="
116
- - !ruby/object:Gem::Version
117
- version: '0'
118
- type: :development
119
- prerelease: false
120
- version_requirements: !ruby/object:Gem::Requirement
121
- requirements:
122
- - - ">="
123
- - !ruby/object:Gem::Version
124
- version: '0'
125
111
  - !ruby/object:Gem::Dependency
126
112
  name: yard
127
113
  requirement: !ruby/object:Gem::Requirement
@@ -147,12 +133,13 @@ files:
147
133
  - README.md
148
134
  - bin/brutal
149
135
  - lib/brutal.rb
150
- - lib/brutal/configuration.rb
151
- - lib/brutal/file.rb
152
- - lib/brutal/file/read.rb
153
- - lib/brutal/file/write.rb
154
- - lib/brutal/scaffold.rb
155
- - lib/brutal/yaml.rb
136
+ - lib/brutal/command_line_arguments_parser.rb
137
+ - lib/brutal/format.rb
138
+ - lib/brutal/format/ruby.rb
139
+ - lib/brutal/format/ruby/filename.rb
140
+ - lib/brutal/manifest.rb
141
+ - lib/brutal/manifest/file.rb
142
+ - lib/brutal/manifest/file/name.rb
156
143
  homepage: https://github.com/fixrb/brutal
157
144
  licenses:
158
145
  - MIT
@@ -169,9 +156,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
169
156
  version: 2.7.0
170
157
  required_rubygems_version: !ruby/object:Gem::Requirement
171
158
  requirements:
172
- - - ">="
159
+ - - ">"
173
160
  - !ruby/object:Gem::Version
174
- version: '0'
161
+ version: 1.3.1
175
162
  requirements: []
176
163
  rubygems_version: 3.1.6
177
164
  signing_key:
@@ -1,55 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Brutal
4
- # Brutal::Configuration
5
- #
6
- # @since 1.0.0
7
- class Configuration
8
- ACTUALS_KEY = "actuals"
9
- CONTEXTS_KEY = "contexts"
10
- HEADER_KEY = "header"
11
- SUBJECT_KEY = "subject"
12
-
13
- DEFAULT_ACTUALS = [].freeze
14
- DEFAULT_CONTEXTS = {}.freeze
15
- DEFAULT_HEADER = "# Brutal test suite"
16
- DEFAULT_SUBJECT = ""
17
-
18
- # Load the configuration parameters.
19
- #
20
- # @param params [Hash] Receive the 4 top-level section parameters.
21
- def self.load(params)
22
- new(
23
- actuals: params.fetch(ACTUALS_KEY, DEFAULT_ACTUALS),
24
- contexts: params.fetch(CONTEXTS_KEY, DEFAULT_CONTEXTS),
25
- header: params.fetch(HEADER_KEY, DEFAULT_HEADER),
26
- subject: params.fetch(SUBJECT_KEY, DEFAULT_SUBJECT)
27
- )
28
- end
29
-
30
- # Specifies templates to challenge evaluated subjects & get results.
31
- attr_reader :actuals
32
-
33
- # Specifies a list of variables to populate the subject's template.
34
- attr_reader :contexts
35
-
36
- # Specifies the code to execute before generating the test suite.
37
- attr_reader :header
38
-
39
- # Specifies the template of the code to be declined across contexts.
40
- attr_reader :subject
41
-
42
- # Initialize a new configuration.
43
- def initialize(actuals:, contexts:, header:, subject:)
44
- raise ::TypeError, actuals.inspect unless actuals.is_a?(::Array)
45
- raise ::TypeError, contexts.inspect unless contexts.is_a?(::Hash)
46
- raise ::TypeError, header.inspect unless header.is_a?(::String)
47
- raise ::TypeError, subject.inspect unless subject.is_a?(::String)
48
-
49
- @actuals = actuals.sort
50
- @contexts = contexts
51
- @header = header
52
- @subject = subject
53
- end
54
- end
55
- end
@@ -1,28 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Brutal
4
- module File
5
- # Brutal::File::Read
6
- #
7
- # @since 1.1.0
8
- class Read
9
- attr_reader :name
10
-
11
- def initialize(name)
12
- @name = name
13
- end
14
-
15
- def call
16
- ::File.read(path)
17
- rescue ::Errno::ENOENT => _e
18
- abort "File #{path} not found!"
19
- end
20
-
21
- protected
22
-
23
- def path
24
- ::File.join(::Dir.pwd, name)
25
- end
26
- end
27
- end
28
- end
@@ -1,31 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Brutal
4
- module File
5
- # Brutal::File::Write
6
- #
7
- # @since 1.1.0
8
- class Write
9
- attr_reader :name
10
-
11
- def initialize(name)
12
- @name = name
13
- end
14
-
15
- def call(scaffold)
16
- file = ::File.open(path, "w")
17
- file.write(scaffold)
18
-
19
- true
20
- ensure
21
- file.close
22
- end
23
-
24
- protected
25
-
26
- def path
27
- ::File.join(::Dir.pwd, name)
28
- end
29
- end
30
- end
31
- end
data/lib/brutal/file.rb DELETED
@@ -1,31 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- %w[
4
- read
5
- write
6
- ].each { |filename| require_relative(File.join("file", filename)) }
7
-
8
- module Brutal
9
- # Brutal::File
10
- module File
11
- DEFAULT_CONFIG_FILENAME = ".brutal.yml"
12
- DEFAULT_GENERATED_FILENAME = "test.rb"
13
- RUBY_EXTENSION = ".rb"
14
-
15
- def self.generated_pathname(pathname)
16
- filename = pathname.basename
17
- return pathname.dirname + DEFAULT_GENERATED_FILENAME if default_config_filename?(filename)
18
-
19
- pathname.sub_ext(RUBY_EXTENSION)
20
- end
21
-
22
- def self.override_protection(pathname)
23
- abort "A #{pathname} file already exists!" if pathname.exist?
24
- end
25
-
26
- def self.default_config_filename?(filename)
27
- filename.to_s == DEFAULT_CONFIG_FILENAME
28
- end
29
- private_class_method :default_config_filename?
30
- end
31
- end
@@ -1,113 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Brutal
4
- # Brutal::Scaffold
5
- #
6
- # @since 1.0.0
7
- class Scaffold
8
- # Specifies templates to challenge evaluated subjects & get results.
9
- attr_reader :actuals
10
-
11
- # Specifies a list of variables to populate the subject's template.
12
- attr_reader :contexts
13
-
14
- # Specifies the code to execute before generating the test suite.
15
- attr_reader :header
16
-
17
- # Specifies the template of the code to be declined across contexts.
18
- attr_reader :subject
19
-
20
- # Initialize a new scaffold generator.
21
- def initialize(header, subject, *actuals, **contexts)
22
- warn("Empty subject!") if subject.empty?
23
- warn("Empty actual values!") if actuals.empty?
24
- warn("Empty contexts!") if contexts.empty?
25
-
26
- eval(header) # rubocop:disable Security/Eval
27
-
28
- @header = header
29
- @subject = subject
30
- @actuals = actuals
31
- @contexts = contexts
32
- end
33
-
34
- # Return a Ruby string that can be evaluated.
35
- def inspect(object)
36
- return object.to_s unless object.is_a?(::String)
37
-
38
- object.strip
39
- end
40
-
41
- # Return a string representation.
42
- #
43
- # @return [String]
44
- def to_s
45
- ruby_lines.join(separator_ruby_code)
46
- end
47
-
48
- def attributes(*values)
49
- context_names.each_with_index.inject({}) do |h, (name, i)|
50
- h.merge(name.to_sym => inspect(values.fetch(i)))
51
- end
52
- end
53
-
54
- def context_names
55
- contexts.keys.sort
56
- end
57
-
58
- def contexts_values
59
- context_names.map { |context_name| contexts.fetch(context_name) }
60
- end
61
-
62
- def combinations_values
63
- Array(contexts_values[0]).product(*Array(contexts_values[1..]))
64
- end
65
-
66
- def ruby_lines
67
- [header_ruby_code] + actual_ruby_codes
68
- end
69
-
70
- def actual_ruby_codes
71
- combinations_values.map do |values|
72
- actual_str = format(inspect(subject), **attributes(*values))
73
- string = actual_ruby_code(actual_str)
74
- actual = eval(actual_str) # rubocop:disable Security/Eval, Lint/UselessAssignment
75
-
76
- actuals.each do |actual_value|
77
- result_str = format(actual_value, subject: "actual")
78
- string += "raise if #{result_str} != #{eval(result_str).inspect}\n" # rubocop:disable Security/Eval
79
- end
80
-
81
- string
82
- end
83
- end
84
-
85
- def actual_ruby_code(actual_str)
86
- <<~RUBY_CODE
87
- actual = begin
88
- #{actual_str.gsub(/^/, ' ')}
89
- end
90
-
91
- RUBY_CODE
92
- end
93
-
94
- def header_ruby_code
95
- <<~RUBY_CODE
96
- #{header.chomp}
97
- RUBY_CODE
98
- end
99
-
100
- def separator_ruby_code
101
- <<~RUBY_CODE
102
-
103
- #{thematic_break_ruby_code}
104
- RUBY_CODE
105
- end
106
-
107
- def thematic_break_ruby_code
108
- <<~RUBY_CODE
109
- # #{'-' * 78}
110
- RUBY_CODE
111
- end
112
- end
113
- end
data/lib/brutal/yaml.rb DELETED
@@ -1,24 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "yaml"
4
-
5
- module Brutal
6
- # Brutal::Yaml
7
- #
8
- # @since 1.1.0
9
- module Yaml
10
- FILENAME_EXTENSIONS = %w[
11
- .yaml
12
- .yml
13
- ].freeze
14
-
15
- def self.parse(yaml)
16
- ::YAML.safe_load(yaml, symbolize_names: false)
17
- end
18
-
19
- def self.parse?(pathname)
20
- filename_extension = pathname.extname
21
- FILENAME_EXTENSIONS.include?(filename_extension)
22
- end
23
- end
24
- end