abstract_interface 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile ADDED
@@ -0,0 +1,64 @@
1
+ require 'rake'
2
+ require 'fileutils'
3
+ current_dir = File.expand_path(File.dirname(__FILE__))
4
+ Dir.chdir current_dir
5
+
6
+
7
+ #
8
+ # Specs
9
+ #
10
+ require 'spec/rake/spectask'
11
+
12
+ task :default => :spec
13
+
14
+ Spec::Rake::SpecTask.new('spec') do |t|
15
+ t.spec_files = FileList["spec/**/*_spec.rb"].select{|f| f !~ /\/_/}
16
+ t.libs = ["#{current_dir}/lib"]
17
+ end
18
+
19
+
20
+ #
21
+ # Gem
22
+ #
23
+ require 'rake/clean'
24
+ require 'rake/gempackagetask'
25
+
26
+ gem_options = {
27
+ :name => "abstract_interface",
28
+ :version => "0.1.0",
29
+ :summary => "Ruby language extensions",
30
+ :dependencies => %w(facets)
31
+ }
32
+
33
+ spec = Gem::Specification.new do |s|
34
+ gem_options.delete(:dependencies).each{|d| s.add_dependency d}
35
+ gem_options.each{|k, v| s.send "#{k}=", v}
36
+
37
+ s.author = "Alexey Petrushin"
38
+ s.homepage = "http://github.com/alexeypetrushin/#{gem_options[:name]}"
39
+ s.require_path = "lib"
40
+ s.files = (%w{Rakefile readme.md} + Dir.glob("{lib,spec}/**/*"))
41
+
42
+ s.platform = Gem::Platform::RUBY
43
+ s.has_rdoc = true
44
+ end
45
+
46
+ package_dir = "#{current_dir}/build"
47
+ Rake::GemPackageTask.new(spec) do |p|
48
+ p.need_tar = true if RUBY_PLATFORM !~ /mswin/
49
+ p.need_zip = true
50
+ p.package_dir = package_dir
51
+ end
52
+
53
+ task :push do
54
+ # dir = Dir.chdir package_dir do
55
+ gem_file = Dir.glob("#{package_dir}/#{gem_options[:name]}*.gem").first
56
+ system "gem push #{gem_file}"
57
+ # end
58
+ end
59
+
60
+ task :clean do
61
+ system "rm -r #{package_dir}"
62
+ end
63
+
64
+ task :release => [:gem, :push, :clean]
@@ -0,0 +1,64 @@
1
+ module AbstractInterface
2
+ class << self
3
+ inject :logger => :logger, :config => :config, :environment => :environment
4
+
5
+ attr_accessor :plugin_name
6
+ attr_accessor :layout_configurations_dir
7
+
8
+ def generate_helper_methods *args
9
+ AbstractInterface::ViewBuilder.generate_helper_methods *args
10
+ end
11
+
12
+ def available_themes; @available_themes ||= [] end
13
+
14
+ def theme_metadata theme
15
+ logger.warn "Complex calculation (AbstractInterface.theme_metadata) called in production!" if config.production?
16
+
17
+ metadata = {}
18
+
19
+ name = "/#{Crystal::Template::DIRECTORY_NAME}/#{THEMES_DIR}/#{theme}/metadata.rb"
20
+ if environment.file_exist? name
21
+ fname = environment.find_file name
22
+ code = File.read fname
23
+ metadata = eval code
24
+ metadata.must_be.a Hash
25
+ end
26
+
27
+ metadata.to_openobject
28
+ end
29
+
30
+ def layouts_defined?
31
+ !!layout_configurations_dir
32
+ end
33
+
34
+ # Place definitions of your layouts into :layout_configurations_dir folder, you can have multiple such directories
35
+ def layout_definitions_without_cache theme
36
+ name = "#{layout_configurations_dir.must_be.present}/#{theme}.yml"
37
+ raise "File '#{name}' not exist!" unless environment.file_exist? name
38
+
39
+ result = {}
40
+ environment.find_files(name).each do |fname|
41
+ lds = YAML.load_file(fname)
42
+ validate_layout_definition!(lds, theme)
43
+ result.merge! lds
44
+ end
45
+ result
46
+ end
47
+ cache_method_with_params_in_production :layout_definitions
48
+
49
+ protected
50
+ def validate_layout_definition! lds, theme
51
+ lds.must_be.a Hash
52
+ unless lds.include?('default')
53
+ raise "No 'default' layout definition for '#{theme}' Theme (there always should be definition for 'default' layout)!"
54
+ end
55
+ lds.each do |theme_name, ld|
56
+ ld.must_be.a Hash
57
+ ld.must.include 'layout_template'
58
+ ld.must.include 'slots'
59
+ ld['slots'].must_be.a Hash
60
+ end
61
+ end
62
+ end
63
+
64
+ end
@@ -0,0 +1,24 @@
1
+ module AbstractInterface
2
+ module ControllerHelper
3
+ def current_theme
4
+ @current_theme ||= AbstractInterface::Theme.new
5
+ end
6
+
7
+ # TODO1
8
+ # def build_layout layout = nil
9
+ # # Configuring
10
+ # current_theme.layout = layout
11
+ #
12
+ # # Rendering
13
+ # current_theme.layout_definition['slots'].each do |slot_name, slots|
14
+ # slots = Array(slots)
15
+ # slots.each do |partial|
16
+ # content_for slot_name do
17
+ # render :partial => partial
18
+ # end
19
+ # end
20
+ # end
21
+ # end
22
+
23
+ end
24
+ end
@@ -0,0 +1,56 @@
1
+ module AbstractInterface
2
+ class HamlBuilder < BasicObject
3
+ def initialize template, hash = OpenObject.new
4
+ @template = template
5
+ @hash, @array = hash, []
6
+ end
7
+
8
+ def method_missing m, value = nil, &block
9
+ @hash[m] = HamlBuilder.get_input @template, value, &block
10
+ nil
11
+ end
12
+
13
+ def add value = nil, &block
14
+ @array << HamlBuilder.get_input(@template, value, &block)
15
+ nil
16
+ end
17
+
18
+ # def add_item content, opt = {}, &block
19
+ # opt[:content] = content
20
+ # opt[:content] ||= @template.capture &block if block
21
+ # add opt
22
+ # end
23
+
24
+ def get_value
25
+ !@array.empty? ? @array : @hash
26
+ end
27
+
28
+ def self.get_input template, value, &block
29
+ value = value.to_openobject if value.is_a? Hash
30
+
31
+ block_value = if block
32
+ if block.arity <= 0
33
+ template.must_be.defined
34
+ template.capture &block
35
+ else
36
+ b = HamlBuilder.new template
37
+ block.call b
38
+ b.get_value
39
+ end
40
+ else
41
+ nil
42
+ end
43
+
44
+ if value and block_value
45
+ if block_value.is_a? Hash
46
+ value = value.merge block_value
47
+ else
48
+ raise "Invalid usage!" if value.include? :content
49
+ value.content = block_value
50
+ end
51
+ end
52
+
53
+ value || block_value
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,40 @@
1
+ # content_or_self
2
+ [Hash, OpenObject].each do |aclass|
3
+ aclass.class_eval do
4
+ def hash?; true end
5
+ end
6
+ end
7
+
8
+ NilClass.class_eval do
9
+ def content; "" end
10
+ def hash?; false end
11
+ end
12
+
13
+ String.class_eval do
14
+ def content; self end
15
+ def hash?; false end
16
+ end
17
+
18
+ # OpenObject
19
+ OpenObject.class_eval do
20
+ HTML_ATTRIBUTES = [:id, :class]
21
+
22
+ def merge_html_attributes hash
23
+ # html attributes
24
+ result = {}
25
+ HTML_ATTRIBUTES.each{|k| result[k.to_s] = self[k] if include? k}
26
+ html_attributes.each{|k, v| result[k.to_s] = v} if html_attributes?
27
+
28
+ # merging html attributes with hash
29
+ hash.each do |k, v|
30
+ k = k.to_s
31
+ if result.include?(k) and v.is_a?(String)
32
+ string = result[k].must_be.a [Symbol, String]
33
+ result[k] = "#{result[k]}#{v}"
34
+ else
35
+ result[k] = v
36
+ end
37
+ end
38
+ result
39
+ end
40
+ end
@@ -0,0 +1,37 @@
1
+ module AbstractInterface
2
+ class Theme
3
+ attr_writer :name, :layout_template, :layout
4
+ def name; @name || 'default' end
5
+ def layout; @layout || 'default' end
6
+
7
+ def layout_template
8
+ if @layout_template
9
+ # Check if this template exists
10
+ exists = layout_definitions.any?{|layout_name, ld| ld['layout_template'] == @layout_template}
11
+ exists ? @layout_template : 'default'
12
+ else
13
+ layout_definition['layout_template'] || 'default'
14
+ end
15
+ end
16
+
17
+ def layout_definition
18
+ layout_definitions[layout] || layout_definitions['default'] || {}
19
+ end
20
+
21
+ def layout_definitions
22
+ if AbstractInterface.layouts_defined?
23
+ AbstractInterface.layout_definitions(name)
24
+ else
25
+ {}
26
+ end
27
+ end
28
+
29
+ def available_layouts_names
30
+ layout_definitions.keys
31
+ end
32
+
33
+ def metadata
34
+ AbstractInterface.theme_metadata(name)
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,74 @@
1
+ module AbstractInterface
2
+ class ThemedFormHelper
3
+ attr_accessor :template
4
+
5
+ def initialize template
6
+ self.template = template
7
+ end
8
+
9
+ def error_messages *errors
10
+ errors = errors.first if errors.size == 1 and errors.first.is_a? Array
11
+ template.render template.themed_partial('forms/errors'), :object => errors
12
+ end
13
+
14
+ def form_field options, &block
15
+ html_options = options.to_openobject
16
+ options = OpenObject.new
17
+
18
+ # prepare options
19
+ %w(errors label description required theme).each do |k|
20
+ v = html_options.delete k
21
+ options[k] = v unless v.nil?
22
+ end
23
+ options.errors = options.errors.to_a
24
+
25
+ # CSS style
26
+ html_options.class ||= ""
27
+ html_options << " themed_input"
28
+
29
+ options.content = template.capture{block.call(html_options)}
30
+
31
+ html = template.render(template.themed_partial('forms/field'), :object => options)
32
+ template.concat html
33
+ end
34
+
35
+ def line *items
36
+ template.render template.themed_partial('forms/line'), :object => {:items => items, :delimiter => false}.to_openobject
37
+ end
38
+
39
+ def line_with_delimiters *items
40
+ template.render template.themed_partial('forms/line'), :object => {:items => items, :delimiter => true}.to_openobject
41
+ end
42
+
43
+
44
+ #
45
+ # Form fields
46
+ #
47
+ %w(
48
+ check_box_tag
49
+ field_set_tag
50
+ file_field_tag
51
+ password_field_tag
52
+ radio_button_tag
53
+ select_tag
54
+ text_field_tag
55
+ text_area_tag
56
+ ).each do |m|
57
+ define_method m do |*args|
58
+ options = args.extract_options!
59
+ template.capture do
60
+ form_field options do |html_options|
61
+ args << html_options
62
+ template.concat(template.send(m, *args))
63
+ end
64
+ end
65
+ end
66
+ end
67
+
68
+ %w(
69
+ hidden_field_tag
70
+ submit_tag
71
+ ).each{|m| delegate m, :to => :template}
72
+
73
+ end
74
+ end
@@ -0,0 +1,100 @@
1
+ module AbstractInterface
2
+ class ViewBuilder
3
+
4
+ def self.generate_helper_methods methods
5
+ methods.each do |folder, templates|
6
+ templates.each do |template|
7
+ code = %{\
8
+ def #{template} *args, &block
9
+ render_haml_builder "#{folder}", "#{template}", *args, &block
10
+ end}
11
+
12
+ eval code, binding, __FILE__, __LINE__
13
+ end
14
+ end
15
+ end
16
+
17
+
18
+ attr_reader :template
19
+ def initialize template
20
+ @template = template
21
+ end
22
+
23
+
24
+ #
25
+ # Template methods
26
+ #
27
+ %w(
28
+ capture
29
+ concat
30
+ content_for
31
+ tag
32
+ render
33
+ themed_resource
34
+ themed_partial
35
+ controller
36
+ ).each do |m|
37
+ delegate m, :to => :template
38
+ end
39
+
40
+
41
+ #
42
+ # Builders
43
+ #
44
+ def options *args, &block
45
+ opt = args.extract_options!
46
+ args.size.must_be.in 0..1
47
+ opt[:content] = args.first if args.size == 1
48
+
49
+ AbstractInterface::HamlBuilder.get_input self.template, opt, &block
50
+ end
51
+
52
+
53
+ #
54
+ # Forms
55
+ #
56
+ def form_tag *args, &block
57
+ f = ThemedFormHelper.new(template)
58
+
59
+ content = block ? capture{block.call(f)} : ""
60
+ html = render(
61
+ themed_partial('forms/form'),
62
+ :object => {:form_attributes => options, :content => content}.to_openobject
63
+ )
64
+
65
+ if block
66
+ template.concat html
67
+ else
68
+ html
69
+ end
70
+ end
71
+
72
+ def form_for *args, &block
73
+ model_helper, options = template.build_form_model_helper_and_form_options *args
74
+
75
+ form_tag options do |themed_form_helper|
76
+ model_helper.form_helper = themed_form_helper
77
+
78
+ block.call model_helper if block
79
+ end
80
+ end
81
+
82
+ private
83
+
84
+ def render_haml_builder folder, template, *args, &block
85
+ opt = options *args, &block
86
+
87
+ partial = "#{folder}/#{template}"
88
+
89
+ html = render themed_partial(partial), :object => opt
90
+
91
+ block ? self.concat(html) : html
92
+ end
93
+
94
+ def prepare_form! options, *args
95
+ buff = template.form_tag *args
96
+ options[:begin] = buff
97
+ options[:end] = '</form>'
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,42 @@
1
+ module AbstractInterface
2
+ module ViewHelper
3
+ def b
4
+ @b ||= AbstractInterface::ViewBuilder.new self
5
+ end
6
+ alias_method :builder, :b
7
+
8
+ def themed_resource resource
9
+ "/#{AbstractInterface.plugin_name.must_not_be.blank}/#{THEMES_DIR}/#{current_theme.name}/#{resource}"
10
+ end
11
+
12
+ def themed_partial partial
13
+ themed_partial = "/#{THEMES_DIR}/#{current_theme.name}/#{partial}"
14
+ if Crystal::Template.exist? themed_partial
15
+ themed_partial
16
+ else
17
+ "/#{THEMES_DIR}/default/#{partial}"
18
+ end
19
+ end
20
+
21
+ def current_theme
22
+ controller.current_theme
23
+ end
24
+
25
+ # TODO1
26
+ # def build_layout layout = nil
27
+ # # Configuring
28
+ # current_theme.layout = layout
29
+ #
30
+ # # Rendering
31
+ # current_theme.layout_definition['slots'].each do |slot_name, slots|
32
+ # slots = Array(slots)
33
+ # slots.each do |partial|
34
+ # content_for slot_name do
35
+ # render :partial => partial
36
+ # end
37
+ # end
38
+ # end
39
+ # end
40
+
41
+ end
42
+ end
@@ -0,0 +1,25 @@
1
+ require 'abstract_interface/support'
2
+
3
+ module AbstractInterface
4
+ THEMES_DIR = 'themes'
5
+
6
+ autoload :Theme, 'abstract_interface/theme'
7
+ autoload :HamlBuilder, 'abstract_interface/haml_builder'
8
+ autoload :ThemedFormHelper, 'abstract_interface/themed_form_helper'
9
+ autoload :ViewBuilder, 'abstract_interface/view_builder'
10
+ autoload :ViewHelper, 'abstract_interface/view_helper'
11
+ autoload :ControllerHelper, 'abstract_interface/controller_helper'
12
+ end
13
+
14
+ require 'abstract_interface/abstract_interface'
15
+
16
+ Crystal::ControllerContext.inherit AbstractInterface::ViewHelper
17
+ Crystal::AbstractController.inherit AbstractInterface::ControllerHelper
18
+
19
+ # TODO2
20
+ # ActionView::Base.field_error_proc = lambda do |html_tag, instance|
21
+ # html_tag
22
+ # end
23
+
24
+ # TODO1
25
+ # Rails.development{RailsExt.create_public_symlinks!} # rails_ext.css, rails_ext.js in development mode
data/readme.md ADDED
@@ -0,0 +1,3 @@
1
+ # Abstract Interface for the Crystal Framework
2
+
3
+ Tool for rapid interface creation, [add more]
@@ -0,0 +1,121 @@
1
+ dir = File.expand_path(File.dirname(__FILE__))
2
+ require "#{dir}/helper"
3
+
4
+ #
5
+ # Don't use should ==, it doesn't works with OpenObject
6
+ #
7
+ describe "HamlBuilder use cases" do
8
+ class TemplateStub
9
+ def self.capture &block
10
+ block.call
11
+ self.output
12
+ end
13
+
14
+ class << self
15
+ attr_accessor :output
16
+ end
17
+ end
18
+
19
+ def build *args, &block
20
+ opt = args.extract_options!
21
+ args.size.must_be.in 0..1
22
+ opt[:content] = args.first if args.size == 1
23
+
24
+ AbstractInterface::HamlBuilder.get_input(TemplateStub, opt, &block)
25
+ end
26
+
27
+ it "should accept OpenObject as input" do
28
+ build({:a => :b}.to_openobject).should == {:a => :b}
29
+ end
30
+
31
+ it "hash" do
32
+ (build do |o|
33
+ o.a :b
34
+ end).should == {:a => :b}
35
+
36
+ build(:a => :b).should == {:a => :b}
37
+ end
38
+
39
+ it "array" do
40
+ (build do |a|
41
+ a.add 1
42
+ a.add 2
43
+ end).should == {:content => [1, 2]}
44
+
45
+ (build do |o|
46
+ o.a :b
47
+ o.ar do |a|
48
+ a.add 1
49
+ a.add 2
50
+ end
51
+ end).should == {:a => :b, :ar => [1, 2]}
52
+ end
53
+
54
+ it "capture" do
55
+ build("value").should == {:content => "value"}
56
+
57
+ (build do
58
+ TemplateStub.output = "value"
59
+ end).should == {:content => "value"}
60
+
61
+ (build do |o|
62
+ o.content do
63
+ TemplateStub.output = "value"
64
+ end
65
+ end).should == {:content => "value"}
66
+
67
+ (build do |o|
68
+ o.value do
69
+ TemplateStub.output = "value"
70
+ end
71
+ end).should == {:value => "value"}
72
+ end
73
+
74
+ it "invalid usage" do
75
+ lambda{
76
+ build "value" do
77
+ TemplateStub.output = "value"
78
+ end
79
+ }.should raise_error(/Invalid usage!/)
80
+ end
81
+
82
+ it "merge" do
83
+ (build :a => :b do
84
+ TemplateStub.output = "value"
85
+ end).should == {:a => :b, :content => "value"}
86
+
87
+ (build :a => :b do |o|
88
+ o.c :d
89
+ end).should == {:a => :b, :c => :d}
90
+ end
91
+
92
+ it "nested" do
93
+ (build :a => :b do |o|
94
+ o.a do |o|
95
+ o.b :c
96
+ end
97
+ end).should == {
98
+ :a => {:b => :c}
99
+ }
100
+ end
101
+
102
+ it "complex" do
103
+ (build :a => :b do |o|
104
+ o.hs do |h|
105
+ h.c :d
106
+ end
107
+ o.ar do |a|
108
+ a.add 1
109
+ a.add 2
110
+ end
111
+ o.html do
112
+ TemplateStub.output = "value"
113
+ end
114
+ end).should == {
115
+ :a => :b,
116
+ :hs => {:c => :d},
117
+ :ar => [1, 2],
118
+ :html => 'value'
119
+ }
120
+ end
121
+ end
data/spec/helper.rb ADDED
@@ -0,0 +1,9 @@
1
+ dir = File.expand_path(File.dirname(__FILE__))
2
+ lib_dir = File.expand_path("#{dir}/../../../lib")
3
+ $LOAD_PATH << lib_dir unless $LOAD_PATH.include? lib_dir
4
+
5
+ require 'crystal/support'
6
+ require 'abstract_interface/support'
7
+ require "abstract_interface/haml_builder"
8
+
9
+ require 'spec_ext'
data/spec/spec.opts ADDED
@@ -0,0 +1,4 @@
1
+ --colour
2
+ --format progress
3
+ --loadby mtime
4
+ --reverse
metadata ADDED
@@ -0,0 +1,93 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: abstract_interface
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
+ - Alexey Petrushin
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-10-11 00:00:00 +04:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: facets
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 3
30
+ segments:
31
+ - 0
32
+ version: "0"
33
+ type: :runtime
34
+ version_requirements: *id001
35
+ description:
36
+ email:
37
+ executables: []
38
+
39
+ extensions: []
40
+
41
+ extra_rdoc_files: []
42
+
43
+ files:
44
+ - Rakefile
45
+ - readme.md
46
+ - lib/abstract_interface/abstract_interface.rb
47
+ - lib/abstract_interface/controller_helper.rb
48
+ - lib/abstract_interface/haml_builder.rb
49
+ - lib/abstract_interface/support.rb
50
+ - lib/abstract_interface/theme.rb
51
+ - lib/abstract_interface/themed_form_helper.rb
52
+ - lib/abstract_interface/view_builder.rb
53
+ - lib/abstract_interface/view_helper.rb
54
+ - lib/abstract_interface.rb
55
+ - spec/haml_builder_spec.rb
56
+ - spec/helper.rb
57
+ - spec/spec.opts
58
+ has_rdoc: true
59
+ homepage: http://github.com/alexeypetrushin/abstract_interface
60
+ licenses: []
61
+
62
+ post_install_message:
63
+ rdoc_options: []
64
+
65
+ require_paths:
66
+ - lib
67
+ required_ruby_version: !ruby/object:Gem::Requirement
68
+ none: false
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ hash: 3
73
+ segments:
74
+ - 0
75
+ version: "0"
76
+ required_rubygems_version: !ruby/object:Gem::Requirement
77
+ none: false
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ hash: 3
82
+ segments:
83
+ - 0
84
+ version: "0"
85
+ requirements: []
86
+
87
+ rubyforge_project:
88
+ rubygems_version: 1.3.7
89
+ signing_key:
90
+ specification_version: 3
91
+ summary: Ruby language extensions
92
+ test_files: []
93
+