tempo-cli 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|