├── .gitignore ├── .travis.yml ├── LICENSE ├── Projectfile ├── README.md ├── shard.lock ├── shard.yml ├── spec ├── app │ └── views │ │ ├── flash.ecr │ │ ├── layout.ecr │ │ ├── test.ecr │ │ └── test_data.ecr ├── base_spec.cr ├── cache_spec.cr ├── controller_spec.cr ├── response_spec.cr ├── route_spec.cr ├── sessions_spec.cr ├── spec_helper.cr └── view │ ├── base_view_spec.cr │ └── view_tag_spec.cr └── src ├── amatista.cr └── amatista ├── base.cr ├── controller.cr ├── filter.cr ├── flash.cr ├── handler.cr ├── helpers.cr ├── model.cr ├── response.cr ├── route.cr ├── sessions.cr ├── version.cr └── view ├── base_view.cr ├── view_helpers.cr └── view_tag.cr /.gitignore: -------------------------------------------------------------------------------- 1 | .crystal/ 2 | .deps/* 3 | .deps.lock 4 | libs/* 5 | .shards/* 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: crystal 2 | 3 | before_install: | 4 | mkdir -p .bin 5 | curl -L https://github.com/ysbaddaden/shards/releases/download/v0.4.0/shards-0.4.0_linux_amd64.tar.gz | tar xz -C .bin 6 | export PATH=".bin:$PATH" 7 | 8 | branches: 9 | only: 10 | - master 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Werner 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /Projectfile: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/werner/amatista/739a2a3d15d9cc0d1b8350472620a2e1ca50e1bc/Projectfile -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Amatista [](https://travis-ci.org/werner/amatista) [](http://www.docrystal.org/github.com/werner/amatista) 2 | 3 | # Deprecated!. 4 | ## you want to use [Kemal](https://github.com/kemalcr/kemal) instead 5 | 6 | This is a web framework build in [Crystal](https://github.com/manastech/crystal) to create quick applications. 7 | 8 | ### Shard file 9 | 10 | shard.yml 11 | ```yml 12 | name: myapp 13 | version: 0.0.1 14 | 15 | dependencies: 16 | amatista: 17 | github: werner/amatista 18 | ``` 19 | 20 | ### Basic Usage 21 | 22 | ```crystal 23 | require "amatista" 24 | 25 | class HelloWorldController < Amatista::Controller 26 | get "/" do 27 | html = %(
125 | <%= check_box_tag(:task, "id#{task[0]}", task[0], task[2], { class: "checkTask" }) %> 126 | <%= label_tag("task_id#{task[0]}", task[1].to_s) %> 127 | | 128 |129 | <%= link_to("Edit", "/tasks/edit/#{task[0]}", { class: "btn btn-success btn-xs" }) %> 130 | | 131 |132 | <%= link_to("Delete", "/tasks/delete/#{task[0]}", { class: "del btn btn-danger btn-xs" }) %> 133 | | 134 |
Hello world!
") 55 | end 56 | 57 | it "display an input text inside a div content tag" do 58 | view = BaseView.new 59 | 60 | view.content_tag(:div, view.text_field(:post, :title), 61 | { class: "form-control" }).should( 62 | eq("") 63 | ) 64 | end 65 | 66 | it "display a link tag" do 67 | view = BaseView.new 68 | 69 | view.link_to("Profile", "/profiles/1").should eq("Profile") 70 | end 71 | 72 | it "display a label tag" do 73 | view = BaseView.new 74 | 75 | view.label_tag("name", "Name").should eq("") 76 | end 77 | 78 | it "display a checkbox tag" do 79 | view = BaseView.new 80 | 81 | view.check_box_tag("task", "accept", "0", true).should( 82 | eq("") 83 | ) 84 | end 85 | 86 | it "display a radio button tag" do 87 | view = BaseView.new 88 | 89 | view.radio_button_tag("task", "accept", "0", true).should( 90 | eq("") 91 | ) 92 | end 93 | 94 | it "display a select tag" do 95 | view = BaseView.new 96 | 97 | view.select_tag("task", "countries", [["1", "USA"], ["2", "CANADA"], ["3", "VENEZUELA"]]).should( 98 | eq("") 103 | ) 104 | end 105 | end 106 | end 107 | -------------------------------------------------------------------------------- /src/amatista.cr: -------------------------------------------------------------------------------- 1 | require "./amatista/**" 2 | -------------------------------------------------------------------------------- /src/amatista/base.cr: -------------------------------------------------------------------------------- 1 | require "./helpers" 2 | require "./sessions" 3 | 4 | module Amatista 5 | # This class is used as a base for running amatista apps. 6 | class Base 7 | include Helpers 8 | include Sessions 9 | 10 | # Saves the configure options in a global variable 11 | # 12 | # Example: 13 | # ```crystal 14 | # class Main < Amatista::Base 15 | # configure do |conf| 16 | # conf[:secret_key] = "secret" 17 | # conf[:database_driver] = "postgres" 18 | # conf[:database_connection] = ENV["DATABASE_URL"] 19 | # end 20 | # end 21 | # ``` 22 | def self.configure 23 | configuration = {} of Symbol => (String | Bool) 24 | yield(configuration) 25 | $amatista.secret_key = configuration[:secret_key]?.to_s 26 | $amatista.database_connection = configuration[:database_connection]?.to_s 27 | $amatista.database_driver = configuration[:database_driver]?.to_s 28 | public_dir = configuration[:public_dir]?.to_s 29 | $amatista.public_dir = public_dir unless public_dir.empty? 30 | @@logs = configuration[:logs]? || false 31 | end 32 | 33 | # Run the server, just needs a port number. 34 | def run(port, environment = :development) 35 | $amatista.environment = environment 36 | server = create_server(port) 37 | server.listen 38 | end 39 | 40 | def run_forked(port, environment = :development, workers = 8) 41 | $amatista.environment = environment 42 | server = create_server(port) 43 | server.listen_fork(workers) 44 | end 45 | 46 | # Process static file 47 | def process_static(path) 48 | file = File.join($amatista.public_dir, path) 49 | if File.file?(file) 50 | if $amatista.environment == :production 51 | add_cache_control 52 | add_last_modified(file) 53 | end 54 | respond_to(File.extname(path).gsub(".", ""), File.read(file)) 55 | end 56 | end 57 | 58 | # Returns a response based on the request client. 59 | def process_request(request : HTTP::Request) : HTTP::Response 60 | begin 61 | response = Response.new(request) 62 | $amatista.request = request 63 | route = Response.find_route($amatista.routes, request.method, request.path.to_s) 64 | return HTTP::Response.not_found unless route 65 | 66 | response_filter = Filter.find_response($amatista.filters, route.controller, route.path) 67 | return response_filter.try &.call() if response_filter.is_a?(-> HTTP::Response) 68 | 69 | Filter.execute_blocks($amatista.filters, route.controller, route.path) 70 | 71 | $amatista.params = response.process_params(route) 72 | route.block.call($amatista.params) 73 | rescue e 74 | HTTP::Response.error "text/plain", "Error: #{e}" 75 | end 76 | end 77 | 78 | private def create_server(port) 79 | HTTP::Server.new port, do |request| 80 | p request if @@logs 81 | static_response = process_static(request.path.to_s) 82 | return static_response if static_response.is_a? HTTP::Response 83 | process_request(request) 84 | end 85 | end 86 | end 87 | end 88 | -------------------------------------------------------------------------------- /src/amatista/controller.cr: -------------------------------------------------------------------------------- 1 | require "./flash" 2 | require "./helpers" 3 | require "./sessions" 4 | 5 | module Amatista 6 | class Controller 7 | extend Helpers 8 | extend Sessions 9 | extend Flash 10 | 11 | macro inherited 12 | # Creates 5 methods to handle the http requests from the browser. 13 | {% for method in %w(get post put delete patch) %} 14 | def self.{{method.id}}(path : String, &block : Amatista::Handler::Params -> HTTP::Response) 15 | $amatista.routes << Amatista::Route.new({{@type}}, "{{method.id}}".upcase, path, block) 16 | yield($amatista.params) 17 | end 18 | {% end %} 19 | 20 | def self.before_filter(paths = [] of String, condition = -> { false }, &block : -> T) 21 | $amatista.filters << Amatista::Filter.new({{@type}}, paths, condition, block) 22 | end 23 | 24 | def self.superclass 25 | {{@type.superclass}} 26 | end 27 | end 28 | 29 | def self.superclass 30 | self 31 | end 32 | 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /src/amatista/filter.cr: -------------------------------------------------------------------------------- 1 | module Amatista 2 | #Callback filters to use before the actions. 3 | class Filter 4 | property block 5 | property paths 6 | property controller 7 | property condition 8 | 9 | def initialize(@controller, @paths, @condition, @block : T) 10 | end 11 | 12 | # Finds the filters based on the controller or the ApplicationController father 13 | # and the paths selected. 14 | def self.find_all(filters, controller, path) 15 | filters.select do |filter| 16 | check_controller(filter.controller, controller) && 17 | (filter.paths.includes?(path) || filter.paths.empty?) 18 | end 19 | end 20 | 21 | # This will search for the filters callbacks and execute the block 22 | #if it's not an HTTP::Response 23 | def self.execute_blocks(filters, controller, path) 24 | filters.each do |filter| 25 | if check_controller(filter.controller, controller) && 26 | (filter.paths.includes?(path) || filter.paths.empty?) && 27 | !filter.block.is_a?(-> HTTP::Response) 28 | filter.block.call() 29 | end 30 | end 31 | end 32 | 33 | #Find a filter that has a block as an HTTP::Response return's value 34 | def self.find_response(filters, controller, path) 35 | filters.each do |filter| 36 | block = filter.block 37 | if check_controller(filter.controller, controller) && 38 | (filter.paths.includes?(path) || filter.paths.empty?) && 39 | (filter.block.is_a?(-> HTTP::Response) && filter.condition.call()) 40 | return block 41 | end 42 | end 43 | end 44 | 45 | private def self.check_controller(filter_controller, controller) 46 | (filter_controller == controller || 47 | filter_controller == controller.try(&.superclass)) 48 | end 49 | 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /src/amatista/flash.cr: -------------------------------------------------------------------------------- 1 | module Amatista 2 | #Flash sessions used until it is called. 3 | module Flash 4 | def set_flash(key, value) 5 | $amatista.flash[key] = value 6 | end 7 | 8 | def get_flash(key) 9 | flash_value = $amatista.flash[key]? 10 | $amatista.flash.delete(key) 11 | flash_value 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /src/amatista/handler.cr: -------------------------------------------------------------------------------- 1 | require "http/server" 2 | require "./route" 3 | 4 | module Amatista 5 | # Use to saves configuration, routes and other data needed for the application. 6 | class Handler 7 | alias ParamsValue = Hash(String, String | Array(String)) | String | Array(String) 8 | alias Params = Hash(String, ParamsValue) 9 | property params 10 | property routes 11 | property filters 12 | property sessions 13 | property cookie_hash 14 | property secret_key 15 | property request 16 | property database_connection 17 | property database_driver 18 | property public_dir 19 | property flash 20 | property environment 21 | 22 | def initialize 23 | @params = {} of String => ParamsValue 24 | @routes = [] of Route 25 | @filters = [] of Filter 26 | @sessions = {} of String => Hash(String, String) 27 | @cookie_hash = "" 28 | @secret_key = "" 29 | @request = nil 30 | @database_connection = "" 31 | @database_driver = "" 32 | @public_dir = Dir.current 33 | @flash = {} of Symbol => String 34 | @environment = :development 35 | end 36 | end 37 | end 38 | 39 | $amatista = Amatista::Handler.new 40 | -------------------------------------------------------------------------------- /src/amatista/helpers.cr: -------------------------------------------------------------------------------- 1 | require "http/server" 2 | require "mime" 3 | require "crypto/md5" 4 | 5 | module Amatista 6 | # Helpers used by the methods in the controller class. 7 | module Helpers 8 | # Redirects to an url 9 | # 10 | # Example 11 | # ```crystal 12 | # redirect_to "/tasks" 13 | # ``` 14 | def redirect_to(path) 15 | add_headers :location, path 16 | HTTP::Response.new 303, "redirection", set_headers 17 | end 18 | 19 | # Makes a respond based on context type 20 | # The body argument should be string if used html context type 21 | def respond_to(context, body) 22 | context = Mime.from_ext(context).to_s 23 | add_headers :context, context 24 | HTTP::Response.new 200, body, set_headers 25 | end 26 | 27 | # Send data as attachment 28 | def send_data(body, filename, disposition="attachment") 29 | add_headers :disposition, "attachment; filename='#{filename}'" 30 | HTTP::Response.new 200, body, set_headers 31 | end 32 | 33 | def add_cache_control(max_age = 31536000, public = true) 34 | state = public ? "public" : "private" 35 | add_headers :cache, "#{state}, max-age=#{max_age}" 36 | end 37 | 38 | def add_last_modified(resource) 39 | add_headers :last_modified, File.stat(resource).mtime.to_s 40 | end 41 | 42 | def add_etag(resource) 43 | stat = File.stat(resource) 44 | to_encrypt = stat.ino + stat.size + stat.mtime.ticks 45 | add_headers :etag, Crypto::MD5.hex_digest(to_encrypt.to_s) 46 | end 47 | 48 | def set_headers 49 | headers = @@header 50 | @@header = HTTP::Headers.new 51 | headers || HTTP::Headers.new 52 | end 53 | 54 | private def add_headers(type, value) 55 | @@header = HTTP::Headers.new unless @@header 56 | 57 | header_label = {context: "Content-Type", 58 | location: "Location", 59 | cache: "Cache-Control", 60 | last_modified: "Last-Modified", 61 | disposition: "Content-Disposition", 62 | etag: "ETag"} 63 | 64 | header = @@header 65 | if header 66 | header.add(header_label[type], value) 67 | header.add("Set-Cookie", send_sessions_to_cookie) unless has_session? 68 | end 69 | @@header = header 70 | end 71 | 72 | # Find out the IP address 73 | def remote_ip 74 | return unless request = $amatista.request 75 | 76 | headers = %w(X-Forwarded-For Proxy-Client-IP WL-Proxy-Client-IP HTTP_X_FORWARDED_FOR HTTP_X_FORWARDED 77 | HTTP_X_CLUSTER_CLIENT_IP HTTP_CLIENT_IP HTTP_FORWARDED_FOR HTTP_FORWARDED HTTP_VIA 78 | REMOTE_ADDR) 79 | 80 | headers.map{|header| request.headers[header]? as String | Nil}.compact.first 81 | end 82 | end 83 | end 84 | -------------------------------------------------------------------------------- /src/amatista/model.cr: -------------------------------------------------------------------------------- 1 | module Amatista 2 | class Model 3 | # Executes a query against a database. 4 | # Example 5 | # ```crystal 6 | # class Task < Amatista::Model 7 | # def self.all 8 | # records = [] of String 9 | # connect {|db| records = db.exec("select * from tasks order by done").rows } 10 | # records 11 | # end 12 | # end 13 | # ``` 14 | def self.connect 15 | if $amatista.database_driver == "postgres" 16 | db = PG::Connection.new($amatista.database_connection) 17 | yield(db) 18 | db.finish 19 | end 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /src/amatista/response.cr: -------------------------------------------------------------------------------- 1 | require "uri" 2 | require "mime" 3 | 4 | module Amatista 5 | # Use by the framework to return an appropiate response based on the request. 6 | class Response 7 | property request 8 | 9 | def initialize(@request) 10 | end 11 | 12 | def self.find_route(routes, method, path_to_find) 13 | routes.find {|route_request| route_request.method == method && route_request.match_path?(path_to_find) } 14 | end 15 | 16 | def process_params(route) : Handler::Params 17 | route.request_path = @request.path.to_s 18 | route.add_params(objectify_params(@request.body.to_s)) 19 | route.get_params 20 | end 21 | 22 | #Convert params get from CGI to a Crystal Hash object 23 | private def objectify_params(raw_params) : Handler::Params 24 | result = {} of String => Handler::ParamsValue 25 | params = {} of String => Array(String) 26 | 27 | HTTP::Params.parse(raw_params) do |key, value| 28 | ary = params[key] ||= [] of String 29 | ary.push value 30 | end 31 | 32 | params.each do |key, value| 33 | object = key.match(/(\w*)\[(\w*)\]/) { |x| [x[1], x[2]] } 34 | if object.is_a?(Array(String)) 35 | name, method = object 36 | final_value = value.size > 1 ? value : value.first 37 | merge_same_key(result, name, method, final_value, result[name]?) 38 | elsif object.nil? 39 | result.merge!({key => value.first}) 40 | end 41 | end 42 | result 43 | end 44 | 45 | private def merge_same_key(result, name, method, value : String | Array(String), 46 | child : Handler::ParamsValue | Nil) 47 | 48 | case child 49 | when Hash(String, String | Array(String)) 50 | child.merge!({method => value}) 51 | else 52 | result.merge!({name => {method => value}}) 53 | end 54 | end 55 | 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /src/amatista/route.cr: -------------------------------------------------------------------------------- 1 | module Amatista 2 | # Use by the framework to handle the request params. 3 | class Route 4 | property controller 5 | property method 6 | property path 7 | property block 8 | property request_path 9 | 10 | def initialize(@controller, @method, @path, @block) 11 | @params = {} of String => Handler::ParamsValue 12 | @request_path = "" 13 | end 14 | 15 | # Get personalized params from routes defined by user 16 | def get_params 17 | if @request_path == "" 18 | raise "You need to set params and request_path first" 19 | else 20 | extract_params_from_path 21 | @params 22 | end 23 | end 24 | 25 | # Search for similar paths 26 | # Example: /tasks/edit/:id == /tasks/edit/2 27 | def match_path?(path) 28 | return path == "/" if @path == "/" 29 | 30 | original_path = @path.split("/") - [""] 31 | path_to_match = path.split("/") - [""] 32 | 33 | original_path.size == path_to_match.size && 34 | original_path.zip(path_to_match).all? do |item| 35 | item[0].match(/(:\w*)/) ? true : item[0] == item[1] 36 | end 37 | end 38 | 39 | # Add personalized params to the coming from requests 40 | def add_params(params : Handler::Params) 41 | params.each do |key, value| 42 | @params[key] = value 43 | end 44 | end 45 | 46 | private def extract_params_from_path 47 | params = @path.to_s.scan(/(:\w*)/).map(&.[](0)) 48 | pairs = @path.split("/").zip(@request_path.split("/")) 49 | pairs.select{|pair| params.includes?(pair[0])}.each do |p| 50 | @params.merge!({p[0].gsub(/:/, "") => p[1]}) 51 | end 52 | end 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /src/amatista/sessions.cr: -------------------------------------------------------------------------------- 1 | require "secure_random" 2 | 3 | module Amatista 4 | # Methods to save a sessions hash in a cookie. 5 | module Sessions 6 | 7 | def send_sessions_to_cookie 8 | return "" unless request = $amatista.request 9 | hash = SecureRandom.base64 10 | $amatista.cookie_hash = $amatista.sessions[hash]? ? hash : SecureRandom.base64 11 | "_amatista_session_id= #{$amatista.cookie_hash}" 12 | end 13 | 14 | # Saves a session key. 15 | def set_session(key, value) 16 | $amatista.cookie_hash = $amatista.cookie_hash.empty? ? get_cookie.to_s : $amatista.cookie_hash 17 | $amatista.sessions[$amatista.cookie_hash] = {key => value} 18 | end 19 | 20 | # Get a value from the cookie. 21 | def get_session(key) 22 | session_hash = get_cookie 23 | return nil unless $amatista.sessions[session_hash]? 24 | $amatista.sessions[session_hash][key]? if session_hash 25 | end 26 | 27 | # remove a sessions value from the cookie. 28 | def remove_session(key) 29 | session_hash = get_cookie 30 | return nil unless $amatista.sessions[session_hash]? 31 | $amatista.sessions[session_hash].delete(key) 32 | end 33 | 34 | def has_session? 35 | return unless request = $amatista.request 36 | cookie = request.headers["Cookie"]?.to_s 37 | !cookie.split(";").select(&.match(/_amatista_session_id/)).empty? 38 | end 39 | 40 | private def get_cookie 41 | return unless request = $amatista.request 42 | cookie = request.headers["Cookie"]?.to_s 43 | process_session(cookie) 44 | end 45 | 46 | private def process_session(string) 47 | amatista_session = string.split(";").select(&.match(/_amatista_session_id/)).first? 48 | amatista_session.gsub(/_amatista_session_id=/,"").gsub(/\s/,"") if amatista_session 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /src/amatista/version.cr: -------------------------------------------------------------------------------- 1 | module Amatista 2 | VERSION = "0.5.1" 3 | end 4 | -------------------------------------------------------------------------------- /src/amatista/view/base_view.cr: -------------------------------------------------------------------------------- 1 | require "ecr" 2 | require "ecr/macros" 3 | require "./view_tag" 4 | require "../sessions" 5 | require "../flash" 6 | 7 | module Amatista 8 | # Set of methods to reduce the steps to display a view 9 | # It needs a LayoutView class that works as a layout view. 10 | # The views should be placed in app/views folder. 11 | class BaseView 12 | include ViewTag 13 | include Sessions 14 | include Flash 15 | 16 | def initialize(@arguments = nil) 17 | end 18 | 19 | # compiles the view with data in a string format 20 | def set_view 21 | LayoutView.new(self.to_s).to_s.strip 22 | end 23 | 24 | macro set_ecr(view_name, path = "src/views") 25 | ecr_file "#{{{path}}}/#{{{view_name}}}.ecr" 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /src/amatista/view/view_helpers.cr: -------------------------------------------------------------------------------- 1 | require "html" 2 | 3 | module Amatista 4 | # Set of helpers for the tag views 5 | module ViewHelpers 6 | private def options_transfomed(options = [] of Hash(Symbol, String)) 7 | options.map do |key, value| 8 | "#{key.to_s} = \"#{HTML.escape(value.to_s)}\"" 9 | end.join(" ") 10 | end 11 | 12 | private def extract_option_tags(collection) 13 | collection.inject("") do |acc, item| 14 | acc + 15 | surrounded_tag(:option, item[1], [] of Hash(Symbol, String)) do |str_result| 16 | str_result << " value =\"#{item[0]}\"" 17 | end 18 | end 19 | end 20 | 21 | private def input_tag(raw_options) 22 | options = options_transfomed(raw_options) 23 | str_result = MemoryIO.new 24 | yield(str_result) 25 | str_result << " #{options}" unless options.empty? 26 | str_result << " />" 27 | str_result.to_s 28 | end 29 | 30 | private def surrounded_tag(tag, value, raw_options) 31 | options = options_transfomed(raw_options) 32 | str_result = MemoryIO.new 33 | str_result << "<#{tag.to_s}" 34 | yield(str_result) 35 | str_result << " #{options}" unless options.empty? 36 | str_result << ">" 37 | str_result << value 38 | str_result << "#{tag.to_s}>" 39 | str_result.to_s 40 | end 41 | 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /src/amatista/view/view_tag.cr: -------------------------------------------------------------------------------- 1 | require "./view_helpers" 2 | 3 | module Amatista 4 | # Methods to create html tags using crystal 5 | module ViewTag 6 | include ViewHelpers 7 | 8 | def text_field(object_name, method, raw_options = [] of Hash(Symbol, String)) 9 | input_tag(raw_options) do |str_result| 10 | str_result << "" 72 | str_body_result = MemoryIO.new 73 | str_result << yield(str_body_result) 74 | str_result << "" 75 | str_result.to_s 76 | end 77 | 78 | def select_tag(object_name, method, collection, raw_options = [] of Hash(Symbol, String)) 79 | option_tags = extract_option_tags(collection) 80 | surrounded_tag(:select, option_tags, raw_options) do |str_result| 81 | str_result << " id=\"#{HTML.escape(object_name.to_s)}_#{HTML.escape(method.to_s)}\"" 82 | str_result << " name=\"#{HTML.escape(object_name.to_s)}[#{HTML.escape(method.to_s)}]\" " 83 | end 84 | end 85 | 86 | def content_tag(tag, value, raw_options = [] of Hash(Symbol, String)) 87 | surrounded_tag(tag, value, raw_options) {} 88 | end 89 | end 90 | end 91 | --------------------------------------------------------------------------------