codespicuous 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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
+