muon 0.0.2

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 ADDED
@@ -0,0 +1 @@
1
+ Gemfile.lock
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source 'http://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in muon-cli.gemspec
4
+ gemspec
5
+
6
+ # gem 'ruby-debug19'
data/README.md ADDED
@@ -0,0 +1,52 @@
1
+ muon
2
+ =================
3
+
4
+ muon is going to be a distributed time tracking tool. It already tracks time, but it's not distributed yet.
5
+
6
+ Installation
7
+ ------------
8
+ ```
9
+ gem install muon
10
+ ```
11
+
12
+ Usage
13
+ ------------
14
+ ```
15
+ cd ~/myproject
16
+ muon init
17
+ muon start
18
+ # do some work
19
+ muon stop
20
+ muon log
21
+ ```
22
+
23
+ You'll find more details via `muon help`.
24
+
25
+ Configuration
26
+ -------------
27
+ You can set up some handy aliases:
28
+ ```
29
+ muon config --global alias.a start
30
+ muon config --global alias.z stop
31
+ muon config --global alias.st status
32
+ ```
33
+
34
+ Bash completion
35
+ ------------
36
+ ```
37
+ source /path/to/muon/extras/muon-completion.bash
38
+ ```
39
+
40
+ Bash prompt
41
+ ------------
42
+ ```
43
+ source /path/to/muon/extras/muon-prompt.sh
44
+ ```
45
+ Then add `$(__muon_ps1)` somewhere in your $PS1, for example:
46
+ ```
47
+ export PS1='\w$(__muon_ps1) \$ '
48
+ ```
49
+ Or with some colors:
50
+ ```
51
+ export PS1='\w\[\033[31m\]$(__muon_ps1) \[\033[00m\]\$ '
52
+ ```
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+ require "rake/testtask"
4
+
5
+ Rake::TestTask.new do |t|
6
+ t.libs << "lib"
7
+ t.libs << "test"
8
+ t.test_files = FileList["test/*_test.rb"]
9
+ end
10
+
11
+ task :default => :test
data/bin/muon ADDED
@@ -0,0 +1,103 @@
1
+ #!/usr/bin/env ruby
2
+ $:.unshift File.expand_path(__FILE__ + '/../../lib')
3
+
4
+ require 'muon'
5
+ require 'muon/app'
6
+ require 'gli'
7
+
8
+ include GLI
9
+
10
+ program_desc 'muon tracks your working time'
11
+ version Muon::VERSION
12
+
13
+ desc 'Set up current directory as muon project'
14
+ command :init do |c|
15
+ c.action do |global, options, arguments|
16
+ @app.init_directory
17
+ end
18
+ end
19
+
20
+ desc 'Start tracking time'
21
+ command :start do |c|
22
+ c.action do |global, options, arguments|
23
+ @app.start_tracking(arguments[0])
24
+ end
25
+ end
26
+
27
+ desc 'Stop tracking time'
28
+ command :stop do |c|
29
+ c.action do |global, options, arguments|
30
+ @app.stop_tracking(arguments[0])
31
+ end
32
+ end
33
+
34
+ desc 'Cancel current tracking entry'
35
+ command :abort do |c|
36
+ c.action do |global, options, arguments|
37
+ @app.abort_tracking
38
+ end
39
+ end
40
+
41
+ desc 'Add complete tracking entry'
42
+ command :commit do |c|
43
+ c.action do |global, options, arguments|
44
+ @app.commit_entry(arguments[0], arguments[1])
45
+ end
46
+ end
47
+
48
+ desc 'Display tracking status'
49
+ command :status do |c|
50
+ c.action do |global, options, arguments|
51
+ @app.show_status
52
+ end
53
+ end
54
+
55
+ desc 'Display total time on given day'
56
+ command :total do |c|
57
+ c.action do |global, options, arguments|
58
+ @app.show_total(arguments[0])
59
+ end
60
+ end
61
+
62
+ desc 'Display time entries'
63
+ command :log do |c|
64
+ c.flag [:n, :"max-count"]
65
+ c.action do |global, options, arguments|
66
+ limit = options[:n] ? options[:n].to_i : nil
67
+ @app.show_log(limit)
68
+ end
69
+ end
70
+
71
+ desc 'Set or see goal for current month'
72
+ command :goal do |c|
73
+ c.action do |global, options, arguments|
74
+ if arguments.length == 0
75
+ @app.show_goal
76
+ elsif arguments.length > 0
77
+ @app.set_goal(arguments[0])
78
+ end
79
+ end
80
+ end
81
+
82
+ desc 'Get or set config options'
83
+ command :config do |c|
84
+ c.switch :global
85
+ c.action do |global, options, arguments|
86
+ if arguments.length == 1
87
+ @app.read_config_option(arguments[0], options)
88
+ elsif arguments.length > 1
89
+ @app.set_config_option(arguments[0], arguments[1], options)
90
+ end
91
+ end
92
+ end
93
+
94
+ desc 'Register project in ~/.muonprojects'
95
+ command :register do |c|
96
+ c.action do |global, options, arguments|
97
+ @app.register_global_project
98
+ end
99
+ end
100
+
101
+ @app = Muon::App.new(Dir.pwd)
102
+
103
+ exit GLI.run(@app.dealiasify(ARGV))
@@ -0,0 +1 @@
1
+ complete -W "abort commit config goal help init log register start status stop total" muon
@@ -0,0 +1,20 @@
1
+ __muon_dir ()
2
+ {
3
+ if [ -d ".muon" ]; then
4
+ echo ".muon"
5
+ else
6
+ echo ""
7
+ fi
8
+ }
9
+
10
+ __muon_ps1 ()
11
+ {
12
+ local dir="$(__muon_dir)"
13
+ if [ -n "$dir" ]; then
14
+ if [ -f "$dir/current" ]; then
15
+ echo " [●]"
16
+ else
17
+ echo " [ ]"
18
+ fi
19
+ fi
20
+ }
data/lib/muon.rb ADDED
@@ -0,0 +1 @@
1
+ require 'muon/version'
data/lib/muon/app.rb ADDED
@@ -0,0 +1,136 @@
1
+ require "time"
2
+ require "chronic_duration"
3
+ require "muon/config"
4
+ require "muon/entry"
5
+ require "muon/format"
6
+ require "muon/project"
7
+
8
+ module Muon
9
+ class App
10
+ def initialize(working_dir, home_dir = ENV["HOME"], output = $stdout)
11
+ @working_dir = working_dir
12
+ @home_dir = home_dir
13
+ @output = output
14
+ @project = Project.new(working_dir)
15
+ @config = Config.new(config_file, global_config_file)
16
+ end
17
+
18
+ attr_reader :project
19
+
20
+ def init_directory
21
+ project.init_directory
22
+ end
23
+
24
+ def start_tracking(start_time = nil)
25
+ start_time = Time.parse(start_time) if start_time
26
+ project.start_tracking(start_time)
27
+ end
28
+
29
+ def stop_tracking(end_time = nil)
30
+ end_time = Time.parse(end_time) if end_time
31
+ project.stop_tracking(end_time)
32
+ end
33
+
34
+ def abort_tracking
35
+ project.abort_tracking
36
+ end
37
+
38
+ def commit_entry(start_time, end_time)
39
+ start_time = Time.parse(start_time)
40
+ end_time = Time.parse(end_time)
41
+ project.commit_entry(start_time, end_time)
42
+ end
43
+
44
+ def show_status
45
+ if @project.tracking?
46
+ @output.puts "Time tracking is running since #{Format.duration @project.tracking_duration}."
47
+ else
48
+ @output.puts "Time tracking is stopped."
49
+ end
50
+ @output.puts "Today's total time is #{Format.duration @project.day_total_time(Time.now)}."
51
+ end
52
+
53
+ def show_total(date = nil)
54
+ if date.nil?
55
+ date = Time.now
56
+ else
57
+ date = Time.parse(date)
58
+ end
59
+ @output.puts "Total time on #{date.strftime('%Y-%m-%d')} is #{Format.duration @project.day_total_time(date)}."
60
+ end
61
+
62
+ def show_log(limit = nil)
63
+ entries = @project.history_entries
64
+ entries = entries.take(limit) if limit
65
+ entries.each do |entry|
66
+ @output.puts "#{entry.start_time} - #{entry.end_time} (#{Format.duration entry.duration})"
67
+ end
68
+ end
69
+
70
+ def show_goal
71
+ if project.has_goal?
72
+ @output.puts "The goal for this month is #{Format.duration project.goal}."
73
+ @output.puts "Time left to achieve this goal: #{Format.duration project.goal_remaining_time}."
74
+ else
75
+ @output.puts "No goal has been set."
76
+ end
77
+ end
78
+
79
+ def set_goal(duration)
80
+ duration = ChronicDuration.parse(duration)
81
+ project.goal = duration
82
+ @output.puts "Setting goal for this month to #{Format.duration duration}."
83
+ end
84
+
85
+ def dealiasify(args)
86
+ dealiased = @config.get_option("alias.#{args.first}")
87
+ if dealiased
88
+ [dealiased] + args.drop(1)
89
+ else
90
+ args
91
+ end
92
+ end
93
+
94
+ def read_config_option(key, options = {})
95
+ if options[:global]
96
+ @output.puts @config.get_global_option(key)
97
+ else
98
+ @output.puts @config.get_option(key)
99
+ end
100
+ end
101
+
102
+ def set_config_option(key, value, options = {})
103
+ if options[:global]
104
+ @config.set_global_option(key, value)
105
+ else
106
+ @config.set_option(key, value)
107
+ end
108
+ end
109
+
110
+ def global_projects
111
+ File.open(global_projects_file, "r") do |f|
112
+ f.lines.to_a.map(&:strip).map { |path| Project.new(path) }
113
+ end
114
+ end
115
+
116
+ def register_global_project
117
+ File.open(global_projects_file, "a") { |f| f << "#{@working_dir}\n" }
118
+ end
119
+
120
+ private
121
+
122
+ # attr_reader :history
123
+
124
+ def config_file
125
+ File.join(project.working_dir, "config")
126
+ end
127
+
128
+ def global_config_file
129
+ File.join(@home_dir, ".muonconfig") if @home_dir
130
+ end
131
+
132
+ def global_projects_file
133
+ File.join(@home_dir, ".muonprojects") if @home_dir
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,66 @@
1
+ require "yaml"
2
+
3
+ module Muon
4
+ class Config
5
+ attr_reader :local_file, :global_file
6
+
7
+ def initialize(local_file, global_file)
8
+ @local_file, @global_file = local_file, global_file
9
+ end
10
+
11
+ def get_option(key)
12
+ get_local_option(key) || get_global_option(key)
13
+ end
14
+
15
+ def get_local_option(key)
16
+ get_option_from_file(local_file, key)
17
+ end
18
+
19
+ def set_local_option(key, value)
20
+ set_option_in_file(local_file, key, value)
21
+ end
22
+
23
+ alias :set_option :set_local_option
24
+
25
+ def get_global_option(key)
26
+ get_option_from_file(global_file, key)
27
+ end
28
+
29
+ def set_global_option(key, value)
30
+ set_option_in_file(global_file, key, value)
31
+ end
32
+
33
+ private
34
+
35
+ def get_option_from_file(file, key)
36
+ config = read_config_file(file) || {}
37
+ group, name = split_config_key(key)
38
+ config[group] ||= {}
39
+ config[group][name]
40
+ end
41
+
42
+ def set_option_in_file(file, key, value)
43
+ config = read_config_file(file) || {}
44
+ group, name = *key.split(".", 2)
45
+ config[group] ||= {}
46
+ config[group][name] = value
47
+ write_config_file(file, config)
48
+ end
49
+
50
+ def read_config_file(file)
51
+ return nil unless File.exists?(file)
52
+ YAML.load_file(file)
53
+ end
54
+
55
+ def write_config_file(file, config)
56
+ File.open(file, "w") { |f| f.write YAML.dump(config) }
57
+ end
58
+
59
+ def split_config_key(key)
60
+ parts = key.split(".")
61
+ group = parts.slice(0, parts.length - 1).join(".")
62
+ name = parts.last
63
+ [group, name]
64
+ end
65
+ end
66
+ end
data/lib/muon/entry.rb ADDED
@@ -0,0 +1,33 @@
1
+ require "time"
2
+
3
+ module Muon
4
+ class Entry
5
+ attr_reader :start_time, :end_time, :parent_hash
6
+
7
+ def initialize(start_time, end_time, parent_hash = nil)
8
+ @start_time, @end_time = start_time, end_time
9
+ @parent_hash = parent_hash
10
+ end
11
+
12
+ def duration
13
+ (end_time - start_time).to_i
14
+ end
15
+
16
+ def with_parent_hash(new_parent_hash)
17
+ raise "Already have parent hash" if parent_hash
18
+ Entry.new(start_time, end_time, new_parent_hash)
19
+ end
20
+
21
+ def to_s
22
+ "parent #{parent_hash}\n" +
23
+ "#{start_time},#{end_time}\n"
24
+ end
25
+
26
+ def self.from_s(str)
27
+ parent_hash = str.lines.to_a[0].sub(/^parent /, "").strip
28
+ parent_hash = nil if parent_hash == ""
29
+ start_time, end_time = *str.lines.to_a[1].strip.split(",")
30
+ new(Time.parse(start_time), Time.parse(end_time), parent_hash)
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,21 @@
1
+ module Muon
2
+ class Format
3
+ def self.duration(seconds)
4
+ seconds ||= 0
5
+ seconds = seconds.to_i
6
+ minutes = seconds / 60
7
+ if minutes == 0
8
+ "#{seconds} seconds"
9
+ else
10
+ seconds = seconds % 60
11
+ hours = minutes / 60
12
+ if hours == 0
13
+ "#{minutes} minutes, #{seconds} seconds"
14
+ else
15
+ minutes = minutes % 60
16
+ "#{hours} hours, #{minutes} minutes, #{seconds} seconds"
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,67 @@
1
+ require "digest/sha1"
2
+
3
+ module Muon
4
+ class History
5
+ def initialize(working_dir)
6
+ @working_dir = working_dir
7
+ end
8
+
9
+ def entries
10
+ if block_given?
11
+ entry = load_entry(head)
12
+ while entry
13
+ yield entry
14
+ entry = load_entry(entry.parent_hash)
15
+ end
16
+ else
17
+ enum_for :entries
18
+ end
19
+ end
20
+
21
+ def append(entry)
22
+ self.head = save_entry(entry.with_parent_hash(head))
23
+ end
24
+
25
+ private
26
+
27
+ def head_file
28
+ File.join(@working_dir, "HEAD")
29
+ end
30
+
31
+ def head
32
+ if File.exists?(head_file)
33
+ File.read(head_file).strip
34
+ else
35
+ nil
36
+ end
37
+ end
38
+
39
+ def head=(hash)
40
+ File.open(head_file, "w") { |f| f.write(hash + "\n") }
41
+ end
42
+
43
+ def load_entry(hash)
44
+ return nil if hash.nil?
45
+ File.open(file_for_hash(hash), "r") { |f| Entry.from_s(f.read) }
46
+ end
47
+
48
+ def save_entry(entry)
49
+ contents = entry.to_s
50
+ hash = compute_hash(contents)
51
+ File.open(file_for_hash(hash), "w") { |f| f.write(contents) }
52
+ return hash
53
+ end
54
+
55
+ def file_for_hash(hash)
56
+ File.join(@working_dir, "objects", hash)
57
+ end
58
+
59
+ def verify_hash(contents, hash)
60
+ raise "Hash #{hash} does not match contents!" if compute_hash(compute_hash) != hash
61
+ end
62
+
63
+ def compute_hash(contents)
64
+ Digest::SHA1.hexdigest(contents)
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,128 @@
1
+ require "muon/history"
2
+
3
+ module Muon
4
+ class Project
5
+ attr_reader :path
6
+
7
+ def initialize(path)
8
+ @path = path
9
+ @history = History.new(working_dir)
10
+ end
11
+
12
+ def name
13
+ File.basename(path)
14
+ end
15
+
16
+ def working_dir
17
+ File.join(path, ".muon")
18
+ end
19
+
20
+ def init_directory
21
+ raise "Already initialized!" if Dir.exists?(working_dir)
22
+ Dir.mkdir(working_dir)
23
+ Dir.mkdir(File.join working_dir, "objects")
24
+ end
25
+
26
+ def tracking?
27
+ File.exists?(File.join(working_dir, "current"))
28
+ end
29
+
30
+ def start_tracking(start_time = nil)
31
+ raise "Already tracking!" if tracking_file_exists?
32
+ start_time ||= Time.now
33
+ create_tracking_file(start_time.to_s)
34
+ end
35
+
36
+ def stop_tracking(end_time = nil)
37
+ raise "Not tracking!" unless tracking_file_exists?
38
+ start_time = Time.parse(read_tracking_file)
39
+ end_time ||= Time.now
40
+ commit_entry(start_time.to_s, end_time.to_s)
41
+ delete_tracking_file
42
+ end
43
+
44
+ def abort_tracking
45
+ raise "Not tracking!" unless tracking_file_exists?
46
+ delete_tracking_file
47
+ end
48
+
49
+ def tracking_duration
50
+ (Time.now - Time.parse(read_tracking_file)).to_i
51
+ end
52
+
53
+ def commit_entry(start_time, end_time)
54
+ entry = Entry.new(start_time, end_time)
55
+ history.append(entry)
56
+ end
57
+
58
+ def history_entries
59
+ history.entries
60
+ end
61
+
62
+ def day_total_time(date = Time.now)
63
+ date = date.strftime("%Y%m%d")
64
+ history.entries.select { |e| e.start_time.strftime("%Y%m%d") == date }.map(&:duration).inject(&:+)
65
+ end
66
+
67
+ def month_total_time(date = Time.now)
68
+ date = date.strftime("%Y%m")
69
+ history.entries.select { |e| e.start_time.strftime("%Y%m") == date }.map(&:duration).inject(&:+)
70
+ end
71
+
72
+ def has_goal?
73
+ goal_file_exists?
74
+ end
75
+
76
+ def goal
77
+ read_goal_file.to_i
78
+ end
79
+
80
+ def goal=(seconds)
81
+ write_goal_file(seconds.to_s)
82
+ end
83
+
84
+ def goal_remaining_time
85
+ goal - month_total_time
86
+ end
87
+
88
+ private
89
+
90
+ attr_reader :history
91
+
92
+ def tracking_file
93
+ File.join(working_dir, "current")
94
+ end
95
+
96
+ def tracking_file_exists?
97
+ File.exists?(tracking_file)
98
+ end
99
+
100
+ def read_tracking_file
101
+ File.open(tracking_file, "r") { |f| f.read }
102
+ end
103
+
104
+ def create_tracking_file(contents = "")
105
+ File.open(tracking_file, "w") { |f| f.puts(contents) }
106
+ end
107
+
108
+ def delete_tracking_file
109
+ File.unlink(tracking_file)
110
+ end
111
+
112
+ def goal_file
113
+ File.join(working_dir, "goal")
114
+ end
115
+
116
+ def goal_file_exists?
117
+ File.exists?(goal_file)
118
+ end
119
+
120
+ def read_goal_file
121
+ File.open(goal_file, "r") { |f| f.read }
122
+ end
123
+
124
+ def write_goal_file(contents = "")
125
+ File.open(goal_file, "w") { |f| f.puts(contents) }
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,3 @@
1
+ module Muon
2
+ VERSION = "0.0.2"
3
+ end
data/muon.gemspec ADDED
@@ -0,0 +1,23 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/muon/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["DRUG"]
6
+ # gem.email = [""]
7
+ gem.description = %q{Distributed time tracking tool}
8
+ gem.summary = %q{Distributed time tracking tool}
9
+ gem.homepage = "http://drug.org.pl"
10
+
11
+ gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
12
+ gem.files = `git ls-files`.split("\n")
13
+ gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
14
+ gem.name = "muon"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = Muon::VERSION
17
+
18
+ gem.add_dependency('gli', "~> 1.0")
19
+ gem.add_dependency('chronic_duration', "~> 0.9.6")
20
+
21
+ gem.add_development_dependency('rake')
22
+ gem.add_development_dependency('delorean')
23
+ end
data/test/cli_test.rb ADDED
@@ -0,0 +1,174 @@
1
+ require 'test_helper'
2
+ require 'tmpdir'
3
+ require 'stringio'
4
+ require 'delorean'
5
+
6
+ class CliTest < Test::Unit::TestCase
7
+ include Delorean
8
+
9
+ def setup
10
+ @dir = Dir.mktmpdir
11
+ @home_dir = Dir.mktmpdir
12
+ @output = StringIO.new
13
+ @app = Muon::App.new(@dir, @home_dir, @output)
14
+ @app.init_directory
15
+ end
16
+
17
+ def teardown
18
+ FileUtils.remove_entry_secure(@dir)
19
+ FileUtils.remove_entry_secure(@home_dir)
20
+ end
21
+
22
+ def test_init_initializes_only_once
23
+ assert_raise(RuntimeError) { @app.init_directory }
24
+ end
25
+
26
+ def test_start_creates_tracking_file
27
+ @app.start_tracking
28
+ assert File.exists?("#{@dir}/.muon/current")
29
+ end
30
+
31
+ def test_start_fails_if_already_tracking
32
+ @app.start_tracking
33
+ assert_raise(RuntimeError) { @app.start_tracking }
34
+ end
35
+
36
+ def test_stop_stops_and_creates_history_entry
37
+ @app.start_tracking
38
+ @app.stop_tracking
39
+ assert ! File.exists?("#{@dir}/.muon/current")
40
+ assert File.exists?("#{@dir}/.muon/HEAD")
41
+ end
42
+
43
+ def test_stop_fails_if_not_tracking_yet
44
+ assert_raise(RuntimeError) { @app.stop_tracking }
45
+ end
46
+
47
+ def test_abort_stop_and_does_not_create_entry
48
+ @app.start_tracking
49
+ @app.abort_tracking
50
+ assert ! File.exists?("#{@dir}/.muon/current")
51
+ assert ! File.exists?("#{@dir}/.muon/HEAD")
52
+ end
53
+
54
+ def test_commit_creates_history_entry
55
+ @app.commit_entry("00:01", "00:05")
56
+ assert File.exists?("#{@dir}/.muon/HEAD")
57
+ end
58
+
59
+ def test_status_reports_whether_running
60
+ @app.show_status
61
+ assert_equal "Time tracking is stopped.\n", @output.string.lines.to_a[0]
62
+
63
+ @app.start_tracking
64
+ reset_output
65
+ @app.show_status
66
+ assert_equal "Time tracking is running since 0 seconds.\n", @output.string.lines.to_a[0]
67
+ end
68
+
69
+ def test_status_reports_todays_total_time
70
+ time_travel_to("yesterday 12:00") { @app.start_tracking }
71
+ time_travel_to("yesterday 12:10") { @app.stop_tracking }
72
+ time_travel_to("today 00:00") { @app.start_tracking }
73
+ time_travel_to("today 00:05") { @app.stop_tracking }
74
+
75
+ @app.show_status
76
+ assert_equal "Time tracking is stopped.\nToday's total time is 5 minutes, 0 seconds.\n", @output.string
77
+ end
78
+
79
+ def test_total_calculates_total_time_for_a_day
80
+ time_travel_to("2012-01-01 12:00") { @app.start_tracking }
81
+ time_travel_to("2012-01-01 12:10") { @app.stop_tracking }
82
+ time_travel_to("2012-01-02 00:00") { @app.start_tracking }
83
+ time_travel_to("2012-01-02 00:05") { @app.stop_tracking }
84
+
85
+ @app.show_total('2012-01-01')
86
+ assert_equal "Total time on 2012-01-01 is 10 minutes, 0 seconds.\n", @output.string
87
+
88
+ reset_output
89
+ @app.show_total('2012-01-05')
90
+ assert_equal "Total time on 2012-01-05 is 0 seconds.\n", @output.string
91
+ end
92
+
93
+ def test_log_lists_entries
94
+ time_travel_to("2012-08-01") do
95
+ @app.commit_entry("00:01", "00:05")
96
+ @app.commit_entry("00:10", "00:20")
97
+ end
98
+
99
+ @app.show_log
100
+ assert_equal "2012-08-01 00:10:00 +0200 - 2012-08-01 00:20:00 +0200 (10 minutes, 0 seconds)\n", @output.string.lines.to_a[0]
101
+ assert_equal "2012-08-01 00:01:00 +0200 - 2012-08-01 00:05:00 +0200 (4 minutes, 0 seconds)\n", @output.string.lines.to_a[1]
102
+ assert_equal 2, @output.string.lines.to_a.length
103
+
104
+ reset_output
105
+ @app.show_log(1)
106
+ assert_equal "2012-08-01 00:10:00 +0200 - 2012-08-01 00:20:00 +0200 (10 minutes, 0 seconds)\n", @output.string.lines.to_a[0]
107
+ assert_equal 1, @output.string.lines.to_a.length
108
+ end
109
+
110
+ def test_goal_for_month
111
+ time_travel_to("2012-08-01") do
112
+ @app.commit_entry("00:00", "00:05")
113
+ @app.commit_entry("00:10", "00:20")
114
+
115
+ test_command do
116
+ @app.show_goal
117
+ assert_equal "No goal has been set.\n", output
118
+ end
119
+
120
+ test_command do
121
+ @app.set_goal('30:00:00')
122
+ assert_equal "Setting goal for this month to 30 hours, 0 minutes, 0 seconds.\n", output
123
+ end
124
+
125
+ test_command do
126
+ @app.show_goal
127
+ assert_equal "The goal for this month is 30 hours, 0 minutes, 0 seconds.\n", output_lines[0]
128
+ assert_equal "Time left to achieve this goal: 29 hours, 45 minutes, 0 seconds.\n", output_lines[1]
129
+ assert_equal 2, output_lines.length
130
+ end
131
+ end
132
+ end
133
+
134
+ def test_config_setting_and_reading
135
+ @app.set_config_option("alias.ci", "commit")
136
+
137
+ @app.read_config_option("alias.ci")
138
+ assert_equal "commit\n", @output.string
139
+
140
+ reset_output
141
+ @app.read_config_option("xxx.y")
142
+ assert_equal "\n", @output.string
143
+ end
144
+
145
+ def test_dealiasify_replaces_aliases
146
+ @app.set_config_option("alias.ci", "commit")
147
+ assert_equal ["commit", "00:01", "00:05"], @app.dealiasify(["ci", "00:01", "00:05"])
148
+ end
149
+
150
+ def test_register_global_project
151
+ @app.register_global_project
152
+ assert_equal File.basename(@dir), @app.global_projects[0].name
153
+ assert_equal @dir, @app.global_projects[0].path
154
+ end
155
+
156
+ private
157
+
158
+ def test_command
159
+ yield
160
+ reset_output
161
+ end
162
+
163
+ def reset_output
164
+ @output.string = ""
165
+ end
166
+
167
+ def output
168
+ @output.string
169
+ end
170
+
171
+ def output_lines
172
+ @output.string.lines.to_a
173
+ end
174
+ end
@@ -0,0 +1,2 @@
1
+ require 'test/unit'
2
+ require 'muon/app'
metadata ADDED
@@ -0,0 +1,129 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: muon
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - DRUG
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-01-10 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: gli
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '1.0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '1.0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: chronic_duration
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: 0.9.6
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: 0.9.6
46
+ - !ruby/object:Gem::Dependency
47
+ name: rake
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: delorean
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ description: Distributed time tracking tool
79
+ email:
80
+ executables:
81
+ - muon
82
+ extensions: []
83
+ extra_rdoc_files: []
84
+ files:
85
+ - .gitignore
86
+ - Gemfile
87
+ - README.md
88
+ - Rakefile
89
+ - bin/muon
90
+ - extras/muon-completion.bash
91
+ - extras/muon-prompt.sh
92
+ - lib/muon.rb
93
+ - lib/muon/app.rb
94
+ - lib/muon/config.rb
95
+ - lib/muon/entry.rb
96
+ - lib/muon/format.rb
97
+ - lib/muon/history.rb
98
+ - lib/muon/project.rb
99
+ - lib/muon/version.rb
100
+ - muon.gemspec
101
+ - test/cli_test.rb
102
+ - test/test_helper.rb
103
+ homepage: http://drug.org.pl
104
+ licenses: []
105
+ post_install_message:
106
+ rdoc_options: []
107
+ require_paths:
108
+ - lib
109
+ required_ruby_version: !ruby/object:Gem::Requirement
110
+ none: false
111
+ requirements:
112
+ - - ! '>='
113
+ - !ruby/object:Gem::Version
114
+ version: '0'
115
+ required_rubygems_version: !ruby/object:Gem::Requirement
116
+ none: false
117
+ requirements:
118
+ - - ! '>='
119
+ - !ruby/object:Gem::Version
120
+ version: '0'
121
+ requirements: []
122
+ rubyforge_project:
123
+ rubygems_version: 1.8.23
124
+ signing_key:
125
+ specification_version: 3
126
+ summary: Distributed time tracking tool
127
+ test_files:
128
+ - test/cli_test.rb
129
+ - test/test_helper.rb