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.
@@ -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: []