asker-tool 2.6.0 → 2.7.1
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 +4 -4
- data/README.md +5 -5
- data/lib/asker/ai/ai.rb +6 -6
- data/lib/asker/ai/ai_calculate.rb +3 -3
- data/lib/asker/ai/code/base_code_ai.rb +5 -30
- data/lib/asker/ai/code/code_ai_factory.rb +6 -12
- data/lib/asker/ai/code/javascript_code_ai.rb +33 -34
- data/lib/asker/ai/code/python_code_ai.rb +35 -36
- data/lib/asker/ai/code/ruby_code_ai.rb +33 -33
- data/lib/asker/ai/code/sql_code_ai.rb +20 -21
- data/lib/asker/ai/concept_ai.rb +12 -22
- data/lib/asker/ai/problem/problem_ai.rb +226 -0
- data/lib/asker/ai/question.rb +34 -45
- data/lib/asker/ai/stages/base_stage.rb +7 -7
- data/lib/asker/ai/stages/stage_b.rb +62 -28
- data/lib/asker/ai/stages/stage_d.rb +10 -10
- data/lib/asker/ai/stages/stage_f.rb +17 -17
- data/lib/asker/ai/stages/stage_i.rb +8 -18
- data/lib/asker/ai/stages/stage_s.rb +28 -26
- data/lib/asker/ai/stages/stage_t.rb +40 -51
- data/lib/asker/application.rb +15 -14
- data/lib/asker/check_input/check_haml_data.rb +52 -51
- data/lib/asker/check_input/check_table.rb +17 -20
- data/lib/asker/check_input.rb +10 -23
- data/lib/asker/cli.rb +43 -24
- data/lib/asker/data/code.rb +10 -9
- data/lib/asker/data/column.rb +21 -17
- data/lib/asker/data/concept.rb +24 -37
- data/lib/asker/data/data_field.rb +2 -2
- data/lib/asker/data/problem.rb +112 -0
- data/lib/asker/data/project_data.rb +11 -15
- data/lib/asker/data/row.rb +25 -23
- data/lib/asker/data/table.rb +25 -46
- data/lib/asker/data/template.rb +7 -7
- data/lib/asker/data/world.rb +3 -3
- data/lib/asker/{formatter → deprecated}/question_moodlexml_formatter.rb +19 -21
- data/lib/asker/displayer/code_displayer.rb +10 -10
- data/lib/asker/displayer/concept_ai_displayer.erb +1 -1
- data/lib/asker/displayer/concept_ai_displayer.rb +17 -17
- data/lib/asker/displayer/concept_displayer.rb +4 -2
- data/lib/asker/displayer/problem_displayer.rb +45 -0
- data/lib/asker/displayer/stats_displayer.rb +7 -12
- data/lib/asker/exporter/code_gift_exporter.rb +2 -2
- data/lib/asker/exporter/concept_ai_gift_exporter.rb +4 -4
- data/lib/asker/exporter/concept_ai_yaml_exporter.rb +7 -7
- data/lib/asker/exporter/concept_doc_exporter.rb +5 -5
- data/lib/asker/exporter/data_gift_exporter.rb +14 -15
- data/lib/asker/exporter/data_moodle_exporter.rb +51 -20
- data/lib/asker/exporter/output_file_exporter.rb +9 -8
- data/lib/asker/exporter/problem_gift_exporter.rb +30 -0
- data/lib/asker/files/language/ca/templates.yaml +6 -0
- data/lib/asker/files/language/du/templates.yaml +6 -0
- data/lib/asker/files/language/en/templates.yaml +7 -1
- data/lib/asker/files/language/es/templates.yaml +6 -0
- data/lib/asker/files/language/fr/templates.yaml +6 -0
- data/lib/asker/formatter/code_string_formatter.rb +5 -5
- data/lib/asker/formatter/concept_doc_formatter.rb +3 -3
- data/lib/asker/formatter/concept_string_formatter.rb +6 -6
- data/lib/asker/formatter/moodle/ddmatch.erb +40 -0
- data/lib/asker/formatter/moodle/gapfill.erb +57 -0
- data/lib/asker/formatter/moodle/ordering.erb +41 -0
- data/lib/asker/formatter/question_gift_formatter.rb +41 -14
- data/lib/asker/formatter/question_hash_formatter.rb +5 -6
- data/lib/asker/formatter/question_moodle_formatter.rb +14 -7
- data/lib/asker/formatter/rb2haml_formatter.rb +8 -7
- data/lib/asker/lang/lang.rb +16 -16
- data/lib/asker/lang/lang_factory.rb +13 -16
- data/lib/asker/lang/text_actions.rb +20 -18
- data/lib/asker/loader/code_loader.rb +10 -22
- data/lib/asker/loader/content_loader.rb +42 -49
- data/lib/asker/loader/directory_loader.rb +13 -16
- data/lib/asker/loader/embedded_file.rb +14 -14
- data/lib/asker/loader/file_loader.rb +5 -4
- data/lib/asker/loader/haml_loader.rb +4 -3
- data/lib/asker/loader/image_url_loader.rb +6 -5
- data/lib/asker/loader/input_loader.rb +24 -10
- data/lib/asker/loader/problem_loader.rb +88 -0
- data/lib/asker/loader/project_loader.rb +5 -12
- data/lib/asker/logger.rb +19 -10
- data/lib/asker/skeleton.rb +19 -35
- data/lib/asker/start.rb +44 -0
- data/lib/asker/version.rb +1 -1
- data/lib/asker.rb +7 -52
- metadata +12 -6
- data/lib/asker/ai/code/problem_code_ai.rb +0 -176
- data/lib/asker/exporter/code_moodle_exporter.rb +0 -15
- data/lib/asker/exporter/concept_ai_moodle_exporter.rb +0 -15
| @@ -16,29 +16,29 @@ class RubyCodeAI < BaseCodeAI | |
| 16 16 | 
             
                questions = []
         | 
| 17 17 | 
             
                # error_lines = []
         | 
| 18 18 | 
             
                @lines.each_with_index do |line, index|
         | 
| 19 | 
            -
                  if line.strip.start_with?( | 
| 19 | 
            +
                  if line.strip.start_with?("#")
         | 
| 20 20 | 
             
                    lines = clone_array @lines
         | 
| 21 | 
            -
                    lines[index].sub!( | 
| 21 | 
            +
                    lines[index].sub!("#", "").strip!
         | 
| 22 22 |  | 
| 23 23 | 
             
                    q = Question.new(:short)
         | 
| 24 24 | 
             
                    q.name = "#{name}-#{num}-uncomment"
         | 
| 25 | 
            -
                    q.text = @lang.text_for(:code1,lines_to_html(lines))
         | 
| 26 | 
            -
                    q.shorts << (index+1)
         | 
| 27 | 
            -
                    q.feedback =  | 
| 25 | 
            +
                    q.text = @lang.text_for(:code1, lines_to_html(lines))
         | 
| 26 | 
            +
                    q.shorts << (index + 1)
         | 
| 27 | 
            +
                    q.feedback = "Comment symbol removed"
         | 
| 28 28 | 
             
                    questions << q
         | 
| 29 | 
            -
                  elsif line.strip.size>0
         | 
| 29 | 
            +
                  elsif line.strip.size > 0
         | 
| 30 30 | 
             
                    lines = clone_array @lines
         | 
| 31 | 
            -
                    lines[index]= | 
| 31 | 
            +
                    lines[index] = "# " + lines[index]
         | 
| 32 32 |  | 
| 33 33 | 
             
                    q = Question.new(:short)
         | 
| 34 34 | 
             
                    q.name = "#{name}-#{num}-comment"
         | 
| 35 | 
            -
                    q.text = @lang.text_for(:code1,lines_to_html(lines))
         | 
| 36 | 
            -
                    q.shorts << (index+1)
         | 
| 37 | 
            -
                    q.feedback =  | 
| 35 | 
            +
                    q.text = @lang.text_for(:code1, lines_to_html(lines))
         | 
| 36 | 
            +
                    q.shorts << (index + 1)
         | 
| 37 | 
            +
                    q.feedback = "Comment symbol added"
         | 
| 38 38 | 
             
                    questions << q
         | 
| 39 39 | 
             
                  end
         | 
| 40 40 | 
             
                end
         | 
| 41 | 
            -
                questions.shuffle[0 | 
| 41 | 
            +
                questions.shuffle[0, @lines.size / @reduce]
         | 
| 42 42 | 
             
              end
         | 
| 43 43 |  | 
| 44 44 | 
             
              ##
         | 
| @@ -47,7 +47,7 @@ class RubyCodeAI < BaseCodeAI | |
| 47 47 | 
             
                questions = []
         | 
| 48 48 | 
             
                empty_lines = []
         | 
| 49 49 | 
             
                used_lines = []
         | 
| 50 | 
            -
                @lines.each_with_index do |line,index|
         | 
| 50 | 
            +
                @lines.each_with_index do |line, index|
         | 
| 51 51 | 
             
                  if line.strip.size.zero?
         | 
| 52 52 | 
             
                    empty_lines << index
         | 
| 53 53 | 
             
                  else
         | 
| @@ -57,28 +57,28 @@ class RubyCodeAI < BaseCodeAI | |
| 57 57 |  | 
| 58 58 | 
             
                used_lines.each do |index|
         | 
| 59 59 | 
             
                  lines = clone_array(@lines)
         | 
| 60 | 
            -
                  lines.insert(index,  | 
| 60 | 
            +
                  lines.insert(index, " " * (rand(4).to_i + 1))
         | 
| 61 61 | 
             
                  if @lines.size < 4 || rand(2) == 0
         | 
| 62 62 | 
             
                    q = Question.new(:short)
         | 
| 63 63 | 
             
                    q.name = "#{name}-#{num}-codeok"
         | 
| 64 | 
            -
                    q.text = @lang.text_for(:code1,lines_to_html(lines))
         | 
| 65 | 
            -
                    q.shorts <<  | 
| 66 | 
            -
                    q.feedback =  | 
| 64 | 
            +
                    q.text = @lang.text_for(:code1, lines_to_html(lines))
         | 
| 65 | 
            +
                    q.shorts << "0"
         | 
| 66 | 
            +
                    q.feedback = "Code is OK"
         | 
| 67 67 | 
             
                    questions << q
         | 
| 68 68 | 
             
                  else
         | 
| 69 69 | 
             
                    q = Question.new(:choice)
         | 
| 70 70 | 
             
                    q.name = "#{name}-#{num}-codeok"
         | 
| 71 | 
            -
                    q.text = @lang.text_for(:code2,lines_to_html(lines))
         | 
| 71 | 
            +
                    q.text = @lang.text_for(:code2, lines_to_html(lines))
         | 
| 72 72 | 
             
                    others = (1..@lines.size).to_a.shuffle!
         | 
| 73 | 
            -
                    q.good =  | 
| 73 | 
            +
                    q.good = "0"
         | 
| 74 74 | 
             
                    q.bads << others[0].to_s
         | 
| 75 75 | 
             
                    q.bads << others[1].to_s
         | 
| 76 76 | 
             
                    q.bads << others[2].to_s
         | 
| 77 | 
            -
                    q.feedback =  | 
| 77 | 
            +
                    q.feedback = "Code is OK"
         | 
| 78 78 | 
             
                  end
         | 
| 79 79 | 
             
                end
         | 
| 80 80 |  | 
| 81 | 
            -
                questions.shuffle[0 | 
| 81 | 
            +
                questions.shuffle[0, @lines.size / @reduce]
         | 
| 82 82 | 
             
              end
         | 
| 83 83 |  | 
| 84 84 | 
             
              ##
         | 
| @@ -86,13 +86,13 @@ class RubyCodeAI < BaseCodeAI | |
| 86 86 | 
             
              def make_syntax_error
         | 
| 87 87 | 
             
                questions = []
         | 
| 88 88 |  | 
| 89 | 
            -
                @lang.mistakes.each_pair do |key,values|
         | 
| 89 | 
            +
                @lang.mistakes.each_pair do |key, values|
         | 
| 90 90 | 
             
                  error_lines = []
         | 
| 91 | 
            -
                  @lines.each_with_index do |line,index|
         | 
| 91 | 
            +
                  @lines.each_with_index do |line, index|
         | 
| 92 92 | 
             
                    error_lines << index if line.include?(key.to_s)
         | 
| 93 93 | 
             
                  end
         | 
| 94 94 |  | 
| 95 | 
            -
                  v = values.split( | 
| 95 | 
            +
                  v = values.split(",")
         | 
| 96 96 | 
             
                  v.each do |value|
         | 
| 97 97 | 
             
                    error_lines.each do |index|
         | 
| 98 98 | 
             
                      lines = clone_array(@lines)
         | 
| @@ -100,15 +100,15 @@ class RubyCodeAI < BaseCodeAI | |
| 100 100 | 
             
                      if @lines.size < 4 || rand(2) == 0
         | 
| 101 101 | 
             
                        q = Question.new(:short)
         | 
| 102 102 | 
             
                        q.name = "#{name}-#{num}-syntaxerror"
         | 
| 103 | 
            -
                        q.text = @lang.text_for(:code1,lines_to_html(lines))
         | 
| 104 | 
            -
                        q.shorts << (index+1)
         | 
| 103 | 
            +
                        q.text = @lang.text_for(:code1, lines_to_html(lines))
         | 
| 104 | 
            +
                        q.shorts << (index + 1)
         | 
| 105 105 | 
             
                        q.feedback = "Syntax error: '#{value}' must be '#{key}'"
         | 
| 106 106 | 
             
                      else
         | 
| 107 107 | 
             
                        q = Question.new(:choice)
         | 
| 108 108 | 
             
                        q.name = "#{name}-#{num}-syntaxerror"
         | 
| 109 | 
            -
                        q.text = @lang.text_for(:code2,lines_to_html(lines))
         | 
| 109 | 
            +
                        q.text = @lang.text_for(:code2, lines_to_html(lines))
         | 
| 110 110 | 
             
                        others = (1..@lines.size).to_a.shuffle!
         | 
| 111 | 
            -
                        others.delete(index+1)
         | 
| 111 | 
            +
                        others.delete(index + 1)
         | 
| 112 112 | 
             
                        q.good = (index + 1).to_s
         | 
| 113 113 | 
             
                        q.bads << others[0].to_s
         | 
| 114 114 | 
             
                        q.bads << others[1].to_s
         | 
| @@ -119,7 +119,7 @@ class RubyCodeAI < BaseCodeAI | |
| 119 119 | 
             
                    end
         | 
| 120 120 | 
             
                  end
         | 
| 121 121 | 
             
                end
         | 
| 122 | 
            -
                questions.shuffle[0 | 
| 122 | 
            +
                questions.shuffle[0, @lines.size / @reduce]
         | 
| 123 123 | 
             
              end
         | 
| 124 124 |  | 
| 125 125 | 
             
              ##
         | 
| @@ -129,7 +129,7 @@ class RubyCodeAI < BaseCodeAI | |
| 129 129 | 
             
                # error_lines = []
         | 
| 130 130 | 
             
                @lines.each_with_index do |line, index|
         | 
| 131 131 | 
             
                  # Search Variable assignment
         | 
| 132 | 
            -
                  m = /\s*(\w*)\s | 
| 132 | 
            +
                  m = /\s*(\w*)\s*=\w*/.match(line)
         | 
| 133 133 | 
             
                  i = []
         | 
| 134 134 | 
             
                  unless m.nil?
         | 
| 135 135 | 
             
                    varname = (m.values_at 1)[0]
         | 
| @@ -152,22 +152,22 @@ class RubyCodeAI < BaseCodeAI | |
| 152 152 | 
             
                      q.name = "#{name}-#{num}-variable"
         | 
| 153 153 | 
             
                      q.text = @lang.text_for(:code1, lines_to_html(lines))
         | 
| 154 154 | 
             
                      q.shorts << (index + 1)
         | 
| 155 | 
            -
                      q.feedback = "Variable error! Swapped lines #{ | 
| 155 | 
            +
                      q.feedback = "Variable error! Swapped lines #{index + 1} with #{k + 1}"
         | 
| 156 156 | 
             
                    else
         | 
| 157 157 | 
             
                      q = Question.new(:choice)
         | 
| 158 158 | 
             
                      q.name = "#{name}-#{num}-variable"
         | 
| 159 159 | 
             
                      q.text = @lang.text_for(:code2, lines_to_html(lines))
         | 
| 160 160 | 
             
                      others = (1..@lines.size).to_a.shuffle!
         | 
| 161 | 
            -
                      others.delete(index+1)
         | 
| 161 | 
            +
                      others.delete(index + 1)
         | 
| 162 162 | 
             
                      q.good = (index + 1).to_s
         | 
| 163 163 | 
             
                      q.bads << others[0].to_s
         | 
| 164 164 | 
             
                      q.bads << others[1].to_s
         | 
| 165 165 | 
             
                      q.bads << others[2].to_s
         | 
| 166 | 
            -
                      q.feedback = "Variable error! Swapped lines #{ | 
| 166 | 
            +
                      q.feedback = "Variable error! Swapped lines #{index + 1} with #{k + 1}"
         | 
| 167 167 | 
             
                    end
         | 
| 168 168 | 
             
                    questions << q
         | 
| 169 169 | 
             
                  end
         | 
| 170 170 | 
             
                end
         | 
| 171 | 
            -
                questions.shuffle[0 | 
| 171 | 
            +
                questions.shuffle[0, @lines.size / @reduce]
         | 
| 172 172 | 
             
              end
         | 
| 173 173 | 
             
            end
         | 
| @@ -1,36 +1,35 @@ | |
| 1 | 
            -
             | 
| 2 | 
            -
            require_relative  | 
| 3 | 
            -
            require_relative  | 
| 4 | 
            -
            require_relative 'base_code_ai'
         | 
| 1 | 
            +
            require_relative "../../lang/lang_factory"
         | 
| 2 | 
            +
            require_relative "../../ai/question"
         | 
| 3 | 
            +
            require_relative "base_code_ai"
         | 
| 5 4 |  | 
| 6 5 | 
             
            class SQLCodeAI < BaseCodeAI
         | 
| 7 6 | 
             
              def initialize(code)
         | 
| 8 | 
            -
                @lang = LangFactory.instance.get( | 
| 7 | 
            +
                @lang = LangFactory.instance.get("sql")
         | 
| 9 8 | 
             
                super code
         | 
| 10 9 | 
             
              end
         | 
| 11 10 |  | 
| 12 11 | 
             
              def make_comment_error
         | 
| 13 12 | 
             
                questions = []
         | 
| 14 | 
            -
                @lines.each_with_index do |line,index|
         | 
| 15 | 
            -
                  if line.include?( | 
| 13 | 
            +
                @lines.each_with_index do |line, index|
         | 
| 14 | 
            +
                  if line.include?("//")
         | 
| 16 15 | 
             
                    lines = clone_array @lines
         | 
| 17 | 
            -
                    lines[index].sub!( | 
| 16 | 
            +
                    lines[index].sub!("//", "").strip!
         | 
| 18 17 |  | 
| 19 18 | 
             
                    q = Question.new(:short)
         | 
| 20 19 | 
             
                    q.name = "#{name}-#{num}-code1uncomment"
         | 
| 21 | 
            -
                    q.text = @lang.text_for(:code1,lines_to_html(lines))
         | 
| 22 | 
            -
                    q.shorts << (index+1)
         | 
| 23 | 
            -
                    q.feedback =  | 
| 20 | 
            +
                    q.text = @lang.text_for(:code1, lines_to_html(lines))
         | 
| 21 | 
            +
                    q.shorts << (index + 1)
         | 
| 22 | 
            +
                    q.feedback = "Comment symbol removed"
         | 
| 24 23 | 
             
                    questions << q
         | 
| 25 | 
            -
                  elsif line.strip.size>0
         | 
| 24 | 
            +
                  elsif line.strip.size > 0
         | 
| 26 25 | 
             
                    lines = clone_array @lines
         | 
| 27 | 
            -
                    lines[index]= | 
| 26 | 
            +
                    lines[index] = "// " + lines[index]
         | 
| 28 27 |  | 
| 29 28 | 
             
                    q = Question.new(:short)
         | 
| 30 29 | 
             
                    q.name = "#{name}-#{num}-code1comment"
         | 
| 31 | 
            -
                    q.text = @lang.text_for(:code1,lines_to_html(lines))
         | 
| 32 | 
            -
                    q.shorts << (index+1)
         | 
| 33 | 
            -
                    q.feedback =  | 
| 30 | 
            +
                    q.text = @lang.text_for(:code1, lines_to_html(lines))
         | 
| 31 | 
            +
                    q.shorts << (index + 1)
         | 
| 32 | 
            +
                    q.feedback = "Comment symbol added"
         | 
| 34 33 | 
             
                    questions << q
         | 
| 35 34 | 
             
                  end
         | 
| 36 35 | 
             
                end
         | 
| @@ -41,10 +40,10 @@ class SQLCodeAI < BaseCodeAI | |
| 41 40 | 
             
                error_lines = []
         | 
| 42 41 | 
             
                questions = []
         | 
| 43 42 |  | 
| 44 | 
            -
                @lang.mistakes.each_pair do |key,values|
         | 
| 45 | 
            -
                  v = values.split( | 
| 43 | 
            +
                @lang.mistakes.each_pair do |key, values|
         | 
| 44 | 
            +
                  v = values.split(",")
         | 
| 46 45 | 
             
                  v.each do |value|
         | 
| 47 | 
            -
                    @lines.each_with_index do |line,index|
         | 
| 46 | 
            +
                    @lines.each_with_index do |line, index|
         | 
| 48 47 | 
             
                      error_lines << index if line.include?(key.to_s)
         | 
| 49 48 | 
             
                    end
         | 
| 50 49 |  | 
| @@ -53,8 +52,8 @@ class SQLCodeAI < BaseCodeAI | |
| 53 52 | 
             
                      lines[index].sub!(key.to_s, value)
         | 
| 54 53 | 
             
                      q = Question.new(:short)
         | 
| 55 54 | 
             
                      q.name = "#{name}-#{num}-code1keyword"
         | 
| 56 | 
            -
                      q.text = @lang.text_for(:code1,lines_to_html(lines))
         | 
| 57 | 
            -
                      q.shorts << (index+1)
         | 
| 55 | 
            +
                      q.text = @lang.text_for(:code1, lines_to_html(lines))
         | 
| 56 | 
            +
                      q.shorts << (index + 1)
         | 
| 58 57 | 
             
                      q.feedback = "Keyword error: '#{value}' must be '#{key}'"
         | 
| 59 58 | 
             
                      questions << q
         | 
| 60 59 | 
             
                    end
         | 
    
        data/lib/asker/ai/concept_ai.rb
    CHANGED
    
    | @@ -1,15 +1,11 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 | 
            -
            require_relative  | 
| 4 | 
            -
            require_relative  | 
| 5 | 
            -
            require_relative  | 
| 6 | 
            -
             | 
| 7 | 
            -
            #  | 
| 8 | 
            -
            # | 
| 9 | 
            -
            # * concept
         | 
| 10 | 
            -
            # * questions
         | 
| 11 | 
            -
            # * num
         | 
| 12 | 
            -
            # * random_image_for
         | 
| 3 | 
            +
            require_relative "../lang/lang"
         | 
| 4 | 
            +
            require_relative "ai"
         | 
| 5 | 
            +
            require_relative "question"
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            # Add more info to every Concept instance.
         | 
| 8 | 
            +
            # Encapsulating AI data => questions
         | 
| 13 9 | 
             
            class ConceptAI
         | 
| 14 10 | 
             
              include AI
         | 
| 15 11 |  | 
| @@ -17,22 +13,17 @@ class ConceptAI | |
| 17 13 | 
             
              attr_reader :questions
         | 
| 18 14 | 
             
              attr_reader :excluded_questions
         | 
| 19 15 |  | 
| 20 | 
            -
              ##
         | 
| 21 | 
            -
              # Initialize ConcepAI
         | 
| 22 | 
            -
              # @param concept (Concept)
         | 
| 23 | 
            -
              # @param world (World)
         | 
| 24 16 | 
             
              def initialize(concept, world)
         | 
| 25 17 | 
             
                @concept = concept
         | 
| 26 18 | 
             
                @world = world
         | 
| 27 | 
            -
                @questions = { | 
| 28 | 
            -
                @excluded_questions = { | 
| 19 | 
            +
                @questions = {d: [], b: [], f: [], i: [], s: [], t: []}
         | 
| 20 | 
            +
                @excluded_questions = {d: [], b: [], f: [], i: [], s: [], t: []}
         | 
| 29 21 | 
             
                @num = 0 # Add a unique number to every question
         | 
| 30 22 | 
             
                make_questions
         | 
| 31 23 | 
             
              end
         | 
| 32 24 |  | 
| 33 | 
            -
              ##
         | 
| 34 | 
            -
              # Generates and return new "num" value
         | 
| 35 25 | 
             
              def num
         | 
| 26 | 
            +
                # Generates and return new "num" value
         | 
| 36 27 | 
             
                @num += 1
         | 
| 37 28 | 
             
                @num.to_s
         | 
| 38 29 | 
             
              end
         | 
| @@ -43,15 +34,14 @@ class ConceptAI | |
| 43 34 | 
             
                @concept.send(method, *args, &block)
         | 
| 44 35 | 
             
              end
         | 
| 45 36 |  | 
| 46 | 
            -
              ##
         | 
| 47 | 
            -
              # Generates random image URL
         | 
| 48 37 | 
             
              def random_image_for(_conceptname)
         | 
| 49 | 
            -
                 | 
| 38 | 
            +
                # Generates random image URL
         | 
| 39 | 
            +
                return "" if rand <= ProjectData.instance.get(:threshold)
         | 
| 50 40 |  | 
| 51 41 | 
             
                keys = @world.image_urls.keys
         | 
| 52 42 | 
             
                keys.shuffle!
         | 
| 53 43 | 
             
                values = @world.image_urls[keys[0]] # keys[0] could be conceptname
         | 
| 54 | 
            -
                return  | 
| 44 | 
            +
                return "" if values.nil?
         | 
| 55 45 |  | 
| 56 46 | 
             
                values.shuffle!
         | 
| 57 47 | 
             
                "<img src=\"#{values[0]}\" alt=\"image\"><br/>"
         | 
| @@ -0,0 +1,226 @@ | |
| 1 | 
            +
            require_relative "../../lang/lang_factory"
         | 
| 2 | 
            +
            require_relative "../question"
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            class ProblemAI
         | 
| 5 | 
            +
              attr_accessor :problem
         | 
| 6 | 
            +
             | 
| 7 | 
            +
              def call(problem)
         | 
| 8 | 
            +
                @problem = problem
         | 
| 9 | 
            +
                make_questions
         | 
| 10 | 
            +
                @problem
         | 
| 11 | 
            +
              end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
              def make_questions
         | 
| 14 | 
            +
                @counter = 0
         | 
| 15 | 
            +
                @questions = []
         | 
| 16 | 
            +
                @customs = get_customs(@problem)
         | 
| 17 | 
            +
                make_questions_with_aswers
         | 
| 18 | 
            +
                make_questions_with_steps
         | 
| 19 | 
            +
                @problem.questions = @questions
         | 
| 20 | 
            +
              end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
              private
         | 
| 23 | 
            +
             | 
| 24 | 
            +
              def counter
         | 
| 25 | 
            +
                @counter += 1
         | 
| 26 | 
            +
              end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
              def customize(text:, custom:)
         | 
| 29 | 
            +
                output = text.clone
         | 
| 30 | 
            +
                custom.each_pair { |oldvalue, newvalue| output.gsub!(oldvalue, newvalue) }
         | 
| 31 | 
            +
                output
         | 
| 32 | 
            +
              end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
              def get_customs(problem)
         | 
| 35 | 
            +
                customs = []
         | 
| 36 | 
            +
                vars = problem.varnames
         | 
| 37 | 
            +
                problem.cases.each do |acase|
         | 
| 38 | 
            +
                  custom = {}
         | 
| 39 | 
            +
                  vars.each_with_index { |varname, index| custom[varname] = acase[index] }
         | 
| 40 | 
            +
                  customs << custom
         | 
| 41 | 
            +
                end
         | 
| 42 | 
            +
                customs
         | 
| 43 | 
            +
              end
         | 
| 44 | 
            +
             | 
| 45 | 
            +
              def lines_to_s(lines)
         | 
| 46 | 
            +
                output = ""
         | 
| 47 | 
            +
                lines.each_with_index do |line, index|
         | 
| 48 | 
            +
                  output << "%2d: #{line}\n" % (index + 1)
         | 
| 49 | 
            +
                end
         | 
| 50 | 
            +
                output
         | 
| 51 | 
            +
              end
         | 
| 52 | 
            +
             | 
| 53 | 
            +
              def make_questions_with_aswers
         | 
| 54 | 
            +
                name = @problem.name
         | 
| 55 | 
            +
                lang = @problem.lang
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                @customs.each do |custom|
         | 
| 58 | 
            +
                  desc = customize(text: @problem.desc, custom: custom)
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                  @problem.asks.each do |ask|
         | 
| 61 | 
            +
                    next if ask[:text].nil?
         | 
| 62 | 
            +
                    asktext = customize(text: ask[:text], custom: custom)
         | 
| 63 | 
            +
                    next if ask[:answer].nil?
         | 
| 64 | 
            +
                    correct_answer = customize(text: ask[:answer], custom: custom)
         | 
| 65 | 
            +
             | 
| 66 | 
            +
                    # Question boolean => true
         | 
| 67 | 
            +
                    q = Question.new(:boolean)
         | 
| 68 | 
            +
                    q.name = "#{name}-#{counter}-pa1true1"
         | 
| 69 | 
            +
                    q.text = lang.text_for(:pa1, desc, asktext, correct_answer)
         | 
| 70 | 
            +
                    q.good = "TRUE"
         | 
| 71 | 
            +
                    @questions << q
         | 
| 72 | 
            +
             | 
| 73 | 
            +
                    # Locate incorrect answers
         | 
| 74 | 
            +
                    incorrect_answers = []
         | 
| 75 | 
            +
                    @customs.each do |aux|
         | 
| 76 | 
            +
                      next if aux == custom
         | 
| 77 | 
            +
                      incorrect = customize(text: ask[:answer], custom: aux)
         | 
| 78 | 
            +
                      incorrect_answers << incorrect if incorrect != correct_answer
         | 
| 79 | 
            +
                    end
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                    # Question boolean => true
         | 
| 82 | 
            +
                    if incorrect_answers.size > 0
         | 
| 83 | 
            +
                      q = Question.new(:boolean)
         | 
| 84 | 
            +
                      q.name = "#{name}-#{counter}-pa2false2"
         | 
| 85 | 
            +
                      q.text = lang.text_for(:pa1, desc, asktext, incorrect_answers.first)
         | 
| 86 | 
            +
                      q.good = "FALSE"
         | 
| 87 | 
            +
                      @questions << q
         | 
| 88 | 
            +
                    end
         | 
| 89 | 
            +
             | 
| 90 | 
            +
                    # Question choice NONE
         | 
| 91 | 
            +
                    if incorrect_answers.size > 2
         | 
| 92 | 
            +
                      q = Question.new(:choice)
         | 
| 93 | 
            +
                      q.name = "#{name}-#{counter}-pa2-choice-none3"
         | 
| 94 | 
            +
                      q.text = lang.text_for(:pa2, desc, asktext)
         | 
| 95 | 
            +
                      q.good = lang.text_for(:none)
         | 
| 96 | 
            +
                      incorrect_answers.shuffle!
         | 
| 97 | 
            +
                      q.bads << incorrect_answers[0]
         | 
| 98 | 
            +
                      q.bads << incorrect_answers[1]
         | 
| 99 | 
            +
                      q.bads << incorrect_answers[2]
         | 
| 100 | 
            +
                      q.feedback = "Correct answer is #{correct_answer}."
         | 
| 101 | 
            +
                      @questions << q
         | 
| 102 | 
            +
                    end
         | 
| 103 | 
            +
             | 
| 104 | 
            +
                    # Question choice OK
         | 
| 105 | 
            +
                    if incorrect_answers.size > 2
         | 
| 106 | 
            +
                      q = Question.new(:choice)
         | 
| 107 | 
            +
                      q.name = "#{name}-#{counter}-pa2choice4"
         | 
| 108 | 
            +
                      q.text = lang.text_for(:pa2, desc, asktext)
         | 
| 109 | 
            +
                      q.good = correct_answer
         | 
| 110 | 
            +
                      incorrect_answers.shuffle!
         | 
| 111 | 
            +
                      q.bads << incorrect_answers[0]
         | 
| 112 | 
            +
                      q.bads << incorrect_answers[1]
         | 
| 113 | 
            +
                      q.bads << incorrect_answers[2]
         | 
| 114 | 
            +
                      q.feedback = "Correct answer is #{correct_answer}."
         | 
| 115 | 
            +
                      @questions << q
         | 
| 116 | 
            +
                    end
         | 
| 117 | 
            +
             | 
| 118 | 
            +
                    if incorrect_answers.size > 1
         | 
| 119 | 
            +
                      q = Question.new(:choice)
         | 
| 120 | 
            +
                      q.name = "#{name}-#{counter}-pa2choice5"
         | 
| 121 | 
            +
                      q.text = lang.text_for(:pa2, desc, asktext)
         | 
| 122 | 
            +
                      q.good = correct_answer
         | 
| 123 | 
            +
                      incorrect_answers.shuffle!
         | 
| 124 | 
            +
                      q.bads << incorrect_answers[0]
         | 
| 125 | 
            +
                      q.bads << incorrect_answers[1]
         | 
| 126 | 
            +
                      q.bads << lang.text_for(:none)
         | 
| 127 | 
            +
                      q.feedback = "Correct answer is #{correct_answer}."
         | 
| 128 | 
            +
                      @questions << q
         | 
| 129 | 
            +
                    end
         | 
| 130 | 
            +
             | 
| 131 | 
            +
                    # Question short
         | 
| 132 | 
            +
                    q = Question.new(:short)
         | 
| 133 | 
            +
                    q.name = "#{name}-#{counter}-pa2short6"
         | 
| 134 | 
            +
                    q.text = lang.text_for(:pa2, desc, asktext)
         | 
| 135 | 
            +
                    q.shorts << correct_answer
         | 
| 136 | 
            +
                    q.feedback = "Correct answer is #{correct_answer}."
         | 
| 137 | 
            +
                    @questions << q
         | 
| 138 | 
            +
                  end
         | 
| 139 | 
            +
                end
         | 
| 140 | 
            +
             | 
| 141 | 
            +
                def make_questions_with_steps
         | 
| 142 | 
            +
                  name = @problem.name
         | 
| 143 | 
            +
                  lang = @problem.lang
         | 
| 144 | 
            +
             | 
| 145 | 
            +
                  @customs.each do |custom|
         | 
| 146 | 
            +
                    desc = customize(text: @problem.desc, custom: custom)
         | 
| 147 | 
            +
             | 
| 148 | 
            +
                    @problem.asks.each do |ask|
         | 
| 149 | 
            +
                      next if ask[:text].nil?
         | 
| 150 | 
            +
                      asktext = customize(text: ask[:text], custom: custom)
         | 
| 151 | 
            +
                      next if ask[:steps].nil? || ask[:steps].empty?
         | 
| 152 | 
            +
                      steps = ask[:steps].map { |step| customize(text: step, custom: custom) }
         | 
| 153 | 
            +
             | 
| 154 | 
            +
                      # Question steps ok
         | 
| 155 | 
            +
                      q = Question.new(:short)
         | 
| 156 | 
            +
                      q.name = "#{name}-#{counter}-ps3short7"
         | 
| 157 | 
            +
                      q.text = lang.text_for(:ps3, desc, asktext, lines_to_s(steps))
         | 
| 158 | 
            +
                      q.shorts << 0
         | 
| 159 | 
            +
                      @questions << q
         | 
| 160 | 
            +
             | 
| 161 | 
            +
                      if steps.size > 3
         | 
| 162 | 
            +
                        q = Question.new(:ordering)
         | 
| 163 | 
            +
                        q.name = "#{name}-#{counter}-ps6ordering8"
         | 
| 164 | 
            +
                        q.text = lang.text_for(:ps6, desc, asktext, lines_to_s(steps))
         | 
| 165 | 
            +
                        steps.each { |step| q.ordering << step }
         | 
| 166 | 
            +
                        @questions << q
         | 
| 167 | 
            +
                      end
         | 
| 168 | 
            +
             | 
| 169 | 
            +
                      # Using diferents wrong steps sequences
         | 
| 170 | 
            +
                      max = steps.size - 1
         | 
| 171 | 
            +
                      (0..max).each do |index|
         | 
| 172 | 
            +
                        change = rand(max + 1)
         | 
| 173 | 
            +
                        bads = steps.clone
         | 
| 174 | 
            +
             | 
| 175 | 
            +
                        minor = index
         | 
| 176 | 
            +
                        major = change
         | 
| 177 | 
            +
                        if minor > major
         | 
| 178 | 
            +
                          minor, major = major, minor
         | 
| 179 | 
            +
                        elsif minor == major
         | 
| 180 | 
            +
                          next
         | 
| 181 | 
            +
                        end
         | 
| 182 | 
            +
                        bads[minor], bads[major] = bads[major], bads[minor]
         | 
| 183 | 
            +
             | 
| 184 | 
            +
                        # Question steps error
         | 
| 185 | 
            +
                        q = Question.new(:short)
         | 
| 186 | 
            +
                        q.name = "#{name}-#{counter}-ps3short-error9"
         | 
| 187 | 
            +
                        q.text = lang.text_for(:ps3, desc, asktext, lines_to_s(bads))
         | 
| 188 | 
            +
                        q.shorts << minor + 1
         | 
| 189 | 
            +
                        q.feedback = lang.text_for(:ps4, minor + 1, major + 1)
         | 
| 190 | 
            +
                        @questions << q
         | 
| 191 | 
            +
                      end
         | 
| 192 | 
            +
             | 
| 193 | 
            +
                      # Match questions
         | 
| 194 | 
            +
                      indexes = (0..(steps.size - 1)).to_a.shuffle
         | 
| 195 | 
            +
                      (0..(steps.size - 4)).each do |first|
         | 
| 196 | 
            +
                        incomplete_steps = steps.clone
         | 
| 197 | 
            +
                        incomplete_steps[indexes[first]] = "?"
         | 
| 198 | 
            +
                        incomplete_steps[indexes[first + 1]] = "?"
         | 
| 199 | 
            +
                        incomplete_steps[indexes[first + 2]] = "?"
         | 
| 200 | 
            +
                        incomplete_steps[indexes[first + 3]] = "?"
         | 
| 201 | 
            +
             | 
| 202 | 
            +
                        q = Question.new(:match)
         | 
| 203 | 
            +
                        q.name = "#{name}-#{counter}-ps5match10"
         | 
| 204 | 
            +
                        q.text = lang.text_for(:ps5, desc,  asktext, lines_to_s(incomplete_steps))
         | 
| 205 | 
            +
                        q.matching << [steps[indexes[first]], (indexes[first] + 1).to_s]
         | 
| 206 | 
            +
                        q.matching << [steps[indexes[first + 1]], (indexes[first + 1] + 1).to_s]
         | 
| 207 | 
            +
                        q.matching << [steps[indexes[first + 2]], (indexes[first + 2] + 1).to_s]
         | 
| 208 | 
            +
                        q.matching << [steps[indexes[first + 3]], (indexes[first + 3] + 1).to_s]
         | 
| 209 | 
            +
                        q.matching << ["", lang.text_for(:error)]
         | 
| 210 | 
            +
                        @questions << q
         | 
| 211 | 
            +
             | 
| 212 | 
            +
                        q = Question.new(:ddmatch)
         | 
| 213 | 
            +
                        q.name = "#{name}-#{counter}-ps5ddmatch11"
         | 
| 214 | 
            +
                        q.text = lang.text_for(:ps5, desc,  asktext, lines_to_s(incomplete_steps))
         | 
| 215 | 
            +
                        q.matching << [(indexes[first] + 1).to_s, steps[indexes[first]]]
         | 
| 216 | 
            +
                        q.matching << [(indexes[first + 1] + 1).to_s, steps[indexes[first + 1]]]
         | 
| 217 | 
            +
                        q.matching << [(indexes[first + 2] + 1).to_s, steps[indexes[first + 2]]]
         | 
| 218 | 
            +
                        q.matching << [(indexes[first + 3] + 1).to_s, steps[indexes[first + 3]]]
         | 
| 219 | 
            +
                        q.matching << ["", lang.text_for(:error)]
         | 
| 220 | 
            +
                        @questions << q
         | 
| 221 | 
            +
                      end
         | 
| 222 | 
            +
                    end
         | 
| 223 | 
            +
                  end
         | 
| 224 | 
            +
                end
         | 
| 225 | 
            +
              end
         | 
| 226 | 
            +
            end
         | 
    
        data/lib/asker/ai/question.rb
    CHANGED
    
    | @@ -1,80 +1,69 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 | 
            -
            require  | 
| 3 | 
            +
            require "set"
         | 
| 4 4 |  | 
| 5 | 
            -
            # Define Question class
         | 
| 6 5 | 
             
            class Question
         | 
| 6 | 
            +
              TYPES = %i[boolean choice ddmatch match ordering short]
         | 
| 7 | 
            +
             | 
| 7 8 | 
             
              attr_accessor :name     # Question name used as identification
         | 
| 8 9 | 
             
              attr_accessor :comment  # Comments asociated
         | 
| 9 | 
            -
              attr_accessor :text     # The real text of the question
         | 
| 10 | 
            -
              attr_accessor :good     # The correct answer
         | 
| 11 | 
            -
              attr_accessor :bads     # Bads answers used by choice type question
         | 
| 12 | 
            -
              attr_accessor :matching # Matching answers used by match type question
         | 
| 13 | 
            -
              attr_accessor :shorts   # Short answers used by short type question
         | 
| 14 | 
            -
              attr_accessor :feedback # Question feedbak
         | 
| 15 | 
            -
              attr_reader   :type     # Question type: :choice, :match, :boolean, :short
         | 
| 16 10 | 
             
              attr_accessor :tags
         | 
| 17 11 | 
             
              attr_accessor :lang     # Info used when export (YAML)
         | 
| 18 12 | 
             
              attr_accessor :encode   # image base64 content used when export Moodle xml
         | 
| 19 13 |  | 
| 20 | 
            -
              #  | 
| 21 | 
            -
               | 
| 14 | 
            +
              attr_accessor :text     # The real text of the question
         | 
| 15 | 
            +
              attr_accessor :feedback # Question feedbak
         | 
| 16 | 
            +
              attr_reader :type       # Question type: ;boolean, :choice, :match, :short
         | 
| 17 | 
            +
             | 
| 18 | 
            +
              attr_accessor :good     # The correct answer (types: boolean, choice)
         | 
| 19 | 
            +
              attr_accessor :bads     # Bads answers (type: choice)
         | 
| 20 | 
            +
              attr_accessor :matching # Matching answers (type: match)
         | 
| 21 | 
            +
              attr_accessor :ordering # Steps answer (type: ordering)
         | 
| 22 | 
            +
              attr_accessor :shorts   # Short answers (type: short)
         | 
| 23 | 
            +
             | 
| 22 24 | 
             
              def initialize(type = :choice)
         | 
| 23 25 | 
             
                reset(type)
         | 
| 24 26 | 
             
              end
         | 
| 25 27 |  | 
| 26 | 
            -
              # Reset attributes
         | 
| 27 28 | 
             
              # @param type (Symbol) Question type: choice, match, boolean, short
         | 
| 28 | 
            -
              # rubocop:disable Metrics/MethodLength
         | 
| 29 29 | 
             
              def reset(type = :choice)
         | 
| 30 | 
            -
                 | 
| 31 | 
            -
                @comment = ''
         | 
| 32 | 
            -
                @text = ''
         | 
| 30 | 
            +
                validate type
         | 
| 33 31 | 
             
                @type = type
         | 
| 34 | 
            -
             | 
| 35 | 
            -
                @ | 
| 36 | 
            -
                @ | 
| 37 | 
            -
                @shorts = []
         | 
| 38 | 
            -
                @feedback = nil
         | 
| 39 | 
            -
                shuffle_on
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                @name = ""
         | 
| 34 | 
            +
                @comment = ""
         | 
| 40 35 | 
             
                @tags = Set.new
         | 
| 41 36 | 
             
                @lang = nil
         | 
| 42 37 | 
             
                @encode = :none
         | 
| 43 | 
            -
              end
         | 
| 44 | 
            -
              # rubocop:enable Metrics/MethodLength
         | 
| 45 38 |  | 
| 46 | 
            -
             | 
| 47 | 
            -
             | 
| 48 | 
            -
                @ | 
| 49 | 
            -
             | 
| 50 | 
            -
             | 
| 51 | 
            -
             | 
| 52 | 
            -
             | 
| 53 | 
            -
                 | 
| 54 | 
            -
              end
         | 
| 55 | 
            -
             | 
| 56 | 
            -
              # Set boolean type
         | 
| 57 | 
            -
              def set_boolean
         | 
| 58 | 
            -
                @type = :boolean
         | 
| 59 | 
            -
              end
         | 
| 60 | 
            -
             | 
| 61 | 
            -
              # Set short type
         | 
| 62 | 
            -
              def set_short
         | 
| 63 | 
            -
                @type = :short
         | 
| 39 | 
            +
                @text = ""
         | 
| 40 | 
            +
                @feedback = nil
         | 
| 41 | 
            +
                @good = ""
         | 
| 42 | 
            +
                @bads = []
         | 
| 43 | 
            +
                @matching = []
         | 
| 44 | 
            +
                @ordering = []
         | 
| 45 | 
            +
                @shorts = []
         | 
| 46 | 
            +
                shuffle_on
         | 
| 64 47 | 
             
              end
         | 
| 65 48 |  | 
| 66 | 
            -
              # Set shuffle off
         | 
| 67 49 | 
             
              def shuffle_off
         | 
| 68 50 | 
             
                @shuffle = false
         | 
| 69 51 | 
             
              end
         | 
| 70 52 |  | 
| 71 | 
            -
              # Set shuffle on
         | 
| 72 53 | 
             
              def shuffle_on
         | 
| 73 54 | 
             
                @shuffle = true
         | 
| 74 55 | 
             
              end
         | 
| 75 56 |  | 
| 76 | 
            -
              # Return shuffle value
         | 
| 77 57 | 
             
              def shuffle?
         | 
| 78 58 | 
             
                @shuffle
         | 
| 79 59 | 
             
              end
         | 
| 60 | 
            +
             | 
| 61 | 
            +
              private
         | 
| 62 | 
            +
             | 
| 63 | 
            +
              def validate(type)
         | 
| 64 | 
            +
                unless TYPES.include? type
         | 
| 65 | 
            +
                  warn "[ERROR] Question type error (#{type})"
         | 
| 66 | 
            +
                  exit 1
         | 
| 67 | 
            +
                end
         | 
| 68 | 
            +
              end
         | 
| 80 69 | 
             
            end
         |