brutal 1.2.1 → 1.5.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 958cf59b6aea2f5e323b849c83a86399cd9ad99e2433b899160a2422275a319e
4
- data.tar.gz: bdb36f749b6bfcb87683f12d3db9e74c716bdbfbf41e21c845864bf1e17f7a7e
3
+ metadata.gz: ea124053ebeef681c2e046022936f9d67ca97778733a23bd2d81781e90daa9a8
4
+ data.tar.gz: 677089454fece7f60aedfc4693b53c132f057bbeeaca5e651f88ec52d026ea34
5
5
  SHA512:
6
- metadata.gz: 6f4b098047917136946c7bb88b111b5e6facf2a13ab70677bf0bce480bc2b4f0931f89f0be8ad2dc6db152abaaeffbdece5993d7ac5fba2ca85fde601df0c4c2
7
- data.tar.gz: 7d5d125189afacf46a008a6b12c79ec68ffeb6e15c4324c1a6924a19be9061dac5760224225a564a8a1d9410b18a3da80591beeb30e5abc15995a5094326fab4
6
+ metadata.gz: b1d043d4740db1987c188755e9b339da566034a3cc2a43f99ced12fb3de26a93df0f93d669b20700ba662a935d9be662a25bffba8f8ae92aae3e01deade92d9b
7
+ data.tar.gz: 5abc20c46849f69b20de5b89848f8fed831b2d92641f57cfa349be4e0a975c8630c27268d16a022911693d403af8262b4f0d1099dcf468879392e9c2d7fe7ec0
data/LICENSE.md CHANGED
@@ -1,4 +1,4 @@
1
- The MIT License (MIT)
1
+ # The MIT License
2
2
 
3
3
  Copyright (c) 2020-2022 Cyril Kato
4
4
 
data/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # Brutal 💎🔨
2
2
 
3
- [![Version](https://img.shields.io/github/v/tag/fixrb/brutal?label=Version&logo=github)](https://github.com/fixrb/brutal/releases)
3
+ [![Version](https://img.shields.io/github/v/tag/fixrb/brutal?label=Version&logo=github)](https://github.com/fixrb/brutal/tags)
4
4
  [![Yard documentation](https://img.shields.io/badge/Yard-documentation-blue.svg?logo=github)](https://rubydoc.info/github/fixrb/brutal/main)
5
- [![CI](https://github.com/fixrb/brutal/workflows/CI/badge.svg?branch=main)](https://github.com/fixrb/brutal/actions?query=workflow%3Aci+branch%3Amain)
5
+ [![Ruby](https://github.com/fixrb/brutal/workflows/Ruby/badge.svg?branch=main)](https://github.com/fixrb/brutal/actions?query=workflow%3Aruby+branch%3Amain)
6
6
  [![RuboCop](https://github.com/fixrb/brutal/workflows/RuboCop/badge.svg?branch=main)](https://github.com/fixrb/brutal/actions?query=workflow%3Arubocop+branch%3Amain)
7
7
  [![License](https://img.shields.io/github/license/fixrb/brutal?label=License&logo=github)](https://github.com/fixrb/brutal/raw/main/LICENSE.md)
8
8
 
@@ -59,63 +59,60 @@ Just type `brutal` in a Ruby project's folder and watch the magic happen.
59
59
 
60
60
  ## Usage
61
61
 
62
- The `brutal.yml` file is a manifest you can use to define your __Brutal__ meta-spec.
63
- It has 4 top-level sections:
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:
64
65
 
65
66
  * `header` - Specifies the code to execute before generating the test suite.
66
67
  * `subject` - Specifies the template of the code to be declined across contexts.
67
68
  * `contexts` - Specifies a list of variables to populate the subject's template.
68
69
  * `actuals` - Specifies templates to challenge evaluated subjects & get results.
69
70
 
70
- ### Getting started
71
+ When the configuration file is present, the generation of a test suite can be done with the command:
71
72
 
72
- 1. Create a `brutal.yml` file in your application's root directory.
73
- The following example `brutal.yml` defines the shape of a Hello test suite:
73
+ ```sh
74
+ brutal .brutal.yml
75
+ ```
74
76
 
75
- ```yaml
76
- ---
77
- subject: |
78
- "Hello " + "%{string}"
77
+ or:
78
+
79
+ ```sh
80
+ brutal .
81
+ ```
79
82
 
80
- contexts:
81
- string:
82
- - Alice
83
- - Bob
83
+ or even:
84
84
 
85
- actuals:
86
- - "%{subject}.to_s"
87
- - "%{subject}.length"
85
+ ```sh
86
+ brutal
88
87
  ```
89
88
 
90
- 2. Run the `brutal` command from the same directory.
89
+ This would create a `test.rb` file containing the test suite.
91
90
 
92
- 3. Read the generated `test.rb` file in the same directory:
91
+ Configuration files can also be named differently:
93
92
 
94
- ```ruby
95
- # Brutal test suite
93
+ ```sh
94
+ brutal path/to/test_hello_world.yml
95
+ ```
96
96
 
97
- # ------------------------------------------------------------------------------
97
+ This would create a `path/to/test_hello_world.rb` file containing the test suite.
98
98
 
99
- actual = begin
100
- "Hello " + "Alice"
101
- end
99
+ To avoid accidentally overwriting a file, the `--no-force` option can be used:
102
100
 
103
- raise if actual.to_s != "Hello Alice"
104
- raise if actual.length != 11
101
+ ```sh
102
+ brutal path/to/test_hello_world.yml --no-force
103
+ ```
105
104
 
106
- # ------------------------------------------------------------------------------
105
+ > A path/to/test_hello_world.rb file already exists!
107
106
 
108
- actual = begin
109
- "Hello " + "Bob"
110
- end
107
+ ### Getting started
111
108
 
112
- raise if actual.to_s != "Hello Bob"
113
- raise if actual.length != 9
114
- ```
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>
115
112
 
116
113
  ### More examples
117
114
 
118
- https://github.com/fixrb/brutal/raw/main/examples/
115
+ <https://github.com/fixrb/brutal/blob/v1.4.0/examples/>
119
116
 
120
117
  ## Rake integration example
121
118
 
@@ -145,9 +142,7 @@ The [gem](https://rubygems.org/gems/brutal) is available as open source under th
145
142
 
146
143
  ***
147
144
 
148
- <p>
149
- This project is sponsored by:<br />
150
- <a href="https://sashite.com/"><img
151
- src="https://github.com/fixrb/brutal/raw/main/img/sashite.png"
152
- alt="Sashite" /></a>
153
- </p>
145
+ This project is sponsored by [Sashité](https://github.com/sashite/):
146
+
147
+ ![Sashité logo](https://github.com/fixrb/brutal/raw/main/img/sponsor/dark/en/sashite.png#gh-dark-mode-only "Sashité")
148
+ ![Sashité logo](https://github.com/fixrb/brutal/raw/main/img/sponsor/light/en/sashite.png#gh-light-mode-only "Sashité")
data/bin/brutal CHANGED
@@ -1,6 +1,12 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
+ require "pathname"
4
5
  require_relative File.join("..", "lib", "brutal")
5
6
 
6
- Brutal.generate!
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")
11
+
12
+ Brutal.generate!(pathname, force: force_opt)
@@ -1,26 +1,21 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Brutal
4
- # Brutal::File
5
- #
6
- # @since 1.1.0
7
4
  module File
8
5
  # Brutal::File::Read
9
6
  #
10
7
  # @since 1.1.0
11
8
  class Read
12
- NAME = ".brutal.yml"
13
-
14
9
  attr_reader :name
15
10
 
16
- def initialize(name = NAME)
11
+ def initialize(name)
17
12
  @name = name
18
13
  end
19
14
 
20
15
  def call
21
16
  ::File.read(path)
22
17
  rescue ::Errno::ENOENT => _e
23
- abort("File #{path} not found!")
18
+ abort "File #{path} not found!"
24
19
  end
25
20
 
26
21
  protected
@@ -1,19 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Brutal
4
- # Brutal::File
5
- #
6
- # @since 1.1.0
7
4
  module File
8
5
  # Brutal::File::Write
9
6
  #
10
7
  # @since 1.1.0
11
8
  class Write
12
- NAME = "test.rb"
13
-
14
9
  attr_reader :name
15
10
 
16
- def initialize(name = NAME)
11
+ def initialize(name)
17
12
  @name = name
18
13
  end
19
14
 
@@ -0,0 +1,31 @@
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
@@ -42,20 +42,35 @@ module Brutal
42
42
  #
43
43
  # @return [String]
44
44
  def to_s
45
- "#{header.chomp}\n#{blank_line}" + combinations_values.map do |values|
46
- attributes = context_names.each_with_index.inject({}) do |h, (name, i)|
47
- h.merge(name.to_sym => inspect(values.fetch(i)))
48
- end
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
49
57
 
50
- actual_str = format(inspect(subject), **attributes)
58
+ def contexts_values
59
+ context_names.map { |context_name| contexts.fetch(context_name) }
60
+ end
51
61
 
52
- string = <<~CODE
53
- actual = begin
54
- #{actual_str.gsub(/^/, " ")}
55
- end
62
+ def combinations_values
63
+ Array(contexts_values[0]).product(*Array(contexts_values[1..]))
64
+ end
56
65
 
57
- CODE
66
+ def ruby_lines
67
+ [header_ruby_code] + actual_ruby_codes
68
+ end
58
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)
59
74
  actual = eval(actual_str) # rubocop:disable Security/Eval, Lint/UselessAssignment
60
75
 
61
76
  actuals.each do |actual_value|
@@ -64,25 +79,35 @@ module Brutal
64
79
  end
65
80
 
66
81
  string
67
- end.join(blank_line)
82
+ end
68
83
  end
69
84
 
70
- def blank_line
71
- "\n" \
72
- "# #{"-" * 78}\n" \
73
- "\n"
85
+ def actual_ruby_code(actual_str)
86
+ <<~RUBY_CODE
87
+ actual = begin
88
+ #{actual_str.gsub(/^/, ' ')}
89
+ end
90
+
91
+ RUBY_CODE
74
92
  end
75
93
 
76
- def context_names
77
- contexts.keys.sort
94
+ def header_ruby_code
95
+ <<~RUBY_CODE
96
+ #{header.chomp}
97
+ RUBY_CODE
78
98
  end
79
99
 
80
- def contexts_values
81
- context_names.map { |context_name| contexts.fetch(context_name) }
100
+ def separator_ruby_code
101
+ <<~RUBY_CODE
102
+
103
+ #{thematic_break_ruby_code}
104
+ RUBY_CODE
82
105
  end
83
106
 
84
- def combinations_values
85
- Array(contexts_values[0]).product(*Array(contexts_values[1..]))
107
+ def thematic_break_ruby_code
108
+ <<~RUBY_CODE
109
+ # #{'-' * 78}
110
+ RUBY_CODE
86
111
  end
87
112
  end
88
113
  end
data/lib/brutal/yaml.rb CHANGED
@@ -7,8 +7,18 @@ module Brutal
7
7
  #
8
8
  # @since 1.1.0
9
9
  module Yaml
10
+ FILENAME_EXTENSIONS = %w[
11
+ .yaml
12
+ .yml
13
+ ].freeze
14
+
10
15
  def self.parse(yaml)
11
16
  ::YAML.safe_load(yaml, symbolize_names: false)
12
17
  end
18
+
19
+ def self.parse?(pathname)
20
+ filename_extension = pathname.extname
21
+ FILENAME_EXTENSIONS.include?(filename_extension)
22
+ end
13
23
  end
14
24
  end
data/lib/brutal.rb CHANGED
@@ -2,17 +2,15 @@
2
2
 
3
3
  %w[
4
4
  configuration
5
- file/read
6
- file/write
5
+ file
7
6
  scaffold
8
7
  yaml
9
- ].each { |file_name| require_relative(File.join("brutal", file_name)) }
8
+ ].each { |filename| require_relative(File.join("brutal", filename)) }
10
9
 
11
10
  # The Brutal namespace.
12
11
  module Brutal
13
- def self.generate!
14
- yaml = File::Read.new.call
15
- hash = Yaml.parse(yaml)
12
+ def self.generate!(pathname, force: true)
13
+ hash = parse(pathname)
16
14
  conf = Configuration.load(hash)
17
15
 
18
16
  ruby = Scaffold.new(conf.header,
@@ -20,6 +18,26 @@ module Brutal
20
18
  *conf.actuals,
21
19
  **conf.contexts)
22
20
 
23
- File::Write.new.call(ruby)
21
+ write(pathname, ruby, force: force)
24
22
  end
23
+
24
+ def self.parse(pathname)
25
+ return Yaml.parse(read(pathname)) if Yaml.parse?(pathname)
26
+
27
+ raise ::ArgumentError, "Unrecognized extension. " \
28
+ "Impossible to parse #{pathname.inspect}."
29
+ end
30
+ private_class_method :parse
31
+
32
+ def self.read(pathname)
33
+ File::Read.new(pathname).call
34
+ end
35
+ private_class_method :read
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)
41
+ end
42
+ private_class_method :write
25
43
  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.2.1
4
+ version: 1.5.0
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-04-03 00:00:00.000000000 Z
11
+ date: 2022-08-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -38,6 +38,20 @@ dependencies:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rubocop-gitlab-security
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
41
55
  - !ruby/object:Gem::Dependency
42
56
  name: rubocop-md
43
57
  requirement: !ruby/object:Gem::Requirement
@@ -134,6 +148,7 @@ files:
134
148
  - bin/brutal
135
149
  - lib/brutal.rb
136
150
  - lib/brutal/configuration.rb
151
+ - lib/brutal/file.rb
137
152
  - lib/brutal/file/read.rb
138
153
  - lib/brutal/file/write.rb
139
154
  - lib/brutal/scaffold.rb