youplot 0.3.5 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6bfd3292028fb88c3a5c902884d6c3dd17f59292eb4980b09a75396c921cb46f
4
- data.tar.gz: '00524329ddcea39543d4a064e5f930c417544f86a853f538823eb6ece37ac255'
3
+ metadata.gz: 83145e216c0d124d537c8a01d63f9203730a74191fefcb9de5743b08e0e504bd
4
+ data.tar.gz: a246eab22f11ab8c526cc838d9f2fbf7d249721b4782287087c08a5410f8647b
5
5
  SHA512:
6
- metadata.gz: 3b37389aab9904bc7129397a8c04b6a1d6e244bc1af986f57d21131eec55cf7051308a7eabc2e81d17e8e4838d45d9c5d6cc65169375329e9884726a2ea49408
7
- data.tar.gz: 6a1c8605980520e8d0bae43b0302d6e07d5511e6bf85d0fd3def1c6c994ed5e1375a076af69ef843b2a1a0f5e7bbbb877148ef03bf833a734c53c1115e59c0fd
6
+ metadata.gz: d9138c26fade36d7d0ca19100b47dcb8e65c8c884d13b74d258dcae9ea304ced2fc783b08e871d76607e57e394e6b861c81f1f5030caba53493b2f4740a05cd2
7
+ data.tar.gz: f293d686791ba1481948877e534c9085e99c24ab9c36026979002ecaa9ee2ece6f12ad7193cda936c62e472e2f94008466cc666e9405157bfcc348c4c21c4579
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  <p align="center">
2
2
  <img src="logo.svg" width="60%" height="60%" />
3
- </7>
3
+ </p>
4
4
 
5
5
  ![Build Status](https://github.com/kojix2/youplot/workflows/test/badge.svg)
6
6
  [![Gem Version](https://badge.fury.io/rb/youplot.svg)](https://badge.fury.io/rb/youplot)
@@ -106,8 +106,8 @@ In this example, YouPlot counts the number of chromosomes where the gene is loca
106
106
 
107
107
  ```sh
108
108
  cat gencode.v35.annotation.gff3 \
109
- | grep -v '#' | grep 'gene' | cut -f1 | \
110
- uplot count -t "The number of human gene annotations per chromosome" -c blue
109
+ | grep -v '#' | grep 'gene' | cut -f1 \
110
+ | uplot count -t "The number of human gene annotations per chromosome" -c blue
111
111
  ```
112
112
 
113
113
  <p align="center">
@@ -119,7 +119,7 @@ This is fine in most cases, as long as the data size is small. If you want to vi
119
119
 
120
120
  ```sh
121
121
  cat gencode.v35.annotation.gff3 | grep -v '#' | grep 'gene' | cut -f1 \
122
- |sort | uniq -c | sort -nrk2 | awk '{print $2,$1}' \
122
+ | sort | uniq -c | sort -nrk2 | awk '{print $2,$1}' \
123
123
  | uplot bar -d ' ' -t "The number of human gene annotations per chromosome" -c blue
124
124
  ```
125
125
 
@@ -130,15 +130,15 @@ cat gencode.v35.annotation.gff3 | grep -v '#' | grep 'gene' | cut -f1 \
130
130
  Wouldn't it be a pain to have to run R, Python, Julia, gnuplot or whatever REPL just to check your data?
131
131
  YouPlot is a command line tool for this purpose. With YouPlot, you can continue working without leaving your terminal and shell.
132
132
 
133
- ### how to use YouPlot?
133
+ ### How to use YouPlot?
134
134
 
135
135
  `uplot` is the shortened form of `youplot`. You can use either.
136
136
 
137
- | | |
138
- |-----------------------------------|------------------------------------------------|
139
- | Reads data from standard input | `cat data.tsv \| uplot <command> [options]` |
140
- | Reads data from files | `uplot <command> [options] data.tsv ...` |
141
- | Outputs data from stdin to stdout | `pipeline1 \| uplot <command> -O \| pipeline2` |
137
+ | Command | Description |
138
+ |------------------------------------------------|-----------------------------------|
139
+ | `cat data.tsv \| uplot <command> [options]` | Take input from stdin |
140
+ | `uplot <command> [options] data.tsv ...` | Take input from files |
141
+ | `pipeline1 \| uplot <command> -O \| pipeline2` | Outputs data from stdin to stdout |
142
142
 
143
143
  ### Where to output the plot?
144
144
 
@@ -148,7 +148,8 @@ The output file or stream for the plot can be specified with the `-o` option.
148
148
  ### Where to output the input data?
149
149
 
150
150
  By default, the input data is not shown anywhere.
151
- The `-O` option, with no arguments, outputs the input data directly to the standard output. This is useful when passing data to a subsequent pipeline.
151
+ The `-O` option, with no arguments, outputs the input data directly to the standard output.
152
+ This is useful when passing data to a subsequent pipeline.
152
153
 
153
154
  ### What types of plots are available?
154
155
 
@@ -176,11 +177,21 @@ If your input data contains a header line, you need to specify the `-H` option.
176
177
 
177
178
  ### How to specify the delimiter?
178
179
 
179
- Use the `-d` option. To specify a blank space, you can use `uplot bar -d ' ' data.txt`. You do not need to use `-d` option for tab-delimited text since the default value is tab.
180
+ Use the `-d` option. To specify a blank space, you can use `uplot bar -d ' ' data.txt`.
181
+ You do not need to use `-d` option for tab-delimited text since the default value is tab.
180
182
 
181
183
  ### Is there a way to specify a column as the x-axis or y-axis?
182
184
 
183
- Not yet. In principle, YouPlot treats the first column as the X axis and the second column as the Y axis. When working with multiple series, the first row is the X axis, the second row is series 1, the third row is series 2, and so on. If you pass only one column of data for `line` and `bar`, YouPlot will automatically use a sequential number starting from 1 as the X-axis. The `--fmt xyy`, `--fmt xyxy` and `--fmt yx` options give you a few more choices. See `youplot <command> --help` for more details. YouPlot has limited functionalities, but you can use shell scripts such as `awk '{print $2, $1}'` to swap lines.
185
+ Not yet.
186
+ YouPlot treats the first column as the X axis and the second column as the Y axis.
187
+ When working with multiple series, the first column is the X axis, the second column is series Y1, the third column is series Y2, and so on.
188
+ If you pass only one column of data for `line` and `bar`, YouPlot will automatically use a sequential number starting from 1 as the X-axis.
189
+
190
+ * `--fmt xyy` `--fmt xyxy` `--fmt yx` options give you a few more choices.
191
+ See `youplot <command> --help` for more details.
192
+
193
+ * Use `awk '{print $2, $1}'` to swap lines.
194
+ * Use `paste` to concatenate series.
184
195
 
185
196
  ### How to plot real-time data?
186
197
 
@@ -216,14 +227,15 @@ uplot colors
216
227
 
217
228
  ## Contributing
218
229
 
219
- YouPlot is a library under development, so even small improvements like typofix are welcome! Please feel free to send us your pull requests.
230
+ YouPlot is a library under development, so even small improvements like typofix are welcome!
231
+ Please feel free to send us your pull requests.
220
232
 
221
233
  * [Report bugs](https://github.com/kojix2/youplot/issues)
222
234
  * Fix bugs and [submit pull requests](https://github.com/kojix2/youplot/pulls)
223
235
  * Write, clarify, or fix documentation
224
236
  * English corrections by native speakers are welcome.
225
237
  * Suggest or add new features
226
-
238
+ * Make a donation
227
239
 
228
240
  ### Development
229
241
 
data/lib/youplot.rb CHANGED
@@ -1,9 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'unicode_plot'
4
- require 'youplot/version'
5
- require 'youplot/dsv'
6
- require 'youplot/command'
3
+ require_relative 'youplot/version'
4
+ require_relative 'youplot/dsv'
5
+ require_relative 'youplot/parameters'
6
+ require_relative 'youplot/command'
7
7
 
8
8
  module YouPlot
9
9
  class << self
@@ -11,12 +11,16 @@ module YouPlot
11
11
  if tally && Enumerable.method_defined?(:tally)
12
12
  arr.tally
13
13
  else
14
- # https://github.com/marcandre/backports
15
- arr.each_with_object(Hash.new(0)) { |item, res| res[item] += 1 }
16
- .tap { |h| h.default = nil }
14
+ # value_counts Enumerable::Statistics
15
+ arr.value_counts(dropna: false)
17
16
  end
18
- .sort { |a, b| a[1] <=> b[1] }
19
- .reverse
17
+ .sort do |a, b|
18
+ # compare values
19
+ r = b[1] <=> a[1]
20
+ # If the values are the same, compare by name
21
+ r = a[0] <=> b[0] if r == 0
22
+ r
23
+ end
20
24
  .transpose
21
25
  end
22
26
  end
@@ -1,12 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # UnicodePlot - Plot your data by Unicode characters
4
+ # https://github.com/red-data-tools/unicode_plot.rb
5
+
3
6
  require_relative 'processing'
4
7
  require 'unicode_plot'
5
8
 
6
9
  module YouPlot
7
10
  # plotting functions.
8
11
  module Backends
9
- module UnicodePlotBackend
12
+ module UnicodePlot
10
13
  class Error < StandardError; end
11
14
 
12
15
  module_function
@@ -39,7 +42,7 @@ module YouPlot
39
42
  labels = series[x_col]
40
43
  values = series[y_col].map(&:to_f)
41
44
  end
42
- UnicodePlot.barplot(labels, values, **params.to_hc)
45
+ ::UnicodePlot.barplot(labels, values, **params.to_hc)
43
46
  end
44
47
 
45
48
  def histogram(data, params)
@@ -47,7 +50,7 @@ module YouPlot
47
50
  series = data.series
48
51
  params.title ||= data.headers[0] if headers
49
52
  values = series[0].map(&:to_f)
50
- UnicodePlot.histogram(values, **params.to_hc)
53
+ ::UnicodePlot.histogram(values, **params.to_hc)
51
54
  end
52
55
 
53
56
  def line(data, params, fmt = nil)
@@ -57,7 +60,7 @@ module YouPlot
57
60
  # If there is only one series, it is assumed to be sequential data.
58
61
  params.ylabel ||= headers[0] if headers
59
62
  y = series[0].map(&:to_f)
60
- UnicodePlot.lineplot(y, **params.to_hc)
63
+ ::UnicodePlot.lineplot(y, **params.to_hc)
61
64
  else
62
65
  # If there are 2 or more series...
63
66
  if fmt == 'yx'
@@ -75,7 +78,7 @@ module YouPlot
75
78
  end
76
79
  x = series[x_col].map(&:to_f)
77
80
  y = series[y_col].map(&:to_f)
78
- UnicodePlot.lineplot(x, y, **params.to_hc)
81
+ ::UnicodePlot.lineplot(x, y, **params.to_hc)
79
82
  end
80
83
  end
81
84
 
@@ -94,9 +97,9 @@ module YouPlot
94
97
  end
95
98
  params.xlim ||= series[0].flatten.minmax # why need?
96
99
  params.ylim ||= series[1..-1].flatten.minmax # why need?
97
- plot = UnicodePlot.public_send(method1, series[0], series[1], **params.to_hc)
100
+ plot = ::UnicodePlot.public_send(method1, series[0], series[1], **params.to_hc)
98
101
  2.upto(series.size - 1) do |i|
99
- UnicodePlot.public_send(method2, plot, series[0], series[i], name: headers&.[](i))
102
+ ::UnicodePlot.public_send(method2, plot, series[0], series[i], name: headers&.[](i))
100
103
  end
101
104
  plot
102
105
  end
@@ -112,9 +115,9 @@ module YouPlot
112
115
  params.xlim ||= series2.map(&:first).flatten.minmax # why need?
113
116
  params.ylim ||= series2.map(&:last).flatten.minmax # why need?
114
117
  x1, y1 = series2.shift
115
- plot = UnicodePlot.public_send(method1, x1, y1, **params.to_hc)
118
+ plot = ::UnicodePlot.public_send(method1, x1, y1, **params.to_hc)
116
119
  series2.each_with_index do |(xi, yi), i|
117
- UnicodePlot.public_send(method2, plot, xi, yi, name: headers&.[]((i + 1) * 2))
120
+ ::UnicodePlot.public_send(method2, plot, xi, yi, name: headers&.[]((i + 1) * 2))
118
121
  end
119
122
  plot
120
123
  end
@@ -152,13 +155,13 @@ module YouPlot
152
155
  series = data.series
153
156
  headers ||= (1..series.size).map(&:to_s)
154
157
  series.map! { |s| s.map(&:to_f) }
155
- UnicodePlot.boxplot(headers, series, **params.to_hc)
158
+ ::UnicodePlot.boxplot(headers, series, **params.to_hc)
156
159
  end
157
160
 
158
161
  def colors(color_names = false)
159
162
  # FIXME
160
163
  s = String.new
161
- UnicodePlot::StyledPrinter::TEXT_COLORS.each do |k, v|
164
+ ::UnicodePlot::StyledPrinter::TEXT_COLORS.each do |k, v|
162
165
  s << v
163
166
  s << k.to_s
164
167
  unless color_names
@@ -1,10 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'dsv'
4
- require_relative 'command/parser'
4
+ require_relative 'parser'
5
5
 
6
6
  # FIXME
7
- require_relative 'backends/unicode_plot_backend'
7
+ require_relative 'backends/unicode_plot'
8
8
 
9
9
  module YouPlot
10
10
  Data = Struct.new(:headers, :series)
@@ -19,7 +19,7 @@ module YouPlot
19
19
  @command = nil
20
20
  @params = nil
21
21
  @options = nil
22
- @backend = YouPlot::Backends::UnicodePlotBackend
22
+ @backend = YouPlot::Backends::UnicodePlot
23
23
  end
24
24
 
25
25
  def run_as_executable
@@ -33,21 +33,34 @@ module YouPlot
33
33
  @options ||= parser.options
34
34
  @params ||= parser.params
35
35
 
36
+ # color command
36
37
  if %i[colors color colours colour].include? @command
37
38
  plot = create_plot
38
39
  output_plot(plot)
39
- elsif options[:progressive]
40
+ return
41
+ end
42
+
43
+ # progressive mode
44
+ if options[:progressive]
40
45
  stop = false
41
46
  Signal.trap(:INT) { stop = true }
42
- options[:output].print "\e[?25l" # make cursor invisible
47
+
48
+ # make cursor invisible
49
+ options[:output].print "\e[?25l"
50
+
51
+ # mainloop
43
52
  while (input = Kernel.gets)
44
53
  n = main_progressive(input)
45
54
  break if stop
46
55
 
47
56
  options[:output].print "\e[#{n}F"
48
57
  end
58
+
49
59
  options[:output].print "\e[0J"
50
- options[:output].print "\e[?25h" # make cursor visible
60
+ # make cursor visible
61
+ options[:output].print "\e[?25h"
62
+
63
+ # normal mode
51
64
  else
52
65
  # Sometimes the input file does not end with a newline code.
53
66
  while (input = Kernel.gets(nil))
@@ -59,23 +72,32 @@ module YouPlot
59
72
  private
60
73
 
61
74
  def main(input)
75
+ # Outputs input data to a file or stdout.
62
76
  output_data(input)
63
77
 
64
- @data = read_dsv(input)
78
+ @data = parse_dsv(input)
65
79
 
80
+ # Debug mode, show parsed results
66
81
  pp @data if options[:debug]
67
82
 
83
+ # When run as a program instead of a library
68
84
  if YouPlot.run_as_executable?
69
85
  begin
70
86
  plot = create_plot
71
87
  rescue ArgumentError => e
88
+ # Show only one line of error.
72
89
  warn e.backtrace[0]
90
+ # Show error message in purple
73
91
  warn "\e[35m#{e}\e[0m"
92
+ # Explicitly terminated with exit code: 1
74
93
  exit 1
75
94
  end
95
+
96
+ # When running YouPlot as a library (e.g. for testing)
76
97
  else
77
98
  plot = create_plot
78
99
  end
100
+
79
101
  output_plot(plot)
80
102
  end
81
103
 
@@ -95,15 +117,28 @@ module YouPlot
95
117
  @raw_data << input
96
118
 
97
119
  # FIXME
98
- @data = read_dsv(@raw_data)
120
+ @data = parse_dsv(@raw_data)
99
121
 
100
122
  plot = create_plot
101
123
  output_plot_progressive(plot)
102
124
  end
103
125
 
104
- def read_dsv(input)
105
- input = input.dup.force_encoding(options[:encoding]).encode('utf-8') if options[:encoding]
106
- DSV.parse(input, options[:delimiter], options[:headers], options[:transpose])
126
+ def parse_dsv(input)
127
+ # If encoding is specified, convert to UTF-8
128
+ if options[:encoding]
129
+ input.force_encoding(options[:encoding])
130
+ .encode!('utf-8')
131
+ end
132
+
133
+ begin
134
+ data = DSV.parse(input, options[:delimiter], options[:headers], options[:transpose])
135
+ rescue CSV::MalformedCSVError => e
136
+ warn 'Failed to parse the text. '
137
+ warn 'Please try to set the correct character encoding with --encoding option.'
138
+ raise e
139
+ end
140
+
141
+ data
107
142
  end
108
143
 
109
144
  def create_plot
@@ -147,9 +182,9 @@ module YouPlot
147
182
 
148
183
  def output_plot(plot)
149
184
  case options[:output]
150
- when IO
185
+ when IO, StringIO
151
186
  plot.render(options[:output])
152
- else
187
+ when String, Tempfile
153
188
  File.open(options[:output], 'w') do |f|
154
189
  plot.render(f)
155
190
  end
@@ -158,7 +193,7 @@ module YouPlot
158
193
 
159
194
  def output_plot_progressive(plot)
160
195
  case options[:output]
161
- when IO
196
+ when IO, StringIO
162
197
  # RefactorMe
163
198
  out = StringIO.new(String.new)
164
199
  def out.tty?
data/lib/youplot/dsv.rb CHANGED
@@ -3,41 +3,49 @@
3
3
  require 'csv'
4
4
 
5
5
  module YouPlot
6
- # Read and interpret Delimiter-separated values format file or stream.
6
+ # Module to handle DSV (Delimiter-separated values) format.
7
+ # Extract header and series.
7
8
  module DSV
8
9
  module_function
9
10
 
10
11
  def parse(input, delimiter, headers, transpose)
11
- arr = parse_as_csv(input, delimiter)
12
+ # Parse as CSV
13
+ arr = CSV.parse(input, col_sep: delimiter)
14
+
15
+ # Remove blank lines
16
+ arr.delete_if do |i|
17
+ i == [] or i.all? nil
18
+ end
19
+
20
+ # get header
12
21
  headers = get_headers(arr, headers, transpose)
22
+
23
+ # get series
13
24
  series = get_series(arr, headers, transpose)
14
- if headers.nil?
15
- Data.new(headers, series)
16
- else
17
- if headers.include?(nil)
18
- warn "\e[35mHeaders contains nil in it.\e[0m"
19
- elsif headers.include? ''
20
- warn "\e[35mHeaders contains \"\" in it.\e[0m"
21
- end
22
- h_size = headers.size
23
- s_size = series.size
24
- if h_size == s_size
25
- Data.new(headers, series)
26
- elsif h_size > s_size
27
- warn "\e[35mThe number of headers is greater than the number of series.\e[0m"
28
- exit 1 if YouPlot.run_as_executable?
29
- elsif h_size < s_size
30
- warn "\e[35mThe number of headers is less than the number of series.\e[0m"
31
- exit 1 if YouPlot.run_as_executable?
32
- end
25
+
26
+ # Return if No header
27
+ return Data.new(headers, series) if headers.nil?
28
+
29
+ # Warn if header contains nil
30
+ warn "\e[35mHeaders contains nil in it.\e[0m" if headers.include?(nil)
31
+
32
+ # Warn if header contains ''
33
+ warn "\e[35mHeaders contains \"\" in it.\e[0m" if headers.include? ''
34
+
35
+ # Make sure the number of elements in the header matches the number of series.
36
+ h_size = headers.size
37
+ s_size = series.size
38
+
39
+ if h_size > s_size
40
+ warn "\e[35mThe number of headers is greater than the number of series.\e[0m"
41
+ exit 1 if YouPlot.run_as_executable?
42
+
43
+ elsif h_size < s_size
44
+ warn "\e[35mThe number of headers is less than the number of series.\e[0m"
45
+ exit 1 if YouPlot.run_as_executable?
33
46
  end
34
- end
35
47
 
36
- def parse_as_csv(input, delimiter)
37
- CSV.parse(input, col_sep: delimiter)
38
- .delete_if do |i|
39
- i == [] or i.all? nil
40
- end
48
+ Data.new(headers, series) if h_size == s_size
41
49
  end
42
50
 
43
51
  # Transpose different sized ruby arrays
@@ -47,31 +55,33 @@ module YouPlot
47
55
  end
48
56
 
49
57
  def get_headers(arr, headers, transpose)
50
- if headers
51
- if transpose
52
- arr.map(&:first)
53
- else
54
- arr[0]
55
- end
56
- end
58
+ # header(-)
59
+ return nil unless headers
60
+
61
+ # header(+) trenspose(+)
62
+ return arr.map(&:first) if transpose
63
+
64
+ # header(+) transpose(-)
65
+ arr[0]
57
66
  end
58
67
 
59
68
  def get_series(arr, headers, transpose)
60
- if headers
61
- if arr.size > 1
62
- if transpose
63
- arr.map { |row| row[1..-1] }
64
- else
65
- transpose2(arr[1..-1])
66
- end
67
- else
68
- Array.new(arr[0].size, [])
69
- end
70
- elsif transpose
71
- arr
72
- else
73
- transpose2(arr)
69
+ # header(-)
70
+ unless headers
71
+ return arr if transpose
72
+
73
+ return transpose2(arr)
74
74
  end
75
+
76
+ # header(+) but no element in the series.
77
+ # TODO: should raise error?
78
+ return Array.new(arr[0].size, []) if arr.size == 1
79
+
80
+ # header(+) transpose(+)
81
+ return arr.map { |row| row[1..-1] } if transpose
82
+
83
+ # header(+) transpose(-)
84
+ transpose2(arr[1..-1])
75
85
  end
76
86
  end
77
87
  end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module YouPlot
4
+ # Command line options that are not Plot parameters
5
+ Options = Struct.new(
6
+ :delimiter,
7
+ :transpose,
8
+ :headers,
9
+ :pass,
10
+ :output,
11
+ :fmt,
12
+ :progressive,
13
+ :encoding,
14
+ :color_names,
15
+ :debug,
16
+ keyword_init: true
17
+ )
18
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module YouPlot
4
+ # UnicodePlot parameters.
5
+ # Why Struct, not Hash?
6
+ # * The keys are static in Struct.
7
+ # * Struct does not conflict with keyword arguments. Hash dose.
8
+ Parameters = Struct.new(
9
+ # Sort me!
10
+ :title,
11
+ :width,
12
+ :height,
13
+ :border,
14
+ :margin,
15
+ :padding,
16
+ :color,
17
+ :xlabel,
18
+ :ylabel,
19
+ :labels,
20
+ :symbol,
21
+ :xscale,
22
+ :nbins,
23
+ :closed,
24
+ :canvas,
25
+ :xlim,
26
+ :ylim,
27
+ :grid,
28
+ :name
29
+ ) do
30
+ def to_hc
31
+ to_h.compact
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,306 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'optparse'
4
+ require_relative 'options'
5
+
6
+ module YouPlot
7
+ # Class for parsing command line options
8
+ class Parser
9
+ class Error < StandardError; end
10
+
11
+ attr_reader :command, :options, :params,
12
+ :main_parser, :sub_parser
13
+
14
+ def initialize
15
+ @command = nil
16
+
17
+ @options = Options.new(
18
+ delimiter: "\t",
19
+ transpose: false,
20
+ headers: nil,
21
+ pass: false,
22
+ output: $stderr,
23
+ fmt: 'xyy',
24
+ progressive: false,
25
+ encoding: nil,
26
+ color_names: false,
27
+ debug: false
28
+ )
29
+
30
+ @params = Parameters.new
31
+ end
32
+
33
+ def create_base_parser
34
+ OptionParser.new do |parser|
35
+ parser.program_name = 'YouPlot'
36
+ parser.version = YouPlot::VERSION
37
+ parser.summary_width = 23
38
+ parser.on_tail('') # Add a blank line at the end
39
+ parser.separator('')
40
+ parser.on('Common options:')
41
+ parser.on('-O', '--pass [FILE]', 'file to output input data to [stdout]',
42
+ 'for inserting YouPlot in the middle of Unix pipes') do |v|
43
+ options[:pass] = v || $stdout
44
+ end
45
+ parser.on('-o', '--output [FILE]', 'file to output plots to [stdout]',
46
+ 'If no option is specified, plot will print to stderr') do |v|
47
+ options[:output] = v || $stdout
48
+ end
49
+ parser.on('-d', '--delimiter DELIM', String, 'use DELIM instead of [TAB] for field delimiter') do |v|
50
+ options[:delimiter] = v
51
+ end
52
+ parser.on('-H', '--headers', TrueClass, 'specify that the input has header row') do |v|
53
+ options[:headers] = v
54
+ end
55
+ parser.on('-T', '--transpose', TrueClass, 'transpose the axes of the input data') do |v|
56
+ options[:transpose] = v
57
+ end
58
+ parser.on('-t', '--title STR', String, 'print string on the top of plot') do |v|
59
+ params.title = v
60
+ end
61
+ parser.on('-x', '--xlabel STR', String, 'print string on the bottom of the plot') do |v|
62
+ params.xlabel = v
63
+ end
64
+ parser.on('-y', '--ylabel STR', String, 'print string on the far left of the plot') do |v|
65
+ params.ylabel = v
66
+ end
67
+ parser.on('-w', '--width INT', Integer, 'number of characters per row') do |v|
68
+ params.width = v
69
+ end
70
+ parser.on('-h', '--height INT', Numeric, 'number of rows') do |v|
71
+ params.height = v
72
+ end
73
+ border_options = UnicodePlot::BORDER_MAP.keys.join(', ')
74
+ parser.on('-b', '--border STR', String, 'specify the style of the bounding box', "(#{border_options})") do |v|
75
+ params.border = v.to_sym
76
+ end
77
+ parser.on('-m', '--margin INT', Numeric, 'number of spaces to the left of the plot') do |v|
78
+ params.margin = v
79
+ end
80
+ parser.on('--padding INT', Numeric, 'space of the left and right of the plot') do |v|
81
+ params.padding = v
82
+ end
83
+ parser.on('-c', '--color VAL', String, 'color of the drawing') do |v|
84
+ params.color = v =~ /\A[0-9]+\z/ ? v.to_i : v.to_sym
85
+ end
86
+ parser.on('--[no-]labels', TrueClass, 'hide the labels') do |v|
87
+ params.labels = v
88
+ end
89
+ parser.on('-p', '--progress', TrueClass, 'progressive mode [experimental]') do |v|
90
+ options[:progressive] = v
91
+ end
92
+ parser.on('-C', '--color-output', TrueClass, 'colorize even if writing to a pipe') do |_v|
93
+ UnicodePlot::IOContext.define_method(:color?) { true } # FIXME
94
+ end
95
+ parser.on('-M', '--monochrome', TrueClass, 'no colouring even if writing to a tty') do |_v|
96
+ UnicodePlot::IOContext.define_method(:color?) { false } # FIXME
97
+ end
98
+ parser.on('--encoding STR', String, 'Specify the input encoding') do |v|
99
+ options[:encoding] = v
100
+ end
101
+ # Optparse adds the help option, but it doesn't show up in usage.
102
+ # This is why you need the code below.
103
+ parser.on('--help', 'print sub-command help menu') do
104
+ puts parser.help
105
+ exit if YouPlot.run_as_executable?
106
+ end
107
+ parser.on('--debug', TrueClass, 'print preprocessed data') do |v|
108
+ options[:debug] = v
109
+ end
110
+ # yield opt if block_given?
111
+ end
112
+ end
113
+
114
+ def create_main_parser
115
+ @main_parser = create_base_parser
116
+ main_parser.banner = \
117
+ <<~MSG
118
+
119
+ Program: YouPlot (Tools for plotting on the terminal)
120
+ Version: #{YouPlot::VERSION} (using UnicodePlot #{UnicodePlot::VERSION})
121
+ Source: https://github.com/kojix2/youplot
122
+
123
+ Usage: uplot <command> [options] <in.tsv>
124
+
125
+ Commands:
126
+ barplot bar draw a horizontal barplot
127
+ histogram hist draw a horizontal histogram
128
+ lineplot line draw a line chart
129
+ lineplots lines draw a line chart with multiple series
130
+ scatter s draw a scatter plot
131
+ density d draw a density plot
132
+ boxplot box draw a horizontal boxplot
133
+ colors color show the list of available colors
134
+
135
+ count c draw a baplot based on the number of
136
+ occurrences (slow)
137
+
138
+ General options:
139
+ --help print command specific help menu
140
+ --version print the version of YouPlot
141
+ MSG
142
+
143
+ # Help for the main parser is simple.
144
+ # Simply show the banner above.
145
+ main_parser.on('--help', 'print sub-command help menu') do
146
+ puts main_parser.banner
147
+ puts
148
+ exit if YouPlot.run_as_executable?
149
+ end
150
+ end
151
+
152
+ def sub_parser_add_symbol
153
+ sub_parser.on_head('--symbol STR', String, 'character to be used to plot the bars') do |v|
154
+ params.symbol = v
155
+ end
156
+ end
157
+
158
+ def sub_parser_add_xscale
159
+ xscale_options = UnicodePlot::ValueTransformer::PREDEFINED_TRANSFORM_FUNCTIONS.keys.join(', ')
160
+ sub_parser.on_head('--xscale STR', String, "axis scaling (#{xscale_options})") do |v|
161
+ params.xscale = v.to_sym
162
+ end
163
+ end
164
+
165
+ def sub_parser_add_canvas
166
+ sub_parser.on_head('--canvas STR', String, 'type of canvas') do |v|
167
+ params.canvas = v.to_sym
168
+ end
169
+ end
170
+
171
+ def sub_parser_add_xlim
172
+ sub_parser.on_head('--xlim FLOAT,FLOAT', Array, 'plotting range for the x coordinate') do |v|
173
+ params.xlim = v
174
+ end
175
+ end
176
+
177
+ def sub_parser_add_ylim
178
+ sub_parser.on_head('--ylim FLOAT,FLOAT', Array, 'plotting range for the y coordinate') do |v|
179
+ params.ylim = v
180
+ end
181
+ end
182
+
183
+ def sub_parser_add_grid
184
+ sub_parser.on_head('--[no-]grid', TrueClass, 'draws grid-lines at the origin') do |v|
185
+ params.grid = v
186
+ end
187
+ end
188
+
189
+ def sub_parser_add_fmt_xyxy
190
+ sub_parser.on_head('--fmt STR', String,
191
+ 'xyxy : header is like x1, y1, x2, y2, x3, y3...',
192
+ 'xyy : header is like x, y1, y2, y2, y3...') do |v|
193
+ options[:fmt] = v
194
+ end
195
+ end
196
+
197
+ def sub_parser_add_fmt_yx
198
+ sub_parser.on_head('--fmt STR', String,
199
+ 'xy : header is like x, y...',
200
+ 'yx : header is like y, x...') do |v|
201
+ options[:fmt] = v
202
+ end
203
+ end
204
+
205
+ def create_sub_parser
206
+ @sub_parser = create_base_parser
207
+ sub_parser.banner = \
208
+ <<~MSG
209
+
210
+ Usage: YouPlot #{command} [options] <in.tsv>
211
+
212
+ Options for #{command}:
213
+ MSG
214
+
215
+ case command
216
+
217
+ # If you type only `uplot` in the terminal.
218
+ when nil
219
+ warn main_parser.banner
220
+ warn "\n"
221
+ exit 1 if YouPlot.run_as_executable?
222
+
223
+ when :barplot, :bar
224
+ sub_parser_add_symbol
225
+ sub_parser_add_fmt_yx
226
+ sub_parser_add_xscale
227
+
228
+ when :count, :c
229
+ sub_parser_add_symbol
230
+ sub_parser_add_xscale
231
+
232
+ when :histogram, :hist
233
+ sub_parser_add_symbol
234
+ sub_parser.on_head('--closed STR', String, 'side of the intervals to be closed [left]') do |v|
235
+ params.closed = v
236
+ end
237
+ sub_parser.on_head('-n', '--nbins INT', Numeric, 'approximate number of bins') do |v|
238
+ params.nbins = v
239
+ end
240
+
241
+ when :lineplot, :line
242
+ sub_parser_add_canvas
243
+ sub_parser_add_grid
244
+ sub_parser_add_fmt_yx
245
+ sub_parser_add_ylim
246
+ sub_parser_add_xlim
247
+
248
+ when :lineplots, :lines
249
+ sub_parser_add_canvas
250
+ sub_parser_add_grid
251
+ sub_parser_add_fmt_xyxy
252
+ sub_parser_add_ylim
253
+ sub_parser_add_xlim
254
+
255
+ when :scatter, :s
256
+ sub_parser_add_canvas
257
+ sub_parser_add_grid
258
+ sub_parser_add_fmt_xyxy
259
+ sub_parser_add_ylim
260
+ sub_parser_add_xlim
261
+
262
+ when :density, :d
263
+ sub_parser_add_canvas
264
+ sub_parser_add_grid
265
+ sub_parser_add_fmt_xyxy
266
+ sub_parser_add_ylim
267
+ sub_parser_add_xlim
268
+
269
+ when :boxplot, :box
270
+ sub_parser_add_xlim
271
+
272
+ when :colors, :color, :colours, :colour
273
+ sub_parser.on_head('-n', '--names', 'show color names only', TrueClass) do |v|
274
+ options[:color_names] = v
275
+ end
276
+
277
+ else
278
+ error_message = "uplot: unrecognized command '#{command}'"
279
+ if YouPlot.run_as_executable?
280
+ warn error_message
281
+ exit 1
282
+ else
283
+ raise Error, error_message
284
+ end
285
+ end
286
+ end
287
+
288
+ def parse_options(argv = ARGV)
289
+ begin
290
+ create_main_parser.order!(argv)
291
+ rescue OptionParser::ParseError => e
292
+ warn "uplot: #{e.message}"
293
+ exit 1 if YouPlot.run_as_executable?
294
+ end
295
+
296
+ @command = argv.shift&.to_sym
297
+
298
+ begin
299
+ create_sub_parser&.parse!(argv)
300
+ rescue OptionParser::ParseError => e
301
+ warn "uplot: #{e.message}"
302
+ exit 1 if YouPlot.run_as_executable?
303
+ end
304
+ end
305
+ end
306
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module YouPlot
4
- VERSION = '0.3.5'
4
+ VERSION = '0.4.0'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: youplot
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.5
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - kojix2
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-05-21 00:00:00.000000000 Z
11
+ date: 2021-05-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: unicode_plot
@@ -109,12 +109,12 @@ files:
109
109
  - exe/youplot
110
110
  - lib/youplot.rb
111
111
  - lib/youplot/backends/processing.rb
112
- - lib/youplot/backends/unicode_plot_backend.rb
112
+ - lib/youplot/backends/unicode_plot.rb
113
113
  - lib/youplot/command.rb
114
- - lib/youplot/command/options.rb
115
- - lib/youplot/command/parser.rb
116
- - lib/youplot/command/plot_params.rb
117
114
  - lib/youplot/dsv.rb
115
+ - lib/youplot/options.rb
116
+ - lib/youplot/parameters.rb
117
+ - lib/youplot/parser.rb
118
118
  - lib/youplot/version.rb
119
119
  homepage: https://github.com/kojix2/youplot
120
120
  licenses:
@@ -1,19 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module YouPlot
4
- class Command
5
- Options = Struct.new(
6
- :delimiter,
7
- :transpose,
8
- :headers,
9
- :pass,
10
- :output,
11
- :fmt,
12
- :progressive,
13
- :encoding,
14
- :color_names,
15
- :debug,
16
- keyword_init: true
17
- )
18
- end
19
- end
@@ -1,305 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'optparse'
4
- require_relative 'options'
5
- require_relative 'plot_params'
6
-
7
- module YouPlot
8
- class Command
9
- class Parser
10
- class Error < StandardError; end
11
-
12
- attr_reader :command, :options, :params,
13
- :main_parser, :sub_parser
14
-
15
- def initialize
16
- @command = nil
17
-
18
- @options = Options.new(
19
- delimiter: "\t",
20
- transpose: false,
21
- headers: nil,
22
- pass: false,
23
- output: $stderr,
24
- fmt: 'xyy',
25
- progressive: false,
26
- encoding: nil,
27
- color_names: false,
28
- debug: false
29
- )
30
-
31
- @params = PlotParams.new
32
- end
33
-
34
- def create_default_parser
35
- OptionParser.new do |parser|
36
- parser.program_name = 'YouPlot'
37
- parser.version = YouPlot::VERSION
38
- parser.summary_width = 24
39
- parser.on_tail('') # Add a blank line at the end
40
- parser.separator('')
41
- parser.on('Common options:')
42
- parser.on('-O', '--pass [FILE]', 'file to output input data to [stdout]',
43
- 'for inserting YouPlot in the middle of Unix pipes') do |v|
44
- options[:pass] = v || $stdout
45
- end
46
- parser.on('-o', '--output [FILE]', 'file to output plots to [stdout]',
47
- 'If no option is specified, plot will print to stderr') do |v|
48
- options[:output] = v || $stdout
49
- end
50
- parser.on('-d', '--delimiter DELIM', String, 'use DELIM instead of [TAB] for field delimiter') do |v|
51
- options[:delimiter] = v
52
- end
53
- parser.on('-H', '--headers', TrueClass, 'specify that the input has header row') do |v|
54
- options[:headers] = v
55
- end
56
- parser.on('-T', '--transpose', TrueClass, 'transpose the axes of the input data') do |v|
57
- options[:transpose] = v
58
- end
59
- parser.on('-t', '--title STR', String, 'print string on the top of plot') do |v|
60
- params.title = v
61
- end
62
- parser.on('-x', '--xlabel STR', String, 'print string on the bottom of the plot') do |v|
63
- params.xlabel = v
64
- end
65
- parser.on('-y', '--ylabel STR', String, 'print string on the far left of the plot') do |v|
66
- params.ylabel = v
67
- end
68
- parser.on('-w', '--width INT', Integer, 'number of characters per row') do |v|
69
- params.width = v
70
- end
71
- parser.on('-h', '--height INT', Numeric, 'number of rows') do |v|
72
- params.height = v
73
- end
74
- border_options = UnicodePlot::BORDER_MAP.keys.join(', ')
75
- parser.on('-b', '--border STR', String, 'specify the style of the bounding box', "(#{border_options})") do |v|
76
- params.border = v.to_sym
77
- end
78
- parser.on('-m', '--margin INT', Numeric, 'number of spaces to the left of the plot') do |v|
79
- params.margin = v
80
- end
81
- parser.on('--padding INT', Numeric, 'space of the left and right of the plot') do |v|
82
- params.padding = v
83
- end
84
- parser.on('-c', '--color VAL', String, 'color of the drawing') do |v|
85
- params.color = v =~ /\A[0-9]+\z/ ? v.to_i : v.to_sym
86
- end
87
- parser.on('--[no-]labels', TrueClass, 'hide the labels') do |v|
88
- params.labels = v
89
- end
90
- parser.on('-p', '--progress', TrueClass, 'progressive mode [experimental]') do |v|
91
- options[:progressive] = v
92
- end
93
- parser.on('-C', '--color-output', TrueClass, 'colorize even if writing to a pipe') do |v|
94
- UnicodePlot::StyledPrinter.define_method(:color?){ |o| true }
95
- end
96
- parser.on('-M', '--monochrome', TrueClass, 'no colouring even if writing to a tty') do |v|
97
- UnicodePlot::StyledPrinter.define_method(:color?){ |o| false }
98
- end
99
- parser.on('--encoding STR', String, 'Specify the input encoding') do |v|
100
- options[:encoding] = v
101
- end
102
- # Optparse adds the help option, but it doesn't show up in usage.
103
- # This is why you need the code below.
104
- parser.on('--help', 'print sub-command help menu') do
105
- puts parser.help
106
- exit if YouPlot.run_as_executable?
107
- end
108
- parser.on('--debug', TrueClass, 'print preprocessed data') do |v|
109
- options[:debug] = v
110
- end
111
- # yield opt if block_given?
112
- end
113
- end
114
-
115
- def create_main_parser
116
- @main_parser = create_default_parser
117
- main_parser.banner = \
118
- <<~MSG
119
-
120
- Program: YouPlot (Tools for plotting on the terminal)
121
- Version: #{YouPlot::VERSION} (using UnicodePlot #{UnicodePlot::VERSION})
122
- Source: https://github.com/kojix2/youplot
123
-
124
- Usage: uplot <command> [options] <in.tsv>
125
-
126
- Commands:
127
- barplot bar draw a horizontal barplot
128
- histogram hist draw a horizontal histogram
129
- lineplot line draw a line chart
130
- lineplots lines draw a line chart with multiple series
131
- scatter s draw a scatter plot
132
- density d draw a density plot
133
- boxplot box draw a horizontal boxplot
134
- colors color show the list of available colors
135
-
136
- count c draw a baplot based on the number of
137
- occurrences (slow)
138
-
139
- General options:
140
- --help print command specific help menu
141
- --version print the version of YouPlot
142
- MSG
143
-
144
- # Help for the main parser is simple.
145
- # Simply show the banner above.
146
- main_parser.on('--help', 'print sub-command help menu') do
147
- puts main_parser.banner
148
- puts
149
- exit if YouPlot.run_as_executable?
150
- end
151
- end
152
-
153
- def sub_parser_add_symbol
154
- sub_parser.on_head('--symbol STR', String, 'character to be used to plot the bars') do |v|
155
- params.symbol = v
156
- end
157
- end
158
-
159
- def sub_parser_add_xscale
160
- xscale_options = UnicodePlot::ValueTransformer::PREDEFINED_TRANSFORM_FUNCTIONS.keys.join(', ')
161
- sub_parser.on_head('--xscale STR', String, "axis scaling (#{xscale_options})") do |v|
162
- params.xscale = v.to_sym
163
- end
164
- end
165
-
166
- def sub_parser_add_canvas
167
- sub_parser.on_head('--canvas STR', String, 'type of canvas') do |v|
168
- params.canvas = v.to_sym
169
- end
170
- end
171
-
172
- def sub_parser_add_xlim
173
- sub_parser.on_head('--xlim FLOAT,FLOAT', Array, 'plotting range for the x coordinate') do |v|
174
- params.xlim = v
175
- end
176
- end
177
-
178
- def sub_parser_add_ylim
179
- sub_parser.on_head('--ylim FLOAT,FLOAT', Array, 'plotting range for the y coordinate') do |v|
180
- params.ylim = v
181
- end
182
- end
183
-
184
- def sub_parser_add_grid
185
- sub_parser.on_head('--[no-]grid', TrueClass, 'draws grid-lines at the origin') do |v|
186
- params.grid = v
187
- end
188
- end
189
-
190
- def create_sub_parser
191
- @sub_parser = create_default_parser
192
- sub_parser.banner = \
193
- <<~MSG
194
-
195
- Usage: YouPlot #{command} [options] <in.tsv>
196
-
197
- Options for #{command}:
198
- MSG
199
-
200
- case command
201
-
202
- # If you type only `uplot` in the terminal.
203
- when nil
204
- warn main_parser.banner
205
- warn "\n"
206
- exit 1 if YouPlot.run_as_executable?
207
-
208
- when :barplot, :bar
209
- sub_parser_add_symbol
210
- sub_parser.on_head('--fmt STR', String, 'xy : header is like x, y...', 'yx : header is like y, x...') do |v|
211
- options[:fmt] = v
212
- end
213
- sub_parser_add_xscale
214
-
215
- when :count, :c
216
- sub_parser_add_symbol
217
- sub_parser_add_xscale
218
-
219
- when :histogram, :hist
220
- sub_parser_add_symbol
221
- sub_parser.on_head('--closed STR', String, 'side of the intervals to be closed [left]') do |v|
222
- params.closed = v
223
- end
224
- sub_parser.on_head('-n', '--nbins INT', Numeric, 'approximate number of bins') do |v|
225
- params.nbins = v
226
- end
227
-
228
- when :lineplot, :line
229
- sub_parser_add_canvas
230
- sub_parser_add_grid
231
- sub_parser.on_head('--fmt STR', String, 'xy : header is like x, y...', 'yx : header is like y, x...') do |v|
232
- options[:fmt] = v
233
- end
234
- sub_parser_add_ylim
235
- sub_parser_add_xlim
236
-
237
- when :lineplots, :lines
238
- sub_parser_add_canvas
239
- sub_parser_add_grid
240
- sub_parser.on_head('--fmt STR', String, 'xyxy : header is like x1, y1, x2, y2, x3, y3...',
241
- 'xyy : header is like x, y1, y2, y2, y3...') do |v|
242
- options[:fmt] = v
243
- end
244
- sub_parser_add_ylim
245
- sub_parser_add_xlim
246
-
247
- when :scatter, :s
248
- sub_parser_add_canvas
249
- sub_parser_add_grid
250
- sub_parser.on_head('--fmt STR', String, 'xyxy : header is like x1, y1, x2, y2, x3, y3...',
251
- 'xyy : header is like x, y1, y2, y2, y3...') do |v|
252
- options[:fmt] = v
253
- end
254
- sub_parser_add_ylim
255
- sub_parser_add_xlim
256
-
257
- when :density, :d
258
- sub_parser_add_canvas
259
- sub_parser_add_grid
260
- sub_parser.on('--fmt STR', String, 'xyxy : header is like x1, y1, x2, y2, x3, y3...',
261
- 'xyy : header is like x, y1, y2, y2, y3...') do |v|
262
- options[:fmt] = v
263
- end
264
- sub_parser_add_ylim
265
- sub_parser_add_xlim
266
-
267
- when :boxplot, :box
268
- sub_parser_add_xlim
269
-
270
- when :colors, :color, :colours, :colour
271
- sub_parser.on_head('-n', '--names', 'show color names only', TrueClass) do |v|
272
- options[:color_names] = v
273
- end
274
-
275
- else
276
- error_message = "uplot: unrecognized command '#{command}'"
277
- if YouPlot.run_as_executable?
278
- warn error_message
279
- exit 1
280
- else
281
- raise Error, error_message
282
- end
283
- end
284
- end
285
-
286
- def parse_options(argv = ARGV)
287
- begin
288
- create_main_parser.order!(argv)
289
- rescue OptionParser::ParseError => e
290
- warn "uplot: #{e.message}"
291
- exit 1 if YouPlot.run_as_executable?
292
- end
293
-
294
- @command = argv.shift&.to_sym
295
-
296
- begin
297
- create_sub_parser&.parse!(argv)
298
- rescue OptionParser::ParseError => e
299
- warn "uplot: #{e.message}"
300
- exit 1 if YouPlot.run_as_executable?
301
- end
302
- end
303
- end
304
- end
305
- end
@@ -1,37 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module YouPlot
4
- class Command
5
- # UnicodePlot parameters.
6
- # * Normally in a Ruby program, you might use hash for the parameter object.
7
- # * Here, I use Struct for 2 safety reason.
8
- # * The keys are static in Struct.
9
- # * Struct does not conflict with keyword arguments. Hash dose.
10
- PlotParams = Struct.new(
11
- # Sort me!
12
- :title,
13
- :width,
14
- :height,
15
- :border,
16
- :margin,
17
- :padding,
18
- :color,
19
- :xlabel,
20
- :ylabel,
21
- :labels,
22
- :symbol,
23
- :xscale,
24
- :nbins,
25
- :closed,
26
- :canvas,
27
- :xlim,
28
- :ylim,
29
- :grid,
30
- :name
31
- ) do
32
- def to_hc
33
- to_h.compact
34
- end
35
- end
36
- end
37
- end