tchart 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. checksums.yaml +7 -0
  2. data/bin/tchart +5 -0
  3. data/lib/tchart.rb +27 -0
  4. data/lib/tchart/lang/tchart_error.rb +9 -0
  5. data/lib/tchart/model/bar.rb +24 -0
  6. data/lib/tchart/model/chart.rb +25 -0
  7. data/lib/tchart/model/command_line_args.rb +18 -0
  8. data/lib/tchart/model/coordinate.rb +29 -0
  9. data/lib/tchart/model/grid_line.rb +25 -0
  10. data/lib/tchart/model/label.rb +43 -0
  11. data/lib/tchart/model/layout.rb +85 -0
  12. data/lib/tchart/model/separator.rb +28 -0
  13. data/lib/tchart/model/settings.rb +68 -0
  14. data/lib/tchart/model/y_item.rb +44 -0
  15. data/lib/tchart/process/chart_builder.rb +63 -0
  16. data/lib/tchart/process/command_line_parser.rb +73 -0
  17. data/lib/tchart/process/data_parser.rb +76 -0
  18. data/lib/tchart/process/data_reader.rb +14 -0
  19. data/lib/tchart/process/items_parser.rb +156 -0
  20. data/lib/tchart/process/layout_builder.rb +106 -0
  21. data/lib/tchart/process/settings_parser.rb +63 -0
  22. data/lib/tchart/process/tex_builder.rb +85 -0
  23. data/lib/tchart/process/tex_writer.rb +13 -0
  24. data/lib/tchart/version.rb +3 -0
  25. data/test/integration_test.rb +82 -0
  26. data/test/tchart/model/bar_test.rb +17 -0
  27. data/test/tchart/model/coordinate_test.rb +10 -0
  28. data/test/tchart/model/grid_line_test.rb +17 -0
  29. data/test/tchart/model/label_test.rb +31 -0
  30. data/test/tchart/model/layout_test.rb +17 -0
  31. data/test/tchart/model/separator_test.rb +19 -0
  32. data/test/tchart/model/settings_test.rb +48 -0
  33. data/test/tchart/model/y_item_test.rb +24 -0
  34. data/test/tchart/process/chart_builder_test.rb +33 -0
  35. data/test/tchart/process/command_line_parser_test.rb +89 -0
  36. data/test/tchart/process/data_parser_test.rb +60 -0
  37. data/test/tchart/process/data_reader_test.rb +23 -0
  38. data/test/tchart/process/items_parser_test.rb +154 -0
  39. data/test/tchart/process/layout_builder_test.rb +189 -0
  40. data/test/tchart/process/settings_parser_test.rb +75 -0
  41. data/test/tchart/process/tex_builder_test.rb +120 -0
  42. data/test/tchart/process/tex_writer_test.rb +38 -0
  43. data/test/tchart_test.rb +47 -0
  44. metadata +154 -0
@@ -0,0 +1,63 @@
1
+ module TChart
2
+
3
+ #
4
+ # Responsible for parsing a line of source data that contains a
5
+ # setting. Also responsible for accumulating parsed settings.
6
+ #
7
+ class SettingsParser
8
+
9
+ #
10
+ # The accumulated settings. All settings start with their default
11
+ # values and get updated as setting lines are parsed. If a setting
12
+ # is specified more than once in the source data, the last value
13
+ # found will be the one used.
14
+ #
15
+ attr_reader :settings
16
+
17
+ def initialize
18
+ @settings = Settings.new
19
+ end
20
+
21
+ #
22
+ # Return true if the passed line is a recognizable settings line
23
+ # (which may nonetheless have errors, such as unknown setting, etc.),
24
+ # false otherwise.
25
+ #
26
+ def parse?(line)
27
+ return false if (match = /^([^=]+)=(.+)$/.match(line)).nil?
28
+ name, value_as_string = match[1].strip, match[2].strip
29
+ raise_unknown_setting(name) unless known_setting?(name)
30
+ raise_not_a_recognizable_value(value_as_string) unless recognizable_value?(value_as_string)
31
+ save_setting(name, value_as_string)
32
+ true
33
+ end
34
+
35
+ private
36
+
37
+ def known_setting?(name)
38
+ @settings.has_setting?(name)
39
+ end
40
+
41
+ #
42
+ # "chart_width = 42" => true
43
+ # "gobbledygook = junk" => true
44
+ # "chart_width 42" => false
45
+ #
46
+ def recognizable_value?(value_as_string)
47
+ value_as_string =~ /^(\+|-)?\d+(\.\d*)?$/
48
+ end
49
+
50
+ def save_setting(name, value_as_string)
51
+ @settings.send("#{name}=", value_as_string.to_f)
52
+ end
53
+
54
+ def raise_unknown_setting(name)
55
+ raise TChartError, "unknown setting \"#{name}\"; expecting one of: #{@settings.setting_names.join(', ')}"
56
+ end
57
+
58
+ def raise_not_a_recognizable_value(value_as_string)
59
+ raise TChartError, "\"#{value_as_string}\" is not a recognizable setting value; expecting e.g. 123 or 123.45"
60
+ end
61
+
62
+ end
63
+ end
@@ -0,0 +1,85 @@
1
+ require 'stringio'
2
+
3
+ module TChart
4
+
5
+ #
6
+ # Responsible for generating TikZ/TeX code for the various chart elements,
7
+ # including labels, bars, (grid)lines, etc. Also responsible for escaping
8
+ # TeX special characters, such as {, }, \, etc., and for accumulating the
9
+ # generated code.
10
+ #
11
+ class TeXBuilder
12
+
13
+ def initialize
14
+ @output = StringIO.new
15
+ end
16
+
17
+ def begin_chart
18
+ @output << "\\tikzpicture\n"
19
+ end
20
+
21
+ def end_chart
22
+ @output << "\\endtikzpicture\n"
23
+ end
24
+
25
+ def comment(text)
26
+ @output << "% #{escape_tex_special_chars text.to_s}\n"
27
+ end
28
+
29
+ def gridline(from, to, style)
30
+ @output << "\\draw [#{style}] (#{f from.x}mm, #{f from.y}mm) -- (#{f to.x}mm, #{f to.y}mm);\n"
31
+ end
32
+
33
+ def label(coord, width, style, text)
34
+ @output << "\\node [#{style}, text width = #{f width}mm] at (#{f coord.x}mm, #{f coord.y}mm) {#{escape_tex_special_chars text.to_s}};\n"
35
+ end
36
+
37
+ def bar(from, to, style)
38
+ x_mid, width = to_tikz_coords(from.x, to.x)
39
+ @output << "\\node [#{style}] at (#{f x_mid}mm, #{f from.y}mm) [minimum width = #{f width}mm] {};\n"
40
+ end
41
+
42
+ def to_s # => String
43
+ @output.string
44
+ end
45
+
46
+ private
47
+
48
+ #
49
+ # f(1.2345) => "1.23"
50
+ #
51
+ def f(number)
52
+ '%.02f' % number
53
+ end
54
+
55
+ #
56
+ # escape_tex_special_chars('# $ % & _ { } \ ~ ^ |') => '\# \$ \% \& \_ \{ \} \textbackslash{} \~{} \^{} $\vert$'
57
+ #
58
+ def escape_tex_special_chars(text)
59
+ text.gsub(/([#$%&_{}~^\\|])/) do |match|
60
+ case match
61
+ when '#', '$', '%', '&', '_', '{', '}'
62
+ "\\#{match}"
63
+ when '\\'
64
+ '$\\backslash$'
65
+ when '~'
66
+ '\\~{}'
67
+ when '^'
68
+ '\\^{}'
69
+ when '|'
70
+ '$\\vert$'
71
+ end
72
+ end
73
+ end
74
+
75
+ #
76
+ # to_tikx_coords(x_from, x_to) => [ x_mid, width ]
77
+ #
78
+ def to_tikz_coords(x_from, x_to)
79
+ width = x_to - x_from
80
+ x_mid = x_from + (width / 2.0)
81
+ [x_mid, width]
82
+ end
83
+
84
+ end
85
+ end
@@ -0,0 +1,13 @@
1
+ module TChart
2
+
3
+ #
4
+ # Responsible for writing TeX code to a file.
5
+ #
6
+ module TeXWriter
7
+
8
+ def self.write(tex_filename, tex_content)
9
+ File.open(tex_filename, 'w') {|f| f.puts(tex_content)}
10
+ end
11
+
12
+ end
13
+ end
@@ -0,0 +1,3 @@
1
+ module TChart
2
+ Version = "1.0.2"
3
+ end
@@ -0,0 +1,82 @@
1
+ require_relative 'test_helper'
2
+
3
+ module TChart
4
+ describe "integration test" do
5
+
6
+ before do
7
+ delete_test_files(data_filename, tex_filename)
8
+ end
9
+
10
+ after do
11
+ delete_test_files(data_filename, tex_filename)
12
+ end
13
+
14
+ it do
15
+ create_file(data_filename, data)
16
+ TChart.run( [ data_filename, tex_filename ] )
17
+ contents_of(tex_filename).must_equal expected_tex
18
+ end
19
+
20
+ def data_filename
21
+ "integration_test.txt"
22
+ end
23
+
24
+ def tex_filename
25
+ "integration_test.tex"
26
+ end
27
+
28
+ def data
29
+ <<-'EOS'.gsub(/^\s+/, '')
30
+ chart_width = 100
31
+ line_height = 5
32
+ x_axis_label_width = 15
33
+ x_axis_label_y_coordinate = -5
34
+ y_axis_label_width = 30
35
+
36
+ Objective-C | lang | 2001.5-2002.6
37
+ -----------------------------------
38
+ OS X | os | 2001.3-2002.7
39
+ EOS
40
+ end
41
+
42
+ def expected_tex
43
+ <<-'EOS'.gsub(/^\s+/, '')
44
+ \tikzpicture
45
+ \draw [gridline] (0.00mm, 20.00mm) -- (55.00mm, 20.00mm);
46
+ \draw [gridline] (0.00mm, 0.00mm) -- (55.00mm, 0.00mm);
47
+
48
+ \node [xlabel, text width = 15.00mm] at (0.00mm, -5.00mm) {2001};
49
+ \draw [gridline] (0.00mm, 0.00mm) -- (0.00mm, 20.00mm);
50
+
51
+ \node [xlabel, text width = 15.00mm] at (27.50mm, -5.00mm) {2002};
52
+ \draw [gridline] (27.50mm, 0.00mm) -- (27.50mm, 20.00mm);
53
+
54
+ \node [xlabel, text width = 15.00mm] at (55.00mm, -5.00mm) {2003};
55
+ \draw [gridline] (55.00mm, 0.00mm) -- (55.00mm, 20.00mm);
56
+
57
+ \node [ylabel, text width = 30.00mm] at (-22.50mm, 15.00mm) {Objective-C};
58
+
59
+ \node [lang] at (25.09mm, 15.00mm) [minimum width = 32.10mm] {};
60
+ \draw [gridline] (0.00mm, 10.00mm) -- (55.00mm, 10.00mm);
61
+
62
+ \node [ylabel, text width = 30.00mm] at (-22.50mm, 5.00mm) {OS X};
63
+
64
+ \node [os] at (23.96mm, 5.00mm) [minimum width = 39.03mm] {};
65
+ \endtikzpicture
66
+ EOS
67
+ end
68
+
69
+ def create_file(filename, contents)
70
+ File.open(filename, "w") { |f| f.puts(contents) }
71
+ end
72
+
73
+ def contents_of(filename)
74
+ File.open(filename, "r") { |f| f.read }
75
+ end
76
+
77
+ def delete_test_files(*filenames)
78
+ filenames.each { |fn| File.delete(fn) if File.exists?(fn) }
79
+ end
80
+
81
+ end
82
+ end
@@ -0,0 +1,17 @@
1
+ require_relative '../../test_helper'
2
+
3
+ module TChart
4
+ describe Bar, "render" do
5
+
6
+ before do
7
+ @tex = TeXBuilder.new
8
+ @bar = Bar.new(xy(0,30), xy(50, 30), "bar_style")
9
+ end
10
+
11
+ it "generates TeX code to render itself" do
12
+ @tex.expects(:bar).once
13
+ @bar.render(@tex)
14
+ end
15
+
16
+ end
17
+ end
@@ -0,0 +1,10 @@
1
+ require_relative '../../test_helper'
2
+
3
+ describe "xy" do
4
+ it "returns a newly allocated coordinate with the passed x,y coordinates" do
5
+ coordinate = xy(4, 2)
6
+ coordinate.must_be_kind_of TChart::Coordinate
7
+ coordinate.x.must_equal 4
8
+ coordinate.y.must_equal 2
9
+ end
10
+ end
@@ -0,0 +1,17 @@
1
+ require_relative '../../test_helper'
2
+
3
+ module TChart
4
+ describe GridLine, "render" do
5
+
6
+ before do
7
+ @tex = TeXBuilder.new
8
+ @gridline = GridLine.new(xy(0,0), xy(10,0))
9
+ end
10
+
11
+ it "generates TeX code to render the grid line" do
12
+ @tex.expects(:gridline).once
13
+ @gridline.render(@tex)
14
+ end
15
+
16
+ end
17
+ end
@@ -0,0 +1,31 @@
1
+ require_relative '../../test_helper'
2
+
3
+ module TChart
4
+ describe Label, "build_xlabel" do
5
+ it "returns a label with style 'xlabel'" do
6
+ Label.build_xlabel(xy(4,2), 42, "text").style.must_equal "xlabel"
7
+ end
8
+ end
9
+
10
+
11
+ describe Label, "build_ylabel" do
12
+ it "returns a label with style 'ylabel'" do
13
+ Label.build_ylabel(xy(4,2), 42, "text").style.must_equal "ylabel"
14
+ end
15
+ end
16
+
17
+
18
+ describe Label, "render" do
19
+
20
+ before do
21
+ @tex = TeXBuilder.new
22
+ @label = Label.build_ylabel(xy(-10,20), xy(30,20), "description")
23
+ end
24
+
25
+ it "generates TikZ code to render and item" do
26
+ @tex.expects(:label)
27
+ @label.render(@tex)
28
+ end
29
+
30
+ end
31
+ end
@@ -0,0 +1,17 @@
1
+ require_relative '../../test_helper'
2
+
3
+ module TChart
4
+ describe Layout, "date_range_to_x_coordinates" do
5
+
6
+ before do
7
+ @layout = Layout.new
8
+ @layout.x_axis_tick_dates = [Date.new(2001,1,1), Date.new(2002,1,1)]
9
+ @layout.x_axis_length = 100
10
+ end
11
+
12
+ it "converts a date range to its equivalent x_coordinate range on the chart" do
13
+ @layout.date_range_to_x_coordinates(Date.new(2001,1,1)..Date.new(2001,12,31)).must_equal [0.0, 100.0]
14
+ end
15
+
16
+ end
17
+ end
@@ -0,0 +1,19 @@
1
+ require_relative '../../test_helper'
2
+
3
+ module TChart
4
+ describe Separator, "build" do
5
+
6
+ before do
7
+ @layout = stub( x_axis_length: 100 )
8
+ @separator = Separator.new
9
+ @y = 10
10
+ end
11
+
12
+ it "returns an array containing a horizontal gridline" do
13
+ elements = @separator.build(@layout, @y)
14
+ elements.length.must_equal 1
15
+ elements[0].must_equal GridLine.new(xy(0,10), xy(100,10))
16
+ end
17
+
18
+ end
19
+ end
@@ -0,0 +1,48 @@
1
+ require_relative '../../test_helper'
2
+
3
+ module TChart
4
+ describe Settings, "initialize" do
5
+
6
+ before do
7
+ @settings = Settings.new
8
+ end
9
+
10
+ it "sets its settings to default values" do
11
+ @settings.setting_names.each do |setting_name|
12
+ @settings.send(setting_name).wont_be_nil "\"#{setting_name}\" is not being set to a default value in Settings#initialize"
13
+ end
14
+ end
15
+
16
+ end
17
+
18
+
19
+ describe Settings, "has_setting?" do
20
+
21
+ before do
22
+ @settings = Settings.new
23
+ end
24
+
25
+ it "returns true if the setting exists" do
26
+ @settings.has_setting?('chart_width').must_equal true
27
+ end
28
+ it "returns false if the setting does not exist" do
29
+ @settings.has_setting?('gobbledygook').must_equal false
30
+ end
31
+ it "returns false when asked about itself" do
32
+ @settings.has_setting?('has_setting?').must_equal false
33
+ end
34
+ end
35
+
36
+
37
+ describe Settings, "setting_names" do
38
+
39
+ before do
40
+ @settings = Settings.new
41
+ end
42
+
43
+ it "returns the list of setting names" do
44
+ @settings.setting_names.must_equal ['chart_width', 'line_height', 'x_axis_label_width',
45
+ 'x_axis_label_y_coordinate', 'y_axis_label_width']
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,24 @@
1
+ require_relative '../../test_helper'
2
+
3
+ module TChart
4
+ describe YItem, "build" do
5
+
6
+ before do
7
+ @layout = stub
8
+ @layout.stubs(:x_axis_date_range).returns( Date.new(2001,1,1)..Date.new(2003,1,1) )
9
+ @layout.stubs(:y_axis_label_x_coordinate).returns(-10)
10
+ @layout.stubs(:y_axis_label_width).returns(20)
11
+ @layout.stubs(:date_range_to_x_coordinates).returns [0, 50]
12
+ @item = YItem.new("description", "bar_style", [ Date.new(2001,1,1)..Date.new(2001,12,31) ])
13
+ @y = 10
14
+ end
15
+
16
+ it "returns an array containing a label and bars" do
17
+ elements = @item.build(@layout, @y)
18
+ elements.length.must_equal 2
19
+ elements[0].must_be_kind_of Label
20
+ elements[1].must_be_kind_of Bar
21
+ end
22
+
23
+ end
24
+ end