mako_rss 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.
Files changed (50) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +33 -0
  3. data/.rspec +2 -0
  4. data/.rubocop.yml +7 -0
  5. data/.rubocop_todo.yml +38 -0
  6. data/.travis.yml +7 -0
  7. data/Gemfile +6 -0
  8. data/LICENSE.txt +21 -0
  9. data/README.md +124 -0
  10. data/Rakefile +13 -0
  11. data/bin/mako +6 -0
  12. data/lib/mako/article.rb +36 -0
  13. data/lib/mako/cli.rb +49 -0
  14. data/lib/mako/commands/build.rb +33 -0
  15. data/lib/mako/commands/new.rb +28 -0
  16. data/lib/mako/commands/schedule.rb +12 -0
  17. data/lib/mako/commands/version.rb +11 -0
  18. data/lib/mako/configuration.rb +44 -0
  19. data/lib/mako/core.rb +81 -0
  20. data/lib/mako/core_ext/numeric.rb +10 -0
  21. data/lib/mako/core_ext/time.rb +10 -0
  22. data/lib/mako/core_ext.rb +4 -0
  23. data/lib/mako/errors.rb +25 -0
  24. data/lib/mako/feed.rb +29 -0
  25. data/lib/mako/feed_constructor.rb +71 -0
  26. data/lib/mako/feed_requester.rb +43 -0
  27. data/lib/mako/file_open_util.rb +19 -0
  28. data/lib/mako/html_renderer.rb +30 -0
  29. data/lib/mako/layouts/_feed_container.html.erb +19 -0
  30. data/lib/mako/mako_logger.rb +12 -0
  31. data/lib/mako/sass_renderer.rb +30 -0
  32. data/lib/mako/subscription_list_parser.rb +28 -0
  33. data/lib/mako/version.rb +5 -0
  34. data/lib/mako/view_helpers.rb +28 -0
  35. data/lib/mako/writer.rb +18 -0
  36. data/lib/mako.rb +41 -0
  37. data/lib/templates/Gemfile +5 -0
  38. data/lib/templates/config.yaml +14 -0
  39. data/lib/templates/sample_subscriptions/subscriptions.json +1 -0
  40. data/lib/templates/sample_subscriptions/subscriptions.txt +5 -0
  41. data/lib/templates/sample_subscriptions/subscriptions.xml +12 -0
  42. data/lib/templates/themes/sass/_fonts.scss +38 -0
  43. data/lib/templates/themes/sass/_layout.scss +97 -0
  44. data/lib/templates/themes/sass/_reboot.scss +473 -0
  45. data/lib/templates/themes/sass/_utilities.scss +13 -0
  46. data/lib/templates/themes/sass/_variables.scss +57 -0
  47. data/lib/templates/themes/simple.html.erb +46 -0
  48. data/lib/templates/themes/simple.scss +6 -0
  49. data/mako_rss.gemspec +36 -0
  50. metadata +233 -0
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mako
4
+ class FeedConstructor
5
+ attr_reader :feed_url, :feed_data
6
+
7
+ def initialize(args)
8
+ @feed_url = args.fetch(:feed_url)
9
+ @feed_data = args.fetch(:feed_data)
10
+ end
11
+
12
+ # Parses raw XML feed and creates Feed and Article objects to
13
+ # be rendered. Returns false if feed cannot be parsed.
14
+ #
15
+ # @return [Feed]
16
+ def parse_and_create
17
+ parsed_feed = parse_feed
18
+ return false unless parsed_feed
19
+ feed = create_feed(parsed_feed)
20
+ create_articles(feed, parsed_feed)
21
+ feed
22
+ end
23
+
24
+ private
25
+
26
+ # @private
27
+ # Takes raw XML and parses it into a Feedjira::Feed object
28
+ #
29
+ # @return [Feedjira::Feed]
30
+ def parse_feed
31
+ Feedjira::Feed.parse(feed_data)
32
+ rescue Feedjira::NoParserAvailable
33
+ Mako.errors.add_error "Unable to parse #{feed_url}."
34
+ return false
35
+ end
36
+
37
+ # Creates new Mako::Feed object from the parsed Feedjira::Feed object
38
+ #
39
+ # @param [Feedjira::Feed]
40
+ # @return [Mako::Feed]
41
+ def create_feed(parsed_feed)
42
+ Feed.new(url: parsed_feed.url, title: parsed_feed.title)
43
+ end
44
+
45
+ # Creates new Mako::Article objects from the parsed Feedjira::Feed object
46
+ # if the source article was published within the last day. Adds the
47
+ # Mako::Article objects to the Mako::Feed object's articles attribute.
48
+ #
49
+ # @param [Mako::Feed] feed
50
+ # @param [Feedjira::Feed] parsed_feed
51
+ def create_articles(feed, parsed_feed)
52
+ parsed_feed.entries.each do |entry|
53
+ next unless entry.published >= (Time.now - 1.day).beginning_of_day
54
+ feed.articles << Article.new(title: entry.title,
55
+ published: entry.published,
56
+ summary: entry_summary(entry),
57
+ url: entry.url)
58
+ end
59
+ end
60
+
61
+ # Atom and RSS Feedjira::Feed objects have different names for the
62
+ # article body. Returns entry.content if Atom and entry.summary if
63
+ # RSS.
64
+ #
65
+ # @param [Feedjira::Feed]
66
+ # @return [String] an HTML string of the source article body
67
+ def entry_summary(entry)
68
+ entry.class.to_s.include?('Atom') ? entry.content : entry.summary
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mako
4
+ class FeedRequester
5
+ attr_reader :feed_url
6
+ attr_accessor :ok, :body
7
+
8
+ def initialize(args)
9
+ @ok = true
10
+ @body = ''
11
+ @feed_url = args.fetch(:feed_url)
12
+ end
13
+
14
+ # Performs HTTP request on the given feed_url. Sets the Mako::FeedRequester
15
+ # body attribute equal to the request body if successful and returns self.
16
+ # If the request fails, @ok is set to false.
17
+ #
18
+ # @return [Mako::FeedRequester]
19
+ def fetch
20
+ begin
21
+ request = Faraday.get(feed_url)
22
+ rescue Faraday::Error
23
+ Mako.errors.add_error "Could not complete request to #{feed_url}."
24
+ self.ok = false
25
+ return self
26
+ end
27
+ unless request.status == 200
28
+ Mako.errors.add_error "Request to #{feed_url} returned #{request.status}."
29
+ self.ok = false
30
+ return self
31
+ end
32
+ self.body = request.body
33
+ self
34
+ end
35
+
36
+ # Predicate method returning the value of @ok
37
+ #
38
+ # @return [Boolean]
39
+ def ok?
40
+ ok
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FileOpenUtil
4
+ # Opens and reads the passed in file. Thanks to the following article
5
+ # for explaining how to define a method on the included singleton class:
6
+ # https://6ftdan.com/allyourdev/2015/02/24/writing-methods-for-both-class-and-instance-levels/
7
+ #
8
+ # @param [String] resource the path to the resource
9
+ # @return [String] the opened resource
10
+ def self.included(base)
11
+ def base.load_resource(resource)
12
+ File.open(resource, 'rb', encoding: 'utf-8', &:read)
13
+ end
14
+ end
15
+
16
+ def load_resource(resource)
17
+ self.class.load_resource(resource)
18
+ end
19
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mako
4
+ class HTMLRenderer
5
+ include FileOpenUtil
6
+
7
+ attr_reader :template, :bound, :feed_template
8
+
9
+ def initialize(args)
10
+ @template = args.fetch(:template, File.expand_path(File.join('themes', "#{Mako.config.theme}.html.erb"), Dir.pwd))
11
+ @bound = args.fetch(:bound)
12
+ @feed_template = File.expand_path('../layouts/_feed_container.html.erb', __FILE__)
13
+ end
14
+
15
+ # Wrapper for ERB renderer. Creates new ERB instance with view template
16
+ # and renders it with binding from core.
17
+ #
18
+ # @return [String]
19
+ def render
20
+ ERB.new(load_resource(template)).result(bound.get_binding)
21
+ end
22
+
23
+ # Default file name for rendererd file.
24
+ #
25
+ # @return [String]
26
+ def file_path
27
+ 'index.html'
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,19 @@
1
+ <section id="feed-container">
2
+ <% @feeds.each do |feed| %>
3
+ <% if feed.articles.size > 0 %>
4
+ <section>
5
+ <header class="article-header">
6
+ <h1><a href="<%= feed.url %>"><%= h(feed.title) %></a></h1>
7
+ </header>
8
+ <% feed.articles.each do |article| %>
9
+ <article>
10
+ <h3><a href="<%= article.url %>"><%= h(article.title) %></a></h3>
11
+ <p class="meta"><%= article.formatted_published %></p>
12
+ <%= article.summary %>
13
+ </article>
14
+ <hr />
15
+ <% end %>
16
+ </section>
17
+ <% end %>
18
+ <% end %>
19
+ </section>
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mako
4
+ class MakoLogger < Logger
5
+ def initialize(logdev, shift_age = 0, shift_size = 1_048_576)
6
+ super(logdev, shift_age, shift_size)
7
+ self.formatter = proc do |_severity, _datetime, _progname, msg|
8
+ "#{msg}\n"
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mako
4
+ class SassRenderer
5
+ include FileOpenUtil
6
+
7
+ attr_reader :template
8
+
9
+ def initialize(args)
10
+ @template = args.fetch(:template, File.expand_path(File.join('themes', "#{Mako.config.theme}.scss"), Dir.pwd))
11
+ end
12
+
13
+ # Wrapper for Sass::Engine. Creates new Sass::Engine instance with main
14
+ # Sass file and renders it.
15
+ #
16
+ # @return [String]
17
+ def render
18
+ Sass::Engine.new(load_resource(template), syntax: :scss,
19
+ load_paths: [File.expand_path('themes/', Dir.pwd)],
20
+ style: :compressed).render
21
+ end
22
+
23
+ # Default file name for rendererd file.
24
+ #
25
+ # @return [String]
26
+ def file_path
27
+ 'main.css'
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mako
4
+ class SubscriptionListParser
5
+ include FileOpenUtil
6
+
7
+ attr_reader :list
8
+
9
+ def initialize(args)
10
+ @list = args.fetch(:list)
11
+ end
12
+
13
+ # Parses OPML, JSON, or plain text documents and returns an Array of feed urls.
14
+ #
15
+ # @return [Array]
16
+ def parse
17
+ loaded_list = load_resource(list)
18
+ case File.extname list
19
+ when '.xml' || '.opml'
20
+ Nokogiri::XML(loaded_list).xpath('//@xmlUrl').map(&:value)
21
+ when '.json'
22
+ JSON.parse(loaded_list)
23
+ when '.txt'
24
+ loaded_list.split("\n")
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mako
4
+ VERSION = '0.1.0'
5
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ViewHelpers
4
+ # Returns today's date in Day, Date Month Year format
5
+ #
6
+ # @return [String]
7
+ def today
8
+ Time.now.strftime('%A, %d %B %Y')
9
+ end
10
+
11
+ # Returns the current time in month day year hour:minute:second format
12
+ #
13
+ # @return [String]
14
+ def last_updated
15
+ Time.now.strftime('%d %b %Y %H:%M:%S')
16
+ end
17
+
18
+ # Returns a string with anchor tag links to each Feed generated on the page
19
+ #
20
+ # @return [String]
21
+ def quick_nav(feeds)
22
+ feeds.select { |feed| feed.articles.size.positive? }
23
+ .each_with_index.inject('') do |string, (feed, index)|
24
+ string += "<a href='#feed-#{index}' class='quick-nav-item'>#{feed.title} <div class='circle'>#{feed.articles.size}</div></a>"
25
+ string
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mako
4
+ class Writer
5
+ attr_reader :renderer, :destination
6
+
7
+ def initialize(args)
8
+ @renderer = args.fetch(:renderer)
9
+ @destination = args.fetch(:destination)
10
+ end
11
+
12
+ def write
13
+ File.open(destination, 'w+', encoding: 'utf-8') do |f|
14
+ f.write(renderer.render)
15
+ end
16
+ end
17
+ end
18
+ end
data/lib/mako.rb ADDED
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'time'
4
+ require 'erb'
5
+ require 'logger'
6
+ require 'faraday'
7
+ require 'feedjira'
8
+ require 'nokogiri'
9
+ require 'json'
10
+ require 'sass'
11
+
12
+ require_relative 'mako/core_ext'
13
+ require_relative 'mako/errors'
14
+ require_relative 'mako/file_open_util'
15
+ require_relative 'mako/mako_logger'
16
+ require_relative 'mako/view_helpers'
17
+ require_relative 'mako/configuration'
18
+ require_relative 'mako/subscription_list_parser'
19
+ require_relative 'mako/feed'
20
+ require_relative 'mako/article'
21
+ require_relative 'mako/feed_requester'
22
+ require_relative 'mako/feed_constructor'
23
+ require_relative 'mako/html_renderer'
24
+ require_relative 'mako/sass_renderer'
25
+ require_relative 'mako/writer'
26
+ require_relative 'mako/core'
27
+ require_relative 'mako/cli'
28
+
29
+ module Mako
30
+ def self.logger
31
+ @logger ||= MakoLogger.new(STDOUT)
32
+ end
33
+
34
+ def self.config
35
+ @config ||= Configuration.load(File.expand_path('config.yaml', Dir.pwd))
36
+ end
37
+
38
+ def self.errors
39
+ @errors ||= Errors.new
40
+ end
41
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ gem 'mako'
@@ -0,0 +1,14 @@
1
+ ---
2
+ # This configuration file is where you can change settings for your
3
+ # Mako site.
4
+
5
+ # To change the theme, uncomment the next line and change the name of the
6
+ # theme. Be sure that your .html.erb and .scss files both match the
7
+ # name of the theme.
8
+
9
+ # theme: simple
10
+
11
+ # By default, images are removed from any articles from your feeds. If you
12
+ # want images to be loaded in, change this next line to false.
13
+
14
+ # sanitize_images: true
@@ -0,0 +1 @@
1
+ ["https://jonathanpike.net/feed.xml","https://daringfireball.net/feeds/main","https://marco.org/rss","https://www.speedshop.co/feed.xml","https://tenderlovemaking.com/atom.xml"]
@@ -0,0 +1,5 @@
1
+ https://jonathanpike.net/feed.xml
2
+ https://daringfireball.net/feeds/main
3
+ https://marco.org/rss
4
+ https://www.speedshop.co/feed.xml
5
+ https://tenderlovemaking.com/atom.xml
@@ -0,0 +1,12 @@
1
+ <opml version="2.0">
2
+ <body>
3
+ <outline text="Subscriptions" title="Subscriptions">
4
+ <outline xmlUrl='https://jonathanpike.net/feed.xml' />
5
+ <outline xmlUrl='https://daringfireball.net/feeds/main' />
6
+ <outline xmlUrl='https://marco.org/rss' />
7
+ <outline xmlUrl='https://www.speedshop.co/feed.xml' />
8
+ <outline xmlUrl='https://tenderlovemaking.com/atom.xml' />
9
+
10
+ </outline>
11
+ </body>
12
+ </opml>
@@ -0,0 +1,38 @@
1
+ // Headers
2
+ h1, h2, h3, h4, h5, h6,
3
+ .h1, .h2, .h3, .h4, .h5, .h6 {
4
+ margin-top: $headings-margin-top;
5
+ margin-bottom: $headings-margin-bottom;
6
+ font-family: $headings-font-family;
7
+ font-weight: $headings-font-weight;
8
+ line-height: $headings-line-height;
9
+ color: $headings-color;
10
+ }
11
+
12
+ h1, .h1 { font-size: $font-size-h1; }
13
+ h2, .h2 { font-size: $font-size-h2; }
14
+ h3, .h3 { font-size: $font-size-h3; }
15
+ h4, .h4 { font-size: $font-size-h4; }
16
+ h5, .h5 { font-size: $font-size-h5; }
17
+ h6, .h6 { font-size: $font-size-h6; }
18
+
19
+ // Horizontal Rules
20
+ hr {
21
+ margin-top: 1rem;
22
+ margin-bottom: 1rem;
23
+ border: 0;
24
+ border-top: 1px solid rgba($pd-black,.1);
25
+ }
26
+
27
+ // Blockquotes
28
+ blockquote {
29
+ padding: (1rem / 2) 1rem;
30
+ margin-bottom: 1rem;
31
+ font-size: $font-size-base * 1.1;
32
+ color: $pd-light-gray;
33
+ border-left: .15rem solid $pd-banner;
34
+ }
35
+
36
+ .bold {
37
+ font-weight: bold;
38
+ }
@@ -0,0 +1,97 @@
1
+ body {
2
+ font-family: $pd-font-family-sans-serif;
3
+ font-size: $font-size-base;
4
+ background-color: $pd-off-white;
5
+ margin: 0;
6
+ }
7
+
8
+ a:link {
9
+ border-bottom: 1px black dotted;
10
+ text-decoration: none;
11
+ color: $pd-black;
12
+ transition: all 0.2s ease;
13
+ -webkit-transition: all 0.2s ease;
14
+ }
15
+
16
+ a:visited {
17
+ text-decoration: none;
18
+ color: $pd-black;
19
+ }
20
+
21
+ a:hover{
22
+ text-decoration: none;
23
+ color: $pd-dark-blue;
24
+ }
25
+
26
+ .meta {
27
+ color: $pd-light-gray;
28
+ }
29
+
30
+ #banner {
31
+ background-color: $pd-banner;
32
+ text-align: center;
33
+ color: $pd-white;
34
+ h4 {
35
+ font-style: italic;
36
+ }
37
+ }
38
+
39
+ .banner-text {
40
+ display: inline-block;
41
+ }
42
+
43
+ #quick-nav {
44
+ background-color: $pd-banner-light;
45
+ max-width: 650px;
46
+ margin: auto;
47
+ margin-top:5px;
48
+ padding: 5px;
49
+ text-align: center;
50
+ @media only screen and (max-width: $screen-phone) {
51
+ margin-top: 0px;
52
+ }
53
+ }
54
+
55
+ .quick-nav-item {
56
+ display: inline-block;
57
+ padding: 3px;
58
+ margin: 0 0.25em;
59
+ }
60
+
61
+ a.quick-nav-item {
62
+ border-bottom: 0px;
63
+ color: $pd-white;
64
+ }
65
+
66
+ a.quick-nav-item:hover {
67
+ color: $pd-banner-dark;
68
+ }
69
+
70
+ .circle {
71
+ display: inline-block;
72
+ background: #84BE62;
73
+ padding: 3px 9px;
74
+ border: 2px solid $pd-white;
75
+ border-radius: 100px;
76
+ }
77
+
78
+ .back-to-top {
79
+ text-align: right;
80
+ }
81
+
82
+ #feed-container {
83
+ max-width: 700px;
84
+ margin: auto;
85
+ padding: 1em;
86
+ }
87
+
88
+ .article-header {
89
+ margin-left: -1em;
90
+ }
91
+
92
+ #footer {
93
+ border-top: 1px dotted $pd-light-gray;
94
+ text-align: center;
95
+ padding-top: 3em;
96
+ margin-bottom: 3em;
97
+ }