twigg 0.0.1
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.
- checksums.yaml +7 -0
- data/bin/twigg +5 -0
- data/files/github.pem +97 -0
- data/lib/twigg/command/git.rb +23 -0
- data/lib/twigg/command/git_host.rb +98 -0
- data/lib/twigg/command/git_hub.rb +67 -0
- data/lib/twigg/command/help.rb +113 -0
- data/lib/twigg/command/init.rb +15 -0
- data/lib/twigg/command/russian.rb +18 -0
- data/lib/twigg/command/stats.rb +75 -0
- data/lib/twigg/command.rb +108 -0
- data/lib/twigg/commit.rb +59 -0
- data/lib/twigg/commit_set.rb +137 -0
- data/lib/twigg/config.rb +95 -0
- data/lib/twigg/console.rb +68 -0
- data/lib/twigg/dependency.rb +12 -0
- data/lib/twigg/flesch.rb +65 -0
- data/lib/twigg/gatherer.rb +15 -0
- data/lib/twigg/pair_matrix.rb +83 -0
- data/lib/twigg/repo.rb +134 -0
- data/lib/twigg/repo_set.rb +31 -0
- data/lib/twigg/russian_novel.rb +40 -0
- data/lib/twigg/settings/dsl.rb +144 -0
- data/lib/twigg/settings.rb +69 -0
- data/lib/twigg/team.rb +25 -0
- data/lib/twigg/util.rb +68 -0
- data/lib/twigg/version.rb +3 -0
- data/lib/twigg.rb +26 -0
- metadata +142 -0
@@ -0,0 +1,144 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
|
3
|
+
module Twigg
|
4
|
+
class Settings
|
5
|
+
module DSL
|
6
|
+
# Hash subclass used as a book-keeping detail, which allows us to
|
7
|
+
# differentiate settings hashes from hashes used to construct namespaces.
|
8
|
+
class Namespace < Hash; end
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
attr_reader :overrides
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def namespaces
|
16
|
+
@namespaces ||= []
|
17
|
+
end
|
18
|
+
|
19
|
+
# DSL method for declaring settings underneath a namespace.
|
20
|
+
#
|
21
|
+
# Example:
|
22
|
+
#
|
23
|
+
# namespace :app do
|
24
|
+
# setting :bind, default: '0.0.0.0'
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
def namespace(scope, &block)
|
28
|
+
namespaces.push scope
|
29
|
+
yield
|
30
|
+
ensure
|
31
|
+
namespaces.pop
|
32
|
+
end
|
33
|
+
|
34
|
+
# DSL method which is used to create a reader for the setting `name`. If
|
35
|
+
# the configuration file does not contain a value for the setting, return
|
36
|
+
# `options[:default]` from the `options` hash.
|
37
|
+
#
|
38
|
+
# A block maybe provided to do checking of the supplied value prior to
|
39
|
+
# returning; for an example, see the `repositories_directory` setting in
|
40
|
+
# this file.
|
41
|
+
#
|
42
|
+
# Note that this method doesn't actually create any readers; it merely
|
43
|
+
# records data about default values and validation which are then used at
|
44
|
+
# initialization time to override the readers provided by OpenStruct
|
45
|
+
# itself.
|
46
|
+
def setting(name, options = {}, &block)
|
47
|
+
options.merge!(block: block)
|
48
|
+
@overrides ||= {}
|
49
|
+
|
50
|
+
overrides = namespaces.inject(@overrides) do |overrides, namespace|
|
51
|
+
overrides[namespace] ||= Namespace.new
|
52
|
+
end
|
53
|
+
|
54
|
+
overrides[name] = options
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
module InstanceMethods
|
59
|
+
private
|
60
|
+
|
61
|
+
def initialize(hash = nil)
|
62
|
+
super
|
63
|
+
deep_open_structify!(self)
|
64
|
+
override_methods!(self, self.class.overrides)
|
65
|
+
end
|
66
|
+
|
67
|
+
# Recursively replace nested hash values with OpenStructs.
|
68
|
+
#
|
69
|
+
# This enables us to make nice calls like `Config.foo.bar` instead of
|
70
|
+
# the somewhat unsightly `Config.foo[:bar]`.
|
71
|
+
def deep_open_structify!(instance)
|
72
|
+
# call `to_a` here to avoid mutating the collection during iteration
|
73
|
+
instance.each_pair.to_a.each do |key, value|
|
74
|
+
if value.is_a?(Hash)
|
75
|
+
deep_open_structify!(instance.[]=(key, OpenStruct.new(value)))
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# Recurses through the `overrides` hash, overriding OpenStruct-supplied
|
81
|
+
# methods with ones which handle defaults, perform validation, and memoize
|
82
|
+
# their results.
|
83
|
+
#
|
84
|
+
# The `overrides` hash itself is built up using the DSL as this class file
|
85
|
+
# is passed. This method is called at runtime, once the YAML file containing
|
86
|
+
# the configuration has been loaded from disk.
|
87
|
+
def override_methods!(instance, overrides, namespace = nil)
|
88
|
+
return unless overrides
|
89
|
+
|
90
|
+
overrides.each do |name, options|
|
91
|
+
if options.is_a?(Namespace)
|
92
|
+
process_namespace(instance, namespace, name, options) # recurses
|
93
|
+
else
|
94
|
+
define_getter(instance, namespace, name, options)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# Sets up the given `namespace` on `instance`, if not already set up,
|
100
|
+
# and (recursively) calls {#override_methods!} to set up `name`.
|
101
|
+
def process_namespace(instance, namespace, name, options)
|
102
|
+
nested = instance.[](name)
|
103
|
+
|
104
|
+
unless nested.is_a?(OpenStruct)
|
105
|
+
instance.[]=(name, nested = OpenStruct.new)
|
106
|
+
end
|
107
|
+
|
108
|
+
override_methods!(nested, options, name)
|
109
|
+
end
|
110
|
+
|
111
|
+
# Defines a getter on the `instance` for setting `name` within
|
112
|
+
# `namespace`.
|
113
|
+
#
|
114
|
+
# The getter:
|
115
|
+
#
|
116
|
+
# - memoizes its result
|
117
|
+
# - raises an ArgumentError if the option is required by not
|
118
|
+
# configured
|
119
|
+
# - supplies a default value if the option is not configured and a
|
120
|
+
# default setting is available
|
121
|
+
# - optionally calls a supplied block to perform validation of the
|
122
|
+
# configured value
|
123
|
+
#
|
124
|
+
def define_getter(instance, namespace, name, options)
|
125
|
+
instance.define_singleton_method name do
|
126
|
+
value = instance_variable_get("@#{namespace}__#{name}")
|
127
|
+
return value if value
|
128
|
+
|
129
|
+
if options[:required] &&
|
130
|
+
!instance.each_pair.to_a.map(&:first).include?(name)
|
131
|
+
raise ArgumentError, "#{name} not set"
|
132
|
+
end
|
133
|
+
|
134
|
+
value = instance.[](name) || options[:default]
|
135
|
+
|
136
|
+
options[:block].call(name, value) if options[:block]
|
137
|
+
|
138
|
+
instance_variable_set("@#{namespace}__#{name}", value)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
|
3
|
+
module Twigg
|
4
|
+
# Simple OpenStruct subclass with some methods overridden in order to provide
|
5
|
+
# more detailed feedback in the event of a misconfiguration, or to provide
|
6
|
+
# reasonable default values.
|
7
|
+
#
|
8
|
+
# For example, we override `repositories_directory` so that we can report to
|
9
|
+
# the user that:
|
10
|
+
#
|
11
|
+
# - the setting is missing from the configuration file
|
12
|
+
# - the setting does not point at a directory
|
13
|
+
#
|
14
|
+
# We override `app.bind` so that we can supply a reasonable default when the
|
15
|
+
# configuration file has no value set.
|
16
|
+
#
|
17
|
+
# In order to keep this class a simple and readable manifest of the different
|
18
|
+
# options, the real logic is all implemented in the {Twigg::Settings::DSL}
|
19
|
+
# module, leaving only the DSL declarations here.
|
20
|
+
class Settings < OpenStruct
|
21
|
+
autoload :DSL, 'twigg/settings/dsl'
|
22
|
+
extend DSL::ClassMethods
|
23
|
+
include DSL::InstanceMethods
|
24
|
+
|
25
|
+
namespace :app do
|
26
|
+
setting :bind, default: '0.0.0.0'
|
27
|
+
|
28
|
+
namespace :gerrit do
|
29
|
+
setting :enabled, default: false
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
setting :default_days, default: 7
|
34
|
+
|
35
|
+
namespace :gerrit do
|
36
|
+
setting :host, default: 'localhost'
|
37
|
+
setting :port, default: 29418
|
38
|
+
setting :user, default: ENV['USER']
|
39
|
+
|
40
|
+
namespace :web do
|
41
|
+
setting :host, default: 'https://localhost'
|
42
|
+
end
|
43
|
+
|
44
|
+
namespace :db do
|
45
|
+
setting :adapter, default: 'mysql2'
|
46
|
+
setting :database, required: true
|
47
|
+
setting :host, default: 'localhost'
|
48
|
+
setting :password
|
49
|
+
setting :port, default: 3306
|
50
|
+
setting :user, default: ENV['USER']
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
namespace :github do
|
55
|
+
setting :organization, required: true
|
56
|
+
setting :token, required: true
|
57
|
+
end
|
58
|
+
|
59
|
+
setting :repositories_directory, required: true do |name, value|
|
60
|
+
raise ArgumentError, "#{name} not a directory" unless File.directory?(value)
|
61
|
+
end
|
62
|
+
|
63
|
+
setting :teams, default: {} do |name, value|
|
64
|
+
if !value.respond_to?(:to_h) || value.to_h.values.any? { |team| !team.is_a?(Array) }
|
65
|
+
raise ArgumentError, "#{name} should be a hash of arrays"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
data/lib/twigg/team.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
module Twigg
|
2
|
+
class Team
|
3
|
+
# Catch-all team name for authors who aren't assigned to a particular team.
|
4
|
+
OTHER_TEAM_NAME = 'Other'
|
5
|
+
|
6
|
+
class << self
|
7
|
+
# Returns a hash where the keys are author names and the values are team
|
8
|
+
# names.
|
9
|
+
#
|
10
|
+
# As there is only one value per key, an author must be in one team only;
|
11
|
+
# if the author is assigned to multiple teams, this method will pick the
|
12
|
+
# first team the author is assigned to as his or her team.
|
13
|
+
def author_to_team_map
|
14
|
+
Config.
|
15
|
+
teams.
|
16
|
+
each_pair.
|
17
|
+
each_with_object(Hash.new(OTHER_TEAM_NAME)) do |(team, members), map|
|
18
|
+
members.each do |member|
|
19
|
+
map[member] = team.to_s unless map.has_key?(member)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/lib/twigg/util.rb
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
module Twigg
|
2
|
+
module Util
|
3
|
+
private
|
4
|
+
|
5
|
+
# Returns the age of `time` relative to now in hours (for short intervals)
|
6
|
+
# or days (for intervals longer than 24 hours).
|
7
|
+
def age(time)
|
8
|
+
delta = Time.now - time
|
9
|
+
return 'future' if delta < 0
|
10
|
+
hours = (delta / (60 * 60)).to_i
|
11
|
+
days = hours / 24
|
12
|
+
(hours > 24 ? "#{pluralize days, 'day'}" : "#{pluralize hours, 'hour'}") +
|
13
|
+
' ago'
|
14
|
+
end
|
15
|
+
|
16
|
+
def number_with_delimiter(integer)
|
17
|
+
# Regex based on one in `ActiveSupport::NumberHelper#number_to_delimited`;
|
18
|
+
# this method is simpler because it only needs to handle integers.
|
19
|
+
integer.to_s.tap do |string|
|
20
|
+
string.gsub!(/(\d)(?=(\d{3})+(?!\d))/, '\\1,')
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Dumb implementation of a Rails-style `#pluralize` helper.
|
25
|
+
#
|
26
|
+
# As a default, it pluralizes by adding an "s" to the singular form, and
|
27
|
+
# will use `#number_with_delimiter` to insert delimiters, unless passed
|
28
|
+
# `delimit: false`.
|
29
|
+
#
|
30
|
+
# If you pass a plural inflection, it is remembered for subsequent calls.
|
31
|
+
#
|
32
|
+
# Example:
|
33
|
+
#
|
34
|
+
# pluralize(1, 'octopus', 'octopi') # => "1 octopus"
|
35
|
+
# pluralize(2, 'octopus') # => "2 octopi"
|
36
|
+
# pluralize(1_200, 'commit') # => "1,200 commits"
|
37
|
+
# pluralize(1_200, 'commit', delimit: false) # => "1200 commits"
|
38
|
+
#
|
39
|
+
def pluralize(count, singular, plural = nil, delimit: true)
|
40
|
+
@inflections ||= Hash.new do |hash, key|
|
41
|
+
hash[key] = [key, plural ? plural : key + 's']
|
42
|
+
end
|
43
|
+
|
44
|
+
(delimit ? number_with_delimiter(count) : count.to_s) + ' ' +
|
45
|
+
@inflections[singular][count == 1 ? 0 : 1]
|
46
|
+
end
|
47
|
+
|
48
|
+
# Returns a per-repo breakdown (repo names, commit counts) of commits in
|
49
|
+
# `commit_set`.
|
50
|
+
#
|
51
|
+
# Returns HTML output by default, or plain-text when `html` is `false`.
|
52
|
+
def breakdown(commit_set, html: true)
|
53
|
+
commit_set.count_by_repo.map do |data|
|
54
|
+
if html && (link = data[:repo].link)
|
55
|
+
name = %{<a href="#{link}">#{data[:repo].name}</a>}
|
56
|
+
else
|
57
|
+
name = data[:repo].name
|
58
|
+
end
|
59
|
+
|
60
|
+
if html
|
61
|
+
"<i>#{name}:</i> <b>#{number_with_delimiter data[:count]}</b>"
|
62
|
+
else
|
63
|
+
"#{name}: #{number_with_delimiter data[:count]}"
|
64
|
+
end
|
65
|
+
end.join(', ')
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
data/lib/twigg.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
|
3
|
+
module Twigg
|
4
|
+
autoload :Command, 'twigg/command'
|
5
|
+
autoload :Commit, 'twigg/commit'
|
6
|
+
autoload :CommitSet, 'twigg/commit_set'
|
7
|
+
autoload :Config, 'twigg/config'
|
8
|
+
autoload :Console, 'twigg/console'
|
9
|
+
autoload :Dependency, 'twigg/dependency'
|
10
|
+
autoload :Flesch, 'twigg/flesch'
|
11
|
+
autoload :Gatherer, 'twigg/gatherer'
|
12
|
+
autoload :PairMatrix, 'twigg/pair_matrix'
|
13
|
+
autoload :Repo, 'twigg/repo'
|
14
|
+
autoload :RepoSet, 'twigg/repo_set'
|
15
|
+
autoload :RussianNovel, 'twigg/russian_novel'
|
16
|
+
autoload :Settings, 'twigg/settings'
|
17
|
+
autoload :Team, 'twigg/team'
|
18
|
+
autoload :Util, 'twigg/util'
|
19
|
+
autoload :VERSION, 'twigg/version'
|
20
|
+
|
21
|
+
# Returns a Pathname instance corresponding to the root directory of the gem
|
22
|
+
# (ie. the directory containing the `files` and `templates` directories).
|
23
|
+
def self.root
|
24
|
+
Pathname.new(__dir__) + '..'
|
25
|
+
end
|
26
|
+
end
|
metadata
ADDED
@@ -0,0 +1,142 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: twigg
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Causes Engineering
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-08-17 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.3'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.3'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: factory_girl
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rr
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rspec
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - '>='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - '>='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
description: Twigg provides stats for activity in Git repositories.
|
84
|
+
email:
|
85
|
+
- eng@causes.com
|
86
|
+
executables:
|
87
|
+
- twigg
|
88
|
+
extensions: []
|
89
|
+
extra_rdoc_files: []
|
90
|
+
files:
|
91
|
+
- bin/twigg
|
92
|
+
- files/github.pem
|
93
|
+
- lib/twigg/command/git.rb
|
94
|
+
- lib/twigg/command/git_host.rb
|
95
|
+
- lib/twigg/command/git_hub.rb
|
96
|
+
- lib/twigg/command/help.rb
|
97
|
+
- lib/twigg/command/init.rb
|
98
|
+
- lib/twigg/command/russian.rb
|
99
|
+
- lib/twigg/command/stats.rb
|
100
|
+
- lib/twigg/command.rb
|
101
|
+
- lib/twigg/commit.rb
|
102
|
+
- lib/twigg/commit_set.rb
|
103
|
+
- lib/twigg/config.rb
|
104
|
+
- lib/twigg/console.rb
|
105
|
+
- lib/twigg/dependency.rb
|
106
|
+
- lib/twigg/flesch.rb
|
107
|
+
- lib/twigg/gatherer.rb
|
108
|
+
- lib/twigg/pair_matrix.rb
|
109
|
+
- lib/twigg/repo.rb
|
110
|
+
- lib/twigg/repo_set.rb
|
111
|
+
- lib/twigg/russian_novel.rb
|
112
|
+
- lib/twigg/settings/dsl.rb
|
113
|
+
- lib/twigg/settings.rb
|
114
|
+
- lib/twigg/team.rb
|
115
|
+
- lib/twigg/util.rb
|
116
|
+
- lib/twigg/version.rb
|
117
|
+
- lib/twigg.rb
|
118
|
+
homepage: https://github.com/causes/twigg
|
119
|
+
licenses:
|
120
|
+
- MIT
|
121
|
+
metadata: {}
|
122
|
+
post_install_message:
|
123
|
+
rdoc_options: []
|
124
|
+
require_paths:
|
125
|
+
- lib
|
126
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
127
|
+
requirements:
|
128
|
+
- - '>='
|
129
|
+
- !ruby/object:Gem::Version
|
130
|
+
version: 2.0.0
|
131
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
132
|
+
requirements:
|
133
|
+
- - '>='
|
134
|
+
- !ruby/object:Gem::Version
|
135
|
+
version: '0'
|
136
|
+
requirements: []
|
137
|
+
rubyforge_project:
|
138
|
+
rubygems_version: 2.0.3
|
139
|
+
signing_key:
|
140
|
+
specification_version: 4
|
141
|
+
summary: Statistics for Git repositories
|
142
|
+
test_files: []
|