cline 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -2,3 +2,6 @@
2
2
  .bundle
3
3
  Gemfile.lock
4
4
  pkg/*
5
+ coverage
6
+ spec/tmp/*
7
+ tmp/*
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format=Fuubar
data/README.md ADDED
@@ -0,0 +1,96 @@
1
+ # Cline - CLI Line Notifier
2
+
3
+ ## Installation
4
+
5
+ ~~~~
6
+ gem install cline
7
+ cline init
8
+ echo "Cline.out_stream = Cline::OutStreams::WithGrowl.new($stdout)" > ~/.cline/config # growlnotify required
9
+ echo "Cline.fetchers << Cline::Fetchers::Feed" >> ~/.cline/config
10
+ curl http://foo.examle.com/url/to/opml.xml > ~/.cline/feeds.xml
11
+
12
+ cline fetch
13
+ cline tick --offset 0 --interval 5
14
+ ~~~~
15
+
16
+ ## Use case
17
+
18
+ in ~/.screenrc
19
+
20
+ ~~~~
21
+ backtick 0 0 0 cline tick 0 60
22
+ ~~~~
23
+
24
+ ## initialize Database
25
+
26
+ `init`command initialize new sqlite3 database.
27
+
28
+ ~~~~
29
+ cline init
30
+ ~~~~
31
+
32
+ ## Reload
33
+
34
+ `fetch`command fetch new notifications from `Cline.fetchers`.
35
+
36
+ ~~~~
37
+ cline fetch
38
+ ~~~~
39
+
40
+ ### Custom Fetcher
41
+
42
+ *fetcher* required `fetch` method.
43
+
44
+ example:
45
+
46
+ ~~~~ruby
47
+ class MyFetcher
48
+ def self.fetch
49
+ new.sources.each do |source|
50
+ Cline::Notification.find_by_message(source.body) || Cline::Notification.create!(message: source.body, time: source.created_at)
51
+ end
52
+ end
53
+
54
+ def sources
55
+ # get new sources...
56
+ end
57
+ end
58
+ ~~~~
59
+
60
+ ### Registration
61
+
62
+ in ~/.cline/config
63
+
64
+ ~~~~ruby
65
+ require 'path/to/my_fetcher'
66
+ Cline.fetchers << MyFetcher
67
+ ~~~~
68
+
69
+ ## Notifier
70
+
71
+ `show` and `tick` command uses Cline's notifier.
72
+ Default notifier is STDOUT.
73
+
74
+ ### Custom Notifyer
75
+
76
+ Cline's notifier required `puts` instance method.
77
+
78
+ example:
79
+
80
+ ~~~~ruby
81
+ class MyNotifier
82
+ def puts(str)
83
+ # implement notifier behaviour...
84
+ end
85
+ end
86
+ ~~~~
87
+
88
+ ### Registration
89
+
90
+ in ~/.cline/config
91
+
92
+ ~~~~ruby
93
+ require 'path/to/my_notifier'
94
+ Cline.out_stream = MyNotifier
95
+ ~~~~
96
+
data/Rakefile CHANGED
@@ -1 +1,7 @@
1
1
  require "bundler/gem_tasks"
2
+
3
+ require 'rspec/core/rake_task'
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task default: :spec
7
+
data/bin/cline ADDED
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env ruby
2
+ # coding: utf-8
3
+
4
+ require_relative '../lib/cline'
5
+
6
+ begin
7
+ Cline::Command.start
8
+ rescue Exception
9
+ Pathname.new("#{Cline.cline_dir}/log").open('a') do |f|
10
+ f.puts '---'
11
+ f.puts Time.now
12
+ f.puts $!.class
13
+ f.puts $!.message
14
+ f.puts $!.backtrace.join("\n")
15
+ end
16
+
17
+ raise $!
18
+ end
data/cline.gemspec CHANGED
@@ -8,8 +8,8 @@ Gem::Specification.new do |s|
8
8
  s.authors = ["hibariya"]
9
9
  s.email = ["celluloid.key@gmail.com"]
10
10
  s.homepage = "https://github.com/hibariya/cline"
11
- s.summary = %q{CLI local news}
12
- s.description = %q{TBA}
11
+ s.summary = %q{CLI Line Notifier}
12
+ s.description = %q{Cline - CLI Line Notifier}
13
13
 
14
14
  #s.rubyforge_project = "cline"
15
15
 
@@ -21,4 +21,19 @@ Gem::Specification.new do |s|
21
21
  # specify any dependencies here; for example:
22
22
  # s.add_development_dependency "rspec"
23
23
  # s.add_runtime_dependency "rest-client"
24
+
25
+ s.add_runtime_dependency 'thor', ['>= 0.14.6']
26
+ s.add_runtime_dependency 'activerecord', ['>= 3.1.1']
27
+ s.add_runtime_dependency 'sqlite3', ['>= 1.3.4']
28
+ s.add_runtime_dependency 'feedzirra', ['~> 0.0.31'] # FIXME builder dependency workaround...
29
+
30
+ s.add_development_dependency 'rake', ['>= 0.9.2']
31
+ s.add_development_dependency 'ir_b', ['>= 1.4.0']
32
+ s.add_development_dependency 'tapp', ['>= 1.1.0']
33
+ s.add_development_dependency 'rspec', ['>= 2.6.0']
34
+ s.add_development_dependency 'rr', ['>= 1.0.4']
35
+ s.add_development_dependency 'fabrication', ['>= 1.2.0']
36
+ s.add_development_dependency 'fuubar', ['>= 0.0.6']
37
+ s.add_development_dependency 'simplecov', ['>= 0.5.3']
38
+ s.add_development_dependency 'activesupport', ['>= 3.1.1']
24
39
  end
@@ -0,0 +1,45 @@
1
+ # coding: utf-8
2
+
3
+ module Cline
4
+ class Command < Thor
5
+ def self.start(*)
6
+ Cline.boot
7
+ super
8
+ end
9
+
10
+ desc 'show', 'show a latest message'
11
+ method_options offset: :integer
12
+ def show(offset = options[:offset] || 0)
13
+ Notification.display offset
14
+ end
15
+
16
+ desc 'tick', 'rotate message'
17
+ method_options offset: :integer, interval: :integer
18
+ def tick(offset = options[:offset] || 0, interval = options[:interval] || 60)
19
+ loop do
20
+ show offset
21
+ sleep interval.to_i
22
+ end
23
+ end
24
+
25
+ desc 'status', 'show status'
26
+ def status
27
+ say "displayed : #{Notification.displayed.count}", :green
28
+ say "total : #{Notification.count}", :cyan
29
+ end
30
+
31
+ desc 'fetch', 'fetch sources'
32
+ def fetch
33
+ Cline.fetchers.each &:fetch
34
+ end
35
+
36
+ desc 'init', 'init database'
37
+ def init
38
+ ActiveRecord::Base.connection.create_table(:notifications) do |t|
39
+ t.text :message, null: false, default: ''
40
+ t.integer :display_count, null: false, default: 0
41
+ t.time :time, null: false
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,55 @@
1
+ # coding: utf-8
2
+
3
+ module Cline::Fetchers
4
+ class Feed
5
+ def self.fetch
6
+ opml = Pathname.new("#{Cline.cline_dir}/feeds.xml")
7
+ entries = new(opml.read).fetch
8
+
9
+ entries.each do |entry|
10
+ message = "#{entry.title} #{entry.url}"
11
+ Cline::Notification.tap {|n| n.find_by_message(message) || n.create(message: message, time: entry.published) }
12
+ end
13
+ end
14
+
15
+ def initialize(opml_str)
16
+ require 'rexml/document'
17
+ require 'feedzirra'
18
+
19
+ @opml = REXML::Document.new(opml_str)
20
+ @feeds = parse_opml(@opml.elements['opml/body'])
21
+ end
22
+
23
+ def fetch
24
+ entries = []
25
+
26
+ @feeds.map { |feed_url|
27
+ Thread.fork do
28
+ begin
29
+ feed = Feedzirra::Feed.fetch_and_parse(feed_url)
30
+ entries += feed.entries if feed.is_a? Feedzirra::FeedUtilities
31
+ rescue
32
+ puts $!.class, $!.message
33
+ end
34
+ end
35
+ }.map(&:join)
36
+
37
+ entries
38
+ end
39
+
40
+ def parse_opml(opml_node)
41
+ feeds = []
42
+
43
+ opml_node.elements.each('outline') do |el|
44
+ unless el.elements.size.zero?
45
+ feeds += parse_opml(el)
46
+ else
47
+ url = el.attributes['xmlUrl']
48
+ feeds << url if url
49
+ end
50
+ end
51
+
52
+ feeds
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,31 @@
1
+ # coding: utf-8
2
+
3
+ module Cline
4
+ class Notification < ActiveRecord::Base
5
+ validate :time, presence: true
6
+ validate :message, presence: true, uniqueness: true
7
+ validate :display_count, presence: true, numerically: true
8
+
9
+ scope :earliest, ->(limit = 1, offset = 0) {
10
+ order('display_count, time').limit(limit).offset(offset)
11
+ }
12
+
13
+ scope :displayed, where('display_count > 0')
14
+
15
+ class << self
16
+ def display(offset)
17
+ earliest(1, offset).first.display
18
+ end
19
+ end
20
+
21
+ def display
22
+ Cline.out_stream.puts display_message
23
+
24
+ increment! :display_count
25
+ end
26
+
27
+ def display_message
28
+ "[#{time}][#{display_count}] #{message}"
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+
3
+ module Cline::OutStreams
4
+ class WithGrowl
5
+ attr_reader:stream
6
+
7
+ def initialize(stream = $stdout)
8
+ @stream = stream
9
+ end
10
+
11
+ def puts(str)
12
+ puts_stream str
13
+ puts_growlnotify str
14
+ end
15
+
16
+ private
17
+
18
+ def puts_stream(str)
19
+ stream.puts str
20
+ end
21
+
22
+ def puts_growlnotify(str)
23
+ `growlnotify -m '#{str}'`
24
+ end
25
+ end
26
+ end
data/lib/cline/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Cline
2
- VERSION = "0.0.1"
2
+ VERSION = "0.1.0"
3
3
  end
data/lib/cline.rb CHANGED
@@ -1,5 +1,61 @@
1
- require "cline/version"
1
+ # coding: utf-8
2
+
3
+ here = File.dirname(__FILE__)
4
+ $LOAD_PATH.unshift here unless $LOAD_PATH.include?(here)
2
5
 
3
6
  module Cline
4
- # Your code goes here...
7
+ class << self
8
+ def cline_dir
9
+ "#{ENV['HOME']}/.cline"
10
+ end
11
+
12
+ def boot
13
+ mkdir_if_needed
14
+ setup_logger
15
+ establish_connection
16
+ load_config_if_exists
17
+ end
18
+
19
+ def mkdir_if_needed
20
+ path = Pathname.new(cline_dir)
21
+ path.mkdir unless path.directory?
22
+ end
23
+
24
+ def setup_logger
25
+ ActiveRecord::Base.logger = Logger.new(STDOUT)
26
+ ActiveRecord::Base.logger.level = Logger::WARN
27
+ end
28
+
29
+ def establish_connection
30
+ ActiveRecord::Base.establish_connection adapter: 'sqlite3', database: "#{cline_dir}/cline.sqlite3", timeout: 10000
31
+ end
32
+
33
+ def load_config_if_exists
34
+ config = Pathname.new("#{cline_dir}/config")
35
+ load config if config.exist?
36
+ end
37
+
38
+ def out_stream
39
+ @out_stream || STDOUT
40
+ end
41
+
42
+ def out_stream=(stream)
43
+ @out_stream = stream
44
+ end
45
+
46
+ fetchers = []
47
+ define_method(:fetchers) { fetchers }
48
+ end
5
49
  end
50
+
51
+ require 'logger'
52
+ require 'pathname'
53
+ require 'thor'
54
+ require 'sqlite3'
55
+ require 'active_record'
56
+
57
+ require "cline/version"
58
+ require "cline/notification"
59
+ require "cline/command"
60
+ require "cline/fetchers"
61
+ require "cline/out_streams"
@@ -0,0 +1,6 @@
1
+ # coding: utf-8
2
+
3
+ Fabricator(:notification, class_name: Cline::Notification) do
4
+ message { sequence(:message) {|i| "message ##{i}" } }
5
+ time { Time.now }
6
+ end
@@ -0,0 +1,23 @@
1
+ # coding: utf-8
2
+
3
+ require_relative '../spec_helper'
4
+
5
+ describe Cline do
6
+ describe '#load_config_if_exists' do
7
+ let(:config) { Pathname.new(Cline.cline_dir).join('config') }
8
+
9
+ before do
10
+ config.open('w') {|f| f.puts "Cline.fetchers << Cline::Fetchers::Feed" }
11
+
12
+ Cline.boot
13
+ end
14
+
15
+ specify "config file should loaded" do
16
+ Cline.fetchers.should include Cline::Fetchers::Feed
17
+ end
18
+
19
+ after do
20
+ config.unlink
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,40 @@
1
+ # coding: utf-8
2
+
3
+ require_relative '../spec_helper'
4
+
5
+ describe Cline::Notification do
6
+ describe '.ealiest' do
7
+ let!(:notification1) { Fabricate(:notification, time: 2.days.ago.beginning_of_day, display_count: 3) }
8
+ let!(:notification2) { Fabricate(:notification, time: 3.days.ago.beginning_of_day, display_count: 3) }
9
+ let!(:notification3) { Fabricate(:notification, time: 3.days.ago.beginning_of_day, display_count: 2) }
10
+
11
+ context 'default(limit 1 offset 0)' do
12
+ subject { Cline::Notification.earliest.all }
13
+
14
+ it { should == [notification3] }
15
+ end
16
+
17
+ context 'limit 2 offset 1' do
18
+ subject { Cline::Notification.earliest(2, 1).all }
19
+
20
+ it { should == [notification2, notification1] }
21
+ end
22
+ end
23
+
24
+ describe '.displayed' do
25
+ let!(:notification1) { Fabricate(:notification, display_count: 0) }
26
+ let!(:notification2) { Fabricate(:notification, display_count: 1) }
27
+
28
+ subject { Cline::Notification.displayed.all }
29
+
30
+ it { should == [notification2] }
31
+ end
32
+
33
+ describe '#display' do
34
+ let!(:notification) { Fabricate(:notification, display_count: 0) }
35
+
36
+ specify 'display_count should incremented' do
37
+ -> { notification.display }.should change(notification, :display_count).by(1)
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,36 @@
1
+ # coding: utf-8
2
+
3
+ require 'tapp'
4
+ require 'simplecov'
5
+ require 'fabrication'
6
+
7
+ SimpleCov.start
8
+
9
+ CLINE_ROOT = Pathname.new(File.dirname(__FILE__) + '/../').realpath
10
+ require_relative '../lib/cline'
11
+
12
+ Dir[File.dirname(__FILE__) + '/support/*'].each {|f| require f }
13
+ require_relative 'fabricators'
14
+
15
+ RSpec.configure do |config|
16
+ config.filter_run focus: true
17
+ config.run_all_when_everything_filtered = true
18
+ config.mock_with :rr
19
+
20
+ config.before(:suite) do
21
+ home = CLINE_ROOT.join('spec', 'tmp')
22
+ ENV['HOME'] = home.to_s
23
+
24
+ cline_dir = Pathname.new(Cline.cline_dir)
25
+ cline_dir.rmtree if cline_dir.directory?
26
+
27
+ Cline.boot
28
+ Cline::Command.new.invoke :init
29
+ end
30
+
31
+ config.before(:each) do
32
+ Cline.boot
33
+ Cline.out_stream = StringIO.new
34
+ Cline::Notification.delete_all
35
+ end
36
+ end
File without changes
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cline
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,21 +9,177 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2011-10-22 00:00:00.000000000 Z
13
- dependencies: []
14
- description: TBA
12
+ date: 2011-11-06 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: thor
16
+ requirement: &70240666988320 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: 0.14.6
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *70240666988320
25
+ - !ruby/object:Gem::Dependency
26
+ name: activerecord
27
+ requirement: &70240666984720 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: 3.1.1
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *70240666984720
36
+ - !ruby/object:Gem::Dependency
37
+ name: sqlite3
38
+ requirement: &70240666983100 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: 1.3.4
44
+ type: :runtime
45
+ prerelease: false
46
+ version_requirements: *70240666983100
47
+ - !ruby/object:Gem::Dependency
48
+ name: feedzirra
49
+ requirement: &70240666980960 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: 0.0.31
55
+ type: :runtime
56
+ prerelease: false
57
+ version_requirements: *70240666980960
58
+ - !ruby/object:Gem::Dependency
59
+ name: rake
60
+ requirement: &70240666979860 !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ! '>='
64
+ - !ruby/object:Gem::Version
65
+ version: 0.9.2
66
+ type: :development
67
+ prerelease: false
68
+ version_requirements: *70240666979860
69
+ - !ruby/object:Gem::Dependency
70
+ name: ir_b
71
+ requirement: &70240666978660 !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ! '>='
75
+ - !ruby/object:Gem::Version
76
+ version: 1.4.0
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: *70240666978660
80
+ - !ruby/object:Gem::Dependency
81
+ name: tapp
82
+ requirement: &70240666977600 !ruby/object:Gem::Requirement
83
+ none: false
84
+ requirements:
85
+ - - ! '>='
86
+ - !ruby/object:Gem::Version
87
+ version: 1.1.0
88
+ type: :development
89
+ prerelease: false
90
+ version_requirements: *70240666977600
91
+ - !ruby/object:Gem::Dependency
92
+ name: rspec
93
+ requirement: &70240666976460 !ruby/object:Gem::Requirement
94
+ none: false
95
+ requirements:
96
+ - - ! '>='
97
+ - !ruby/object:Gem::Version
98
+ version: 2.6.0
99
+ type: :development
100
+ prerelease: false
101
+ version_requirements: *70240666976460
102
+ - !ruby/object:Gem::Dependency
103
+ name: rr
104
+ requirement: &70240666974980 !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: 1.0.4
110
+ type: :development
111
+ prerelease: false
112
+ version_requirements: *70240666974980
113
+ - !ruby/object:Gem::Dependency
114
+ name: fabrication
115
+ requirement: &70240666973900 !ruby/object:Gem::Requirement
116
+ none: false
117
+ requirements:
118
+ - - ! '>='
119
+ - !ruby/object:Gem::Version
120
+ version: 1.2.0
121
+ type: :development
122
+ prerelease: false
123
+ version_requirements: *70240666973900
124
+ - !ruby/object:Gem::Dependency
125
+ name: fuubar
126
+ requirement: &70240666973120 !ruby/object:Gem::Requirement
127
+ none: false
128
+ requirements:
129
+ - - ! '>='
130
+ - !ruby/object:Gem::Version
131
+ version: 0.0.6
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: *70240666973120
135
+ - !ruby/object:Gem::Dependency
136
+ name: simplecov
137
+ requirement: &70240666971520 !ruby/object:Gem::Requirement
138
+ none: false
139
+ requirements:
140
+ - - ! '>='
141
+ - !ruby/object:Gem::Version
142
+ version: 0.5.3
143
+ type: :development
144
+ prerelease: false
145
+ version_requirements: *70240666971520
146
+ - !ruby/object:Gem::Dependency
147
+ name: activesupport
148
+ requirement: &70240666970180 !ruby/object:Gem::Requirement
149
+ none: false
150
+ requirements:
151
+ - - ! '>='
152
+ - !ruby/object:Gem::Version
153
+ version: 3.1.1
154
+ type: :development
155
+ prerelease: false
156
+ version_requirements: *70240666970180
157
+ description: Cline - CLI Line Notifier
15
158
  email:
16
159
  - celluloid.key@gmail.com
17
- executables: []
160
+ executables:
161
+ - cline
18
162
  extensions: []
19
163
  extra_rdoc_files: []
20
164
  files:
21
165
  - .gitignore
166
+ - .rspec
22
167
  - Gemfile
168
+ - README.md
23
169
  - Rakefile
170
+ - bin/cline
24
171
  - cline.gemspec
25
172
  - lib/cline.rb
173
+ - lib/cline/command.rb
174
+ - lib/cline/fetchers.rb
175
+ - lib/cline/notification.rb
176
+ - lib/cline/out_streams.rb
26
177
  - lib/cline/version.rb
178
+ - spec/fabricators.rb
179
+ - spec/lib/cline_spec.rb
180
+ - spec/lib/notification_spec.rb
181
+ - spec/spec_helper.rb
182
+ - spec/support/.gitkeep
27
183
  homepage: https://github.com/hibariya/cline
28
184
  licenses: []
29
185
  post_install_message:
@@ -44,8 +200,13 @@ required_rubygems_version: !ruby/object:Gem::Requirement
44
200
  version: '0'
45
201
  requirements: []
46
202
  rubyforge_project:
47
- rubygems_version: 1.8.10
203
+ rubygems_version: 1.8.11
48
204
  signing_key:
49
205
  specification_version: 3
50
- summary: CLI local news
51
- test_files: []
206
+ summary: CLI Line Notifier
207
+ test_files:
208
+ - spec/fabricators.rb
209
+ - spec/lib/cline_spec.rb
210
+ - spec/lib/notification_spec.rb
211
+ - spec/spec_helper.rb
212
+ - spec/support/.gitkeep