summaryrefslogtreecommitdiff
path: root/lib/comics/data.rb
blob: d3d3a13f4b7b9afef0b2600c9751b0ae10f20697 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
require 'digest'
require 'json'
require 'time'

module Comics
  # A generic base class for exceptions.
  class ComicError < StandardError
    def status
      500
    end

    def to_s
      'Something bad happened.'
    end
  end

  # An error representing an item which was not found or does not exist.
  class NotFoundError < ComicError
    def status
      404
    end

    def to_s
      "That item wasn't found."
    end
  end

  # Generate IDs based on a fixed set of arguments.
  class IDGenerator
    def self.generate(prefix, *args)
      hash = Digest::SHA2.new(256)
      [prefix, *args].each do |v|
        s = v.to_s
        hash << '%d %s' % [s.length, s]
      end
      'tag:sandals@crustytoothpaste.net,2013:urn:sha256:id:' + hash.hexdigest
    end
  end

  # A feed entry.  Equivalent to Atom <entry>.
  class Entry
    attr_reader :id, :image, :link

    def initialize(id, date, website, image = nil)
      @id = id
      @date = date
      @link = @date.strftime(website)
      @image = @date.strftime(image) if image
    end

    def date(format = nil)
      return @date if format.nil?
      @date.strftime(format)
    end

    def link_only?
      @image.nil?
    end

    def html_message
      %(<a href="#{@link}">Click here to view the comic.</a>)
    end
  end

  # A particular comic.
  class Comic
    include Enumerable

    attr_reader :tag, :updatetime

    def initialize(config, tag, data, defaults)
      @tag_prefix = config.tag_prefix
      @tag = tag
      @data = data
      @defaults = defaults
      @updatetime = @data['comics']['daily']['time']
    end

    def name
      @data['name'] || 'Mystery Comic'
    end

    def author
      @data['author'] || 'Someone unknown'
    end

    def each(&_block)
      res = []
      count.times do |i|
        date = latest - (86_400 * i)
        id = id_for :entry, date
        res << Entry.new(id, date, @data['comics']['daily']['link'])
      end
      block_given? ? res.each { |e| yield e } : res
    end

    def id
      id_for :comic, latest
    end

    def link
      @data['comics']['daily']['website']
    end

    private

    def count
      5
    end

    def id_for(type, date)
      IDGenerator.generate(@tag_prefix, type, @tag, date.strftime('%F'))
    end

    def latest
      return @latest if @latest

      now = Time.now.gmtime
      @latest = if @updatetime
                  parsed = Time.parse(updatetime, now)
                  parsed > now ? parsed - 86_400 : parsed
                else
                  Time.utc(now.year, now.month, now.day)
                end
    end
  end

  # Represents the config.json configuration.
  class Configuration
    include Enumerable

    def initialize(source = nil)
      source = File.new('config.json') if source.nil?
      @data = JSON.parse(source.read)
    end

    def each(&_block)
      @data['comics'].each do |tag, data|
        yield Comic.new self, tag, data, @data['config']['default']
      end
    end

    def comic(tag)
      data = @data['comics'][tag]
      raise NotFoundError, "I don't know about that comic." unless data
      Comic.new self, tag, data, @data['config']['default']
    end

    def tag_prefix
      @data['config']['id']
    end

    def css_url
      @data['config']['css']
    end
  end
end