forgetful 0.4.0 → 0.5.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.
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source "http://rubygems.org"
2
+
3
+ group :development do
4
+ gem "rspec"
5
+ gem "bundler"
6
+ gem "jeweler"
7
+ gem "fastercsv" if RUBY_VERSION < '1.9'
8
+ end
@@ -0,0 +1,26 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ diff-lcs (1.1.2)
5
+ git (1.2.5)
6
+ jeweler (1.6.0)
7
+ bundler (~> 1.0.0)
8
+ git (>= 1.2.5)
9
+ rake
10
+ rake (0.8.7)
11
+ rspec (2.3.0)
12
+ rspec-core (~> 2.3.0)
13
+ rspec-expectations (~> 2.3.0)
14
+ rspec-mocks (~> 2.3.0)
15
+ rspec-core (2.3.1)
16
+ rspec-expectations (2.3.0)
17
+ diff-lcs (~> 1.1.2)
18
+ rspec-mocks (2.3.0)
19
+
20
+ PLATFORMS
21
+ ruby
22
+
23
+ DEPENDENCIES
24
+ bundler
25
+ jeweler
26
+ rspec
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2010 Jonathan Palardy
1
+ Copyright (c) 2011 Jonathan Palardy
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person
4
4
  obtaining a copy of this software and associated documentation
@@ -1,7 +1,5 @@
1
1
  = forgetful
2
2
 
3
- (tested with ree-1.8.7 and 1.9.2dev)
4
-
5
3
  A minimal command-line app implementing the SuperMemo 2 algorithm.
6
4
 
7
5
  The SuperMemo algorithm is designed to refresh your memory at
@@ -53,4 +51,4 @@ Enjoy!
53
51
 
54
52
  == Copyright
55
53
 
56
- Copyright (c) 2010 Jonathan Palardy. See LICENSE for details.
54
+ Copyright (c) 2011 Jonathan Palardy. See LICENSE for details.
data/Rakefile CHANGED
@@ -1,44 +1,32 @@
1
1
  require 'rubygems'
2
- require 'rake'
3
-
2
+ require 'bundler'
4
3
  begin
5
- require 'jeweler'
6
- Jeweler::Tasks.new do |gem|
7
- gem.name = "forgetful"
8
- gem.summary = "A minimal command-line implementation of the SuperMemo 2 algorithm."
9
- gem.description = "A minimal command-line implementation of the SuperMemo 2 algorithm."
10
- gem.email = "jonathan.palardy@gmail.com"
11
- gem.homepage = "http://github.com/jpalardy/forgetful"
12
- gem.authors = ["Jonathan Palardy"]
13
- gem.add_development_dependency "rspec", "= 1.2.9"
14
- end
15
- Jeweler::GemcutterTasks.new
16
- rescue LoadError
17
- puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
4
+ Bundler.setup(:default, :development)
5
+ rescue Bundler::BundlerError => e
6
+ $stderr.puts e.message
7
+ $stderr.puts "Run `bundle install` to install missing gems"
8
+ exit e.status_code
18
9
  end
10
+ require 'rake'
19
11
 
20
- require 'spec/rake/spectask'
21
- Spec::Rake::SpecTask.new(:spec) do |spec|
22
- spec.libs << 'lib' << 'spec'
23
- spec.spec_files = FileList['spec/**/*_spec.rb']
12
+ require 'jeweler'
13
+ Jeweler::Tasks.new do |gem|
14
+ gem.name = "forgetful"
15
+ gem.homepage = "http://github.com/jpalardy/forgetful"
16
+ gem.license = "MIT"
17
+ gem.summary = "A minimal command-line implementation of the SuperMemo 2 algorithm."
18
+ gem.description = "A minimal command-line implementation of the SuperMemo 2 algorithm."
19
+ gem.email = "jonathan.palardy@gmail.com"
20
+ gem.authors = ["Jonathan Palardy"]
21
+ # dependencies defined in Gemfile
24
22
  end
23
+ Jeweler::RubygemsDotOrgTasks.new
25
24
 
26
- Spec::Rake::SpecTask.new(:rcov) do |spec|
27
- spec.libs << 'lib' << 'spec'
28
- spec.pattern = 'spec/**/*_spec.rb'
29
- spec.rcov = true
25
+ require 'rspec/core'
26
+ require 'rspec/core/rake_task'
27
+ RSpec::Core::RakeTask.new(:spec) do |spec|
28
+ spec.pattern = FileList['spec/**/*_spec.rb']
30
29
  end
31
30
 
32
- task :spec => :check_dependencies
33
-
34
31
  task :default => :spec
35
32
 
36
- require 'rake/rdoctask'
37
- Rake::RDocTask.new do |rdoc|
38
- version = File.exist?('VERSION') ? File.read('VERSION') : ""
39
-
40
- rdoc.rdoc_dir = 'rdoc'
41
- rdoc.title = "forgetful #{version}"
42
- rdoc.rdoc_files.include('README*')
43
- rdoc.rdoc_files.include('lib/**/*.rb')
44
- end
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.4.0
1
+ 0.5.0
@@ -1,7 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require "forgetful"
4
- require "forgetful/cli"
5
3
  require 'optparse'
6
4
 
7
5
  #-------------------------------------------------
@@ -29,7 +27,6 @@ end
29
27
 
30
28
  def parse(args)
31
29
  options = { :filenames => [],
32
- :verbose => false,
33
30
  :action => :quiz,
34
31
  :delay => 0..0 }
35
32
 
@@ -53,10 +50,6 @@ def parse(args)
53
50
  arg.separator ""
54
51
  arg.separator "Common options:"
55
52
 
56
- arg.on( '-v', '--verbose', "Show more information") do
57
- options[:verbose] = true
58
- end
59
-
60
53
  arg.on( '--version', "Show version") do
61
54
  puts File.read(File.join(File.dirname(__FILE__), "..", "VERSION"))
62
55
  exit
@@ -80,12 +73,35 @@ end
80
73
 
81
74
  options = parse(ARGV)
82
75
 
76
+ $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
77
+ require "forgetful"
78
+ require "forgetful/extensions/csv/reminder_file"
79
+ require "forgetful/questionaire"
80
+ require "forgetful/cli/quizzer"
81
+
83
82
  begin
84
83
  options[:filenames].each do |filename|
85
- ReminderFile.new(filename, options[:verbose], options[:delay]).send(options[:action])
84
+ csv_file = ReminderFile.new(filename, options[:delay])
85
+
86
+ case options[:action]
87
+ when :touch
88
+ puts "### TOUCH: #{filename}"
89
+ # subtle -- reading and writing back a file will:
90
+ # 1. validate the file
91
+ # 2. add the date column
92
+ csv_file.write(csv_file.read)
93
+ when :quiz
94
+ puts "### QUIZ: #{filename}"
95
+
96
+ questionaire = Questionaire.new(csv_file)
97
+ quizzer = CLI::Quizzer.new(questionaire)
98
+
99
+ quizzer.quiz
100
+ else
101
+ raise 'hell' # this should not happen
102
+ end
86
103
  end
87
104
  rescue Interrupt
88
105
  puts
89
106
  # aborting ... won't save file
90
107
  end
91
-
@@ -1,5 +1,6 @@
1
1
 
2
+ require "date"
3
+
2
4
  require "forgetful/supermemo"
3
5
  require "forgetful/reminder"
4
- require "forgetful/csv_ext/reminder"
5
6
 
@@ -0,0 +1,46 @@
1
+ module CLI
2
+ class Quizzer
3
+ attr_reader :questionaire
4
+
5
+ def initialize(questionaire)
6
+ @questionaire = questionaire
7
+ end
8
+
9
+ def quiz
10
+ results = []
11
+ questions = questionaire.questions.sort_by { rand }
12
+
13
+ begin
14
+ questions.each_with_index do |question, i|
15
+ q = ask(question, i+1, questions.size)
16
+ results << [question[:id], q]
17
+ end
18
+ rescue EOFError
19
+ # tolerate Ctrl-D, skips the rest of the quiz
20
+ end
21
+
22
+ questionaire.grade(results)
23
+ end
24
+
25
+ private
26
+ def ask(question, i, n)
27
+ width = "#{n}/#{n}. ".size
28
+ padding = " " * width
29
+
30
+ print "#{i}/#{n}. ".rjust(width) + "Q: #{question[:question]}"
31
+ readline
32
+
33
+ puts padding + "A: #{question[:answer]}"
34
+
35
+ while true
36
+ print padding + "? "
37
+ answer = readline.chomp
38
+
39
+ if answer =~ /\A[0-5]\Z/
40
+ puts
41
+ return answer.to_i
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,17 @@
1
+ require "csv"
2
+ if CSV.const_defined? :Reader
3
+ # Ruby 1.8 compatible
4
+ require 'fastercsv'
5
+ Object.send(:remove_const, :CSV)
6
+ CSV = FasterCSV
7
+ end
8
+
9
+ class Reminder
10
+ def to_csv
11
+ if history.empty?
12
+ [question, answer, due_on.to_s]
13
+ else
14
+ [question, answer, due_on.to_s, history.join]
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,50 @@
1
+ require 'forgetful/extensions/csv/reminder'
2
+
3
+ class ReminderFile
4
+ attr_reader :filename, :delay
5
+
6
+ def initialize(filename, delay=0..0)
7
+ @filename = filename
8
+ @delay = delay
9
+ end
10
+
11
+ def read
12
+ File.open(filename) do |file|
13
+ parse_csv(file)
14
+ end
15
+ end
16
+
17
+ def write(reminders)
18
+ data = CSV.generate do |csv|
19
+ reminders.each do |reminder|
20
+ csv << reminder.to_csv
21
+ end
22
+ end
23
+
24
+ File.open(filename, "w") do |file|
25
+ file.write(data)
26
+ end
27
+ end
28
+
29
+ private
30
+ def parse_csv(io)
31
+ converters = [lambda { |question| question },
32
+ lambda { |answer| answer },
33
+ lambda { |due_on| Date.parse(due_on) },
34
+ lambda { |history| history.scan(/./).map { |x| x.to_i } }]
35
+
36
+ CSV.parse(io, :skip_blanks => true).map do |list|
37
+ list = list.zip(converters).map { |col, converter| converter[col] }
38
+ list << Date.today + rand_from_range(delay) if list.length == 2 # missing date
39
+ Reminder.new(*list)
40
+ end
41
+ end
42
+
43
+ def rand_from_range(range)
44
+ if RUBY_VERSION < '1.9'
45
+ (range.min + (range.max - range.min + 1) * rand).floor
46
+ else
47
+ Random.new.rand(range)
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,37 @@
1
+ class Questionaire
2
+ attr_reader :source
3
+
4
+ def initialize(source)
5
+ @source = source
6
+ end
7
+
8
+ def questions
9
+ i = 0
10
+ reminders.map { |reminder| pair = [reminder, i]; i+=1; pair }.
11
+ select { |reminder, i| reminder.due_on <= Date.today }.
12
+ map do |reminder, i|
13
+ { :id => i,
14
+ :question => reminder.question,
15
+ :answer => reminder.answer }
16
+ end
17
+ end
18
+
19
+ def grade(results)
20
+ source.write(update(reminders, results))
21
+ end
22
+
23
+ #-------------------------------------------------
24
+ private
25
+ # results is a list of pairs: [id, q]
26
+ def update(reminders, results)
27
+ reminders = reminders.dup
28
+ results.each do |id,q|
29
+ reminders[id] = reminders.fetch(id).next(q)
30
+ end
31
+ reminders
32
+ end
33
+
34
+ def reminders
35
+ source.read
36
+ end
37
+ end
@@ -0,0 +1,49 @@
1
+ require 'spec_helper'
2
+ require 'forgetful/questionaire'
3
+
4
+ describe "questionaire" do
5
+ describe "source" do
6
+ before do
7
+ @source = double("source", :read => [])
8
+ @questionaire = Questionaire.new(@source)
9
+ end
10
+
11
+ it "should be pulled" do
12
+ @source.should_receive(:read).once
13
+ @questionaire.questions
14
+ end
15
+
16
+ it "should be pushed" do
17
+ @source.should_receive(:read).once
18
+ @source.should_receive(:write).once
19
+ @questionaire.grade([])
20
+ end
21
+ end
22
+
23
+ describe "reminders" do
24
+ before do
25
+ @reminder1 = double("reminder1", :due_on => Date.today, :question => "some question", :answer => "some answer")
26
+ @reminder2 = double("reminder2", :due_on => (Date.today - 1), :question => "another question", :answer => "another answer")
27
+ @reminder3 = double("reminder3", :due_on => (Date.today + 1), :question => "final question", :answer => "final answer")
28
+ @source = double("source", :read => [@reminder1, @reminder2, @reminder3])
29
+
30
+ @questionaire = Questionaire.new(@source)
31
+ end
32
+
33
+ it "should be converted" do
34
+ @questionaire.questions.should == [{:question=>"some question", :answer=>"some answer", :id=>0}, {:question=>"another question", :answer=>"another answer", :id=>1}]
35
+ end
36
+
37
+ it "should be adjusted" do
38
+ @reminder2_1 = double("reminder2_1")
39
+ @reminder2.stub(:next) { @reminder2_1 }
40
+
41
+ @source.should_receive(:write).once.with([@reminder1, @reminder2_1, @reminder3])
42
+ @questionaire.grade([[1, 3]])
43
+ end
44
+
45
+ it "should raise on unknown indices" do
46
+ expect{ @questionaire.grade([[42, 3]]) }.to raise_error(IndexError)
47
+ end
48
+ end
49
+ end
@@ -1,67 +1,25 @@
1
- require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
-
3
- describe "reminder (csv)" do
4
- describe "parsing the bare format" do
5
- before do
6
- csv = <<END
7
- carbon,6
8
- nitrogen,7
9
- oxygen,8
10
- END
11
- @reminders = Reminder.parse_csv(csv)
12
- end
13
-
14
- it "should have the right number of lines" do
15
- @reminders.size.should == 3
16
- end
17
-
18
- it "should have the right data" do
19
- @reminders[0].to_a.should == ['carbon', '6',Date.today, []]
20
- @reminders[1].to_a.should == ['nitrogen','7',Date.today, []]
21
- @reminders[2].to_a.should == ['oxygen', '8',Date.today, []]
22
- end
1
+ require 'spec_helper'
2
+ require 'forgetful/extensions/csv/reminder'
3
+
4
+ describe "reminder_csv" do
5
+ before do
6
+ @question = "some question"
7
+ @answer = "some answer"
8
+ @date = Date.today + 1
23
9
  end
24
10
 
25
- describe "parsing the full format" do
26
- before do
27
- csv = <<END
28
- carbon,6,2008-11-11,5
29
- nitrogen,7,2008-11-11,535
30
-
31
- oxygen,8,2008-11-11,5042
32
- END
33
- @reminders = Reminder.parse_csv(csv)
34
- end
35
-
36
- it "should have the right number of lines" do
37
- @reminders.size.should == 3
38
- end
39
-
40
- it "should have the right data" do
41
- @reminders[0].to_a.should == ['carbon', '6',Date.parse('2008-11-11'), [5]]
42
- @reminders[1].to_a.should == ['nitrogen','7',Date.parse('2008-11-11'), [5,3,5]]
43
- @reminders[2].to_a.should == ['oxygen', '8',Date.parse('2008-11-11'), [5,0,4,2]]
44
- end
11
+ it "should handle 2 fields" do
12
+ reminder = Reminder.new(@question, @answer)
13
+ reminder.to_csv.should == [@question, @answer, Date.today.to_s]
45
14
  end
46
15
 
47
- describe "generating csv" do
48
- before do
49
- @reminders = []
50
- @reminders << Reminder.new('carbon','6')
51
- @reminders << Reminder.new('nitrogen','7').next(5).next(5).next(5)
52
- @reminders << Reminder.new('oxygen','8').next(1).next(1).next(1)
53
- @reminders << Reminder.new('fluorine','9').next(1).next(5).next(1).next(5)
54
- @reminders << Reminder.new('neon','10',Date.today+5)
55
- end
16
+ it "should handle 3 fields" do
17
+ reminder = Reminder.new(@question, @answer, @date)
18
+ reminder.to_csv.should == [@question, @answer, @date.to_s]
19
+ end
56
20
 
57
- it "should generate appropriate csv" do
58
- Reminder.generate_csv(@reminders).should == <<END
59
- carbon,6,#{Date.today}
60
- nitrogen,7,#{(Date.today+16)},555
61
- oxygen,8,#{(Date.today+1)},111
62
- fluorine,9,#{(Date.today+1)},1515
63
- neon,10,#{(Date.today+5)}
64
- END
65
- end
21
+ it "should handle 4 fields" do
22
+ reminder = Reminder.new(@question, @answer, @date, [1,2,3])
23
+ reminder.to_csv.should == [@question, @answer, @date.to_s, "123"]
66
24
  end
67
25
  end
@@ -0,0 +1,70 @@
1
+ require 'spec_helper'
2
+ require 'forgetful/extensions/csv/reminder_file'
3
+
4
+ describe "reminder file parsing (csv)" do
5
+ before do
6
+ @filename = "DUMMY"
7
+ end
8
+
9
+ describe "parsing the bare format" do
10
+ before do
11
+ csv = <<END
12
+ carbon,6
13
+ nitrogen,7
14
+ oxygen,8
15
+ END
16
+ @reminder_file = ReminderFile.new(@filename)
17
+ @reminders = @reminder_file.send(:parse_csv, csv)
18
+ end
19
+
20
+ it "should have the right number of lines" do
21
+ @reminders.size.should == 3
22
+ end
23
+
24
+ it "should have the right data" do
25
+ @reminders[0].to_a.should == ['carbon', '6',Date.today, []]
26
+ @reminders[1].to_a.should == ['nitrogen','7',Date.today, []]
27
+ @reminders[2].to_a.should == ['oxygen', '8',Date.today, []]
28
+ end
29
+ end
30
+
31
+ describe "parsing the bare format (with delay)" do
32
+ before do
33
+ @csv = <<END
34
+ carbon,6
35
+ END
36
+ @reminder_file = ReminderFile.new(@filename, 3..7)
37
+ @date_range = (Date.today + 3)..(Date.today + 7)
38
+ end
39
+
40
+ it "should have dates in the given range" do
41
+ 100.times do
42
+ reminder = @reminder_file.send(:parse_csv, @csv).first
43
+ @date_range.should include(reminder.due_on)
44
+ end
45
+ end
46
+ end
47
+
48
+ describe "parsing the full format" do
49
+ before do
50
+ csv = <<END
51
+ carbon,6,2008-11-11,5
52
+ nitrogen,7,2008-11-11,535
53
+
54
+ oxygen,8,2008-11-11,5042
55
+ END
56
+ @reminder_file = ReminderFile.new(@filename)
57
+ @reminders = @reminder_file.send(:parse_csv, csv)
58
+ end
59
+
60
+ it "should have the right number of lines" do
61
+ @reminders.size.should == 3
62
+ end
63
+
64
+ it "should have the right data" do
65
+ @reminders[0].to_a.should == ['carbon', '6',Date.parse('2008-11-11'), [5]]
66
+ @reminders[1].to_a.should == ['nitrogen','7',Date.parse('2008-11-11'), [5,3,5]]
67
+ @reminders[2].to_a.should == ['oxygen', '8',Date.parse('2008-11-11'), [5,0,4,2]]
68
+ end
69
+ end
70
+ end
@@ -1,4 +1,4 @@
1
- require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
1
+ require 'spec_helper'
2
2
 
3
3
  describe "reminder" do
4
4
  before do
@@ -18,7 +18,7 @@ describe "reminder" do
18
18
  end
19
19
 
20
20
  it "should have an ef of 2.5" do
21
- @reminder.ef.should be_close(2.5, 0.0001)
21
+ @reminder.ef.should be_within(EPSILON).of(2.5)
22
22
  end
23
23
  end
24
24
 
@@ -39,7 +39,7 @@ describe "reminder" do
39
39
  end
40
40
 
41
41
  it "should have an ef of 2.8 (based on history)" do
42
- @reminder.ef.should be_close(2.8, 0.0001)
42
+ @reminder.ef.should be_within(EPSILON).of(2.8)
43
43
  end
44
44
  end
45
45
 
@@ -1,8 +1,5 @@
1
- $LOAD_PATH.unshift(File.dirname(__FILE__))
2
- $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
1
+
3
2
  require 'forgetful'
4
- require 'spec'
5
- require 'spec/autorun'
6
3
 
7
- Spec::Runner.configure do |config|
8
- end
4
+ EPSILON = 0.00001
5
+
@@ -1,4 +1,4 @@
1
- require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
1
+ require 'spec_helper'
2
2
 
3
3
  describe "supermemo" do
4
4
  describe "next_i" do
@@ -62,7 +62,7 @@ describe "supermemo" do
62
62
 
63
63
  qs.zip(results).each do |q,result|
64
64
  it "-- next_ef(q, 2.5) = #{result}" do
65
- SuperMemo::next_ef(q, 2.5).should be_close(result, 0.00001)
65
+ SuperMemo::next_ef(q, 2.5).should be_within(EPSILON).of(result)
66
66
  end
67
67
  end
68
68
  end
@@ -73,7 +73,7 @@ describe "supermemo" do
73
73
 
74
74
  qs.zip(results).each do |q,result|
75
75
  it "-- next_ef(q, 1.3) = #{result}" do
76
- SuperMemo::next_ef(q, 1.3).should be_close(result, 0.00001)
76
+ SuperMemo::next_ef(q, 1.3).should be_within(EPSILON).of(result)
77
77
  end
78
78
  end
79
79
  end
@@ -86,7 +86,7 @@ describe "supermemo" do
86
86
  it "-- traverse(#{history.inspect}) = #{result.inspect}" do
87
87
  ef, i, interval = SuperMemo::traverse(history)
88
88
 
89
- ef.should be_close(result[0], 0.00001)
89
+ ef.should be_within(EPSILON).of(result[0])
90
90
  i.should == result[1]
91
91
  interval.should == result[2]
92
92
  end
metadata CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
4
4
  prerelease: false
5
5
  segments:
6
6
  - 0
7
- - 4
7
+ - 5
8
8
  - 0
9
- version: 0.4.0
9
+ version: 0.5.0
10
10
  platform: ruby
11
11
  authors:
12
12
  - Jonathan Palardy
@@ -14,24 +14,48 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2011-04-19 00:00:00 -04:00
17
+ date: 2011-05-14 00:00:00 -04:00
18
18
  default_executable: forgetful
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
21
21
  name: rspec
22
- prerelease: false
23
22
  requirement: &id001 !ruby/object:Gem::Requirement
24
23
  none: false
25
24
  requirements:
26
- - - "="
25
+ - - ">="
27
26
  - !ruby/object:Gem::Version
28
27
  segments:
29
- - 1
30
- - 2
31
- - 9
32
- version: 1.2.9
28
+ - 0
29
+ version: "0"
33
30
  type: :development
31
+ prerelease: false
34
32
  version_requirements: *id001
33
+ - !ruby/object:Gem::Dependency
34
+ name: bundler
35
+ requirement: &id002 !ruby/object:Gem::Requirement
36
+ none: false
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ segments:
41
+ - 0
42
+ version: "0"
43
+ type: :development
44
+ prerelease: false
45
+ version_requirements: *id002
46
+ - !ruby/object:Gem::Dependency
47
+ name: jeweler
48
+ requirement: &id003 !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ segments:
54
+ - 0
55
+ version: "0"
56
+ type: :development
57
+ prerelease: false
58
+ version_requirements: *id003
35
59
  description: A minimal command-line implementation of the SuperMemo 2 algorithm.
36
60
  email: jonathan.palardy@gmail.com
37
61
  executables:
@@ -42,6 +66,9 @@ extra_rdoc_files:
42
66
  - LICENSE
43
67
  - README.rdoc
44
68
  files:
69
+ - .rspec
70
+ - Gemfile
71
+ - Gemfile.lock
45
72
  - LICENSE
46
73
  - README.rdoc
47
74
  - Rakefile
@@ -50,19 +77,22 @@ files:
50
77
  - examples/katakana_romaji.csv
51
78
  - examples/romaji_katakana.csv
52
79
  - lib/forgetful.rb
53
- - lib/forgetful/cli.rb
54
- - lib/forgetful/csv_ext/reminder.rb
80
+ - lib/forgetful/cli/quizzer.rb
81
+ - lib/forgetful/extensions/csv/reminder.rb
82
+ - lib/forgetful/extensions/csv/reminder_file.rb
83
+ - lib/forgetful/questionaire.rb
55
84
  - lib/forgetful/reminder.rb
56
85
  - lib/forgetful/supermemo.rb
86
+ - spec/questionaire_spec.rb
57
87
  - spec/reminder_csv_spec.rb
88
+ - spec/reminder_file_spec.rb
58
89
  - spec/reminder_spec.rb
59
- - spec/spec.opts
60
90
  - spec/spec_helper.rb
61
91
  - spec/supermemo_spec.rb
62
92
  has_rdoc: true
63
93
  homepage: http://github.com/jpalardy/forgetful
64
- licenses: []
65
-
94
+ licenses:
95
+ - MIT
66
96
  post_install_message:
67
97
  rdoc_options: []
68
98
 
@@ -73,6 +103,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
73
103
  requirements:
74
104
  - - ">="
75
105
  - !ruby/object:Gem::Version
106
+ hash: -36298353
76
107
  segments:
77
108
  - 0
78
109
  version: "0"
@@ -91,8 +122,5 @@ rubygems_version: 1.3.7
91
122
  signing_key:
92
123
  specification_version: 3
93
124
  summary: A minimal command-line implementation of the SuperMemo 2 algorithm.
94
- test_files:
95
- - spec/reminder_csv_spec.rb
96
- - spec/reminder_spec.rb
97
- - spec/spec_helper.rb
98
- - spec/supermemo_spec.rb
125
+ test_files: []
126
+
@@ -1,78 +0,0 @@
1
-
2
- class ReminderFile
3
- attr_reader :filename, :verbose, :delay
4
-
5
- def initialize(filename, verbose=false, delay=0..0)
6
- @filename = filename
7
- @verbose = verbose
8
- @delay = delay
9
- end
10
-
11
- def reminders
12
- @reminders ||= Reminder.read_csv(filename, delay)
13
- end
14
-
15
- def write(reminders)
16
- raise "Writing back a different number of reminders than read from file (#{reminders.size} != #{@reminders.size})" if reminders.size != @reminders.size
17
- Reminder.write_csv(filename, reminders)
18
- end
19
-
20
- # subtle -- reading and writing back a file will:
21
- # 1. validate the file
22
- # 2. add the date column
23
- def touch
24
- puts "### TOUCH: #{filename}"
25
- write(reminders)
26
- end
27
-
28
- def quiz
29
- puts "### QUIZ: #{filename}"
30
- dues, not_dues = reminders.partition { |reminder| reminder.due_on <= Date.today }
31
- gradeds, ungradeds = quiz_map(dues.sort_by { rand })
32
-
33
- write(gradeds + ungradeds + not_dues)
34
-
35
- faileds = gradeds.select { |reminder| reminder.review? }
36
- until faileds.empty? || ungradeds.any?
37
- puts "### REVIEW: #{filename}"
38
- gradeds, ungradeds = quiz_map(faileds.sort_by { rand })
39
- faileds = gradeds.select { |reminder| reminder.review? }
40
- end
41
- end
42
-
43
- private
44
- def quiz_map(reminders)
45
- gradeds = []
46
- ungradeds = reminders.dup
47
-
48
- reminders.each_with_index do |reminder, i|
49
- q = ask(reminder, i+1, reminders.size)
50
- gradeds << ungradeds.shift.next(q)
51
- end
52
- rescue EOFError
53
- # tolerate Ctrl-D, skips the rest of the quiz
54
- ensure
55
- return gradeds, ungradeds
56
- end
57
-
58
- def ask(reminder, i, n)
59
- width = "#{n}/#{n}. ".size
60
- padding = " " * width
61
-
62
- print "#{i}/#{n}. ".rjust(width) + "Q: #{reminder.question}"
63
- readline
64
-
65
- puts padding + "A: #{reminder.answer}"
66
- puts padding + " %.2f -> %s" % [reminder.ef, reminder.history.join] if verbose
67
-
68
- while true
69
- print padding + "? "
70
- answer = readline.chomp
71
-
72
- if answer =~ /\A[0-5]\Z/
73
- puts
74
- return answer.to_i
75
- end
76
- end
77
- end
78
- end
@@ -1,51 +0,0 @@
1
- require "csv"
2
- if CSV.const_defined? :Reader
3
- # Ruby 1.8 compatible
4
- require 'rubygems'
5
- require 'fastercsv'
6
- Object.send(:remove_const, :CSV)
7
- CSV = FasterCSV
8
- end
9
-
10
- class Reminder
11
- def to_csv
12
- if history.empty?
13
- [question, answer, due_on.to_s]
14
- else
15
- [question, answer, due_on.to_s, history.join]
16
- end
17
- end
18
-
19
- def self.read_csv(filename, delay=0..0)
20
- File.open(filename) do |file|
21
- self.parse_csv(file, delay)
22
- end
23
- end
24
-
25
- def self.parse_csv(io, delay=0..0)
26
- converters = [lambda { |question| question },
27
- lambda { |answer| answer },
28
- lambda { |due_on| Date.parse(due_on) },
29
- lambda { |history| history.scan(/./).map { |x| x.to_i } }]
30
-
31
- CSV.parse(io, :skip_blanks => true).map do |list|
32
- list = list.zip(converters).map { |col, converter| converter[col] }
33
- list << Date.today + Random.new.rand(delay) if list.length == 2 # missing date
34
- new(*list)
35
- end
36
- end
37
-
38
- def self.write_csv(filename, reminders)
39
- File.open(filename, "w") do |file|
40
- file.write(generate_csv(reminders))
41
- end
42
- end
43
-
44
- def self.generate_csv(reminders)
45
- CSV.generate do |csv|
46
- reminders.each do |reminder|
47
- csv << reminder.to_csv
48
- end
49
- end
50
- end
51
- end
@@ -1 +0,0 @@
1
- --color --backtrace