File: TextFormatter.rb

package info (click to toggle)
tj3 3.8.1-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 5,048 kB
  • sloc: ruby: 36,481; javascript: 1,113; sh: 19; makefile: 17
file content (185 lines) | stat: -rw-r--r-- 5,622 bytes parent folder | download | duplicates (4)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
#!/usr/bin/env ruby -w
# encoding: UTF-8
#
# = TextFormatter.rb -- The TaskJuggler III Project Management Software
#
# Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014
#               by Chris Schlaeger <cs@taskjuggler.org>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of version 2 of the GNU General Public License as
# published by the Free Software Foundation.
#

require 'taskjuggler/UTF8String'

class TaskJuggler

  # This class provides a simple text block formatting function. Plain text
  # can be indented and limited to a given text width.
  class TextFormatter

    attr_accessor :indentation, :width, :firstLineIndent

    def initialize(width = 80, indentation = 0, firstLineIndent = nil)
      # The width of the text including the indent.
      @width = width
      # The indent for the first line of a paragraph
      @firstLineIndent = firstLineIndent || indentation
      # The indent for other lines.
      @indentation = indentation
    end

    # Add @indentation number of spaces at the beginning of each line. The
    # first line will be indented by @firstLineIndent. Lines that are longer
    # than @width will be clipped.
    def indent(str)
      out = ''
      # Indentation to be used for the currently processed line. It will be
      # set to nil if it was inserted already.
      indentBuf = ' ' * @firstLineIndent
      linePos = 0
      # Process the input String from start to finish.
      str.each_utf8_char do |c|
        if c == "\n"
          # To prevent trailing white spaces we only insert a line break
          # instead of the indent buffer.
          if indentBuf
            out += "\n"
          end
          # The indent buffer for the next line.
          indentBuf = "\n" + ' ' * @indentation
        else
          # If we still have an indent buffer, we need to insert it first.
          if indentBuf
            out += indentBuf
            linePos = indentBuf.delete("\n").length
            indentBuf = nil
          end
          # Discard all characters that extend of the requested line width.
          if linePos < @width
            out << c
            linePos += 1
          end
        end
      end

      # Always end with a line break
      out += "\n" unless out[-1] == "\n"

      out
    end

    # Format the String _str_ according to the class settings.
    def format(str)
      # The resulting String.
      @out = ''
      # The column of the last character of the current line.
      @linePos = 0
      # A buffer for the currently processed word.
      @wordBuf = ''
      # True of we are at the beginning of a line.
      @beginOfLine = true
      # A buffer for the indentation to be used for the next line.
      @indentBuf = ' ' * @firstLineIndent
      # The status of the state machine.
      state = :beginOfParagraph

      # Process the input String from start to finish.
      str.each_utf8_char do |c|
        case state
        when :beginOfParagraph
          # We are currently a the beginning of a new paragraph.
          if c == ' ' || c == "\n"
            # ignore it
          else
            # A new word started.
            @wordBuf << c
            state = :inWord
          end
        when :inWord
          # We are in the middle of processing a word.
          if c == ' ' || c == "\n"
            # The word has ended.
            appendWord
            state = c == ' ' ? :betweenWords : :betweenWordsOrLines
          elsif c == "\r"
            # CR is used to start a new line but without starting a new
            # paragraph.
            appendWord
            @indentBuf = "\n" + ' ' * @firstLineIndent
            @beginOfLine = true
            state = :betweenWords
          else
            # Add the character to the word buffer.
            @wordBuf << c
          end
        when :betweenWords
          # We are in between words.
          if c == ' '
            # ignore it
          elsif c == "\n"
            state = :betweenWordsOrLines
          else
            # A new word started.
            @wordBuf << c
            state = :inWord
          end
        when :betweenWordsOrLines
          if c == "\n"
            # The word break is really a paragraph break.
            @indentBuf = "\n\n" + ' ' * @firstLineIndent
            @beginOfLine = true
            state = :beginOfParagraph
          elsif c == ' '
            state = :betweenWords
          else
            @wordBuf << c
            state = :inWord
          end
        else
          raise "Unknown state in state machine: #{state}"
        end
      end
      # Add any still pending word.
      appendWord

      # Always end with a line break
      @out += "\n" unless @out[-1] == "\n"

      @out
    end

    private

    def appendWord
      # Ignore empty words.
      wordLength = @wordBuf.length
      return unless wordLength > 0

      # If the word does not fit into the current line anymore, we have to
      # start a new line.
      @beginOfLine = true if @linePos + 1 + wordLength > @width

      if @beginOfLine
        # Insert the content of the @indentBuf and reset @linePos and
        # @indentBuf.
        @out += @indentBuf
        @linePos = @indentBuf.delete("\n").length
        @indentBuf = "\n" + ' ' * @indentation
      else
        # Insert a space to separate the words.
        @out += ' '
        @linePos += 1
      end

      # Append the word and reset the @wordBuf.
      @out += @wordBuf
      @wordBuf = ''
      @linePos += wordLength
      @beginOfLine = false if @beginOfLine
    end

  end

end