tempo-cli 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +56 -0
- data/README.md +326 -0
- data/Rakefile +65 -0
- data/bin/tempo +477 -0
- data/features/arrange.feature +43 -0
- data/features/checkout.feature +63 -0
- data/features/end.feature +65 -0
- data/features/project.feature +246 -0
- data/features/report.feature +62 -0
- data/features/start.feature +87 -0
- data/features/step_definitions/tempo_steps.rb +138 -0
- data/features/support/env.rb +26 -0
- data/features/tempo.feature +13 -0
- data/features/update.feature +69 -0
- data/lib/file_record/directory.rb +11 -0
- data/lib/file_record/directory_structure/tempo/README.txt +4 -0
- data/lib/file_record/directory_structure/tempo/tempo_projects.yaml +6 -0
- data/lib/file_record/record.rb +120 -0
- data/lib/tempo/controllers/arrange_controller.rb +52 -0
- data/lib/tempo/controllers/base.rb +117 -0
- data/lib/tempo/controllers/checkout_controller.rb +42 -0
- data/lib/tempo/controllers/end_controller.rb +42 -0
- data/lib/tempo/controllers/projects_controller.rb +107 -0
- data/lib/tempo/controllers/records_controller.rb +21 -0
- data/lib/tempo/controllers/report_controller.rb +55 -0
- data/lib/tempo/controllers/start_controller.rb +42 -0
- data/lib/tempo/controllers/update_controller.rb +78 -0
- data/lib/tempo/models/base.rb +176 -0
- data/lib/tempo/models/composite.rb +71 -0
- data/lib/tempo/models/log.rb +194 -0
- data/lib/tempo/models/project.rb +73 -0
- data/lib/tempo/models/time_record.rb +235 -0
- data/lib/tempo/version.rb +3 -0
- data/lib/tempo/views/arrange_view.rb +27 -0
- data/lib/tempo/views/base.rb +82 -0
- data/lib/tempo/views/formatters/base.rb +30 -0
- data/lib/tempo/views/formatters/screen.rb +86 -0
- data/lib/tempo/views/projects_view.rb +82 -0
- data/lib/tempo/views/report_view.rb +26 -0
- data/lib/tempo/views/reporter.rb +70 -0
- data/lib/tempo/views/time_record_view.rb +30 -0
- data/lib/tempo/views/view_records/base.rb +117 -0
- data/lib/tempo/views/view_records/composite.rb +40 -0
- data/lib/tempo/views/view_records/log.rb +28 -0
- data/lib/tempo/views/view_records/project.rb +32 -0
- data/lib/tempo/views/view_records/time_record.rb +48 -0
- data/lib/tempo.rb +26 -0
- data/lib/time_utilities.rb +30 -0
- data/tempo-cli.gemspec +26 -0
- data/test/lib/file_record/directory_test.rb +30 -0
- data/test/lib/file_record/record_test.rb +106 -0
- data/test/lib/tempo/controllers/base_controller_test.rb +60 -0
- data/test/lib/tempo/controllers/project_controller_test.rb +24 -0
- data/test/lib/tempo/models/base_test.rb +173 -0
- data/test/lib/tempo/models/composite_test.rb +76 -0
- data/test/lib/tempo/models/log_test.rb +171 -0
- data/test/lib/tempo/models/project_test.rb +105 -0
- data/test/lib/tempo/models/time_record_test.rb +212 -0
- data/test/lib/tempo/views/base_test.rb +31 -0
- data/test/lib/tempo/views/formatters/base_test.rb +13 -0
- data/test/lib/tempo/views/formatters/screen_test.rb +94 -0
- data/test/lib/tempo/views/reporter_test.rb +40 -0
- data/test/lib/tempo/views/view_records/base_test.rb +77 -0
- data/test/lib/tempo/views/view_records/composite_test.rb +57 -0
- data/test/lib/tempo/views/view_records/log_test.rb +28 -0
- data/test/lib/tempo/views/view_records/project_test.rb +0 -0
- data/test/lib/tempo/views/view_records/time_record_test.rb +0 -0
- data/test/support/factories.rb +177 -0
- data/test/support/helpers.rb +69 -0
- data/test/test_helper.rb +31 -0
- metadata +230 -0
@@ -0,0 +1,117 @@
|
|
1
|
+
# ViewRecords are simplified models, with additional display information, used in views.
|
2
|
+
#
|
3
|
+
# Each viewrecord has a :type, which can be queried in the view to know
|
4
|
+
# what type of record it is managing.
|
5
|
+
#
|
6
|
+
# They also each have a format method, which accept a block, and also includes a default
|
7
|
+
# block which returns a basic formatted string.
|
8
|
+
#
|
9
|
+
# ViewRecords can be nested, for instance the Time ViewRecords contain
|
10
|
+
# a Duration ViewRecord.
|
11
|
+
#
|
12
|
+
# View records should add themselves to the Reporter on init, with the exception of
|
13
|
+
# partials (such as duration), which are managed within other view records
|
14
|
+
#
|
15
|
+
# They have no logic, and so it is up
|
16
|
+
# to the creation method to make sure they are a correct copy of the information
|
17
|
+
# they are representing.
|
18
|
+
|
19
|
+
module Tempo
|
20
|
+
module Views
|
21
|
+
|
22
|
+
class InvalidViewRecordError < Exception
|
23
|
+
end
|
24
|
+
|
25
|
+
module ViewRecords
|
26
|
+
|
27
|
+
# The most simple view records, with a message string
|
28
|
+
# and a category, which defaults to :info. Categories
|
29
|
+
# can be used for color / logging diferentiation.
|
30
|
+
# category :error will raise an error after all viewRecords
|
31
|
+
# have been run through the reporters
|
32
|
+
#
|
33
|
+
class Message
|
34
|
+
attr_accessor :type, :message, :category
|
35
|
+
|
36
|
+
def initialize message, options={}
|
37
|
+
@message = message
|
38
|
+
@category = options.fetch( :category, :info )
|
39
|
+
@type = "message"
|
40
|
+
Reporter.add_view_record self
|
41
|
+
end
|
42
|
+
|
43
|
+
def format &block
|
44
|
+
block ||= lambda {|m| "#{m.message}"}
|
45
|
+
block.call self
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Specifically for managing a time duration, nested in other
|
50
|
+
# view records. This can be used with a start and end time,
|
51
|
+
# or used to manage a sum of times.
|
52
|
+
#
|
53
|
+
# Total duration is stored in seconds.
|
54
|
+
#
|
55
|
+
# Duration records can be further queried for hours and minutes
|
56
|
+
# in order to construct a human redable duration.
|
57
|
+
# This can be used to construct time as #{hours}:#{minutes}
|
58
|
+
# Hours returns the total whole hours, minutes returns the remaining
|
59
|
+
# whole minutes after the hours have been removed from the total.
|
60
|
+
#
|
61
|
+
class Duration
|
62
|
+
attr_accessor :type, :total
|
63
|
+
|
64
|
+
def initialize seconds=0
|
65
|
+
@type = "duration"
|
66
|
+
@total = seconds
|
67
|
+
end
|
68
|
+
|
69
|
+
def format &block
|
70
|
+
block ||= lambda do |d|
|
71
|
+
"#{ d.hours.to_s }:#{ d.minutes.to_s.rjust(2, '0') }"
|
72
|
+
end
|
73
|
+
block.call self
|
74
|
+
end
|
75
|
+
|
76
|
+
def add seconds
|
77
|
+
@total += seconds
|
78
|
+
end
|
79
|
+
|
80
|
+
def subtract seconds
|
81
|
+
@total -= seconds
|
82
|
+
end
|
83
|
+
|
84
|
+
def hours
|
85
|
+
hours = ( @total / 3600 ).to_i
|
86
|
+
end
|
87
|
+
|
88
|
+
def minutes
|
89
|
+
minutes = ( @total / 60 - hours * 60 ).to_i
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# Base model class, used for extending views for any child of Tempo::Model::Base
|
94
|
+
# Sets the id, and type, where type is the class type of the model, for example
|
95
|
+
# "project" for Tempo::Model::Project. ViewReord::Model should handle any type of
|
96
|
+
# tempo model without error, but most likely won't be as useful as a child class
|
97
|
+
# taylored to the specifics of the actual model's child class.
|
98
|
+
#
|
99
|
+
class Model
|
100
|
+
attr_accessor :id, :type
|
101
|
+
|
102
|
+
def initialize model, options={}
|
103
|
+
@id = model.id
|
104
|
+
|
105
|
+
# example: Tempo::Model::Something => "something"
|
106
|
+
@type = /Tempo::Model::(.*)$/.match( model.class.to_s )[1].downcase
|
107
|
+
Reporter.add_view_record self
|
108
|
+
end
|
109
|
+
|
110
|
+
def format &block
|
111
|
+
block ||= lambda {|model| "#{ model.type.capitalize} #{model.id}"}
|
112
|
+
block.call self
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Tempo
|
2
|
+
module Views
|
3
|
+
module ViewRecords
|
4
|
+
|
5
|
+
# Base Composite model class, used for extending views for any child of Tempo::Model::Composite
|
6
|
+
# Inherits the id, and type from ViewRecords::Model, and adds an integer depth to hold the depth
|
7
|
+
# within the tree structure of the Composite model. It is important to note, the ViewRecord has
|
8
|
+
# no way of determining the depth of the model it represents, and this must be supplied to the
|
9
|
+
# instance on instantiation, or after.
|
10
|
+
#
|
11
|
+
# The Composite ViewRecord class also keeps track of the max depth of all of it's members, this
|
12
|
+
# can be used to calculate the padding added to any views.
|
13
|
+
#
|
14
|
+
# The Composite View Model is an abstract model that is extended to create views for children
|
15
|
+
# of the Composite Model class. See ViewRecords::Project for an example.
|
16
|
+
#
|
17
|
+
class Composite < ViewRecords::Model
|
18
|
+
attr_accessor :depth
|
19
|
+
|
20
|
+
class << self
|
21
|
+
def max_depth depth=0
|
22
|
+
@max_depth ||= 0
|
23
|
+
@max_depth = @max_depth > depth ? @max_depth : depth
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def initialize model, options={}
|
28
|
+
super model, options
|
29
|
+
@depth = options.fetch(:depth, 0)
|
30
|
+
self.class.max_depth @depth
|
31
|
+
end
|
32
|
+
|
33
|
+
def format &block
|
34
|
+
block ||= lambda {|model| "#{" " * model.depth}#{ model.type.capitalize} #{model.id}"}
|
35
|
+
block.call self
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Tempo
|
2
|
+
module Views
|
3
|
+
module ViewRecords
|
4
|
+
|
5
|
+
# Base Composite log class, used for extending views for any child of Tempo::Model::Log
|
6
|
+
# Inherits the id, and type from ViewRecords::Model, and adds an start time and date_id.
|
7
|
+
#
|
8
|
+
#
|
9
|
+
# The Log View Model is an abstract model that is extended to create views for children
|
10
|
+
# of the Log Model class. See ViewRecords::TimeRecord for an example.
|
11
|
+
#
|
12
|
+
class Log < ViewRecords::Model
|
13
|
+
attr_accessor :start_time, :d_id
|
14
|
+
|
15
|
+
def initialize model, options={}
|
16
|
+
super model, options
|
17
|
+
@start_time = model.start_time
|
18
|
+
@d_id = model.d_id
|
19
|
+
end
|
20
|
+
|
21
|
+
def format &block
|
22
|
+
block ||= lambda {|model| "#{ model.type.capitalize} #{model.d_id}-#{model.id} #{model.start_time.strftime('%H:%M')}"}
|
23
|
+
block.call self
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Tempo
|
2
|
+
module Views
|
3
|
+
module ViewRecords
|
4
|
+
|
5
|
+
# Project ViewRecords adds the project title, any tags, and a
|
6
|
+
# duration to the composite model. It also keeps track of the
|
7
|
+
# maximum title length of all Project views.
|
8
|
+
#
|
9
|
+
# :depth is inhereted from Composite
|
10
|
+
#
|
11
|
+
class Project < ViewRecords::Composite
|
12
|
+
attr_accessor :title, :tags, :current, :duration
|
13
|
+
|
14
|
+
class << self
|
15
|
+
def max_title_length len=0
|
16
|
+
@max_title_length ||= 0
|
17
|
+
@max_title_length = @max_title_length > len ? @max_title_length : len
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def initialize model, options={}
|
22
|
+
super model, options
|
23
|
+
@title = model.title
|
24
|
+
@tags = model.tags
|
25
|
+
@current = model.current?
|
26
|
+
@duration = Duration.new
|
27
|
+
self.class.max_title_length @title.length
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Tempo
|
2
|
+
module Views
|
3
|
+
module ViewRecords
|
4
|
+
|
5
|
+
# TimeRecord adds a description, project and end_time and running flag
|
6
|
+
# to the Log Record to represent any instance of the TimeRecord model.
|
7
|
+
# It also includes a Duration ViewRecord for presenting the total duration
|
8
|
+
# of the time record.
|
9
|
+
#
|
10
|
+
class TimeRecord < ViewRecords::Log
|
11
|
+
attr_accessor :description, :duration, :end_time, :project, :running
|
12
|
+
|
13
|
+
class << self
|
14
|
+
def max_description_length len=0
|
15
|
+
@max_description_length ||= 0
|
16
|
+
@max_description_length = @max_description_length > len ? @max_description_length : len
|
17
|
+
end
|
18
|
+
|
19
|
+
def max_project_length len=0
|
20
|
+
@max_project_length ||= 0
|
21
|
+
@max_project_length = @max_project_length > len ? @max_project_length : len
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def initialize model, options={}
|
26
|
+
super model, options
|
27
|
+
@description = model.description
|
28
|
+
@description ||= ""
|
29
|
+
@duration = Duration.new model.duration
|
30
|
+
@end_time = model.end_time == :running ? Time.now() : model.end_time
|
31
|
+
@project = model.project_title
|
32
|
+
@running = model.running?
|
33
|
+
self.class.max_description_length @description.length
|
34
|
+
self.class.max_project_length @project.length
|
35
|
+
end
|
36
|
+
|
37
|
+
def format &block
|
38
|
+
block ||= lambda do |m|
|
39
|
+
running = m.running ? "*" : " "
|
40
|
+
description = @description ? "#{m.project}: #{m.description}" : "#{m.project}"
|
41
|
+
"#{m.start_time.strftime('%H:%M')} - #{m.end_time.strftime('%H:%M')}#{running} [#{m.duration.format}] #{description}"
|
42
|
+
end
|
43
|
+
block.call self
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
data/lib/tempo.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# All files required for Tempo to run:
|
2
|
+
|
3
|
+
# Additional functionality to the time class, requires Chronic:
|
4
|
+
require 'time_utilities.rb'
|
5
|
+
|
6
|
+
require 'tempo/version.rb'
|
7
|
+
|
8
|
+
require 'tempo/models/base.rb'
|
9
|
+
require 'tempo/models/composite.rb'
|
10
|
+
require 'tempo/models/log.rb'
|
11
|
+
Dir[File.dirname(__FILE__) + '/tempo/models/*.rb'].each {|file| require file }
|
12
|
+
|
13
|
+
require 'tempo/controllers/base.rb'
|
14
|
+
Dir[File.dirname(__FILE__) + '/tempo/controllers/*.rb'].each {|file| require file }
|
15
|
+
|
16
|
+
Dir[File.dirname(__FILE__) + '/tempo/views/view_records/*.rb'].each {|file| require file }
|
17
|
+
|
18
|
+
require 'tempo/views/reporter.rb'
|
19
|
+
require 'tempo/views/formatters/base.rb'
|
20
|
+
Dir[File.dirname(__FILE__) + '/tempo/views/formatters/*.rb'].each {|file| require file }
|
21
|
+
|
22
|
+
require 'tempo/views/base.rb'
|
23
|
+
Dir[File.dirname(__FILE__) + '/tempo/views/*.rb'].each {|file| require file }
|
24
|
+
|
25
|
+
require 'file_record/directory.rb'
|
26
|
+
require 'file_record/record.rb'
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'chronic'
|
2
|
+
# see also
|
3
|
+
# http://rtmatheson.com/2011/12/rounding-time-to-the-closest-hour-in-ruby/
|
4
|
+
|
5
|
+
class Time
|
6
|
+
|
7
|
+
class << self
|
8
|
+
def parse time
|
9
|
+
# Chronic will usually return nil when unable to parse time
|
10
|
+
# it throws an error, on 't' and a few other string, so we
|
11
|
+
# capture these here an assure that nil is returned
|
12
|
+
begin
|
13
|
+
chron = Chronic.parse time
|
14
|
+
chron.round
|
15
|
+
rescue Exception => e
|
16
|
+
return nil
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
#default to whole minutes
|
22
|
+
def round options={}
|
23
|
+
seconds = 60
|
24
|
+
Time.at((self.to_f / seconds).round * seconds)
|
25
|
+
end
|
26
|
+
|
27
|
+
def add_days days
|
28
|
+
t = self + days * 86400 # 24 * 60 * 60
|
29
|
+
end
|
30
|
+
end
|
data/tempo-cli.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# Ensure we require the local version and not one we might have installed already
|
2
|
+
require File.join([File.dirname(__FILE__),'lib','tempo','version.rb'])
|
3
|
+
spec = Gem::Specification.new do |s|
|
4
|
+
s.name = 'tempo-cli'
|
5
|
+
s.version = Tempo::VERSION
|
6
|
+
s.author = 'Jonathan Gabel'
|
7
|
+
s.email = 'hello@jonathangabel.com'
|
8
|
+
s.homepage = 'http://jonathangabel.com'
|
9
|
+
s.platform = Gem::Platform::RUBY
|
10
|
+
s.summary = 'A command line time tracker for recording by day or by project'
|
11
|
+
s.description = 'Record and report time spent by project'
|
12
|
+
# Add your other files here if you make them
|
13
|
+
# Add lib files to lib.tempo.rb
|
14
|
+
s.files = `git ls-files`.split("\n")
|
15
|
+
s.require_paths << 'lib'
|
16
|
+
s.has_rdoc = true
|
17
|
+
s.bindir = 'bin'
|
18
|
+
s.executables << 'tempo'
|
19
|
+
s.add_development_dependency('rake')
|
20
|
+
s.add_development_dependency('rdoc')
|
21
|
+
s.add_development_dependency('aruba')
|
22
|
+
s.add_development_dependency('turn', '~> 0.9.6')
|
23
|
+
s.add_development_dependency('pry','~> 0.9.12.2')
|
24
|
+
s.add_runtime_dependency('gli','2.6.1')
|
25
|
+
s.add_runtime_dependency "chronic", "~> 0.10.2"
|
26
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
describe FileRecord do
|
4
|
+
|
5
|
+
before do
|
6
|
+
@dir = File.join(Dir.home,"tempo")
|
7
|
+
FileUtils.rm_r @dir if File.exists?(@dir)
|
8
|
+
end
|
9
|
+
|
10
|
+
after do
|
11
|
+
FileUtils.rm_r @dir if File.exists?(@dir)
|
12
|
+
end
|
13
|
+
|
14
|
+
|
15
|
+
describe "Directory" do
|
16
|
+
|
17
|
+
describe "initialize" do
|
18
|
+
|
19
|
+
it "should initialize a new directory structure" do
|
20
|
+
project_file = File.join(@dir, "tempo_projects.yaml")
|
21
|
+
readme = File.join(@dir, "README.txt")
|
22
|
+
|
23
|
+
FileRecord::Directory.create_new
|
24
|
+
File.exists?( @dir ).must_equal true
|
25
|
+
File.exists?( project_file ).must_equal true
|
26
|
+
File.exists?( readme ).must_equal true
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
describe FileRecord do
|
4
|
+
|
5
|
+
before do
|
6
|
+
# See Rakefile for directory prep and cleanup
|
7
|
+
dir = File.join(Dir.home,"tempo")
|
8
|
+
Dir.mkdir(dir, 0700) unless File.exists?(dir)
|
9
|
+
@dir = File.join(Dir.home,"tempo", "tempo_unit_tests")
|
10
|
+
Dir.mkdir(@dir, 0700) unless File.exists?(@dir)
|
11
|
+
end
|
12
|
+
|
13
|
+
describe "Record" do
|
14
|
+
|
15
|
+
before do
|
16
|
+
@file = File.join( @dir, "filerecord_create.txt")
|
17
|
+
end
|
18
|
+
|
19
|
+
after do
|
20
|
+
File.delete(@file) if File.exists?(@file)
|
21
|
+
end
|
22
|
+
|
23
|
+
describe "create" do
|
24
|
+
|
25
|
+
it "should create a new record" do
|
26
|
+
FileRecord::Record.create( @file, "" )
|
27
|
+
File.exists?( @file ).must_equal true
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should raise and error if the file exists" do
|
31
|
+
FileRecord::Record.create( @file, "" )
|
32
|
+
proc { FileRecord::Record.create( @file, "" ) }.must_raise ArgumentError
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should overwrite a file with option :force" do
|
36
|
+
File.open( @file,'w' ) do |f|
|
37
|
+
f.puts "Now this file already exists"
|
38
|
+
end
|
39
|
+
FileRecord::Record.create( @file, "overwrite file", force: true )
|
40
|
+
contents = eval_file_as_array( @file )
|
41
|
+
contents.must_equal ["overwrite file"]
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe "recording a string" do
|
46
|
+
it "should be able to record a string" do
|
47
|
+
FileRecord::Record.create( @file, "a simple string" )
|
48
|
+
contents = eval_file_as_array( @file )
|
49
|
+
contents.must_equal ["a simple string"]
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should be able to record a string as yaml" do
|
53
|
+
FileRecord::Record.create( @file, "a simple string", format: 'yaml' )
|
54
|
+
contents = eval_file_as_array( @file )
|
55
|
+
contents.must_equal ["--- a simple string", "..."]
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe "recording and array" do
|
60
|
+
|
61
|
+
it "should be able to record a shallow array as string" do
|
62
|
+
FileRecord::Record.create( @file, ["a","simple","array"], format: "string" )
|
63
|
+
contents = eval_file_as_array( @file )
|
64
|
+
contents.must_equal ["a","simple","array"]
|
65
|
+
end
|
66
|
+
|
67
|
+
it "should default to recording a shallow array as yaml" do
|
68
|
+
FileRecord::Record.create( @file, ["a","simple","array"] )
|
69
|
+
contents = eval_file_as_array( @file )
|
70
|
+
contents.must_equal ["---", "- a", "- simple", "- array"]
|
71
|
+
end
|
72
|
+
|
73
|
+
it "should record a nested array as yaml" do
|
74
|
+
FileRecord::Record.create( @file, ["a",["nested",["array"]]])
|
75
|
+
contents = eval_file_as_array( @file )
|
76
|
+
contents.must_equal ["---", "- a", "- - nested", " - - array"]
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
describe "recording a hash" do
|
81
|
+
|
82
|
+
it "should default to and record a hash as yaml" do
|
83
|
+
hash = {a: 1, b: true, c: Hash.new, d: "object", with: ['an', 'array']}
|
84
|
+
FileRecord::Record.create( @file, hash )
|
85
|
+
contents = eval_file_as_array( @file )
|
86
|
+
contents.must_equal ["---", ":a: 1", ":b: true", ":c: {}", ":d: object", ":with:", "- an", "- array"]
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
describe "recording a Tempo Model" do
|
91
|
+
|
92
|
+
it "should create a record of all instances" do
|
93
|
+
test_file = File.join(ENV['HOME'],'tempo','tempo_animals.yaml')
|
94
|
+
File.delete( test_file ) if File.exists?( test_file )
|
95
|
+
pantherinae_factory
|
96
|
+
FileRecord::Record.save_model( Tempo::Model::Animal )
|
97
|
+
contents = eval_file_as_array( test_file )
|
98
|
+
contents.must_equal [ "---", ":id: 1", ":genious: Panthera", ":species: p. tigris",
|
99
|
+
"---", ":id: 2", ":genious: Panthera", ":species: p. leo",
|
100
|
+
"---", ":id: 3", ":genious: Panthera", ":species: p. onca",
|
101
|
+
"---", ":id: 4", ":genious: Panthera", ":species: p. pardus",
|
102
|
+
"---", ":id: 5", ":genious: Panthera", ":species: p. zdanskyi"]
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
describe Tempo::Controllers::Base do
|
4
|
+
describe "Class Methods" do
|
5
|
+
|
6
|
+
before do
|
7
|
+
@controller = Tempo::Controllers::Base
|
8
|
+
end
|
9
|
+
|
10
|
+
describe "fuzzy match" do
|
11
|
+
describe "matching an array" do
|
12
|
+
|
13
|
+
before do
|
14
|
+
@haystack = ['sheep','ducks','peeping-ducks','sheep ducks']
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should find all matches to a single match object" do
|
18
|
+
@controller.fuzzy_match( @haystack, 'ducks' ).must_equal ['ducks','peeping-ducks','sheep ducks']
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should find all matches to an array of match objects" do
|
22
|
+
@controller.fuzzy_match( @haystack, ['ducks', 'eep'] ).must_equal ['peeping-ducks','sheep ducks']
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe "matching a Tempo Model" do
|
27
|
+
|
28
|
+
before do
|
29
|
+
pantherinae_factory
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should find all matches to a single match object" do
|
33
|
+
matches = @controller.fuzzy_match( Tempo::Model::Animal, "Panthera", "genious" )
|
34
|
+
matches.length.must_equal 5
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should find all matches to an array of match objects" do
|
38
|
+
matches = @controller.fuzzy_match( Tempo::Model::Animal, ["p. ", "a"], "species" )
|
39
|
+
# "p. onca", "p. pardus" "p. zdanskyi"
|
40
|
+
matches.length.must_equal 3
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe "reassemble to args" do
|
46
|
+
|
47
|
+
it "should reassemble the args passed in as an array" do
|
48
|
+
test_args = ["an", "array", "of", "args"]
|
49
|
+
test_flag = "I'm"
|
50
|
+
@controller.reassemble_the( test_args ).must_equal "an array of args"
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should reassemble the args with a flag in the front" do
|
54
|
+
test_args = ["an", "array", "of", "args"]
|
55
|
+
test_flag = "I'm"
|
56
|
+
@controller.reassemble_the( test_args, test_flag ).must_equal "I'm an array of args"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
describe Tempo::Controllers::Projects do
|
4
|
+
describe "Class Methods" do
|
5
|
+
|
6
|
+
before do
|
7
|
+
@controller = Tempo::Controllers::Projects
|
8
|
+
end
|
9
|
+
|
10
|
+
describe "exact match" do
|
11
|
+
|
12
|
+
before do
|
13
|
+
project_factory
|
14
|
+
Tempo::Model::Project.new title: 'horticulture'
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should find an exact match" do
|
18
|
+
match1 = @controller.filter_projects_by_title({ exact: true }, ["horticulture"])
|
19
|
+
match1.length.must_equal 1
|
20
|
+
match1[0].title.must_equal "horticulture"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|