tldr 0.10.1 → 1.1.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.
@@ -1,46 +1,52 @@
1
1
  class TLDR
2
2
  CONFLAGS = {
3
- seed: "--seed",
4
- no_helper: "--no-helper",
5
- verbose: "--verbose",
6
- print_interrupted_test_backtraces: "--print-interrupted-test-backtraces",
7
- reporter: "--reporter",
8
- helper_paths: "--helper",
9
- load_paths: "--load-path",
3
+ timeout: "--[no-]timeout",
4
+ watch: "--watch",
5
+ fail_fast: "--fail-fast",
10
6
  parallel: "--[no-]parallel",
7
+ seed: "--seed",
11
8
  names: "--name",
12
- fail_fast: "--fail-fast",
13
- no_emoji: "--no-emoji",
9
+ exclude_names: "--exclude-name",
10
+ exclude_paths: "--exclude-path",
11
+ helper_paths: "--helper",
12
+ no_helper: "--no-helper",
14
13
  prepend_paths: "--prepend",
15
14
  no_prepend: "--no-prepend",
16
- exclude_paths: "--exclude-path",
17
- exclude_names: "--exclude-name",
15
+ load_paths: "--load-path",
18
16
  base_path: "--base-path",
19
- no_dotfile: "--no-dotfile",
17
+ config_path: "--[no-]config",
18
+ reporter: "--reporter",
19
+ emoji: "--[no-]emoji",
20
20
  warnings: "--[no-]warnings",
21
- watch: "--watch",
21
+ verbose: "--verbose",
22
22
  yes_i_know: "--yes-i-know",
23
+ print_interrupted_test_backtraces: "--print-interrupted-test-backtraces",
23
24
  i_am_being_watched: "--i-am-being-watched",
25
+ exit_0_on_timeout: "--exit-0-on-timeout",
26
+ exit_2_on_failure: "--exit-2-on-failure",
24
27
  paths: nil
25
28
  }.freeze
26
29
 
27
30
  PATH_FLAGS = [:paths, :helper_paths, :load_paths, :prepend_paths, :exclude_paths].freeze
28
31
  MOST_RECENTLY_MODIFIED_TAG = "MOST_RECENTLY_MODIFIED".freeze
29
32
  CONFIG_ATTRIBUTES = [
30
- :paths, :seed, :no_helper, :verbose, :print_interrupted_test_backtraces, :reporter,
31
- :helper_paths, :load_paths, :parallel, :names, :fail_fast, :no_emoji,
32
- :prepend_paths, :no_prepend, :exclude_paths, :exclude_names, :base_path,
33
- :no_dotfile, :warnings, :watch, :yes_i_know, :i_am_being_watched,
33
+ :timeout, :watch, :fail_fast, :parallel, :seed, :names, :exclude_names,
34
+ :exclude_paths, :helper_paths, :no_helper, :prepend_paths, :no_prepend,
35
+ :load_paths, :base_path, :config_path, :reporter, :emoji, :warnings,
36
+ :verbose, :yes_i_know, :print_interrupted_test_backtraces,
37
+ :i_am_being_watched, :exit_0_on_timeout, :exit_2_on_failure, :paths,
34
38
  # Internal properties
35
39
  :config_intended_for_merge_only, :seed_set_intentionally, :cli_defaults
36
40
  ].freeze
37
41
 
38
42
  Config = Struct.new(*CONFIG_ATTRIBUTES, keyword_init: true) do
39
43
  def initialize(**args)
44
+ @argv_reconstructor = ArgvReconstructor.new
45
+
40
46
  original_base_path = Dir.pwd
41
47
  unless args[:config_intended_for_merge_only]
42
48
  change_working_directory_because_i_am_bad_and_i_should_feel_bad!(args[:base_path])
43
- args = merge_dotfile_args(args) unless args[:no_dotfile]
49
+ args = merge_dotfile_args(args) unless args[:config_path].nil?
44
50
  end
45
51
  args = undefault_parallel_if_seed_set(args)
46
52
  unless args[:config_intended_for_merge_only]
@@ -54,50 +60,55 @@ class TLDR
54
60
  # These are for internal tracking and resolved at initialization-time
55
61
  undef_method :config_intended_for_merge_only=, :seed_set_intentionally=,
56
62
  # These must be set when the Config is first initialized
57
- :cli_defaults=, :no_dotfile=, :base_path=
63
+ :cli_defaults=, :config_path=, :base_path=
58
64
 
59
65
  def self.build_defaults cli_defaults: true
60
66
  common = {
61
- seed: rand(10_000),
62
- no_helper: false,
63
- verbose: false,
64
- print_interrupted_test_backtraces: false,
65
- reporter: Reporters::Default,
67
+ timeout: -1,
68
+ watch: false,
69
+ fail_fast: false,
66
70
  parallel: true,
71
+ seed: rand(10_000),
67
72
  names: [],
68
- fail_fast: false,
69
- no_emoji: false,
70
- no_prepend: false,
71
- exclude_paths: [],
72
73
  exclude_names: [],
74
+ exclude_paths: [],
75
+ no_helper: false,
76
+ no_prepend: false,
73
77
  base_path: nil,
78
+ reporter: "TLDR::Reporters::Default",
79
+ emoji: false,
74
80
  warnings: true,
75
- watch: false,
81
+ verbose: false,
76
82
  yes_i_know: false,
77
- i_am_being_watched: false
83
+ print_interrupted_test_backtraces: false,
84
+ i_am_being_watched: false,
85
+ exit_0_on_timeout: false,
86
+ exit_2_on_failure: false
78
87
  }
79
88
 
80
89
  if cli_defaults
81
90
  common.merge(
82
- paths: Dir["test/**/*_test.rb", "test/**/test_*.rb"],
83
91
  helper_paths: ["test/helper.rb"],
92
+ prepend_paths: [MOST_RECENTLY_MODIFIED_TAG],
84
93
  load_paths: ["lib", "test"],
85
- prepend_paths: [MOST_RECENTLY_MODIFIED_TAG]
94
+ config_path: nil,
95
+ paths: Dir["test/**/*_test.rb", "test/**/test_*.rb"]
86
96
  )
87
97
  else
88
98
  common.merge(
89
- paths: [],
90
99
  helper_paths: [],
100
+ prepend_paths: [],
91
101
  load_paths: [],
92
- prepend_paths: []
102
+ config_path: Config::DEFAULT_YAML_PATH, # ArgvParser#parse will set this default and if it sets nil that is intentionally blank b/c --no-config
103
+ paths: []
93
104
  )
94
105
  end
95
106
  end
96
107
 
97
108
  def undefault_parallel_if_seed_set args
98
109
  args.merge(
99
- seed_set_intentionally: !args[:seed].nil?,
100
- parallel: (args[:parallel].nil? ? args[:seed].nil? : args[:parallel])
110
+ parallel: (args[:parallel].nil? ? args[:seed].nil? : args[:parallel]),
111
+ seed_set_intentionally: !args[:seed].nil?
101
112
  )
102
113
  end
103
114
 
@@ -106,17 +117,17 @@ class TLDR
106
117
  defaults = Config.build_defaults(cli_defaults: merged_args[:cli_defaults])
107
118
 
108
119
  # Arrays
109
- [:paths, :helper_paths, :load_paths, :names, :prepend_paths, :exclude_paths, :exclude_names].each do |key|
120
+ [:names, :exclude_names, :exclude_paths, :helper_paths, :prepend_paths, :load_paths, :paths].each do |key|
110
121
  merged_args[key] = defaults[key] if merged_args[key].nil? || merged_args[key].empty?
111
122
  end
112
123
 
113
124
  # Booleans
114
- [:no_helper, :verbose, :print_interrupted_test_backtraces, :fail_fast, :no_emoji, :no_prepend, :warnings, :yes_i_know, :i_am_being_watched].each do |key|
125
+ [:watch, :fail_fast, :parallel, :no_helper, :no_prepend, :emoji, :warnings, :verbose, :yes_i_know, :print_interrupted_test_backtraces, :i_am_being_watched, :exit_0_on_timeout, :exit_2_on_failure].each do |key|
115
126
  merged_args[key] = defaults[key] if merged_args[key].nil?
116
127
  end
117
128
 
118
129
  # Values
119
- [:seed, :reporter].each do |key|
130
+ [:timeout, :seed, :base_path, :config_path, :reporter].each do |key|
120
131
  merged_args[key] ||= defaults[key]
121
132
  end
122
133
 
@@ -147,87 +158,20 @@ class TLDR
147
158
  end
148
159
 
149
160
  def to_full_args exclude: [], ensure_args: [], exclude_dotfile_matches: false
150
- argv = to_cli_argv(
151
- CONFLAGS.keys - exclude - [
152
- (:seed unless seed_set_intentionally),
153
- :watch,
154
- :i_am_being_watched
155
- ],
156
- exclude_dotfile_matches:
157
- )
158
-
159
- ensure_args.each do |arg|
160
- argv << arg unless argv.include?(arg)
161
- end
162
-
163
- argv.join(" ")
161
+ @argv_reconstructor.reconstruct(self, exclude:, ensure_args:, exclude_dotfile_matches:)
164
162
  end
165
163
 
166
164
  def to_single_path_args path, exclude_dotfile_matches: false
167
- argv = to_cli_argv(CONFLAGS.keys - [
168
- :seed, :parallel, :names, :fail_fast, :paths, :prepend_paths,
169
- :no_prepend, :exclude_paths, :watch, :i_am_being_watched
170
- ], exclude_dotfile_matches:)
171
-
172
- (argv + [stringify(:paths, path)]).join(" ")
165
+ @argv_reconstructor.reconstruct_single_path_args(self, path, exclude_dotfile_matches:)
173
166
  end
174
167
 
175
- private
176
-
177
- def to_cli_argv options = CONFLAGS.keys, exclude_dotfile_matches:
178
- defaults = Config.build_defaults(cli_defaults: true)
179
- defaults = defaults.merge(dotfile_args) if exclude_dotfile_matches
180
- options.map { |key|
181
- flag = CONFLAGS[key]
182
-
183
- # Special cases
184
- if key == :prepend_paths
185
- if prepend_paths.map { |s| stringify(key, s) }.sort == paths.map { |s| stringify(:paths, s) }.sort
186
- # Don't print prepended tests if they're the same as the test paths
187
- next
188
- elsif no_prepend
189
- # Don't print prepended tests if they're disabled
190
- next
191
- end
192
- elsif key == :helper_paths && no_helper
193
- # Don't print the helper if it's disabled
194
- next
195
- elsif key == :parallel
196
- val = if !seed_set_intentionally && !parallel
197
- "--no-parallel"
198
- elsif !seed.nil? && seed_set_intentionally && parallel
199
- "--parallel"
200
- end
201
- next val
202
- elsif key == :warnings && defaults[:warnings] != self[:warnings]
203
- next warnings ? "--warnings" : "--no-warnings"
204
- end
168
+ def dotfile_args config_path
169
+ return {} unless File.exist?(config_path)
205
170
 
206
- if defaults[key] == self[key] && (key != :seed || !seed_set_intentionally)
207
- next
208
- elsif self[key].is_a?(Array)
209
- self[key].map { |value| [flag, stringify(key, value)] }
210
- elsif self[key].is_a?(TrueClass) || self[key].is_a?(FalseClass)
211
- flag if self[key]
212
- elsif self[key].is_a?(Class)
213
- [flag, self[key].name]
214
- elsif !self[key].nil?
215
- [flag, stringify(key, self[key])]
216
- end
217
- }.flatten.compact
171
+ @dotfile_args ||= YamlParser.new.parse(config_path)
218
172
  end
219
173
 
220
- def stringify key, val
221
- if PATH_FLAGS.include?(key) && val.start_with?(Dir.pwd)
222
- val = val[Dir.pwd.length + 1..]
223
- end
224
-
225
- if val.nil? || val.is_a?(Integer)
226
- val
227
- else
228
- "\"#{val}\""
229
- end
230
- end
174
+ private
231
175
 
232
176
  def most_recently_modified_test_file tests
233
177
  return if tests.empty?
@@ -248,28 +192,10 @@ class TLDR
248
192
  end
249
193
 
250
194
  def merge_dotfile_args args
251
- return args if args[:no_dotfile]
252
-
253
- dotfile_args.merge(args)
254
- end
255
-
256
- def dotfile_args
257
- return {} unless File.exist?(".tldr.yml")
258
-
259
- require "yaml"
260
- @dotfile_args ||= YAML.load_file(".tldr.yml").transform_keys { |k| k.to_sym }.tap do |dotfile_args|
261
- # Since we don't have shell expansion, we have to glob any paths ourselves
262
- if dotfile_args.key?(:paths)
263
- dotfile_args[:paths] = dotfile_args[:paths].flat_map { |path| Dir[path] }
264
- end
265
- # The argv parser normally does this:
266
- if dotfile_args.key?(:reporter)
267
- dotfile_args[:reporter] = Kernel.const_get(dotfile_args[:reporter])
268
- end
269
- if (invalid_args = dotfile_args.except(*CONFIG_ATTRIBUTES)).any?
270
- raise Error, "Invalid keys in .tldr.yml file: #{invalid_args.keys.join(", ")}"
271
- end
272
- end
195
+ dotfile_args(args[:config_path]).merge(args)
273
196
  end
274
197
  end
198
+
199
+ Config::DEFAULT_YAML_PATH = ".tldr.yml"
200
+ Config::DEFAULT_TIMEOUT = 1.8
275
201
  end
@@ -10,7 +10,7 @@ class TLDR
10
10
 
11
11
  # Test exact match starting line condition first to save us a potential re-parsing to look up end_line
12
12
  def covers_line? l
13
- line == l || (l >= line && l <= end_line)
13
+ line == l || l.between?(line, end_line)
14
14
  end
15
15
 
16
16
  def group?
data/lib/tldr/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  class TLDR
2
- VERSION = "0.10.1"
2
+ VERSION = "1.1.0"
3
3
  end
@@ -0,0 +1,29 @@
1
+ require "optparse"
2
+
3
+ class TLDR
4
+ class YamlParser
5
+ def parse path
6
+ require "yaml"
7
+ YAML.load_file(path)
8
+ .transform_keys { |k| k.to_sym }
9
+ .tap do |dotfile_args|
10
+ # Since we don't have shell expansion, we have to glob any paths ourselves
11
+ if dotfile_args.key?(:paths)
12
+ dotfile_args[:paths] = dotfile_args[:paths].flat_map { |path| Dir[path] }
13
+ end
14
+ if dotfile_args.key?(:timeout)
15
+ dotfile_args[:timeout] = case dotfile_args[:timeout]
16
+ when true then Config::DEFAULT_TIMEOUT
17
+ when false then -1
18
+ when String then Float(dotfile_args[:timeout])
19
+ else dotfile_args[:timeout]
20
+ end
21
+ end
22
+
23
+ if (invalid_args = dotfile_args.except(*CONFIG_ATTRIBUTES)).any?
24
+ raise Error, "Invalid keys in #{File.basename(path)} file: #{invalid_args.keys.join(", ")}"
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
data/lib/tldr.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  require "concurrent-ruby"
2
2
 
3
3
  require_relative "tldr/argv_parser"
4
+ require_relative "tldr/argv_reconstructor"
4
5
  require_relative "tldr/assertions"
5
6
  require_relative "tldr/backtrace_filter"
6
7
  require_relative "tldr/class_util"
@@ -10,6 +11,7 @@ require_relative "tldr/hooks"
10
11
  require_relative "tldr/parallel_controls"
11
12
  require_relative "tldr/path_util"
12
13
  require_relative "tldr/planner"
14
+ require_relative "tldr/minitest_compatibility"
13
15
  require_relative "tldr/reporters"
14
16
  require_relative "tldr/ruby_util"
15
17
  require_relative "tldr/runner"
@@ -19,6 +21,7 @@ require_relative "tldr/strategizer"
19
21
  require_relative "tldr/value"
20
22
  require_relative "tldr/version"
21
23
  require_relative "tldr/watcher"
24
+ require_relative "tldr/yaml_parser"
22
25
 
23
26
  class TLDR
24
27
  include Assertions
data/script/test CHANGED
@@ -5,10 +5,10 @@ set -e
5
5
  bundle exec rake
6
6
 
7
7
  cd example/a
8
- bundle exec tldr | grep "😁😁"
8
+ bundle exec tldr | ruby -e 'exit(ARGF.read =~ /\n..\n/ ? 0 : 1)'
9
9
  cd ../..
10
10
 
11
11
  cd example/b
12
- bundle exec ruby -Itest test/some_test.rb | grep "😁"
13
- bundle exec rake tldr | grep "😁"
12
+ bundle exec ruby -Itest test/some_test.rb | ruby -e 'exit(ARGF.read =~ /\n.\n/ ? 0 : 1)'
13
+ bundle exec rake tldr | ruby -e 'exit(ARGF.read =~ /\n.\n/ ? 0 : 1)'
14
14
  cd ../..
metadata CHANGED
@@ -1,14 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tldr
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.10.1
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Justin Searls
8
8
  - Aaron Patterson
9
+ autorequire:
9
10
  bindir: exe
10
11
  cert_chain: []
11
- date: 2025-03-26 00:00:00.000000000 Z
12
+ date: 2025-07-13 00:00:00.000000000 Z
12
13
  dependencies:
13
14
  - !ruby/object:Gem::Dependency
14
15
  name: super_diff
@@ -38,6 +39,21 @@ dependencies:
38
39
  - - "~>"
39
40
  - !ruby/object:Gem::Version
40
41
  version: '1.2'
42
+ - !ruby/object:Gem::Dependency
43
+ name: irb
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - "~>"
47
+ - !ruby/object:Gem::Version
48
+ version: '1.10'
49
+ type: :runtime
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - "~>"
54
+ - !ruby/object:Gem::Version
55
+ version: '1.10'
56
+ description:
41
57
  email:
42
58
  - searls@gmail.com
43
59
  - tenderlove@ruby-lang.org
@@ -56,13 +72,15 @@ files:
56
72
  - exe/tldt
57
73
  - lib/tldr.rb
58
74
  - lib/tldr/argv_parser.rb
75
+ - lib/tldr/argv_reconstructor.rb
59
76
  - lib/tldr/assertions.rb
60
- - lib/tldr/assertions/minitest_compatibility.rb
77
+ - lib/tldr/autorun.rb
61
78
  - lib/tldr/backtrace_filter.rb
62
79
  - lib/tldr/class_util.rb
63
80
  - lib/tldr/error.rb
64
81
  - lib/tldr/executor.rb
65
82
  - lib/tldr/hooks.rb
83
+ - lib/tldr/minitest_compatibility.rb
66
84
  - lib/tldr/parallel_controls.rb
67
85
  - lib/tldr/path_util.rb
68
86
  - lib/tldr/planner.rb
@@ -86,6 +104,7 @@ files:
86
104
  - lib/tldr/value/wip_test.rb
87
105
  - lib/tldr/version.rb
88
106
  - lib/tldr/watcher.rb
107
+ - lib/tldr/yaml_parser.rb
89
108
  - script/setup
90
109
  - script/test
91
110
  - script/upgrade
@@ -96,6 +115,7 @@ metadata:
96
115
  homepage_uri: https://github.com/tendersearls/tldr
97
116
  source_code_uri: https://github.com/tendersearls/tldr
98
117
  changelog_uri: https://github.com/tendersearls/tldr/blob/main/CHANGELOG.md
118
+ post_install_message:
99
119
  rdoc_options: []
100
120
  require_paths:
101
121
  - lib
@@ -110,7 +130,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
110
130
  - !ruby/object:Gem::Version
111
131
  version: '0'
112
132
  requirements: []
113
- rubygems_version: 3.6.2
133
+ rubygems_version: 3.3.26
134
+ signing_key:
114
135
  specification_version: 4
115
136
  summary: TLDR will run your tests, but only for 1.8 seconds.
116
137
  test_files: []
@@ -1,55 +0,0 @@
1
- # These methods are provided only for drop-in compatibility with Minitest:
2
- #
3
- # require "tldr/assertions/minitest"
4
- #
5
- # Will load these methods for use in your tests
6
- #
7
- # While all the methods in this file were written for TLDR, they were designed
8
- # to maximize compatibility with minitest's assertions API and messages here:
9
- #
10
- # https://github.com/minitest/minitest/blob/master/lib/minitest/assertions.rb
11
- #
12
- # As a result, many implementations are extremely similar to those found in
13
- # minitest. Any such implementations are Copyright © Ryan Davis, seattle.rb and
14
- # distributed under the MIT License
15
-
16
- class TLDR
17
- module Assertions
18
- module MinitestCompatibility
19
- def assert_includes actual, expected, message = nil
20
- assert_include? expected, actual, message
21
- end
22
-
23
- def refute_includes actual, expected, message = nil
24
- refute_include? expected, actual, message
25
- end
26
-
27
- def assert_send receiver_method_args, message = nil
28
- warn "DEPRECATED: assert_send. From #{TLDR.filter_backtrace(caller).first}"
29
- receiver, method, *args = receiver_method_args
30
- message = Assertions.msg(message) {
31
- "Expected #{Assertions.h(receiver)}.#{method}(*#{Assertions.h(args)}) to return true"
32
- }
33
-
34
- assert receiver.__send__(method, *args), message
35
- end
36
-
37
- def capture_io &blk
38
- Assertions.capture_io(&blk)
39
- end
40
-
41
- def mu_pp obj
42
- s = obj.inspect.encode(Encoding.default_external)
43
-
44
- if String === obj && (obj.encoding != Encoding.default_external ||
45
- !obj.valid_encoding?)
46
- enc = "# encoding: #{obj.encoding}"
47
- val = "# valid: #{obj.valid_encoding?}"
48
- "#{enc}\n#{val}\n#{s}"
49
- else
50
- s
51
- end
52
- end
53
- end
54
- end
55
- end