diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f2624ef..9982905 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -37,6 +37,8 @@ jobs: run: just lint - name: test run: just test + - name: examples + run: just examples - name: doc run: just doc - name: package gem diff --git a/.rubocop.yml b/.rubocop.yml index ad7c4e2..815ad1c 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -79,6 +79,9 @@ Style/Lambda: Style/LambdaCall: EnforcedStyle: braces +Style/MultilineBlockChain: + Enabled: false + Style/TrailingCommaInArrayLiteral: EnforcedStyleForMultiline: comma diff --git a/CHANGELOG.md b/CHANGELOG.md index 75f044c..f679e6c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,23 @@ ## [unreleased] +- Deprarture from the `ruby/pretty_print` API with Wadler: + - accept `indent` which will apply to `nest` and `group` implicitly if no + `indent:` kwarg is passed. + - accept `base_indent` which sets the base global indentation level for + the whole printer. + - all printing methods return `self` for call chaining. +- Wadler: + - rename ctor param `space` to `space_gen`. + - add `consistent` and `inconsistent` as shorthands to + `group(:consistent)` and `group(:inconsistent)`, respectively. + - remove args of `group_close`. + - add `do` to avoid braking call chains. + - `group_open` accepts a symbol `break_type` instead of a boolean. + - add `separate` to separate a list of items. + - add `space` to generate a literal space. + - add `surround` as a glorified `group` that add breakables next to delimiters. + ## v0.9.8 (30-12-2024) - Oppen now supports Ruby 3.0+. It used to be restricted to Ruby 3.2+. diff --git a/bin/repl.rb b/bin/repl.rb index 955df9e..d7f4609 100644 --- a/bin/repl.rb +++ b/bin/repl.rb @@ -3,17 +3,12 @@ require 'colored' # Fancy -WELCOME = <<-'YGM' - __ __ ___ __ __ ____ ____ ___ ___ ___ ____ ____ ___ ___ ___ - | | | / \ | | || \ / | / _]| | || \ / || | | / _] - | | || || | || D )| __| / [_ | _ _ || _ || o || _ _ | / [_ - | ~ || O || | || / | | || _]| \_/ || | || || \_/ || _] - |___, || || : || \ | |_ || [_ | | || | || _ || | || [_ - | || || || . \| ______ ______ ______ __ || | - |____/ \___/ \__,_||__|\_||___ /\ == \ /\ ___\ /\ == \ /\ \ ___||_____| - \ \ __< \ \ __\ \ \ _-/ \ \ \____ - \ \_\ \_\ \ \_____\ \ \_\ \ \_____\ - \/_/ /_/ \/_____/ \/_/ \/_____/ +WELCOME = <<~YGM + ___ ___ ___ ___ ___ + | . | . | . | -_| | + |___| _| _|___|_|_| + |_| |_| + YGM puts WELCOME.green diff --git a/examples/configs/eager_print.rb b/examples/configs/eager_print.rb old mode 100644 new mode 100755 index d2334a3..877b535 --- a/examples/configs/eager_print.rb +++ b/examples/configs/eager_print.rb @@ -24,7 +24,7 @@ title 'With eager printing:' puts printer_with_config.output # abc defghi -# jkl +# jkl puts '' diff --git a/examples/configs/indent_anchor.rb b/examples/configs/indent_anchor.rb old mode 100644 new mode 100755 index 09c9fc7..6eb2b84 --- a/examples/configs/indent_anchor.rb +++ b/examples/configs/indent_anchor.rb @@ -5,17 +5,19 @@ printer_with_config = Oppen::Wadler.new( config: Oppen::Config.new(indent_anchor: :current_offset), + indent: 4, width: 13, ) printer_no_config = Oppen::Wadler.new( config: Oppen::Config.new(indent_anchor: :end_of_previous_line), + indent: 4, width: 13, ) test_block = ->(printer) { printer.text 'And she said:' - printer.group(4) { - printer.group(4) { + printer.group { + printer.group { printer.break printer.text 'Hello, World!' } diff --git a/examples/configs/trim_trailing_whitespaces.rb b/examples/configs/trim_trailing_whitespaces.rb old mode 100644 new mode 100755 diff --git a/examples/configs/upsize_stack.rb b/examples/configs/upsize_stack.rb old mode 100644 new mode 100755 diff --git a/examples/oppen/oppen.rb b/examples/oppen/oppen.rb old mode 100644 new mode 100755 diff --git a/examples/oppen_and_wadler_customization/new_line.rb b/examples/oppen_and_wadler_customization/new_line.rb old mode 100644 new mode 100755 index 4412031..b1ba64f --- a/examples/oppen_and_wadler_customization/new_line.rb +++ b/examples/oppen_and_wadler_customization/new_line.rb @@ -5,9 +5,9 @@ # The new line String can be specified using the `new_line` parameter. new_line = '
' -printer = Oppen::Wadler.new(new_line: new_line) +printer = Oppen::Wadler.new(indent: 2, new_line: new_line) -printer.group(2) { +printer.group { printer.text 'Hello, World!' printer.break printer.text 'How are you doing?' diff --git a/examples/oppen_and_wadler_customization/space.rb b/examples/oppen_and_wadler_customization/space.rb old mode 100644 new mode 100755 index 37e30bb..0bd8a5e --- a/examples/oppen_and_wadler_customization/space.rb +++ b/examples/oppen_and_wadler_customization/space.rb @@ -9,13 +9,13 @@ # By using a callable: space = ->(n) { '---' * n } -printer = Oppen::Wadler.new(space: space) +printer = Oppen::Wadler.new(indent: 2, space_gen: space) -printer.group(2) { +printer.group { printer.text 'Hello, World!' printer.break printer.text 'How are you doing?' - printer.group(2) { + printer.group { printer.break printer.text 'I am fine' } diff --git a/examples/oppen_and_wadler_customization/whitespace.rb b/examples/oppen_and_wadler_customization/whitespace.rb old mode 100644 new mode 100755 index f21734c..2348028 --- a/examples/oppen_and_wadler_customization/whitespace.rb +++ b/examples/oppen_and_wadler_customization/whitespace.rb @@ -9,9 +9,9 @@ # to prevent trailing whitespaces can be specified using the `whitespace` parameter. whitespace = '**' -printer = Oppen::Wadler.new(whitespace: whitespace) +printer = Oppen::Wadler.new(indent: 2, whitespace: whitespace) -printer.group(2) { +printer.group { printer.text '******Hello, World!******' printer.break } diff --git a/examples/oppen_and_wadler_customization/width.rb b/examples/oppen_and_wadler_customization/width.rb old mode 100644 new mode 100755 index 440e914..8c4814f --- a/examples/oppen_and_wadler_customization/width.rb +++ b/examples/oppen_and_wadler_customization/width.rb @@ -5,10 +5,10 @@ # The maximum width can be specified using the `width` parameter. width = 5 -printer_width_default = Oppen::Wadler.new -printer_width_narrow = Oppen::Wadler.new(width: width) +printer_width_default = Oppen::Wadler.new(indent: 2) +printer_width_narrow = Oppen::Wadler.new(indent: 2, width: width) test_block = ->(printer) { - printer.group(2) { + printer.group { printer.text 'Hello, World!' printer.breakable printer.text 'How are you doing?' diff --git a/examples/show_print_commands/show_print_commands.rb b/examples/show_print_commands/show_print_commands.rb old mode 100644 new mode 100755 index 2a42568..3217df1 --- a/examples/show_print_commands/show_print_commands.rb +++ b/examples/show_print_commands/show_print_commands.rb @@ -4,19 +4,21 @@ printer = Oppen::Wadler.new -printer.group(2) { +printer.group(indent: 2) { printer.text 'Hello, World!' - printer.nest(4) { + printer.nest(indent: 4) { printer.break printer.text 'GoodBye, World!' } } puts printer.show_print_commands -# out.group(2, "", "", :consistent) { -# out.text("Hello, World!", width: 13) -# out.nest(4, "", "") { -# out.break(line_continuation: "") +# out.group(:consistent, indent: 0) { +# out.group(:consistent, indent: 2) { +# out.text("Hello, World!", width: 13) +# out.nest(indent: 4) { +# out.break(line_continuation: "") +# } +# out.text("GoodBye, World!", width: 15) # } -# out.text("GoodBye, World!", width: 15) # } diff --git a/examples/wadler_break_and_breakable/break.rb b/examples/wadler_break_and_breakable/break.rb old mode 100644 new mode 100755 diff --git a/examples/wadler_break_and_breakable/breakable.rb b/examples/wadler_break_and_breakable/breakable.rb old mode 100644 new mode 100755 index 07830f5..2197592 --- a/examples/wadler_break_and_breakable/breakable.rb +++ b/examples/wadler_break_and_breakable/breakable.rb @@ -8,7 +8,7 @@ printer = Oppen::Wadler.new(width: 40) # See `examples/wadler_group/inconsistent.rb` for more infos about `:inconsistent` -printer.group(0, '', '', :inconsistent) { +printer.group(:inconsistent) { printer.text 'Hello, World!' printer.breakable printer.text '(still fits on the line)' diff --git a/examples/wadler_break_and_breakable/line_continuation.rb b/examples/wadler_break_and_breakable/line_continuation.rb old mode 100644 new mode 100755 diff --git a/examples/wadler_group/consistent.rb b/examples/wadler_group/consistent.rb old mode 100644 new mode 100755 index a946b66..6607a80 --- a/examples/wadler_group/consistent.rb +++ b/examples/wadler_group/consistent.rb @@ -8,7 +8,7 @@ printer = Oppen::Wadler.new(width: 999_999) # Groups are consistent by default. -printer.group { +printer.consistent { # alias for printer.group(:consistent) and same as printer.group printer.text 'Hello, World!' printer.breakable printer.text 'How are you doing?' diff --git a/examples/wadler_group/delimiters.rb b/examples/wadler_group/delimiters.rb old mode 100644 new mode 100755 index fc2c22a..a31caf9 --- a/examples/wadler_group/delimiters.rb +++ b/examples/wadler_group/delimiters.rb @@ -2,10 +2,10 @@ require_relative '../helper' -printer = Oppen::Wadler.new(width: 999_999) +printer = Oppen::Wadler.new(indent: 2, width: 999_999) # Groups have no delimiters by default. -printer.group(2, '<<', '>>') { +printer.group(delim: %w[<< >>]) { printer.text 'Hello, World!' printer.break printer.text 'How are you doing?' @@ -16,6 +16,7 @@ } puts printer.output +# # <>') { +printer.nest(delim: %w[<< >>]) { printer.text 'Hello, World!' printer.break printer.text 'How are you doing?' diff --git a/examples/wadler_nest/indentation.rb b/examples/wadler_nest/indentation.rb old mode 100644 new mode 100755 index 525e45d..5e0f9e0 --- a/examples/wadler_nest/indentation.rb +++ b/examples/wadler_nest/indentation.rb @@ -2,9 +2,9 @@ require_relative '../helper' -printer = Oppen::Wadler.new(width: 999_999) +printer = Oppen::Wadler.new(indent: 4, width: 999_999) -printer.nest(4) { +printer.nest { printer.text 'Hello, World!' printer.break printer.text 'How are you doing?' diff --git a/examples/wadler_utils/separate.rb b/examples/wadler_utils/separate.rb new file mode 100755 index 0000000..8d52ef4 --- /dev/null +++ b/examples/wadler_utils/separate.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require_relative '../helper' + +# You might want to also take a look at the `lines` and `concat` methods. + +printer = Oppen::Wadler.new(width: 10) + +printer.separate((1..10).map(&:to_s), ',', break_type: :inconsistent, indent: 2) { |i| + printer.text i +} + +puts printer.output +# 1, 2, 3, +# 4, 5, 6, +# 7, 8, 9, +# 10 diff --git a/examples/wadler_utils/surround.rb b/examples/wadler_utils/surround.rb new file mode 100755 index 0000000..c06a713 --- /dev/null +++ b/examples/wadler_utils/surround.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +require_relative '../helper' + +# You might want to also take a look at the `parens`, `parens_break_both`, `angles`, ... methods. + +printer = Oppen::Wadler.new(width: 10) + +printer.surround('<<', '>>', indent: 2, lft_force_break: true, rgt_force_break: true) { + printer.text '42' +} + +puts printer.output +# << +# 42 +# >> diff --git a/justfile b/justfile index f2105a0..f7b5eb8 100644 --- a/justfile +++ b/justfile @@ -30,6 +30,12 @@ doc: doc-stats: bundle exec yard stats --list-undoc +[group('test')] +examples: + find examples/ -mindepth 2 -type f -name "*.rb" | while IFS= read -r file; do \ + bundle exec ruby "$file" > /dev/null || exit 1; \ + done + [group('publish')] gem: mkdir -p {{PKG_OUT}} diff --git a/lib/oppen.rb b/lib/oppen.rb index 5897ce3..1a0c3c5 100644 --- a/lib/oppen.rb +++ b/lib/oppen.rb @@ -64,8 +64,8 @@ class Config # config = Oppen::Config.new(indent_anchor: :end_of_previous_line) # out = Oppen::Wadler.new config:, width: 13 # out.text 'And she said:' - # out.group(4) { - # out.group(4) { + # out.group(indent: 4) { + # out.group(indent: 4) { # out.break # out.text 'Hello, World!' # } @@ -80,8 +80,8 @@ class Config # config = Oppen::Config.new(indent_anchor: :current_offset) # out = Oppen::Wadler.new config:, width: 13 # out.text 'And she said:' - # out.group(4) { - # out.group(4) { + # out.group(indent: 4) { + # out.group(indent: 4) { # out.break # out.text 'Hello, World!' # } @@ -123,7 +123,7 @@ def initialize(eager_print: false, indent_anchor: :end_of_previous_line, # # # # eager_print: true => # # abc defghi - # # jkl + # # jkl # # @return [Boolean] def eager_print? = @eager_print diff --git a/lib/oppen/mixins.rb b/lib/oppen/mixins.rb index 33550b6..2f30bb5 100644 --- a/lib/oppen/mixins.rb +++ b/lib/oppen/mixins.rb @@ -24,15 +24,14 @@ def upsize_circular_array(arr, offset) # @return [String] def tokens_to_wadler(tokens, base_indent: 0, printer_name: 'out', width: tokens.length * 3) - printer = Oppen::Wadler.new(width: width) - printer.base_indent(base_indent) - indent = 2 + printer = Oppen::Wadler.new(base_indent: base_indent, indent: 2, width: width) handle_break_token = ->(token) { if token.offset.positive? - printer.text "#{printer_name}.nest(#{token.offset}, '', '') {" - printer.nest_open indent - printer.break + printer + .text("#{printer_name}.nest(indent: #{token.offset}) {") + .nest_open + .break end printer.text( @@ -45,11 +44,7 @@ def tokens_to_wadler(tokens, base_indent: 0, printer_name: 'out', width: tokens. end, ) - if token.offset.positive? - printer.nest_close indent - printer.break - printer.text '}' - end + printer.nest_close.break.text '}' if token.offset.positive? } tokens.each_with_index do |token, idx| @@ -59,17 +54,17 @@ def tokens_to_wadler(tokens, base_indent: 0, printer_name: 'out', width: tokens. in Token::Break handle_break_token.(token) in Token::Begin - printer.text "#{printer_name}.group(#{token.offset}, '', '', #{token.break_type.inspect}) {" - printer.nest_open indent + printer + .text("#{printer_name}.group(#{token.break_type.inspect}, indent: #{token.offset}) {") + .nest_open in Token::End - printer.nest_close indent - printer.break - printer.text '}' + printer.nest_close.break.text '}' in Token::EOF nil end printer.break if !tokens[idx + 1].is_a?(Token::End) end + printer.output end end diff --git a/lib/wadler/print.rb b/lib/wadler/print.rb index de28274..2ead099 100644 --- a/lib/wadler/print.rb +++ b/lib/wadler/print.rb @@ -18,7 +18,7 @@ class Wadler attr_reader :out # @return [Proc] # space generator, a callable. - attr_reader :space + attr_reader :space_gen # @return [Array] # the tokens list that is being built. attr_reader :tokens @@ -29,13 +29,17 @@ class Wadler # maximum line width. attr_reader :width + # @param base_indent [Integer] + # the starting indentation level for the whole printer. # @param config [Config] # to customize the printer's behavior. + # @param indent [Integer] + # the default indentation amount for {group} and {nest}. # @param new_line [String] # the new line String. # @param out [Object] # the output string buffer. It should have a `write` and `string` methods. - # @param space [String, Proc] + # @param space_gen [String, Proc] # indentation string or a string generator. # - If a `String`, spaces will be generated with the the lambda # `->(n){ space * n }`, where `n` is the number of columns to indent. @@ -44,27 +48,26 @@ class Wadler # @param width [Integer] maximum line width desired. # # @see Token::Whitespace - def initialize(config: Config.wadler, new_line: "\n", - out: StringIO.new, space: ' ', + def initialize(base_indent: 0, config: Config.wadler, indent: 0, new_line: "\n", + out: StringIO.new, space_gen: ' ', whitespace: ' ', width: 80) @config = config - @current_indent = 0 - @space = space - @width = width + @current_indent = base_indent + @indent = indent @new_line = new_line @out = out + @space_gen = space_gen @tokens = [] @whitespace = whitespace + @width = width end # Add missing {Token::Begin}, {Token::End} or {Token::EOF}. # # @return [Nil] def add_missing_begin_and_end - if !tokens.first.is_a? Token::Begin - tokens.unshift Oppen.begin_consistent(offset: 0) - tokens << Oppen.end - end + tokens.unshift Oppen.begin_consistent(offset: 0) + tokens << Oppen.end tokens << Oppen.eof if !tokens.last.is_a?(Oppen::Token::EOF) end @@ -77,7 +80,7 @@ def output tokens: tokens, new_line: new_line, config: config, - space: space, + space: space_gen, out: out, width: width, ) @@ -96,13 +99,11 @@ def output # # @example # out = Oppen::Wadler.new - # out.group { - # out.text('Hello World!') - # } + # out.text('Hello World!') # out.show_print_commands(out_name: 'out') # # # => - # # out.group(0, "", "", :consistent) { + # # out.group(:consistent, indent: 0) { # # out.text("Hello World!", width: 12) # # } # @@ -114,26 +115,57 @@ def show_print_commands(**kwargs) # Create a new group. # - # @param indent [Integer] + # @param indent [Integer] # indentation. - # @param open_obj [String] - # opening delimiter. - # @param close_obj [String] - # closing delimiter. + # @param delim [Nil|String|Symbol|Array] + # delimiters, to be printed at the start and the end of the group: + # - If it's nil, nothing will be printed + # - If it's a Strings or a Symbol, it will be printed at both positions. + # - If it's an Array of many items, the first two elements will be used + # for the start and end of the group. # @param break_type [Token::BreakType] # break type. # # @yield # the block of text in a group. # - # @example + # @example 1 String Delimiter # out = Oppen::Wadler.new - # out.text 'a' - # out.group(2, '{', '}') { - # out.break - # out.text 'b' - # } - # out.output + # out + # .text('a') + # .group(indent: 2, delim: '|') { + # out.break.text 'b' + # } + # puts out.output + # + # # => + # # a + # # | + # # b + # # | + # + # @example 1 Delimiter in Array + # out = Oppen::Wadler.new + # out + # .text('a') + # .group(indent: 2, delim: ['|']) { + # out.break.text 'b' + # } + # puts out.output + # + # # => + # # a + # # | + # # b + # + # @example 2 Delimiters + # out = Oppen::Wadler.new + # out + # .text('a') + # .group(indent: 2, delim: %i[{ }]) { + # out.break.text 'b' + # } + # puts out.output # # # => # # a @@ -143,14 +175,10 @@ def show_print_commands(**kwargs) # # @example Consistent Breaking # out = Oppen::Wadler.new - # out.group(0, '', '', :consistent) { - # out.text 'a' - # out.break - # out.text 'b' - # out.breakable - # out.text 'c' + # out.group(:consistent) { + # out.text('a').break.text('b').breakable.text('c') # } - # out.output + # puts out.output # # # => # # a @@ -159,27 +187,26 @@ def show_print_commands(**kwargs) # # @example Inconsistent Breaking # out = Oppen::Wadler.new - # out.group(0, '', '', :inconsistent) { - # out.text 'a' - # out.break - # out.text 'b' - # out.breakable - # out.text 'c' + # out.group(:inconsistent) { + # out.text('a').break.text('b').breakable.text('c') # } - # out.output + # puts out.output # # # => # # a # # b c # - # @return [Nil] + # @return [self] # # @see Oppen.begin_consistent # @see Oppen.begin_inconsistent - def group(indent = 0, open_obj = '', close_obj = '', - break_type = :consistent) - raise ArgumentError, "#{open_obj.nil? ? 'open_obj' : 'close_obj'} cannot be nil" \ - if open_obj.nil? || close_obj.nil? + def group(break_type = :consistent, delim: nil, indent: @indent) + lft, rgt = + case delim + in nil then ['', ''] + in String | Symbol then [delim, delim] + in Array then delim.values_at(0, 1).map(&:to_s) + end tokens << case break_type @@ -189,19 +216,31 @@ def group(indent = 0, open_obj = '', close_obj = '', Oppen.begin_inconsistent(offset: indent) end - if !open_obj.empty? + if !lft.empty? self.break - text(open_obj) + text lft end yield - if !close_obj.empty? + if !rgt.empty? self.break - text(close_obj) + text rgt end tokens << Oppen.end + + self + end + + # An alias for `group(:consistent, ...)` + def consistent(...) + group(:consistent, ...) + end + + # An alias for `group(:inconsistent, ...)` + def inconsistent(...) + group(:inconsistent, ...) end # Create a new non-strict {group}. @@ -218,24 +257,24 @@ def group(indent = 0, open_obj = '', close_obj = '', # @note a {nest} will not forcibly indent its content if the break type of # the enclosing {group} is `:inconsistent`. # - # @param indent [Integer] + # @param delim [Nil|String|Symbol|Array] + # delimiters, to be printed at the start and the end of the group: + # - `nil` is always the empty string. + # - If it's a Strings or a Symbol, it will be printed at both positions. + # - If it's an Array of many items, the first two elements will be used + # for the start and end of the group. + # @param indent [Integer] # indentation. - # @param open_obj [String] - # opening delimiter. A {break} is implicitly slipped after it if it's not empty. - # @param close_obj [String] - # closing delimiter. A {break} is implicitly slipped before it if it's not empty. # # @yield # the block of text in a nest. # # @example # out = Oppen::Wadler.new - # out.nest(2, '{', '}') { - # out.text 'a' - # out.break - # out.text 'b' + # out.nest(delim: %i[{ }], indent: 2) { + # out.text('a').break.text('b') # } - # out.output + # puts out.output # # # => # # { @@ -243,15 +282,19 @@ def group(indent = 0, open_obj = '', close_obj = '', # # b # # } # - # @return [Nil] - def nest(indent, open_obj = '', close_obj = '') - raise ArgumentError, "#{open_obj.nil? ? 'open_obj' : 'close_obj'} cannot be nil" \ - if open_obj.nil? || close_obj.nil? + # @return [self] + def nest(delim: nil, indent: @indent) + lft, rgt = + case delim + in nil then ['', ''] + in String | Symbol then [delim, delim] + in Array then delim.values_at(0, 1).map(&:to_s) + end @current_indent += indent - if !open_obj.empty? - text(open_obj) + if !lft.empty? + text lft self.break end @@ -261,10 +304,12 @@ def nest(indent, open_obj = '', close_obj = '') @current_indent -= indent end - return if close_obj.empty? + if !rgt.empty? + self.break + text rgt + end - self.break - text(close_obj) + self end # Create a new text element. @@ -272,7 +317,7 @@ def nest(indent, open_obj = '', close_obj = '') # @param value [String] # the value of the token. # - # @return [Nil] + # @return [self] def text(value, width: value.length) if config.trim_trailing_whitespaces? && value.match(/((?:#{Regexp.escape(whitespace)})+)\z/) match = Regexp.last_match(1) @@ -284,6 +329,7 @@ def text(value, width: value.length) else tokens << Oppen.string(value, width: width) end + self end # Create a new breakable element. @@ -295,11 +341,12 @@ def text(value, width: value.length) # @param width [Integer] # the width of the token. # - # @return [Nil] + # @return [self] # # @see Wadler#break example on `line_continuation`. def breakable(str = ' ', line_continuation: '', width: str.length) tokens << Oppen.break(str, width: width, line_continuation: line_continuation, offset: current_indent) + self end # Create a new break element. @@ -314,55 +361,424 @@ def breakable(str = ' ', line_continuation: '', width: str.length) # out.text 'b' # out.break line_continuation: '#' # out.text 'c' - # out.output + # puts out.output # # # => # # a # # b# # # c # - # @return [Nil] + # @return [self] def break(line_continuation: '') tokens << Oppen.line_break(line_continuation: line_continuation, offset: current_indent) + self + end + + # A convenient way to avoid breaking chains of calls. + # + # @example + # out + # .do { fn_call(fn_arg) } + # .breakable + # .text('=') + # .breakable + # .do { fn_call(fn_arg) } + # + # @yield to execute the passed block + # + # @return [self] + def do + yield + self end - # @!group Helpers + # A means to wrap a piece of code in several ways. + # + # @example + # out + # .wrap { + # # all printing instructions here will be deferred. + # # they will be executed in `when` blocks by calling the `wrapped`. + # out.text(...) + # # ... + # } # This is "wrapped". + # .when(cond1){ |wrapped| + # # when cond1 is true you execute this block. + # out.text("before wrapped") + # # call the wrapped + # wrapped.call + # # and continue printing + # out.text("after wrapped) + # } + # .when(cond2){ |wrapped| + # # and you cand define many conditions. + # } + # .end + # + # @example Calling `end` is not needed if there's another call after the last `when`: + # out + # .wrap{...} # This is "wrapped". + # .when(cond1){ |wrapped| ... } + # .when(cond2){ |wrapped| ... } + # .text('foo') + # + # @return [Wrap] + def wrap(&blk) + Wrap.new(blk) + end - # Set a base indenetaion level for the printer. + # Produce a separated list. # - # @param indent [Integer] - # the amount of indentation. + # @example Consistent Breaking + # puts out.separate((1..3).map(&:to_s), ',') { |i| out.text i} # - # @return [Nil] - def base_indent(indent = 0) - @current_indent = indent if !indent.nil? + # # => + # # 1, + # # 2, + # # 3 + # + # @example Inconsistent Breaking + # puts out.separate((1..3).map(&:to_s), ',', break_type: :inconsistent) { |i| out.text i} + # + # # => + # # 1, 2, + # # 3 + # + # @param args [String] + # a list of values. + # @param sep [String] + # a separator. + # @param breakable [String|Nil] + # adds a `breakable` after the separator. + # @param break_pos [Symbol] + # whether to break :before or :after the seraparator. + # @param break_type [Symbol|Nil] + # whether the break is :consistent or :inconsistent. + # If nil is given, the tokens will not be surrounded by a group. + # @param indent [Boolean|Integer] + # - If `true`, indent by @indent. + # - If an 'Integer', indent by its value. + # @param force_break [Boolean] + # adds a `break` after the separator. + # @param line_continuation [String] + # string to display before new line. + # + # @yield to execute the passed block. + # + # @return [self] + def separate(args, sep, breakable: ' ', break_pos: :after, + break_type: nil, indent: false, + force_break: false, line_continuation: '') + if args.is_a?(Enumerator) ? args.count == 1 : args.length == 1 + yield(*args[0]) + return self + end + + first = true + wrap { + wrap { + args&.each do |*as| + if first + breakable '' if !line_continuation.empty? && break_pos == :after + first = false + elsif break_pos == :after + text sep + breakable(breakable, line_continuation: line_continuation) if breakable && !force_break + self.break(line_continuation: line_continuation) if force_break + else + breakable(breakable, line_continuation: line_continuation) if breakable && !force_break + self.break(line_continuation: line_continuation) if force_break + text sep + end + yield(*as) + end + } + .when(break_type) { |body| + group(break_type, indent: 0) { + body.() + } + } + .end + } + .when(indent) { |body| + nest(indent: indent.is_a?(Integer) ? indent : @indent) { + body.() + } + }.end + breakable('', line_continuation: line_continuation) if !line_continuation.empty? && !break_type + + self + end + + # A shorhand for `text ' '`. + # + # @return [self] + def space + text ' ' + end + + # Surround a block with +lft+ and +rgt+ + # + # @param lft [String] lft + # left surrounding string. + # @param rgt [String] rgt + # right surrounding string. + # + # @yield the passed block to be surrounded with `lft` and `rgt`. + # + # @option opts [Boolean] :group (true) + # whether to create a group enclosing `lft`, `rgt`, and the passed block. + # @option opts [Boolean] :indent (@indent) + # whether to indent the passed block. + # @option opts [String] :lft_breakable ('') + # left breakable string. + # @option opts [Boolean] :lft_can_break (true) + # injects `break` or `breakable` only if true; + # i.e. `lft_breakable` will be ignored if false. + # @option opts [Boolean] :lft_force_break (false) + # force break instead of using `lft_breakable`. + # @option opts [String] :rgt_breakable ('') + # right breakable string. + # @option opts [Boolean] :rgt_can_break (true) + # injects `break` or `breakable` only if true. + # i.e. `rgt_breakable` will be ignored if false. + # @option opts [Boolean] :rgt_force_break (false) + # force break instead of using `rgt_breakable`. + # + # @return [self] + def surround(lft, rgt, **opts) + group = opts.fetch(:group, true) + group_open(break_type: :inconsistent) if group + + text lft if lft + + indent = opts.fetch(:indent, @indent) + nest_open(indent: indent) + + lft_breakable = opts.fetch(:lft_breakable, '') + lft_can_break = opts.fetch(:lft_can_break, true) + lft_force_break = opts.fetch(:lft_force_break, false) + if lft && lft_can_break + if lft_force_break + self.break + else + breakable lft_breakable + end + end + + if block_given? + yield + end + + nest_close + + rgt_breakable = opts.fetch(:rgt_breakable, '') + rgt_can_break = opts.fetch(:rgt_can_break, true) + rgt_force_break = opts.fetch(:rgt_force_break, false) + if rgt + if rgt_can_break + if rgt_force_break + self.break + else + breakable rgt_breakable + end + end + text rgt + end + + group_close if group + + self + end + + # @!group Convenience Methods Built On {separate} + + # Separate args into lines. + # + # This is a wrapper around {separate} where `breakable: true`. + # + # @see [separate] + def lines(*args, **kwargs, &block) + separate(*args, **kwargs.merge(force_break: true), &block) + end + + # Concatenates args. + # + # This is a wrapper around {separate} where `breakable: false`. + # + # @see [separate] + def concat *args, **kwargs, &block + separate(*args, **kwargs.merge(breakable: false), &block) + end + + # @!endgroup + # @!group Convenience Methods Built On {surround} + + # YARD doesn't drop into blocks, so we can't use metaprogramming + # to generate all these functions, so we're copy-pastring. + + # {surround} with `< >`. New lines can appear after and before the delimiters. + # + # @param padding [String] ('') + # Passed to `lft_breakable` and `rgt_breakable`. + # + # @return [self] + def angles(padding: '', **kwargs, &block) + surround( + '<', '>', + **kwargs.merge(lft_breakable: padding, rgt_breakable: padding), + &block + ) + end + + # {surround} with `< >`. New lines cannot appear after and before the delimiters. + # + # @return [self] + def angles_break_both(**kwargs, &block) + angles(**kwargs.merge(lft_force_break: true, rgt_force_break: true), &block) + end + + # {surround} with `< >`. New lines will appear after and before the delimiters. + # + # @return [self] + def angles_break_none(**kwargs, &block) + angles(**kwargs.merge(lft_can_break: false, rgt_can_break: false), &block) + end + + # {surround} with `{ }`. New lines can appear after and before the delimiters. + # + # @param padding [String] ('') + # Passed to `lft_breakable` and `rgt_breakable`. + # + # @return [self] + def braces(padding: '', **kwargs, &block) + surround( + '{', '}', + **kwargs.merge(lft_breakable: padding, rgt_breakable: padding), + &block + ) + end + + # {surround} with `{ }`. New lines cannot appear after and before the delimiters. + # + # @return [self] + def braces_break_both(**kwargs, &block) + braces(**kwargs.merge(lft_force_break: true, rgt_force_break: true), &block) + end + + # {surround} with `{ }`. New lines will appear after and before the delimiters. + # + # @return [self] + def braces_break_none(**kwargs, &block) + braces(**kwargs.merge(lft_can_break: false, rgt_can_break: false), &block) + end + + # {surround} with `[ ]`. New lines can appear after and before the delimiters. + # + # @param padding [String] ('') + # Passed to `lft_breakable` and `rgt_breakable`. + # + # @return [self] + def brackets(padding: '', **kwargs, &block) + surround( + '[', ']', + **kwargs.merge(lft_breakable: padding, rgt_breakable: padding), + &block + ) + end + + # {surround} with `[ ]`. New lines cannot appear after and before the delimiters. + # + # @return [self] + def brackets_break_both(**kwargs, &block) + brackets(**kwargs.merge(lft_force_break: true, rgt_force_break: true), &block) + end + + # {surround} with `[ ]`. New lines will appear after and before the delimiters. + # + # @return [self] + def brackets_break_none(**kwargs, &block) + brackets(**kwargs.merge(lft_can_break: false, rgt_can_break: false), &block) + end + + # {surround} with `( )`. New lines can appear after and before the delimiters. + # + # @param padding [String] ('') + # Passed to `lft_breakable` and `rgt_breakable`. + # + # @return [self] + def parens(padding: '', **kwargs, &block) + surround( + '(', ')', + **kwargs.merge(lft_breakable: padding, rgt_breakable: padding), + &block + ) + end + + # {surround} with `( )`. New lines cannot appear after and before the delimiters. + # + # @return [self] + def parens_break_both(**kwargs, &block) + parens(**kwargs.merge(lft_force_break: true, rgt_force_break: true), &block) + end + + # {surround} with `( )`. New lines will appear after and before the delimiters. + # + # @return [self] + def parens_break_none(**kwargs, &block) + parens(**kwargs.merge(lft_can_break: false, rgt_can_break: false), &block) + end + + # {surround} with `` ` ` ``. New lines cannot appear after and before the delimiters + # unless you specify it with `rgt_can_break` and `lft_can_break`. + # + # @return [self] + def backticks(**kwargs, &block) + surround('`', '`', lft_can_break: false, rgt_can_break: false, **kwargs, &block) + end + + # {surround} with `" "`. New lines cannot appear after and before the delimiters + # unless you specify it with `rgt_can_break` and `lft_can_break`. + # + # @return [self] + def quote_double(**kwargs, &block) + surround('"', '"', lft_can_break: false, rgt_can_break: false, **kwargs, &block) + end + + # {surround} with `' '`. New lines cannot appear after and before the delimiters + # unless you specify it with `rgt_can_break` and `lft_can_break`. + # + # @return [self] + def quote_single(**kwargs, &block) + surround("'", "'", lft_can_break: false, rgt_can_break: false, **kwargs, &block) end # Open a consistent group. # - # @param inconsistent [Boolean] - # whether the break type of the group should be inconsistent. - # @param indent [Integer] + # @param break_type [Symbol] + # `:consistent` or `:inconsistent` + # @param indent [Integer] # the amount of indentation of the group. # - # @return [Nil] + # @return [self] # # @see Oppen.begin_consistent # @see Oppen.begin_inconsistent - def group_open(inconsistent: false, indent: 0) - tokens << - if inconsistent - Oppen.begin_inconsistent(offset: indent) - else - Oppen.begin_consistent(offset: indent) - end + def group_open(break_type: :consistent, indent: 0) + if %i[consistent inconsistent].none?(break_type) + raise ArgumentError, '%s is not a valid type. Choose one: :consistent or :inconsistent' + end + + tokens << Oppen.send(:"begin_#{break_type}", offset: indent) + self end # Close a group. # - # @return [Nil] - def group_close(_) + # @return [self] + def group_close tokens << Oppen.end + self end # Open a consistent group and add indent amount. @@ -370,8 +786,8 @@ def group_close(_) # @param indent [Integer] # the amount of indentation of the group. # - # @return [Nil] - def indent_open(indent) + # @return [self] + def indent_open(indent: @indent) @current_indent += indent group_open end @@ -381,10 +797,10 @@ def indent_open(indent) # @param indent [Integer] # the amount of indentation of the group. # - # @return [Nil] - def indent_close(group, indent) + # @return [self] + def indent_close(indent: @indent) @current_indent -= indent - group_close(group) + group_close end # Open a nest by adding indent. @@ -392,9 +808,10 @@ def indent_close(group, indent) # @param indent [Integer] # the amount of indentation of the nest. # - # @return [Nil] - def nest_open(indent) + # @return [self] + def nest_open(indent: @indent) @current_indent += indent + self end # Close a nest by subtracting indent. @@ -402,11 +819,43 @@ def nest_open(indent) # @param indent [Integer] # the amount of indentation of the nest. # - # @return [Nil] - def nest_close(indent) + # @return [self] + def nest_close(indent: @indent) @current_indent -= indent + self end # @!endgroup + + # Helper class to allow conditional printing. + class Wrap + def initialize(blk) + @wrapped = blk + @wrapper = nil + end + + # Conditional. + def when(cond, &blk) + if cond + @wrapper = blk + end + self + end + + # Flush. + def end + @wrapper ? @wrapper.(@wrapped) : @wrapped.() + end + + # To re-enable chaining. + def method_missing(meth, ...) + self.end.send(meth, ...) + end + + # To re-enable chaining. + def respond_to_missing?(meth, include_private) + self.end.respond_to_missing?(meth, include_private) + end + end end end diff --git a/test/customization_test.rb b/test/customization_test.rb index 39c832c..33400bf 100644 --- a/test/customization_test.rb +++ b/test/customization_test.rb @@ -3,11 +3,11 @@ require_relative 'lib' def customization_build_output(printer) - printer.group(2) { + printer.group(indent: 2) { printer.text 'function' printer.breakable printer.text 'test(' - printer.group(2, '', '', :inconsistent) { + printer.group(:inconsistent, indent: 2) { printer.break printer.text 'int index,' printer.breakable @@ -25,11 +25,11 @@ def customization_build_output(printer) describe 'Inconsistent break tests' do it 'must work in a group if the line fits' do printer = Oppen::Wadler.new(width: 100) - printer.group(2) { + printer.group(indent: 2) { printer.text 'function' printer.breakable printer.text 'test(' - printer.group(2, '', '', :inconsistent) { + printer.group(:inconsistent, indent: 2) { printer.breakable printer.text 'int index,' printer.breakable @@ -62,7 +62,7 @@ def customization_build_output(printer) describe 'Spaces tests' do it 'must work with a string of length 1' do - printer = Oppen::Wadler.new(width: 45, space: '*') + printer = Oppen::Wadler.new(width: 45, space_gen: '*') customization_build_output(printer) _(printer.output).must_equal <<~LANG.chomp function @@ -74,7 +74,7 @@ def customization_build_output(printer) end it 'must work with a string of length greater than 1' do - printer = Oppen::Wadler.new(width: 45, space: '**') + printer = Oppen::Wadler.new(width: 45, space_gen: '**') customization_build_output(printer) _(printer.output).must_equal <<~LANG.chomp function @@ -86,7 +86,7 @@ def customization_build_output(printer) end it 'must work with an UTF-8 string' do - printer = Oppen::Wadler.new(width: 45, space: 'ω') + printer = Oppen::Wadler.new(width: 45, space_gen: 'ω') customization_build_output(printer) _(printer.output).must_equal <<~LANG.chomp function @@ -98,7 +98,7 @@ def customization_build_output(printer) end it 'must work with a lambda' do - printer = Oppen::Wadler.new(width: 45, space: ->(n) { '*' * n }) + printer = Oppen::Wadler.new(width: 45, space_gen: ->(n) { '*' * n }) customization_build_output(printer) _(printer.output).must_equal <<~LANG.chomp function @@ -110,7 +110,7 @@ def customization_build_output(printer) end it 'must work with an UTF-8 string lambda' do - printer = Oppen::Wadler.new(width: 45, space: ->(n) { 'ω' * n }) + printer = Oppen::Wadler.new(width: 45, space_gen: ->(n) { 'ω' * n }) customization_build_output(printer) _(printer.output).must_equal <<~LANG.chomp function @@ -122,7 +122,7 @@ def customization_build_output(printer) end it 'must work with a lambda different from default' do - printer = Oppen::Wadler.new(width: 45, space: ->(n) { '*' * n * 2 }) + printer = Oppen::Wadler.new(width: 45, space_gen: ->(n) { '*' * n * 2 }) customization_build_output(printer) _(printer.output).must_equal <<~LANG.chomp function @@ -134,7 +134,7 @@ def customization_build_output(printer) end it 'must work with a proc' do - printer = Oppen::Wadler.new(width: 45, space: proc { |n| '*' * n }) + printer = Oppen::Wadler.new(width: 45, space_gen: proc { |n| '*' * n }) customization_build_output(printer) _(printer.output).must_equal <<~LANG.chomp function @@ -146,21 +146,21 @@ def customization_build_output(printer) end it 'must raise an error for a lambda of arity different than one' do - printer = Oppen::Wadler.new(width: 45, space: ->(n, _o) { '*' * n }) + printer = Oppen::Wadler.new(width: 45, space_gen: ->(n, _o) { '*' * n }) _ { printer.output }.must_raise ArgumentError - printer = Oppen::Wadler.new(width: 45, space: -> { '*' }) + printer = Oppen::Wadler.new(width: 45, space_gen: -> { '*' }) _ { printer.output }.must_raise ArgumentError - printer = Oppen::Wadler.new(width: 45, space: ->(*_args) { '*' }) + printer = Oppen::Wadler.new(width: 45, space_gen: ->(*_args) { '*' }) _ { printer.output }.must_raise ArgumentError end # The indentation lambda can yield a fixed size string indepedant of the indentation level. # In these cases, the fixed sized string should not be displayed if the indentation is 0. - it 'calls space only when indent is positive' do - printer = Oppen::Wadler.new(width: 10, space: ->(_n) { '*******' }) - printer.group(0) { + it 'calls space_gen only when indent is positive' do + printer = Oppen::Wadler.new(width: 10, space_gen: ->(_n) { '*******' }) + printer.group { printer.text 'Hello' printer.break printer.text 'World' @@ -195,9 +195,9 @@ def customization_build_output(printer) it 'must work with a different delimiter' do printer = Oppen::Wadler.new(width: 45) printer.group { - printer.breakable('') + printer.breakable '' printer.text 'Hello' - printer.breakable(', ') + printer.breakable ', ' printer.text 'World!' } _(printer.output).must_equal 'Hello, World!' @@ -206,9 +206,9 @@ def customization_build_output(printer) it 'must work with an UTF-8 delimiter' do printer = Oppen::Wadler.new(width: 45) printer.group { - printer.breakable('') + printer.breakable '' printer.text 'Hello' - printer.breakable(' ʃ ') + printer.breakable ' ʃ ' printer.text 'World!' } _(printer.output).must_equal 'Hello ʃ World!' @@ -216,16 +216,16 @@ def customization_build_output(printer) end describe 'Nest delimiter tests' do - def nest_delimiter_build_output(printer, open_obj, close_obj) - printer.group(0) { - printer.group(2) { + def nest_delimiter_build_output(printer, delim) + printer.group { + printer.group(indent: 2) { printer.text 'function' printer.breakable printer.text 'foo()' } printer.break - printer.nest(2, open_obj, close_obj) { - printer.group(0) { + printer.nest(delim: delim, indent: 2) { + printer.group { printer.text 'Hello' printer.breakable(', ') printer.text 'World!' @@ -236,7 +236,7 @@ def nest_delimiter_build_output(printer, open_obj, close_obj) it 'must work with an open and close object' do printer = Oppen::Wadler.new(width: 5) - nest_delimiter_build_output(printer, '{', '}') + nest_delimiter_build_output(printer, %w[{ }]) _(printer.output).must_equal <<~LANG.chomp function foo() @@ -249,7 +249,7 @@ def nest_delimiter_build_output(printer, open_obj, close_obj) it 'must work with no open and close object' do printer = Oppen::Wadler.new(width: 5) - nest_delimiter_build_output(printer, '', '') + nest_delimiter_build_output(printer, []) _(printer.output).must_equal <<~LANG.chomp function foo() @@ -260,7 +260,7 @@ def nest_delimiter_build_output(printer, open_obj, close_obj) it 'must work with only an open object' do printer = Oppen::Wadler.new(width: 5) - nest_delimiter_build_output(printer, '{', '') + nest_delimiter_build_output(printer, ['{', '']) _(printer.output).must_equal <<~LANG.chomp function foo() @@ -272,7 +272,7 @@ def nest_delimiter_build_output(printer, open_obj, close_obj) it 'must work with only a close object' do printer = Oppen::Wadler.new(width: 5) - nest_delimiter_build_output(printer, '', '}') + nest_delimiter_build_output(printer, ['', '}']) _(printer.output).must_equal <<~LANG.chomp function foo() @@ -284,7 +284,7 @@ def nest_delimiter_build_output(printer, open_obj, close_obj) it 'must work with an UTF-8 string' do printer = Oppen::Wadler.new(width: 5) - nest_delimiter_build_output(printer, 'Ϯ', 'Ϯ') + nest_delimiter_build_output(printer, %w[Ϯ Ϯ]) _(printer.output).must_equal <<~LANG.chomp function foo() @@ -294,31 +294,21 @@ def nest_delimiter_build_output(printer, open_obj, close_obj) Ϯ LANG end - - it 'must raise an error when open_obj is nil' do - printer = Oppen::Wadler.new(width: 5) - _ { nest_delimiter_build_output(printer, nil, '}') }.must_raise ArgumentError - end - - it 'must raise an error when close_obj is nil' do - printer = Oppen::Wadler.new(width: 5) - _ { nest_delimiter_build_output(printer, '{', nil) }.must_raise ArgumentError - end end describe 'Group delimiter tests' do - def group_delimiter_build_output(printer, open_obj, close_obj) - printer.group(0) { - printer.group(2) { + def group_delimiter_build_output(printer, delim) + printer.group { + printer.group(indent: 2) { printer.text 'function' printer.breakable printer.text 'foo()' } - printer.group(2, open_obj, close_obj) { - printer.group(0) { + printer.group(delim: delim, indent: 2) { + printer.group { printer.break printer.text 'Hello' - printer.breakable(', ') + printer.breakable ', ' printer.text 'World!' } } @@ -327,7 +317,7 @@ def group_delimiter_build_output(printer, open_obj, close_obj) it 'must work with an open and close object' do printer = Oppen::Wadler.new(width: 5) - group_delimiter_build_output(printer, '{', '}') + group_delimiter_build_output(printer, %i[{ }]) _(printer.output).must_equal <<~LANG.chomp function foo() @@ -340,7 +330,7 @@ def group_delimiter_build_output(printer, open_obj, close_obj) it 'must work with no open and close object' do printer = Oppen::Wadler.new(width: 5) - group_delimiter_build_output(printer, '', '') + group_delimiter_build_output(printer, nil) _(printer.output).must_equal <<~LANG.chomp function foo() @@ -351,7 +341,7 @@ def group_delimiter_build_output(printer, open_obj, close_obj) it 'must work with only an open object' do printer = Oppen::Wadler.new(width: 5) - group_delimiter_build_output(printer, '{', '') + group_delimiter_build_output(printer, ['{', '']) _(printer.output).must_equal <<~LANG.chomp function foo() @@ -363,7 +353,7 @@ def group_delimiter_build_output(printer, open_obj, close_obj) it 'must work with only a close object' do printer = Oppen::Wadler.new(width: 5) - group_delimiter_build_output(printer, '', '}') + group_delimiter_build_output(printer, ['', '}']) _(printer.output).must_equal <<~LANG.chomp function foo() @@ -375,7 +365,7 @@ def group_delimiter_build_output(printer, open_obj, close_obj) it 'must work with an UTF-8 string' do printer = Oppen::Wadler.new(width: 5) - group_delimiter_build_output(printer, 'Ϯ', 'Ϯ') + group_delimiter_build_output(printer, %w[Ϯ Ϯ]) _(printer.output).must_equal <<~LANG.chomp function foo() @@ -385,33 +375,23 @@ def group_delimiter_build_output(printer, open_obj, close_obj) Ϯ LANG end - - it 'must raise an error when open_obj is nil' do - printer = Oppen::Wadler.new(width: 5) - _ { group_delimiter_build_output(printer, nil, '}') }.must_raise ArgumentError - end - - it 'must raise an error when close_obj is nil' do - printer = Oppen::Wadler.new(width: 5) - _ { group_delimiter_build_output(printer, '{', nil) }.must_raise ArgumentError - end end describe 'Line continuation tests' do def line_continuation_build_output(printer, break_type = :consistent, line_continuation = ',') - printer.group(0) { - printer.text('[') - printer.group(2, '', '', break_type) { - printer.breakable('') - printer.text('1') - printer.breakable(', ', line_continuation: line_continuation) - printer.text('2') - printer.breakable(', ', line_continuation: line_continuation) - printer.text('3') + printer.group { + printer.text '[' + printer.group(break_type, indent: 2) { + printer.breakable '' + printer.text '1' + printer.breakable ', ', line_continuation: line_continuation + printer.text '2' + printer.breakable ', ', line_continuation: line_continuation + printer.text '3' } - printer.breakable('', line_continuation: line_continuation) - printer.text(']') + printer.breakable '', line_continuation: line_continuation + printer.text ']' } end it 'must not display line continuation if line fits' do diff --git a/test/indent_anchor_test.rb b/test/indent_anchor_test.rb index b357550..e9929ac 100644 --- a/test/indent_anchor_test.rb +++ b/test/indent_anchor_test.rb @@ -12,11 +12,6 @@ def check_difference_oppen_wadler(width, expected_oppen, expected_wadler, builde printer = Oppen::Wadler.new(width: width, config: Oppen::Config.wadler) builder_block.(printer) _(printer.output).must_equal expected_wadler, 'Wadler failed the test' - - printer = PrettyPrint.new(''.dup, width) - builder_block.(printer) - printer.flush - _(printer.output).must_equal expected_wadler, 'PrettyPrint failed the test' end describe 'Indent anchor tests' do @@ -24,7 +19,7 @@ def check_difference_oppen_wadler(width, expected_oppen, expected_wadler, builde [ 'must work with a simple group of indentation 0', proc { |out| - out.group(0) { + out.group { out.text 'Hello, World!' out.break out.text 'How are you?' @@ -44,7 +39,7 @@ def check_difference_oppen_wadler(width, expected_oppen, expected_wadler, builde [ 'must work with a simple group of indentation 1', proc { |out| - out.group(1) { + out.group(indent: 1) { out.text 'Hello, World!' out.break out.text 'How are you?' @@ -64,7 +59,7 @@ def check_difference_oppen_wadler(width, expected_oppen, expected_wadler, builde [ 'must work with a simple group of indentation 2', proc { |out| - out.group(2) { + out.group(indent: 2) { out.text 'Hello, World!' out.break out.text 'How are you?' @@ -84,9 +79,9 @@ def check_difference_oppen_wadler(width, expected_oppen, expected_wadler, builde [ 'must work with a simple nested case of indentation 2-0', proc { |out| - out.group(2) { + out.group(indent: 2) { out.text 'Hello, World!' - out.group(0) { + out.group { out.break out.text 'How are you?' out.break @@ -106,9 +101,9 @@ def check_difference_oppen_wadler(width, expected_oppen, expected_wadler, builde [ 'must work with a simple nested case of indentation 2-1', proc { |out| - out.group(2) { + out.group(indent: 2) { out.text 'Hello, World!' - out.group(1) { + out.group(indent: 1) { out.break out.text 'How are you?' out.break @@ -128,9 +123,9 @@ def check_difference_oppen_wadler(width, expected_oppen, expected_wadler, builde [ 'must work with a simple nested case of indentation 2-2', proc { |out| - out.group(2) { + out.group(indent: 2) { out.text 'Hello, World!' - out.group(2) { + out.group(indent: 2) { out.break out.text 'How are you?' out.break @@ -150,9 +145,9 @@ def check_difference_oppen_wadler(width, expected_oppen, expected_wadler, builde [ 'must work with a simple nested case of indentation 0-0', proc { |out| - out.group(0) { + out.group { out.text 'Hello, World!' - out.group(0) { + out.group { out.break out.text 'How are you?' out.break @@ -172,9 +167,9 @@ def check_difference_oppen_wadler(width, expected_oppen, expected_wadler, builde [ 'must work with a simple nested case of indentation 0-1', proc { |out| - out.group(0) { + out.group { out.text 'Hello, World!' - out.group(1) { + out.group(indent: 1) { out.break out.text 'How are you?' out.break @@ -194,13 +189,13 @@ def check_difference_oppen_wadler(width, expected_oppen, expected_wadler, builde [ 'must work with a multiple groups of same indentation nested in a group', proc { |out| - out.group(2) { + out.group(indent: 2) { out.text 'Hello, World!' - out.group(1) { + out.group(indent: 1) { out.break out.text 'How are you?' } - out.group(1) { + out.group(indent: 1) { out.break out.text 'I am fine and you?' } @@ -218,13 +213,13 @@ def check_difference_oppen_wadler(width, expected_oppen, expected_wadler, builde [ 'must work with a multiple groups of different increasing indentation nested in a group', proc { |out| - out.group(2) { + out.group(indent: 2) { out.text 'Hello, World!' - out.group(1) { + out.group(indent: 1) { out.break out.text 'How are you?' } - out.group(2) { + out.group(indent: 2) { out.break out.text 'I am fine and you?' } @@ -242,13 +237,13 @@ def check_difference_oppen_wadler(width, expected_oppen, expected_wadler, builde [ 'must work with a multiple groups of different decreasing indentation nested in a group', proc { |out| - out.group(2) { + out.group(indent: 2) { out.text 'Hello, World!' - out.group(2) { + out.group(indent: 2) { out.break out.text 'How are you?' } - out.group(1) { + out.group(indent: 1) { out.break out.text 'I am fine and you?' } @@ -266,12 +261,12 @@ def check_difference_oppen_wadler(width, expected_oppen, expected_wadler, builde [ 'must work with 3 nested blocks of same indentation', proc { |out| - out.group(2) { + out.group(indent: 2) { out.text 'Hello, World!' - out.group(2) { + out.group(indent: 2) { out.break out.text 'How' - out.group(2) { + out.group(indent: 2) { out.break out.text 'are' out.break @@ -294,12 +289,12 @@ def check_difference_oppen_wadler(width, expected_oppen, expected_wadler, builde [ 'must work with 3 nested blocks of different indentation', proc { |out| - out.group(2) { + out.group(indent: 2) { out.text 'Hello, World!' - out.group(3) { + out.group(indent: 3) { out.break out.text 'How' - out.group(4) { + out.group(indent: 4) { out.break out.text 'are' out.break @@ -322,10 +317,10 @@ def check_difference_oppen_wadler(width, expected_oppen, expected_wadler, builde [ 'must work with a simple nested case break outside nested', proc { |out| - out.group(0) { + out.group { out.text 'Hello, World!' out.break - out.group(1) { + out.group(indent: 1) { out.text 'How are you?' out.break out.text 'I am fine and you?' @@ -344,9 +339,9 @@ def check_difference_oppen_wadler(width, expected_oppen, expected_wadler, builde [ 'must work with a simple nested case text outside nested', proc { |out| - out.group(0) { + out.group { out.text 'Hello, World!' - out.group(1) { + out.group(indent: 1) { out.break out.text 'How are you?' out.break @@ -366,10 +361,10 @@ def check_difference_oppen_wadler(width, expected_oppen, expected_wadler, builde [ 'must work with a simple nested case text and break outside nested', proc { |out| - out.group(0) { + out.group { out.text 'Hello, World!' out.break - out.group(1) { + out.group(indent: 1) { out.text 'How are you?' out.break } @@ -396,13 +391,7 @@ def check_difference_oppen_wadler(width, expected_oppen, expected_wadler, builde describe 'Indent anchor error tests' do it 'must raise a LocalJumpError if no block is given to group' do - width = 30 - printer = PrettyPrint.new ''.dup, width - - _ { printer.group(2) }.must_raise LocalJumpError - - printer = Oppen::Wadler.new(width: width) - - _ { printer.group(2) }.must_raise LocalJumpError + printer = Oppen::Wadler.new(width: 20) + _ { printer.group(indent: 2) }.must_raise LocalJumpError end end diff --git a/test/lib.rb b/test/lib.rb index dd32943..6cda1dc 100644 --- a/test/lib.rb +++ b/test/lib.rb @@ -39,3 +39,9 @@ def break current_group.break end end + +def assert_wadler(width, expected, builder_block, indent: 0) + printer = Oppen::Wadler.new(indent: indent, width: width) + builder_block.(printer) + _(printer.output).must_equal expected, 'Oppen failed the test' +end diff --git a/test/token_to_wadler_test.rb b/test/token_to_wadler_test.rb index 41cb6af..9ac3327 100644 --- a/test/token_to_wadler_test.rb +++ b/test/token_to_wadler_test.rb @@ -10,7 +10,7 @@ printer }, expected: <<~LANG, - printer.group(0, '', '', :consistent) { + printer.group(:consistent, indent: 0) { } LANG @@ -21,7 +21,7 @@ printer.text('Hello World!') }, expected: <<~LANG, - printer.group(0, '', '', :consistent) { + printer.group(:consistent, indent: 0) { printer.text("Hello World!", width: 12) } @@ -33,7 +33,7 @@ printer.text('"\'Hello World!\'"') }, expected: <<~LANG, - printer.group(0, '', '', :consistent) { + printer.group(:consistent, indent: 0) { printer.text("\\"'Hello World!'\\"", width: 16) } @@ -45,7 +45,7 @@ printer.text('Ḽơᶉëᶆ ȋṕšᶙṁ ḍỡḽǭᵳ') }, expected: <<~LANG, - printer.group(0, '', '', :consistent) { + printer.group(:consistent, indent: 0) { printer.text("Ḽơᶉëᶆ ȋṕšᶙṁ ḍỡḽǭᵳ", width: 17) } @@ -57,7 +57,7 @@ printer.text('Hello World!', width: 42) }, expected: <<~LANG, - printer.group(0, '', '', :consistent) { + printer.group(:consistent, indent: 0) { printer.text("Hello World!", width: 42) } @@ -69,7 +69,7 @@ printer.text('Hello World! ') }, expected: <<~LANG, - printer.group(0, '', '', :consistent) { + printer.group(:consistent, indent: 0) { printer.text("Hello World!", width: 12) printer.text(" ", width: 2) } @@ -80,7 +80,7 @@ title: 'displays a simple break token', block: proc(&:break), expected: <<~LANG, - printer.group(0, '', '', :consistent) { + printer.group(:consistent, indent: 0) { printer.break(line_continuation: "") } @@ -92,7 +92,7 @@ printer.break(line_continuation: '##') }, expected: <<~LANG, - printer.group(0, '', '', :consistent) { + printer.group(:consistent, indent: 0) { printer.break(line_continuation: "##") } @@ -102,7 +102,7 @@ title: 'displays a simple breakable token', block: proc(&:breakable), expected: <<~LANG, - printer.group(0, '', '', :consistent) { + printer.group(:consistent, indent: 0) { printer.breakable(" ", width: 1, line_continuation: "") } @@ -114,7 +114,7 @@ printer.breakable('**', width: 42, line_continuation: '##') }, expected: <<~LANG, - printer.group(0, '', '', :consistent) { + printer.group(:consistent, indent: 0) { printer.breakable("**", width: 42, line_continuation: "##") } @@ -128,8 +128,10 @@ } }, expected: <<~LANG, - printer.group(0, '', '', :consistent) { - printer.text("Hello World!", width: 12) + printer.group(:consistent, indent: 0) { + printer.group(:consistent, indent: 0) { + printer.text("Hello World!", width: 12) + } } LANG @@ -137,17 +139,19 @@ { title: 'displays a group token with arguments', block: proc { |printer| - printer.group(2, '{', '}', :inconsistent) { + printer.group(:inconsistent, delim: %w[{ }], indent: 2) { printer.text('Hello World!') } }, expected: <<~LANG, - printer.group(2, '', '', :inconsistent) { - printer.break(line_continuation: "") - printer.text("{", width: 1) - printer.text("Hello World!", width: 12) - printer.break(line_continuation: "") - printer.text("}", width: 1) + printer.group(:consistent, indent: 0) { + printer.group(:inconsistent, indent: 2) { + printer.break(line_continuation: "") + printer.text("{", width: 1) + printer.text("Hello World!", width: 12) + printer.break(line_continuation: "") + printer.text("}", width: 1) + } } LANG @@ -166,7 +170,7 @@ printer.text('Hello World!') }, expected: <<~LANG, - printer.group(0, '', '', :consistent) { + printer.group(:consistent, indent: 0) { printer.text("Hello World!", width: 12) printer.break(line_continuation: "") printer.breakable(" ", width: 1, line_continuation: "") @@ -196,12 +200,14 @@ } }, expected: <<~LANG, - printer.group(0, '', '', :consistent) { - printer.group(0, '', '', :consistent) { - printer.group(0, '', '', :consistent) { - printer.group(0, '', '', :consistent) { - printer.group(0, '', '', :consistent) { - printer.text("Hello World!", width: 12) + printer.group(:consistent, indent: 0) { + printer.group(:consistent, indent: 0) { + printer.group(:consistent, indent: 0) { + printer.group(:consistent, indent: 0) { + printer.group(:consistent, indent: 0) { + printer.group(:consistent, indent: 0) { + printer.text("Hello World!", width: 12) + } } } } @@ -213,19 +219,19 @@ { title: 'displays a simple nest token', block: proc { |printer| - printer.nest(2) { + printer.nest(indent: 2) { printer.breakable printer.text('Hello World!') printer.break } }, expected: <<~LANG, - printer.group(0, '', '', :consistent) { - printer.nest(2, '', '') { + printer.group(:consistent, indent: 0) { + printer.nest(indent: 2) { printer.breakable(" ", width: 1, line_continuation: "") } printer.text("Hello World!", width: 12) - printer.nest(2, '', '') { + printer.nest(indent: 2) { printer.break(line_continuation: "") } } @@ -235,23 +241,23 @@ { title: 'displays a nest token with arguments', block: proc { |printer| - printer.nest(2, '{', '}') { + printer.nest(delim: %w[{ }], indent: 2) { printer.breakable printer.text('Hello World!') printer.break } }, expected: <<~LANG, - printer.group(0, '', '', :consistent) { + printer.group(:consistent, indent: 0) { printer.text("{", width: 1) - printer.nest(2, '', '') { + printer.nest(indent: 2) { printer.break(line_continuation: "") } - printer.nest(2, '', '') { + printer.nest(indent: 2) { printer.breakable(" ", width: 1, line_continuation: "") } printer.text("Hello World!", width: 12) - printer.nest(2, '', '') { + printer.nest(indent: 2) { printer.break(line_continuation: "") } printer.break(line_continuation: "") @@ -263,9 +269,9 @@ { title: 'displays nested nest tokens', block: proc { |printer| - printer.nest(2) { - printer.nest(2) { - printer.nest(2) { + printer.nest(indent: 2) { + printer.nest(indent: 2) { + printer.nest(indent: 2) { printer.breakable printer.text('Hello World!') printer.break @@ -274,12 +280,12 @@ } }, expected: <<~LANG, - printer.group(0, '', '', :consistent) { - printer.nest(6, '', '') { + printer.group(:consistent, indent: 0) { + printer.nest(indent: 6) { printer.breakable(" ", width: 1, line_continuation: "") } printer.text("Hello World!", width: 12) - printer.nest(6, '', '') { + printer.nest(indent: 6) { printer.break(line_continuation: "") } } @@ -289,10 +295,10 @@ { title: 'displays nested nest and group tokens', block: proc { |printer| - printer.group(2) { - printer.nest(2) { - printer.group(2) { - printer.nest(2) { + printer.group(indent: 2) { + printer.nest(indent: 2) { + printer.group(indent: 2) { + printer.nest(indent: 2) { printer.breakable printer.text('Hello World!') printer.break @@ -302,14 +308,16 @@ } }, expected: <<~LANG, - printer.group(2, '', '', :consistent) { - printer.group(2, '', '', :consistent) { - printer.nest(4, '', '') { - printer.breakable(" ", width: 1, line_continuation: "") - } - printer.text("Hello World!", width: 12) - printer.nest(4, '', '') { - printer.break(line_continuation: "") + printer.group(:consistent, indent: 0) { + printer.group(:consistent, indent: 2) { + printer.group(:consistent, indent: 2) { + printer.nest(indent: 4) { + printer.breakable(" ", width: 1, line_continuation: "") + } + printer.text("Hello World!", width: 12) + printer.nest(indent: 4) { + printer.break(line_continuation: "") + } } } } diff --git a/test/wadler_separate_test.rb b/test/wadler_separate_test.rb new file mode 100644 index 0000000..3b09c25 --- /dev/null +++ b/test/wadler_separate_test.rb @@ -0,0 +1,167 @@ +# frozen_string_literal: true + +require_relative 'lib' + +describe 'separate' do + [ + { + title: 'does nothing for singletons', + block: proc { |out| + out.separate(%w[1], ',') { |i| + out.text i + } + }, + expected: '1', + }, + { + title: 'adds separator for non-sigletons', + block: proc { |out| + out.separate(%w[1 2 3], ',') { |i| + out.text i + } + }, + expected: '1, 2, 3', + }, + { + title: 'breaks consistently by default', + block: proc { |out| + out.separate((1..10).map(&:to_s), ',') { |i| + out.text i + } + }, + expected: <<~OUT.chomp, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10 + OUT + }, + { + title: 'breaks inconsistently', + block: proc { |out| + out.separate((1..10).map(&:to_s), ',', break_type: :inconsistent) { |i| + out.text i + } + }, + expected: <<~OUT.chomp, + 1, 2, 3, + 4, 5, 6, + 7, 8, 9, + 10 + OUT + }, + { + title: 'breaks consistently before the separator', + block: proc { |out| + out.separate((1..10).map(&:to_s), ',', break_pos: :before) { |i| + out.text i + } + }, + expected: <<~OUT.chomp, + 1 + ,2 + ,3 + ,4 + ,5 + ,6 + ,7 + ,8 + ,9 + ,10 + OUT + }, + { + title: 'breaks inconsistently before the separator', + block: proc { |out| + out.separate((1..10).map(&:to_s), ',', break_pos: :before, break_type: :inconsistent) { |i| + out.text i + } + }, + expected: <<~OUT.chomp, + 1 ,2 ,3 ,4 + ,5 ,6 ,7 + ,8 ,9 ,10 + OUT + }, + { + title: 'indents when using a Boolean', + block: proc { |out| + out.separate((1..10).map(&:to_s), ',', break_type: :inconsistent, indent: true) { |i| + out.text i + } + }, + expected: <<~OUT.chomp, + 1, 2, 3, + 4, 5, + 6, 7, + 8, 9, + 10 + OUT + indent: 4, + }, + { + title: 'indents when using an Integer', + block: proc { |out| + out.separate((1..10).map(&:to_s), ',', break_type: :inconsistent, indent: 2) { |i| + out.text i + } + }, + expected: <<~OUT.chomp, + 1, 2, 3, + 4, 5, 6, + 7, 8, 9, + 10 + OUT + }, + ].each do |test| + it test[:title] do + assert_wadler 10, test[:expected], test[:block], indent: test[:indent] || 0 + end + end +end + +describe 'helpers built on separate' do + [ + { + title: 'creates lines from a list', + block: proc { |out| + out.lines((1..10).map(&:to_s), ',') { |i| + out.text i + } + }, + expected: <<~OUT.chomp, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10 + OUT + }, + { + title: 'concatenates args from a list', + block: proc { |out| + out.concat((1..10).map(&:to_s), ',') { |i| + out.text i + } + }, + expected: <<~OUT.chomp, + 1,2,3,4,5,6,7,8,9,10 + OUT + }, + ].each do |test| + it test[:title] do + assert_wadler 10, test[:expected], test[:block], indent: test[:indent] || 0 + end + end +end diff --git a/test/wadler_surround_test.rb b/test/wadler_surround_test.rb new file mode 100644 index 0000000..843e20d --- /dev/null +++ b/test/wadler_surround_test.rb @@ -0,0 +1,281 @@ +# frozen_string_literal: true + +require_relative 'lib' + +describe 'surround' do + [ + { + title: 'prints lft and rg only', + block: proc { |out| + out.surround('{', '}').text('!') + }, + expected: '{}!', + }, + { + title: 'accepts a block', + block: proc { |out| + out.surround('{', '}') { + out.text '1' + } + }, + expected: '{1}', + }, + { + title: 'breaks on left and right inconsistently by default', + block: proc { |out| + out.text 'A long long string' + out.surround('{', '}') { + out.text '1' + } + }, + expected: <<~OUT.chomp, + A long long string{ + 1} + OUT + }, + { + title: 'force breaks on left', + block: proc { |out| + out.text 'A long long string' + out.surround('{', '}', lft_force_break: true) { + out.text '1' + } + }, + expected: <<~OUT.chomp, + A long long string{ + 1} + OUT + }, + { + title: 'force breaks on left with indent', + block: proc { |out| + out.text 'A long long string' + out.surround('{', '}', indent: 2, lft_force_break: true) { + out.text '1' + } + }, + expected: <<~OUT.chomp, + A long long string{ + 1} + OUT + }, + { + title: 'force breaks on right', + block: proc { |out| + out.text 'A long long string' + out.surround('{', '}', rgt_force_break: true) { + out.text '1' + } + }, + expected: <<~OUT.chomp, + A long long string{ + 1 + } + OUT + }, + { + title: 'force breaks on right with indent', + block: proc { |out| + out.text 'A long long string' + out.surround('{', '}', indent: 2, rgt_force_break: true) { + out.text '1' + } + }, + expected: <<~OUT.chomp, + A long long string{ + 1 + } + OUT + }, + { + title: 'prevents breaks on left', + block: proc { |out| + out.text 'A long long string' + out.surround('{', '}', lft_can_break: false) { + out.text '1' + } + }, + expected: <<~OUT.chomp, + A long long string{1 + } + OUT + }, + { + title: 'prevents breaks on left with indent', + block: proc { |out| + out.text 'A long long string' + out.surround('{', '}', indent: 2, lft_can_break: false) { + out.text '1' + } + }, + expected: <<~OUT.chomp, + A long long string{1 + } + OUT + }, + { + title: 'prevents breaks on right', + block: proc { |out| + out.text 'A long long string' + out.surround('{', '}', rgt_can_break: false) { + out.text '1' + } + }, + expected: <<~OUT.chomp, + A long long string{ + 1} + OUT + }, + { + title: 'prevents breaks on right with indent', + block: proc { |out| + out.text 'A long long string' + out.surround('{', '}', indent: 2, rgt_can_break: false) { + out.text '1' + } + }, + expected: <<~OUT.chomp, + A long long string{ + 1} + OUT + }, + { + title: 'can change breakable', + block: proc { |out| + out.surround('{', '}', lft_breakable: '<', rgt_breakable: '>') { + out.text '1' + } + }, + expected: '{<1>}', + }, + { + title: 'can change breakable and breaks on left and right inconsistently by default', + block: proc { |out| + out.text 'A long long string' + out.surround('{', '}', lft_breakable: '<', rgt_breakable: '>') { + out.text '1' + } + }, + expected: <<~OUT.chomp, + A long long string{ + 1>} + OUT + }, + { + title: 'can change breakable and prevent left break', + block: proc { |out| + out.text 'A long long string' + out.surround('{', '}', lft_breakable: '<', lft_can_break: false, rgt_breakable: '>') { + out.text '1' + } + }, + expected: <<~OUT.chomp, + A long long string{1 + } + OUT + }, + { + title: 'can change breakable and prevent right break', + block: proc { |out| + out.text 'A long long string' + out.surround('{', '}', lft_breakable: '<', rgt_breakable: '>', rgt_can_break: false) { + out.text '1' + } + }, + expected: <<~OUT.chomp, + A long long string{ + 1} + OUT + }, + ].each do |test| + it test[:title] do + assert_wadler 10, test[:expected], test[:block] + end + end +end + +describe 'helpers for surround' do + padding_no = ->(name) { + proc { |out| + out.send(name) { + out.text '1' + } + } + } + padding_yes = ->(name) { + proc { |out| + out.send(name, padding: '~') { + out.text '1' + } + } + } + [ + [:angles, %w[< >]], + [:braces, %w[{ }]], + [:brackets, %w{[ ]}], + [:parens, %w[( )]], + ].each do |name, chars| + [ + { + title: "prints #{name} w/o padding", + block: padding_no.(name), + expected: "#{chars[0]}1#{chars[1]}", + }, + { + title: "prints #{name} w padding", + block: padding_yes.(name), + expected: "#{chars[0]}~1~#{chars[1]}", + }, + { + title: "prints #{name}_break_both w/o padding", + block: padding_no.("#{name}_break_both"), + expected: <<~OUT.chomp, + #{chars[0]} + 1 + #{chars[1]} + OUT + }, + { + title: "prints #{name}_break_both w padding but removes them", + block: padding_yes.("#{name}_break_both"), + expected: <<~OUT.chomp, + #{chars[0]} + 1 + #{chars[1]} + OUT + }, + { + title: "prints #{name}_break_non w/o padding but removes them", + block: padding_no.("#{name}_break_none"), + expected: "#{chars[0]}1#{chars[1]}", + }, + { + title: "prints #{name}_break_non w padding but removes them", + block: padding_yes.("#{name}_break_none"), + expected: "#{chars[0]}1#{chars[1]}", + }, + ].each do |test| + it test[:title] do + assert_wadler 10, test[:expected], test[:block] + end + end + end + + [ + [:backticks, '`'], + [:quote_double, '"'], + [:quote_single, "'"], + ].each do |name, char| + [ + { + title: "prints #{name}", + block: padding_no.(name), + expected: "#{char}1#{char}", + }, + ].each do |test| + it test[:title] do + assert_wadler 10, test[:expected], test[:block] + end + end + end +end diff --git a/test/wadler_test.rb b/test/wadler_test.rb index 9ed5222..daa9206 100644 --- a/test/wadler_test.rb +++ b/test/wadler_test.rb @@ -1,39 +1,26 @@ # frozen_string_literal: true -require 'prettyprint' - require_relative 'lib' -def check_roundtrip(width, expected, builder_block) - printer = Oppen::Wadler.new(width: width) - builder_block.(printer) - _(printer.output).must_equal expected, 'Oppen failed the test' - - printer = PrettyPrint.new(''.dup, width) - builder_block.(printer) - printer.flush - _(printer.output).must_equal expected, 'PrettyPrint failed the test' -end - describe 'Wadler tests' do describe 'must work like ruby\'s PrettyPrint library' do builder_block = proc { |out| out.group { out.group { out.group { - out.group { - out.text 'hello' - out.breakable - out.text 'a' - } - out.breakable - out.text 'b' + out + .group { + out + .text('hello') + .breakable + .text('a') + } + .breakable + .text('b') } - out.breakable - out.text 'c' + out.breakable.text 'c' } - out.breakable - out.text 'd' + out.breakable.text 'd' } } @@ -76,7 +63,7 @@ def check_roundtrip(width, expected, builder_block) ].each do |expected, vals| vals.each do |width| it "must work with line width: #{width}" do - check_roundtrip(width, expected, builder_block) + assert_wadler(width, expected, builder_block) end end end @@ -94,25 +81,26 @@ def initialize(string, *children) def show(out) out.group { - out.text string - out.nest(string.length) { - unless children.empty? - out.text '[' - out.nest(1) { - first = true - children.each { |t| - if first - first = false - else - out.text ',' - out.breakable - end - t.show(out) - } - } - out.text ']' - end - } + out + .text(string) + .nest(indent: string.length) { + unless children.empty? + out + .text('[') + .nest(indent: 1) { + first = true + children.each { |t| + if first + first = false + else + out.text(',').breakable + end + t.show(out) + } + } + .text ']' + end + } } end end @@ -159,7 +147,7 @@ def show(out) ].each do |expected, vals| vals.each do |width| it "must work with line width: #{width}" do - check_roundtrip(width, expected, builder_block) + assert_wadler(width, expected, builder_block) end end end @@ -172,7 +160,7 @@ def altshow(out) out.text @string unless @children.empty? out.text '[' - out.nest(2) { + out.nest(indent: 2) { out.breakable first = true @children.each { |t| @@ -246,7 +234,7 @@ def altshow(out) ].each do |expected, vals| vals.each do |width| it "must work with line width: #{width}" do - check_roundtrip(width, expected, builder_block_altshow) + assert_wadler(width, expected, builder_block_altshow) end end end @@ -256,11 +244,11 @@ def altshow(out) builder_block = proc { |out| out.group { out.group { - out.nest(2) { + out.nest(indent: 2) { out.text 'if' out.breakable out.group { - out.nest(2) { + out.nest(indent: 2) { out.group { out.text 'a' out.breakable @@ -274,11 +262,11 @@ def altshow(out) } out.breakable out.group { - out.nest(2) { + out.nest(indent: 2) { out.text 'then' out.breakable out.group { - out.nest(2) { + out.nest(indent: 2) { out.group { out.text 'a' out.breakable @@ -292,11 +280,11 @@ def altshow(out) } out.breakable out.group { - out.nest(2) { + out.nest(indent: 2) { out.text 'else' out.breakable out.group { - out.nest(2) { + out.nest(indent: 2) { out.group { out.text 'a' out.breakable @@ -410,7 +398,7 @@ def altshow(out) ].each do |expected, vals| vals.each do |width| it "must work with line width: #{width}" do - check_roundtrip(width, expected, builder_block) + assert_wadler(width, expected, builder_block) end end end @@ -442,7 +430,7 @@ def altshow(out) ].each do |expected, vals| vals.each do |width| it "must work with line width: #{width}" do - check_roundtrip(width, expected, builder_block) + assert_wadler(width, expected, builder_block) end end end @@ -451,22 +439,22 @@ def altshow(out) describe 'must not break if stack is full' do it 'must work with a big token list' do wadler = Oppen::Wadler.new(width: 3) - wadler.group(1) { - wadler.group(1) { - wadler.group(1) { - wadler.group(1) { - wadler.group(1) { - wadler.group(1) { - wadler.group(1) { - wadler.group(1) { - wadler.group(1) { - wadler.group(1) { - wadler.group(1) { - wadler.group(1) { - wadler.group(1) { - wadler.group(1) { - wadler.group(1) { - wadler.group(1) { + wadler.group(indent: 1) { + wadler.group(indent: 1) { + wadler.group(indent: 1) { + wadler.group(indent: 1) { + wadler.group(indent: 1) { + wadler.group(indent: 1) { + wadler.group(indent: 1) { + wadler.group(indent: 1) { + wadler.group(indent: 1) { + wadler.group(indent: 1) { + wadler.group(indent: 1) { + wadler.group(indent: 1) { + wadler.group(indent: 1) { + wadler.group(indent: 1) { + wadler.group(indent: 1) { + wadler.group(indent: 1) { wadler.text '1 +' } wadler.breakable @@ -563,7 +551,7 @@ def altshow(out) } } expected = 'This is a long sentence This is another long sentence.' - check_roundtrip(10, expected, builder_block) + assert_wadler(10, expected, builder_block) end it 'must work with a width bigger than text length' do @@ -584,7 +572,7 @@ def altshow(out) This is a small sentence This is another small sentence. LANG - check_roundtrip(100, expected, builder_block) + assert_wadler(100, expected, builder_block) end it 'must work with a width smaller than break length' do @@ -600,7 +588,7 @@ def altshow(out) } } expected = 'axxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxb' - check_roundtrip(5, expected, builder_block) + assert_wadler(5, expected, builder_block) end it 'must work with a width bigger than break length' do @@ -619,7 +607,7 @@ def altshow(out) This is a small sentence This is another small sentence. LANG - check_roundtrip(100, expected, builder_block) + assert_wadler(100, expected, builder_block) end end @@ -638,7 +626,7 @@ def altshow(out) it 'works with only a nest' do out = Oppen::Wadler.new - out.nest(0) { out.text 'Hello World' } + out.nest { out.text 'Hello World' } _(out.output).must_equal 'Hello World' end @@ -660,7 +648,7 @@ def altshow(out) describe 'handling empty lines' do it 'does not indent by default' do out = Oppen::Wadler.new(width: 10) - out.group(2) { + out.group(indent: 2) { out.text 'a' out.break out.break @@ -676,7 +664,7 @@ def altshow(out) it 'does not indent if empty first line' do out = Oppen::Wadler.new(width: 10) - out.group(8) { + out.group(indent: 8) { out.break out.break out.text 'b' @@ -691,7 +679,7 @@ def altshow(out) it 'does not indent if empty last line' do out = Oppen::Wadler.new(width: 10) - out.group(2) { + out.group(indent: 2) { out.text 'a' out.break out.break @@ -706,7 +694,7 @@ def altshow(out) it 'does not indent if empty first and last line' do out = Oppen::Wadler.new(width: 10) - out.group(8) { + out.group(indent: 8) { out.break out.break out.break @@ -715,4 +703,30 @@ def altshow(out) _(out.output).must_equal "\n\n\n" end end + + describe 'Convenience methods' do + it 'chains with calls of do' do + out = Oppen::Wadler.new(width: 10) + lam = -> { out.text 'from lambda' } + out.group(indent: 8) { + out + .do { lam.() } + .break + .do { lam.() } + .break + } + + _(out.output).must_equal <<~OUT + from lambda + from lambda + OUT + end + + it 'produces an actual space' do + out = Oppen::Wadler.new(width: 10) + out.text('Hello').space.text('World!') + + _(out.output).must_equal 'Hello World!' + end + end end