audrey2 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.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,21 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## PROJECT::SPECIFIC
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 Sven Aas
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,87 @@
1
+ = Audrey 2.0
2
+
3
+ Audrey 2.0 is a gem for feed processing and aggregation.
4
+
5
+ == Please note: This should be considered an Alpha release
6
+
7
+ The initial codebase is an adaptation of several other prototypes. It suffers from
8
+ severe shortages of documentation and testing. Both are coming soon.
9
+
10
+ == Installation
11
+
12
+ gem install audrey2
13
+
14
+ == Usage
15
+
16
+ Audrey 2.0 installs a command-line script, feedme, which can be fed one or more recipes:
17
+
18
+ feedme recipe [recipe2] [recipe3] ...
19
+
20
+ Each recipe lists one or more feeds and identifies a theme to be used at output. When
21
+ feedme is run it aggregates the feeds according to the recipe and generates themed
22
+ output.
23
+
24
+ == Requirements
25
+
26
+ Audrey 2.0 depends on the feed_normalizer and haml gems.
27
+
28
+ == Configuration
29
+
30
+ * A configuration file which is located by default at /etc/audrey2.conf. This location
31
+ can be overridden with the command line --config option. The configuration file is in
32
+ YAML format and must at minimum include the following lines:
33
+ * recipes_folder: ./recipes
34
+ * themes_folder: ./themes
35
+ * A recipes folder specified in the configuration file and containing at least one recipe.
36
+ * A themes folder specified in the configuration file and containing at leastone theme.
37
+
38
+ == Recipes
39
+
40
+ Each recipe is a YAML-format file located in the recipes folder specified in Audrey 2.0 config.
41
+ The filename must match the command line argument used when passing the recipe to the feedme
42
+ command-line program. By convention recipe filenames omit an extension.
43
+
44
+ Each recipe must
45
+
46
+ * List one or more feeds in an array of hashes formatted like this:
47
+
48
+ feeds:
49
+ -
50
+ name: nytimes
51
+ url: http://www.nytimes.com/services/xml/rss/nyt/HomePage.xml
52
+ -
53
+ name: bostonglobe
54
+ url: http://syndication.boston.com/topstories.xml
55
+
56
+ * Identify a theme
57
+
58
+ theme: mytheme
59
+
60
+ * Identify an output filename and path
61
+
62
+ output_file: entries.html
63
+
64
+ == Themes
65
+
66
+ Themes are located in eponymous folders within the themes folder identified in Audrey 2.0 config.
67
+ A theme folder must contain a single HAML template named entries.haml. This template is rendered
68
+ once for each entry in the feed aggregation. The rendered entries are concatenated and output at
69
+ the path specified in the recipe.
70
+
71
+ A theme folder may also contain a file named helpers.rb. When this file is present its code is
72
+ loaded into the HAML rendering scope, making any methods defined within available during
73
+ rendering of entries.haml.
74
+
75
+ == Note on Patches/Pull Requests
76
+
77
+ * Fork the project.
78
+ * Make your feature addition or bug fix.
79
+ * Add tests for it. This is important so I don't break it in a
80
+ future version unintentionally.
81
+ * Commit, do not mess with rakefile, version, or history.
82
+ (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
83
+ * Send me a pull request. Bonus points for topic branches.
84
+
85
+ == Copyright
86
+
87
+ Copyright (c) 2010 Sven Aas. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,54 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "audrey2"
8
+ gem.summary = "Gem for feed processing and aggregation"
9
+ gem.description = "Gem for feed processing and aggregation"
10
+ gem.email = "sven.aas@gmail.com"
11
+ gem.homepage = "http://github.com/svenaas/audrey2"
12
+ gem.authors = ["Sven Aas"]
13
+ gem.add_dependency "feed-normalizer", "~>1.5.2"
14
+ gem.add_dependency "haml", "~>3.0.13"
15
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
16
+ end
17
+ Jeweler::GemcutterTasks.new
18
+ rescue LoadError
19
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
20
+ end
21
+
22
+ require 'rake/testtask'
23
+ Rake::TestTask.new(:test) do |test|
24
+ test.libs << 'lib' << 'test'
25
+ test.pattern = 'test/**/test_*.rb'
26
+ test.verbose = true
27
+ end
28
+
29
+ begin
30
+ require 'rcov/rcovtask'
31
+ Rcov::RcovTask.new do |test|
32
+ test.libs << 'test'
33
+ test.pattern = 'test/**/test_*.rb'
34
+ test.verbose = true
35
+ end
36
+ rescue LoadError
37
+ task :rcov do
38
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
39
+ end
40
+ end
41
+
42
+ task :test => :check_dependencies
43
+
44
+ task :default => :test
45
+
46
+ require 'rake/rdoctask'
47
+ Rake::RDocTask.new do |rdoc|
48
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
49
+
50
+ rdoc.rdoc_dir = 'rdoc'
51
+ rdoc.title = "audrey2 #{version}"
52
+ rdoc.rdoc_files.include('README*')
53
+ rdoc.rdoc_files.include('lib/**/*.rb')
54
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,24 @@
1
+ # Sample audrey2.conf
2
+
3
+ # The default location for this file is /etc/audrey2/audrey2.conf, but an alternate location
4
+ # may be specified with the --config command-line option.
5
+
6
+ #
7
+ # Folders for recipes and themes
8
+ #
9
+ # Suggested locations:
10
+ # /etc/audrey2/recipes
11
+ # /etc/audrey2/themes
12
+ recipes_folder: ./recipes
13
+ themes_folder: ./themes
14
+
15
+ # User agent to impersonate when retrieving themes. Please use with discretion.
16
+ #user_agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3 (.NET CLR 1.1.4322)
17
+
18
+ #
19
+ # Not yet implemented:
20
+ #
21
+ # email_errors_to: test@test.com
22
+ # log_file: /var/log/audrey2.log
23
+ # log_level: debug
24
+
data/audrey2.gemspec ADDED
@@ -0,0 +1,55 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{audrey2}
8
+ s.version = "0.1.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Sven Aas"]
12
+ s.date = %q{2010-07-20}
13
+ s.default_executable = %q{feedme}
14
+ s.description = %q{Gem for feed processing and aggregation}
15
+ s.email = %q{sven.aas@gmail.com}
16
+ s.executables = ["feedme"]
17
+ s.extra_rdoc_files = [
18
+ "LICENSE",
19
+ "README.rdoc"
20
+ ]
21
+ s.files = [
22
+ ".document",
23
+ ".gitignore",
24
+ "LICENSE",
25
+ "README.rdoc",
26
+ "Rakefile",
27
+ "VERSION",
28
+ "audrey2.conf.sample",
29
+ "audrey2.gemspec",
30
+ "bin/feedme",
31
+ "lib/audrey2.rb"
32
+ ]
33
+ s.homepage = %q{http://github.com/svenaas/audrey2}
34
+ s.rdoc_options = ["--charset=UTF-8"]
35
+ s.require_paths = ["lib"]
36
+ s.rubygems_version = %q{1.3.7}
37
+ s.summary = %q{Gem for feed processing and aggregation}
38
+
39
+ if s.respond_to? :specification_version then
40
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
41
+ s.specification_version = 3
42
+
43
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
44
+ s.add_runtime_dependency(%q<feed-normalizer>, ["~> 1.5.2"])
45
+ s.add_runtime_dependency(%q<haml>, ["~> 3.0.13"])
46
+ else
47
+ s.add_dependency(%q<feed-normalizer>, ["~> 1.5.2"])
48
+ s.add_dependency(%q<haml>, ["~> 3.0.13"])
49
+ end
50
+ else
51
+ s.add_dependency(%q<feed-normalizer>, ["~> 1.5.2"])
52
+ s.add_dependency(%q<haml>, ["~> 3.0.13"])
53
+ end
54
+ end
55
+
data/bin/feedme ADDED
@@ -0,0 +1,45 @@
1
+ #!/usr/bin/ruby
2
+
3
+ require 'rubygems'
4
+ require 'audrey2'
5
+ require 'optparse'
6
+
7
+ options = {}
8
+
9
+ opts = OptionParser.new do |opts|
10
+ opts.banner = "Usage: feedme [OPTIONS] recipes"
11
+
12
+ options[:config] = '/etc/audrey2/audrey2.conf'
13
+ opts.on( '--config CONFIGFILE', "Location of config file", "(default: /etc/audrey2/audrey2.conf)" ) do |f|
14
+ options[:config] = f
15
+ end
16
+
17
+ opts.on_tail( '-h', '--help', 'Display this screen' ) do
18
+ puts opts
19
+ exit
20
+ end
21
+ end
22
+
23
+ begin
24
+ opts.parse! ARGV
25
+ options
26
+ rescue OptionParser::InvalidOption => e
27
+ $stderr.puts e
28
+ $stderr.puts opts
29
+ exit 1
30
+ rescue OptionParser::MissingArgument => e
31
+ $stderr.puts e
32
+ $stderr.puts opts
33
+ exit 1
34
+ end
35
+
36
+ recipes = ARGV
37
+ if recipes.length == 0
38
+ $stderr.puts "You must specify at least one recipe to feed me"
39
+ $stderr.puts opts
40
+ exit 1
41
+ end
42
+
43
+ audrey2 = Audrey2::Aggregator.new(options[:config])
44
+
45
+ recipes.each { |recipe| audrey2.feed_me(recipe) }
data/lib/audrey2.rb ADDED
@@ -0,0 +1,169 @@
1
+ require 'rubygems'
2
+ require 'yaml'
3
+ require 'feed-normalizer'
4
+ require 'open-uri'
5
+ require 'haml'
6
+
7
+ module Audrey2
8
+
9
+ class Aggregator
10
+ def initialize(configfile)
11
+ init_config(configfile)
12
+ end
13
+
14
+ def feed_me(recipe_name)
15
+ # Load recipe and theme and make sure everything is in order
16
+ recipe = load_recipe(recipe_name)
17
+ init_theme(recipe['theme'])
18
+ output_file = recipe['output_file']
19
+ verify_output_file(output_file)
20
+
21
+ # Download and parse the feeds
22
+ entry_sources = {}
23
+ feeds = recipe['feeds'].collect { |feed| parse_feed(feed, entry_sources) }
24
+
25
+ # Aggregate and sort the entries
26
+ entries = []
27
+ feeds.each { |feed| entries += feed.entries }
28
+ sort!(entries)
29
+
30
+ # Prepare template evaluation scope including any helper code defined in the theme
31
+ scope = Object.new
32
+ scope.instance_eval(@helper_code) if @helper_code
33
+
34
+ # Output the aggregated entries
35
+ output = ''
36
+ engine ||= Haml::Engine.new(@entry_template)
37
+
38
+ entries[0..@max_entries - 1].each do |entry|
39
+ output << engine.render(scope, :entry => entry, :source => entry_sources[entry])
40
+ end
41
+
42
+ File.open(output_file, 'w') { |f| f << output }
43
+ end
44
+
45
+ protected
46
+ def verify_output_file(output_file)
47
+ output_folder = File.dirname(output_file)
48
+ if ! File.exist? output_folder
49
+ $stderr.puts "ERROR: Output folder #{output_folder} does not exist."
50
+ exit(1)
51
+ elsif ! File.writable? output_folder
52
+ $stderr.puts "ERROR: Output folder #{output_folder} is not writable"
53
+ exit(1)
54
+ end
55
+ if File.exist?(output_file)
56
+ if ! File.writable? output_file
57
+ $stderr.puts "ERROR: Output file #{output_folder} is not writable"
58
+ exit(1)
59
+ end
60
+ end
61
+ end
62
+
63
+ def init_theme(theme)
64
+ theme_path = File.join(@themes_folder, theme)
65
+ if ! File.exist? theme_path
66
+ $stderr.puts "ERROR: Theme #{theme_path} does not exist."
67
+ exit(1)
68
+ elsif ! File.readable? theme_path
69
+ $stderr.puts "ERROR: Theme #{theme_path} is not readable"
70
+ exit(1)
71
+ end
72
+
73
+ entry_template_file = File.join(@themes_folder, theme, 'entry.haml')
74
+ if ! File.exist? entry_template_file
75
+ $stderr.puts "ERROR: Theme #{theme} does not include an entry template (entry.haml)"
76
+ exit(1)
77
+ elsif ! File.readable? entry_template_file
78
+ $stderr.puts "ERROR: Entry template #{entry_template_file} is not readable"
79
+ exit(1)
80
+ end
81
+ @entry_template = File.read(entry_template_file)
82
+
83
+ helper_file = File.join(@themes_folder, theme, 'helpers.rb')
84
+ @helper_code = nil
85
+ if File.exist? helper_file
86
+ if ! File.readable? helper_file
87
+ $stderr.puts "ERROR: Helper file #{helper_file} is not readable"
88
+ exit(1)
89
+ end
90
+ @helper_code = File.open(helper_file) { |f| f.read }
91
+ end
92
+ end
93
+
94
+ # Uses the sort order specified in configuration
95
+ def sort!(entries)
96
+ case @sort
97
+ when 'reverse-chronological'
98
+ entries.sort! {|a, b| b.date_published <=> a.date_published } # Reverse chronological
99
+ end
100
+ end
101
+
102
+ def parse_feed(feed, entry_sources)
103
+ remote_feed = nil
104
+ begin
105
+ remote_feed = open(feed['url'], "User-Agent" => @user_agent)
106
+ rescue Exception => e
107
+ raise "Exception occurred when opening feed #{feed['name']} at #{feed['url']}:\n\n" + e.to_s
108
+ end
109
+
110
+ parsed_feed = nil
111
+ begin
112
+ parsed_feed = FeedNormalizer::FeedNormalizer.parse remote_feed
113
+ rescue Exception => e
114
+ raise "Exception occurred when parsing feed #{feed['name']} which was downloaded from #{feed['url']}:\n\n" + e.to_s
115
+ end
116
+
117
+ parsed_feed.entries.each { |entry| entry_sources[entry] = feed }
118
+
119
+ parsed_feed
120
+ end
121
+
122
+ def load_recipe(recipe)
123
+ recipefile = File.join(@recipes_folder, recipe)
124
+ if ! File.exist? recipefile
125
+ $stderr.puts "ERROR: Recipe #{recipefile} does not exist"
126
+ exit(1)
127
+ elsif ! File.readable? recipefile
128
+ $stderr.puts "ERROR: Recipe file #{recipefile} is not readable"
129
+ exit(1)
130
+ end
131
+ YAML::load_file(recipefile)
132
+ end
133
+
134
+ def init_config(configfile)
135
+ if ! File.exist? configfile
136
+ $stderr.puts "ERROR: Configuration file #{configfile} does not exist"
137
+ exit(1)
138
+ elsif ! File.readable? configfile
139
+ $stderr.puts "ERROR: Configuration file #{configfile} is not readable"
140
+ exit(1)
141
+ end
142
+
143
+ config = YAML::load_file(configfile)
144
+
145
+ @recipes_folder = config['recipes_folder']
146
+ if ! File.exist? @recipes_folder
147
+ $stderr.puts "ERROR: Recipes folder #{@recipes_folder} does not exist"
148
+ exit(1)
149
+ elsif ! File.readable? @recipes_folder
150
+ $stderr.puts "ERROR: Recipes folder #{@recipes_folder} is not readable"
151
+ exit(1)
152
+ end
153
+
154
+ @themes_folder = config['themes_folder']
155
+ if ! File.exist? @themes_folder
156
+ $stderr.puts "ERROR: Themes folder #{@themes_folder} does not exist"
157
+ exit(1)
158
+ elsif ! File.readable? @themes_folder
159
+ $stderr.puts "ERROR: Themes folder #{@themes_folder} is not readable"
160
+ exit(1)
161
+ end
162
+
163
+ @user_agent = config['user_agent'] || 'Audrey 2.0 Feed Aggregator'
164
+ @max_entries = config['max_entries'] || 1000
165
+ @sort = config['sort'] || 'reverse-chronological'
166
+ end
167
+ end
168
+
169
+ end
metadata ADDED
@@ -0,0 +1,108 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: audrey2
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 0
10
+ version: 0.1.0
11
+ platform: ruby
12
+ authors:
13
+ - Sven Aas
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-07-20 00:00:00 -04:00
19
+ default_executable: feedme
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: feed-normalizer
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ hash: 7
30
+ segments:
31
+ - 1
32
+ - 5
33
+ - 2
34
+ version: 1.5.2
35
+ type: :runtime
36
+ version_requirements: *id001
37
+ - !ruby/object:Gem::Dependency
38
+ name: haml
39
+ prerelease: false
40
+ requirement: &id002 !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ hash: 29
46
+ segments:
47
+ - 3
48
+ - 0
49
+ - 13
50
+ version: 3.0.13
51
+ type: :runtime
52
+ version_requirements: *id002
53
+ description: Gem for feed processing and aggregation
54
+ email: sven.aas@gmail.com
55
+ executables:
56
+ - feedme
57
+ extensions: []
58
+
59
+ extra_rdoc_files:
60
+ - LICENSE
61
+ - README.rdoc
62
+ files:
63
+ - .document
64
+ - .gitignore
65
+ - LICENSE
66
+ - README.rdoc
67
+ - Rakefile
68
+ - VERSION
69
+ - audrey2.conf.sample
70
+ - audrey2.gemspec
71
+ - bin/feedme
72
+ - lib/audrey2.rb
73
+ has_rdoc: true
74
+ homepage: http://github.com/svenaas/audrey2
75
+ licenses: []
76
+
77
+ post_install_message:
78
+ rdoc_options:
79
+ - --charset=UTF-8
80
+ require_paths:
81
+ - lib
82
+ required_ruby_version: !ruby/object:Gem::Requirement
83
+ none: false
84
+ requirements:
85
+ - - ">="
86
+ - !ruby/object:Gem::Version
87
+ hash: 3
88
+ segments:
89
+ - 0
90
+ version: "0"
91
+ required_rubygems_version: !ruby/object:Gem::Requirement
92
+ none: false
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ hash: 3
97
+ segments:
98
+ - 0
99
+ version: "0"
100
+ requirements: []
101
+
102
+ rubyforge_project:
103
+ rubygems_version: 1.3.7
104
+ signing_key:
105
+ specification_version: 3
106
+ summary: Gem for feed processing and aggregation
107
+ test_files: []
108
+