ruby_test_student_grader 0.1.0
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/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/LICENSE.txt +21 -0
- data/README.md +256 -0
- data/README_PROBAR.md +97 -0
- data/Rakefile +12 -0
- data/lib/ruby_test_student_grader/class_reporter.rb +87 -0
- data/lib/ruby_test_student_grader/reporter.rb +98 -0
- data/lib/ruby_test_student_grader/similarity.rb +29 -0
- data/lib/ruby_test_student_grader/template.html.erb +48 -0
- data/lib/ruby_test_student_grader/version.rb +5 -0
- data/lib/ruby_test_student_grader.rb +11 -0
- data/sig/ruby_test_student_grader.rbs +4 -0
- metadata +134 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: e8bd58f1dc44191017f6fc9b9a9304b9a236ce205654391884daacdf7cafdf17
|
4
|
+
data.tar.gz: def8f3c53b4a51b078a4f66fd49affa3266771b03c7360277b9af60a129c9716
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 5791506535eea5cd4827ea672992c20d8a702e9932cbf921c2c17cd4836f6ea471a826ecd4b2698376d08dd47c674a01495c57bc0739230825ff523f5898ea97
|
7
|
+
data.tar.gz: 246b0070222d481ad44b78f349540478814dd8bae081dd48a481a1604ffd5eb7d2a356a653ac08b8f0608abfa1d12162cceb6318d8465d662cefe4e3112a03e6
|
data/CHANGELOG.md
ADDED
data/CODE_OF_CONDUCT.md
ADDED
@@ -0,0 +1,132 @@
|
|
1
|
+
# Contributor Covenant Code of Conduct
|
2
|
+
|
3
|
+
## Our Pledge
|
4
|
+
|
5
|
+
We as members, contributors, and leaders pledge to make participation in our
|
6
|
+
community a harassment-free experience for everyone, regardless of age, body
|
7
|
+
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
8
|
+
identity and expression, level of experience, education, socio-economic status,
|
9
|
+
nationality, personal appearance, race, caste, color, religion, or sexual
|
10
|
+
identity and orientation.
|
11
|
+
|
12
|
+
We pledge to act and interact in ways that contribute to an open, welcoming,
|
13
|
+
diverse, inclusive, and healthy community.
|
14
|
+
|
15
|
+
## Our Standards
|
16
|
+
|
17
|
+
Examples of behavior that contributes to a positive environment for our
|
18
|
+
community include:
|
19
|
+
|
20
|
+
* Demonstrating empathy and kindness toward other people
|
21
|
+
* Being respectful of differing opinions, viewpoints, and experiences
|
22
|
+
* Giving and gracefully accepting constructive feedback
|
23
|
+
* Accepting responsibility and apologizing to those affected by our mistakes,
|
24
|
+
and learning from the experience
|
25
|
+
* Focusing on what is best not just for us as individuals, but for the overall
|
26
|
+
community
|
27
|
+
|
28
|
+
Examples of unacceptable behavior include:
|
29
|
+
|
30
|
+
* The use of sexualized language or imagery, and sexual attention or advances of
|
31
|
+
any kind
|
32
|
+
* Trolling, insulting or derogatory comments, and personal or political attacks
|
33
|
+
* Public or private harassment
|
34
|
+
* Publishing others' private information, such as a physical or email address,
|
35
|
+
without their explicit permission
|
36
|
+
* Other conduct which could reasonably be considered inappropriate in a
|
37
|
+
professional setting
|
38
|
+
|
39
|
+
## Enforcement Responsibilities
|
40
|
+
|
41
|
+
Community leaders are responsible for clarifying and enforcing our standards of
|
42
|
+
acceptable behavior and will take appropriate and fair corrective action in
|
43
|
+
response to any behavior that they deem inappropriate, threatening, offensive,
|
44
|
+
or harmful.
|
45
|
+
|
46
|
+
Community leaders have the right and responsibility to remove, edit, or reject
|
47
|
+
comments, commits, code, wiki edits, issues, and other contributions that are
|
48
|
+
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
49
|
+
decisions when appropriate.
|
50
|
+
|
51
|
+
## Scope
|
52
|
+
|
53
|
+
This Code of Conduct applies within all community spaces, and also applies when
|
54
|
+
an individual is officially representing the community in public spaces.
|
55
|
+
Examples of representing our community include using an official email address,
|
56
|
+
posting via an official social media account, or acting as an appointed
|
57
|
+
representative at an online or offline event.
|
58
|
+
|
59
|
+
## Enforcement
|
60
|
+
|
61
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
62
|
+
reported to the community leaders responsible for enforcement at
|
63
|
+
[INSERT CONTACT METHOD].
|
64
|
+
All complaints will be reviewed and investigated promptly and fairly.
|
65
|
+
|
66
|
+
All community leaders are obligated to respect the privacy and security of the
|
67
|
+
reporter of any incident.
|
68
|
+
|
69
|
+
## Enforcement Guidelines
|
70
|
+
|
71
|
+
Community leaders will follow these Community Impact Guidelines in determining
|
72
|
+
the consequences for any action they deem in violation of this Code of Conduct:
|
73
|
+
|
74
|
+
### 1. Correction
|
75
|
+
|
76
|
+
**Community Impact**: Use of inappropriate language or other behavior deemed
|
77
|
+
unprofessional or unwelcome in the community.
|
78
|
+
|
79
|
+
**Consequence**: A private, written warning from community leaders, providing
|
80
|
+
clarity around the nature of the violation and an explanation of why the
|
81
|
+
behavior was inappropriate. A public apology may be requested.
|
82
|
+
|
83
|
+
### 2. Warning
|
84
|
+
|
85
|
+
**Community Impact**: A violation through a single incident or series of
|
86
|
+
actions.
|
87
|
+
|
88
|
+
**Consequence**: A warning with consequences for continued behavior. No
|
89
|
+
interaction with the people involved, including unsolicited interaction with
|
90
|
+
those enforcing the Code of Conduct, for a specified period of time. This
|
91
|
+
includes avoiding interactions in community spaces as well as external channels
|
92
|
+
like social media. Violating these terms may lead to a temporary or permanent
|
93
|
+
ban.
|
94
|
+
|
95
|
+
### 3. Temporary Ban
|
96
|
+
|
97
|
+
**Community Impact**: A serious violation of community standards, including
|
98
|
+
sustained inappropriate behavior.
|
99
|
+
|
100
|
+
**Consequence**: A temporary ban from any sort of interaction or public
|
101
|
+
communication with the community for a specified period of time. No public or
|
102
|
+
private interaction with the people involved, including unsolicited interaction
|
103
|
+
with those enforcing the Code of Conduct, is allowed during this period.
|
104
|
+
Violating these terms may lead to a permanent ban.
|
105
|
+
|
106
|
+
### 4. Permanent Ban
|
107
|
+
|
108
|
+
**Community Impact**: Demonstrating a pattern of violation of community
|
109
|
+
standards, including sustained inappropriate behavior, harassment of an
|
110
|
+
individual, or aggression toward or disparagement of classes of individuals.
|
111
|
+
|
112
|
+
**Consequence**: A permanent ban from any sort of public interaction within the
|
113
|
+
community.
|
114
|
+
|
115
|
+
## Attribution
|
116
|
+
|
117
|
+
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
118
|
+
version 2.1, available at
|
119
|
+
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
|
120
|
+
|
121
|
+
Community Impact Guidelines were inspired by
|
122
|
+
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
|
123
|
+
|
124
|
+
For answers to common questions about this code of conduct, see the FAQ at
|
125
|
+
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
|
126
|
+
[https://www.contributor-covenant.org/translations][translations].
|
127
|
+
|
128
|
+
[homepage]: https://www.contributor-covenant.org
|
129
|
+
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
|
130
|
+
[Mozilla CoC]: https://github.com/mozilla/diversity
|
131
|
+
[FAQ]: https://www.contributor-covenant.org/faq
|
132
|
+
[translations]: https://www.contributor-covenant.org/translations
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2025 jtvaldivia
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,256 @@
|
|
1
|
+
|
2
|
+
# ruby_test_student_grader
|
3
|
+
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
5
|
+
[](https://badge.fury.io/rb/ruby_test_student_grader)
|
6
|
+
|
7
|
+
|
8
|
+
A Ruby gem for generating academic test reports in HTML format. Initially designed for automated grading of university programming assignments, with flexibility to adapt to different testing frameworks.
|
9
|
+
|
10
|
+
**Current Status**: Early Development (Beta)
|
11
|
+
**Primary Use**: Automated evaluation of academic coding tasks
|
12
|
+
**Future Goal**: Modular system for diverse testing scenarios
|
13
|
+
|
14
|
+
---
|
15
|
+
|
16
|
+
## Installation
|
17
|
+
|
18
|
+
### Local Development
|
19
|
+
Add to your Rails Gemfile:
|
20
|
+
```ruby
|
21
|
+
gem 'ruby_test_student_grader', path: '/path/to/gem/directory'
|
22
|
+
```
|
23
|
+
Then run:
|
24
|
+
|
25
|
+
```bash
|
26
|
+
bundle install
|
27
|
+
```
|
28
|
+
From RubyGems (Once Published)
|
29
|
+
```bash
|
30
|
+
gem install ruby_test_student_grader
|
31
|
+
```
|
32
|
+
## Basic Usage
|
33
|
+
Configure in your test helper:
|
34
|
+
|
35
|
+
```bash
|
36
|
+
test/test_helper.rb
|
37
|
+
```
|
38
|
+
```ruby
|
39
|
+
|
40
|
+
require 'ruby_test_student_grader'
|
41
|
+
|
42
|
+
Minitest.after_run do
|
43
|
+
RubyTestStudentGrader::Reporter.new(
|
44
|
+
total_score, # Calculated score
|
45
|
+
max_score, # Maximum possible score
|
46
|
+
failed_tests, # Array of failed test names
|
47
|
+
automated_feedback # Generated feedback summary
|
48
|
+
).generate
|
49
|
+
end
|
50
|
+
```
|
51
|
+
Report will be generated at:
|
52
|
+
|
53
|
+
|
54
|
+
[](https://opensource.org/licenses/MIT)
|
55
|
+
[](https://badge.fury.io/rb/ruby_test_student_grader)
|
56
|
+
|
57
|
+
A Ruby gem for generating academic test reports in HTML format. Initially designed for automated grading of university programming assignments, with flexibility to adapt to different testing frameworks.
|
58
|
+
|
59
|
+
**Current Status**: Early Development (Beta)
|
60
|
+
**Primary Use**: Automated evaluation of academic coding tasks
|
61
|
+
**Future Goal**: Modular system for diverse testing scenarios
|
62
|
+
|
63
|
+
---
|
64
|
+
|
65
|
+
|
66
|
+
## Installation
|
67
|
+
|
68
|
+
|
69
|
+
### Local Development
|
70
|
+
|
71
|
+
Add to your Rails Gemfile:
|
72
|
+
|
73
|
+
```ruby
|
74
|
+
gem 'ruby_test_student_grader', path: '/path/to/gem/directory'
|
75
|
+
```
|
76
|
+
|
77
|
+
Then run:
|
78
|
+
|
79
|
+
```bash
|
80
|
+
bundle install
|
81
|
+
```
|
82
|
+
|
83
|
+
From RubyGems (Once Published):
|
84
|
+
|
85
|
+
```bash
|
86
|
+
gem install ruby_test_student_grader
|
87
|
+
```
|
88
|
+
|
89
|
+
|
90
|
+
## Basic Usage
|
91
|
+
|
92
|
+
Configure in your test helper:
|
93
|
+
|
94
|
+
`test/test_helper.rb`
|
95
|
+
|
96
|
+
```ruby
|
97
|
+
require 'ruby_test_student_grader'
|
98
|
+
|
99
|
+
Minitest.after_run do
|
100
|
+
RubyTestStudentGrader::Reporter.new(
|
101
|
+
total_score, # Calculated score
|
102
|
+
max_score, # Maximum possible score
|
103
|
+
failed_tests, # Array of failed test names
|
104
|
+
automated_feedback # Generated feedback summary
|
105
|
+
).generate
|
106
|
+
end
|
107
|
+
```
|
108
|
+
|
109
|
+
Report will be generated at:
|
110
|
+
|
111
|
+
`public/test_report.html`
|
112
|
+
|
113
|
+
## Evaluated Data
|
114
|
+
|
115
|
+
|
116
|
+
- Test results (pass/fail)
|
117
|
+
- Score calculations
|
118
|
+
- Execution metadata
|
119
|
+
- Custom feedback
|
120
|
+
- Detailed error traces
|
121
|
+
|
122
|
+
|
123
|
+
## Development Current Features
|
124
|
+
|
125
|
+
|
126
|
+
- [x] HTML Report Generation
|
127
|
+
- [x] Academic Grading System
|
128
|
+
- [x] Detailed Error Analysis
|
129
|
+
- [x] Customizable Templates
|
130
|
+
|
131
|
+
|
132
|
+
Planned Features
|
133
|
+
|
134
|
+
- [ ] PDF/CSV Export
|
135
|
+
- [ ] LMS Integration
|
136
|
+
- [ ] Code Similarity Detection
|
137
|
+
- [ ] AI-Powered Feedback
|
138
|
+
|
139
|
+
|
140
|
+
## Academic Configuration
|
141
|
+
|
142
|
+
```ruby
|
143
|
+
RubyTestStudentGrader.configure do |config|
|
144
|
+
config.grading_scale = {
|
145
|
+
passing: 4.0,
|
146
|
+
excellence: 5.5
|
147
|
+
}
|
148
|
+
config.rubric = {
|
149
|
+
code_quality: 30%,
|
150
|
+
coverage: 20%,
|
151
|
+
functionality: 50%
|
152
|
+
}
|
153
|
+
end
|
154
|
+
```
|
155
|
+
|
156
|
+
|
157
|
+
## Architecture
|
158
|
+
|
159
|
+
```bash
|
160
|
+
lib/
|
161
|
+
├── ruby_test_student_grader/
|
162
|
+
│ ├── reporter.rb # Core logic
|
163
|
+
│ ├── template.html.erb # HTML template
|
164
|
+
├── ruby_test_student_grader.rb # Main module
|
165
|
+
```
|
166
|
+
|
167
|
+
|
168
|
+
## Contributing
|
169
|
+
|
170
|
+
We welcome contributions for:
|
171
|
+
|
172
|
+
- New report formats
|
173
|
+
- Additional framework support
|
174
|
+
- Code analysis improvements
|
175
|
+
- AI integration modules
|
176
|
+
|
177
|
+
|
178
|
+
## License
|
179
|
+
|
180
|
+
MIT License - See LICENSE. Requires attribution in academic materials using this software.
|
181
|
+
|
182
|
+
Educational assessment tool - Adaptable for diverse pedagogical needs.
|
183
|
+
|
184
|
+
**Note**: This project is in very early stage and could have major bugs
|
185
|
+
|
186
|
+
## Try the new features
|
187
|
+
|
188
|
+
See `README_PROBAR.md` for a step-by-step guide. Quick summary:
|
189
|
+
|
190
|
+
```ruby
|
191
|
+
# HTML and CSV
|
192
|
+
reporter = RubyTestStudentGrader::Reporter.new(5.2, 6.0, ["test_model"], "Good job", course_name: "IIC2143")
|
193
|
+
reporter.generate
|
194
|
+
reporter.generate_csv("public/test_report.csv")
|
195
|
+
|
196
|
+
# PDF (optional, requires prawn)
|
197
|
+
reporter.generate_pdf("public/test_report.pdf")
|
198
|
+
|
199
|
+
# Multiple students
|
200
|
+
students = [ { id: 1, name: "Alice", total_score: 5.2, max_score: 6.0, failed_tests: ["test_model"], feedback: "OK" } ]
|
201
|
+
RubyTestStudentGrader::ClassReporter.new(students, course_name: "IIC2143", output_dir: "public/reports").generate_all
|
202
|
+
|
203
|
+
# Similarity
|
204
|
+
RubyTestStudentGrader::Similarity.jaccard("def a;1;end", "def b;1;end", k: 5)
|
205
|
+
```
|
206
|
+
|
207
|
+
## Demo script
|
208
|
+
|
209
|
+
Use the demo script to verify everything end-to-end.
|
210
|
+
|
211
|
+
- Path: `scripts/demo.rb`
|
212
|
+
- It will:
|
213
|
+
- Generate HTML and CSV in `public/`
|
214
|
+
- Try to generate PDF (if `prawn` is installed)
|
215
|
+
- Create per-student reports and an index under `public/reports/`
|
216
|
+
- Print a code similarity score
|
217
|
+
|
218
|
+
Run:
|
219
|
+
|
220
|
+
```bash
|
221
|
+
bundle exec ruby scripts/demo.rb
|
222
|
+
```
|
223
|
+
|
224
|
+
Optional (for PDF):
|
225
|
+
|
226
|
+
```bash
|
227
|
+
bundle add prawn
|
228
|
+
```
|
229
|
+
|
230
|
+
Tip: set a course name for the report via env var (optional):
|
231
|
+
|
232
|
+
```bash
|
233
|
+
export COURSE_NAME="IIC2143"
|
234
|
+
```
|
235
|
+
|
236
|
+
## Extras
|
237
|
+
|
238
|
+
- CSV export: `RubyTestStudentGrader::Reporter#generate_csv("public/test_report.csv")`
|
239
|
+
- PDF export (optional, requires prawn): `RubyTestStudentGrader::Reporter#generate_pdf("public/test_report.pdf")`
|
240
|
+
- Course name in HTML/CSV/PDF: pass `course_name: "IIC2143"` to the constructor or set `COURSE_NAME` in your environment.
|
241
|
+
|
242
|
+
### Reports for multiple students
|
243
|
+
|
244
|
+
```ruby
|
245
|
+
students = [
|
246
|
+
{ id: 1, name: "Alice", total_score: 5.2, max_score: 6.0, failed_tests: ["test_model"], feedback: "Good job" },
|
247
|
+
{ id: 2, name: "Bob", total_score: 3.9, max_score: 6.0, failed_tests: ["test_controller"], feedback: "Check controllers" }
|
248
|
+
]
|
249
|
+
RubyTestStudentGrader::ClassReporter.new(students, course_name: "IIC2143", output_dir: "public/reports").generate_all
|
250
|
+
```
|
251
|
+
|
252
|
+
### Basic similarity detection
|
253
|
+
|
254
|
+
```ruby
|
255
|
+
RubyTestStudentGrader::Similarity.jaccard(code_a, code_b, k: 5) # => 0.0..1.0
|
256
|
+
```
|
data/README_PROBAR.md
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
# Try the new features of ruby_test_student_grader
|
2
|
+
|
3
|
+
This document helps you quickly try the new features: CSV/PDF export, code similarity, class (multi-student) reports, and course name in the HTML report.
|
4
|
+
|
5
|
+
Requirements:
|
6
|
+
|
7
|
+
- Ruby >= 3.2
|
8
|
+
- Bundler
|
9
|
+
- (Optional) PDF: `prawn` gem
|
10
|
+
|
11
|
+
## 1) Start the gem console
|
12
|
+
|
13
|
+
```bash
|
14
|
+
bin/console
|
15
|
+
```
|
16
|
+
|
17
|
+
Inside the console, you can run the examples below. If you prefer, copy the snippets into a `.rb` script and run with `bundle exec ruby your_script.rb`.
|
18
|
+
|
19
|
+
## 2) Single report (HTML/CSV)
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
require "ruby_test_student_grader"
|
23
|
+
|
24
|
+
reporter = RubyTestStudentGrader::Reporter.new(
|
25
|
+
5.2, # total_score
|
26
|
+
6.0, # max_score
|
27
|
+
["test_model"], # failed_tests
|
28
|
+
"Good job", # feedback
|
29
|
+
course_name: "IIC2143"
|
30
|
+
)
|
31
|
+
|
32
|
+
reporter.generate # => test_report.html by default (or the path you pass)
|
33
|
+
reporter.generate_csv("public/test_report.csv")
|
34
|
+
```
|
35
|
+
|
36
|
+
By default, the HTML uses the course name passed via `course_name` or, if omitted, the `COURSE_NAME` environment variable.
|
37
|
+
|
38
|
+
## 3) PDF (optional)
|
39
|
+
|
40
|
+
Install `prawn` if you don't have it:
|
41
|
+
|
42
|
+
```bash
|
43
|
+
bundle add prawn
|
44
|
+
```
|
45
|
+
|
46
|
+
Then in Ruby:
|
47
|
+
|
48
|
+
```ruby
|
49
|
+
reporter.generate_pdf("public/test_report.pdf") # generates a PDF if prawn is installed
|
50
|
+
```
|
51
|
+
|
52
|
+
If `prawn` isn't available, the call will show a warning and return `nil`.
|
53
|
+
|
54
|
+
## 4) Class reports (index + per-student files)
|
55
|
+
|
56
|
+
```ruby
|
57
|
+
students = [
|
58
|
+
{ id: 1, name: "Alice", total_score: 5.2, max_score: 6.0, failed_tests: ["test_model"], feedback: "Good job" },
|
59
|
+
{ id: 2, name: "Bob", total_score: 3.9, max_score: 6.0, failed_tests: ["test_controller"], feedback: "Check controllers" }
|
60
|
+
]
|
61
|
+
|
62
|
+
RubyTestStudentGrader::ClassReporter
|
63
|
+
.new(students, course_name: "IIC2143", output_dir: "public/reports")
|
64
|
+
.generate_all
|
65
|
+
# Creates public/reports/index.html and one .html and .csv per student
|
66
|
+
```
|
67
|
+
|
68
|
+
## 5) Basic code similarity (Jaccard)
|
69
|
+
|
70
|
+
```ruby
|
71
|
+
code_a = "def sum(a,b); a+b; end"
|
72
|
+
code_b = "def add(x,y); x + y; end"
|
73
|
+
|
74
|
+
RubyTestStudentGrader::Similarity.jaccard(code_a, code_b, k: 5)
|
75
|
+
# => 0.0..1.0 (higher means more similar)
|
76
|
+
```
|
77
|
+
|
78
|
+
## 6) Environment variables
|
79
|
+
|
80
|
+
You can set the course via an environment variable to avoid passing `course_name` each time:
|
81
|
+
|
82
|
+
```bash
|
83
|
+
export COURSE_NAME="IIC2143"
|
84
|
+
```
|
85
|
+
|
86
|
+
## 7) Output locations
|
87
|
+
|
88
|
+
- Default HTML: `test_report.html` (or the path you give)
|
89
|
+
- Default CSV: `test_report.csv` (or the path you give)
|
90
|
+
- Default PDF: `test_report.pdf` (if `prawn` is available)
|
91
|
+
- Class reports: `public/reports/` (configurable with `output_dir`)
|
92
|
+
|
93
|
+
## 8) Common issues
|
94
|
+
|
95
|
+
- PDF not generated: install `prawn` with `bundle add prawn`.
|
96
|
+
- Permissions/paths: create the `public/` or `public/reports/` directory if it doesn't exist.
|
97
|
+
- 0% score: ensure `max_score` is > 0.
|
data/Rakefile
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RubyTestStudentGrader
|
4
|
+
# Genera reportes para múltiples estudiantes y un índice HTML simple.
|
5
|
+
class ClassReporter
|
6
|
+
# students: Array<Hash> con claves:
|
7
|
+
# :id, :name, :total_score, :max_score, :failed_tests (Array), :feedback (String)
|
8
|
+
# opts: { course_name?: String, output_dir?: String }
|
9
|
+
def initialize(students, **opts)
|
10
|
+
@students = students
|
11
|
+
@course_name = opts[:course_name] || ENV["COURSE_NAME"] || "Curso"
|
12
|
+
@output_dir = opts[:output_dir] || "reports"
|
13
|
+
end
|
14
|
+
|
15
|
+
def generate_all
|
16
|
+
Dir.mkdir(@output_dir) unless Dir.exist?(@output_dir)
|
17
|
+
|
18
|
+
index_rows = []
|
19
|
+
@students.each do |s|
|
20
|
+
base = safe_slug("#{s[:id]}-#{s[:name]}")
|
21
|
+
html_path = File.join(@output_dir, "#{base}.html")
|
22
|
+
csv_path = File.join(@output_dir, "#{base}.csv")
|
23
|
+
|
24
|
+
reporter = Reporter.new(
|
25
|
+
s[:total_score],
|
26
|
+
s[:max_score],
|
27
|
+
s[:failed_tests] || [],
|
28
|
+
s[:feedback].to_s,
|
29
|
+
course_name: @course_name
|
30
|
+
)
|
31
|
+
reporter.generate_html(html_path)
|
32
|
+
reporter.generate_csv(csv_path)
|
33
|
+
|
34
|
+
percentage = s[:max_score].to_f.zero? ? 0.0 : (s[:total_score].to_f / s[:max_score] * 100)
|
35
|
+
index_rows << {
|
36
|
+
id: s[:id], name: s[:name], html: File.basename(html_path), csv: File.basename(csv_path), percentage: percentage.round(2)
|
37
|
+
}
|
38
|
+
end
|
39
|
+
|
40
|
+
index_file = File.join(@output_dir, "index.html")
|
41
|
+
File.write(index_file, render_index(index_rows))
|
42
|
+
puts "✅ Class index generated at: #{index_file}"
|
43
|
+
index_file
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def safe_slug(str)
|
49
|
+
str.to_s.downcase.gsub(/[^a-z0-9\-]+/, "-").gsub(/-+/, "-").gsub(/^-|-$|\A\z/, "")
|
50
|
+
end
|
51
|
+
|
52
|
+
def render_index(rows)
|
53
|
+
<<~HTML
|
54
|
+
<!DOCTYPE html>
|
55
|
+
<html>
|
56
|
+
<head>
|
57
|
+
<meta charset="utf-8" />
|
58
|
+
<title>Reporte de Clase - #{@course_name}</title>
|
59
|
+
<style>
|
60
|
+
body { font-family: Arial, sans-serif; margin: 2rem; }
|
61
|
+
table { border-collapse: collapse; width: 100%; }
|
62
|
+
th, td { border: 1px solid #ddd; padding: 8px; }
|
63
|
+
th { background: #f4f4f4; }
|
64
|
+
</style>
|
65
|
+
</head>
|
66
|
+
<body>
|
67
|
+
<h1>Reporte de Clase - #{@course_name}</h1>
|
68
|
+
<table>
|
69
|
+
<thead>
|
70
|
+
<tr>
|
71
|
+
<th>ID</th>
|
72
|
+
<th>Nombre</th>
|
73
|
+
<th>Porcentaje</th>
|
74
|
+
<th>HTML</th>
|
75
|
+
<th>CSV</th>
|
76
|
+
</tr>
|
77
|
+
</thead>
|
78
|
+
<tbody>
|
79
|
+
#{rows.map { |r| "<tr><td>#{r[:id]}</td><td>#{r[:name]}</td><td>#{r[:percentage]}%</td><td><a href=\"#{r[:html]}\">Ver</a></td><td><a href=\"#{r[:csv]}\">Descargar</a></td></tr>" }.join}
|
80
|
+
</tbody>
|
81
|
+
</table>
|
82
|
+
</body>
|
83
|
+
</html>
|
84
|
+
HTML
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
# lib/ruby_test_student_grader/reporter.rb
|
2
|
+
require "erb"
|
3
|
+
require "csv"
|
4
|
+
begin
|
5
|
+
require "prawn"
|
6
|
+
rescue LoadError
|
7
|
+
# Prawn es opcional en entorno de desarrollo si no se instaló via gemspec
|
8
|
+
end
|
9
|
+
|
10
|
+
|
11
|
+
module RubyTestStudentGrader
|
12
|
+
class Reporter
|
13
|
+
TEMPLATE = File.join(File.dirname(__FILE__), "template.html.erb")
|
14
|
+
|
15
|
+
# total_score: Numeric
|
16
|
+
# max_score: Numeric
|
17
|
+
# failed_tests: Array<String>
|
18
|
+
# feedback: String
|
19
|
+
# opts: { course_name?: String }
|
20
|
+
def initialize(total_score, max_score, failed_tests, feedback, **opts)
|
21
|
+
@total_score = total_score
|
22
|
+
@max_score = max_score
|
23
|
+
@failed_tests = failed_tests || []
|
24
|
+
@feedback = feedback.to_s
|
25
|
+
@timestamp = Time.now.strftime("%Y-%m-%d %H:%M:%S")
|
26
|
+
@course_name = opts[:course_name] || ENV["COURSE_NAME"] || "Curso"
|
27
|
+
@feedback_class = feedback_class
|
28
|
+
end
|
29
|
+
|
30
|
+
# Conserva compatibilidad: genera HTML por defecto
|
31
|
+
def generate(output_path = "test_report.html")
|
32
|
+
generate_html(output_path)
|
33
|
+
end
|
34
|
+
|
35
|
+
def generate_html(output_path = "test_report.html")
|
36
|
+
template = File.read(TEMPLATE)
|
37
|
+
html = ERB.new(template).result(binding)
|
38
|
+
File.write(output_path, html)
|
39
|
+
puts "✅ HTML report generated at: #{output_path}"
|
40
|
+
output_path
|
41
|
+
end
|
42
|
+
|
43
|
+
def generate_csv(output_path = "test_report.csv")
|
44
|
+
data = {
|
45
|
+
course: @course_name,
|
46
|
+
total_score: @total_score,
|
47
|
+
max_score: @max_score,
|
48
|
+
percentage: (@max_score.to_f.zero? ? 0.0 : (@total_score.to_f / @max_score * 100)).round(2),
|
49
|
+
failed_tests_count: @failed_tests.size,
|
50
|
+
failed_tests: @failed_tests.join("; "),
|
51
|
+
feedback: @feedback,
|
52
|
+
timestamp: @timestamp
|
53
|
+
}
|
54
|
+
|
55
|
+
CSV.open(output_path, "w") do |csv|
|
56
|
+
csv << data.keys
|
57
|
+
csv << data.values
|
58
|
+
end
|
59
|
+
puts "✅ CSV report generated at: #{output_path}"
|
60
|
+
output_path
|
61
|
+
end
|
62
|
+
|
63
|
+
def generate_pdf(output_path = "test_report.pdf")
|
64
|
+
unless defined?(Prawn)
|
65
|
+
warn "Prawn is not available. Make sure the dependency is installed."
|
66
|
+
return nil
|
67
|
+
end
|
68
|
+
|
69
|
+
Prawn::Document.generate(output_path) do |pdf|
|
70
|
+
pdf.text "Reporte de Tests - #{@course_name}", size: 22, style: :bold, align: :center
|
71
|
+
pdf.move_down 20
|
72
|
+
pdf.text "Puntaje: #{@total_score} / #{@max_score}", size: 16
|
73
|
+
percentage = @max_score.to_f.zero? ? 0.0 : (@total_score.to_f / @max_score * 100)
|
74
|
+
pdf.text "Porcentaje: #{percentage.round(2)}%", size: 12
|
75
|
+
pdf.move_down 10
|
76
|
+
pdf.text "Feedback:", style: :bold
|
77
|
+
pdf.text @feedback.to_s
|
78
|
+
unless @failed_tests.empty?
|
79
|
+
pdf.move_down 10
|
80
|
+
pdf.text "Tests Fallidos:", style: :bold
|
81
|
+
@failed_tests.each { |t| pdf.text "• #{t}" }
|
82
|
+
end
|
83
|
+
pdf.move_down 20
|
84
|
+
pdf.text "Generado el: #{@timestamp}", size: 10, align: :right
|
85
|
+
end
|
86
|
+
puts "✅ PDF report generated at: #{output_path}"
|
87
|
+
output_path
|
88
|
+
end
|
89
|
+
|
90
|
+
def feedback_class
|
91
|
+
case @total_score
|
92
|
+
when 5.5..6.0 then "success"
|
93
|
+
when 4.0..5.4 then "warning"
|
94
|
+
else "danger"
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RubyTestStudentGrader
|
4
|
+
module Similarity
|
5
|
+
module_function
|
6
|
+
|
7
|
+
# Calcula similitud de Jaccard sobre shingles de tokens simples.
|
8
|
+
# text_a, text_b: String
|
9
|
+
# k: tamaño de shingle (por defecto 5 tokens)
|
10
|
+
def jaccard(text_a, text_b, k: 5)
|
11
|
+
a = shingles(tokenize(text_a.to_s), k)
|
12
|
+
b = shingles(tokenize(text_b.to_s), k)
|
13
|
+
return 1.0 if a.empty? && b.empty?
|
14
|
+
inter = (a & b).size.to_f
|
15
|
+
union = (a | b).size.to_f
|
16
|
+
return 0.0 if union.zero?
|
17
|
+
(inter / union).round(4)
|
18
|
+
end
|
19
|
+
|
20
|
+
def tokenize(text)
|
21
|
+
text.downcase.gsub(/[^a-z0-9_]+/, " ").split
|
22
|
+
end
|
23
|
+
|
24
|
+
def shingles(tokens, k)
|
25
|
+
return [] if tokens.size < k
|
26
|
+
(0..tokens.size - k).map { |i| tokens[i, k].join(" ") }.uniq
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
<!-- lib/ruby_test_student_grader/template.html.erb -->
|
2
|
+
<!DOCTYPE html>
|
3
|
+
<html>
|
4
|
+
<head>
|
5
|
+
<title>Test Report</title>
|
6
|
+
<style>
|
7
|
+
:root { --color-primary: #2c3e50; --color-success: #27ae60; --color-danger: #e74c3c; }
|
8
|
+
body { font-family: 'Segoe UI', sans-serif; margin: 2rem; }
|
9
|
+
.header { text-align: center; margin-bottom: 2rem; }
|
10
|
+
.score { font-size: 2.5em; font-weight: bold; margin: 1rem 0; }
|
11
|
+
.feedback { padding: 1rem; border-radius: 8px; margin: 1rem 0; }
|
12
|
+
.success { background-color: #e8f5e9; color: var(--color-success); }
|
13
|
+
.warning { background-color: #fff3e0; color: #f39c12; }
|
14
|
+
.danger { background-color: #ffebee; color: var(--color-danger); }
|
15
|
+
.failed-tests { margin-top: 2rem; }
|
16
|
+
.test-list { list-style-type: none; padding: 0; }
|
17
|
+
.test-item { padding: 0.5rem; margin: 0.3rem 0; border-left: 4px solid var(--color-danger); }
|
18
|
+
.timestamp { color: #7f8c8d; font-size: 0.9em; text-align: center; margin-top: 2rem; }
|
19
|
+
</style>
|
20
|
+
</head>
|
21
|
+
<body>
|
22
|
+
<div class="header">
|
23
|
+
<h1>📊 Test Report</h1>
|
24
|
+
<h3><%= @course_name %></h3>
|
25
|
+
<div class="score">
|
26
|
+
<%= @total_score.round(1) %>/<%= @max_score %>
|
27
|
+
</div>
|
28
|
+
<div class="feedback <%= feedback_class %>">
|
29
|
+
<%= @feedback %>
|
30
|
+
</div>
|
31
|
+
</div>
|
32
|
+
|
33
|
+
<% if @failed_tests.any? %>
|
34
|
+
<div class="failed-tests">
|
35
|
+
<h3>🔴 Failed tests:</h3>
|
36
|
+
<ul class="test-list">
|
37
|
+
<% @failed_tests.each do |test| %>
|
38
|
+
<li class="test-item"><%= test %></li>
|
39
|
+
<% end %>
|
40
|
+
</ul>
|
41
|
+
</div>
|
42
|
+
<% end %>
|
43
|
+
|
44
|
+
<div class="timestamp">
|
45
|
+
Generated at: <%= @timestamp %>
|
46
|
+
</div>
|
47
|
+
</body>
|
48
|
+
</html>
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "ruby_test_student_grader/version"
|
4
|
+
require_relative "ruby_test_student_grader/reporter"
|
5
|
+
require_relative "ruby_test_student_grader/class_reporter"
|
6
|
+
require_relative "ruby_test_student_grader/similarity"
|
7
|
+
|
8
|
+
module RubyTestStudentGrader
|
9
|
+
class Error < StandardError; end
|
10
|
+
# Your code goes here...
|
11
|
+
end
|
metadata
ADDED
@@ -0,0 +1,134 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ruby_test_student_grader
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- jtvaldivia
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2025-08-10 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: csv
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '3.2'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '3.2'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '13.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '13.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: minitest
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '5.16'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '5.16'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rubocop
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.21'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1.21'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: prawn
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '2.5'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '2.5'
|
83
|
+
description: Ruby gem to generate automated test reports for academic assignments
|
84
|
+
with HTML templates, CSV export, optional PDF (via prawn), class-wide reporting,
|
85
|
+
and basic Jaccard code similarity.
|
86
|
+
email:
|
87
|
+
- josevaldivia9@gmail.com
|
88
|
+
executables: []
|
89
|
+
extensions: []
|
90
|
+
extra_rdoc_files: []
|
91
|
+
files:
|
92
|
+
- CHANGELOG.md
|
93
|
+
- CODE_OF_CONDUCT.md
|
94
|
+
- LICENSE.txt
|
95
|
+
- README.md
|
96
|
+
- README_PROBAR.md
|
97
|
+
- Rakefile
|
98
|
+
- lib/ruby_test_student_grader.rb
|
99
|
+
- lib/ruby_test_student_grader/class_reporter.rb
|
100
|
+
- lib/ruby_test_student_grader/reporter.rb
|
101
|
+
- lib/ruby_test_student_grader/similarity.rb
|
102
|
+
- lib/ruby_test_student_grader/template.html.erb
|
103
|
+
- lib/ruby_test_student_grader/version.rb
|
104
|
+
- sig/ruby_test_student_grader.rbs
|
105
|
+
homepage: https://github.com/jtvaldivia/ruby-grader
|
106
|
+
licenses:
|
107
|
+
- MIT
|
108
|
+
metadata:
|
109
|
+
allowed_push_host: https://rubygems.org
|
110
|
+
homepage_uri: https://github.com/jtvaldivia/ruby-grader
|
111
|
+
source_code_uri: https://github.com/jtvaldivia/ruby-grader
|
112
|
+
changelog_uri: https://github.com/jtvaldivia/ruby-grader/blob/master/CHANGELOG.md
|
113
|
+
rubygems_mfa_required: 'true'
|
114
|
+
post_install_message:
|
115
|
+
rdoc_options: []
|
116
|
+
require_paths:
|
117
|
+
- lib
|
118
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
119
|
+
requirements:
|
120
|
+
- - ">="
|
121
|
+
- !ruby/object:Gem::Version
|
122
|
+
version: 3.2.0
|
123
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
124
|
+
requirements:
|
125
|
+
- - ">="
|
126
|
+
- !ruby/object:Gem::Version
|
127
|
+
version: '0'
|
128
|
+
requirements: []
|
129
|
+
rubygems_version: 3.5.22
|
130
|
+
signing_key:
|
131
|
+
specification_version: 4
|
132
|
+
summary: Generate student test reports (HTML/CSV, optional PDF), class index, and
|
133
|
+
basic code similarity.
|
134
|
+
test_files: []
|