cline 0.2.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
data/cline.gemspec CHANGED
@@ -12,18 +12,15 @@ Gem::Specification.new do |s|
12
12
  s.description = %q{Cline - CLI Line Notifier}
13
13
 
14
14
  s.post_install_message = <<-EOM
15
- **Important** Some APIs name have changed.
15
+ **Important** Some features were added.
16
16
 
17
- s/fetch/collect/g
17
+ * New collector (GitHub News Feed) is available.
18
+ $ echo "Cline.collectors << Cline::Collectors::Github" >> ~/.cline/config
19
+ $ echo "Cline::Collectors::Github.login_name = 'your_github_login'" >> ~/.cline/config
18
20
 
19
- * Command
20
- $ cline fetch
21
- => $ cline collect
22
-
23
- * Classes And Modules
24
- Cline.fechers => Cline.collectors
25
- Cline::Fetchers => Cline::Collectors
26
- Cline::Fetchers::Feed.fetch => Cline::Fetchers::Feed.collect
21
+ * `search` command is available.
22
+ usage:
23
+ $ cline search [keyword]
27
24
  EOM
28
25
 
29
26
  #s.rubyforge_project = "cline"
@@ -41,6 +38,7 @@ Gem::Specification.new do |s|
41
38
  s.add_runtime_dependency 'activerecord', ['>= 3.1.1']
42
39
  s.add_runtime_dependency 'sqlite3', ['>= 1.3.4']
43
40
  s.add_runtime_dependency 'feedzirra', ['~> 0.0.31'] # FIXME builder dependency workaround...
41
+ s.add_runtime_dependency 'notify', ['>= 0.3.0']
44
42
 
45
43
  s.add_development_dependency 'rake', ['>= 0.9.2']
46
44
  s.add_development_dependency 'ir_b', ['>= 1.4.0']
@@ -0,0 +1,63 @@
1
+ # coding: utf-8
2
+
3
+ module Cline::Collectors
4
+ class Feed < Base
5
+ class << self
6
+ def collect
7
+ new(opml_path.read).entries.each do |entry|
8
+ message = Cline::Notification.normalize_message("#{entry.title} #{entry.url}").encode(Encoding::UTF_8)
9
+ create_or_pass message, entry.published
10
+ end
11
+ end
12
+
13
+ def opml_path
14
+ opml = Pathname.new("#{Cline.cline_dir}/feeds.xml")
15
+ end
16
+ end
17
+
18
+ def initialize(opml_str)
19
+ require 'rexml/document'
20
+ require 'feedzirra'
21
+
22
+ @opml = REXML::Document.new(opml_str)
23
+ @feeds = parse_opml(@opml.elements['opml/body'])
24
+ end
25
+
26
+ def entries
27
+ entries = []
28
+
29
+ @feeds.map { |feed_url|
30
+ Thread.fork {
31
+ begin
32
+ feed = Feedzirra::Feed.fetch_and_parse(feed_url)
33
+
34
+ if feed.is_a?(Feedzirra::FeedUtilities)
35
+ feed.entries.each { |entry| entries << entry }
36
+ end
37
+ rescue
38
+ puts $!.class, $!.message
39
+ ensure
40
+ Thread.pass
41
+ end
42
+ }
43
+ }.map(&:join)
44
+
45
+ entries
46
+ end
47
+
48
+ def parse_opml(opml_node)
49
+ feeds = []
50
+
51
+ opml_node.elements.each('outline') do |el|
52
+ unless el.elements.size.zero?
53
+ feeds += parse_opml(el)
54
+ else
55
+ url = el.attributes['xmlUrl']
56
+ feeds << url if url
57
+ end
58
+ end
59
+
60
+ feeds
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,139 @@
1
+ # coding: utf-8
2
+
3
+ module Cline::Collectors
4
+ class Github < Base
5
+ class << self
6
+ def collect
7
+ new(login_name).activities.each do |message, time|
8
+ create_or_pass message, time
9
+ end
10
+ end
11
+
12
+ def login_name=(name)
13
+ @login_name = name
14
+ end
15
+
16
+ def login_name
17
+ @login_name
18
+ end
19
+ end
20
+
21
+ Activity = Struct.new(:type, :actor, :action, :url)
22
+
23
+ def initialize(name)
24
+ require 'open-uri'
25
+ require 'uri'
26
+
27
+ @api_url = URI.parse("https://api.github.com/users/#{name}/received_events")
28
+ end
29
+
30
+ def activities
31
+ events = JSON.parse(@api_url.read)
32
+
33
+ events.map { |event|
34
+ [extract_message(event), event['created_at']]
35
+ }
36
+ end
37
+
38
+ private
39
+
40
+ def extract_message(event)
41
+ act = extract_activity(event)
42
+ return unless act
43
+
44
+ event_string = act.type.gsub(/Event/i, '')
45
+ message = Cline::Notification.normalize_message("#{event_string}: #{act.actor} #{act.action} #{act.url}")
46
+ message.encode(Encoding::UTF_8)
47
+ end
48
+
49
+ def extract_activity(event)
50
+ activity = Activity.new(
51
+ event['type'],
52
+ event['actor']['login']
53
+ )
54
+
55
+ apply_method = "apply_for_#{event_name_to_underscore(event['type'])}"
56
+
57
+ if respond_to?(apply_method, true)
58
+ payload = event['payload']
59
+ send apply_method, activity, event, payload
60
+ else
61
+ return nil
62
+ end
63
+ end
64
+
65
+ def base_url
66
+ @base_url ||= URI.parse('https://github.com/')
67
+ end
68
+
69
+ def event_name_to_underscore(str)
70
+ str.chars.inject([]) {|r, c| # XXX 難しい
71
+ r << (r.empty? ? c.downcase : (c.match(/[A-Z]/) ? "_#{c.downcase}" : c))
72
+ }.join
73
+ end
74
+
75
+ def apply_for_commit_comment_event(activity, event, payload)
76
+ activity.url = payload['comment']['html_url']
77
+ activity
78
+ end
79
+
80
+ def apply_for_create_event(activity, event, payload)
81
+ activity.url = base_url + event['repo']['name']
82
+ activity
83
+ end
84
+
85
+ def apply_for_follow_event(activity, event, payload)
86
+ activity.url = payload['target']['html_url']
87
+ activity
88
+ end
89
+
90
+ def apply_for_fork_event(activity, event, payload)
91
+ activity.url = payload['forkee']['html_url']
92
+ activity
93
+ end
94
+
95
+ def apply_for_gist_event(activity, event, payload)
96
+ activity.action = payload['action']
97
+ activity.url = payload['gist']['html_url']
98
+ activity
99
+ end
100
+
101
+ def apply_for_gollum_event(activity, event, payload)
102
+ page = payload['pages'].first # TODO pages contain some pages information
103
+ activity.action = page['action']
104
+ activity.url = page['html_url']
105
+ activity
106
+ end
107
+
108
+ def apply_for_issue_comment_event(activity, event, payload)
109
+ activity.action = payload['action']
110
+ activity.url = payload['issue']['html_url']
111
+ activity
112
+ end
113
+
114
+ alias_method :apply_for_issues_event, :apply_for_issue_comment_event
115
+
116
+ def apply_for_member_event(activity, event, payload)
117
+ activity.action = payload['action']
118
+ activity.url = base_url + payload['member']['login']
119
+ activity
120
+ end
121
+
122
+ def apply_for_pull_request_event(activity, event, payload)
123
+ activity.action = payload['action']
124
+ activity.url = payload['pull_request']['_links']['html']['href']
125
+ activity
126
+ end
127
+
128
+ def apply_for_push_event(activity, event, payload)
129
+ activity.url = base_url + event['repo']['name']
130
+ activity
131
+ end
132
+
133
+ def apply_for_watch_event(activity, event, payload)
134
+ activity.action = payload['action'] # WatchEvent
135
+ activity.url = base_url + event['repo']['name']
136
+ activity
137
+ end
138
+ end
139
+ end
@@ -1,72 +1,16 @@
1
1
  # coding: utf-8
2
2
 
3
3
  module Cline::Collectors
4
- class Feed
5
- class << self
6
- def collect
7
- entries = new(opml_path.read).collect
8
-
9
- entries.each do |entry|
10
- begin
11
- Cline::Notification.instance_exec entry do |entry|
12
- message = normalize_message("#{entry.title} #{entry.url}").encode(Encoding::UTF_8)
13
-
14
- find_by_message(message) || create(message: message, time: entry.published)
15
- end
16
- rescue ActiveRecord::StatementInvalid => e
17
- puts e.class, e.message
18
- end
19
- end
20
- end
21
-
22
- def opml_path
23
- opml = Pathname.new("#{Cline.cline_dir}/feeds.xml")
24
- end
25
- end
26
-
27
- def initialize(opml_str)
28
- require 'rexml/document'
29
- require 'feedzirra'
30
-
31
- @opml = REXML::Document.new(opml_str)
32
- @feeds = parse_opml(@opml.elements['opml/body'])
33
- end
34
-
35
- def collect
36
- entries = []
37
-
38
- @feeds.map { |feed_url|
39
- Thread.fork {
40
- begin
41
- feed = Feedzirra::Feed.fetch_and_parse(feed_url)
42
-
43
- if feed.is_a?(Feedzirra::FeedUtilities)
44
- feed.entries.each { |entry| entries << entry }
45
- end
46
- rescue
47
- puts $!.class, $!.message
48
- ensure
49
- Thread.pass
50
- end
51
- }
52
- }.map(&:join)
53
-
54
- entries
55
- end
56
-
57
- def parse_opml(opml_node)
58
- feeds = []
59
-
60
- opml_node.elements.each('outline') do |el|
61
- unless el.elements.size.zero?
62
- feeds += parse_opml(el)
63
- else
64
- url = el.attributes['xmlUrl']
65
- feeds << url if url
66
- end
4
+ class Base
5
+ def self.create_or_pass(message, time)
6
+ Cline::Notification.instance_exec message, time do |message, time|
7
+ create(message: message, time: time) unless find_by_message_and_time(message, time)
67
8
  end
68
-
69
- feeds
9
+ rescue ActiveRecord::StatementInvalid => e
10
+ puts e.class, e.message
70
11
  end
71
12
  end
72
13
  end
14
+
15
+ require 'cline/collectors/feed'
16
+ require 'cline/collectors/github'
data/lib/cline/command.rb CHANGED
@@ -7,12 +7,13 @@ module Cline
7
7
  super
8
8
  end
9
9
 
10
- map '-s' => :show,
11
- '-t' => :tick,
12
- '-s' => :status,
13
- '-c' => :collect,
14
- '-i' => :init,
15
- '-v' => :version
10
+ map '-s' => :show,
11
+ '-t' => :tick,
12
+ '-sr' => :search,
13
+ '-st' => :status,
14
+ '-c' => :collect,
15
+ '-i' => :init,
16
+ '-v' => :version
16
17
 
17
18
  desc 'show', 'Show a latest message'
18
19
  method_options offset: :integer
@@ -29,6 +30,14 @@ module Cline
29
30
  end
30
31
  end
31
32
 
33
+ desc 'search', 'Search by query'
34
+ method_options query: :string
35
+ def search(keyword = optoins[:query])
36
+ Notification.by_keyword(keyword).each do |notification|
37
+ say notification.display_message
38
+ end
39
+ end
40
+
32
41
  desc 'status', 'Show status'
33
42
  def status
34
43
  say "displayed : #{Notification.displayed.count}", :green
@@ -6,6 +6,10 @@ module Cline
6
6
  validate :message, presence: true, uniqueness: true
7
7
  validate :display_count, presence: true, numerically: true
8
8
 
9
+ scope :by_keyword, ->(word) {
10
+ where('message like ?', "%#{word}%").order('time DESC, display_count')
11
+ }
12
+
9
13
  scope :earliest, ->(limit = 1, offset = 0) {
10
14
  order('display_count, time').limit(limit).offset(offset)
11
15
  }
@@ -1,7 +1,18 @@
1
1
  # coding: utf-8
2
2
 
3
+ require 'notify'
4
+
3
5
  module Cline::OutStreams
4
- class WithGrowl
6
+ def self.const_missing(name)
7
+ case name
8
+ when :WithGrowl
9
+ WithNotify
10
+ else
11
+ super
12
+ end
13
+ end
14
+
15
+ class WithNotify
5
16
  attr_reader:stream
6
17
 
7
18
  def initialize(stream = $stdout)
@@ -10,7 +21,7 @@ module Cline::OutStreams
10
21
 
11
22
  def puts(str)
12
23
  puts_stream str
13
- puts_growlnotify str
24
+ Notify.notify '', str
14
25
  end
15
26
 
16
27
  private
@@ -18,9 +29,5 @@ module Cline::OutStreams
18
29
  def puts_stream(str)
19
30
  stream.puts str
20
31
  end
21
-
22
- def puts_growlnotify(str)
23
- `growlnotify -m '#{str}'`
24
- end
25
32
  end
26
33
  end
data/lib/cline/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Cline
2
- VERSION = "0.2.0"
2
+ VERSION = "0.2.1"
3
3
  end
@@ -0,0 +1,43 @@
1
+ {
2
+ "type": "CommitCommentEvent",
3
+ "public": true,
4
+ "org": {
5
+ "gravatar_id": "253768044712357787be0f6a3a53cc66",
6
+ "id": 639823,
7
+ "url": "https://api.github.com/orgs/",
8
+ "login": "travis-ci"
9
+ },
10
+ "created_at": "2011-11-12T03:14:21Z",
11
+ "payload": {
12
+ "comment": {
13
+ "position": null,
14
+ "created_at": "2011-11-12T03:14:21Z",
15
+ "line": null,
16
+ "body": "I would connect on start the way we did. You never know when lazy connections will be initialized with autoload & ActiveSupport::Dependencies magic and we need to guarantee that we do it after Unicorn forks.\r\n\r\nAs for the initializer (that only matters if you need amqp connection in tests and Rails console) this will work just as well.",
17
+ "commit_id": "d57b3e9b83ee8f265e0720e4b9fcda9b79b6c468",
18
+ "updated_at": "2011-11-12T03:14:21Z",
19
+ "url": "https://api.github.com/repos/travis-ci/travis-ci/comments/712684",
20
+ "id": 712684,
21
+ "path": null,
22
+ "user": {
23
+ "avatar_url": "https://secure.gravatar.com/avatar/388dee5b4fb00316282b5f40b8409438?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png",
24
+ "gravatar_id": "388dee5b4fb00316282b5f40b8409438",
25
+ "url": "https://api.github.com/users/michaelklishin",
26
+ "id": 1090,
27
+ "login": "michaelklishin"
28
+ },
29
+ "html_url": "https://github.com/travis-ci/travis-ci/commit/d57b3e9b83#commitcomment-712684"
30
+ }
31
+ },
32
+ "repo": {
33
+ "id": 1420493,
34
+ "url": "https://api.github.com/repos/travis-ci/travis-ci",
35
+ "name": "travis-ci/travis-ci"
36
+ },
37
+ "actor": {
38
+ "gravatar_id": "388dee5b4fb00316282b5f40b8409438",
39
+ "id": 1090,
40
+ "url": "https://api.github.com/users/michaelklishin",
41
+ "login": "michaelklishin"
42
+ }
43
+ }
@@ -0,0 +1,25 @@
1
+ {
2
+ "actor": {
3
+ "gravatar_id": "142f56fa109167d398a312e968485087",
4
+ "id": 7354,
5
+ "url": "https://api.github.com/users/jugyo",
6
+ "login": "jugyo"
7
+ },
8
+ "type": "CreateEvent",
9
+ "public": true,
10
+ "org": {
11
+ "url": "https://api.github.com/orgs/"
12
+ },
13
+ "payload": {
14
+ "master_branch": "master",
15
+ "ref": null,
16
+ "ref_type": "repository",
17
+ "description": ""
18
+ },
19
+ "repo": {
20
+ "id": 2754911,
21
+ "url": "https://api.github.com/repos/jugyo/unicolor",
22
+ "name": "jugyo/unicolor"
23
+ },
24
+ "created_at": "2011-11-11T09:32:04Z"
25
+ }
@@ -0,0 +1,26 @@
1
+ {
2
+ "actor": {
3
+ "gravatar_id": "1a3fec33b61038721fd4e609c1d1a4b7",
4
+ "id": 257678,
5
+ "url": "https://api.github.com/users/gabebw",
6
+ "login": "gabebw"
7
+ },
8
+ "type": "DeleteEvent",
9
+ "public": true,
10
+ "org": {
11
+ "gravatar_id": "a95a04df2dae60397c38c9bd04492c53",
12
+ "id": 6183,
13
+ "url": "https://api.github.com/orgs/",
14
+ "login": "thoughtbot"
15
+ },
16
+ "repo": {
17
+ "id": 2144417,
18
+ "url": "https://api.github.com/repos/thoughtbot/kumade",
19
+ "name": "thoughtbot/kumade"
20
+ },
21
+ "created_at": "2011-11-11T14:00:19Z",
22
+ "payload": {
23
+ "ref_type": "branch",
24
+ "ref": "clean-up-cucumber"
25
+ }
26
+ }
@@ -0,0 +1,41 @@
1
+ {
2
+ "actor": {
3
+ "gravatar_id": "17f1d633137766d9237c6af71738d091",
4
+ "id": 653658,
5
+ "url": "https://api.github.com/users/miural",
6
+ "login": "miural"
7
+ },
8
+ "type": "FollowEvent",
9
+ "public": false,
10
+ "org": {
11
+ "url": "https://api.github.com/orgs/"
12
+ },
13
+ "repo": {
14
+ "url": "https://api.github.com/repos//",
15
+ "name": "/"
16
+ },
17
+ "created_at": "2011-11-11T15:05:55Z",
18
+ "payload": {
19
+ "target": {
20
+ "name": "Hitoshi Amano",
21
+ "company": "Cybozu Labs, Inc",
22
+ "avatar_url": "https://secure.gravatar.com/avatar/fd5f685078e05abc24334d86adf20fae?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png",
23
+ "gravatar_id": "fd5f685078e05abc24334d86adf20fae",
24
+ "created_at": "2008-05-19T05:08:21Z",
25
+ "location": "Tokyo, Japan",
26
+ "blog": "http://d.hatena.ne.jp/amachang/",
27
+ "public_repos": 8,
28
+ "followers": 166,
29
+ "url": "https://api.github.com/users/amachang",
30
+ "public_gists": 0,
31
+ "id": 10735,
32
+ "hireable": false,
33
+ "type": "User",
34
+ "bio": null,
35
+ "login": "amachang",
36
+ "html_url": "https://github.com/amachang",
37
+ "following": 14,
38
+ "email": "seijro@gmail.com"
39
+ }
40
+ }
41
+ }
@@ -0,0 +1,55 @@
1
+ {
2
+ "actor": {
3
+ "gravatar_id": "dc03a27ae31ba428c560c00c9128cd75",
4
+ "id": 290782,
5
+ "url": "https://api.github.com/users/tricknotes",
6
+ "login": "tricknotes"
7
+ },
8
+ "type": "ForkEvent",
9
+ "public": true,
10
+ "org": {
11
+ "gravatar_id": "61024896f291303615bcd4f7a0dcfb74",
12
+ "id": 9919,
13
+ "url": "https://api.github.com/orgs/",
14
+ "login": "github"
15
+ },
16
+ "payload": {
17
+ "forkee": {
18
+ "master_branch": null,
19
+ "name": "gollum",
20
+ "size": 504,
21
+ "created_at": "2011-11-11T11:21:48Z",
22
+ "public": true,
23
+ "clone_url": "https://github.com/tricknotes/gollum.git",
24
+ "private": false,
25
+ "updated_at": "2011-11-11T11:21:48Z",
26
+ "watchers": 1,
27
+ "url": "https://api.github.com/repos/tricknotes/gollum",
28
+ "fork": true,
29
+ "ssh_url": "git@github.com:tricknotes/gollum.git",
30
+ "git_url": "git://github.com/tricknotes/gollum.git",
31
+ "language": "JavaScript",
32
+ "id": 2755421,
33
+ "svn_url": "https://svn.github.com/tricknotes/gollum",
34
+ "pushed_at": "2011-10-28T13:04:54Z",
35
+ "open_issues": 0,
36
+ "homepage": "",
37
+ "description": "A simple, Git-powered wiki with a sweet API and local frontend.",
38
+ "forks": 0,
39
+ "html_url": "https://github.com/tricknotes/gollum",
40
+ "owner": {
41
+ "avatar_url": "https://secure.gravatar.com/avatar/dc03a27ae31ba428c560c00c9128cd75?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png",
42
+ "gravatar_id": "dc03a27ae31ba428c560c00c9128cd75",
43
+ "url": "https://api.github.com/users/tricknotes",
44
+ "id": 290782,
45
+ "login": "tricknotes"
46
+ }
47
+ }
48
+ },
49
+ "repo": {
50
+ "id": 585285,
51
+ "url": "https://api.github.com/repos/github/gollum",
52
+ "name": "github/gollum"
53
+ },
54
+ "created_at": "2011-11-11T11:21:52Z"
55
+ }
@@ -0,0 +1,43 @@
1
+ {
2
+ "type": "GistEvent",
3
+ "public": true,
4
+ "org": {
5
+ "url": "https://api.github.com/orgs/"
6
+ },
7
+ "created_at": "2011-11-12T03:28:05Z",
8
+ "payload": {
9
+ "action": "create",
10
+ "gist": {
11
+ "created_at": "2011-11-12T03:28:04Z",
12
+ "comments": 0,
13
+ "public": true,
14
+ "git_push_url": "git@gist.github.com:1359990.git",
15
+ "files": {
16
+
17
+ },
18
+ "updated_at": "2011-11-12T03:28:04Z",
19
+ "url": "https://api.github.com/gists/1359990",
20
+ "id": "1359990",
21
+ "git_pull_url": "git://gist.github.com/1359990.git",
22
+ "description": "rake tasks for jasmine with guard-livereload",
23
+ "user": {
24
+ "gravatar_id": "088b1b43ff5dd64aa0f000da9e9da777",
25
+ "avatar_url": "https://secure.gravatar.com/avatar/088b1b43ff5dd64aa0f000da9e9da777?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png",
26
+ "url": "https://api.github.com/users/wtnabe",
27
+ "id": 18510,
28
+ "login": "wtnabe"
29
+ },
30
+ "html_url": "https://gist.github.com/1359990"
31
+ }
32
+ },
33
+ "repo": {
34
+ "url": "https://api.github.com/repos//",
35
+ "name": "/"
36
+ },
37
+ "actor": {
38
+ "gravatar_id": "088b1b43ff5dd64aa0f000da9e9da777",
39
+ "id": 18510,
40
+ "url": "https://api.github.com/users/wtnabe",
41
+ "login": "wtnabe"
42
+ }
43
+ }
@@ -0,0 +1,34 @@
1
+ {
2
+ "type": "GollumEvent",
3
+ "public": true,
4
+ "org": {
5
+ "gravatar_id": "d41d8cd98f00b204e9800998ecf8427e",
6
+ "id": 845682,
7
+ "url": "https://api.github.com/orgs/",
8
+ "login": "pry"
9
+ },
10
+ "created_at": "2011-11-12T02:15:32Z",
11
+ "payload": {
12
+ "pages": [
13
+ {
14
+ "sha": "2011d7ba43b8c40636bb025ee685e741c3287f8d",
15
+ "title": "State navigation",
16
+ "action": "edited",
17
+ "page_name": "State navigation",
18
+ "summary": null,
19
+ "html_url": "https://github.com/pry/pry/wiki/State-navigation"
20
+ }
21
+ ]
22
+ },
23
+ "repo": {
24
+ "id": 1145832,
25
+ "url": "https://api.github.com/repos/pry/pry",
26
+ "name": "pry/pry"
27
+ },
28
+ "actor": {
29
+ "gravatar_id": "890f7065d081f3a735362b6a1ca74b92",
30
+ "id": 17518,
31
+ "url": "https://api.github.com/users/banister",
32
+ "login": "banister"
33
+ }
34
+ }