brutal 1.5.0 → 1.6.0.beta2

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 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