rad_kit 0.0.1

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 (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