ical_punch 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.gitignore +22 -0
- data/LICENSE +20 -0
- data/README.rdoc +24 -0
- data/Rakefile +46 -0
- data/VERSION +1 -0
- data/bin/ical_punch +127 -0
- data/ical_punch.gemspec +63 -0
- data/lib/ical_punch.rb +157 -0
- data/spec/fixtures/punch.yml +418 -0
- data/spec/fixtures/test.ics +655 -0
- data/spec/ical_punch_spec.rb +48 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +9 -0
- metadata +104 -0
data/.document
ADDED
data/.gitignore
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Rob Kaufman
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
= ical_punch
|
2
|
+
by Rob Kaufman
|
3
|
+
notch8.com
|
4
|
+
|
5
|
+
|
6
|
+
ical_punch converts between iCalendar files and the punch.yml format
|
7
|
+
|
8
|
+
== INSTALL:
|
9
|
+
|
10
|
+
* sudo gem install ical_punch
|
11
|
+
|
12
|
+
== Note on Patches/Pull Requests
|
13
|
+
|
14
|
+
* Fork the project.
|
15
|
+
* Make your feature addition or bug fix.
|
16
|
+
* Add tests for it. This is important so I don't break it in a
|
17
|
+
future version unintentionally.
|
18
|
+
* Commit, do not mess with rakefile, version, or history.
|
19
|
+
(if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
|
20
|
+
* Send me a pull request. Bonus points for topic branches.
|
21
|
+
|
22
|
+
== Copyright
|
23
|
+
|
24
|
+
Copyright (c) 2010 Rob Kaufman. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "ical_punch"
|
8
|
+
gem.summary = %Q{ical_punch converts between iCalendar files and the punch.yml format}
|
9
|
+
gem.description = %Q{ical_punch converts between iCalendar files and the punch.yml format}
|
10
|
+
gem.email = "rob@notch8.com"
|
11
|
+
gem.homepage = "http://github.com/notch8/ical_punch"
|
12
|
+
gem.authors = ["Rob Kaufman"]
|
13
|
+
gem.add_development_dependency "rspec", ">= 1.2.9"
|
14
|
+
gem.add_dependency "icalendar", ">= 1.1.3"
|
15
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
16
|
+
end
|
17
|
+
Jeweler::GemcutterTasks.new
|
18
|
+
rescue LoadError
|
19
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
20
|
+
end
|
21
|
+
|
22
|
+
require 'spec/rake/spectask'
|
23
|
+
Spec::Rake::SpecTask.new(:spec) do |spec|
|
24
|
+
spec.libs << 'lib' << 'spec'
|
25
|
+
spec.spec_files = FileList['spec/**/*_spec.rb']
|
26
|
+
end
|
27
|
+
|
28
|
+
Spec::Rake::SpecTask.new(:rcov) do |spec|
|
29
|
+
spec.libs << 'lib' << 'spec'
|
30
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
31
|
+
spec.rcov = true
|
32
|
+
end
|
33
|
+
|
34
|
+
task :spec => :check_dependencies
|
35
|
+
|
36
|
+
task :default => :spec
|
37
|
+
|
38
|
+
require 'rake/rdoctask'
|
39
|
+
Rake::RDocTask.new do |rdoc|
|
40
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
41
|
+
|
42
|
+
rdoc.rdoc_dir = 'rdoc'
|
43
|
+
rdoc.title = "ical_punch #{version}"
|
44
|
+
rdoc.rdoc_files.include('README*')
|
45
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
46
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.0.0
|
data/bin/ical_punch
ADDED
@@ -0,0 +1,127 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require File.expand_path(
|
4
|
+
File.join(File.dirname(__FILE__), '..', 'lib', 'ical_punch'))
|
5
|
+
|
6
|
+
# Put your code here
|
7
|
+
require 'optparse'
|
8
|
+
|
9
|
+
OPTIONS = {}
|
10
|
+
MANDATORY_OPTIONS = %w[]
|
11
|
+
|
12
|
+
parser = OptionParser.new do |opts|
|
13
|
+
opts.banner = <<BANNER
|
14
|
+
Usage: #{File.basename($0)} [command] <project>
|
15
|
+
|
16
|
+
Options are:
|
17
|
+
BANNER
|
18
|
+
opts.separator ''
|
19
|
+
opts.on('-v', '--version',
|
20
|
+
"Show the #{File.basename($0)} version number and exit") { puts "#{File.basename($0)} #{Punch.verson}"; exit }
|
21
|
+
# opts.on('--after [TIME]', String,
|
22
|
+
# "Restrict command to only after the given time") { |time| OPTIONS[:after] = Time.parse(time) }
|
23
|
+
# opts.on('--before [TIME]', String,
|
24
|
+
# "Restrict command to only before the given time") { |time| OPTIONS[:before] = Time.parse(time) }
|
25
|
+
# opts.on('--at [TIME]', '--time [TIME]', String,
|
26
|
+
# "Use the given time to punch in/out") { |time| OPTIONS[:time] = Time.parse(time) }
|
27
|
+
# opts.on('-m', '--message [MESSAGE]', String,
|
28
|
+
# "Add the given log message when punching in/out") { |message| OPTIONS[:message] = message }
|
29
|
+
opts.on('-f', '--file [FILE_NAME]', String,
|
30
|
+
"File name of the you want to output to") {|message| OPTIONS[:file_name] = file_name}
|
31
|
+
opts.on("-h", "--help",
|
32
|
+
"Show this help message.") { puts opts; exit }
|
33
|
+
opts.parse!(ARGV)
|
34
|
+
|
35
|
+
if MANDATORY_OPTIONS && MANDATORY_OPTIONS.find { |option| OPTIONS[option.to_sym].nil? }
|
36
|
+
puts opts; exit
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
command = ARGV[0]
|
41
|
+
|
42
|
+
unless command
|
43
|
+
puts "Usage: #{File.basename($0)} [command] <project>"
|
44
|
+
exit
|
45
|
+
end
|
46
|
+
|
47
|
+
project = ARGV[1]
|
48
|
+
|
49
|
+
IcalPunch.load
|
50
|
+
|
51
|
+
commands = {
|
52
|
+
'to_ical' => lambda do |project|
|
53
|
+
if OPTIONS[:file_name]
|
54
|
+
IcalPunch.to_ical(project, OPTIONS[:file_name])
|
55
|
+
else
|
56
|
+
IcalPunch.to_ical(project)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
# 'status' => lambda do |project|
|
60
|
+
# result = Punch.status(project)
|
61
|
+
# if project
|
62
|
+
# puts result.inspect
|
63
|
+
# else
|
64
|
+
# puts result.to_yaml
|
65
|
+
# end
|
66
|
+
# end,
|
67
|
+
# 'total' => lambda do |project|
|
68
|
+
# result = Punch.total(project, OPTIONS.merge(:format => true))
|
69
|
+
# if project
|
70
|
+
# puts result.inspect
|
71
|
+
# else
|
72
|
+
# puts result.to_yaml
|
73
|
+
# end
|
74
|
+
# end,
|
75
|
+
# 'in' => lambda do |project|
|
76
|
+
# if project
|
77
|
+
# if Punch.in(project, OPTIONS)
|
78
|
+
# Punch.write
|
79
|
+
# else
|
80
|
+
# puts "Already punched in to '#{project}'"
|
81
|
+
# end
|
82
|
+
# else
|
83
|
+
# puts "Project required"
|
84
|
+
# end
|
85
|
+
# end,
|
86
|
+
# 'delete' => lambda do |project|
|
87
|
+
# if project
|
88
|
+
# Punch.write if result = Punch.delete(project)
|
89
|
+
# puts result.inspect
|
90
|
+
# else
|
91
|
+
# puts "Project required"
|
92
|
+
# end
|
93
|
+
# end,
|
94
|
+
# 'out' => lambda do |project|
|
95
|
+
# if Punch.out(project, OPTIONS)
|
96
|
+
# Punch.write
|
97
|
+
# else
|
98
|
+
# message = 'Already punched out of '
|
99
|
+
# message += project ? "'#{project}'" : 'all projects'
|
100
|
+
# puts message
|
101
|
+
# end
|
102
|
+
# end,
|
103
|
+
# 'log' => lambda do |project|
|
104
|
+
# if project
|
105
|
+
# if message = ARGV[2]
|
106
|
+
# if Punch.log(project, message)
|
107
|
+
# Punch.write
|
108
|
+
# else
|
109
|
+
# puts "Not punched in to '#{project}'"
|
110
|
+
# end
|
111
|
+
# else
|
112
|
+
# puts "Message required"
|
113
|
+
# end
|
114
|
+
# else
|
115
|
+
# puts "Project required"
|
116
|
+
# end
|
117
|
+
# end,
|
118
|
+
# 'list' => lambda { |project| puts Punch.list(project, OPTIONS).to_yaml },
|
119
|
+
}
|
120
|
+
|
121
|
+
if command_code = commands[command]
|
122
|
+
command_code.call(project)
|
123
|
+
else
|
124
|
+
puts "Command '#{command}' unknown"
|
125
|
+
end
|
126
|
+
|
127
|
+
# EOF
|
data/ical_punch.gemspec
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{ical_punch}
|
8
|
+
s.version = "1.0.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Rob Kaufman"]
|
12
|
+
s.date = %q{2010-05-06}
|
13
|
+
s.default_executable = %q{ical_punch}
|
14
|
+
s.description = %q{ical_punch converts between iCalendar files and the punch.yml format}
|
15
|
+
s.email = %q{rob@notch8.com}
|
16
|
+
s.executables = ["ical_punch"]
|
17
|
+
s.extra_rdoc_files = [
|
18
|
+
"LICENSE",
|
19
|
+
"README.rdoc"
|
20
|
+
]
|
21
|
+
s.files = [
|
22
|
+
".document",
|
23
|
+
".gitignore",
|
24
|
+
"LICENSE",
|
25
|
+
"README.rdoc",
|
26
|
+
"Rakefile",
|
27
|
+
"VERSION",
|
28
|
+
"bin/ical_punch",
|
29
|
+
"ical_punch.gemspec",
|
30
|
+
"lib/ical_punch.rb",
|
31
|
+
"spec/fixtures/punch.yml",
|
32
|
+
"spec/fixtures/test.ics",
|
33
|
+
"spec/ical_punch_spec.rb",
|
34
|
+
"spec/spec.opts",
|
35
|
+
"spec/spec_helper.rb"
|
36
|
+
]
|
37
|
+
s.homepage = %q{http://github.com/notch8/ical_punch}
|
38
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
39
|
+
s.require_paths = ["lib"]
|
40
|
+
s.rubygems_version = %q{1.3.6}
|
41
|
+
s.summary = %q{ical_punch converts between iCalendar files and the punch.yml format}
|
42
|
+
s.test_files = [
|
43
|
+
"spec/ical_punch_spec.rb",
|
44
|
+
"spec/spec_helper.rb"
|
45
|
+
]
|
46
|
+
|
47
|
+
if s.respond_to? :specification_version then
|
48
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
49
|
+
s.specification_version = 3
|
50
|
+
|
51
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
52
|
+
s.add_development_dependency(%q<rspec>, [">= 1.2.9"])
|
53
|
+
s.add_runtime_dependency(%q<icalendar>, [">= 1.1.3"])
|
54
|
+
else
|
55
|
+
s.add_dependency(%q<rspec>, [">= 1.2.9"])
|
56
|
+
s.add_dependency(%q<icalendar>, [">= 1.1.3"])
|
57
|
+
end
|
58
|
+
else
|
59
|
+
s.add_dependency(%q<rspec>, [">= 1.2.9"])
|
60
|
+
s.add_dependency(%q<icalendar>, [">= 1.1.3"])
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
data/lib/ical_punch.rb
ADDED
@@ -0,0 +1,157 @@
|
|
1
|
+
# $Id$
|
2
|
+
require 'rubygems'
|
3
|
+
require 'icalendar'
|
4
|
+
require 'date'
|
5
|
+
|
6
|
+
# Equivalent to a header guard in C/C++
|
7
|
+
# Used to prevent the class/module from being loaded more than once
|
8
|
+
unless defined? IcalPunch
|
9
|
+
|
10
|
+
module IcalPunch
|
11
|
+
class << self
|
12
|
+
|
13
|
+
def data
|
14
|
+
@data
|
15
|
+
end
|
16
|
+
|
17
|
+
def data=(value)
|
18
|
+
@data = value
|
19
|
+
end
|
20
|
+
|
21
|
+
def calendars
|
22
|
+
@calendars
|
23
|
+
end
|
24
|
+
|
25
|
+
def calendars=(value)
|
26
|
+
@calendars = value
|
27
|
+
end
|
28
|
+
|
29
|
+
def load(file_path = '~/.punch.yml')
|
30
|
+
begin
|
31
|
+
raw = File.read(File.expand_path(file_path))
|
32
|
+
@data = YAML.load(raw)
|
33
|
+
rescue Errno::ENOENT
|
34
|
+
return false
|
35
|
+
end
|
36
|
+
|
37
|
+
true
|
38
|
+
end
|
39
|
+
|
40
|
+
def write(file_path = "~/.punch.yml")
|
41
|
+
File.open(File.expand_path(file_path), 'w') do |file|
|
42
|
+
file.puts @data.to_yaml
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def punch_to_calendars
|
47
|
+
data.keys.each do |key|
|
48
|
+
punch_to_calendar(key)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def punch_to_calendar(project)
|
53
|
+
@calendars = []
|
54
|
+
value = data[project]
|
55
|
+
calendar = Icalendar::Calendar.new
|
56
|
+
calendar.prodid += "/#{project}"
|
57
|
+
value.each do |entry|
|
58
|
+
check_entry(entry) || next
|
59
|
+
start_time = entry["in"].strftime("%Y%m%dT%H%M%S")
|
60
|
+
end_time = entry["out"].strftime("%Y%m%dT%H%M%S")
|
61
|
+
calendar.event do
|
62
|
+
dtstart start_time
|
63
|
+
dtend end_time
|
64
|
+
summary project
|
65
|
+
description entry["log"].join("\n")
|
66
|
+
end
|
67
|
+
end
|
68
|
+
@calendars << calendar
|
69
|
+
@calendars
|
70
|
+
end
|
71
|
+
|
72
|
+
def check_entry(entry)
|
73
|
+
if entry["in"].nil?
|
74
|
+
puts "Error processing start time for #{entry.inspect}"
|
75
|
+
return false
|
76
|
+
end
|
77
|
+
if entry["out"].nil?
|
78
|
+
puts "Error processing end time for #{entry.inspect}"
|
79
|
+
return false
|
80
|
+
end
|
81
|
+
return true
|
82
|
+
end
|
83
|
+
|
84
|
+
def calendars_to_punch
|
85
|
+
@data = {}
|
86
|
+
@calendars.each do |calendar|
|
87
|
+
key = calendar.prodid.split("/").last
|
88
|
+
@data[key] = calendar.events.collect do |event|
|
89
|
+
{"out" => event.dtend, "in" => event.dtstart, "total" => nil, "log" => event.description.to_s}
|
90
|
+
end
|
91
|
+
end
|
92
|
+
return @data
|
93
|
+
end
|
94
|
+
|
95
|
+
def to_ical(project_name, file_path = "~/punch.ics")
|
96
|
+
File.open(File.expand_path(file_path), "w") do |file|
|
97
|
+
file.write(punch_to_calendar(project_name).to_ical)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def from_ical(file_path = "~/punch.ics")
|
102
|
+
File.open(File.expand_path(file_path), "r") do |file|
|
103
|
+
@calendars = Icalendar.parse(file)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
|
109
|
+
|
110
|
+
# :stopdoc:
|
111
|
+
VERSION = '0.5.0'
|
112
|
+
LIBPATH = ::File.expand_path(::File.dirname(__FILE__)) + ::File::SEPARATOR
|
113
|
+
PATH = ::File.dirname(LIBPATH) + ::File::SEPARATOR
|
114
|
+
# :startdoc:
|
115
|
+
|
116
|
+
# Returns the version string for the library.
|
117
|
+
#
|
118
|
+
def self.version
|
119
|
+
VERSION
|
120
|
+
end
|
121
|
+
|
122
|
+
# Returns the library path for the module. If any arguments are given,
|
123
|
+
# they will be joined to the end of the libray path using
|
124
|
+
# <tt>File.join</tt>.
|
125
|
+
#
|
126
|
+
def self.libpath( *args )
|
127
|
+
args.empty? ? LIBPATH : ::File.join(LIBPATH, *args)
|
128
|
+
end
|
129
|
+
|
130
|
+
# Returns the lpath for the module. If any arguments are given,
|
131
|
+
# they will be joined to the end of the path using
|
132
|
+
# <tt>File.join</tt>.
|
133
|
+
#
|
134
|
+
def self.path( *args )
|
135
|
+
args.empty? ? PATH : ::File.join(PATH, *args)
|
136
|
+
end
|
137
|
+
|
138
|
+
# Utility method used to rquire all files ending in .rb that lie in the
|
139
|
+
# directory below this file that has the same name as the filename passed
|
140
|
+
# in. Optionally, a specific _directory_ name can be passed in such that
|
141
|
+
# the _filename_ does not have to be equivalent to the directory.
|
142
|
+
#
|
143
|
+
def self.require_all_libs_relative_to( fname, dir = nil )
|
144
|
+
dir ||= ::File.basename(fname, '.*')
|
145
|
+
search_me = ::File.expand_path(
|
146
|
+
::File.join(::File.dirname(fname), dir, '**', '*.rb'))
|
147
|
+
|
148
|
+
Dir.glob(search_me).sort.each {|rb| require rb}
|
149
|
+
end
|
150
|
+
|
151
|
+
end # module IcalPunch
|
152
|
+
|
153
|
+
IcalPunch.require_all_libs_relative_to __FILE__
|
154
|
+
|
155
|
+
end # unless defined?
|
156
|
+
|
157
|
+
# EOF
|