Skip to content
This repository
Browse code

defines String#indent [closes #7263] [Xavier Noria & Ace Suares]

  • Loading branch information...
commit 2f58795e783150f2e1b1f6c64e305703f0061129 1 parent 9cd1f69
Xavier Noria authored August 07, 2012
2  activesupport/CHANGELOG.md
Source Rendered
... ...
@@ -1,5 +1,7 @@
1 1
 ## Rails 4.0.0 (unreleased) ##
2 2
 
  3
+*   Add String#indent. *fxn & Ace Suares*
  4
+
3 5
 *   Inflections can now be defined per locale. `singularize` and `pluralize` accept locale as an extra argument. *David Celis*
4 6
 
5 7
 *   `Object#try` will now return nil instead of raise a NoMethodError if the receiving object does not implement the method, but you can still get the old behavior by using the new `Object#try!` *DHH*
1  activesupport/lib/active_support/core_ext/string.rb
@@ -10,3 +10,4 @@
10 10
 require 'active_support/core_ext/string/exclude'
11 11
 require 'active_support/core_ext/string/strip'
12 12
 require 'active_support/core_ext/string/inquiry'
  13
+require 'active_support/core_ext/string/indent'
43  activesupport/lib/active_support/core_ext/string/indent.rb
... ...
@@ -0,0 +1,43 @@
  1
+class String
  2
+  # Same as +indent+, except it indents the receiver in-place.
  3
+  #
  4
+  # Returns the indented string, or +nil+ if there was nothing to indent.
  5
+  def indent!(amount, indent_string=nil, indent_empty_lines=false)
  6
+    indent_string = indent_string || self[/^[ \t]/] || ' '
  7
+    re = indent_empty_lines ? /^/ : /^(?!$)/
  8
+    gsub!(re, indent_string * amount)
  9
+  end
  10
+
  11
+  # Indents the lines in the receiver:
  12
+  #
  13
+  #   <<EOS.indent(2)
  14
+  #   def some_method
  15
+  #     some_code
  16
+  #   end
  17
+  #   EOS
  18
+  #   # =>
  19
+  #     def some_method
  20
+  #       some_code
  21
+  #     end
  22
+  #
  23
+  # The second argument, +indent_string+, specifies which indent string to
  24
+  # use. The default is +nil+, which tells the method to make a guess by
  25
+  # peeking at the first indented line, and fallback to a space if there is
  26
+  # none.
  27
+  #
  28
+  #   "  foo".indent(2)        # => "    foo"
  29
+  #   "foo\n\t\tbar".indent(2) # => "\t\tfoo\n\t\t\t\tbar"
  30
+  #   "foo".indent(2, "\t")    # => "\t\tfoo"
  31
+  #
  32
+  # While +indent_string+ is tipically one space or tab, it may be any string.
  33
+  #
  34
+  # The third argument, +indent_empty_lines+, is a flag that says whether
  35
+  # empty lines should be indented. Default is false.
  36
+  #
  37
+  #   "foo\n\nbar".indent(2)            # => "  foo\n\n  bar"
  38
+  #   "foo\n\nbar".indent(2, nil, true) # => "  foo\n  \n  bar"
  39
+  #
  40
+  def indent(amount, indent_string=nil, indent_empty_lines=false)
  41
+    dup.tap {|_| _.indent!(amount, indent_string, indent_empty_lines)}
  42
+  end
  43
+end
56  activesupport/test/core_ext/string_ext_test.rb
@@ -9,6 +9,7 @@
9 9
 require 'active_support/time'
10 10
 require 'active_support/core_ext/string/strip'
11 11
 require 'active_support/core_ext/string/output_safety'
  12
+require 'active_support/core_ext/string/indent'
12 13
 
13 14
 module Ace
14 15
   module Base
@@ -521,3 +522,58 @@ class StringExcludeTest < ActiveSupport::TestCase
521 522
     assert_equal true, 'foo'.exclude?('p')
522 523
   end
523 524
 end
  525
+
  526
+class StringIndentTest < ActiveSupport::TestCase
  527
+  test 'does not indent strings that only contain newlines (edge cases)' do
  528
+    ['', "\n", "\n" * 7].each do |str|
  529
+      assert_nil str.indent!(8)
  530
+      assert_equal str, str.indent(8)
  531
+      assert_equal str, str.indent(1, "\t")
  532
+    end
  533
+  end
  534
+
  535
+  test "by default, indents with spaces if the existing indentation uses them" do
  536
+    assert_equal "    foo\n      bar", "foo\n  bar".indent(4)
  537
+  end
  538
+
  539
+  test "by default, indents with tabs if the existing indentation uses them" do
  540
+    assert_equal "\tfoo\n\t\t\bar", "foo\n\t\bar".indent(1)
  541
+  end
  542
+
  543
+  test "by default, indents with spaces as a fallback if there is no indentation" do
  544
+    assert_equal "   foo\n   bar\n   baz", "foo\nbar\nbaz".indent(3)
  545
+  end
  546
+
  547
+  # Nothing is said about existing indentation that mixes spaces and tabs, so
  548
+  # there is nothing to test.
  549
+
  550
+  test 'uses the indent char if passed' do
  551
+    assert_equal <<EXPECTED, <<ACTUAL.indent(4, '.')
  552
+....  def some_method(x, y)
  553
+....    some_code
  554
+....  end
  555
+EXPECTED
  556
+  def some_method(x, y)
  557
+    some_code
  558
+  end
  559
+ACTUAL
  560
+
  561
+    assert_equal <<EXPECTED, <<ACTUAL.indent(2, '&nbsp;')
  562
+&nbsp;&nbsp;&nbsp;&nbsp;def some_method(x, y)
  563
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;some_code
  564
+&nbsp;&nbsp;&nbsp;&nbsp;end
  565
+EXPECTED
  566
+&nbsp;&nbsp;def some_method(x, y)
  567
+&nbsp;&nbsp;&nbsp;&nbsp;some_code
  568
+&nbsp;&nbsp;end
  569
+ACTUAL
  570
+  end
  571
+
  572
+  test "does not indent blank lines by default" do
  573
+    assert_equal " foo\n\n bar", "foo\n\nbar".indent(1)
  574
+  end
  575
+
  576
+  test 'indents blank lines if told so' do
  577
+    assert_equal " foo\n \n bar", "foo\n\nbar".indent(1, nil, true)
  578
+  end
  579
+end
35  guides/source/active_support_core_extensions.textile
Source Rendered
@@ -1325,6 +1325,41 @@ that amount of leading whitespace.
1325 1325
 
1326 1326
 NOTE: Defined in +active_support/core_ext/string/strip.rb+.
1327 1327
 
  1328
+h4. +indent+
  1329
+
  1330
+Indents the lines in the receiver:
  1331
+
  1332
+<ruby>
  1333
+<<EOS.indent(2)
  1334
+def some_method
  1335
+  some_code
  1336
+end
  1337
+EOS
  1338
+# =>
  1339
+  def some_method
  1340
+    some_code
  1341
+  end
  1342
+</ruby>
  1343
+
  1344
+The second argument, +indent_string+, specifies which indent string to use. The default is +nil+, which tells the method to make an educated guess peeking at the first indented line, and fallback to a space if there is none.
  1345
+
  1346
+<ruby>
  1347
+"  foo".indent(2)        # => "    foo"
  1348
+"foo\n\t\tbar".indent(2) # => "\t\tfoo\n\t\t\t\tbar"
  1349
+"foo".indent(2, "\t")    # => "\t\tfoo"
  1350
+</ruby>
  1351
+
  1352
+While +indent_string+ is tipically one space or tab, it may be any string.
  1353
+
  1354
+The third argument, +indent_empty_lines+, is a flag that says whether empty lines should be indented. Default is false.
  1355
+
  1356
+<ruby>
  1357
+"foo\n\nbar".indent(2)            # => "  foo\n\n  bar"
  1358
+"foo\n\nbar".indent(2, nil, true) # => "  foo\n  \n  bar"
  1359
+</ruby>
  1360
+
  1361
+The +indent!+ method performs indentation in-place.
  1362
+
1328 1363
 h4. Access
1329 1364
 
1330 1365
 h5. +at(position)+

6 notes on commit 2f58795

Ace Suares

Excellent! I leared a lot, like self[/^[ \t]/] and /^(?!$)/.

So, the way I would use it, is strip_heredoc.indent(2), where strip_heredoc would leave the first line without any space or tab, and indent(2) would add spaces (the default) or I would have to specify '\t' as second argument. Great! and the extra flag for empty lines, also nice ot have.

Ace Suares

Thx for the credits (feel I hardly deserve them). And you closed the pull request by a commit, so now it's in rails? Wow.

Ace Suares

Hmm the only thing I don't get is the ! in /^(?!$)/

Xavier Noria
Owner
fxn commented on 2f58795 August 11, 2012

@acesuares exactly, as per the origin of the ticket you'd write

class C
  def some_method
    <<-EOS.strip_heredoc.indent(2)
      cofing.foo = :bar
      config.baz = :zoo
    EOS
  end
end

Regarding the regexp, as you surely know ^ is an anchor that matches start of string or beginning of line. In some programming languages a regexp modifier is needed to match beginning of line, Perl is one of them, but not in Ruby.

Then (?!...) says "and from this point on this should not match", where "this" is the regexp following the exclamation mark up to the closing paren. For example, /a(?!e)/ matches any a that is not followed by an e. That is technically called a negative look-ahead assertion. Our regexp in that negative assertion is very simple: $, which is just an anchor that matches at the end of line, optionally matching also the newline, or end of string.

So the whole regexp says: "please match at the beginning of any line that has something else than a newline or end of the string right after that", which is a fancy way to say "please match at the beginning of any line that is not empty" (a line is empty if consists only of the newline character or is an empty string).

Note that we match empty lines. In some contexts you also want to accept blank lines, that is, lines which are either empty or contain only whitespace. But in this case I opted to support only empty lines, because if a line already has 4 spaces, getting 6 or 4 after the indent is not big deal. So what we skip by default are empty lines, which is what you normally want if indenting source code.

Steve Klabnik
Collaborator

It's important to note that Ruby's $ works differently than in other languages' regular expressions, so take care!

Xavier Noria
Owner
fxn commented on 2f58795 August 11, 2012

Yeah, the same remark of ^ applies to $. In some languages you need to put a modifier to match end of line, but not in Ruby (that's why there's none in the regexps of this patch).

Please sign in to comment.
Something went wrong with that request. Please try again.