muon 0.0.2

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