mechmarket 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: c5a5e075da555360a3d9c67ac5b0400ea5bf0ed2e7a0ec428af688b37933736d
4
+ data.tar.gz: b0b7ce277c9f2f8dd5ded5645795c3e8fd99788fec36db91aa50a15f8d7fbdbe
5
+ SHA512:
6
+ metadata.gz: c8c4fe6ec103170b0cb26167c102f88606e0283de9ac980f4c87f0001c1ed80f55c3733e3686cacbc5e749300e87c47575ee6ee126aa92c99a31124776b02981
7
+ data.tar.gz: ad9f9e1d9ccaef2431268c2dcd1b725af374cf70f8ecd5227cf0aef33456a48df644a7b1b856972ab7f6d169360ab2f7b4b2f7ad26b75eb120643fe87e6bed96
data/.gitignore ADDED
@@ -0,0 +1,3 @@
1
+ config.yml
2
+ db
3
+ spec/examples.txt
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,32 @@
1
+ AllCops:
2
+ TargetRubyVersion: 2.3
3
+
4
+ Metrics/AbcSize:
5
+ Enabled: false
6
+ Metrics/CyclomaticComplexity:
7
+ Enabled: false
8
+ Metrics/PerceivedComplexity:
9
+ Enabled: false
10
+
11
+ Metrics/LineLength:
12
+ Enabled: false
13
+ Metrics/ClassLength:
14
+ Enabled: false
15
+ Metrics/MethodLength:
16
+ Enabled: false
17
+ Metrics/BlockLength:
18
+ Enabled: false
19
+ Metrics/ParameterLists:
20
+ Enabled: false
21
+
22
+ Layout/EndAlignment:
23
+ Enabled: false
24
+ Layout/IndentationWidth:
25
+ Enabled: false
26
+ Layout/ElseAlignment:
27
+ Enabled: false
28
+
29
+ Style/Documentation:
30
+ Enabled: false
31
+ Style/GuardClause:
32
+ Enabled: false
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,76 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ mechmarket (0.0.1)
5
+ httparty (~> 0.17.0)
6
+ mail (~> 2.7.1)
7
+ sqlite3 (~> 1.4.1)
8
+
9
+ GEM
10
+ remote: https://rubygems.org/
11
+ specs:
12
+ addressable (2.6.0)
13
+ public_suffix (>= 2.0.2, < 4.0)
14
+ ast (2.4.0)
15
+ awesome_print (1.8.0)
16
+ crack (0.4.3)
17
+ safe_yaml (~> 1.0.0)
18
+ diff-lcs (1.3)
19
+ hashdiff (1.0.0)
20
+ httparty (0.17.0)
21
+ mime-types (~> 3.0)
22
+ multi_xml (>= 0.5.2)
23
+ jaro_winkler (1.5.3)
24
+ mail (2.7.1)
25
+ mini_mime (>= 0.1.1)
26
+ mime-types (3.2.2)
27
+ mime-types-data (~> 3.2015)
28
+ mime-types-data (3.2019.0331)
29
+ mini_mime (1.0.2)
30
+ multi_xml (0.6.0)
31
+ parallel (1.17.0)
32
+ parser (2.6.3.0)
33
+ ast (~> 2.4.0)
34
+ public_suffix (3.1.1)
35
+ rainbow (3.0.0)
36
+ rspec (3.8.0)
37
+ rspec-core (~> 3.8.0)
38
+ rspec-expectations (~> 3.8.0)
39
+ rspec-mocks (~> 3.8.0)
40
+ rspec-core (3.8.2)
41
+ rspec-support (~> 3.8.0)
42
+ rspec-expectations (3.8.4)
43
+ diff-lcs (>= 1.2.0, < 2.0)
44
+ rspec-support (~> 3.8.0)
45
+ rspec-mocks (3.8.1)
46
+ diff-lcs (>= 1.2.0, < 2.0)
47
+ rspec-support (~> 3.8.0)
48
+ rspec-support (3.8.2)
49
+ rubocop (0.74.0)
50
+ jaro_winkler (~> 1.5.1)
51
+ parallel (~> 1.10)
52
+ parser (>= 2.6)
53
+ rainbow (>= 2.2.2, < 4.0)
54
+ ruby-progressbar (~> 1.7)
55
+ unicode-display_width (>= 1.4.0, < 1.7)
56
+ ruby-progressbar (1.10.1)
57
+ safe_yaml (1.0.5)
58
+ sqlite3 (1.4.1)
59
+ unicode-display_width (1.6.0)
60
+ webmock (3.7.0)
61
+ addressable (>= 2.3.6)
62
+ crack (>= 0.3.2)
63
+ hashdiff (>= 0.4.0, < 2.0.0)
64
+
65
+ PLATFORMS
66
+ ruby
67
+
68
+ DEPENDENCIES
69
+ awesome_print
70
+ mechmarket!
71
+ rspec
72
+ rubocop
73
+ webmock
74
+
75
+ BUNDLED WITH
76
+ 1.16.6
data/LICENSE.md ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2019 Chris Estreich.
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 all
13
+ 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 THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,45 @@
1
+ ### Setup
2
+
3
+ #### Config:
4
+
5
+ Create an "app password" for your Google account (https://support.google.com/accounts/answer/185833?hl=en) and add your Google username and app-specific password
6
+ in a `config.yml` file. This is used to send yourself an email whenever
7
+ a match is found on a r/mechmarket post. See `alert.rb` for details on how
8
+ this works.
9
+
10
+ Credentials in `config.yml`:
11
+ ```
12
+ username: ****
13
+ password: ****
14
+ ```
15
+
16
+ #### Queries:
17
+
18
+ Add a list of keyword searches to perform on the titles of r/mechmarket posts to `config.yml`:
19
+ ```
20
+ queries:
21
+ - jelly key
22
+ - mito laser
23
+ ```
24
+
25
+ #### Database:
26
+
27
+ Create the SQLite database which will store the history of r/mechmarket posts
28
+ and will be used to perform intelligent full text searches:
29
+ ```
30
+ mechmarket-migrate
31
+ ```
32
+
33
+ ### Run
34
+
35
+ Fetch the latest posts from r/mechmarket and alert yourself via email if any
36
+ of the posts match one of your queries:
37
+ ```
38
+ mechmarket-run
39
+ ```
40
+
41
+ Run this on a cron to alert yourself very quickly if something you're looking
42
+ for is a available:
43
+ ```
44
+ */3 * * * * mechmarket-run >> /tmp/mechmarket.log 2>&1
45
+ ```
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rspec/core/rake_task'
4
+ require 'rubocop/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ RuboCop::RakeTask.new(:rubocop) do |t|
9
+ t.options = ['--display-cop-names']
10
+ end
11
+
12
+ task default: %i[spec rubocop]
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
5
+ require 'mechmarket'
6
+
7
+ Mechmarket::Database.migrate
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
5
+ require 'mechmarket'
6
+
7
+ config = Mechmarket::Config.new
8
+ mechmarket = Mechmarket::Client.new
9
+ posts = mechmarket.posts
10
+ max_id = Mechmarket::Post.max_id
11
+
12
+ posts.each do |post|
13
+ post.save if post.new_record?
14
+ end
15
+
16
+ config.queries.each do |query|
17
+ hits = Mechmarket::Post.search(query: query, type: 'Selling', min_id: max_id)
18
+ next if hits.empty?
19
+
20
+ subject = "Potential r/mechmarket match for \"#{query}\""
21
+ body = hits.map { |post| "#{post.age}m ago\n#{post.title}\n#{post.url}" }.join("\n\n")
22
+ Mechmarket::Alert.deliver(config: config, subject: subject, body: body)
23
+ end
24
+
25
+ if posts.empty?
26
+ puts 'API call failed. Check error logs.'
27
+ exit(1)
28
+ else
29
+ puts "[#{Time.now}] Scanned #{posts.size} post(s) for #{config.queries.size} phrases(s)."
30
+ exit(0)
31
+ end
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
5
+ require 'mechmarket'
6
+
7
+ begin
8
+ puts "max_id: #{Mechmarket::Post.max_id}"
9
+ puts "m65-a search: #{Mechmarket::Post.search(query: 'm65-a').map(&:title).inspect}"
10
+ rescue SQLite3::CantOpenException
11
+ puts 'You must run mechmarket-migrate first.'
12
+ end
@@ -0,0 +1,5 @@
1
+ username: my_username
2
+ password: my_app_specific_password
3
+ queries:
4
+ - jelly key
5
+ - mito laser
data/lib/mechmarket.rb ADDED
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'httparty'
4
+ require 'mail'
5
+ require 'sqlite3'
6
+
7
+ Dir[File.expand_path('mechmarket/*.rb', __dir__)].each { |f| require f }
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mechmarket
4
+ class Alert
5
+ def self.deliver(config:, subject:, body:)
6
+ Mail.defaults do
7
+ delivery_method(
8
+ :smtp,
9
+ address: 'smtp.gmail.com',
10
+ port: 587,
11
+ user_name: config.username,
12
+ password: config.password,
13
+ authentication: 'plain',
14
+ enable_starttls_auto: true
15
+ )
16
+ end
17
+
18
+ from = "#{config.username}@gmail.com"
19
+ to = "Me <#{from}>"
20
+
21
+ # @TODO: Check for errors.
22
+ Mail.deliver do
23
+ from from
24
+ to to
25
+ subject subject
26
+ body body
27
+ end
28
+
29
+ true
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mechmarket
4
+ class Client
5
+ USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.100 Safari/537.36'
6
+
7
+ include HTTParty
8
+ base_uri 'https://www.reddit.com'
9
+
10
+ def posts
11
+ response = get('/r/mechmarket/new.json')
12
+
13
+ if response.code == 200
14
+ response.parsed_response['data']['children'].map { |child| child['data'] }.map do |raw|
15
+ Post.new(
16
+ 'reddit_id' => raw['id'],
17
+ 'created_utc' => raw['created_utc'],
18
+ 'url' => raw['url'],
19
+ 'author' => raw['author'],
20
+ 'title' => raw['title'],
21
+ 'body' => raw['selftext']
22
+ )
23
+ end
24
+ else
25
+ # Log error.
26
+ puts response.parsed_response.inspect
27
+ []
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ def get(path)
34
+ self.class.get(
35
+ path,
36
+ headers: { 'User-Agent' => USER_AGENT }
37
+ # debug_output: STDOUT
38
+ )
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mechmarket
4
+ class Config
5
+ attr_reader :username, :password, :queries
6
+
7
+ def initialize
8
+ config = YAML.load_file(File.expand_path('../../config.yml', __dir__))
9
+ @username = config['username']
10
+ @password = config['password']
11
+ @queries = config['queries']
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mechmarket
4
+ class Database
5
+ SCHEMA = {
6
+ id: 'INTEGER PRIMARY KEY AUTOINCREMENT',
7
+ reddit_id: 'TEXT NOT NULL',
8
+ created_utc: 'INTEGER NOT NULL',
9
+ url: 'TEXT NOT NULL',
10
+ author: 'TEXT NOT NULL',
11
+ title: 'TEXT NOT NULL',
12
+ body: 'TEXT NOT NULL',
13
+ type: 'TEXT NOT NULL'
14
+ }.freeze
15
+
16
+ class << self
17
+ def database
18
+ @database ||= SQLite3::Database.new(File.expand_path('../../db/mechmarket.db', __dir__))
19
+ end
20
+
21
+ def migrate(debug: false)
22
+ statements = []
23
+
24
+ statements << <<~SQL
25
+ CREATE TABLE IF NOT EXISTS posts(#{SCHEMA.map { |key, value| "#{key} #{value}" }.join(', ')});
26
+ SQL
27
+
28
+ statements << <<~SQL
29
+ CREATE UNIQUE INDEX IF NOT EXISTS index_posts_on_reddit_id ON posts(reddit_id);
30
+ SQL
31
+
32
+ statements << <<~SQL
33
+ CREATE VIRTUAL TABLE IF NOT EXISTS posts_fts5 USING fts5(title, tokenize="porter ascii tokenchars '-_'", content="posts", content_rowid="id");
34
+ SQL
35
+
36
+ statements << <<~SQL
37
+ CREATE TRIGGER IF NOT EXISTS posts_ai AFTER INSERT ON posts BEGIN
38
+ INSERT INTO posts_fts5(rowid, title) VALUES(new.id, new.title);
39
+ END;
40
+ SQL
41
+
42
+ statements << <<~SQL
43
+ CREATE TRIGGER IF NOT EXISTS posts_ad AFTER DELETE ON posts BEGIN
44
+ INSERT INTO posts_fts5(posts_fts5, rowid, title) VALUES('delete', old.id, old.title);
45
+ END;
46
+ SQL
47
+
48
+ statements << <<~SQL
49
+ CREATE TRIGGER IF NOT EXISTS posts_au AFTER UPDATE ON posts BEGIN
50
+ INSERT INTO posts_fts5(posts_fts5, rowid, title) VALUES('delete', old.id, old.title);
51
+ INSERT INTO posts_fts5(rowid, title) VALUES(new.id, new.title);
52
+ END;
53
+ SQL
54
+
55
+ statements.each do |statement|
56
+ database.execute(statement)
57
+ end
58
+
59
+ puts database.execute('SELECT * FROM sqlite_master') if debug
60
+ rescue SQLite3::Exception => e
61
+ puts e.inspect
62
+ ensure
63
+ database&.close
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,120 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mechmarket
4
+ class Post
5
+ attr_reader :id, :reddit_id, :created_utc, :url, :author, :title, :body, :type
6
+
7
+ def initialize(raw)
8
+ @id = raw['id']
9
+ @reddit_id = raw['reddit_id']
10
+ @created_utc = raw['created_utc']
11
+ @url = raw['url']
12
+ @author = raw['author']
13
+ @title = raw['title']
14
+ @body = raw['body']
15
+ @type = raw['type']
16
+
17
+ infer_type if @type.nil?
18
+ end
19
+
20
+ def new_record?
21
+ !self.class.exists_by?(reddit_id: reddit_id)
22
+ end
23
+
24
+ def age
25
+ ((Time.now.to_i - @created_utc) / 60.0).round(1)
26
+ end
27
+
28
+ def save
29
+ if new_record?
30
+ self.class.create(self)
31
+ else
32
+ self.class.update(self)
33
+ end
34
+ end
35
+
36
+ class << self
37
+ def database
38
+ @database ||= SQLite3::Database.new(File.expand_path('../data/mechmarket.db', File.dirname(__FILE__)))
39
+ end
40
+
41
+ def exists?(id)
42
+ !find(id).nil?
43
+ end
44
+
45
+ def exists_by?(conditions)
46
+ !find_by(conditions).nil?
47
+ end
48
+
49
+ def find(id)
50
+ if (row = execute('SELECT * FROM posts WHERE id = ?', id).first)
51
+ new(build(row))
52
+ end
53
+ end
54
+
55
+ def find_by(conditions)
56
+ if (row = execute("SELECT * FROM posts WHERE #{conditions.keys.map { |key| "#{key} = ?" }.join(' AND ')}", conditions.values).first)
57
+ new(build(row))
58
+ end
59
+ end
60
+
61
+ def create(post)
62
+ # @TODO: Use SCHEMA instead of hard-coding columns.
63
+ result = execute(
64
+ 'INSERT INTO posts(reddit_id, created_utc, url, author, title, body, type) VALUES(?, ?, ?, ?, ?, ?, ?)',
65
+ [post.reddit_id, post.created_utc, post.url, post.author, post.title, post.body, post.type]
66
+ )
67
+ puts "+ #{post.title}"
68
+ result
69
+ end
70
+
71
+ def max_id
72
+ execute('SELECT MAX(id) FROM posts').flatten.first || 0
73
+ end
74
+
75
+ def search(query:, type: 'Selling', min_id: 0)
76
+ ids = execute("SELECT rowid FROM posts_fts5 WHERE title MATCH '\"#{query}\"' AND rowid > #{min_id}")
77
+ return [] if ids.empty?
78
+
79
+ rows = execute("SELECT * FROM posts WHERE id IN(#{ids.flatten.join(', ')}) AND type = ?", type)
80
+ rows.map { |row| new(build(row)) }
81
+ end
82
+
83
+ private
84
+
85
+ def build(raw)
86
+ Database::SCHEMA.keys.map(&:to_s).zip(raw).to_h
87
+ end
88
+
89
+ def execute(sql, *args)
90
+ Database.database.execute(sql, args)
91
+ end
92
+ end
93
+
94
+ private
95
+
96
+ def infer_type
97
+ signature = title.upcase.delete(' ')
98
+
99
+ @type = if signature.include?('[H]PAYPAL')
100
+ 'Buying'
101
+ elsif signature.include?('[H]')
102
+ 'Selling'
103
+ elsif signature.include?('[GB]')
104
+ 'Group Buy'
105
+ elsif signature.include?('[IC]')
106
+ 'Interest Check'
107
+ elsif signature.include?('[ARTISAN]')
108
+ 'Artisan'
109
+ elsif signature.include?('[VENDOR]')
110
+ 'Vendor'
111
+ elsif signature.include?('[SERVICE]')
112
+ 'Service'
113
+ elsif signature.include?('[GIVEAWAY]')
114
+ 'Giveaway'
115
+ else
116
+ 'Unknown'
117
+ end
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ # To publish the next version:
4
+ # gem build mechmarket.gemspec
5
+ # gem push mechmarket-0.0.1.gem
6
+ Gem::Specification.new do |s|
7
+ s.name = 'mechmarket'
8
+ s.version = '0.0.1'
9
+ s.platform = Gem::Platform::RUBY
10
+ s.licenses = ['MIT']
11
+ s.authors = ['Chris Estreich']
12
+ s.email = 'cestreich@gmail.com'
13
+ s.homepage = 'https://github.com/cte/mechmarket'
14
+ s.summary = 'Search r/mechmarket for new posts with desired products and trigger an alert.'
15
+
16
+ s.extra_rdoc_files = ['README.md']
17
+
18
+ s.required_ruby_version = '~> 2.3'
19
+
20
+ s.add_dependency 'httparty', '~> 0.17'
21
+ s.add_dependency 'mail', '~> 2.7'
22
+ s.add_dependency 'sqlite3', '~> 1.4'
23
+
24
+ s.add_development_dependency 'awesome_print', '~> 1.8'
25
+ s.add_development_dependency 'rspec', '~> 3.8'
26
+ s.add_development_dependency 'rubocop', '~> 0.74'
27
+ s.add_development_dependency 'webmock', '~> 3.7'
28
+
29
+ s.files = `git ls-files`.split("\n")
30
+ s.test_files = `git ls-files -- {spec}/*`.split("\n")
31
+ s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
32
+ s.require_paths = ['lib']
33
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Mechmarket::Alert do
6
+ describe '#deliver' do
7
+ let(:subject) { 'Foo' }
8
+ let(:body) { 'Bar' }
9
+
10
+ it 'sends an email to the configured address', stubbed_config: true do
11
+ expect(Mail).to receive(:deliver)
12
+ Mechmarket::Alert.deliver(config: config, subject: subject, body: body)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Mechmarket::Client do
6
+ describe '' do
7
+ it '', stubbed_config: true do
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Mechmarket::Config do
6
+ describe '' do
7
+ it '', stubbed_config: true do
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Mechmarket::Database do
6
+ describe '' do
7
+ it '', stubbed_config: true do
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Mechmarket::Post do
6
+ describe '' do
7
+ it '', stubbed_config: true do
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'mechmarket'
5
+
6
+ require 'webmock/rspec'
7
+
8
+ Dir[File.expand_path(File.join(File.dirname(__FILE__), 'support', '**', '*.rb'))].each { |f| require f }
9
+
10
+ # This file was generated by the `rspec --init` command. Conventionally, all
11
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
12
+ # The generated `.rspec` file contains `--require spec_helper` which will cause
13
+ # this file to always be loaded, without a need to explicitly require it in any
14
+ # files.
15
+ #
16
+ # Given that it is always loaded, you are encouraged to keep this file as
17
+ # light-weight as possible. Requiring heavyweight dependencies from this file
18
+ # will add to the boot time of your test suite on EVERY test run, even for an
19
+ # individual file that may not need all of that loaded. Instead, consider making
20
+ # a separate helper file that requires the additional dependencies and performs
21
+ # the additional setup, and require it from the spec files that actually need
22
+ # it.
23
+ #
24
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
25
+ RSpec.configure do |config|
26
+ # rspec-expectations config goes here. You can use an alternate
27
+ # assertion/expectation library such as wrong or the stdlib/minitest
28
+ # assertions if you prefer.
29
+ config.expect_with :rspec do |expectations|
30
+ # This option will default to `true` in RSpec 4. It makes the `description`
31
+ # and `failure_message` of custom matchers include text for helper methods
32
+ # defined using `chain`, e.g.:
33
+ # be_bigger_than(2).and_smaller_than(4).description
34
+ # # => "be bigger than 2 and smaller than 4"
35
+ # ...rather than:
36
+ # # => "be bigger than 2"
37
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
38
+ end
39
+
40
+ # rspec-mocks config goes here. You can use an alternate test double
41
+ # library (such as bogus or mocha) by changing the `mock_with` option here.
42
+ config.mock_with :rspec do |mocks|
43
+ # Prevents you from mocking or stubbing a method that does not exist on
44
+ # a real object. This is generally recommended, and will default to
45
+ # `true` in RSpec 4.
46
+ mocks.verify_partial_doubles = true
47
+ end
48
+
49
+ # This option will default to `:apply_to_host_groups` in RSpec 4 (and will
50
+ # have no way to turn it off -- the option exists only for backwards
51
+ # compatibility in RSpec 3). It causes shared context metadata to be
52
+ # inherited by the metadata hash of host groups and examples, rather than
53
+ # triggering implicit auto-inclusion in groups with matching metadata.
54
+ config.shared_context_metadata_behavior = :apply_to_host_groups
55
+
56
+ # The settings below are suggested to provide a good initial experience
57
+ # with RSpec, but feel free to customize to your heart's content.
58
+
59
+ # This allows you to limit a spec run to individual examples or groups
60
+ # you care about by tagging them with `:focus` metadata. When nothing
61
+ # is tagged with `:focus`, all examples get run. RSpec also provides
62
+ # aliases for `it`, `describe`, and `context` that include `:focus`
63
+ # metadata: `fit`, `fdescribe` and `fcontext`, respectively.
64
+ config.filter_run_when_matching :focus
65
+
66
+ # Allows RSpec to persist some state between runs in order to support
67
+ # the `--only-failures` and `--next-failure` CLI options. We recommend
68
+ # you configure your source control system to ignore this file.
69
+ config.example_status_persistence_file_path = 'spec/examples.txt'
70
+
71
+ # Limits the available syntax to the non-monkey patched syntax that is
72
+ # recommended. For more details, see:
73
+ # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/
74
+ # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
75
+ # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode
76
+ # config.disable_monkey_patching!
77
+
78
+ # This setting enables warnings. It's recommended, but in some cases may
79
+ # be too noisy due to issues in dependencies.
80
+ config.warnings = true
81
+
82
+ # Many RSpec users commonly either run the entire suite or an individual
83
+ # file, and it's useful to allow more verbose output when running an
84
+ # individual spec file.
85
+ if config.files_to_run.one?
86
+ # Use the documentation formatter for detailed output,
87
+ # unless a formatter has already been configured
88
+ # (e.g. via a command-line flag).
89
+ config.default_formatter = 'doc'
90
+ end
91
+
92
+ # Print the 10 slowest examples and example groups at the
93
+ # end of the spec run, to help surface which specs are running
94
+ # particularly slow.
95
+ config.profile_examples = 10
96
+
97
+ # Run specs in random order to surface order dependencies. If you find an
98
+ # order dependency and want to debug it, you can fix the order by providing
99
+ # the seed, which is printed after each run.
100
+ # --seed 1234
101
+ config.order = :random
102
+
103
+ # Seed global randomization in this process using the `--seed` CLI option.
104
+ # Setting this allows you to use `--seed` to deterministically reproduce
105
+ # test failures related to randomization by passing the same `--seed` value
106
+ # as the one that triggered the failure.
107
+ Kernel.srand config.seed
108
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.shared_context 'stubbed_config' do
4
+ let(:config) { double(Mechmarket::Config) }
5
+
6
+ before do
7
+ allow(config).to receive(:username).and_return('fake-username')
8
+ allow(config).to receive(:password).and_return('fake-password')
9
+ end
10
+ end
11
+
12
+ RSpec.configure do |rspec|
13
+ rspec.include_context 'stubbed_config', stubbed_config: true
14
+ end
metadata ADDED
@@ -0,0 +1,171 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mechmarket
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Chris Estreich
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-08-30 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: httparty
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.17'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0.17'
27
+ - !ruby/object:Gem::Dependency
28
+ name: mail
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2.7'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '2.7'
41
+ - !ruby/object:Gem::Dependency
42
+ name: sqlite3
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.4'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.4'
55
+ - !ruby/object:Gem::Dependency
56
+ name: awesome_print
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.8'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.8'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '3.8'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3.8'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rubocop
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '0.74'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '0.74'
97
+ - !ruby/object:Gem::Dependency
98
+ name: webmock
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '3.7'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '3.7'
111
+ description:
112
+ email: cestreich@gmail.com
113
+ executables:
114
+ - mechmarket-migrate
115
+ - mechmarket-run
116
+ - mechmarket-test
117
+ extensions: []
118
+ extra_rdoc_files:
119
+ - README.md
120
+ files:
121
+ - ".gitignore"
122
+ - ".rspec"
123
+ - ".rubocop.yml"
124
+ - Gemfile
125
+ - Gemfile.lock
126
+ - LICENSE.md
127
+ - README.md
128
+ - Rakefile
129
+ - bin/mechmarket-migrate
130
+ - bin/mechmarket-run
131
+ - bin/mechmarket-test
132
+ - config-example.yml
133
+ - lib/mechmarket.rb
134
+ - lib/mechmarket/alert.rb
135
+ - lib/mechmarket/client.rb
136
+ - lib/mechmarket/config.rb
137
+ - lib/mechmarket/database.rb
138
+ - lib/mechmarket/post.rb
139
+ - mechmarket.gemspec
140
+ - spec/mechmarket/alert_spec.rb
141
+ - spec/mechmarket/client_spec.rb
142
+ - spec/mechmarket/config_spec.rb
143
+ - spec/mechmarket/database_spec.rb
144
+ - spec/mechmarket/post_spec.rb
145
+ - spec/spec_helper.rb
146
+ - spec/support/stubbed_config.rb
147
+ homepage: https://github.com/cte/mechmarket
148
+ licenses:
149
+ - MIT
150
+ metadata: {}
151
+ post_install_message:
152
+ rdoc_options: []
153
+ require_paths:
154
+ - lib
155
+ required_ruby_version: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - "~>"
158
+ - !ruby/object:Gem::Version
159
+ version: '2.3'
160
+ required_rubygems_version: !ruby/object:Gem::Requirement
161
+ requirements:
162
+ - - ">="
163
+ - !ruby/object:Gem::Version
164
+ version: '0'
165
+ requirements: []
166
+ rubyforge_project:
167
+ rubygems_version: 2.7.8
168
+ signing_key:
169
+ specification_version: 4
170
+ summary: Search r/mechmarket for new posts with desired products and trigger an alert.
171
+ test_files: []