nntp_scrape 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +56 -0
- data/Rakefile +1 -0
- data/bin/nntp_scrape +5 -0
- data/lib/nntp_scrape.rb +24 -0
- data/lib/nntp_scrape/cli.rb +47 -0
- data/lib/nntp_scrape/commands/auth_info.rb +16 -0
- data/lib/nntp_scrape/commands/base.rb +46 -0
- data/lib/nntp_scrape/commands/capabilities.rb +18 -0
- data/lib/nntp_scrape/commands/group.rb +27 -0
- data/lib/nntp_scrape/commands/head.rb +28 -0
- data/lib/nntp_scrape/commands/next.rb +9 -0
- data/lib/nntp_scrape/commands/xhdr.rb +26 -0
- data/lib/nntp_scrape/commands/xover.rb +25 -0
- data/lib/nntp_scrape/config.rb +24 -0
- data/lib/nntp_scrape/nntp.rb +163 -0
- data/lib/nntp_scrape/nntp_command.rb +40 -0
- data/lib/nntp_scrape/repl.rb +41 -0
- data/lib/nntp_scrape/version.rb +3 -0
- data/nntp_scrape.gemspec +24 -0
- metadata +133 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Scott Fleckenstein
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
# NntpScrape
|
2
|
+
|
3
|
+
Various usenet related utilities
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'nntp_scrape'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install nntp_scrape
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
First, write a config file at ~/.nntp_scrape
|
22
|
+
|
23
|
+
```yaml
|
24
|
+
host: unlimited.newshosting.com
|
25
|
+
port: 563 #default
|
26
|
+
ssl: true #default
|
27
|
+
user: foobar
|
28
|
+
pass: bazbak
|
29
|
+
|
30
|
+
```
|
31
|
+
|
32
|
+
Then you can:
|
33
|
+
|
34
|
+
```bash
|
35
|
+
#/usr/bin/env bash
|
36
|
+
# watches the provided group, outputting a line of json everytime a new article is posted
|
37
|
+
nntp_scrape headers alt.binaries.teevee
|
38
|
+
|
39
|
+
# starts a repl to interact with the usenet server directly
|
40
|
+
nntp_scrape repl
|
41
|
+
>> run Capabilities
|
42
|
+
=> #<NntpScrape::Commands::Capabilities:0x007ffc56914e20
|
43
|
+
@caps=
|
44
|
+
["VERSION 1",
|
45
|
+
"MODE-READER",
|
46
|
+
"READER",
|
47
|
+
...
|
48
|
+
```
|
49
|
+
|
50
|
+
## Contributing
|
51
|
+
|
52
|
+
1. Fork it
|
53
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
54
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
55
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
56
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/bin/nntp_scrape
ADDED
data/lib/nntp_scrape.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require "nntp_scrape/version"
|
2
|
+
require 'active_support/core_ext/string'
|
3
|
+
|
4
|
+
module NntpScrape
|
5
|
+
autoload :Cli, "nntp_scrape/cli"
|
6
|
+
autoload :Config, "nntp_scrape/config"
|
7
|
+
autoload :NNTP, "nntp_scrape/nntp"
|
8
|
+
autoload :Repl, "nntp_scrape/repl"
|
9
|
+
|
10
|
+
module Commands
|
11
|
+
autoload :Base, "nntp_scrape/commands/base"
|
12
|
+
autoload :AuthInfo, "nntp_scrape/commands/auth_info"
|
13
|
+
autoload :Group, "nntp_scrape/commands/group"
|
14
|
+
autoload :Next, "nntp_scrape/commands/next"
|
15
|
+
autoload :Head, "nntp_scrape/commands/head"
|
16
|
+
autoload :Xhdr, "nntp_scrape/commands/xhdr"
|
17
|
+
autoload :Xover, "nntp_scrape/commands/xover"
|
18
|
+
autoload :Capabilities, "nntp_scrape/commands/capabilities"
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
|
24
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'thor'
|
2
|
+
require 'active_support'
|
3
|
+
|
4
|
+
module NntpScrape
|
5
|
+
|
6
|
+
class Cli < Thor
|
7
|
+
class_option :config,
|
8
|
+
:desc => "Configuration file location",
|
9
|
+
:type => :string,
|
10
|
+
:default => ENV["HOME"] + "/.nntp_scrape"
|
11
|
+
|
12
|
+
desc "headers GROUP", "watches GROUP, outputting lines of json as new headers are found"
|
13
|
+
def headers(group)
|
14
|
+
setup
|
15
|
+
|
16
|
+
@client.watch_new(group) do |result|
|
17
|
+
p result
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
desc "repl", "Starts an interactive repl"
|
23
|
+
def repl
|
24
|
+
setup
|
25
|
+
r = Repl.new(@client)
|
26
|
+
r.start
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
private
|
31
|
+
def setup
|
32
|
+
config, status = *Config.load(options[:config])
|
33
|
+
|
34
|
+
unless status == true
|
35
|
+
puts status
|
36
|
+
exit 1
|
37
|
+
end
|
38
|
+
|
39
|
+
@client = NNTP.new(config.host, config.port, config.ssl, config.user, config.pass)
|
40
|
+
|
41
|
+
unless @client.logged_in?
|
42
|
+
puts "login failed"
|
43
|
+
exit 1
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module NntpScrape
|
2
|
+
module Commands
|
3
|
+
class Base
|
4
|
+
attr_reader :response, :status_line, :lines
|
5
|
+
attr_accessor :timeout
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@timeout = 5
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.supported?(client)
|
12
|
+
true
|
13
|
+
end
|
14
|
+
|
15
|
+
def ran?
|
16
|
+
status_line.present?
|
17
|
+
end
|
18
|
+
|
19
|
+
def success?
|
20
|
+
return false unless ran?
|
21
|
+
status_code[0] == "1" || status_code[0] == "2"
|
22
|
+
end
|
23
|
+
|
24
|
+
def continue?
|
25
|
+
success? || status_code[0] == "3"
|
26
|
+
end
|
27
|
+
|
28
|
+
def status_code
|
29
|
+
@status_code ||= status_line.split.first
|
30
|
+
end
|
31
|
+
|
32
|
+
def execute(client)
|
33
|
+
raise "implement in subclass"
|
34
|
+
end
|
35
|
+
|
36
|
+
def run_short(client, cmd, *params)
|
37
|
+
@status_line = client.run_short(cmd, *params).strip
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
def run_long(client, cmd, *params)
|
42
|
+
@status_line, @lines = *client.run_long(cmd, *params)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module NntpScrape
|
2
|
+
module Commands
|
3
|
+
class Capabilities < Base
|
4
|
+
attr_reader :caps
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@caps = []
|
8
|
+
end
|
9
|
+
|
10
|
+
def execute(client)
|
11
|
+
run_long client, "CAPABILITIES"
|
12
|
+
return unless success?
|
13
|
+
|
14
|
+
@caps = lines
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module NntpScrape
|
2
|
+
module Commands
|
3
|
+
class Group < Base
|
4
|
+
attr_reader :article_count
|
5
|
+
attr_reader :low_id
|
6
|
+
attr_reader :high_id
|
7
|
+
|
8
|
+
PARSER = /^(\d+)\s+(\d+)\s+(\d+)\s+(\d+)/
|
9
|
+
|
10
|
+
def initialize(group)
|
11
|
+
@group = group
|
12
|
+
end
|
13
|
+
|
14
|
+
def execute(client)
|
15
|
+
run_short client, "GROUP", @group
|
16
|
+
|
17
|
+
if match = PARSER.match(status_line)
|
18
|
+
@article_count = match[2]
|
19
|
+
@low_id = match[3]
|
20
|
+
@high_id = match[4]
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module NntpScrape
|
2
|
+
module Commands
|
3
|
+
class Head < Base
|
4
|
+
attr_reader :message_id
|
5
|
+
attr_reader :data
|
6
|
+
|
7
|
+
def initialize(number=nil)
|
8
|
+
@number = number
|
9
|
+
end
|
10
|
+
|
11
|
+
def execute(client)
|
12
|
+
if @number.present?
|
13
|
+
run_long client, "HEAD", @number
|
14
|
+
else
|
15
|
+
run_long client, "HEAD"
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
@message_id = status_line.split.last
|
20
|
+
@data = {}
|
21
|
+
lines.each do |line|
|
22
|
+
key, value = *line.split(": ", 2)
|
23
|
+
@data[key] = value
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module NntpScrape
|
2
|
+
module Commands
|
3
|
+
class Xhdr < Base
|
4
|
+
attr_reader :results
|
5
|
+
|
6
|
+
def self.supported?(client)
|
7
|
+
client.caps.include? "XHDR"
|
8
|
+
end
|
9
|
+
|
10
|
+
def initialize(field, range)
|
11
|
+
@field = field
|
12
|
+
@range = range
|
13
|
+
end
|
14
|
+
|
15
|
+
def execute(client)
|
16
|
+
if @range.is_a? Range
|
17
|
+
run_long client, "XHDR", @field, "#{@range.begin}-#{@range.end}"
|
18
|
+
else
|
19
|
+
run_long client, "XHDR", @field, @range
|
20
|
+
end
|
21
|
+
|
22
|
+
@results = @lines.map{|l| l.split(" ")}
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module NntpScrape
|
2
|
+
module Commands
|
3
|
+
class Xover < Base
|
4
|
+
attr_reader :results
|
5
|
+
|
6
|
+
def self.supported?(client)
|
7
|
+
client.caps.include? "XOVER"
|
8
|
+
end
|
9
|
+
|
10
|
+
def initialize(range)
|
11
|
+
@range = range
|
12
|
+
end
|
13
|
+
|
14
|
+
def execute(client)
|
15
|
+
if @range.is_a? Range
|
16
|
+
run_long client, "XOVER", "#{@range.begin}-#{@range.end}"
|
17
|
+
else
|
18
|
+
run_long client, "XOVER", @range
|
19
|
+
end
|
20
|
+
|
21
|
+
@results = @lines.map{|l| l.split("\t")}
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'hashie'
|
3
|
+
|
4
|
+
module NntpScrape
|
5
|
+
class Config < Hashie::Trash
|
6
|
+
property :host, :required => true
|
7
|
+
property :ssl, :default => true
|
8
|
+
property :port, :default => 563
|
9
|
+
property :user, :required => true
|
10
|
+
property :pass, :required => true
|
11
|
+
|
12
|
+
def self.load(config_path)
|
13
|
+
return [nil, :path_doesnt_exist] unless File.exist?(config_path)
|
14
|
+
|
15
|
+
yaml = YAML.load_file(config_path)
|
16
|
+
[new(yaml), true]
|
17
|
+
rescue Psych::SyntaxError
|
18
|
+
[nil, :invalid_yaml]
|
19
|
+
rescue ArgumentError => e
|
20
|
+
[nil, e]
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,163 @@
|
|
1
|
+
require 'socket'
|
2
|
+
require 'openssl'
|
3
|
+
require 'timeout'
|
4
|
+
|
5
|
+
module NntpScrape
|
6
|
+
class NNTP
|
7
|
+
attr_reader :socket
|
8
|
+
attr_reader :caps
|
9
|
+
|
10
|
+
def initialize(host, port, ssl, user, pass)
|
11
|
+
@host = host
|
12
|
+
@port = port
|
13
|
+
@ssl = ssl
|
14
|
+
@user = user
|
15
|
+
@pass = pass
|
16
|
+
@caps = []
|
17
|
+
open
|
18
|
+
end
|
19
|
+
|
20
|
+
def logged_in?
|
21
|
+
@logged_in
|
22
|
+
end
|
23
|
+
|
24
|
+
def debug?
|
25
|
+
@debug
|
26
|
+
end
|
27
|
+
|
28
|
+
def debug=(val)
|
29
|
+
@debug = val
|
30
|
+
end
|
31
|
+
|
32
|
+
def open
|
33
|
+
@socket = make_socket
|
34
|
+
login
|
35
|
+
end
|
36
|
+
|
37
|
+
def watch(group, start_id=nil)
|
38
|
+
watch = Commands::Group.new(group)
|
39
|
+
run watch
|
40
|
+
return false unless watch.success?
|
41
|
+
|
42
|
+
puts watch.high_id
|
43
|
+
head = Commands::Head.new(start_id || watch.high_id)
|
44
|
+
run head
|
45
|
+
return false unless head.success?
|
46
|
+
|
47
|
+
loop do
|
48
|
+
commands = [Commands::Next.new, Commands::Head.new]
|
49
|
+
|
50
|
+
unless run *commands
|
51
|
+
puts "pausing..."
|
52
|
+
sleep 1
|
53
|
+
next
|
54
|
+
end
|
55
|
+
puts commands[1].message_id
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def watch_new(group, start_id=nil)
|
60
|
+
if start_id.nil?
|
61
|
+
watch = Commands::Group.new(group)
|
62
|
+
run watch
|
63
|
+
return false unless watch.success?
|
64
|
+
start_id = watch.high_id
|
65
|
+
end
|
66
|
+
|
67
|
+
loop do
|
68
|
+
watch = Commands::Group.new(group)
|
69
|
+
run watch
|
70
|
+
next unless watch.success?
|
71
|
+
|
72
|
+
end_id = watch.high_id
|
73
|
+
|
74
|
+
if start_id == end_id
|
75
|
+
sleep 1
|
76
|
+
next
|
77
|
+
end
|
78
|
+
|
79
|
+
xhdr = Commands::Xhdr.new "Message-ID", start_id...end_id
|
80
|
+
run xhdr
|
81
|
+
next unless xhdr.success?
|
82
|
+
|
83
|
+
xhdr.results.each{|v| yield v}
|
84
|
+
start_id = end_id
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
|
89
|
+
##
|
90
|
+
# Executes an array of commands until one fails
|
91
|
+
#
|
92
|
+
# @return [Boolean] true if all commands succeeded
|
93
|
+
def run(*commands)
|
94
|
+
commands.each do |cmd|
|
95
|
+
Timeout::timeout(cmd.timeout) do
|
96
|
+
cmd.execute(self)
|
97
|
+
end
|
98
|
+
return false unless cmd.continue?
|
99
|
+
end
|
100
|
+
true
|
101
|
+
rescue Errno::EPIPE, Errno::ECONNRESET, OpenSSL::SSL::SSLError, Timeout::Error
|
102
|
+
open
|
103
|
+
retry
|
104
|
+
end
|
105
|
+
|
106
|
+
|
107
|
+
def run_short(cmd, *params)
|
108
|
+
command = "#{cmd} #{params.join " "}"
|
109
|
+
|
110
|
+
puts command if debug?
|
111
|
+
|
112
|
+
socket.print "#{command}\r\n"
|
113
|
+
status_line = socket.gets
|
114
|
+
|
115
|
+
puts status_line if debug?
|
116
|
+
|
117
|
+
status_line
|
118
|
+
end
|
119
|
+
|
120
|
+
def run_long(cmd, *params)
|
121
|
+
status_line = run_short(cmd, *params)
|
122
|
+
lines = []
|
123
|
+
|
124
|
+
return [status_line, lines] unless ["2", "1"].include? status_line[0] #i.e. success
|
125
|
+
|
126
|
+
loop do
|
127
|
+
next_line = socket.gets
|
128
|
+
break if next_line.strip == "."
|
129
|
+
lines << next_line.strip
|
130
|
+
end
|
131
|
+
[status_line, lines]
|
132
|
+
end
|
133
|
+
|
134
|
+
private
|
135
|
+
def make_socket
|
136
|
+
socket = TCPSocket.open @host, @port
|
137
|
+
|
138
|
+
if @ssl
|
139
|
+
cert_store = OpenSSL::X509::Store.new
|
140
|
+
cert_store.set_default_paths
|
141
|
+
ssl_context = OpenSSL::SSL::SSLContext.new
|
142
|
+
ssl_context.cert_store = cert_store
|
143
|
+
socket = OpenSSL::SSL::SSLSocket.new socket, ssl_context
|
144
|
+
socket.connect
|
145
|
+
end
|
146
|
+
|
147
|
+
socket.gets
|
148
|
+
socket
|
149
|
+
end
|
150
|
+
|
151
|
+
def login
|
152
|
+
login_commands = [
|
153
|
+
Commands::AuthInfo.new("USER", @user),
|
154
|
+
Commands::AuthInfo.new("PASS", @pass),
|
155
|
+
Commands::Capabilities.new,
|
156
|
+
]
|
157
|
+
@logged_in = run *login_commands
|
158
|
+
|
159
|
+
@caps = login_commands.last.caps
|
160
|
+
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module NntpScrape
|
2
|
+
class NNTPCommand
|
3
|
+
attr_reader :response, :status_line
|
4
|
+
|
5
|
+
def success?
|
6
|
+
status_code[0] == "2"
|
7
|
+
end
|
8
|
+
|
9
|
+
def status_code
|
10
|
+
@status_code ||= status_line.split.first
|
11
|
+
end
|
12
|
+
|
13
|
+
def execute(client)
|
14
|
+
raise "implement in subclass"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class ShortCommand < NNTPCommand
|
19
|
+
def execute(client)
|
20
|
+
socket = client.socket
|
21
|
+
socket.print "#{cmd} #{params.join " "}\r\n"
|
22
|
+
@status_line = socket.gets
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class LongCommand < ShortCommand
|
27
|
+
attr_reader :lines
|
28
|
+
|
29
|
+
def execute(client)
|
30
|
+
super client
|
31
|
+
|
32
|
+
@lines = []
|
33
|
+
loop do
|
34
|
+
next_line = socket.gets
|
35
|
+
break if next_line.strip == "."
|
36
|
+
@lines << next_line
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'pry'
|
2
|
+
|
3
|
+
module NntpScrape
|
4
|
+
class Repl
|
5
|
+
include Commands
|
6
|
+
|
7
|
+
attr_reader :client
|
8
|
+
|
9
|
+
def initialize(client)
|
10
|
+
@client = client
|
11
|
+
end
|
12
|
+
|
13
|
+
def start
|
14
|
+
my_prompt = [
|
15
|
+
proc { |obj, *| ">> " },
|
16
|
+
proc { |obj, *| "*> "}
|
17
|
+
]
|
18
|
+
binding.pry :quiet => true, :cli => true, :prompt => my_prompt
|
19
|
+
end
|
20
|
+
|
21
|
+
def run(klass, *params)
|
22
|
+
cmd = klass.new(*params)
|
23
|
+
@client.run(cmd)
|
24
|
+
cmd
|
25
|
+
end
|
26
|
+
|
27
|
+
def short(cmd, *params)
|
28
|
+
@client.run_short(cmd, *params)
|
29
|
+
rescue Errno::EPIPE, Errno::ECONNRESET, OpenSSL::SSL::SSLError
|
30
|
+
@client.open
|
31
|
+
retry
|
32
|
+
end
|
33
|
+
|
34
|
+
def long(cmd, *params)
|
35
|
+
@client.run_long(cmd, *params)
|
36
|
+
rescue Errno::EPIPE, Errno::ECONNRESET, OpenSSL::SSL::SSLError
|
37
|
+
@client.open
|
38
|
+
retry
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
data/nntp_scrape.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'nntp_scrape/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "nntp_scrape"
|
8
|
+
gem.version = NntpScrape::VERSION
|
9
|
+
gem.authors = ["Scott Fleckenstein"]
|
10
|
+
gem.email = ["nullstyle@gmail.com"]
|
11
|
+
gem.description = %q{A series of command line tools to interact with usenet}
|
12
|
+
gem.summary = %q{A series of command line tools to interact with usenet}
|
13
|
+
gem.homepage = ""
|
14
|
+
|
15
|
+
gem.files = `git ls-files`.split($/)
|
16
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
|
+
gem.require_paths = ["lib"]
|
19
|
+
|
20
|
+
gem.add_dependency "thor", ">= 0.16.0"
|
21
|
+
gem.add_dependency "activesupport", ">= 3.2.9"
|
22
|
+
gem.add_dependency "hashie", ">= 1.2.0"
|
23
|
+
gem.add_dependency "pry", ">= 0.9.10"
|
24
|
+
end
|
metadata
ADDED
@@ -0,0 +1,133 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: nntp_scrape
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Scott Fleckenstein
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-12-11 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: thor
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 0.16.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: 0.16.0
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: activesupport
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: 3.2.9
|
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: 3.2.9
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: hashie
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: 1.2.0
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 1.2.0
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: pry
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: 0.9.10
|
70
|
+
type: :runtime
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: 0.9.10
|
78
|
+
description: A series of command line tools to interact with usenet
|
79
|
+
email:
|
80
|
+
- nullstyle@gmail.com
|
81
|
+
executables:
|
82
|
+
- nntp_scrape
|
83
|
+
extensions: []
|
84
|
+
extra_rdoc_files: []
|
85
|
+
files:
|
86
|
+
- .gitignore
|
87
|
+
- Gemfile
|
88
|
+
- LICENSE.txt
|
89
|
+
- README.md
|
90
|
+
- Rakefile
|
91
|
+
- bin/nntp_scrape
|
92
|
+
- lib/nntp_scrape.rb
|
93
|
+
- lib/nntp_scrape/cli.rb
|
94
|
+
- lib/nntp_scrape/commands/auth_info.rb
|
95
|
+
- lib/nntp_scrape/commands/base.rb
|
96
|
+
- lib/nntp_scrape/commands/capabilities.rb
|
97
|
+
- lib/nntp_scrape/commands/group.rb
|
98
|
+
- lib/nntp_scrape/commands/head.rb
|
99
|
+
- lib/nntp_scrape/commands/next.rb
|
100
|
+
- lib/nntp_scrape/commands/xhdr.rb
|
101
|
+
- lib/nntp_scrape/commands/xover.rb
|
102
|
+
- lib/nntp_scrape/config.rb
|
103
|
+
- lib/nntp_scrape/nntp.rb
|
104
|
+
- lib/nntp_scrape/nntp_command.rb
|
105
|
+
- lib/nntp_scrape/repl.rb
|
106
|
+
- lib/nntp_scrape/version.rb
|
107
|
+
- nntp_scrape.gemspec
|
108
|
+
homepage: ''
|
109
|
+
licenses: []
|
110
|
+
post_install_message:
|
111
|
+
rdoc_options: []
|
112
|
+
require_paths:
|
113
|
+
- lib
|
114
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
115
|
+
none: false
|
116
|
+
requirements:
|
117
|
+
- - ! '>='
|
118
|
+
- !ruby/object:Gem::Version
|
119
|
+
version: '0'
|
120
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
121
|
+
none: false
|
122
|
+
requirements:
|
123
|
+
- - ! '>='
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '0'
|
126
|
+
requirements: []
|
127
|
+
rubyforge_project:
|
128
|
+
rubygems_version: 1.8.23
|
129
|
+
signing_key:
|
130
|
+
specification_version: 3
|
131
|
+
summary: A series of command line tools to interact with usenet
|
132
|
+
test_files: []
|
133
|
+
has_rdoc:
|