codespicuous 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.
Files changed (52) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +7 -0
  3. data/LICENSE +29 -0
  4. data/README.md +2 -0
  5. data/Rakefile +6 -0
  6. data/bin/codespicuous +4 -0
  7. data/bin/filezilla/codespicuous.yaml +23 -0
  8. data/bin/filezilla/committers.csv +4 -0
  9. data/bin/filezilla/svnlog/filezilla.log +3939 -0
  10. data/bin/filezilla/svnlog/python.log +3 -0
  11. data/bin/filezilla/svnlog/xiph.log +265 -0
  12. data/codespicuous.gemspec +22 -0
  13. data/lib/codespicuous.rb +38 -0
  14. data/lib/codespicuous/codespicuous.rb +55 -0
  15. data/lib/codespicuous/codespicuous_config.rb +35 -0
  16. data/lib/codespicuous/codespicuousconfigurator.rb +136 -0
  17. data/lib/codespicuous/commandrunner.rb +13 -0
  18. data/lib/codespicuous/commithistory.rb +71 -0
  19. data/lib/codespicuous/commithistory_builder.rb +49 -0
  20. data/lib/codespicuous/commits.rb +147 -0
  21. data/lib/codespicuous/commitstatistics.rb +245 -0
  22. data/lib/codespicuous/committer.rb +105 -0
  23. data/lib/codespicuous/danielparser.rb +31 -0
  24. data/lib/codespicuous/dateutil.rb +18 -0
  25. data/lib/codespicuous/metrics_generator.rb +22 -0
  26. data/lib/codespicuous/metrics_generator_csv.rb +67 -0
  27. data/lib/codespicuous/metrics_generator_daniel.rb +13 -0
  28. data/lib/codespicuous/participantsparser_from_csv.rb +37 -0
  29. data/lib/codespicuous/repositories.rb +80 -0
  30. data/lib/codespicuous/repository_lister.rb +8 -0
  31. data/lib/codespicuous/svn_client.rb +14 -0
  32. data/lib/codespicuous/svn_data_collector.rb +50 -0
  33. data/lib/codespicuous/svn_log_parser.rb +100 -0
  34. data/lib/codespicuous/teams.rb +99 -0
  35. data/lib/codespicuous/version.rb +4 -0
  36. data/spec/codespicuous_spec.rb +81 -0
  37. data/spec/codespicuousconfigurator_spec.rb +202 -0
  38. data/spec/commithistories_data.rb +46 -0
  39. data/spec/commithistory_spec.rb +57 -0
  40. data/spec/commits_spec.rb +93 -0
  41. data/spec/committers_spec.rb +66 -0
  42. data/spec/danielparser_spec.rb +12 -0
  43. data/spec/integration_filezilla_spec.rb +41 -0
  44. data/spec/metrics_generator_csv_spec.rb +91 -0
  45. data/spec/metrics_generator_daniel_spec.rb +10 -0
  46. data/spec/metrics_generator_spec.rb +35 -0
  47. data/spec/repositories_spec.rb +29 -0
  48. data/spec/svn_client_spec.rb +16 -0
  49. data/spec/svn_data_collector_spec.rb +93 -0
  50. data/spec/svn_log_parser_spec.rb +141 -0
  51. data/spec/teams_spec.rb +16 -0
  52. metadata +142 -0
@@ -0,0 +1,100 @@
1
+
2
+ require 'rexml/document'
3
+
4
+ class SVNLogParser
5
+
6
+ def initialize
7
+ @commits = Commits.new
8
+ end
9
+
10
+ def xml_to_parse(xml_string)
11
+ @xml_to_parse = xml_string
12
+ end
13
+
14
+ def parse(xml_to_parse = nil)
15
+ @xml_to_parse = xml_to_parse if xml_to_parse
16
+
17
+ xml = REXML::Document.new(@xml_to_parse)
18
+ validate_xml(xml)
19
+
20
+ xml.elements.each( "*/logentry" ) do |logentry|
21
+ commit = create_commit_from_log_entry(logentry)
22
+ @commits.add(commit)
23
+ end
24
+ self
25
+ end
26
+
27
+ def create_commit_from_log_entry(logentry)
28
+ commit = Commit.new
29
+ commit.revision = logentry.attributes["revision"]
30
+ commit.author = logentry.elements["author"].text
31
+ commit.message = logentry.elements["msg"].text
32
+ commit.date = DateTime.parse(logentry.elements["date"].text)
33
+ commit.changes = create_commit_changes_from_log_entry(logentry)
34
+ commit
35
+ end
36
+
37
+ def extract_change_type(path)
38
+ return :modified if path.attributes["action"] == "M"
39
+ return :added if path.attributes["action"] == "A"
40
+ return :deleted if path.attributes["action"] == "D"
41
+ return :renamed if path.attributes["action"] == "R"
42
+ end
43
+
44
+ def extract_kind(path)
45
+ return :file if path.attributes["kind"] == "file"
46
+ return :dir if path.attributes["kind"] == "dir"
47
+ end
48
+
49
+ def create_commit_changes_from_log_entry(logentry)
50
+ changes = []
51
+ logentry.elements.each("*/path") { |path|
52
+ change = Change.new
53
+ change.type = extract_change_type(path)
54
+ change.kind = extract_kind(path)
55
+ change.property_changed if path.attributes["prop-mods"] == "true"
56
+ change.copyfrom = path.attributes["copyfrom-path"]
57
+ change.copyrev = path.attributes["copyfrom-rev"]
58
+ change.file = path.text
59
+ changes << change
60
+ }
61
+ changes
62
+ end
63
+
64
+ def validate_xml(xml)
65
+ non_logentries = xml.elements["log"].elements.collect { |e| e.name unless e.name == "logentry" }.compact
66
+ non_logentries.each { |e| raise("Unexpected log entry: " + e) }
67
+
68
+ xml.elements.each( "*/logentry" ) do |logentry|
69
+ validate_log_entry(logentry)
70
+ end
71
+ end
72
+
73
+ def validate_log_entry logentry
74
+ invalid_attributes = logentry.attributes.collect { |a| a[0] unless a[0] == "revision" }.compact
75
+ invalid_attributes.each { |a| raise ("Unexpected attributes log entry: " + a) }
76
+
77
+ invalid_elements = logentry.elements.collect { |e| e.name unless ["author", "date", "paths", "msg"].include? e.name }.compact
78
+ invalid_elements.each { |e| raise ("Unexpected element in log entry: " + e) }
79
+
80
+ logentry.elements.each("*/path") { |path|
81
+ validate_path path
82
+ }
83
+ end
84
+
85
+ def validate_path path
86
+ path.elements.each { |e| raise ("Unexpected element in path: " + e.name) }
87
+
88
+ invalid_attributes = path.attributes.collect { |a| a[0] unless ["action", "prop-mods", "text-mods", "kind", "copyfrom-path", "copyfrom-rev"].include? a[0] }.compact
89
+ invalid_attributes.each { |a| raise ("Unexpected attributes in path: " + a) }
90
+
91
+ raise("Unexpected value to attribute action in path: " + path.attributes["action"]) unless ["R", "M", "A", "D"].include?(path.attributes["action"])
92
+ raise("Unexpected value to attribute kind in path: " + path.attributes["kind"]) unless ["file", "dir"].include?(path.attributes["kind"])
93
+
94
+ puts "Unexpected value to attribute text-mods in path: " + path.attributes["text-mods"] if path.attributes["text-mods"] == "false" and not ["D", "R"].include?(path.attributes["action"]) and path.attributes["kind"] != "dir"
95
+ end
96
+
97
+ def commits
98
+ @commits
99
+ end
100
+ end
@@ -0,0 +1,99 @@
1
+
2
+ class Team
3
+
4
+ attr_accessor :name
5
+
6
+ def initialize(name)
7
+ @name = name
8
+ @members = {}
9
+ end
10
+
11
+ def add_member(member)
12
+ @members[member.username] = member
13
+ member.team = self
14
+ end
15
+
16
+ def members
17
+ @members.values
18
+ end
19
+
20
+ def each_member
21
+ @members.values.each { |member|
22
+ yield member
23
+ }
24
+ end
25
+
26
+ def amount_of_members
27
+ @members.size
28
+ end
29
+
30
+ def member_usernames
31
+ @members.keys
32
+ end
33
+
34
+ def <=> other
35
+ @name <=> other.name
36
+ end
37
+
38
+ def ==(team)
39
+ name == team.name && members == team.members
40
+ end
41
+ end
42
+
43
+ class Teams
44
+
45
+ attr_reader :teams
46
+
47
+ def initialize
48
+ @teams = {}
49
+ end
50
+
51
+ def find_by_name(name)
52
+ @teams[name]
53
+ end
54
+
55
+ def team_names
56
+ @teams.keys
57
+ end
58
+
59
+ def each
60
+ @teams.values.sort.each { |team|
61
+ yield team
62
+ }
63
+ end
64
+
65
+ def map(&block)
66
+ @teams.values.map(&block)
67
+ end
68
+
69
+ def each_member
70
+ @teams.values.each { |team|
71
+ team.each_member { |member|
72
+ yield team, member
73
+ }
74
+ }
75
+ end
76
+
77
+ def team(name)
78
+ @teams[name] ||= Team.new(name)
79
+ end
80
+
81
+ def add(team)
82
+ @teams[team.name] = team
83
+ end
84
+
85
+ def amount
86
+ @teams.size
87
+ end
88
+
89
+ def member_usernames(team_name = nil)
90
+ @teams.values.collect { |team|
91
+ team.member_usernames if team.name == team_name || team_name == nil
92
+ }.compact.flatten
93
+ end
94
+
95
+ def ==(teams)
96
+ @teams == teams.teams
97
+ end
98
+ end
99
+
@@ -0,0 +1,4 @@
1
+
2
+ module Codespicuous
3
+ VERSION = "0.0.1"
4
+ end
@@ -0,0 +1,81 @@
1
+
2
+ describe "Codespicuous command line" do
3
+
4
+ subject {Codespicuous.new}
5
+
6
+ it "prints an error message when no config it present" do
7
+ expect(subject).to receive(:puts).with("Stage 1: Configuring")
8
+ expect($stdout).to receive(:puts).with("** Error: No repositories configured in \"codespicuous.yaml\"")
9
+
10
+ expect(subject.run([])).to eq false
11
+ end
12
+
13
+ it "creates a configurator, collect data, and lists the repositories the people committed to" do
14
+ subject.config.list_repositories = true
15
+
16
+ expect(subject).to receive(:puts).with("Stage 1: Configuring")
17
+ expect(subject).to receive(:configure).and_return(true)
18
+ expect(subject).to receive(:puts).with("Stage 2: Collecting input data")
19
+ expect(subject).to receive(:collect)
20
+ expect(subject).to receive(:puts).with("Stage 3: Listing repositories committed to")
21
+ expect(subject).to receive(:list_committed_repositories)
22
+
23
+ expect(subject.run([])).to eq true
24
+ end
25
+
26
+ it "creates a configurator, collect data, and generate output" do
27
+ expect(subject).to receive(:puts).with("Stage 1: Configuring")
28
+ expect(subject).to receive(:configure).and_return(true)
29
+ expect(subject).to receive(:puts).with("Stage 2: Collecting input data")
30
+ expect(subject).to receive(:collect)
31
+ expect(subject).to receive(:puts).with("Stage 3: Generating output")
32
+ expect(subject).to receive(:generate_output)
33
+
34
+ expect(subject.run([])).to eq true
35
+ end
36
+
37
+ it "configures the config data" do
38
+ configurator = CodespicuousConfigurator.new(subject.config)
39
+ repositories = Repositories.new
40
+ committers = Committers.new
41
+ teams = Teams.new
42
+
43
+ expect(CodespicuousConfigurator).to receive(:new).and_return(configurator)
44
+ expect(configurator).to receive(:configure).with(["argv"]).and_return(true)
45
+
46
+ expect(configurator).to receive(:repositories).and_return(repositories)
47
+ expect(configurator).to receive(:committers).and_return(committers)
48
+ expect(configurator).to receive(:teams).and_return(teams)
49
+
50
+ subject.configure(["argv"])
51
+ expect(subject.committers).to be committers
52
+ expect(subject.repositories).to be repositories
53
+ expect(subject.teams).to be teams
54
+ end
55
+
56
+ it "collects the input data" do
57
+ collector = SVNDataCollector.new(subject.config)
58
+ commit_history = CommitHistory.new
59
+ expect(SVNDataCollector).to receive(:new).and_return(collector)
60
+ expect(collector).to receive(:collect_commit_history).with(subject.repositories).and_return(commit_history)
61
+
62
+ subject.collect
63
+
64
+ expect(subject.commit_history).to eq commit_history
65
+ end
66
+
67
+ it "generates output" do
68
+ generator = MetricsGenerator.new
69
+ expect(MetricsGenerator).to receive(:new).and_return(generator)
70
+ expect(generator).to receive(:generate).with(subject.commit_history)
71
+ subject.generate_output
72
+ end
73
+
74
+ it "lists the repositories" do
75
+ lister = RepositoryLister.new
76
+ expect(RepositoryLister).to receive(:new).and_return(lister)
77
+ expect(lister).to receive(:list).with(subject.commit_history)
78
+ subject.list_committed_repositories
79
+ end
80
+ end
81
+
@@ -0,0 +1,202 @@
1
+
2
+ describe "CodepicuousConfigurator reads all the config files and provides the data needed for running Codespicuous" do
3
+
4
+ subject { CodespicuousConfigurator.new(CodespicuousConfig.new) }
5
+
6
+ it "Should process the yaml file" do
7
+ expect(subject).to receive(:parse_command_line_arguments).with(["argv"])
8
+ expect(subject).to receive(:configure_from_yaml)
9
+ expect(subject).to receive(:postprocess_yaml_configuration)
10
+ expect(subject).to receive(:find_alternative_configuration_files)
11
+ expect(subject).to receive(:validate_configuration)
12
+ subject.configure(["argv"])
13
+ end
14
+
15
+ it "should be able to handle the -r command line option" do
16
+ subject.parse_command_line_arguments(["-r"])
17
+ expect(subject.config.list_repositories).to be true
18
+ end
19
+
20
+ it "Should be able to handle the -i command line option for input directory " do
21
+ subject.parse_command_line_arguments(["-i", "blah"])
22
+ expect(subject.config.input_path).to eq Pathname.new("blah")
23
+ end
24
+
25
+ it "Should be able to handle the -o command line option for output directory " do
26
+ subject.parse_command_line_arguments(["-o", "blah"])
27
+ expect(subject.config.output_path).to eq Pathname.new("blah")
28
+ end
29
+
30
+ it "Should post process the YAML file" do
31
+ yaml_content = {}
32
+
33
+ expect(subject).to receive(:postprocess_yaml_configuration_repositories).with(yaml_content)
34
+ expect(subject).to receive(:postprocess_yaml_configuration_committers).with(yaml_content)
35
+
36
+ subject.postprocess_yaml_configuration(yaml_content)
37
+ end
38
+
39
+ it "Should find alternative config files" do
40
+ expect(subject).to receive(:find_alternative_configuration_files_for_repositories)
41
+ expect(subject).to receive(:find_alternative_configuration_files_for_committers)
42
+
43
+ subject.find_alternative_configuration_files
44
+ end
45
+
46
+ context "Read the YAML file" do
47
+
48
+ it "reads the codespicuous configuration from YAML file when the file exists" do
49
+ expect(File).to receive(:exist?).with("codespicuous.yaml").and_return(true)
50
+ expect(File).to receive(:read).with("codespicuous.yaml").and_return("offline: true")
51
+ expect(subject).to receive(:puts).with('** Configuring options with "codespicuous.yaml"')
52
+
53
+ subject.configure_from_yaml
54
+ expect(subject.config.offline).to eq true
55
+ end
56
+
57
+ it "Ignores the missing options file and tries to use defaults" do
58
+ expect(File).to receive(:exist?).with("codespicuous.yaml").and_return(false)
59
+
60
+ subject.configure_from_yaml
61
+
62
+ expect(subject.config.offline).to eq false
63
+ end
64
+ end
65
+
66
+ context "Parsing the repositories" do
67
+
68
+ it "Processes the repositories out of the options with all selected" do
69
+
70
+ yaml_content = {"repositories" => { "name" => "url"}}
71
+
72
+ subject.postprocess_yaml_configuration_repositories(yaml_content)
73
+
74
+ repositories = Repositories.new
75
+ repositories.add(Repository.new("name", "url"))
76
+
77
+ expect(subject.repositories).to eq repositories
78
+ expect(subject.repositories_to_check).to eq repositories
79
+ end
80
+
81
+ it "Processes the repositories out of the options, with only the selected one" do
82
+
83
+ yaml_content = {"repositories" => { "name" => "url", "name2" => "url2"},
84
+ "repositories_to_check" => ["name2"]}
85
+
86
+ subject.postprocess_yaml_configuration_repositories(yaml_content)
87
+
88
+ repositories = Repositories.new
89
+ repositories.add(Repository.new("name", "url"))
90
+ repositories.add(Repository.new("name2", "url2"))
91
+
92
+ checked_repositories = Repositories.new
93
+ checked_repositories.add(Repository.new("name2", "url2"))
94
+
95
+ expect(subject.repositories).to eq repositories
96
+ expect(subject.repositories_to_check).to eq checked_repositories
97
+ end
98
+
99
+ it "will not bother checking the CSV file when it found repositories already" do
100
+ repositories = Repositories.new
101
+ repositories.add(Repository.new("name", "url"))
102
+ expect(subject).to receive(:repositories).and_return(repositories)
103
+ expect(File).not_to receive(:exist?)
104
+
105
+ subject.find_alternative_configuration_files_for_repositories
106
+ end
107
+
108
+ it "will check whether there is a CSV file and continue if not found" do
109
+ expect(File).to receive(:exist?).with("repositories.csv").and_return(false)
110
+
111
+ subject.find_alternative_configuration_files_for_repositories
112
+
113
+ expect(subject.repositories).to eq Repositories.new
114
+ end
115
+
116
+ it "will read the CSV file when it exists" do
117
+ expect(File).to receive(:exist?).with("repositories.csv").and_return(true)
118
+ expect(File).to receive(:read).with("repositories.csv").and_return("name,url\nrepos,https://repos.com")
119
+ expect(subject).to receive(:puts).with('** Configuring repositories with "repositories.csv"')
120
+
121
+ subject.find_alternative_configuration_files_for_repositories
122
+
123
+ repositories = Repositories.new
124
+ repositories.add(Repository.new("repos", "https://repos.com"))
125
+
126
+ expect(subject.repositories).to eq repositories
127
+ end
128
+ end
129
+
130
+ context "Parsing the teams and committers" do
131
+
132
+ before (:each) do
133
+ @team_wine = Team.new("Wine")
134
+ @committer_bas = Committer.create_committer("basvodde", "Bas", "Vodde", "basv@wow.com", @team_wine)
135
+ end
136
+
137
+ it "can have no committers and teams. Just empty then" do
138
+ subject.postprocess_yaml_configuration_committers({})
139
+
140
+ expect(subject.committers).to eq Committers.new
141
+ expect(subject.teams).to eq Teams.new
142
+ end
143
+
144
+ it "can get the team info from the yaml file" do
145
+
146
+ yaml_content = {"teams" =>
147
+ {"Wine" => [{
148
+ "First Name" => "Bas",
149
+ "Last Name" => "Vodde",
150
+ "Email" => "basv@wow.com",
151
+ "Login" => "basvodde" } ]
152
+ } }
153
+
154
+ subject.postprocess_yaml_configuration_committers(yaml_content)
155
+
156
+ committers = Committers.new
157
+ @team_wine.add_member(@committer_bas)
158
+ committers.add(@committer_bas)
159
+
160
+ teams = Teams.new
161
+ teams.add(@team_wine)
162
+
163
+ expect(subject.committers).to eq committers
164
+ expect(subject.teams).to eq teams
165
+
166
+ end
167
+
168
+ it "will not bother checking the CSV file when it found committers already" do
169
+ committers = Committers.new
170
+ committers.add(@committer_bas)
171
+ expect(subject).to receive(:committers).and_return(committers)
172
+ expect(File).not_to receive(:exist?)
173
+
174
+ subject.find_alternative_configuration_files_for_committers
175
+ end
176
+
177
+ it "will check whether there is a CSV file and continue if not found" do
178
+ expect(File).to receive(:exist?).with("committers.csv").and_return(false)
179
+
180
+ subject.find_alternative_configuration_files_for_committers
181
+
182
+ expect(subject.committers).to eq Committers.new
183
+ end
184
+
185
+ it "will read the CSV file when it exists" do
186
+ expect(File).to receive(:exist?).with("committers.csv").and_return(true)
187
+ expect(File).to receive(:read).with("committers.csv").and_return("#,First Name,Last Name,Email,Login,Team,Specialization,Manager,day1,day2,day3,Comments,Present,Questionaire send,Answered,Pretest,Dietary,Commits,Blamed lines,Average LOC/Commit
188
+ #1,Bas,Vodde,basv@wow.com,basvodde,Wine")
189
+ expect(subject).to receive(:puts).with('** Configuring committers with "committers.csv"')
190
+
191
+ subject.find_alternative_configuration_files_for_committers
192
+
193
+ committers = Committers.new
194
+ committers.add(@committer_bas)
195
+
196
+ expect(subject.committers).to eq committers
197
+ end
198
+
199
+ end
200
+
201
+ end
202
+