mumukit-assistant 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/lib/locales/narrator.en.yml +14 -0
- data/lib/locales/narrator.es.yml +14 -0
- data/lib/locales/narrator.pt.yml +14 -0
- data/lib/mumukit/assistant.rb +36 -0
- data/lib/mumukit/assistant/message.rb +45 -0
- data/lib/mumukit/assistant/narrator.rb +67 -0
- data/lib/mumukit/assistant/rule.rb +42 -0
- data/lib/mumukit/assistant/rule/base.rb +10 -0
- data/lib/mumukit/assistant/rule/content_empty.rb +5 -0
- data/lib/mumukit/assistant/rule/error_contains.rb +10 -0
- data/lib/mumukit/assistant/rule/only_these_tests_failed.rb +5 -0
- data/lib/mumukit/assistant/rule/submission_errored.rb +5 -0
- data/lib/mumukit/assistant/rule/submission_failed.rb +5 -0
- data/lib/mumukit/assistant/rule/submission_passed_with_warnings.rb +5 -0
- data/lib/mumukit/assistant/rule/these_expectations_failed.rb +22 -0
- data/lib/mumukit/assistant/rule/these_tests_failed.rb +25 -0
- data/lib/mumukit/assistant/version.rb +5 -0
- metadata +119 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 051724369ddfa47a9d19d3f31775a46428ef4a7b
|
4
|
+
data.tar.gz: 231892340a2a47b23eed47c6fbc3cae82789e446
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 64038b701177a0fa290ee8391bad4d9383290444807258f5cffc26767e03c9e1f08cd95aae98a4b6ae68bea74ecfb2a2a9dd2df11445d5c58783505e72b238dc
|
7
|
+
data.tar.gz: 08d3bf283f06dc28b8ab71f6059eb73e9529f4b1efb5bca031625a2edeb21ebd5fda82804ba2f235fa64f2bf2c554755116085991914e6e5b516a00570587846
|
@@ -0,0 +1,14 @@
|
|
1
|
+
en:
|
2
|
+
narrator:
|
3
|
+
retry_0: Let's try again!
|
4
|
+
retry_1: Keep on!
|
5
|
+
retry_2: Let's fix it!
|
6
|
+
introduction_0: "Oops, it didn't work :frowning:."
|
7
|
+
introduction_1: "Oops, it didn't work :frowning:."
|
8
|
+
introduction_2: "Oops, it didn't work :frowning:."
|
9
|
+
middle_0: Also, %{tip}.
|
10
|
+
middle_1: Also, %{tip}.
|
11
|
+
middle_2: Also, %{tip}.
|
12
|
+
ending_0: Finally, %{tip}.
|
13
|
+
ending_1: Finally, %{tip}.
|
14
|
+
ending_2: Finally, %{tip}.
|
@@ -0,0 +1,14 @@
|
|
1
|
+
es:
|
2
|
+
narrator:
|
3
|
+
retry_0: ¡Intentemos de nuevo!
|
4
|
+
retry_1: 'Pero a no desesperar, intentemos otra vez :muscle:'
|
5
|
+
retry_2: ¡Corrijamos el problema!
|
6
|
+
introduction_0: 'Parece que algo no funcionó :see_no_evil:.'
|
7
|
+
introduction_1: 'Eh, ¿qué pasó acá :frowning:?'
|
8
|
+
introduction_2: 'Parece que algo no anduvo bien :sweat_smile:'
|
9
|
+
middle_0: "Además, %{tip}."
|
10
|
+
middle_1: "También %{tip}."
|
11
|
+
middle_2: "Por otro lado, %{tip}."
|
12
|
+
ending_0: "Por último, %{tip}."
|
13
|
+
ending_1: "Ah, algo más: %{tip}."
|
14
|
+
ending_2: "Y para cerrar, %{tip}."
|
@@ -0,0 +1,14 @@
|
|
1
|
+
pt:
|
2
|
+
narrator:
|
3
|
+
retry_0: Vamos tentar de novo!
|
4
|
+
retry_1: 'Mas não se desespere, vamos tentar novamente :muscle:'
|
5
|
+
retry_2: Corrigimos o problema!
|
6
|
+
introduction_0: 'Parece que algo não funcionou :see_no_evil:.'
|
7
|
+
introduction_1: 'Ei, o que aconteceu aqui :frowning:?'
|
8
|
+
introduction_2: 'Parece que algo não correu bem :sweat_smile:'
|
9
|
+
middle_0: "Além disso, %{tip}."
|
10
|
+
middle_1: "Também %{tip}."
|
11
|
+
middle_2: "Por outro lado, %{tip}."
|
12
|
+
ending_0: "Finalmente, %{tip}."
|
13
|
+
ending_1: "Oh, algo mais: %{tip}."
|
14
|
+
ending_2: "E para fechar, %{tip}."
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'mumukit/core'
|
2
|
+
require 'i18n'
|
3
|
+
|
4
|
+
I18n.load_translations_path File.join(__dir__, '..', 'locales', '*.yml')
|
5
|
+
|
6
|
+
module Mumukit
|
7
|
+
# An assistant is used to generate dynamic feedback
|
8
|
+
# over a student's submission, based on rules.
|
9
|
+
#
|
10
|
+
# This feedback is composed of a list of markdown messages called _tips_,
|
11
|
+
# and the whole processes of creating this feedback is called _assistance_.
|
12
|
+
class Assistant
|
13
|
+
attr_accessor :rules
|
14
|
+
|
15
|
+
def initialize(rules)
|
16
|
+
@rules = rules
|
17
|
+
end
|
18
|
+
|
19
|
+
# Provides tips for the student for the given submission,
|
20
|
+
# based on the `rules`.
|
21
|
+
def assist_with(submission)
|
22
|
+
@rules
|
23
|
+
.select { |it| it.matches?(submission) }
|
24
|
+
.map { |it| it.message_for(submission.attemps_count) }
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.parse(rules)
|
28
|
+
new rules.map { |it| Mumukit::Assistant::Rule.parse it }
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
require_relative './assistant/rule'
|
34
|
+
require_relative './assistant/message'
|
35
|
+
require_relative './assistant/narrator'
|
36
|
+
require_relative './assistant/version'
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Mumukit::Assistant::Message
|
2
|
+
# Fixed messages are independent of the number
|
3
|
+
# of attemps
|
4
|
+
class Fixed
|
5
|
+
def initialize(text)
|
6
|
+
@text = text
|
7
|
+
end
|
8
|
+
|
9
|
+
def call(_)
|
10
|
+
@text
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
# Progressive messages depend on the number of attemps
|
15
|
+
# They work with exactly two or three messages:
|
16
|
+
#
|
17
|
+
# * the first message will be displayed in the first three attemps
|
18
|
+
# * the second message will be displayed in the fourth, fifth and sixth attemps
|
19
|
+
# * the third message will be displayed starting at the seventh attemp
|
20
|
+
#
|
21
|
+
class Progressive
|
22
|
+
def initialize(alternatives)
|
23
|
+
raise 'You need two or three alternatives' unless alternatives.size.between?(2, 3)
|
24
|
+
@alternatives = alternatives
|
25
|
+
end
|
26
|
+
|
27
|
+
def call(attemps_count)
|
28
|
+
case attemps_count
|
29
|
+
when (1..3) then @alternatives.first
|
30
|
+
when (4..6) then @alternatives.second
|
31
|
+
else @alternatives.last
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.parse(text_or_alternatives)
|
37
|
+
if text_or_alternatives.is_a? String
|
38
|
+
Fixed.new text_or_alternatives
|
39
|
+
elsif text_or_alternatives.is_a? Array
|
40
|
+
Progressive.new text_or_alternatives
|
41
|
+
else
|
42
|
+
raise "Wrong message format #{text_or_alternatives}"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# A narrator can turn tips - generated by `Mumukit::Assistant` -
|
2
|
+
# into a humanized text with the same information but
|
3
|
+
# a bit more friendly. This text is called _explanation_.
|
4
|
+
#
|
5
|
+
# The narrator uses some internationalized random phrases, and it does provide
|
6
|
+
# a seed as a construction argument to allow its testing.
|
7
|
+
class Mumukit::Assistant::Narrator
|
8
|
+
def initialize(seed)
|
9
|
+
@seed = seed
|
10
|
+
end
|
11
|
+
|
12
|
+
# Generated a markdown explanation using the seeded phrases. Uses `I18n` to get
|
13
|
+
# the appropriate locale.
|
14
|
+
def compose_explanation(tips)
|
15
|
+
"#{explanation_introduction_phrase}\n\n#{explanation_paragraphs(tips).join("\n\n")}\n\n#{retry_phrase}\n"
|
16
|
+
end
|
17
|
+
|
18
|
+
# Generates an html explantion.
|
19
|
+
# See `compose_explanation`
|
20
|
+
def compose_explanation_html(tips)
|
21
|
+
Mumukit::ContentType::Markdown.to_html compose_explanation(tips)
|
22
|
+
end
|
23
|
+
|
24
|
+
def retry_phrase
|
25
|
+
t :retry
|
26
|
+
end
|
27
|
+
|
28
|
+
def explanation_introduction_phrase
|
29
|
+
t :introduction
|
30
|
+
end
|
31
|
+
|
32
|
+
def explanation_paragraphs(tips)
|
33
|
+
tips.take(3).zip([:opening, :middle, :ending]).map do |tip, selector|
|
34
|
+
send "explanation_#{selector}_paragraph", tip
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def explanation_opening_paragraph(tip)
|
39
|
+
"#{tip.capitalize}."
|
40
|
+
end
|
41
|
+
|
42
|
+
def explanation_middle_paragraph(tip)
|
43
|
+
t :middle, tip: tip
|
44
|
+
end
|
45
|
+
|
46
|
+
def explanation_ending_paragraph(tip)
|
47
|
+
t :ending, tip: tip
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.random
|
51
|
+
new seed(*5.times.map { random_index })
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.seed(r, i, o, m, e)
|
55
|
+
{ retry: r, introduction: i, opening: o, middle: m, ending: e }
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def t(key, args={})
|
61
|
+
I18n.t "narrator.#{key}_#{@seed[key]}", args
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.random_index
|
65
|
+
(0..2).to_a.sample
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Mumukit::Assistant::Rule
|
2
|
+
def self.parse(hash)
|
3
|
+
message = Mumukit::Assistant::Message.parse hash[:then]
|
4
|
+
w = hash[:when]
|
5
|
+
if w.is_a? Hash
|
6
|
+
parse_complex_when w.first, message
|
7
|
+
else
|
8
|
+
parse_simple_when w, message
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.parse_simple_when(w, message)
|
13
|
+
case w
|
14
|
+
when :content_empty then Mumukit::Assistant::Rule::ContentEmpty.new(message)
|
15
|
+
when :submission_errored then Mumukit::Assistant::Rule::SubmissionErrored.new(message)
|
16
|
+
when :submission_failed then Mumukit::Assistant::Rule::SubmissionFailed.new(message)
|
17
|
+
when :submission_passed_with_warnings then Mumukit::Assistant::Rule::SubmissionPassedWithWarnings.new(message)
|
18
|
+
else raise "Unsupported rule #{w}"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.parse_complex_when(w, message)
|
23
|
+
condition, value = *w
|
24
|
+
case condition
|
25
|
+
when :error_contains then Mumukit::Assistant::Rule::ErrorContains.new(message, value)
|
26
|
+
when :these_tests_failed then Mumukit::Assistant::Rule::TheseTestsFailed.new(message, value)
|
27
|
+
when :only_these_tests_failed then Mumukit::Assistant::Rule::OnlyTheseTestsFailed.new(message, value)
|
28
|
+
when :these_expectations_failed then Mumukit::Assistant::Rule::TheseExpectationsFailed.new(message, value)
|
29
|
+
else raise "Unsupported rule #{condition}"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
require_relative 'rule/base.rb'
|
35
|
+
require_relative 'rule/content_empty.rb'
|
36
|
+
require_relative 'rule/submission_failed.rb'
|
37
|
+
require_relative 'rule/these_tests_failed.rb'
|
38
|
+
require_relative 'rule/only_these_tests_failed.rb'
|
39
|
+
require_relative 'rule/submission_passed_with_warnings.rb'
|
40
|
+
require_relative 'rule/these_expectations_failed.rb'
|
41
|
+
require_relative 'rule/submission_errored.rb'
|
42
|
+
require_relative 'rule/error_contains.rb'
|
@@ -0,0 +1,22 @@
|
|
1
|
+
class Mumukit::Assistant::Rule::TheseExpectationsFailed < Mumukit::Assistant::Rule::SubmissionPassedWithWarnings
|
2
|
+
def initialize(message, expectations)
|
3
|
+
raise 'missing expectations' if expectations.blank?
|
4
|
+
super(message)
|
5
|
+
@expectations = expectations
|
6
|
+
end
|
7
|
+
|
8
|
+
def matches?(submission)
|
9
|
+
super && matches_failing_expectations?(submission)
|
10
|
+
end
|
11
|
+
|
12
|
+
def matches_failing_expectations?(submission)
|
13
|
+
@expectations.all? do |it|
|
14
|
+
includes_failing_expectation? it, submission.expectation_results
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def includes_failing_expectation?(humanized_expectation, expectation_results)
|
19
|
+
binding, inspection = humanized_expectation.split(' ')
|
20
|
+
expectation_results.include? binding: binding, inspection: inspection, result: :failed
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
class Mumukit::Assistant::Rule::TheseTestsFailed < Mumukit::Assistant::Rule::SubmissionFailed
|
2
|
+
def initialize(message, tests)
|
3
|
+
raise 'missing tests' if tests.blank?
|
4
|
+
super(message)
|
5
|
+
@tests = tests
|
6
|
+
end
|
7
|
+
|
8
|
+
def matches?(submission)
|
9
|
+
super && matches_failing_tests?(submission)
|
10
|
+
end
|
11
|
+
|
12
|
+
def matches_failing_tests?(submission)
|
13
|
+
@tests.all? do |it|
|
14
|
+
includes_failing_test? it, submission
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def includes_failing_test?(title, submission)
|
19
|
+
failed_tests(submission).map { |it| it[:title] }.include?(title)
|
20
|
+
end
|
21
|
+
|
22
|
+
def failed_tests(submission)
|
23
|
+
submission.test_results.select { |it| it[:status].failed? }
|
24
|
+
end
|
25
|
+
end
|
metadata
ADDED
@@ -0,0 +1,119 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mumukit-assistant
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Julián Berbel Alt
|
8
|
+
- Franco Bulgarelli
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2018-06-08 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: bundler
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - "~>"
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: '1.16'
|
21
|
+
type: :development
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - "~>"
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: '1.16'
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: rake
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - "~>"
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: '10.0'
|
35
|
+
type: :development
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - "~>"
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '10.0'
|
42
|
+
- !ruby/object:Gem::Dependency
|
43
|
+
name: rspec
|
44
|
+
requirement: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - "~>"
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '3'
|
49
|
+
type: :development
|
50
|
+
prerelease: false
|
51
|
+
version_requirements: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - "~>"
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '3'
|
56
|
+
- !ruby/object:Gem::Dependency
|
57
|
+
name: mumukit-core
|
58
|
+
requirement: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - "~>"
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '1.3'
|
63
|
+
type: :runtime
|
64
|
+
prerelease: false
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - "~>"
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '1.3'
|
70
|
+
description:
|
71
|
+
email:
|
72
|
+
- julian@mumuki.org
|
73
|
+
- franco@mumuki.org
|
74
|
+
executables: []
|
75
|
+
extensions: []
|
76
|
+
extra_rdoc_files: []
|
77
|
+
files:
|
78
|
+
- lib/locales/narrator.en.yml
|
79
|
+
- lib/locales/narrator.es.yml
|
80
|
+
- lib/locales/narrator.pt.yml
|
81
|
+
- lib/mumukit/assistant.rb
|
82
|
+
- lib/mumukit/assistant/message.rb
|
83
|
+
- lib/mumukit/assistant/narrator.rb
|
84
|
+
- lib/mumukit/assistant/rule.rb
|
85
|
+
- lib/mumukit/assistant/rule/base.rb
|
86
|
+
- lib/mumukit/assistant/rule/content_empty.rb
|
87
|
+
- lib/mumukit/assistant/rule/error_contains.rb
|
88
|
+
- lib/mumukit/assistant/rule/only_these_tests_failed.rb
|
89
|
+
- lib/mumukit/assistant/rule/submission_errored.rb
|
90
|
+
- lib/mumukit/assistant/rule/submission_failed.rb
|
91
|
+
- lib/mumukit/assistant/rule/submission_passed_with_warnings.rb
|
92
|
+
- lib/mumukit/assistant/rule/these_expectations_failed.rb
|
93
|
+
- lib/mumukit/assistant/rule/these_tests_failed.rb
|
94
|
+
- lib/mumukit/assistant/version.rb
|
95
|
+
homepage: http://github.com/mumuki/mumukit-assistant
|
96
|
+
licenses:
|
97
|
+
- MIT
|
98
|
+
metadata: {}
|
99
|
+
post_install_message:
|
100
|
+
rdoc_options: []
|
101
|
+
require_paths:
|
102
|
+
- lib
|
103
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
104
|
+
requirements:
|
105
|
+
- - ">="
|
106
|
+
- !ruby/object:Gem::Version
|
107
|
+
version: '0'
|
108
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
109
|
+
requirements:
|
110
|
+
- - ">="
|
111
|
+
- !ruby/object:Gem::Version
|
112
|
+
version: '0'
|
113
|
+
requirements: []
|
114
|
+
rubyforge_project:
|
115
|
+
rubygems_version: 2.5.1
|
116
|
+
signing_key:
|
117
|
+
specification_version: 4
|
118
|
+
summary: Library for providing assistance with mumuki exercises
|
119
|
+
test_files: []
|