how_is 8.0.0 → 9.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/how_is/chart.rb CHANGED
@@ -1,83 +1,83 @@
1
- class HowIs::Chart
2
- # Generates the gnuplot script in data/issues.plg.
3
- #
4
- # Some configuration is available. Font locations are path to a TTF or other
5
- # Gnuplot-readable font name.
6
- #
7
- # For example that could be '/Users/anne/Library/Fonts/InputMono-Medium.ttf'
8
- # or just 'Helvetica'.
9
- #
10
- # @param font_location [String] Font for the chart
11
- # @param font_size [Integer] Size of the chart text
12
- # @param label_font_location [String] Font for labels
13
- # @param label_font_size [Integer] Size of the label text
14
- #
15
- # @return void
16
- def self.gnuplot(font_location: nil,
17
- font_size: 16,
18
- label_font_location: nil,
19
- label_font_size: 10,
20
- chartsize: '500,500',
21
- data_file:,
22
- png_file:)
23
- default_font_location =
24
- if Gem.win_platform?
25
- 'Arial'
26
- else
27
- 'Helvetica'
28
- end
29
-
30
- font_location ||= default_font_location
31
- label_font_location ||= font_location
32
-
33
- cmd = %Q{
34
- gnuplot -e "labelfont='#{label_font_location},#{label_font_size}'" \
35
- -e "chartfont='#{font_location},#{font_size}'" \
36
- -e "chartsize='#{chartsize}'" \
37
- -e "data='#{data_file}'" \
38
- -e "pngfile='#{png_file}'" \
39
- -c data/issues.plg
40
- }
41
- puts cmd
42
- IO.popen(cmd, 'w')
43
- end
44
-
45
- def self.rotate(offset, filename)
46
- if Gem.win_platform?
47
- rotate_with_dotnet(filename, offset)
48
- else
49
- rotate_with_minimagick(filename, offset)
50
- end
51
- end
52
-
53
- def self.rotate_with_dotnet(filename, offset)
54
- ps_rotate_flip = {
55
- 90 => 'Rotate90FlipNone',
56
- 180 => 'Rotate180FlipNone',
57
- 270 => 'Rotate270FlipNone',
58
- -90 => 'Rotate270FlipNone'
59
- }[offset]
60
-
61
- command = %Q{
62
- $path = "#{filename}"
63
-
64
- [Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms");
65
- $i = new-object System.Drawing.Bitmap $path
66
-
67
- $i.RotateFlip("#{ps_rotate_flip}")
68
-
69
- $i.Save($path,"png")
70
-
71
- exit
72
- }
73
-
74
- IO.popen(["powershell", "-Command", command], 'w') { |io| }
75
- end
76
-
77
- def self.rotate_with_minimagick(filename, offset)
78
- require 'mini_magick'
79
- image = MiniMagick::Image.new(filename) { |b| b.rotate offset.to_s }
80
- image.format 'png'
81
- image.write filename
82
- end
83
- end
1
+ class HowIs::Chart
2
+ # Generates the gnuplot script in data/issues.plg.
3
+ #
4
+ # Some configuration is available. Font locations are path to a TTF or other
5
+ # Gnuplot-readable font name.
6
+ #
7
+ # For example that could be '/Users/anne/Library/Fonts/InputMono-Medium.ttf'
8
+ # or just 'Helvetica'.
9
+ #
10
+ # @param font_location [String] Font for the chart
11
+ # @param font_size [Integer] Size of the chart text
12
+ # @param label_font_location [String] Font for labels
13
+ # @param label_font_size [Integer] Size of the label text
14
+ #
15
+ # @return void
16
+ def self.gnuplot(font_location: nil,
17
+ font_size: 16,
18
+ label_font_location: nil,
19
+ label_font_size: 10,
20
+ chartsize: '500,500',
21
+ data_file:,
22
+ png_file:)
23
+ default_font_location =
24
+ if Gem.win_platform?
25
+ 'Arial'
26
+ else
27
+ 'Helvetica'
28
+ end
29
+
30
+ font_location ||= default_font_location
31
+ label_font_location ||= font_location
32
+
33
+ cmd = %Q{
34
+ gnuplot -e "labelfont='#{label_font_location},#{label_font_size}'" \
35
+ -e "chartfont='#{font_location},#{font_size}'" \
36
+ -e "chartsize='#{chartsize}'" \
37
+ -e "data='#{data_file}'" \
38
+ -e "pngfile='#{png_file}'" \
39
+ -c data/issues.plg
40
+ }
41
+ puts cmd
42
+ IO.popen(cmd, 'w')
43
+ end
44
+
45
+ def self.rotate(offset, filename)
46
+ if Gem.win_platform?
47
+ rotate_with_dotnet(filename, offset)
48
+ else
49
+ rotate_with_minimagick(filename, offset)
50
+ end
51
+ end
52
+
53
+ def self.rotate_with_dotnet(filename, offset)
54
+ ps_rotate_flip = {
55
+ 90 => 'Rotate90FlipNone',
56
+ 180 => 'Rotate180FlipNone',
57
+ 270 => 'Rotate270FlipNone',
58
+ -90 => 'Rotate270FlipNone'
59
+ }[offset]
60
+
61
+ command = %Q{
62
+ $path = "#{filename}"
63
+
64
+ [Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms");
65
+ $i = new-object System.Drawing.Bitmap $path
66
+
67
+ $i.RotateFlip("#{ps_rotate_flip}")
68
+
69
+ $i.Save($path,"png")
70
+
71
+ exit
72
+ }
73
+
74
+ IO.popen(["powershell", "-Command", command], 'w') { |io| }
75
+ end
76
+
77
+ def self.rotate_with_minimagick(filename, offset)
78
+ require 'mini_magick'
79
+ image = MiniMagick::Image.new(filename) { |b| b.rotate offset.to_s }
80
+ image.format 'png'
81
+ image.write filename
82
+ end
83
+ end
data/lib/how_is/cli.rb CHANGED
@@ -1,92 +1,90 @@
1
- require 'how_is'
2
- require 'yaml'
3
- require 'contracts'
4
- require 'stringio'
5
-
6
- C = Contracts
7
-
8
- class HowIs::CLI
9
- include Contracts::Core
10
-
11
- DEFAULT_CONFIG_FILE = 'how_is.yml'
12
-
13
- # Generates YAML frontmatter, as is used in Jekyll and other blog engines.
14
- #
15
- # E.g.,
16
- # generate_frontmatter({'foo' => "bar %{baz}"}, {'baz' => "asdf"})
17
- # => "---\nfoo: bar asdf\n"
18
- Contract C::HashOf[C::Or[String, Symbol] => String],
19
- C::HashOf[C::Or[String, Symbol] => C::Any] => String
20
- def generate_frontmatter(frontmatter, report_data)
21
- frontmatter = convert_keys(frontmatter, :to_s)
22
- report_data = convert_keys(report_data, :to_sym)
23
-
24
- frontmatter = frontmatter.map { |k, v|
25
- v = v % report_data
26
-
27
- [k, v]
28
- }.to_h
29
-
30
- YAML.dump(frontmatter)
31
- end
32
-
33
- def from_config_file(config_file = nil, **kwargs)
34
- config_file ||= DEFAULT_CONFIG_FILE
35
-
36
- from_config(YAML.load_file(config_file), **kwargs)
37
- end
38
-
39
- def from_config(config,
40
- github: nil,
41
- report_class: nil)
42
- report_class ||= HowIs::Report
43
-
44
- date = Date.strptime(Time.now.to_i.to_s, '%s')
45
- date_string = date.strftime('%Y-%m-%d')
46
- friendly_date = date.strftime('%B %d, %y')
47
-
48
- analysis = HowIs.generate_analysis(repository: config['repository'], github: github)
49
-
50
- report_data = {
51
- repository: config['repository'],
52
- date: date,
53
- friendly_date: friendly_date,
54
- }
55
-
56
- config['reports'].map do |format, report_config|
57
- filename = report_config['filename'] % report_data
58
- file = File.join(report_config['directory'], filename)
59
-
60
- report = report_class.export(analysis, format)
61
-
62
- result = build_report(report_config['frontmatter'], report_data, report)
63
-
64
- File.open(file, 'w') do |f|
65
- f.puts result
66
- end
67
-
68
- result
69
- end
70
- end
71
-
72
- def build_report(frontmatter, report_data, report)
73
- str = StringIO.new
74
-
75
- if frontmatter
76
- str.puts generate_frontmatter(frontmatter, report_data)
77
- str.puts "---"
78
- str.puts
79
- end
80
-
81
- str.puts report
82
-
83
- str.string
84
- end
85
-
86
- private
87
- # convert_keys({'foo' => 'bar'}, :to_sym)
88
- # => {:foo => 'bar'}
89
- def convert_keys(data, method_name)
90
- data.map {|k, v| [k.send(method_name), v]}.to_h
91
- end
92
- end
1
+ require 'how_is'
2
+ require 'yaml'
3
+ require 'contracts'
4
+ require 'stringio'
5
+
6
+ class HowIs::CLI
7
+ include Contracts::Core
8
+
9
+ DEFAULT_CONFIG_FILE = 'how_is.yml'
10
+
11
+ # Generates YAML frontmatter, as is used in Jekyll and other blog engines.
12
+ #
13
+ # E.g.,
14
+ # generate_frontmatter({'foo' => "bar %{baz}"}, {'baz' => "asdf"})
15
+ # => "---\nfoo: bar asdf\n"
16
+ Contract C::HashOf[C::Or[String, Symbol] => String],
17
+ C::HashOf[C::Or[String, Symbol] => C::Any] => String
18
+ def generate_frontmatter(frontmatter, report_data)
19
+ frontmatter = convert_keys(frontmatter, :to_s)
20
+ report_data = convert_keys(report_data, :to_sym)
21
+
22
+ frontmatter = frontmatter.map { |k, v|
23
+ v = v % report_data
24
+
25
+ [k, v]
26
+ }.to_h
27
+
28
+ YAML.dump(frontmatter)
29
+ end
30
+
31
+ def from_config_file(config_file = nil, **kwargs)
32
+ config_file ||= DEFAULT_CONFIG_FILE
33
+
34
+ from_config(YAML.load_file(config_file), **kwargs)
35
+ end
36
+
37
+ def from_config(config,
38
+ github: nil,
39
+ report_class: nil)
40
+ report_class ||= HowIs::Report
41
+
42
+ date = Date.strptime(Time.now.to_i.to_s, '%s')
43
+ date_string = date.strftime('%Y-%m-%d')
44
+ friendly_date = date.strftime('%B %d, %y')
45
+
46
+ analysis = HowIs.generate_analysis(repository: config['repository'], github: github)
47
+
48
+ report_data = {
49
+ repository: config['repository'],
50
+ date: date,
51
+ friendly_date: friendly_date,
52
+ }
53
+
54
+ config['reports'].map do |format, report_config|
55
+ filename = report_config['filename'] % report_data
56
+ file = File.join(report_config['directory'], filename)
57
+
58
+ report = report_class.export(analysis, format)
59
+
60
+ result = build_report(report_config['frontmatter'], report_data, report)
61
+
62
+ File.open(file, 'w') do |f|
63
+ f.puts result
64
+ end
65
+
66
+ result
67
+ end
68
+ end
69
+
70
+ def build_report(frontmatter, report_data, report)
71
+ str = StringIO.new
72
+
73
+ if frontmatter
74
+ str.puts generate_frontmatter(frontmatter, report_data)
75
+ str.puts "---"
76
+ str.puts
77
+ end
78
+
79
+ str.puts report
80
+
81
+ str.string
82
+ end
83
+
84
+ private
85
+ # convert_keys({'foo' => 'bar'}, :to_sym)
86
+ # => {:foo => 'bar'}
87
+ def convert_keys(data, method_name)
88
+ data.map {|k, v| [k.send(method_name), v]}.to_h
89
+ end
90
+ end
@@ -0,0 +1,76 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "how_is"
4
+ require "how_is/cli"
5
+ require "slop"
6
+
7
+ class HowIs::CLI
8
+ DEFAULT_REPORT_FILE = "report.#{HowIs::DEFAULT_FORMAT}"
9
+
10
+ class OptionsError < StandardError
11
+ end
12
+
13
+ class Parser
14
+ attr_reader :opts
15
+
16
+ def call(argv)
17
+ opts = Slop::Options.new
18
+ opts.banner =
19
+ <<-EOF.gsub(/ *\| ?/, '')
20
+ | Usage: how_is REPOSITORY [--report REPORT_FILE]
21
+ | how_is --config CONFIG_FILE
22
+ |
23
+ | Where REPOSITORY is of the format <GitHub username or org>/<repository name>.
24
+ | CONFIG_FILE defaults to how_is.yml.
25
+ |
26
+ | E.g., if you wanted to check https://github.com/how-is/how_is,
27
+ | you'd run `how_is how-is/how_is`.
28
+ |
29
+ EOF
30
+
31
+ opts.separator ""
32
+ opts.separator "Options:"
33
+
34
+ opts.bool "-h", "--help", "Print help text"
35
+ opts.string "--config", "YAML config file, used to generate a group of reports"
36
+ opts.string "--from", "JSON report file, used instead of fetching the data again"
37
+ opts.string "--report", "output file for the report (valid extensions: #{HowIs.supported_formats.join(', ')}; default: #{DEFAULT_REPORT_FILE})"
38
+ opts.string "-v", "--version", "prints the version"
39
+
40
+ parser = Slop::Parser.new(opts)
41
+ result = parser.parse(argv)
42
+ options = result.to_hash
43
+ arguments = result.arguments
44
+
45
+ options[:report] ||= DEFAULT_REPORT_FILE
46
+
47
+ # The following are only useful if true.
48
+ # Removing them here simplifies contracts and keyword args for other APIs.
49
+ options.delete(:config) unless options[:config]
50
+ options.delete(:help) unless options[:help]
51
+ options.delete(:version) unless options[:version]
52
+
53
+ unless HowIs.can_export_to?(options[:report])
54
+ raise OptionsError, "Invalid file: #{options[:report_file]}. Supported formats: #{HowIs.supported_formats.join(', ')}"
55
+ end
56
+
57
+ if options[:config]
58
+ # Nothing to do.
59
+ elsif options[:from]
60
+ # Opening this file here seems a bit messy, but it works.
61
+ options[:repository] = JSON.parse(open(options[:from_file]).read)['repository']
62
+ raise OptionsError, "Invalid JSON report file." unless options[:repository]
63
+ elsif argv.length >= 1
64
+ options[:repository] = argv.delete_at(0)
65
+ else
66
+ raise OptionsError, "No repository specified."
67
+ end
68
+
69
+ {
70
+ opts: opts,
71
+ options: options,
72
+ arguments: arguments,
73
+ }
74
+ end
75
+ end
76
+ end
@@ -1,45 +1,45 @@
1
- require 'contracts'
2
- require 'github_api'
3
-
4
- ##
5
- # Fetches data from GitHub.
6
- class HowIs::Fetcher
7
- include Contracts::Core
8
-
9
- ##
10
- # Standardized representation for fetcher results.
11
- #
12
- # Implemented as a class instead of passing around a Hash so that it can
13
- # be more easily referenced by Contracts.
14
- class Results < Struct.new(:repository, :issues, :pulls)
15
- include Contracts::Core
16
-
17
- Contract String, C::ArrayOf[Hash], C::ArrayOf[Hash] => nil
18
- def initialize(repository, issues, pulls)
19
- super(repository, issues, pulls)
20
- end
21
-
22
- # Struct defines #to_h, but not #to_hash, so we alias them.
23
- alias_method :to_hash, :to_h
24
- end
25
-
26
-
27
- Contract String, C::Or[C::RespondTo[:issues, :pulls], nil] => Results
28
- def call(repository,
29
- github = nil)
30
- github ||= Github.new(auto_pagination: true)
31
- user, repo = repository.split('/', 2)
32
- issues = github.issues.list user: user, repo: repo
33
- pulls = github.pulls.list user: user, repo: repo
34
-
35
- Results.new(
36
- repository,
37
- obj_to_array_of_hashes(issues),
38
- obj_to_array_of_hashes(pulls)
39
- )
40
- end
41
-
42
- private def obj_to_array_of_hashes(object)
43
- object.to_a.map(&:to_h)
44
- end
45
- end
1
+ require 'contracts'
2
+ require 'github_api'
3
+
4
+ ##
5
+ # Fetches data from GitHub.
6
+ class HowIs::Fetcher
7
+ include Contracts::Core
8
+
9
+ ##
10
+ # Standardized representation for fetcher results.
11
+ #
12
+ # Implemented as a class instead of passing around a Hash so that it can
13
+ # be more easily referenced by Contracts.
14
+ class Results < Struct.new(:repository, :issues, :pulls)
15
+ include Contracts::Core
16
+
17
+ Contract String, C::ArrayOf[Hash], C::ArrayOf[Hash] => nil
18
+ def initialize(repository, issues, pulls)
19
+ super(repository, issues, pulls)
20
+ end
21
+
22
+ # Struct defines #to_h, but not #to_hash, so we alias them.
23
+ alias_method :to_hash, :to_h
24
+ end
25
+
26
+
27
+ Contract String, C::Or[C::RespondTo[:issues, :pulls], nil] => Results
28
+ def call(repository,
29
+ github = nil)
30
+ github ||= Github.new(auto_pagination: true)
31
+ user, repo = repository.split('/', 2)
32
+ issues = github.issues.list user: user, repo: repo
33
+ pulls = github.pulls.list user: user, repo: repo
34
+
35
+ Results.new(
36
+ repository,
37
+ obj_to_array_of_hashes(issues),
38
+ obj_to_array_of_hashes(pulls)
39
+ )
40
+ end
41
+
42
+ private def obj_to_array_of_hashes(object)
43
+ object.to_a.map(&:to_h)
44
+ end
45
+ end