mumukit-assistant 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,10 @@
1
+ class Mumukit::Assistant::Rule::Base
2
+ attr_accessor :message
3
+ def initialize(message)
4
+ @message = message
5
+ end
6
+
7
+ def message_for(retries)
8
+ message.call(retries)
9
+ end
10
+ end
@@ -0,0 +1,5 @@
1
+ class Mumukit::Assistant::Rule::ContentEmpty < Mumukit::Assistant::Rule::Base
2
+ def matches?(submission)
3
+ submission.content.empty?
4
+ end
5
+ end
@@ -0,0 +1,10 @@
1
+ class Mumukit::Assistant::Rule::ErrorContains < Mumukit::Assistant::Rule::SubmissionErrored
2
+ def initialize(message, text)
3
+ super(message)
4
+ @text = text
5
+ end
6
+
7
+ def matches?(submission)
8
+ super && submission.result.include?(@text)
9
+ end
10
+ end
@@ -0,0 +1,5 @@
1
+ class Mumukit::Assistant::Rule::OnlyTheseTestsFailed < Mumukit::Assistant::Rule::TheseTestsFailed
2
+ def matches_failing_tests?(submission)
3
+ super && failed_tests(submission).count == @tests.count
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ class Mumukit::Assistant::Rule::SubmissionErrored < Mumukit::Assistant::Rule::Base
2
+ def matches?(submission)
3
+ submission.status.errored?
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ class Mumukit::Assistant::Rule::SubmissionFailed < Mumukit::Assistant::Rule::Base
2
+ def matches?(submission)
3
+ submission.status.failed?
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ class Mumukit::Assistant::Rule::SubmissionPassedWithWarnings < Mumukit::Assistant::Rule::Base
2
+ def matches?(submission)
3
+ submission.status.passed_with_warnings?
4
+ end
5
+ end
@@ -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
@@ -0,0 +1,5 @@
1
+ module Mumukit
2
+ class Assistant
3
+ VERSION = "0.1.0"
4
+ end
5
+ 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: []