tc-twitterer 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 6547466a301c154b30cb6ddbd3078659001b0713
4
+ data.tar.gz: ea5b46ce6cfac39b611823a22d7eeabe50a04518
5
+ SHA512:
6
+ metadata.gz: 308afacfbf5f1a571690d17f762049b1c9cc5dc53cb6e5a78a9a4508d8d0976315d9578092006b1792d5bea29bb43d8c2eb48c5e2ed7b15d7886b69c734efa8f
7
+ data.tar.gz: 5c38dd3ea1e701b24d3f2435eece396126a471d25476d97b24b1aff9acc1a4ab7893edce6ea24791589e408d4414ba6a44022d806b820571b55ac776d65bca9c
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ #FIXME source 'https://rubygems.org'
2
+ source 'http://rubygems.org'
3
+
4
+ # Specify your gem's dependencies in tc-twitterer.gemspec
5
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2017 tomonocle
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,135 @@
1
+ # twitterer
2
+ Program for tweeting random lines from files hosted on a public GitHub repo.
3
+
4
+ ## Purpose
5
+ I wanted an easy way to drip-feed my [Miscellany](https://github.com/tomonocle/miscellany/)
6
+ files into the ether, so wrote `twitterer` to do just that.
7
+
8
+ Essentially you feed it paths to publicly accessible files on GitHub and it'll
9
+ tweet a random line from them (one tweet per file) with a link to the line on
10
+ GitHub. Optionally it can keep track of previous tweets to avoid sounding
11
+ repetitive.
12
+
13
+ Note: source files must be in the default branch, but `twitterer` will resolve
14
+ that branch to a commit before tweeting the link.
15
+
16
+ ## Installation
17
+ ```
18
+ $ gem install tc-twitterer
19
+ ```
20
+
21
+ ## Configuration
22
+ `twitterer` requires a configuration file to operate. An example is available on
23
+ [GitHub](https://github.com/tomonocle/tc-twitterer/blob/master/etc/config.toml.example),
24
+ or you can follow the guide below.
25
+
26
+ ### Twitter Consumer API key and secret
27
+ First you need to register a new application with Twitter.
28
+
29
+ Visit [https://apps.twitter.com/app/new](https://apps.twitter.com/app/new) and follow the steps.
30
+
31
+ Once you've created the app, hit _Keys and Access Tokens_ to get your... Keys
32
+ and access tokens.
33
+
34
+ Config entries:
35
+
36
+ ```
37
+ twitter_consumer_key = "012a3AbcBdefg4ChiDjklEmnF"
38
+ twitter_consumer_secret = "AaBCDEbFGcdHefIgJKhi0jLk1MNOlm2nPQoRS3pTqU4rsVtWuv"
39
+ ```
40
+
41
+ ### Twitter User API key and secret
42
+ On the _Keys and Access Tokens_ page for your app, scroll down to _Your Access
43
+ Token_ and hit _Create my access token_ to create your access token.
44
+
45
+ Config entries:
46
+
47
+ ```
48
+ twitter_access_token = "012345678901234567-abAcBdeCDfEghFi0GHIJKjk9l1Lmnop"
49
+ twitter_access_token_secret = "a0AbcB1C2dDEFeGH7If3ghJK4Li15j678kMNlO9PQRmno"
50
+ ```
51
+
52
+ ### History file
53
+ `Twitterer` can track its past tweets if you tell it where to store them, which
54
+ prevents it from repeating itself.
55
+
56
+ Config entry:
57
+
58
+ ```
59
+ history_file = "/path/to/file"
60
+ ```
61
+
62
+ Note: `twitterer` will create the file, but it won't create any directories.
63
+
64
+ Note 2: history is stored in a CSV in the following format, which lets it
65
+ double as a log.
66
+
67
+ ```
68
+ timestamp,source,line
69
+ ```
70
+
71
+ ### Sources
72
+ Finally you need to specify the locations of the files you want to tweet from.
73
+
74
+ The following conditions apply:
75
+
76
+ - Must be hosted on GitHub
77
+ - Must be publicly accessible
78
+ - Must be in the default branch
79
+
80
+ Paths are specified in the following format: `username/repo/path/to/file.txt`
81
+
82
+ Config entry:
83
+
84
+ ```
85
+ source = [
86
+ "tomonocle/tc-twitterer/README.md",
87
+ "tomonocle/trello-list2card/README.md",
88
+ ]
89
+ ```
90
+
91
+ ### Final config file
92
+
93
+ ```
94
+ twitter_consumer_key = "012a3AbcBdefg4ChiDjklEmnF"
95
+ twitter_consumer_secret = "AaBCDEbFGcdHefIgJKhi0jLk1MNOlm2nPQoRS3pTqU4rsVtWuv"
96
+
97
+ twitter_access_token = "012345678901234567-abAcBdeCDfEghFi0GHIJKjk9l1Lmnop"
98
+ twitter_access_token_secret = "a0AbcB1C2dDEFeGH7If3ghJK4Li15j678kMNlO9PQRmno"
99
+
100
+ history_file = "./var/twitterer.history"
101
+
102
+ source = [
103
+ "tomonocle/tc-twitterer/README.md",
104
+ "tomonocle/trello-list2card/README.md",
105
+ ]
106
+ ```
107
+
108
+ ## Usage
109
+
110
+ ```
111
+ $ twitterer -c config.toml
112
+ ```
113
+
114
+ This will pull down each file in the _source_ list, pick a suitable line then tweet it in the following format:
115
+
116
+ ```
117
+ <first N characters of line> <url>
118
+ ```
119
+
120
+ Where `N` is defined as the maximum tweet length (which will _forever_ be 140 chars) minus the maximum URL length
121
+ from `t.co` (23 as of December 2017). 140-23 = **117**
122
+
123
+ Log output goes to `STDERR`.
124
+
125
+ ### Logging
126
+ Log level can be adjusted with `-l [debug,info,warn,error,fatal]` (defaults to **warn**).
127
+
128
+ ### Dry-run
129
+ Dry-run (read-only, don't tweet) can be enabled with `-d`. Note: you'll probably want to increase the log level with `-l` for this to be useful.
130
+
131
+ ### Exit codes
132
+ | Code | Meaning |
133
+ |:----:|---------|
134
+ | 0 | Success (wrote output, or nothing to do) |
135
+ | 1 | Failure. Something went wrong. Always accompanied by a FATAL log message. |
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+ task :default => :spec
data/bin/twitterer ADDED
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env ruby
2
+ require 'optparse'
3
+ require 'bundler/setup'
4
+ require 'tc/twitterer'
5
+
6
+ # twitterer -c <config> -l <log-level> -d <dry-run>
7
+
8
+ options = {}
9
+ OptionParser.new do |opts|
10
+ opts.banner = 'Usage: twitterer -c <config> [options]'
11
+
12
+ opts.on( '-c', '--config <config>', 'Path to config file' ) {
13
+ |c|
14
+ options[ :config ] = c
15
+ }
16
+
17
+ opts.on( '-l', '--log-level <level>', 'Specify log level (debug,info,warn,error,fatal)' ) {
18
+ |l|
19
+ options[ :log_level ] = l
20
+ }
21
+
22
+ opts.on( '-d', '--dry-run', 'Dry run - don\'t change anything' ) {
23
+ options[ :dry_run ] = true
24
+ }
25
+
26
+ opts.on_tail( '-h', '--help', 'Display help' ) {
27
+ puts opts
28
+ exit
29
+ }
30
+ end.parse!
31
+
32
+ twitterer = TC::Twitterer.new( options[ :config ], options[ :log_level ], options[ :dry_run ] )
33
+
34
+ twitterer.run
@@ -0,0 +1,30 @@
1
+ # log_level [OPT]
2
+ # The log level to operate at: debug/info/warn/error/fatal
3
+ # log_level = "warn"
4
+
5
+ # twitter_consumer_key [REQ]
6
+ # Twitter API key for your application (see README.md)
7
+ twitter_consumer_key = ""
8
+
9
+ # twitter_consumer_secret [REQ]
10
+ # Twitter API secret for your application (see README.md)
11
+ twitter_consumer_secret = ""
12
+
13
+ # twitter_access_token [REQ]
14
+ # Twitter API key for your user (see README.md)
15
+ twitter_access_token = ""
16
+
17
+ # twitter_access_token_secret [REQ]
18
+ # Twitter API secret for your user (see README.md)
19
+ twitter_access_token_secret = ""
20
+
21
+ # history_file [OPT]
22
+ # path to store previous tweets to avoid being repetitive
23
+ # history_file = "./var/twitterer.history"
24
+
25
+ # source [REQ]
26
+ # array of GitHub-hosted source files to generate tweets from
27
+ # must be on the default branch
28
+ # e.g
29
+ # source = [ "tomonocle/tc-twitterer/README.md", "tomonocle/trello-list2card/README.md" ]
30
+ source = []
@@ -0,0 +1,5 @@
1
+ module TC
2
+ class Twitterer
3
+ VERSION = "0.1.0"
4
+ end
5
+ end
@@ -0,0 +1,288 @@
1
+ module TC
2
+ class Twitterer
3
+ require 'toml'
4
+ require 'tc/twitterer/version'
5
+ require 'net/http'
6
+ require 'json'
7
+ require 'twitter'
8
+ require 'redcarpet'
9
+ require 'redcarpet/render_strip'
10
+ require 'logger'
11
+ require 'ostruct'
12
+ require 'csv'
13
+
14
+ MAX_TWEET_LENGTH = 140
15
+ MAX_URL_LENGTH = 23
16
+ MAX_STRING_LENGTH = MAX_TWEET_LENGTH - MAX_URL_LENGTH
17
+ MAX_HISTORY_LENGTH = MAX_STRING_LENGTH
18
+
19
+ LOG_LEVEL_MAP = {
20
+ 'debug' => Logger::DEBUG,
21
+ 'info' => Logger::INFO,
22
+ 'warn' => Logger::WARN,
23
+ 'error' => Logger::ERROR,
24
+ 'fatal' => Logger::FATAL,
25
+ }
26
+
27
+ DEFAULT_LOG_LEVEL = 'warn'
28
+
29
+ def initialize( config_path, log_level, dry_run )
30
+ @log = Logger.new( STDERR )
31
+ self.set_log_level( DEFAULT_LOG_LEVEL )
32
+
33
+ @log.info 'Starting up'
34
+
35
+ begin
36
+ fail 'config file not specified' unless config_path
37
+ fail 'config file not found' unless File.file?( config_path )
38
+
39
+ @log.info "Loading config from #{ config_path }"
40
+ @config = OpenStruct.new( TOML.load_file( config_path ) )
41
+ @log.info 'Loaded config'
42
+
43
+ rescue => e
44
+ @log.fatal "Failed to load config: #{e.message}"
45
+ exit 1
46
+ end
47
+
48
+ self.set_log_level( @config[ 'log_level' ] )
49
+ self.set_log_level( log_level )
50
+
51
+ # very basic sanity check of the config
52
+ [ 'twitter_consumer_key', 'twitter_consumer_secret', 'twitter_access_token', 'twitter_access_token_secret', 'source' ].each do |key|
53
+ unless @config[ key ]
54
+ @log.fatal "Required key '#{ key } not present in config"
55
+ exit 1
56
+ end
57
+ end
58
+
59
+ begin
60
+ @twitter = Twitter::REST::Client.new do |config|
61
+ config.consumer_key = @config.twitter_consumer_key
62
+ config.consumer_secret = @config.twitter_consumer_secret
63
+ config.access_token = @config.twitter_access_token
64
+ config.access_token_secret = @config.twitter_access_token_secret
65
+ end
66
+
67
+ @log.info "Connected to twitter as '#{@twitter.user.screen_name}'"
68
+
69
+ rescue => e
70
+ @log.fatal "Failed to connect to twitter: #{e.message}"
71
+ exit 1
72
+ end
73
+
74
+ # open and import the history if configured
75
+ if @config.history_file
76
+ @log.info( "Loading history from '#{ @config.history_file }'" )
77
+ @history = {}
78
+
79
+ begin
80
+ unless File.file?( @config.history_file )
81
+ @log.info 'History not present - creating'
82
+ File.write( @config.history_file, nil )
83
+ end
84
+
85
+ CSV.foreach( @config.history_file ) do |csv|
86
+ timestamp, source, line = csv
87
+
88
+ @log.debug( "Processing history: #{source}/#{line}/#{timestamp}")
89
+
90
+ # apparently ruby doesn't autovivify? Vive la perl!
91
+ @history[source] ||= {}
92
+ @history[source][line] = timestamp
93
+ end
94
+
95
+ rescue => e
96
+ @log.fatal( "Failed to import history from '#{ @config.history_file }': #{ e }" )
97
+ exit 1
98
+ end
99
+ end
100
+
101
+ if dry_run
102
+ @log.warn 'Dry run mode: ACTIVATED'
103
+ @dry_run = true
104
+ end
105
+ end
106
+
107
+ def set_log_level( level )
108
+ return unless level
109
+
110
+ level.downcase!
111
+
112
+ unless LOG_LEVEL_MAP[ level ]
113
+ @log.fatal "Unrecognised log_level '#{ level }'"
114
+ exit 1
115
+ end
116
+
117
+ @log.level = LOG_LEVEL_MAP[ level ]
118
+ end
119
+
120
+ def resolve_repo( repo )
121
+ @log.info "Resolving default->hash for '#{repo}'"
122
+
123
+ begin
124
+ response = Net::HTTP.get_response( URI( "https://api.github.com/repos/#{repo}" ) )
125
+
126
+ # fail if not 200 OK
127
+ response.value
128
+
129
+ json = JSON.parse( response.body )
130
+
131
+ default_branch = json[ 'default_branch' ]
132
+ @log.info "Found default branch '#{default_branch}'"
133
+
134
+ response = Net::HTTP.get_response( URI( "https://api.github.com/repos/#{repo}/git/refs/heads/#{ default_branch }" ) )
135
+
136
+ # this will fail unless we get a 200 OK
137
+ response.value
138
+
139
+ json = JSON.parse( response.body )
140
+
141
+ rescue => e
142
+ @log.error "Failed to resolve '#{repo}' default '#{default_branch}': #{e}"
143
+ raise e
144
+ end
145
+
146
+ hash = json['object']['sha']
147
+
148
+ @log.debug "Resolved #{default_branch}->#{hash} for '#{repo}'"
149
+
150
+ hash
151
+ end
152
+
153
+ def fetch_file( repo, hash, path )
154
+ @log.info "Fetching '#{repo}/#{path}' at '#{hash}'"
155
+
156
+ begin
157
+ response = Net::HTTP.get_response( URI( "https://raw.githubusercontent.com/#{repo}/#{hash}/#{path}" ) )
158
+
159
+ # this will fail unless we get a 200 OK
160
+ response.value
161
+
162
+ rescue => e
163
+ @log.error "Failed to fetch '#{repo}/#{path}' at '#{hash}': #{e}"
164
+ raise e
165
+ end
166
+
167
+ @log.debug "Fetched '#{repo}/#{path}' at '#{hash}'"
168
+
169
+ response.body
170
+ end
171
+
172
+ def pick_line( repo, path, contents )
173
+ n = 0
174
+ pick = ''
175
+ rows = contents.split( "\n" )
176
+
177
+ source = "#{repo}/#{path}"
178
+
179
+ @log.info "Picking suitable line from '#{source}'"
180
+
181
+ for i in 1..rows.count
182
+ line_number = rand( rows.count )
183
+ line = rows[ line_number ]
184
+
185
+ # must contain an alpha - don't bother logging as this is specified behavior
186
+ next unless line =~ /[a-zA-Z]/
187
+
188
+ # mustn't've been used before
189
+ if @config.history_file
190
+ key = line[ 0 .. MAX_HISTORY_LENGTH ]
191
+ if @history.key?( source ) and @history[ source ].key?( key )
192
+ @log.debug "Skipping '#{line}' because we've used it before (#{ @history[ source ][ key ] })"
193
+ next
194
+ end
195
+ end
196
+
197
+ # if we're here, we're good to go
198
+ pick = line
199
+ n = line_number + 1
200
+
201
+ break
202
+ end
203
+
204
+ if n == 0
205
+ fail "Failed to pick an entry from '#{source}' - exhausted content?"
206
+ end
207
+
208
+ @log.debug "Picked '#{pick}' [#{n}] from '#{source}'"
209
+
210
+ return pick, n
211
+ end
212
+
213
+ def sanitise( line )
214
+ rc = Redcarpet::Markdown.new( Redcarpet::Render::StripDown )
215
+
216
+ # remove any markdown
217
+ line = rc.render( line ).strip!
218
+
219
+ # compress any whitespace
220
+ line.gsub!( /\s+/, ' ' )
221
+
222
+ # truncate to a sane length, add an elipsis if necessary
223
+ line = ( line.length > MAX_STRING_LENGTH ? "#{ line[ 0 .. MAX_STRING_LENGTH ] }..." : line )
224
+
225
+ # just in case we truncated after a space
226
+ line.gsub!( /\s\.\.\./, '...' )
227
+
228
+ line
229
+ end
230
+
231
+ def tweet( repo, hash, path, line, line_number )
232
+ link = "https://github.com/#{repo}/blame/#{hash}/#{path}#L#{line_number}"
233
+
234
+ tweet = sprintf '%s %s', sanitise( line ), link
235
+
236
+ @log.warn sprintf "%sTweeting '%s' [%d] from %s/%s", ( @dry_run == true ? '[DRYRUN] ' : '' ), tweet, tweet.length, repo, path
237
+ @twitter.update( tweet ) unless @dry_run
238
+ end
239
+
240
+ def update_history( source, line )
241
+ # limit the length to avoid bloat in the history file
242
+ line = line[ 0 .. MAX_HISTORY_LENGTH ]
243
+
244
+ @log.info sprintf "%sAdding '%s' to history for '%s'", ( @dry_run == true ? '[DRYRUN] ' : '' ), line, source
245
+ return if @dry_run
246
+
247
+ CSV.open( @config.history_file, 'a' ) do |csv|
248
+ csv << [ Time.now.strftime( '%FT%T%z' ), source, line ]
249
+ end
250
+ end
251
+
252
+ def run
253
+ @config.source.each do |source|
254
+ @log.info "Processing '#{source}'"
255
+
256
+ begin
257
+ if m = source.match( %r{(.*?/.*?)/(.*)} )
258
+ repo, path = m.captures
259
+ else
260
+ fail "Couldn't extract repo and path from '#{source}' - skipping"
261
+ next
262
+ end
263
+
264
+ # convert default->hash
265
+ hash = resolve_repo( repo )
266
+
267
+ # fetch file
268
+ file_body = fetch_file( repo, hash, path )
269
+
270
+ # extract suitable line
271
+ line, line_number = pick_line( repo, path, file_body )
272
+
273
+ # generate the tweet and send it
274
+ tweet( repo, hash, path, line, line_number )
275
+
276
+ # store in history
277
+ if @config.history_file
278
+ update_history( source, line )
279
+ end
280
+
281
+ # all done!
282
+ rescue => e
283
+ @log.error "Failed '#{source}': #{e}"
284
+ end
285
+ end
286
+ end
287
+ end
288
+ end
@@ -0,0 +1,28 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'tc/twitterer/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'tc-twitterer'
8
+ spec.version = TC::Twitterer::VERSION
9
+ spec.authors = ['tomonocle']
10
+ spec.email = ['github@woot.co.uk']
11
+
12
+ spec.summary = 'Program for tweeting random lines from files hosted on a public GitHub repo'
13
+ spec.description = 'This program pulls a specified file from a GitHub repo, then tweets a random line with a link to the line on GitHub'
14
+ spec.homepage = 'https://github.com/tomonocle/tc-twitterer'
15
+ spec.license = 'MIT'
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
18
+ spec.bindir = 'exe'
19
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
+ spec.require_paths = ['lib']
21
+
22
+ spec.add_dependency 'twitter', '~> 6.2'
23
+ spec.add_dependency 'toml', '~> 0.2'
24
+ spec.add_dependency 'redcarpet', '~> 3.4'
25
+
26
+ spec.add_development_dependency 'bundler', '~> 1.11'
27
+ spec.add_development_dependency 'rake', '~> 10.0'
28
+ end
metadata ADDED
@@ -0,0 +1,125 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tc-twitterer
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - tomonocle
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2017-12-30 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: twitter
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '6.2'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '6.2'
27
+ - !ruby/object:Gem::Dependency
28
+ name: toml
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.2'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.2'
41
+ - !ruby/object:Gem::Dependency
42
+ name: redcarpet
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.4'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.4'
55
+ - !ruby/object:Gem::Dependency
56
+ name: bundler
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.11'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.11'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '10.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '10.0'
83
+ description: This program pulls a specified file from a GitHub repo, then tweets a
84
+ random line with a link to the line on GitHub
85
+ email:
86
+ - github@woot.co.uk
87
+ executables: []
88
+ extensions: []
89
+ extra_rdoc_files: []
90
+ files:
91
+ - ".gitignore"
92
+ - Gemfile
93
+ - LICENSE.txt
94
+ - README.md
95
+ - Rakefile
96
+ - bin/twitterer
97
+ - etc/config.toml.example
98
+ - lib/tc/twitterer.rb
99
+ - lib/tc/twitterer/version.rb
100
+ - tc-twitterer.gemspec
101
+ homepage: https://github.com/tomonocle/tc-twitterer
102
+ licenses:
103
+ - MIT
104
+ metadata: {}
105
+ post_install_message:
106
+ rdoc_options: []
107
+ require_paths:
108
+ - lib
109
+ required_ruby_version: !ruby/object:Gem::Requirement
110
+ requirements:
111
+ - - ">="
112
+ - !ruby/object:Gem::Version
113
+ version: '0'
114
+ required_rubygems_version: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - ">="
117
+ - !ruby/object:Gem::Version
118
+ version: '0'
119
+ requirements: []
120
+ rubyforge_project:
121
+ rubygems_version: 2.4.5
122
+ signing_key:
123
+ specification_version: 4
124
+ summary: Program for tweeting random lines from files hosted on a public GitHub repo
125
+ test_files: []