rad_kit 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. data/Rakefile +11 -0
  2. data/lib/components/kit.rb +16 -0
  3. data/lib/components/kit.yml +3 -0
  4. data/lib/components/models.rb +7 -0
  5. data/lib/kit/factories.rb +9 -0
  6. data/lib/kit/gems.rb +16 -0
  7. data/lib/kit/http_controller.rb +4 -0
  8. data/lib/kit/http_controller/authorized.rb +51 -0
  9. data/lib/kit/http_controller/localized.rb +13 -0
  10. data/lib/kit/kit.rb +29 -0
  11. data/lib/kit/models.rb +8 -0
  12. data/lib/kit/models/attachment_uploader.rb +15 -0
  13. data/lib/kit/models/attachments_uploader_helper.rb +79 -0
  14. data/lib/kit/models/authorized.rb +188 -0
  15. data/lib/kit/models/authorized_object.rb +167 -0
  16. data/lib/kit/models/default_permissions.yml +29 -0
  17. data/lib/kit/models/file_uploader.rb +26 -0
  18. data/lib/kit/models/micelaneous.rb +1 -0
  19. data/lib/kit/models/role.rb +88 -0
  20. data/lib/kit/models_after.rb +27 -0
  21. data/lib/kit/mongoid.rb +22 -0
  22. data/lib/kit/mongoid/rad_micelaneous.rb +36 -0
  23. data/lib/kit/mongoid/text_processor.rb +44 -0
  24. data/lib/kit/spec.rb +77 -0
  25. data/lib/kit/spec/items_controller_crud.rb +64 -0
  26. data/lib/kit/support.rb +14 -0
  27. data/lib/kit/support/string.rb +6 -0
  28. data/lib/kit/tasks.rb +18 -0
  29. data/lib/kit/text_utils.rb +43 -0
  30. data/lib/kit/text_utils/code_highlighter.rb +58 -0
  31. data/lib/kit/text_utils/custom_markdown.rb +90 -0
  32. data/lib/kit/text_utils/ensure_utf.rb +8 -0
  33. data/lib/kit/text_utils/github_flavoured_markdown.rb +32 -0
  34. data/lib/kit/text_utils/html_sanitizer.rb +89 -0
  35. data/lib/kit/text_utils/image_box.rb +35 -0
  36. data/lib/kit/text_utils/markup.rb +43 -0
  37. data/lib/kit/text_utils/processor.rb +25 -0
  38. data/lib/kit/text_utils/tag_shortcuts.rb +14 -0
  39. data/lib/kit/text_utils/truncate.rb +29 -0
  40. data/lib/kit/text_utils/truncator.rb +15 -0
  41. data/lib/kit/text_utils/urls.rb +13 -0
  42. data/readme.md +10 -0
  43. data/spec/controller/authorization_spec.rb +149 -0
  44. data/spec/controller/comments_spec.rb +54 -0
  45. data/spec/controller/items_spec.rb +45 -0
  46. data/spec/models/attachments_spec.rb +24 -0
  47. data/spec/models/attachments_spec/a.txt +1 -0
  48. data/spec/models/attachments_uploader_helper_spec.rb +108 -0
  49. data/spec/models/attachments_uploader_helper_spec/v1/a.txt +1 -0
  50. data/spec/models/attachments_uploader_helper_spec/v1/b.txt +1 -0
  51. data/spec/models/attachments_uploader_helper_spec/v2/a.txt +1 -0
  52. data/spec/models/authorization_spec.rb +77 -0
  53. data/spec/models/authorized_object_spec.rb +254 -0
  54. data/spec/models/comments_spec.rb +1 -0
  55. data/spec/models/item_spec.rb +51 -0
  56. data/spec/models/role_spec.rb +17 -0
  57. data/spec/models/tags_spec.rb +44 -0
  58. data/spec/models/uploader_spec.rb +37 -0
  59. data/spec/models/uploader_spec//321/204/320/260/320/270/314/206/320/273 /321/201 /320/277/321/200/320/276/320/261/320/265/320/273/320/260/320/274/320/270.txt" +1 -0
  60. data/spec/mongoid/basic_spec.rb +36 -0
  61. data/spec/spec_helper.rb +20 -0
  62. data/spec/spec_helper/controller.rb +9 -0
  63. data/spec/spec_helper/factories.rb +24 -0
  64. data/spec/spec_helper/user.rb +17 -0
  65. data/spec/utils/text_utils_spec.rb +280 -0
  66. metadata +232 -0
data/lib/kit/spec.rb ADDED
@@ -0,0 +1,77 @@
1
+ require 'rad'
2
+
3
+ require 'rad/spec'
4
+
5
+ require 'mongoid_misc'
6
+ require 'mongoid_misc/spec'
7
+ require 'carrierwave_ext/spec'
8
+
9
+
10
+ #
11
+ # SaaS
12
+ #
13
+ unless ENV['saas'] == 'false'
14
+ begin
15
+ require 'saas/spec'
16
+ rescue LoadError
17
+ end
18
+ end
19
+
20
+
21
+ #
22
+ # User
23
+ #
24
+ rad.register :user
25
+ rspec do
26
+ def login_as user
27
+ rad.user = user
28
+ end
29
+
30
+ def self.login_as name, options = {}
31
+ before do
32
+ @user = Factory.create name, options
33
+ login_as @user
34
+ end
35
+ end
36
+ end
37
+
38
+
39
+ #
40
+ # CarrierWave
41
+ #
42
+ module Rad::CarrierWaveSpecHelper
43
+ def with_files
44
+ before do
45
+ rad.config.fs_path = CarrierWaveExtSpecHelper::TEST_PATH
46
+ rad.config.fs_cache_path = CarrierWaveExtSpecHelper::TEST_CACHE_PATH
47
+
48
+ Models::FileUploader.storage :file
49
+ end
50
+
51
+ super
52
+ end
53
+ end
54
+ rspec.extend Rad::CarrierWaveSpecHelper
55
+
56
+
57
+ #
58
+ # Micelaneous
59
+ #
60
+ rspec do
61
+ alias_method :call, :wcall
62
+ alias_method :pcall, :post_wcall
63
+ alias_method :set_call, :set_wcall
64
+
65
+ class << self
66
+ def with_models
67
+ with_mongoid
68
+ rad.extension :with_models, self
69
+ end
70
+
71
+ def with_controllers
72
+ with_models
73
+ end
74
+ end
75
+ end
76
+
77
+ require 'kit/spec/items_controller_crud'
@@ -0,0 +1,64 @@
1
+ shared_examples_for "Items Controller CRUD" do
2
+ before do
3
+ @controller_class.should be_present
4
+ @model_class ||= @controller_class.model_class
5
+ @model_name ||= @model_class.alias.underscore
6
+ end
7
+
8
+ %w{html json}.each do |format|
9
+ it "show :#{format}" do
10
+ @item = Factory.create @model_name
11
+ call :show, id: @item.to_param, format: format
12
+ response.should be_ok
13
+ response.body.should include(@item.name)
14
+ end
15
+
16
+ it "all :#{format}" do
17
+ @item = Factory.create @model_name
18
+ call :all, format: format
19
+ response.should be_ok
20
+ response.body.should include(@item.name)
21
+ end
22
+ end
23
+
24
+ it "edit :js" do
25
+ item = Factory.create @model_name
26
+ call :edit, id: item.to_param, format: 'js'
27
+ response.should be_ok
28
+ end
29
+
30
+ %w{js json}.each do |format|
31
+ it "new :#{format}" do
32
+ call :new, format: format
33
+ response.should be_ok
34
+ end
35
+
36
+ it "create :#{format}" do
37
+ item_attributes = Factory.attributes_for @model_name
38
+ pcall :create, model: item_attributes, format: format
39
+
40
+ (format == 'js') ? response.body.should(include('window.location')) : response.should(be_ok)
41
+ @model_class.count.should == 1
42
+ item = @model_class.first
43
+ item.name.should == item_attributes[:name]
44
+ end
45
+
46
+ it "update :#{format}" do
47
+ item = Factory.create @model_name
48
+ pcall :update, id: item.to_param, model: {name: 'new_name'}, format: format
49
+
50
+ response.should be_ok
51
+ response.body.should =~ /new_name|reload/ if format == 'js'
52
+
53
+ item.reload
54
+ item.name.should == 'new_name'
55
+ end
56
+
57
+ it "destroy :#{format}" do
58
+ item = Factory.create @model_name
59
+ pcall :destroy, id: item.to_param, format: format
60
+ response.should be_ok
61
+ @model_class.count.should == 0
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,14 @@
1
+ require 'kit/gems'
2
+ require 'stringex'
3
+
4
+ # SaaS
5
+ unless ENV['saas'] == 'false'
6
+ begin
7
+ require 'saas_extensions'
8
+ rescue LoadError
9
+ end
10
+ end
11
+
12
+ %w(
13
+ string
14
+ ).each{|f| require "kit/support/#{f}"}
@@ -0,0 +1,6 @@
1
+ class String
2
+ def to_url_with_escape
3
+ to_url_without_escape.gsub /[^a-z0-9_-]/, ''
4
+ end
5
+ alias_method_chain :to_url, :escape
6
+ end
data/lib/kit/tasks.rb ADDED
@@ -0,0 +1,18 @@
1
+ require 'rake_ext'
2
+
3
+ require 'rad_ext/tasks'
4
+ require 'rad/assets/tasks'
5
+ require 'mongo_migration/tasks'
6
+
7
+ namespace :db do
8
+ desc "Internal task to prepare migration environment"
9
+ task migration_evnironment: :environment do
10
+ require 'mongo_migration'
11
+ require 'mongo_migration/adapters/mongoid'
12
+
13
+ adapter = Mongo::Migration::Mongoid.new
14
+ Mongo.migration = Mongo::Migration.new adapter
15
+
16
+ Dir["#{rad.runtime_path}/db/**/*.rb"].each{|f| require f.sub(/\.rb$/, '')}
17
+ end
18
+ end
@@ -0,0 +1,43 @@
1
+ require 'digest/md5'
2
+
3
+ require 'nokogiri'
4
+ require 'bluecloth'
5
+ require 'sanitize'
6
+
7
+ module Rad::TextUtils; end
8
+
9
+ %w(
10
+ processor
11
+
12
+ github_flavoured_markdown
13
+ image_box
14
+ custom_markdown
15
+ urls
16
+ tag_shortcuts
17
+ html_sanitizer
18
+ ensure_utf
19
+ truncate
20
+ code_highlighter
21
+ truncator
22
+
23
+ markup
24
+ ).each{|f| require "kit/text_utils/#{f}"}
25
+
26
+ module Rad::TextUtils
27
+ class << self
28
+ def markup text
29
+ Rad::TextUtils::Markup.new.process text, {}
30
+ end
31
+
32
+ def random_string length = 3
33
+ @digits ||= ('a'..'z').to_a + (0..9).to_a
34
+ (0..(length-1)).map{@digits[rand(@digits.size)]}.join
35
+ end
36
+
37
+ def truncate str_or_html, length
38
+ Rad::TextUtils::Truncator.new(nil, length).process str_or_html, {}
39
+ # str_or_html = HtmlSanitizer.new.process str_or_html, {}
40
+ # Truncator.new(length).process str_or_html
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,58 @@
1
+ class Rad::TextUtils::CodeHighlighter < Rad::TextUtils::Processor
2
+
3
+ # highlights code inside of <code lang/language='java'> ... code ... </code>
4
+ def process html, env
5
+ require 'coderay'
6
+ require 'nokogiri'
7
+
8
+ snippets, unique_id = {}, 0
9
+
10
+ # Highlighting
11
+ html = html.gsub /(<code.*?>)(.+?)(<\/code\s*>)/im do
12
+ begin
13
+ node = Nokogiri::HTML($1 + $3).css('code').first
14
+ language = node.attributes['lang'].try(:value) || node.attributes['language'].try(:value)
15
+ code = $2
16
+
17
+ if language.present? and code.present?
18
+ attributes = []
19
+ node.attributes.each do |name, value|
20
+ attributes << %(#{name}="#{value.value}")
21
+ end
22
+
23
+ code = CodeRay.scan(code, language.downcase.to_sym).div :css => :class
24
+
25
+ snippet = "<code #{attributes.join(' ')}>\n#{code}\n</code>"
26
+
27
+ # adding prefix 'hl_' to all CodeRay classes
28
+ node = Nokogiri::HTML(snippet).css('code').first
29
+ node.css("*").each do |e|
30
+ classes = e.attribute 'class'
31
+ if classes.present? and classes.value.present?
32
+ classes = classes.value.strip.split(/\s+/).collect{|c| "hl_#{c}"}.join(' ')
33
+ e['class'] = classes
34
+ end
35
+ end
36
+ snippet = node.to_s
37
+ end
38
+ # rescue StandardError => e
39
+ # rad.logger.warn "CodeHighlighter: #{e.message}"
40
+ end
41
+
42
+ # temporarilly removing all highlighted code from html to prevent it's beed damaged by next processors
43
+ unique_id += 1
44
+ id = "CODE#{unique_id}CODE"
45
+ snippets[id] = snippet
46
+ id
47
+ end
48
+
49
+ html = call_next html, env
50
+
51
+ # inserting highlighted code back to html
52
+ html = html.gsub /(CODE[0-9]+CODE)/ do |id|
53
+ snippets[id]
54
+ end
55
+
56
+ html
57
+ end
58
+ end
@@ -0,0 +1,90 @@
1
+ class Rad::TextUtils::CustomMarkdown < Rad::TextUtils::Processor
2
+ def process markdown, env
3
+ markdown = github_flavoured_markdown markdown
4
+ markdown = image_box markdown
5
+ text = custom_markdown markdown
6
+
7
+ call_next text, env
8
+ end
9
+
10
+ protected
11
+ def custom_markdown markdown
12
+ # BlueCloth doesn't apply inside of HTML tags, so we temporarilly replacing it
13
+ markdown = markdown.gsub('<', 'HTML_BEGIN').gsub('>', 'HTML_END')
14
+
15
+ markdown = markdown.gsub(" \n", "<br/>\n")
16
+
17
+ text = BlueCloth.new(markdown).to_html
18
+
19
+ text = text.gsub(/\A<.+?>/){"#{$&} "}.gsub(/<\/.+?>\Z/){" #{$&}"}
20
+
21
+ text = text.gsub('HTML_BEGIN', '<').gsub('HTML_END', '>')
22
+
23
+ text.gsub(/[\n]+/, "\n")
24
+ end
25
+
26
+ def github_flavoured_markdown markdown
27
+ # Extract pre blocks
28
+ extractions = {}
29
+ markdown.gsub!(%r{<pre>.*?</pre>}m) do |match|
30
+ md5 = Digest::MD5.hexdigest(match)
31
+ extractions[md5] = match
32
+ "{gfm-extraction-#{md5}}"
33
+ end
34
+
35
+ # prevent foo_bar_baz from ending up with an italic word in the middle
36
+ markdown.gsub!(/(^(?! {4}|\t)\w+_\w+_\w[\w_]*)/) do |x|
37
+ x.gsub('_', '\_') if x.split('').sort.to_s[0..1] == '__'
38
+ end
39
+
40
+ # in very clear cases, let newlines become <br /> tags
41
+ markdown.gsub!(/^[\w\<\!][^\n]*\n+/) do |x|
42
+ if x =~ /\>[\n\r]*/
43
+ x
44
+ else
45
+ x =~ /\n{2}/ ? x : (x.strip!; x << " \n")
46
+ end
47
+ end
48
+
49
+ # Insert pre block extractions
50
+ markdown.gsub!(/\{gfm-extraction-([0-9a-f]{32})\}/) do
51
+ "\n\n" + extractions[$1]
52
+ end
53
+
54
+ markdown
55
+ end
56
+
57
+ # !![img] => [![img_thumb]][img]
58
+ def image_box markdown
59
+ img_urls = {}
60
+ markdown = markdown.gsub(/!!\[(.+?)\]/) do
61
+ img_key = $1 || ''
62
+ if url = markdown.scan(/\[#{img_key}\]:\s*([^\s]+)$/).first.try(:first)
63
+ unless url =~ /\.[^\.]+\.[^\.]+$/ # image.png
64
+ thumb_img_key = "#{img_key}_thumb"
65
+
66
+ # building url with thumb (foo.png => foo.thumb.png)
67
+ img_urls[thumb_img_key] = url.sub(/\.([^\.]+)$/){".thumb.#{$1}"}
68
+
69
+ "[![][#{thumb_img_key}]][#{img_key}]"
70
+ else # image.(icon|thumb|...).png
71
+ img_key_full = "#{img_key}_full"
72
+
73
+ # building url with thumb (foo.png => foo.thumb.png)
74
+ img_urls[img_key_full] = url.sub(/\.([^\.]+)\.([^\.]+)$/){".#{$2}"}
75
+
76
+ "[![][#{img_key}]][#{img_key_full}]"
77
+ end
78
+ else
79
+ $& || ''
80
+ end
81
+ end
82
+
83
+ unless img_urls.blank?
84
+ markdown << "\n"
85
+ markdown << img_urls.to_a.collect{|k, v| "[#{k}]: #{v}"}.join("\n")
86
+ end
87
+
88
+ markdown
89
+ end
90
+ end
@@ -0,0 +1,8 @@
1
+ class Rad::TextUtils::EnsureUtf < Rad::TextUtils::Processor
2
+ def process data, env
3
+ data = call_next data, env
4
+
5
+ # Escape all non-word unicode symbols, otherwise it will raise error when converting to BSON
6
+ Iconv.conv('UTF-8//IGNORE//TRANSLIT', 'UTF-8', data)
7
+ end
8
+ end
@@ -0,0 +1,32 @@
1
+ class Rad::TextUtils::GithubFlavouredMarkdown < Rad::TextUtils::Processor
2
+ def process markdown, env
3
+ # Extract pre blocks
4
+ extractions = {}
5
+ markdown.gsub!(%r{<pre>.*?</pre>}m) do |match|
6
+ md5 = Digest::MD5.hexdigest(match)
7
+ extractions[md5] = match
8
+ "{gfm-extraction-#{md5}}"
9
+ end
10
+
11
+ # prevent foo_bar_baz from ending up with an italic word in the middle
12
+ markdown.gsub!(/(^(?! {4}|\t)\w+_\w+_\w[\w_]*)/) do |x|
13
+ x.gsub('_', '\_') if x.split('').sort.to_s[0..1] == '__'
14
+ end
15
+
16
+ # in very clear cases, let newlines become <br /> tags
17
+ markdown.gsub!(/^[\w\<\!][^\n]*\n+/) do |x|
18
+ if x =~ /\>[\n\r]*/
19
+ x
20
+ else
21
+ x =~ /\n{2}/ ? x : (x.strip!; x << " \n")
22
+ end
23
+ end
24
+
25
+ # Insert pre block extractions
26
+ markdown.gsub!(/\{gfm-extraction-([0-9a-f]{32})\}/) do
27
+ "\n\n" + extractions[$1]
28
+ end
29
+
30
+ call_next markdown, env
31
+ end
32
+ end
@@ -0,0 +1,89 @@
1
+ class Rad::TextUtils::HtmlSanitizer < Rad::TextUtils::Processor
2
+ RELAXED = {
3
+ elements: [
4
+ 'a', 'b', 'blockquote', 'br', 'caption', 'cite', 'code', 'col',
5
+ 'colgroup', 'dd', 'dl', 'dt', 'em', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
6
+ 'i', 'img', 'li', 'ol', 'p', 'pre', 'q', 'small', 'strike', 'strong',
7
+ 'sub', 'sup', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead', 'tr', 'u',
8
+ 'ul', 'div', 'font', 'span'],
9
+
10
+ attributes: {
11
+ :all => ['class', 'style'],
12
+ 'a' => ['href', 'title', 'rel'],
13
+ 'blockquote' => ['cite'],
14
+ 'col' => ['span', 'width'],
15
+ 'colgroup' => ['span', 'width'],
16
+ 'img' => ['align', 'alt', 'height', 'src', 'title', 'width'],
17
+ 'ol' => ['start', 'type'],
18
+ 'q' => ['cite'],
19
+ 'table' => ['summary', 'width'],
20
+ 'td' => ['abbr', 'axis', 'colspan', 'rowspan', 'width'],
21
+ 'th' => ['abbr', 'axis', 'colspan', 'rowspan', 'scope', 'width'],
22
+ 'ul' => ['type'],
23
+ 'code' => ['lang', 'language']
24
+ },
25
+
26
+ protocols: {
27
+ 'a' => {'href' => ['ftp', 'http', 'https', 'mailto', :relative]},
28
+ 'blockquote' => {'cite' => ['http', 'https', :relative]},
29
+ 'img' => {'src' => ['http', 'https', :relative]},
30
+ 'q' => {'cite' => ['http', 'https', :relative]}
31
+ }
32
+ }
33
+
34
+ VIDEO_URLS = [
35
+ /^http:\/\/(?:www\.)?youtube\.com\/v\//,
36
+ ]
37
+
38
+ EMBEDDED_VIDEO = lambda do |env|
39
+ node = env[:node]
40
+ node_name = node.name.to_s.downcase
41
+ parent = node.parent
42
+
43
+ # Since the transformer receives the deepest nodes first, we look for a
44
+ # <param> element or an <embed> element whose parent is an <object>.
45
+ return nil unless (node_name == 'param' || node_name == 'embed') && parent.name.to_s.downcase == 'object'
46
+
47
+ if node_name == 'param'
48
+ # Quick XPath search to find the <param> node that contains the video URL.
49
+ return nil unless movie_node = parent.search('param[@name="movie"]')[0]
50
+ url = movie_node['value']
51
+ else
52
+ # Since this is an <embed>, the video URL is in the "src" attribute. No
53
+ # extra work needed.
54
+ url = node['src']
55
+ end
56
+
57
+ # # Verify that the video URL is actually a valid YouTube video URL.
58
+ return nil unless VIDEO_URLS.any?{|t| url =~ t}
59
+
60
+ # # We're now certain that this is a YouTube embed, but we still need to run
61
+ # # it through a special Sanitize step to ensure that no unwanted elements or
62
+ # # attributes that don't belong in a YouTube embed can sneak in.
63
+ Sanitize.clean_node!(parent, {
64
+ :elements => ['embed', 'object', 'param'],
65
+ attributes: {
66
+ 'embed' => ['allowfullscreen', 'allowscriptaccess', 'height', 'src', 'type', 'width'],
67
+ 'object' => ['height', 'width'],
68
+ 'param' => ['name', 'value']
69
+ }
70
+ })
71
+
72
+ # Now that we're sure that this is a valid YouTube embed and that there are
73
+ # no unwanted elements or attributes hidden inside it, we can tell Sanitize
74
+ # to whitelist the current node (<param> or <embed>) and its parent
75
+ # (<object>).
76
+ {:whitelist_nodes => [node, parent]}
77
+ end
78
+
79
+ def process html, env
80
+ html = call_next html, env
81
+
82
+ Sanitize.clean(html, RELAXED.merge(
83
+ transformers: [EMBEDDED_VIDEO],
84
+ :add_attributes => {
85
+ all: [:class]
86
+ }
87
+ ))
88
+ end
89
+ end