radiant-scraped_layouts-extension 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. data/Gemfile +4 -0
  2. data/Gemfile.lock +69 -0
  3. data/README.md +7 -0
  4. data/Rakefile +109 -0
  5. data/app/controllers/admin/scrapers_controller.rb +74 -0
  6. data/app/models/scraper.rb +10 -0
  7. data/app/models/scraper_insert.rb +7 -0
  8. data/app/views/admin/layouts/_actions_cell.html.haml +3 -0
  9. data/app/views/admin/layouts/_disabled_form.html.haml +32 -0
  10. data/app/views/admin/layouts/_title_cell.html.haml +5 -0
  11. data/app/views/admin/layouts/_warning.html.haml +3 -0
  12. data/app/views/admin/layouts/edit.html.haml +15 -0
  13. data/app/views/admin/scraper_inserts/_scraper_insert.html.haml +10 -0
  14. data/app/views/admin/scrapers/_form.html.haml +31 -0
  15. data/app/views/admin/scrapers/edit.html.haml +7 -0
  16. data/app/views/admin/scrapers/index.html.haml +35 -0
  17. data/app/views/admin/scrapers/new.html.haml +4 -0
  18. data/app/views/admin/scrapers/remove.html.haml +16 -0
  19. data/config/initializers/radiant_config.rb +3 -0
  20. data/config/locales/en.yml +12 -0
  21. data/config/routes.rb +5 -0
  22. data/cucumber.yml +1 -0
  23. data/db/migrate/20121015160755_create_scrapers.rb +15 -0
  24. data/db/migrate/20121015160953_create_scraper_inserts.rb +16 -0
  25. data/db/migrate/20121101183044_integrate_scraper_and_layout.rb +11 -0
  26. data/features/support/env.rb +11 -0
  27. data/features/support/paths.rb +22 -0
  28. data/lib/radiant-scraped_layouts-extension.rb +8 -0
  29. data/lib/tasks/scraped_layouts_extension_tasks.rake +47 -0
  30. data/radiant-scraped_layouts-extension.gemspec +26 -0
  31. data/scraped_layouts_extension.rb +47 -0
  32. data/spec/controllers/admin/scrapers_controller_spec.rb +255 -0
  33. data/spec/fixtures/scraper_inserts.yml +3 -0
  34. data/spec/fixtures/scrapers.yml +4 -0
  35. data/spec/models/layout_spec.rb +44 -0
  36. data/spec/models/scraper_insert_spec.rb +20 -0
  37. data/spec/models/scraper_spec.rb +21 -0
  38. data/spec/spec.opts +6 -0
  39. data/spec/spec_helper.rb +36 -0
  40. metadata +122 -0
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "https://rubygems.org"
2
+
3
+ gem 'radiant', '~> 1.1.0'
4
+
@@ -0,0 +1,69 @@
1
+ GEM
2
+ remote: https://rubygems.org/
3
+ specs:
4
+ RedCloth (4.2.9)
5
+ actionmailer (2.3.17)
6
+ actionpack (= 2.3.17)
7
+ actionpack (2.3.17)
8
+ activesupport (= 2.3.17)
9
+ rack (~> 1.1.0)
10
+ activerecord (2.3.17)
11
+ activesupport (= 2.3.17)
12
+ activeresource (2.3.17)
13
+ activesupport (= 2.3.17)
14
+ activesupport (2.3.17)
15
+ acts_as_tree (0.1.1)
16
+ chunky_png (1.2.7)
17
+ compass (0.12.2)
18
+ chunky_png (~> 1.2)
19
+ fssm (>= 0.2.7)
20
+ sass (~> 3.1)
21
+ compass-rails (1.0.3)
22
+ compass (>= 0.12.2, < 0.14)
23
+ delocalize (0.2.6)
24
+ fssm (0.2.10)
25
+ haml (3.1.8)
26
+ highline (1.6.16)
27
+ json (1.7.7)
28
+ rack (1.1.6)
29
+ rack-cache (1.2)
30
+ rack (>= 0.4)
31
+ radiant (1.1.3)
32
+ RedCloth (~> 4.2.0)
33
+ acts_as_tree (~> 0.1.1)
34
+ bundler (>= 1.0.0)
35
+ compass (~> 0.12.2)
36
+ compass-rails (~> 1.0.3)
37
+ delocalize (~> 0.2.3)
38
+ haml (~> 3.1.1)
39
+ highline (~> 1.6.10)
40
+ rack (~> 1.1.6)
41
+ rack-cache (~> 1.2)
42
+ radius (~> 0.7.3)
43
+ rails (~> 2.3.17)
44
+ rake (>= 0.8.7)
45
+ rdoc (>= 3.9.2)
46
+ stringex (~> 1.3.0)
47
+ tzinfo (~> 0.3.31)
48
+ will_paginate (~> 2.3.11)
49
+ radius (0.7.3)
50
+ rails (2.3.17)
51
+ actionmailer (= 2.3.17)
52
+ actionpack (= 2.3.17)
53
+ activerecord (= 2.3.17)
54
+ activeresource (= 2.3.17)
55
+ activesupport (= 2.3.17)
56
+ rake (>= 0.8.3)
57
+ rake (10.0.3)
58
+ rdoc (4.0.0)
59
+ json (~> 1.4)
60
+ sass (3.2.7)
61
+ stringex (1.3.3)
62
+ tzinfo (0.3.37)
63
+ will_paginate (2.3.16)
64
+
65
+ PLATFORMS
66
+ ruby
67
+
68
+ DEPENDENCIES
69
+ radiant (~> 1.1.0)
@@ -0,0 +1,7 @@
1
+ # Scraped Layouts
2
+
3
+ This extension allows a Radiant designer to pull a webpage from a given URL, replace portions of it using regular expressions, and save the outcome as a standard (but uneditable) Radiant layout.
4
+
5
+ ---
6
+
7
+ Created by Charles Hudson for YP Intellectual Property, LLC (yellowpages.com).
@@ -0,0 +1,109 @@
1
+ # Determine where the RSpec plugin is by loading the boot
2
+ unless defined? RADIANT_ROOT
3
+ ENV["RAILS_ENV"] = "test"
4
+ case
5
+ when ENV["RADIANT_ENV_FILE"]
6
+ require File.dirname(ENV["RADIANT_ENV_FILE"]) + "/boot"
7
+ when File.dirname(__FILE__) =~ %r{vendor/radiant/vendor/extensions}
8
+ require "#{File.expand_path(File.dirname(__FILE__) + "/../../../../../")}/config/boot"
9
+ else
10
+ require "#{File.expand_path(File.dirname(__FILE__) + "/../../../")}/config/boot"
11
+ end
12
+ end
13
+
14
+ require 'rake'
15
+ require 'rdoc/task'
16
+ require 'rake/testtask'
17
+
18
+ rspec_base = File.expand_path(RADIANT_ROOT + '/vendor/plugins/rspec/lib')
19
+ $LOAD_PATH.unshift(rspec_base) if File.exist?(rspec_base)
20
+ require 'spec/rake/spectask'
21
+ require 'cucumber'
22
+ require 'cucumber/rake/task'
23
+
24
+ # Cleanup the RADIANT_ROOT constant so specs will load the environment
25
+ Object.send(:remove_const, :RADIANT_ROOT)
26
+
27
+ extension_root = File.expand_path(File.dirname(__FILE__))
28
+
29
+ task :default => [:spec, :features]
30
+ task :stats => "spec:statsetup"
31
+
32
+ desc "Run all specs in spec directory"
33
+ Spec::Rake::SpecTask.new(:spec) do |t|
34
+ t.spec_opts = ['--options', "\"#{extension_root}/spec/spec.opts\""]
35
+ t.spec_files = FileList['spec/**/*_spec.rb']
36
+ end
37
+
38
+ task :features => 'spec:integration'
39
+
40
+ namespace :spec do
41
+ desc "Run all specs in spec directory with RCov"
42
+ Spec::Rake::SpecTask.new(:rcov) do |t|
43
+ t.spec_opts = ['--options', "\"#{extension_root}/spec/spec.opts\""]
44
+ t.spec_files = FileList['spec/**/*_spec.rb']
45
+ t.rcov = true
46
+ t.rcov_opts = ['--exclude', 'spec', '--rails']
47
+ end
48
+
49
+ desc "Print Specdoc for all specs"
50
+ Spec::Rake::SpecTask.new(:doc) do |t|
51
+ t.spec_opts = ["--format", "specdoc", "--dry-run"]
52
+ t.spec_files = FileList['spec/**/*_spec.rb']
53
+ end
54
+
55
+ [:models, :controllers, :views, :helpers].each do |sub|
56
+ desc "Run the specs under spec/#{sub}"
57
+ Spec::Rake::SpecTask.new(sub) do |t|
58
+ t.spec_opts = ['--options', "\"#{extension_root}/spec/spec.opts\""]
59
+ t.spec_files = FileList["spec/#{sub}/**/*_spec.rb"]
60
+ end
61
+ end
62
+
63
+ desc "Run the Cucumber features"
64
+ Cucumber::Rake::Task.new(:integration) do |t|
65
+ t.fork = true
66
+ t.cucumber_opts = ['--format', (ENV['CUCUMBER_FORMAT'] || 'pretty')]
67
+ # t.feature_pattern = "#{extension_root}/features/**/*.feature"
68
+ t.profile = "default"
69
+ end
70
+
71
+ # Setup specs for stats
72
+ task :statsetup do
73
+ require 'code_statistics'
74
+ ::STATS_DIRECTORIES << %w(Model\ specs spec/models)
75
+ ::STATS_DIRECTORIES << %w(View\ specs spec/views)
76
+ ::STATS_DIRECTORIES << %w(Controller\ specs spec/controllers)
77
+ ::STATS_DIRECTORIES << %w(Helper\ specs spec/views)
78
+ ::CodeStatistics::TEST_TYPES << "Model specs"
79
+ ::CodeStatistics::TEST_TYPES << "View specs"
80
+ ::CodeStatistics::TEST_TYPES << "Controller specs"
81
+ ::CodeStatistics::TEST_TYPES << "Helper specs"
82
+ ::STATS_DIRECTORIES.delete_if {|a| a[0] =~ /test/}
83
+ end
84
+
85
+ namespace :db do
86
+ namespace :fixtures do
87
+ desc "Load fixtures (from spec/fixtures) into the current environment's database. Load specific fixtures using FIXTURES=x,y"
88
+ task :load => :environment do
89
+ require 'active_record/fixtures'
90
+ ActiveRecord::Base.establish_connection(RAILS_ENV.to_sym)
91
+ (ENV['FIXTURES'] ? ENV['FIXTURES'].split(/,/) : Dir.glob(File.join(RAILS_ROOT, 'spec', 'fixtures', '*.{yml,csv}'))).each do |fixture_file|
92
+ Fixtures.create_fixtures('spec/fixtures', File.basename(fixture_file, '.*'))
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
98
+
99
+ desc 'Generate documentation for the scraped_layout extension.'
100
+ RDoc::Task.new(:rdoc) do |rdoc|
101
+ rdoc.rdoc_dir = 'rdoc'
102
+ rdoc.title = 'ScrapedLayoutExtension'
103
+ rdoc.options << '--line-numbers' << '--inline-source'
104
+ rdoc.rdoc_files.include('README')
105
+ rdoc.rdoc_files.include('lib/**/*.rb')
106
+ end
107
+
108
+ # Load any custom rakefiles for extension
109
+ Dir[File.dirname(__FILE__) + '/tasks/*.rake'].sort.each { |f| require f }
@@ -0,0 +1,74 @@
1
+ class Admin::ScrapersController < Admin::ResourceController
2
+
3
+ def new
4
+ @scraper = Scraper.new
5
+ @scraper.scraper_inserts << ScraperInsert.new
6
+ response_for :new
7
+ end
8
+
9
+ def create
10
+ @scraper = Scraper.new(params[:scraper])
11
+ @scraper.layout = Layout.new({ :name => "#{@scraper.name} (scraped)" })
12
+
13
+ save_scraper @scraper
14
+ end
15
+
16
+ def update
17
+ @scraper = Scraper.find(params[:id])
18
+ @scraper.update_attributes(params[:scraper])
19
+
20
+ save_scraper @scraper, :edit
21
+ end
22
+
23
+ private
24
+
25
+ def save_scraper(scraper, action = :new)
26
+ if manage_inserts?(scraper) || !scraper.save
27
+ unless scraper.errors.empty?
28
+ flash[:error] = scraper.errors.full_messages.join(' ')
29
+ end
30
+
31
+ render :action => action
32
+ else
33
+ scrape_site(scraper)
34
+
35
+ flash[:notice] = "Scraper successfully #{action == :new ? 'created' : 'updated'}"
36
+
37
+ redirect_to admin_scrapers_path
38
+ end
39
+ end
40
+
41
+ def manage_inserts?(scraper)
42
+ managed = false
43
+ scraper.scraper_inserts.each do |insert|
44
+ if insert.remove
45
+ scraper.scraper_inserts.delete(insert)
46
+ managed = true
47
+ end
48
+ end
49
+
50
+ if params[:add_insert]
51
+ scraper.scraper_inserts << ScraperInsert.new
52
+ scraper.scraper_inserts.last.errors.clear # TODO: manage this separately so Rails won't try to save it automatically.
53
+ managed = true
54
+ end
55
+
56
+ managed
57
+ end
58
+
59
+ def scrape_site(scraper)
60
+ begin
61
+ html = Net::HTTP.get_response(URI(scraper.url)).body
62
+
63
+ scraper.scraper_inserts.each do |insert|
64
+ html.gsub!(Regexp.new(insert.regex), insert.content)
65
+ end
66
+
67
+ scraper.layout.content = html
68
+ scraper.layout.generated = true
69
+ rescue
70
+ scraper.layout.content = nil
71
+ scraper.layout.generated = false
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,10 @@
1
+ class Scraper < ActiveRecord::Base
2
+ has_many :scraper_inserts, :autosave => true, :dependent => :destroy
3
+
4
+ belongs_to :layout, :autosave => true, :dependent => :destroy
5
+
6
+ validates_uniqueness_of :name, :case_sensitive => true
7
+ validates_presence_of :name, :url, :description
8
+
9
+ accepts_nested_attributes_for :scraper_inserts
10
+ end
@@ -0,0 +1,7 @@
1
+ class ScraperInsert < ActiveRecord::Base
2
+ belongs_to :scraper, :autosave => true
3
+
4
+ validates_presence_of :regex, :content
5
+
6
+ attr_accessor :remove
7
+ end
@@ -0,0 +1,3 @@
1
+ %td.actions
2
+ - unless layout.scraped?
3
+ = link_to image('minus') + ' ' + t('remove'), remove_admin_layout_url(layout), :class => "action"
@@ -0,0 +1,32 @@
1
+ - form_for [:admin, @layout] do |f|
2
+ = f.hidden_field :lock_version
3
+ = render_region :form_top, :locals => {:f => f}
4
+ .form_area
5
+ - render_region :form, :locals => {:f => f} do |form|
6
+ - form.edit_title do
7
+ %p.title
8
+ = f.label :name, t('name')
9
+ = f.text_field :name, :maxlength => 100, :class => "disabled textbox", :disabled => true
10
+ - form.edit_extended_metadata do
11
+ .drawer
12
+ .drawer_contents#attributes
13
+ %table.fieldset
14
+ %tr
15
+ %th.label= f.label :content_type, t('content_type')
16
+ %td.field= f.text_field :content_type, :maxlength => 40, :class => "disabled textbox", :disabled => true
17
+ = render_region :extended_metadata, :locals => {:f => f}
18
+ .drawer_handle
19
+ %a.toggle{:href=>'#attributes', :rel=>"toggle[attributes]", :class=>"#{(meta_errors? ? 'less' : 'more')}"}= meta_label
20
+ - form.edit_content do
21
+ %p.content
22
+ = f.label :layout_content, "Body"
23
+ ~ f.text_area :content, :size => "40x20", :style => "width: 100%", :class => "disabled textarea large", :disabled => true
24
+ - render_region :form_bottom, :locals => {:f => f} do |form_bottom|
25
+ - form_bottom.reference_links do
26
+ %p{ :style => 'padding: 2ex 1em; border: 6px outset #f00; text-align: center; color: #c00; font-weight: bold; background: #fff;' }
27
+ This layout has been generated and may not be directly edited.
28
+ - form_bottom.edit_buttons do
29
+ .buttons
30
+ = link_to '&laquo; Return to Index', admin_layouts_url
31
+ - form_bottom.edit_timestamp do
32
+ = updated_stamp @layout
@@ -0,0 +1,5 @@
1
+ %td.name
2
+ - if layout.scraped?
3
+ %em= link_to image('virtual_page', :alt => '') + ' ' + layout.name, edit_admin_layout_url(layout)
4
+ -else
5
+ = link_to image('layout', :alt => '') + ' ' + layout.name, edit_admin_layout_url(layout)
@@ -0,0 +1,3 @@
1
+ - if @layout.scraped?
2
+ #notice
3
+ %p This layout has been generated and may not be modified.
@@ -0,0 +1,15 @@
1
+ - @page_title = @layout.name + ' ' + t('layout') + ' - ' + default_page_title
2
+
3
+ - render_region :main do |main|
4
+ - if @layout.scraped?
5
+ - main.edit_header do
6
+ %h1= 'Scraped Layout'
7
+ - main.edit_form do
8
+ = render :partial => 'disabled_form'
9
+ - else
10
+ - main.edit_header do
11
+ %h1= t('edit_layout')
12
+ - main.edit_form do
13
+ = render :partial => 'form'
14
+
15
+ - content_for 'page_scripts', layout_edit_javascripts
@@ -0,0 +1,10 @@
1
+ %hr
2
+
3
+ %p
4
+ = f.label :regex
5
+ = f.text_field :regex, :class => 'textbox', :size => 90
6
+ %p
7
+ = f.label :content
8
+ = f.text_area :content, :class => 'textarea', :style => 'width: 90%;', :cols => 90, :rows => 10
9
+ %p
10
+ = f.submit 'Remove Insert', :name => "scraper[scraper_inserts_attributes][#{f.object_name.gsub(/[^0-9]+/,'')}][remove]"
@@ -0,0 +1,31 @@
1
+ - form_for [:admin, @scraper] do |f|
2
+ = render_region :form_top, :locals => {:f => f}
3
+
4
+ - render_region :form do |form|
5
+ - form.edit_title do
6
+ %p
7
+ = f.label :name
8
+ = f.text_field :name, :class => 'textbox', :size => 90
9
+ - form.edit_url do
10
+ %p
11
+ %label{:for => "scraper_url"} URL
12
+ = f.text_field :url, :class => 'textbox', :size => 90
13
+ - form.edit_description do
14
+ %p
15
+ %label{:for => "scraper_description"} Description
16
+ = f.text_field :description, :class => 'textbox', :size => 90
17
+
18
+ - form.edit_inserts do
19
+ - f.fields_for :scraper_inserts do |insert_form|
20
+ = render :partial => 'admin/scraper_inserts/scraper_insert', :locals => { :f => insert_form }
21
+
22
+ %hr
23
+
24
+ = submit_tag 'Add Another Insert', :name => 'add_insert'
25
+
26
+ - render_region :form_bottom do |form_bottom|
27
+ - form_bottom.edit_buttons do
28
+ %p.buttons
29
+ = save_model_button(@scraper)
30
+ or
31
+ = link_to "Cancel", admin_scrapers_path
@@ -0,0 +1,7 @@
1
+ - body_classes << "reversed"
2
+
3
+ - render_region :main do |main|
4
+ - main.edit_header do
5
+ %h1 Edit Scraper
6
+ - main.edit_form do
7
+ = render :partial => "form"
@@ -0,0 +1,35 @@
1
+ - @page_title = t('scraped_layouts') + ' - ' + default_page_title
2
+
3
+ .outset
4
+ = render_region :top
5
+ %table.index#scrapers
6
+ %thead
7
+ %tr
8
+ - render_region :thead do |thead|
9
+ - thead.title_header do
10
+ %th.name
11
+ = t('scraper')
12
+ - thead.actions_header do
13
+ %th.actions{:style=>"width:9em"}
14
+ = t('modify')
15
+ %tbody
16
+ - if @scrapers.any?
17
+ - @scrapers.each do |scraper|
18
+ %tr[scraper]
19
+ - render_region :tbody, :locals => {:scraper => scraper} do |tbody|
20
+ - tbody.title_cell do
21
+ %td.name
22
+ = link_to image('layout', :alt => '') + ' ' + scraper.name, edit_admin_scraper_url(scraper)
23
+ - tbody.actions_cell do
24
+ %td.actions
25
+ = link_to image('minus') + ' ' + t('remove'), remove_admin_scraper_url(scraper), :class => "action"
26
+ - else
27
+ %tr
28
+ %td.empty{:colspan => admin.scrapers.index.tbody.length}= t('no_scrapers')
29
+
30
+ - render_region :bottom do |bottom|
31
+ - bottom.new_button do
32
+ #actions
33
+ = pagination_for(@scrapers)
34
+ %ul
35
+ %li= link_to image('plus') + " " + t('new_scraper'), new_admin_scraper_url, :class => 'action_button'
@@ -0,0 +1,4 @@
1
+ - body_classes << "reversed"
2
+
3
+ %h1 New Scraper
4
+ = render :partial => "form"
@@ -0,0 +1,16 @@
1
+ %h1= t('remove_scraper')
2
+
3
+ %p= t('text.scrapers.remove_warning')
4
+
5
+ %table.index#scrapers
6
+ %tbody
7
+ %tr.node.level_1
8
+ %td.scraper
9
+ = image('layout', :alt => '')
10
+ = h @scraper.name
11
+
12
+ - form_for [:admin, @scraper], :html => { :method => 'delete', 'data-onsubmit_status'=>"Removing scraper&#8230;" } do
13
+ .buttons
14
+ %input.button{:type => "submit", :value => t('delete_scraper') }/
15
+ = t('or')
16
+ = link_to t('cancel'), admin_scrapers_url
@@ -0,0 +1,3 @@
1
+ Radiant.config do |config|
2
+ # config.define "setting.name", :default => 'value', :select_from => ['foo', 'bar']
3
+ end
@@ -0,0 +1,12 @@
1
+ ---
2
+ en:
3
+ scraped_layout: Scraped Layout
4
+ scraped_layouts: Scraped Layouts
5
+ scraper: Scraper
6
+ no_scrapers: No Scraped Layouts
7
+ new_scraper: New Scraped Layout
8
+ delete_scraper: Delete Scraper
9
+ remove_scraper: Remove Scraper
10
+ text:
11
+ scrapers:
12
+ remove_warning: 'Are you sure you want to <strong class="warning">permanently remove</strong> the following layout?'
@@ -0,0 +1,5 @@
1
+ ActionController::Routing::Routes.draw do |map|
2
+ map.namespace :admin, :member => { :remove => :get } do |admin|
3
+ admin.resources :scrapers
4
+ end
5
+ end
@@ -0,0 +1 @@
1
+ default: --format progress features --tags ~@proposed,~@in_progress
@@ -0,0 +1,15 @@
1
+ class CreateScrapers < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :scrapers do |t|
4
+ t.string :name, :unique => true
5
+ t.string :url
6
+ t.text :description
7
+
8
+ t.timestamps
9
+ end
10
+ end
11
+
12
+ def self.down
13
+ drop_table :scrapers
14
+ end
15
+ end
@@ -0,0 +1,16 @@
1
+ class CreateScraperInserts < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :scraper_inserts do |t|
4
+ t.string :regex
5
+ t.text :content
6
+
7
+ t.references :scraper
8
+
9
+ t.timestamps
10
+ end
11
+ end
12
+
13
+ def self.down
14
+ drop_table :scraper_inserts
15
+ end
16
+ end
@@ -0,0 +1,11 @@
1
+ class IntegrateScraperAndLayout < ActiveRecord::Migration
2
+ def self.up
3
+ change_table :scrapers do |t|
4
+ t.references :layout
5
+ end
6
+ end
7
+
8
+ def self.down
9
+ remove_column :scrapers, :layout_id
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ # Sets up the Rails environment for Cucumber
2
+ ENV["RAILS_ENV"] = "test"
3
+ # Extension root
4
+ extension_env = File.expand_path(File.dirname(__FILE__) + '/../../../../../config/environment')
5
+ require extension_env+'.rb'
6
+
7
+ Dir.glob(File.join(RADIANT_ROOT, "features", "**", "*.rb")).each {|step| require step unless step =~ /datasets_loader\.rb$/}
8
+
9
+ Cucumber::Rails::World.class_eval do
10
+ dataset :scraped_layout
11
+ end
@@ -0,0 +1,22 @@
1
+ module NavigationHelpers
2
+
3
+ # Extend the standard PathMatchers with your own paths
4
+ # to be used in your features.
5
+ #
6
+ # The keys and values here may be used in your standard web steps
7
+ # Using:
8
+ #
9
+ # When I go to the "scraped_layout" admin page
10
+ #
11
+ # would direct the request to the path you provide in the value:
12
+ #
13
+ # admin_scraped_layout_path
14
+ #
15
+ PathMatchers = {} unless defined?(PathMatchers)
16
+ PathMatchers.merge!({
17
+ # /scraped_layout/i => 'admin_scraped_layout_path'
18
+ })
19
+
20
+ end
21
+
22
+ World(NavigationHelpers)
@@ -0,0 +1,8 @@
1
+ module RadiantScrapedLayoutsExtension
2
+ VERSION = "1.0.0"
3
+ SUMMARY = "Scraped Layouts for Radiant CMS"
4
+ DESCRIPTION = "Generates scraped layouts, allowing content to be inserted via regular expressions."
5
+ URL = "https://github.com/yp-engineering/radiant-scraped_layouts-extension"
6
+ AUTHORS = ["Charles Hudson"]
7
+ EMAIL = ["charles.h.hudson@phobetron.com"]
8
+ end
@@ -0,0 +1,47 @@
1
+ namespace :radiant do
2
+ namespace :extensions do
3
+ namespace :scraped_layouts do
4
+
5
+ desc "Runs the migration of the Scraped Layout extension"
6
+ task :migrate => :environment do
7
+ require 'radiant/extension_migrator'
8
+ if ENV["VERSION"]
9
+ ScrapedLayoutsExtension.migrator.migrate(ENV["VERSION"].to_i)
10
+ Rake::Task['db:schema:dump'].invoke
11
+ else
12
+ ScrapedLayoutsExtension.migrator.migrate
13
+ Rake::Task['db:schema:dump'].invoke
14
+ end
15
+ end
16
+
17
+ desc "Copies public assets of the Scraped Layout to the instance public/ directory."
18
+ task :update => :environment do
19
+ is_svn_or_dir = proc {|path| path =~ /\.svn/ || File.directory?(path) }
20
+ puts "Copying assets from ScrapedLayoutsExtension"
21
+ Dir[ScrapedLayoutsExtension.root + "/public/**/*"].reject(&is_svn_or_dir).each do |file|
22
+ path = file.sub(ScrapedLayoutsExtension.root, '')
23
+ directory = File.dirname(path)
24
+ mkdir_p RAILS_ROOT + directory, :verbose => false
25
+ cp file, RAILS_ROOT + path, :verbose => false
26
+ end
27
+ end
28
+
29
+ desc "Syncs all available translations for this ext to the English ext master"
30
+ task :sync => :environment do
31
+ # The main translation root, basically where English is kept
32
+ language_root = ScrapedLayoutsExtension.root + "/config/locales"
33
+ words = TranslationSupport.get_translation_keys(language_root)
34
+
35
+ Dir["#{language_root}/*.yml"].each do |filename|
36
+ next if filename.match('_available_tags')
37
+ basename = File.basename(filename, '.yml')
38
+ puts "Syncing #{basename}"
39
+ (comments, other) = TranslationSupport.read_file(filename, basename)
40
+ words.each { |k,v| other[k] ||= words[k] } # Initializing hash variable as empty if it does not exist
41
+ other.delete_if { |k,v| !words[k] } # Remove if not defined in en.yml
42
+ TranslationSupport.write_file(filename, basename, comments, other)
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,26 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "radiant-scraped_layouts-extension"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "radiant-scraped_layouts-extension"
7
+ s.version = RadiantScrapedLayoutsExtension::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = RadiantScrapedLayoutsExtension::AUTHORS
10
+ s.email = RadiantScrapedLayoutsExtension::EMAIL
11
+ s.homepage = RadiantScrapedLayoutsExtension::URL
12
+ s.summary = RadiantScrapedLayoutsExtension::SUMMARY
13
+ s.description = RadiantScrapedLayoutsExtension::DESCRIPTION
14
+
15
+ s.add_dependency "radiant", "~> 1.1.0"
16
+
17
+ ignores = if File.exist?('.gitignore')
18
+ File.read('.gitignore').split("\n").collect { |a| d = Dir["./#{a}/**/*"]; (d.empty? ? a : d) }.flatten
19
+ else
20
+ []
21
+ end
22
+
23
+ s.files = Dir['./**/*'] - ignores
24
+ s.test_files = Dir['test/**/*','spec/**/*','features/**/*'] - ignores
25
+ s.require_paths = ["lib"]
26
+ end
@@ -0,0 +1,47 @@
1
+ # Uncomment this if you reference any of your controllers in activate
2
+ # require_dependency "application_controller"
3
+ require "radiant-scraped_layouts-extension"
4
+
5
+ class ScrapedLayoutsExtension < Radiant::Extension
6
+ version RadiantScrapedLayoutsExtension::VERSION
7
+ description RadiantScrapedLayoutsExtension::DESCRIPTION
8
+ url RadiantScrapedLayoutsExtension::URL
9
+
10
+ # See your config/routes.rb file in this extension to define custom routes
11
+
12
+ extension_config do |config|
13
+ # config is the Radiant.configuration object
14
+ end
15
+
16
+ def activate
17
+ Layout.class_eval do
18
+ has_one :scraper
19
+
20
+ attr_accessor :generated
21
+ validates_presence_of :generated, :if => :scraped?, :message => 'layouts may only be created or edited within the Scraped Layouts section, and when supplied with a working URL.'
22
+
23
+ def scraped?
24
+ !scraper.nil?
25
+ end
26
+ end
27
+
28
+ tab 'Design' do
29
+ add_item "Scraped Layouts", "/admin/scrapers", :after => "Layouts"
30
+ end
31
+
32
+ Radiant::AdminUI.class_eval { attr_accessor :scrapers }
33
+ admin.scrapers = OpenStruct.new.tap do |scrapers|
34
+ scrapers.index = Radiant::AdminUI::RegionSet.new do |index|
35
+ index.thead.concat %w{title_header actions_header}
36
+ index.tbody.concat %w{title_cell actions_cell}
37
+ index.bottom.concat %w{new_button}
38
+ end
39
+ scrapers.edit = Radiant::AdminUI::RegionSet.new do |edit|
40
+ edit.main.concat %w{edit_header edit_form}
41
+ edit.form.concat %w{edit_title edit_url edit_description edit_inserts}
42
+ edit.form_bottom.concat %w{edit_buttons}
43
+ end
44
+ scrapers.new = scrapers.edit
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,255 @@
1
+ require 'spec_helper'
2
+
3
+ describe Admin::ScrapersController do
4
+ dataset :users
5
+
6
+ before :each do
7
+ login_as :designer
8
+
9
+ @scraper_params = {
10
+ :name => 'YP.com',
11
+ :url => 'http://www.yellowpages.com/pages/partner_template',
12
+ :description => 'YP.com Template',
13
+ :scraper_inserts_attributes => []
14
+ }
15
+ @scraper_insert_params = {
16
+ :regex => '^.*$',
17
+ :content => 'New'
18
+ }
19
+ @scraper_params[:scraper_inserts_attributes] << @scraper_insert_params
20
+
21
+ @scraper = mock_model(Scraper)
22
+ @scraper.stub!(:url).and_return(@scraper_params[:url])
23
+ @scraper.stub!(:name).and_return(@scraper_params[:name])
24
+ @scraper.stub!(:description).and_return(@scraper_params[:description])
25
+ end
26
+
27
+ describe :new do
28
+ it 'initializes a new Scraper' do
29
+ get :new
30
+
31
+ assigns(:scraper).should be_new_record
32
+ end
33
+
34
+ it 'adds a new ScraperInsert to the new Scraper' do
35
+ get :new
36
+
37
+ assigns(:scraper).scraper_inserts[0].should be_new_record
38
+ end
39
+ end
40
+
41
+ describe :create do
42
+ before :each do
43
+ @layout = mock_model(Layout)
44
+ Layout.stub!(:new).and_return(@layout)
45
+
46
+ @scraper.stub!(:layout=).and_return(@layout)
47
+ Scraper.stub!(:new).and_return(@scraper)
48
+
49
+ controller.stub!(:save_scraper).and_return(true)
50
+ end
51
+
52
+ it 'creates a new associated Layout with a name based on the Scraper\'s name' do
53
+ Layout.should_receive(:new).with(:name => 'YP.com (scraped)')
54
+
55
+ post :create, :scraper => @scraper_params
56
+ end
57
+
58
+ it 'calls save_scraper method' do
59
+ controller.should_receive(:save_scraper)
60
+
61
+ post :create, :scraper => @scraper_params
62
+ end
63
+ end
64
+
65
+ describe :update do
66
+ before :each do
67
+ @scraper.stub!(:update_attributes).and_return(true)
68
+ Scraper.stub!(:find).and_return(@scraper)
69
+ Scraper.stub!(:update_attributes).and_return(true)
70
+
71
+ controller.stub!(:save_scraper).and_return(true)
72
+ end
73
+
74
+ it 'updates the existing Scraper with form params' do
75
+ @scraper.should_receive(:update_attributes).with(anything)
76
+
77
+ put :update, :id => 0, :scraper => @scraper_params
78
+ end
79
+
80
+ it 'calls save_scraper method with previous action name' do
81
+ controller.should_receive(:save_scraper).with(@scraper, :edit)
82
+
83
+ put :update, :id => 0, :scraper => @scraper_params
84
+ end
85
+ end
86
+
87
+ describe :save_scraper do
88
+ before :each do
89
+ controller.stub!(:scrape_site).and_return(true)
90
+ controller.stub!(:redirect_to)
91
+ controller.stub!(:render)
92
+ controller.stub!(:admin_scrapers_path)
93
+ end
94
+
95
+ it 'calls manage_inserts?' do
96
+ @scraper.stub!(:save).and_return(true)
97
+
98
+ controller.should_receive(:manage_inserts?)
99
+
100
+ controller.send(:save_scraper, @scraper)
101
+ end
102
+
103
+ it 'adds flash notice on successful save or update' do
104
+ @scraper.stub!(:save).and_return(true)
105
+ controller.stub!(:manage_inserts?).and_return(false)
106
+
107
+ controller.send(:save_scraper, @scraper)
108
+
109
+ flash[:error].should be_nil
110
+ flash[:notice].should == 'Scraper successfully created'
111
+ end
112
+
113
+ it 'redirects to Scraped Layouts index on successful save or update' do
114
+ controller.stub!(:manage_inserts?).and_return(false)
115
+ @scraper.stub!(:save).and_return(true)
116
+
117
+ controller.should_receive(:redirect_to)
118
+ controller.should_receive(:admin_scrapers_path)
119
+
120
+ controller.send(:save_scraper, @scraper)
121
+ end
122
+
123
+ it 'renders to previous action when inserts are managed (added or removed)' do
124
+ controller.stub!(:manage_inserts?).and_return(true)
125
+
126
+ @scraper.stub!(:errors).and_return({})
127
+ errors = @scraper.errors
128
+ errors.stub!(:empty?).and_return(true)
129
+
130
+ controller.should_receive(:render).with(:action => :new)
131
+
132
+ controller.send(:save_scraper, @scraper)
133
+ end
134
+
135
+ it 'adds flash error when save is unsuccessful' do
136
+ controller.stub!(:manage_inserts?).and_return(false)
137
+ @scraper.stub!(:save).and_return(false)
138
+
139
+ @scraper.stub!(:errors).and_return({})
140
+ errors = @scraper.errors
141
+ errors.stub!(:empty?).and_return(false)
142
+ errors.stub!(:full_messages).and_return(['Error'])
143
+
144
+ controller.send(:save_scraper, @scraper)
145
+
146
+ flash[:notice].should be_nil
147
+ flash[:error].should == 'Error'
148
+ end
149
+
150
+ it 'renders previous action when save is unsuccessful' do
151
+ controller.stub!(:manage_inserts?).and_return(false)
152
+ @scraper.stub!(:save).and_return(false)
153
+
154
+ @scraper.stub!(:errors).and_return({})
155
+ errors = @scraper.errors
156
+ errors.stub!(:empty?).and_return(true)
157
+
158
+ controller.should_receive(:render).with(:action => :edit)
159
+
160
+ controller.send(:save_scraper, @scraper, :edit)
161
+ end
162
+ end
163
+
164
+ describe :manage_inserts? do
165
+ before :each do
166
+ @scraper_insert = mock_model(ScraperInsert)
167
+ @scraper_insert.stub!(:regex).and_return(@scraper_insert_params[:regex])
168
+ @scraper_insert.stub!(:content).and_return(@scraper_insert_params[:content])
169
+
170
+ @scraper_inserts = [@scraper_insert]
171
+ @scraper.stub!(:scraper_inserts).and_return(@scraper_inserts)
172
+
173
+ controller.stub!(:scrape_site).and_return(true)
174
+ end
175
+
176
+ it 'removes inserts that are marked for removal' do
177
+ @scraper_insert.stub!(:remove).and_return(true)
178
+
179
+ @scraper_inserts.should_receive(:delete).with(@scraper_insert)
180
+
181
+ controller.send(:manage_inserts?, @scraper).should be_true
182
+ end
183
+
184
+ it 'adds inserts when requested' do
185
+ @scraper_insert.stub!(:remove).and_return(false)
186
+ @scraper_insert.stub!(:errors).and_return({})
187
+ errors = @scraper_insert.errors
188
+ errors.stub!(:clear).and_return(true)
189
+
190
+ @scraper_inserts.should_receive(:<<)
191
+
192
+ controller.params[:add_insert] = true
193
+ controller.send(:manage_inserts?, @scraper).should be_true
194
+ end
195
+
196
+ it 'returns false when inserts have not been managed' do
197
+ @scraper_insert.stub!(:remove).and_return(false)
198
+
199
+ controller.send(:manage_inserts?, @scraper).should be_false
200
+ end
201
+ end
202
+
203
+ describe :scrape_site do
204
+ before :each do
205
+ @scraper_insert = mock_model(ScraperInsert)
206
+ @scraper_insert.stub!(:regex).and_return(@scraper_insert_params[:regex])
207
+ @scraper_insert.stub!(:content).and_return(@scraper_insert_params[:content])
208
+
209
+ @scraper_inserts = [@scraper_insert]
210
+ @scraper.stub!(:scraper_inserts).and_return(@scraper_inserts)
211
+
212
+ @layout = mock_model(Layout)
213
+ @layout.stub!(:content=)
214
+ @layout.stub!(:generated=)
215
+ @scraper.stub!(:layout).and_return(@layout)
216
+
217
+ @uri = URI(@scraper.url)
218
+ @response = mock(Object)
219
+ @response.stub!(:body).and_return('Text')
220
+
221
+ @regex = Regexp.new(@scraper_insert.regex)
222
+ Regexp.stub!(:new).and_return(@regex)
223
+
224
+ controller.stub!(:URI).and_return(@uri)
225
+ end
226
+
227
+ it 'stuffs content from an external URL into a Scraper\'s associalte Layout' do
228
+ Net::HTTP.should_receive(:get_response).with(@uri).and_return(@response)
229
+
230
+ @layout.should_receive(:content=).with(anything)
231
+ @layout.should_receive(:generated=).with(true)
232
+
233
+ controller.send(:scrape_site, @scraper)
234
+ end
235
+
236
+ it 'inserts content via the given regex into the HTML returned by the URL' do
237
+ Net::HTTP.should_receive(:get_response).with(@uri).and_return(@response)
238
+
239
+ @layout.should_receive(:content=).with('New')
240
+ @layout.should_receive(:generated=).with(true)
241
+
242
+ controller.send(:scrape_site, @scraper)
243
+ end
244
+
245
+ it 'places the Scraper\'s associated Layout into an invalid state when the URL can not be reached' do
246
+ Net::HTTP.should_receive(:get_response).with(@uri).and_raise('Random exception')
247
+
248
+ @layout.should_receive(:content=).with(nil)
249
+ @layout.should_receive(:generated=).with(false)
250
+
251
+ controller.send(:scrape_site, @scraper)
252
+ end
253
+ end
254
+
255
+ end
@@ -0,0 +1,3 @@
1
+ yellowpages_canvas:
2
+ regex: <!-- CANVAS INSERTION POINT -->
3
+ content: <div id="canvas">Content</div>
@@ -0,0 +1,4 @@
1
+ yellowpages:
2
+ url: http://stage-www.yellowpages.com
3
+ name: YP.com
4
+ description: YP.com Layout
@@ -0,0 +1,44 @@
1
+ require 'spec_helper'
2
+
3
+ describe Layout do
4
+ dataset :layouts
5
+ test_helper :validations
6
+
7
+ before :each do
8
+ @layout = @model = Layout.new(layout_params)
9
+ end
10
+
11
+ describe :scraped_layout do
12
+ before :each do
13
+ @layout.stub!(:scraped?).and_return(true)
14
+ end
15
+
16
+ it 'should be valid when flagged as generated' do
17
+ @layout.generated = true
18
+ @layout.should be_valid
19
+ end
20
+
21
+ it 'should not be valid when not flagged as generated' do
22
+ @layout.generated = false
23
+ @layout.should_not be_valid
24
+ end
25
+ end
26
+
27
+ describe :normal_layout do
28
+ before :each do
29
+ @layout.stub!(:scraped?).and_return(false)
30
+ end
31
+
32
+ it 'should be valid regardless of being generated' do
33
+ @layout.generated = true
34
+ @layout.should be_valid
35
+ end
36
+
37
+ it 'should be valid regardless of not being generated' do
38
+ @layout.generated = false
39
+ @layout.should be_valid
40
+ end
41
+ end
42
+
43
+ end
44
+
@@ -0,0 +1,20 @@
1
+ require 'spec_helper'
2
+
3
+ describe ScraperInsert do
4
+ fixtures :scraper_inserts
5
+
6
+ before(:each) do
7
+ @scraper_insert = scraper_inserts(:yellowpages_canvas)
8
+ end
9
+
10
+ it "should be valid" do
11
+ @scraper_insert.should be_valid
12
+ end
13
+
14
+ it "should validate required fields" do
15
+ @scraper_insert.regex = nil
16
+ @scraper_insert.content = nil
17
+
18
+ @scraper_insert.should_not be_valid
19
+ end
20
+ end
@@ -0,0 +1,21 @@
1
+ require 'spec_helper'
2
+
3
+ describe Scraper do
4
+ fixtures :scrapers
5
+
6
+ before(:each) do
7
+ @scraper = scrapers(:yellowpages)
8
+ end
9
+
10
+ it "should be valid" do
11
+ @scraper.should be_valid
12
+ end
13
+
14
+ it "should validate required fields" do
15
+ @scraper.name = nil
16
+ @scraper.url = nil
17
+ @scraper.description = nil
18
+
19
+ @scraper.should_not be_valid
20
+ end
21
+ end
@@ -0,0 +1,6 @@
1
+ --colour
2
+ --format
3
+ progress
4
+ --loadby
5
+ mtime
6
+ --reverse
@@ -0,0 +1,36 @@
1
+ unless defined? RADIANT_ROOT
2
+ ENV["RAILS_ENV"] = "test"
3
+ case
4
+ when ENV["RADIANT_ENV_FILE"]
5
+ require ENV["RADIANT_ENV_FILE"]
6
+ when File.dirname(__FILE__) =~ %r{vendor/radiant/vendor/extensions}
7
+ require "#{File.expand_path(File.dirname(__FILE__) + "/../../../../../../")}/config/environment"
8
+ else
9
+ require "#{File.expand_path(File.dirname(__FILE__) + "/../../../../")}/config/environment"
10
+ end
11
+ end
12
+ require "#{RADIANT_ROOT}/spec/spec_helper"
13
+
14
+ Dataset::Resolver.default << (File.dirname(__FILE__) + "/datasets")
15
+
16
+ if File.directory?(File.dirname(__FILE__) + "/matchers")
17
+ Dir[File.dirname(__FILE__) + "/matchers/*.rb"].each {|file| require file }
18
+ end
19
+
20
+ Spec::Runner.configure do |config|
21
+ config.use_transactional_fixtures = true
22
+ config.use_instantiated_fixtures = false
23
+ config.fixture_path = 'spec/fixtures'
24
+
25
+ # You can declare fixtures for each behaviour like this:
26
+ # describe "...." do
27
+ # fixtures :table_a, :table_b
28
+ #
29
+ # Alternatively, if you prefer to declare them only once, you can
30
+ # do so here, like so ...
31
+ #
32
+ # config.global_fixtures = :table_a, :table_b
33
+ #
34
+ # If you declare global fixtures, be aware that they will be declared
35
+ # for all of your examples, even those that don't use them.
36
+ end
metadata ADDED
@@ -0,0 +1,122 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: radiant-scraped_layouts-extension
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Charles Hudson
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-03-20 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: radiant
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 1.1.0
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: 1.1.0
30
+ description: Generates scraped layouts, allowing content to be inserted via regular
31
+ expressions.
32
+ email:
33
+ - charles.h.hudson@phobetron.com
34
+ executables: []
35
+ extensions: []
36
+ extra_rdoc_files: []
37
+ files:
38
+ - ./app/controllers/admin/scrapers_controller.rb
39
+ - ./app/models/scraper.rb
40
+ - ./app/models/scraper_insert.rb
41
+ - ./app/views/admin/layouts/_actions_cell.html.haml
42
+ - ./app/views/admin/layouts/_disabled_form.html.haml
43
+ - ./app/views/admin/layouts/_title_cell.html.haml
44
+ - ./app/views/admin/layouts/_warning.html.haml
45
+ - ./app/views/admin/layouts/edit.html.haml
46
+ - ./app/views/admin/scraper_inserts/_scraper_insert.html.haml
47
+ - ./app/views/admin/scrapers/_form.html.haml
48
+ - ./app/views/admin/scrapers/edit.html.haml
49
+ - ./app/views/admin/scrapers/index.html.haml
50
+ - ./app/views/admin/scrapers/new.html.haml
51
+ - ./app/views/admin/scrapers/remove.html.haml
52
+ - ./config/initializers/radiant_config.rb
53
+ - ./config/locales/en.yml
54
+ - ./config/routes.rb
55
+ - ./cucumber.yml
56
+ - ./db/migrate/20121015160755_create_scrapers.rb
57
+ - ./db/migrate/20121015160953_create_scraper_inserts.rb
58
+ - ./db/migrate/20121101183044_integrate_scraper_and_layout.rb
59
+ - ./features/support/env.rb
60
+ - ./features/support/paths.rb
61
+ - ./Gemfile
62
+ - ./Gemfile.lock
63
+ - ./lib/radiant-scraped_layouts-extension.rb
64
+ - ./lib/tasks/scraped_layouts_extension_tasks.rake
65
+ - ./radiant-scraped_layouts-extension.gemspec
66
+ - ./Rakefile
67
+ - ./README.md
68
+ - ./scraped_layouts_extension.rb
69
+ - ./spec/controllers/admin/scrapers_controller_spec.rb
70
+ - ./spec/fixtures/scraper_inserts.yml
71
+ - ./spec/fixtures/scrapers.yml
72
+ - ./spec/models/layout_spec.rb
73
+ - ./spec/models/scraper_insert_spec.rb
74
+ - ./spec/models/scraper_spec.rb
75
+ - ./spec/spec.opts
76
+ - ./spec/spec_helper.rb
77
+ - spec/controllers/admin/scrapers_controller_spec.rb
78
+ - spec/fixtures/scraper_inserts.yml
79
+ - spec/fixtures/scrapers.yml
80
+ - spec/models/layout_spec.rb
81
+ - spec/models/scraper_insert_spec.rb
82
+ - spec/models/scraper_spec.rb
83
+ - spec/spec.opts
84
+ - spec/spec_helper.rb
85
+ - features/support/env.rb
86
+ - features/support/paths.rb
87
+ homepage: https://github.com/yp-engineering/radiant-scraped_layouts-extension
88
+ licenses: []
89
+ post_install_message:
90
+ rdoc_options: []
91
+ require_paths:
92
+ - lib
93
+ required_ruby_version: !ruby/object:Gem::Requirement
94
+ none: false
95
+ requirements:
96
+ - - ! '>='
97
+ - !ruby/object:Gem::Version
98
+ version: '0'
99
+ required_rubygems_version: !ruby/object:Gem::Requirement
100
+ none: false
101
+ requirements:
102
+ - - ! '>='
103
+ - !ruby/object:Gem::Version
104
+ version: '0'
105
+ requirements: []
106
+ rubyforge_project:
107
+ rubygems_version: 1.8.24
108
+ signing_key:
109
+ specification_version: 3
110
+ summary: Scraped Layouts for Radiant CMS
111
+ test_files:
112
+ - spec/controllers/admin/scrapers_controller_spec.rb
113
+ - spec/fixtures/scraper_inserts.yml
114
+ - spec/fixtures/scrapers.yml
115
+ - spec/models/layout_spec.rb
116
+ - spec/models/scraper_insert_spec.rb
117
+ - spec/models/scraper_spec.rb
118
+ - spec/spec.opts
119
+ - spec/spec_helper.rb
120
+ - features/support/env.rb
121
+ - features/support/paths.rb
122
+ has_rdoc: