summaryrefslogtreecommitdiff
path: root/lib/highline/list.rb
blob: 8e0a0b0261cebab643866b23fa2b57d60b9feec9 (plain)
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
# coding: utf-8

class HighLine
  # List class with some convenience methods like {#col_down}.
  class List
    # Original given *items* argument.
    # It's frozen at initialization time and
    # all later transformations will happen on {#list}.
    # @return [Array]
    attr_reader :items

    # Number of columns for each list row.
    # @return [Integer]
    attr_reader :cols

    # Columns turn into rows in transpose mode.
    # @return [Boolean]
    #
    # @example A two columns array like this:
    #   [ [ "a", "b" ],
    #     [ "c", "d" ],
    #     [ "e", "f" ],
    #     [ "g", "h" ],
    #     [ "i", "j" ] ]
    #
    # @example When in transpose mode will be like this:
    #   [ [ "a", "c", "e", "g", "i" ],
    #     [ "b", "d", "f", "h", "j" ] ]
    #
    # @see #col_down_mode

    attr_reader :transpose_mode

    # Content are distributed first by column in col down mode.
    # @return [Boolean]
    #
    # @example A two columns array like this:
    #   [ [ "a", "b" ],
    #     [ "c", "d" ],
    #     [ "e", "f" ],
    #     [ "g", "h" ],
    #     [ "i", "j" ] ]
    #
    # @example In col down mode will be like this:
    #   [ [ "a", "f"],
    #     [ "b", "g"],
    #     [ "c", "h"],
    #     [ "d", "i"],
    #     [ "e", "j"] ]
    #
    # @see #transpose_mode

    attr_reader :col_down_mode

    # @param items [#to_a] an array of items to compose the list.
    # @param options [Hash] a hash of options to tailor the list.
    # @option options [Boolean] :transpose (false) set {#transpose_mode}.
    # @option options [Boolean] :col_down (false) set {#col_down_mode}.
    # @option options [Integer] :cols (1) set {#cols}.

    def initialize(items, options = {})
      @items          = items.to_a.dup.freeze
      @transpose_mode = options.fetch(:transpose) { false }
      @col_down_mode  = options.fetch(:col_down)  { false }
      @cols           = options.fetch(:cols)      { 1 }
      build
    end

    # Transpose the (already sliced by rows) list, turning its rows into columns.
    # @return [self]
    def transpose
      first_row = @list[0]
      other_rows = @list[1..-1]
      @list = first_row.zip(*other_rows)
      self
    end

    # Slice the list by rows and transpose it.
    # @return [self]
    def col_down
      slice_by_rows
      transpose
      self
    end

    # Slice the list by rows. The row count is calculated
    # indirectly based on the {#cols} param and the items count.
    # @return [self]
    def slice_by_rows
      @list = items_sliced_by_rows
      self
    end

    # Slice the list by cols based on the {#cols} param.
    # @return [self]
    def slice_by_cols
      @list = items_sliced_by_cols
      self
    end

    # Set the cols number.
    # @return [self]
    def cols=(cols)
      @cols = cols
      build
    end

    # Returns an Array representation of the list
    # in its current state.
    # @return [Array] @list.dup
    def list
      @list.dup
    end

    # (see #list)
    def to_a
      list
    end

    # Stringfies the list in its current state.
    # It joins each individual _cell_ with the current
    # {#row_join_string} between them.
    # It joins each individual row with a
    # newline character. So the returned String is
    # suitable to be directly outputed
    # to the screen, preserving row/columns divisions.
    # @return [String]
    def to_s
      list.map { |row| stringfy(row) }.join
    end

    # The String that will be used to join each
    # cell of the list and stringfying it.
    # @return [String] defaults to " " (space)
    def row_join_string
      @row_join_string ||= "  "
    end

    # Set the {#row_join_string}.
    # @see #row_join_string
    attr_writer :row_join_string

    # Returns the row join string size.
    # Useful for calculating the actual size of
    # rendered list.
    # @return [Integer]
    def row_join_str_size
      row_join_string.size
    end

    private

    def build
      slice_by_cols
      transpose if transpose_mode
      col_down  if col_down_mode
      self
    end

    def items_sliced_by_cols
      items.each_slice(cols).to_a
    end

    def items_sliced_by_rows
      items.each_slice(row_count).to_a
    end

    def row_count
      (items.count / cols.to_f).ceil
    end

    def stringfy(row)
      row.compact.join(row_join_string) + "\n"
    end
  end
end