forgetful 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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