├── .gitignore ├── README ├── Rakefile ├── lib ├── tagz.rb └── tagz │ └── rails.rb ├── pkg └── tagz-9.10.0.gem ├── readme.erb ├── samples ├── a.rb ├── b.rb ├── c.rb ├── d.rb ├── e.rb ├── f.rb └── g.rb ├── tagz.gemspec └── test └── tagz_test.rb /.gitignore: -------------------------------------------------------------------------------- 1 | .todo 2 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | NAME 2 | 3 | tagz.rb 4 | 5 | SYNOPSIS 6 | 7 | require Tagz 8 | 9 | include Tagz.globally 10 | 11 | a_(:href => "/foo"){ "bar" } #=> bar 12 | 13 | DESCRIPTION 14 | 15 | tagz.rb is generates html, xml, or any sgml variant like a small ninja 16 | running across the backs of a herd of giraffes swatting of heads like a 17 | mark-up weedwacker. weighing in at less than 300 lines of code tagz.rb adds 18 | an html/xml/sgml syntax to ruby that is both unobtrusive, safe, and available 19 | globally to objects without the need for any builder or superfluous objects. 20 | tagz.rb is designed for applications that generate html to be able to do so 21 | easily in any context without heavyweight syntax or scoping issues, like a 22 | ninja sword through butter. 23 | 24 | FEATURES 25 | 26 | - use as a library or mixin 27 | 28 | - simple, clean and consistent mark-up that is easy to visually 29 | distinguish from other ruby methods 30 | 31 | - auto-compatibility with rails/actionview 32 | 33 | - ability to independently open and close tagz in markup 34 | 35 | - intelligent auto-escaping of both attributes and content for both html 36 | and xml 37 | 38 | - validate your html/xml with 'ruby -c' syntax check 39 | 40 | - generally bitchin 41 | 42 | - no lame method_missing approach that prevents tagz like 'type' from being 43 | generated 44 | 45 | RAILS 46 | 47 | in config/environment.rb 48 | 49 | require 'tagz' 50 | 51 | in a helper 52 | 53 | def list_of_users 54 | ul_(:class => 'users'){ 55 | @users.each{|user| li_{ user }} 56 | } 57 | end 58 | 59 | in a view 60 | 61 | table_{ 62 | rows.each do |row| 63 | tr_{ 64 | row.each do |cell| 65 | td_{ cell } 66 | end 67 | } 68 | end 69 | } 70 | 71 | in a controller 72 | 73 | def ajax_responder 74 | text = 75 | tagz{ 76 | table_{ 77 | rows.each do |row| 78 | tr_{ 79 | row.each do |cell| 80 | td_{ cell } 81 | end 82 | } 83 | end 84 | } 85 | } 86 | 87 | render :text => text 88 | end 89 | 90 | INSTALL 91 | 92 | gem install tagz 93 | 94 | URIS 95 | 96 | http://github.com/ahoward/tagz/tree/master 97 | http://rubyforge.org/projects/codeforpeople 98 | 99 | HISTORY 100 | 7.0.0 101 | - * IMPORTANT * NOT BACKWARD COMPATIBLE (thus version bump) 102 | the tagz functionality itself has not changed, but the defaults for 103 | excaping have! now tagz will escape attributes, but NOT content, in the 104 | default mode. you can easily configure something else with 105 | 106 | Tagz.escape!(:content => true, :attributes => true) 107 | 108 | which would be like saying 109 | 110 | Tagz.xml_mode! 111 | 112 | or 113 | 114 | Tagz.escape!(:content => false, :attributes => true) 115 | 116 | which would be like saying 117 | 118 | Tagz.html_mode! 119 | 120 | to repeat, the default is 'Tagz.html_mode!' 121 | 122 | 6.0.0 123 | - reorganize lib to avoid dumping a few constants into the includee - aka 124 | don't absolutely minimize namespace pollution. there is now reason to 125 | thing this version shouldn't be backwards compat - i bumped the version 126 | just in case 127 | 128 | 5.1.0 129 | - attribute/content auto-escaping can be turned off with 130 | 131 | Tagz.i_know_what_the_hell_i_am_doing! 132 | 133 | and turned back on with 134 | 135 | Tagz.i_do_not_know_what_the_hell_i_am_doing! 136 | 137 | attribute and content escaping can be configured individually too. see 138 | tests for examples 139 | 140 | 141 | thanks Dan Fitzpatrick 142 | 143 | - << and concat escape (if configured) while puts and push and write do not 144 | 145 | thanks JoelVanderWerf 146 | 147 | 5.0.0 148 | - introduce better escaping for attributes using xchar.rb approach 149 | - indroduce smart escaping for content 150 | - make Tagz.globally kick ass more hard 151 | - note that this version is not backward compatibile if you were relying 152 | on tagz never escaping any content should be an ok upgrade for most 153 | applications 154 | 155 | 4.6.0 156 | - fix a bug with self closing tagz that had crept in 1.0.0 -> 4.2.0. thx 157 | jeremy hinegardner 158 | 159 | - added tests from 1.0.0 back into svn 160 | 161 | 4.4.0 162 | - remove dependancy on cgi lib, tagz is now completely standalone 163 | 164 | 4.3.0 165 | - detect rails and auto-include into ActionController::Base and include 166 | globally into ActionView::Base 167 | 168 | 4.2.0 169 | - general lib cleanup 170 | - introduction of dual-mixin technique (Tagz.globally) 171 | - few small bug fixes 172 | - ninja tales 173 | 174 | SAMPLES 175 | 176 | 177 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | This.rubyforge_project = 'codeforpeople' 2 | This.author = "Ara T. Howard" 3 | This.email = "ara.t.howard@gmail.com" 4 | This.homepage = "https://github.com/ahoward/#{ This.lib }" 5 | 6 | task :license do 7 | open('LICENSE', 'w'){|fd| fd.puts "Ruby"} 8 | end 9 | 10 | task :default do 11 | puts((Rake::Task.tasks.map{|task| task.name.gsub(/::/,':')} - ['default']).sort) 12 | end 13 | 14 | task :test do 15 | run_tests! 16 | end 17 | 18 | namespace :test do 19 | task(:unit){ run_tests!(:unit) } 20 | task(:functional){ run_tests!(:functional) } 21 | task(:integration){ run_tests!(:integration) } 22 | end 23 | 24 | def run_tests!(which = nil) 25 | which ||= '**' 26 | test_dir = File.join(This.dir, "test") 27 | test_glob ||= File.join(test_dir, "#{ which }/**_test.rb") 28 | test_rbs = Dir.glob(test_glob).sort 29 | 30 | div = ('=' * 119) 31 | line = ('-' * 119) 32 | 33 | test_rbs.each_with_index do |test_rb, index| 34 | testno = index + 1 35 | command = "#{ This.ruby } -w -I ./lib -I ./test/lib #{ test_rb }" 36 | 37 | puts 38 | say(div, :color => :cyan, :bold => true) 39 | say("@#{ testno } => ", :bold => true, :method => :print) 40 | say(command, :color => :cyan, :bold => true) 41 | say(line, :color => :cyan, :bold => true) 42 | 43 | system(command) 44 | 45 | say(line, :color => :cyan, :bold => true) 46 | 47 | status = $?.exitstatus 48 | 49 | if status.zero? 50 | say("@#{ testno } <= ", :bold => true, :color => :white, :method => :print) 51 | say("SUCCESS", :color => :green, :bold => true) 52 | else 53 | say("@#{ testno } <= ", :bold => true, :color => :white, :method => :print) 54 | say("FAILURE", :color => :red, :bold => true) 55 | end 56 | say(line, :color => :cyan, :bold => true) 57 | 58 | exit(status) unless status.zero? 59 | end 60 | end 61 | 62 | 63 | task :gemspec do 64 | ignore_extensions = ['git', 'svn', 'tmp', /sw./, 'bak', 'gem'] 65 | ignore_directories = ['pkg'] 66 | ignore_files = ['test/log'] 67 | 68 | shiteless = 69 | lambda do |list| 70 | list.delete_if do |entry| 71 | next unless test(?e, entry) 72 | extension = File.basename(entry).split(%r/[.]/).last 73 | ignore_extensions.any?{|ext| ext === extension} 74 | end 75 | list.delete_if do |entry| 76 | next unless test(?d, entry) 77 | dirname = File.expand_path(entry) 78 | ignore_directories.any?{|dir| File.expand_path(dir) == dirname} 79 | end 80 | list.delete_if do |entry| 81 | next unless test(?f, entry) 82 | filename = File.expand_path(entry) 83 | ignore_files.any?{|file| File.expand_path(file) == filename} 84 | end 85 | end 86 | 87 | lib = This.lib 88 | object = This.object 89 | version = This.version 90 | files = shiteless[Dir::glob("**/**")] 91 | executables = shiteless[Dir::glob("bin/*")].map{|exe| File.basename(exe)} 92 | #has_rdoc = true #File.exist?('doc') 93 | test_files = "test/#{ lib }.rb" if File.file?("test/#{ lib }.rb") 94 | summary = object.respond_to?(:summary) ? object.summary : "summary: #{ lib } kicks the ass" 95 | description = object.respond_to?(:description) ? object.description : "description: #{ lib } kicks the ass" 96 | license = object.respond_to?(:license) ? object.license : "Ruby" 97 | 98 | if This.extensions.nil? 99 | This.extensions = [] 100 | extensions = This.extensions 101 | %w( Makefile configure extconf.rb ).each do |ext| 102 | extensions << ext if File.exists?(ext) 103 | end 104 | end 105 | extensions = [extensions].flatten.compact 106 | 107 | if This.dependencies.nil? 108 | dependencies = [] 109 | else 110 | case This.dependencies 111 | when Hash 112 | dependencies = This.dependencies.values 113 | when Array 114 | dependencies = This.dependencies 115 | end 116 | end 117 | 118 | template = 119 | if test(?e, 'gemspec.erb') 120 | Template{ IO.read('gemspec.erb') } 121 | else 122 | Template { 123 | <<-__ 124 | ## <%= lib %>.gemspec 125 | # 126 | 127 | Gem::Specification::new do |spec| 128 | spec.name = <%= lib.inspect %> 129 | spec.version = <%= version.inspect %> 130 | spec.platform = Gem::Platform::RUBY 131 | spec.summary = <%= lib.inspect %> 132 | spec.description = <%= description.inspect %> 133 | spec.license = <%= license.inspect %> 134 | 135 | spec.files =\n<%= files.sort.pretty_inspect %> 136 | spec.executables = <%= executables.inspect %> 137 | 138 | spec.require_path = "lib" 139 | 140 | spec.test_files = <%= test_files.inspect %> 141 | 142 | <% dependencies.each do |lib_version| %> 143 | spec.add_dependency(*<%= Array(lib_version).flatten.inspect %>) 144 | <% end %> 145 | 146 | spec.extensions.push(*<%= extensions.inspect %>) 147 | 148 | spec.rubyforge_project = <%= This.rubyforge_project.inspect %> 149 | spec.author = <%= This.author.inspect %> 150 | spec.email = <%= This.email.inspect %> 151 | spec.homepage = <%= This.homepage.inspect %> 152 | end 153 | __ 154 | } 155 | end 156 | 157 | Fu.mkdir_p(This.pkgdir) 158 | gemspec = "#{ lib }.gemspec" 159 | open(gemspec, "w"){|fd| fd.puts(template)} 160 | This.gemspec = gemspec 161 | end 162 | 163 | task :gem => [:clean, :gemspec] do 164 | Fu.mkdir_p(This.pkgdir) 165 | before = Dir['*.gem'] 166 | cmd = "gem build #{ This.gemspec }" 167 | `#{ cmd }` 168 | after = Dir['*.gem'] 169 | gem = ((after - before).first || after.first) or abort('no gem!') 170 | Fu.mv(gem, This.pkgdir) 171 | This.gem = File.join(This.pkgdir, File.basename(gem)) 172 | end 173 | 174 | task :readme do 175 | samples = '' 176 | prompt = '~ > ' 177 | lib = This.lib 178 | version = This.version 179 | 180 | Dir['sample*/*'].sort.each do |sample| 181 | samples << "\n" << " <========< #{ sample } >========>" << "\n\n" 182 | 183 | cmd = "cat #{ sample }" 184 | samples << Util.indent(prompt + cmd, 2) << "\n\n" 185 | samples << Util.indent(`#{ cmd }`, 4) << "\n" 186 | 187 | cmd = "ruby #{ sample }" 188 | samples << Util.indent(prompt + cmd, 2) << "\n\n" 189 | 190 | cmd = "ruby -e'STDOUT.sync=true; exec %(ruby -I ./lib #{ sample })'" 191 | samples << Util.indent(`#{ cmd } 2>&1`, 4) << "\n" 192 | end 193 | 194 | template = 195 | if test(?e, 'README.erb') 196 | Template{ IO.read('README.erb') } 197 | else 198 | Template { 199 | <<-__ 200 | NAME 201 | #{ lib } 202 | 203 | DESCRIPTION 204 | 205 | INSTALL 206 | gem install #{ lib } 207 | 208 | SAMPLES 209 | #{ samples } 210 | __ 211 | } 212 | end 213 | 214 | open("README", "w"){|fd| fd.puts template} 215 | end 216 | 217 | 218 | task :clean do 219 | Dir[File.join(This.pkgdir, '**/**')].each{|entry| Fu.rm_rf(entry)} 220 | end 221 | 222 | 223 | task :release => [:clean, :gemspec, :gem] do 224 | gems = Dir[File.join(This.pkgdir, '*.gem')].flatten 225 | raise "which one? : #{ gems.inspect }" if gems.size > 1 226 | raise "no gems?" if gems.size < 1 227 | 228 | cmd = "gem push #{ This.gem }" 229 | puts cmd 230 | puts 231 | system(cmd) 232 | abort("cmd(#{ cmd }) failed with (#{ $?.inspect })") unless $?.exitstatus.zero? 233 | 234 | cmd = "rubyforge login && rubyforge add_release #{ This.rubyforge_project } #{ This.lib } #{ This.version } #{ This.gem }" 235 | puts cmd 236 | puts 237 | system(cmd) 238 | abort("cmd(#{ cmd }) failed with (#{ $?.inspect })") unless $?.exitstatus.zero? 239 | end 240 | 241 | 242 | 243 | 244 | 245 | BEGIN { 246 | # support for this rakefile 247 | # 248 | $VERBOSE = nil 249 | 250 | require 'ostruct' 251 | require 'erb' 252 | require 'fileutils' 253 | require 'rbconfig' 254 | require 'pp' 255 | 256 | # fu shortcut 257 | # 258 | Fu = FileUtils 259 | 260 | # cache a bunch of stuff about this rakefile/environment 261 | # 262 | This = OpenStruct.new 263 | 264 | This.file = File.expand_path(__FILE__) 265 | This.dir = File.dirname(This.file) 266 | This.pkgdir = File.join(This.dir, 'pkg') 267 | 268 | # grok lib 269 | # 270 | lib = ENV['LIB'] 271 | unless lib 272 | lib = File.basename(Dir.pwd).sub(/[-].*$/, '') 273 | end 274 | This.lib = lib 275 | 276 | # grok version 277 | # 278 | version = ENV['VERSION'] 279 | unless version 280 | require "./lib/#{ This.lib }" 281 | This.name = lib.capitalize 282 | This.object = eval(This.name) 283 | version = This.object.send(:version) 284 | end 285 | This.version = version 286 | 287 | # see if dependencies are export by the module 288 | # 289 | if This.object.respond_to?(:dependencies) 290 | This.dependencies = This.object.dependencies 291 | end 292 | 293 | # we need to know the name of the lib an it's version 294 | # 295 | abort('no lib') unless This.lib 296 | abort('no version') unless This.version 297 | 298 | # discover full path to this ruby executable 299 | # 300 | c = RbConfig::CONFIG 301 | bindir = c["bindir"] || c['BINDIR'] 302 | ruby_install_name = c['ruby_install_name'] || c['RUBY_INSTALL_NAME'] || 'ruby' 303 | ruby_ext = c['EXEEXT'] || '' 304 | ruby = File.join(bindir, (ruby_install_name + ruby_ext)) 305 | This.ruby = ruby 306 | 307 | # some utils 308 | # 309 | module Util 310 | def indent(s, n = 2) 311 | s = unindent(s) 312 | ws = ' ' * n 313 | s.gsub(%r/^/, ws) 314 | end 315 | 316 | def unindent(s) 317 | indent = nil 318 | s.each_line do |line| 319 | next if line =~ %r/^\s*$/ 320 | indent = line[%r/^\s*/] and break 321 | end 322 | indent ? s.gsub(%r/^#{ indent }/, "") : s 323 | end 324 | extend self 325 | end 326 | 327 | # template support 328 | # 329 | class Template 330 | def initialize(&block) 331 | @block = block 332 | @template = block.call.to_s 333 | end 334 | def expand(b=nil) 335 | ERB.new(Util.unindent(@template)).result((b||@block).binding) 336 | end 337 | alias_method 'to_s', 'expand' 338 | end 339 | def Template(*args, &block) Template.new(*args, &block) end 340 | 341 | # colored console output support 342 | # 343 | This.ansi = { 344 | :clear => "\e[0m", 345 | :reset => "\e[0m", 346 | :erase_line => "\e[K", 347 | :erase_char => "\e[P", 348 | :bold => "\e[1m", 349 | :dark => "\e[2m", 350 | :underline => "\e[4m", 351 | :underscore => "\e[4m", 352 | :blink => "\e[5m", 353 | :reverse => "\e[7m", 354 | :concealed => "\e[8m", 355 | :black => "\e[30m", 356 | :red => "\e[31m", 357 | :green => "\e[32m", 358 | :yellow => "\e[33m", 359 | :blue => "\e[34m", 360 | :magenta => "\e[35m", 361 | :cyan => "\e[36m", 362 | :white => "\e[37m", 363 | :on_black => "\e[40m", 364 | :on_red => "\e[41m", 365 | :on_green => "\e[42m", 366 | :on_yellow => "\e[43m", 367 | :on_blue => "\e[44m", 368 | :on_magenta => "\e[45m", 369 | :on_cyan => "\e[46m", 370 | :on_white => "\e[47m" 371 | } 372 | def say(phrase, *args) 373 | options = args.last.is_a?(Hash) ? args.pop : {} 374 | options[:color] = args.shift.to_s.to_sym unless args.empty? 375 | keys = options.keys 376 | keys.each{|key| options[key.to_s.to_sym] = options.delete(key)} 377 | 378 | color = options[:color] 379 | bold = options.has_key?(:bold) 380 | 381 | parts = [phrase] 382 | parts.unshift(This.ansi[color]) if color 383 | parts.unshift(This.ansi[:bold]) if bold 384 | parts.push(This.ansi[:clear]) if parts.size > 1 385 | 386 | method = options[:method] || :puts 387 | 388 | Kernel.send(method, parts.join) 389 | end 390 | 391 | # always run out of the project dir 392 | # 393 | Dir.chdir(This.dir) 394 | } 395 | -------------------------------------------------------------------------------- /lib/tagz.rb: -------------------------------------------------------------------------------- 1 | # -*- encoding : utf-8 -*- 2 | unless defined? Tagz 3 | 4 | # core tagz functions 5 | # 6 | module Tagz 7 | require 'cgi' 8 | 9 | def Tagz.version() 10 | '9.10.0' 11 | end 12 | 13 | def Tagz.description 14 | <<-____ 15 | 16 | tagz.rb is generates html, xml, or any sgml variant like a small ninja 17 | running across the backs of a herd of giraffes swatting of heads like 18 | a mark-up weedwacker. weighing in at less than 300 lines of code 19 | tagz.rb adds an html/xml/sgml syntax to ruby that is both unobtrusive, 20 | safe, and available globally to objects without the need for any 21 | builder or superfluous objects. tagz.rb is designed for applications 22 | that generate html to be able to do so easily in any context without 23 | heavyweight syntax or scoping issues, like a ninja sword through 24 | butter. 25 | 26 | ____ 27 | end 28 | 29 | private 30 | # access tagz doc and enclose tagz operations 31 | # 32 | def tagz(document = nil, &block) 33 | @tagz ||= nil ## shut wornings up 34 | previous = @tagz 35 | 36 | if block 37 | @tagz ||= (Tagz.document.for(document) || Tagz.document.new) 38 | 39 | begin 40 | previous_size = @tagz.size 41 | 42 | content = instance_eval(&block) 43 | 44 | current_size = @tagz.size 45 | 46 | content_was_added = current_size > previous_size 47 | 48 | unless content_was_added 49 | @tagz << content 50 | end 51 | 52 | @tagz 53 | ensure 54 | @tagz = previous 55 | end 56 | else 57 | document ? Tagz.document.for(document) : @tagz 58 | end 59 | end 60 | 61 | 62 | # open_tag 63 | # 64 | def tagz__(name, *argv, &block) 65 | options = argv.last.is_a?(Hash) ? argv.pop : {} 66 | content = argv 67 | attributes = +'' 68 | 69 | unless options.empty? 70 | booleans = [] 71 | options.each do |key, value| 72 | if key.to_s =~ Tagz.namespace(:Boolean) 73 | value = value.to_s =~ %r/\Atrue\Z/imox ? nil : "\"#{ key.to_s.downcase.strip }\"" 74 | booleans.push([key, value].compact) 75 | next 76 | end 77 | 78 | key = Tagz.escape_key(key) 79 | value = Tagz.escape_value(value) 80 | 81 | if value =~ %r/"/ 82 | raise ArgumentError, value if value =~ %r/'/ 83 | value = "'#{ value }'" 84 | else 85 | raise ArgumentError, value if value =~ %r/"/ 86 | value = "\"#{ value }\"" 87 | end 88 | 89 | attributes << ' ' << [key, value].join('=') 90 | end 91 | booleans.each do |kv| 92 | attributes << ' ' << kv.compact.join('=') 93 | end 94 | end 95 | 96 | tagz.push "<#{ name }#{ attributes }>" 97 | 98 | if content.empty? 99 | if block 100 | size = tagz.size 101 | value = block.call(tagz) 102 | 103 | if value.nil? 104 | unless(tagz.size > size) 105 | tagz[-1] = "/>" 106 | else 107 | tagz.push "" 108 | end 109 | else 110 | tagz << value.to_s unless(tagz.size > size) 111 | tagz.push "" 112 | end 113 | 114 | end 115 | else 116 | tagz << content.join 117 | if block 118 | size = tagz.size 119 | value = block.arity.abs >= 1 ? block.call(tagz) : block.call() 120 | tagz << value.to_s unless(tagz.size > size) 121 | end 122 | tagz.push "" 123 | end 124 | 125 | tagz 126 | end 127 | 128 | # close_tag 129 | # 130 | def __tagz(tag, *a, &b) 131 | tagz.push "" 132 | end 133 | 134 | # catch special tagz methods 135 | # 136 | def method_missing(m, *a, &b) 137 | strategy = 138 | case m.to_s 139 | when %r/^(.*[^_])_(!)?$/o 140 | :open_tag 141 | when %r/^_([^_].*)$/o 142 | :close_tag 143 | when 'e' 144 | :element 145 | when '__', '___' 146 | :puts 147 | else 148 | nil 149 | end 150 | 151 | if(strategy.nil? or (tagz.nil? and Tagz.privately===self)) 152 | begin 153 | return super 154 | ensure 155 | :do_nothing_until_strange_core_dump_in_ruby_2_5_is_fixed 156 | # $!.set_backtrace caller(1) if $! 157 | end 158 | end 159 | 160 | case strategy 161 | when :open_tag 162 | m, bang = $1, $2 163 | b ||= lambda{} if bang 164 | tagz{ tagz__(m, *a, &b) } 165 | 166 | when :close_tag 167 | m = $1 168 | tagz{ __tagz(m, *a, &b) } 169 | 170 | when :element 171 | Tagz.element.new(*a, &b) 172 | 173 | when :puts 174 | tagz do 175 | tagz.push("\n") 176 | unless a.empty? 177 | tagz.push(a.join) 178 | tagz.push("\n") 179 | end 180 | end 181 | end 182 | end 183 | end 184 | 185 | 186 | # supporting code 187 | # 188 | module Tagz 189 | # singleton_class access for ad-hoc method adding from inside namespace 190 | # 191 | def Tagz.singleton_class(&block) 192 | @singleton_class ||= ( 193 | class << Tagz 194 | self 195 | end 196 | ) 197 | block ? @singleton_class.module_eval(&block) : @singleton_class 198 | end 199 | 200 | # hide away our own shit to minimize namespace pollution 201 | # 202 | class << Tagz 203 | module Namespace 204 | namespace = self 205 | 206 | Tagz.singleton_class{ 207 | define_method(:namespace){ |*args| 208 | if args.empty? 209 | namespace 210 | else 211 | namespace.const_get(args.first.to_sym) 212 | end 213 | } 214 | } 215 | 216 | Boolean = %r[ 217 | \A checked \Z | 218 | \A selected \Z | 219 | \A disabled \Z | 220 | \A readonly \Z | 221 | \A autofocus \Z | 222 | \A multiple \Z | 223 | \A ismap \Z | 224 | \A defer \Z | 225 | \A declare \Z | 226 | \A noresize \Z | 227 | \A nowrap \Z | 228 | \A noshade \Z | 229 | \A compact \Z 230 | ]iomx 231 | 232 | class HTMLSafe < ::String 233 | def html_safe 234 | self 235 | end 236 | 237 | def html_safe? 238 | true 239 | end 240 | 241 | def to_s 242 | self 243 | end 244 | end 245 | 246 | class Document < HTMLSafe 247 | def Document.for(other) 248 | Document === other ? other : Document.new(other.to_s) 249 | end 250 | 251 | def element 252 | Tagz.element.new(*a, &b) 253 | end 254 | alias_method 'e', 'element' 255 | 256 | alias_method 'write', 'concat' 257 | alias_method 'push', 'concat' 258 | 259 | def << obj 260 | if obj.respond_to?(:html_safe?) and obj.html_safe? 261 | super obj.to_s 262 | else 263 | super Tagz.escape_content(obj) 264 | end 265 | 266 | self 267 | end 268 | 269 | def concat(obj) 270 | self << obj 271 | end 272 | 273 | def escape(string) 274 | Tagz.escape(string) 275 | end 276 | alias_method 'h', 'escape' 277 | 278 | def puts(string) 279 | write "#{ string }\n" 280 | end 281 | 282 | def raw(string) 283 | push Document.for(string) 284 | end 285 | 286 | def document 287 | self 288 | end 289 | alias_method 'doc', 'document' 290 | 291 | def + other 292 | self.dup << other 293 | end 294 | 295 | def to_s 296 | self 297 | end 298 | 299 | def to_str 300 | self 301 | end 302 | end 303 | Tagz.singleton_class{ define_method(:document){ Tagz.namespace(:Document) } } 304 | 305 | class Element < ::String 306 | def Element.attributes(options) 307 | unless options.empty? 308 | +' ' << 309 | options.map do |key, value| 310 | key = Tagz.escape_key(key) 311 | value = Tagz.escape_value(value) 312 | if value =~ %r/"/ 313 | raise ArgumentError, value if value =~ %r/'/ 314 | value = "'#{ value }'" 315 | else 316 | raise ArgumentError, value if value =~ %r/"/ 317 | value = "\"#{ value }\"" 318 | end 319 | [key, value].join('=') 320 | end.join(' ') 321 | else 322 | '' 323 | end 324 | end 325 | 326 | attr 'name' 327 | 328 | def initialize(name, *argv, &block) 329 | options = {} 330 | content = [] 331 | 332 | argv.each do |arg| 333 | case arg 334 | when Hash 335 | options.update arg 336 | else 337 | content.push arg 338 | end 339 | end 340 | 341 | content.push block.call if block 342 | content.compact! 343 | 344 | @name = name.to_s 345 | 346 | if content.empty? 347 | replace "<#{ @name }#{ Element.attributes options }>" 348 | else 349 | replace "<#{ @name }#{ Element.attributes options }>#{ content.join }" 350 | end 351 | end 352 | end 353 | Tagz.singleton_class{ define_method(:element){ Tagz.namespace(:Element) } } 354 | 355 | NoEscapeContentProc = lambda{|*contents| contents.join} 356 | Tagz.singleton_class{ define_method(:no_escape_content_proc){ Tagz.namespace(:NoEscapeContentProc) } } 357 | EscapeContentProc = lambda{|*contents| Tagz.escapeHTML(contents.join)} 358 | Tagz.singleton_class{ define_method(:escape_content_proc){ Tagz.namespace(:EscapeContentProc) } } 359 | 360 | NoEscapeKeyProc = lambda{|*values| values.join} 361 | Tagz.singleton_class{ define_method(:no_escape_key_proc){ Tagz.namespace(:NoEscapeKeyProc) } } 362 | EscapeKeyProc = lambda{|*values| Tagz.escapeAttribute(values).sub(/\Adata_/imox, 'data-') } 363 | Tagz.singleton_class{ define_method(:escape_key_proc){ Tagz.namespace(:EscapeKeyProc) } } 364 | 365 | NoEscapeValueProc = lambda{|*values| values.join} 366 | Tagz.singleton_class{ define_method(:no_escape_value_proc){ Tagz.namespace(:NoEscapeValueProc) } } 367 | EscapeValueProc = lambda{|*values| Tagz.escapeAttribute(values)} 368 | Tagz.singleton_class{ define_method(:escape_value_proc){ Tagz.namespace(:EscapeValueProc) } } 369 | 370 | module Globally; include Tagz; end 371 | Tagz.singleton_class{ define_method(:globally){ Tagz.namespace(:Globally) } } 372 | 373 | module Privately; include Tagz; end 374 | Tagz.singleton_class{ define_method(:privately){ Tagz.namespace(:Privately) } } 375 | end 376 | end 377 | 378 | # escape utils 379 | # 380 | def Tagz.escape_html_map 381 | @escape_html_map ||= { '&' => '&', '>' => '>', '<' => '<', '"' => '"', "'" => ''' } 382 | end 383 | 384 | def Tagz.escape_html_once_regexp 385 | @escape_html_once_regexp ||= /["><']|&(?!([a-zA-Z]+|(#\d+));)/ 386 | end 387 | 388 | def Tagz.escape_html(s) 389 | s = s.to_s 390 | 391 | if Tagz.html_safe?(s) 392 | s 393 | else 394 | Tagz.html_safe(s.gsub(/[&"'><]/, Tagz.escape_html_map)) 395 | end 396 | end 397 | 398 | def Tagz.escape_html_once(s) 399 | result = s.to_s.gsub(Tagz.html_escape_once_regexp){|_| Tagz.escape_html_map[_]} 400 | 401 | Tagz.html_safe?(s) ? Tagz.html_safe(result) : result 402 | end 403 | 404 | def Tagz.escapeHTML(*strings) 405 | Tagz.escape_html(strings.join) 406 | end 407 | 408 | def Tagz.escape(*strings) 409 | Tagz.escape_html(strings.join) 410 | end 411 | 412 | def Tagz.escapeAttribute(*strings) 413 | Tagz.escape_html(strings.join) 414 | end 415 | 416 | # raw utils 417 | # 418 | def Tagz.html_safe(*args, &block) 419 | html_safe = namespace(:HTMLSafe) 420 | 421 | if args.empty? and block.nil? 422 | return html_safe 423 | end 424 | 425 | first = args.first 426 | 427 | case 428 | when first.is_a?(html_safe) 429 | return first 430 | 431 | when args.size == 1 432 | string = first 433 | html_safe.new(string) 434 | 435 | else 436 | string = [args, (block ? block.call : nil)].flatten.compact.join(' ') 437 | html_safe.new(string) 438 | end 439 | end 440 | 441 | def Tagz.html_safe?(string) 442 | string.html_safe? rescue false 443 | end 444 | 445 | def Tagz.raw(*args, &block) 446 | Tagz.html_safe(*args, &block) 447 | end 448 | 449 | def Tagz.h(string) 450 | Tagz.escape_html(string) 451 | end 452 | 453 | # generate code for escape configuration 454 | # 455 | %w( key value content ).each do |type| 456 | 457 | module_eval <<-__, __FILE__, __LINE__ 458 | def Tagz.escape_#{ type }!(*args, &block) 459 | previous = @escape_#{ type } if defined?(@escape_#{ type }) 460 | unless args.empty? 461 | value = args.shift 462 | value = Tagz.escape_#{ type }_proc if value==true 463 | value = Tagz.no_escape_#{ type }_proc if(value==false or value==nil) 464 | @escape_#{ type } = value.to_proc 465 | if block 466 | begin 467 | return block.call() 468 | ensure 469 | @escape_#{ type } = previous 470 | end 471 | else 472 | return previous 473 | end 474 | end 475 | @escape_#{ type } 476 | end 477 | 478 | def Tagz.escape_#{ type }s!(*args, &block) 479 | Tagz.escape_#{ type }!(*args, &block) 480 | end 481 | 482 | def Tagz.escape_#{ type }(value) 483 | @escape_#{ type }.call(value.to_s) 484 | end 485 | __ 486 | 487 | end 488 | 489 | # configure tagz escaping 490 | # 491 | def Tagz.escape!(options = {}) 492 | options = {:keys => options, :values => options, :content => options} unless options.is_a?(Hash) 493 | 494 | escape_keys = options[:keys]||options['keys']||options[:key]||options['key'] 495 | escape_values = options[:values]||options['values']||options[:value]||options['value'] 496 | escape_contents = options[:contents]||options['contents']||options[:content]||options['content'] 497 | 498 | Tagz.escape_keys!(!!escape_keys) 499 | Tagz.escape_values!(!!escape_values) 500 | Tagz.escape_contents!(!!escape_contents) 501 | end 502 | def Tagz.i_know_what_the_hell_i_am_doing! 503 | escape!(false) 504 | end 505 | def Tagz.i_do_not_know_what_the_hell_i_am_doing! 506 | escape!(true) 507 | end 508 | def Tagz.xml_mode! 509 | Tagz.escape!( 510 | :keys => true, 511 | :values => true, 512 | :contents => true 513 | ) 514 | end 515 | def Tagz.html_mode! 516 | Tagz.escape!( 517 | :keys => true, 518 | :values => false, 519 | :content => false 520 | ) 521 | end 522 | 523 | # allow access to instance methods via module handle 524 | # 525 | %w( tagz tagz__ __tagz method_missing ).each{|m| module_function(m)} 526 | end 527 | 528 | def Tagz(*argv, &block) 529 | (argv.empty? and block.nil?) ? ::Tagz : Tagz.tagz(*argv, &block) 530 | end 531 | 532 | def Tagz_(*argv, &block) 533 | (argv.empty? and block.nil?) ? ::Tagz : Tagz.tagz(*argv, &block) 534 | end 535 | 536 | Tagz.escape!(true) 537 | end 538 | -------------------------------------------------------------------------------- /lib/tagz/rails.rb: -------------------------------------------------------------------------------- 1 | # -*- encoding : utf-8 -*- 2 | if defined?(Rails) 3 | #_ = ActionView, ActionView::Base, ActionController, ActionController::Base 4 | #ActionView::Base.send(:include, Tagz.globally) 5 | #ActionController::Base.send(:include, Tagz) 6 | 7 | unloadable(Tagz) 8 | Tagz.xml_mode! 9 | end 10 | -------------------------------------------------------------------------------- /pkg/tagz-9.10.0.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ahoward/tagz/57245b75c3690e46c7bfca008e3a24adc0d0ec7c/pkg/tagz-9.10.0.gem -------------------------------------------------------------------------------- /readme.erb: -------------------------------------------------------------------------------- 1 | NAME 2 | 3 | tagz.rb 4 | 5 | SYNOPSIS 6 | 7 | require Tagz 8 | 9 | include Tagz.globally 10 | 11 | a_(:href => "/foo"){ "bar" } #=> bar 12 | 13 | DESCRIPTION 14 | 15 | tagz.rb is generates html, xml, or any sgml variant like a small ninja 16 | running across the backs of a herd of giraffes swatting of heads like a 17 | mark-up weedwacker. weighing in at less than 300 lines of code tagz.rb adds 18 | an html/xml/sgml syntax to ruby that is both unobtrusive, safe, and available 19 | globally to objects without the need for any builder or superfluous objects. 20 | tagz.rb is designed for applications that generate html to be able to do so 21 | easily in any context without heavyweight syntax or scoping issues, like a 22 | ninja sword through butter. 23 | 24 | FEATURES 25 | 26 | - use as a library or mixin 27 | 28 | - simple, clean and consistent mark-up that is easy to visually 29 | distinguish from other ruby methods 30 | 31 | - auto-compatibility with rails/actionview 32 | 33 | - ability to independently open and close tagz in markup 34 | 35 | - intelligent auto-escaping of both attributes and content for both html 36 | and xml 37 | 38 | - validate your html/xml with 'ruby -c' syntax check 39 | 40 | - generally bitchin 41 | 42 | - no lame method_missing approach that prevents tagz like 'type' from being 43 | generated 44 | 45 | RAILS 46 | 47 | in config/environment.rb 48 | 49 | require 'tagz' 50 | 51 | in a helper 52 | 53 | def list_of_users 54 | ul_(:class => 'users'){ 55 | @users.each{|user| li_{ user }} 56 | } 57 | end 58 | 59 | in a view 60 | 61 | table_{ 62 | rows.each do |row| 63 | tr_{ 64 | row.each do |cell| 65 | td_{ cell } 66 | end 67 | } 68 | end 69 | } 70 | 71 | in a controller 72 | 73 | def ajax_responder 74 | text = 75 | tagz{ 76 | table_{ 77 | rows.each do |row| 78 | tr_{ 79 | row.each do |cell| 80 | td_{ cell } 81 | end 82 | } 83 | end 84 | } 85 | } 86 | 87 | render :text => text 88 | end 89 | 90 | INSTALL 91 | 92 | gem install tagz 93 | 94 | URIS 95 | 96 | http://github.com/ahoward/tagz/tree/master 97 | http://rubyforge.org/projects/codeforpeople 98 | 99 | HISTORY 100 | 7.2.0 101 | - ruby19 compat 102 | 103 | 7.0.0 104 | - * IMPORTANT * NOT BACKWARD COMPATIBLE (thus version bump) 105 | the tagz functionality itself has not changed, but the defaults for 106 | excaping have! now tagz will escape attributes, but NOT content, in the 107 | default mode. you can easily configure something else with 108 | 109 | Tagz.escape!(:content => true, :attributes => true) 110 | 111 | which would be like saying 112 | 113 | Tagz.xml_mode! 114 | 115 | or 116 | 117 | Tagz.escape!(:content => false, :attributes => true) 118 | 119 | which would be like saying 120 | 121 | Tagz.html_mode! 122 | 123 | to repeat, the default is 'Tagz.html_mode!' 124 | 125 | 6.0.0 126 | - reorganize lib to avoid dumping a few constants into the includee - aka 127 | don't absolutely minimize namespace pollution. there is now reason to 128 | thing this version shouldn't be backwards compat - i bumped the version 129 | just in case 130 | 131 | 5.1.0 132 | - attribute/content auto-escaping can be turned off with 133 | 134 | Tagz.i_know_what_the_hell_i_am_doing! 135 | 136 | and turned back on with 137 | 138 | Tagz.i_do_not_know_what_the_hell_i_am_doing! 139 | 140 | attribute and content escaping can be configured individually too. see 141 | tests for examples 142 | 143 | 144 | thanks Dan Fitzpatrick 145 | 146 | - << and concat escape (if configured) while puts and push and write do not 147 | 148 | thanks JoelVanderWerf 149 | 150 | 5.0.0 151 | - introduce better escaping for attributes using xchar.rb approach 152 | - indroduce smart escaping for content 153 | - make Tagz.globally kick ass more hard 154 | - note that this version is not backward compatibile if you were relying 155 | on tagz never escaping any content should be an ok upgrade for most 156 | applications 157 | 158 | 4.6.0 159 | - fix a bug with self closing tagz that had crept in 1.0.0 -> 4.2.0. thx 160 | jeremy hinegardner 161 | 162 | - added tests from 1.0.0 back into svn 163 | 164 | 4.4.0 165 | - remove dependancy on cgi lib, tagz is now completely standalone 166 | 167 | 4.3.0 168 | - detect rails and auto-include into ActionController::Base and include 169 | globally into ActionView::Base 170 | 171 | 4.2.0 172 | - general lib cleanup 173 | - introduction of dual-mixin technique (Tagz.globally) 174 | - few small bug fixes 175 | - ninja tales 176 | 177 | SAMPLES 178 | 179 | <%= @samples %> 180 | -------------------------------------------------------------------------------- /samples/a.rb: -------------------------------------------------------------------------------- 1 | # -*- encoding : utf-8 -*- 2 | # 3 | # in the simplest case tagz generates html using a syntax which safely mixes 4 | # in to any object 5 | # 6 | 7 | require 'tagz' 8 | include Tagz.globally 9 | 10 | class GiraffeModel 11 | def link 12 | a_(:href => "/giraffe/neck/42"){ "whack!" } 13 | end 14 | end 15 | 16 | puts GiraffeModel.new.link 17 | -------------------------------------------------------------------------------- /samples/b.rb: -------------------------------------------------------------------------------- 1 | # -*- encoding : utf-8 -*- 2 | # 3 | # tagz.rb mixes quite easily with your favourite templating engine, avoiding 4 | # the need for '<% rows.each do |row| %> ... <% row.each do |cell| %> ' 5 | # madness and other types of logic to be coded in the templating language, 6 | # leaving templating to template engines and logic and looping to ruby - 7 | # unencumbered by extra funky syntax. in rails tagz will automatically be 8 | # available in your erb templates. 9 | # 10 | 11 | require 'tagz' 12 | include Tagz.globally 13 | 14 | require 'erb' 15 | 16 | rows = %w( a b c ), %w( 1 2 3 ) 17 | 18 | template = ERB.new <<-ERB 19 | 20 | 21 | <%= 22 | table_{ 23 | rows.each do |row| 24 | tr_{ 25 | row.each do |cell| 26 | td_{ cell } 27 | end 28 | } 29 | end 30 | } 31 | %> 32 | 33 | 34 | ERB 35 | 36 | puts template.result(binding) 37 | 38 | -------------------------------------------------------------------------------- /samples/c.rb: -------------------------------------------------------------------------------- 1 | # -*- encoding : utf-8 -*- 2 | # 3 | # once you've learned to generate html using tagz you're primed to generate 4 | # xml too 5 | # 6 | 7 | require 'tagz' 8 | include Tagz.globally 9 | 10 | doc = 11 | xml_{ 12 | giraffe_{ 'large' } 13 | ninja_{ 'small' } 14 | } 15 | 16 | puts doc 17 | -------------------------------------------------------------------------------- /samples/d.rb: -------------------------------------------------------------------------------- 1 | # -*- encoding : utf-8 -*- 2 | # 3 | # tagz.rb doesn't cramp your style, allowing even invalid html to be 4 | # generated. note the use of the 'tagz' method, which can be used both to 5 | # capture output and to append content to the top of the stack. 6 | # 7 | 8 | require 'tagz' 9 | include Tagz.globally 10 | 11 | def header 12 | tagz{ 13 | html_ 14 | body_(:class => 'ninja-like', :id => 'giraffe-slayer') 15 | 16 | __ "" 17 | } 18 | end 19 | 20 | def footer 21 | tagz{ 22 | __ "" 23 | 24 | _body 25 | _html 26 | } 27 | end 28 | 29 | puts header, footer 30 | -------------------------------------------------------------------------------- /samples/e.rb: -------------------------------------------------------------------------------- 1 | # -*- encoding : utf-8 -*- 2 | # 3 | # tagz.rb allows a safer method of mixin which requires any tagz methods to be 4 | # inside a tagz block - tagz generating methods outside a tagz block with 5 | # raise an error if tagz is included this way. also notice that the error is 6 | # reported from where it was raised - not from the bowels of the the tagz.rb 7 | # lib. 8 | # 9 | 10 | require 'tagz' 11 | include Tagz 12 | 13 | puts tagz{ 14 | html_{ 'works only in here' } 15 | } 16 | 17 | begin 18 | html_{ 'not out here' } 19 | rescue Object => e 20 | p :backtrace => e.backtrace 21 | end 22 | 23 | -------------------------------------------------------------------------------- /samples/f.rb: -------------------------------------------------------------------------------- 1 | # -*- encoding : utf-8 -*- 2 | # 3 | # tagz.rb can generate really compact html. this is great to save bandwidth 4 | # but can sometimes make reading the generated html a bit rough. of course 5 | # using tidy or the dom inspector in firebug obviates the issue; nevertheless 6 | # it's sometime nice to break things up a little. you can use 'tagz << "\n"' 7 | # or the special shorthand '__' or '___' to accomplish this 8 | # 9 | 10 | require 'tagz' 11 | include Tagz.globally 12 | 13 | html = 14 | div_{ 15 | span_{ true } 16 | __ 17 | span_{ false } # hey ryan, i fixed this ;-) 18 | ___ 19 | 20 | ___ 'foo & escaped bar' 21 | } 22 | 23 | puts html 24 | -------------------------------------------------------------------------------- /samples/g.rb: -------------------------------------------------------------------------------- 1 | # -*- encoding : utf-8 -*- 2 | # tagz gives you low-level control of the output and makes even dashersized 3 | # xml tagz easy enough to work with 4 | # 5 | 6 | require 'tagz' 7 | include Tagz.globally 8 | 9 | xml = 10 | root_{ 11 | tagz__('foo-bar', :key => 'foo&bar'){ 'content' } 12 | 13 | tagz__('bar-foo') 14 | tagz.concat 'content' 15 | tagz.concat tagz.escape('foo&bar') 16 | __tagz('bar-foo') 17 | } 18 | 19 | puts xml 20 | 21 | -------------------------------------------------------------------------------- /tagz.gemspec: -------------------------------------------------------------------------------- 1 | ## tagz.gemspec 2 | # 3 | 4 | Gem::Specification::new do |spec| 5 | spec.name = "tagz" 6 | spec.version = "9.10.0" 7 | spec.platform = Gem::Platform::RUBY 8 | spec.summary = "tagz" 9 | spec.description = "\n tagz.rb is generates html, xml, or any sgml variant like a small ninja\n running across the backs of a herd of giraffes swatting of heads like\n a mark-up weedwacker. weighing in at less than 300 lines of code\n tagz.rb adds an html/xml/sgml syntax to ruby that is both unobtrusive,\n safe, and available globally to objects without the need for any\n builder or superfluous objects. tagz.rb is designed for applications\n that generate html to be able to do so easily in any context without\n heavyweight syntax or scoping issues, like a ninja sword through\n butter.\n\n" 10 | spec.license = "Ruby" 11 | 12 | spec.files = 13 | ["README", 14 | "Rakefile", 15 | "lib", 16 | "lib/tagz", 17 | "lib/tagz.rb", 18 | "lib/tagz/rails.rb", 19 | "readme.erb", 20 | "samples", 21 | "samples/a.rb", 22 | "samples/b.rb", 23 | "samples/c.rb", 24 | "samples/d.rb", 25 | "samples/e.rb", 26 | "samples/f.rb", 27 | "samples/g.rb", 28 | "tagz.gemspec", 29 | "test", 30 | "test/tagz_test.rb"] 31 | 32 | spec.executables = [] 33 | 34 | spec.require_path = "lib" 35 | 36 | spec.test_files = nil 37 | 38 | 39 | 40 | spec.extensions.push(*[]) 41 | 42 | spec.rubyforge_project = "codeforpeople" 43 | spec.author = "Ara T. Howard" 44 | spec.email = "ara.t.howard@gmail.com" 45 | spec.homepage = "https://github.com/ahoward/tagz" 46 | end 47 | -------------------------------------------------------------------------------- /test/tagz_test.rb: -------------------------------------------------------------------------------- 1 | # -*- encoding : utf-8 -*- 2 | #! /usr/bin/env ruby 3 | 4 | require 'test/unit' 5 | 6 | $VERBOSE = 2 7 | STDOUT.sync = true 8 | $:.unshift 'lib' 9 | $:.unshift '../lib' 10 | $:.unshift '.' 11 | 12 | require 'tagz' 13 | 14 | class TagzTest < Test::Unit::TestCase 15 | include Tagz 16 | 17 | class ::String 18 | Equal = instance_method '==' 19 | remove_method '==' 20 | def == other 21 | Equal.bind(self.delete(' ')).call other.to_s.delete(' ') 22 | end 23 | end 24 | 25 | def test_000 26 | expected = '' 27 | actual = tagz{ 28 | foo_ 29 | _foo 30 | } 31 | assert_equal expected, actual 32 | end 33 | 34 | def test_010 35 | expected = '' 36 | actual = tagz{ 37 | foo_ 38 | bar_ 39 | _bar 40 | _foo 41 | } 42 | assert_equal expected, actual 43 | end 44 | 45 | def test_020 46 | expected = '' 47 | actual = tagz{ 48 | foo_ 49 | bar_{} 50 | _foo 51 | } 52 | assert_equal expected, actual 53 | end 54 | 55 | def test_030 56 | expected = '' 57 | actual = tagz{ 58 | foo_{ 59 | bar_{} 60 | } 61 | } 62 | assert_equal expected, actual 63 | end 64 | 65 | def test_040 66 | expected = 'bar' 67 | actual = tagz{ 68 | foo_{ 'bar' } 69 | } 70 | assert_equal expected, actual 71 | end 72 | 73 | def test_050 74 | expected = 'foobar' 75 | actual = tagz{ 76 | foo_{ 77 | bar_{ 'foobar' } 78 | } 79 | } 80 | assert_equal expected, actual 81 | end 82 | 83 | def test_060 84 | expected = 'foobar' 85 | actual = tagz{ 86 | foo_('key' => 'value'){ 87 | bar_(:a => :b){ 'foobar' } 88 | } 89 | } 90 | assert_equal expected, actual 91 | end 92 | 93 | def test_070 94 | expected = '' 95 | actual = tagz{ 96 | foo_{} + bar_{} 97 | } 98 | assert_equal expected, actual 99 | end 100 | 101 | =begin 102 | def test_080 103 | assert_raises(Tagz::NotOpen) do 104 | foo_{ _bar } 105 | end 106 | end 107 | def test_090 108 | assert_raises(Tagz::NotOpen) do 109 | _foo 110 | end 111 | end 112 | def test_100 113 | assert_nothing_raised do 114 | foo_ 115 | _foo 116 | end 117 | end 118 | =end 119 | 120 | def test_110 121 | expected = 'foobar' 122 | actual = tagz{ 123 | foo_{ 124 | bar_{ 'foobar' } 125 | 'this content is ignored because the block added content' 126 | } 127 | } 128 | assert_equal expected, actual 129 | end 130 | 131 | def test_120 132 | expected = 'foobarbarfoo' 133 | actual = tagz{ 134 | foo_{ 135 | bar_{ 'foobar' } 136 | baz_{ 'barfoo' } 137 | } 138 | } 139 | assert_equal expected, actual 140 | end 141 | 142 | def test_121 143 | expected = 'foobarbarfoo' 144 | actual = tagz{ 145 | foo_{ 146 | bar_{ 'foobar' } 147 | baz_{ 'barfoo' } 148 | } 149 | } 150 | assert_equal expected, actual 151 | end 152 | 153 | def test_130 154 | expected = 'afoobarbbarfoo' 155 | actual = tagz{ 156 | foo_{ |t| 157 | t << 'a' 158 | bar_{ 'foobar' } 159 | t << 'b' 160 | baz_{ 'barfoo' } 161 | } 162 | } 163 | assert_equal expected, actual 164 | end 165 | 166 | def test_140 167 | expected = 'baz' 168 | actual = tagz{ 169 | foo_{ 170 | bar_ << 'baz' 171 | _bar 172 | } 173 | } 174 | assert_equal expected, actual 175 | end 176 | 177 | def test_150 178 | expected = 'barbaz' 179 | actual = tagz{ 180 | foo_{ 181 | bar_ << 'bar' 182 | tag = baz_ 183 | tag << 'baz' 184 | _baz 185 | _bar 186 | } 187 | } 188 | assert_equal expected, actual 189 | end 190 | 191 | def test_160 192 | expected = 'ab' 193 | actual = tagz{ 194 | foo_{ |foo| 195 | foo << 'a' 196 | bar_{ |bar| 197 | bar << 'b' 198 | } 199 | } 200 | } 201 | assert_equal expected, actual 202 | end 203 | 204 | def test_170 205 | expected = '' 206 | @list = %w( a b c ) 207 | actual = tagz{ 208 | html_{ 209 | body_{ 210 | ul_{ 211 | @list.each{|elem| li_{ elem } } 212 | } 213 | } 214 | } 215 | } 216 | assert_equal expected, actual 217 | end 218 | 219 | def test_180 220 | expected = '42' 221 | actual = tagz{ 222 | html_{ 223 | b = body_ 224 | b << 42 225 | _body 226 | } 227 | } 228 | assert_equal expected, actual 229 | end 230 | 231 | def test_190 232 | expected = '42' 233 | actual = tagz{ 234 | html_{ 235 | body_ 236 | tagz << 42 ### tagz is always the current tag! 237 | _body 238 | } 239 | } 240 | assert_equal expected, actual 241 | end 242 | 243 | def test_200 244 | expected = '42' 245 | actual = tagz{ 246 | html_{ 247 | body_{ 248 | tagz << 42 ### tagz is always the current tag! 249 | } 250 | } 251 | } 252 | assert_equal expected, actual 253 | end 254 | 255 | def test_210 256 | expected = '42' 257 | actual = tagz{ 258 | html_{ 259 | body_{ |body| 260 | body << 42 261 | } 262 | } 263 | } 264 | assert_equal expected, actual 265 | end 266 | 267 | =begin 268 | def test_220 269 | expected = '42' 270 | actual = tagz{ 271 | 'html'.tag do 272 | 'body'.tag do 273 | 42 274 | end 275 | end 276 | } 277 | assert_equal expected, actual 278 | end 279 | =end 280 | 281 | def test_230 282 | expected = '
content
' 283 | actual = tagz{ 284 | html_{ 285 | body_{ 286 | div_(:k => :v){ "content" } 287 | } 288 | } 289 | } 290 | assert_equal expected, actual 291 | end 292 | 293 | def test_240 294 | expected = '
content
' 295 | actual = tagz{ 296 | html_{ 297 | body_{ 298 | div_ "content", :k => :v 299 | } 300 | } 301 | } 302 | assert_equal expected, actual 303 | end 304 | 305 | def test_241 306 | expected = '
content
' 307 | actual = tagz{ 308 | html_{ 309 | body_{ 310 | div_ "content", :k => :v 311 | _div 312 | } 313 | } 314 | } 315 | assert_equal expected, actual 316 | end 317 | 318 | def test_250 319 | expected = '
content and more content
' 320 | actual = tagz{ 321 | html_{ 322 | body_{ 323 | div_("content", :k => :v){ ' and more content' } 324 | } 325 | } 326 | } 327 | assert_equal expected, actual 328 | end 329 | 330 | def test_260 331 | expected = '
content
' 332 | actual = tagz{ 333 | html_{ 334 | body_{ 335 | div_ :k => :v 336 | tagz << "content" 337 | _div 338 | } 339 | } 340 | } 341 | assert_equal expected, actual 342 | end 343 | 344 | def test_270 345 | expected = '
content
' 346 | actual = tagz{ 347 | html_{ 348 | body_{ 349 | div_ :k => :v 350 | tagz << "content" 351 | _div 352 | } 353 | } 354 | } 355 | assert_equal expected, actual 356 | end 357 | 358 | def test_280 359 | expected = 'content' 360 | actual = tagz{ 361 | tagz << "content" 362 | } 363 | assert_equal expected, actual 364 | end 365 | 366 | def test_290 367 | expected = 'foobar' 368 | actual = tagz{ 369 | tagz { 370 | tagz << 'foo' << 'bar' 371 | } 372 | } 373 | assert_equal expected, actual 374 | end 375 | 376 | =begin 377 | def test_300 378 | expected = 'foobar' 379 | actual = tagz{ 380 | tagz{ tagz 'foo', 'bar' } 381 | } 382 | assert_equal expected, actual 383 | end 384 | =end 385 | 386 | def test_310 387 | expected = '
foobar
' 388 | actual = tagz{ 389 | html_{ 390 | body_{ 391 | div_! "foo", "bar", :k => :v 392 | } 393 | } 394 | } 395 | assert_equal expected, actual 396 | end 397 | 398 | def test_320 399 | expected = 'a|b|c' 400 | links = %w( a b c ) 401 | actual = tagz{ 402 | html_{ 403 | body_{ 404 | tagz.write links.map{|link| e(:a, :href => link){ link }}.join(e(:span){ '|' }) 405 | } 406 | } 407 | } 408 | assert_equal expected, actual 409 | end 410 | 411 | def test_330 412 | expected = '' 413 | actual = tagz{ 414 | tagz { 415 | a_ 416 | b_ 417 | c_ 418 | } 419 | } 420 | assert_equal expected, actual 421 | end 422 | 423 | def test_340 424 | expected = '' 425 | actual = tagz{ 426 | a_ { 427 | b_ 428 | c_ 429 | } 430 | } 431 | assert_equal expected, actual 432 | end 433 | 434 | def test_350 435 | expected = 'content' 436 | actual = tagz{ 437 | a_ { 438 | b_ 439 | c_ "content" 440 | } 441 | } 442 | assert_equal expected, actual 443 | end 444 | 445 | def test_360 446 | expected = 'contentmore content' 447 | actual = tagz{ 448 | a_ { 449 | b_ "content" 450 | c_ 451 | d_ "more content" 452 | } 453 | } 454 | assert_equal expected, actual 455 | end 456 | 457 | =begin 458 | def test_370 459 | expected = 'ab' 460 | actual = tagz{ 461 | re = 'a' 462 | re << tagz{'b'} 463 | re 464 | } 465 | assert_equal expected, actual 466 | end 467 | =end 468 | 469 | def test_380 470 | expected = 'ab' 471 | actual = tagz{ 472 | tagz{ 'a' } + tagz{ 'b' } 473 | } 474 | assert_equal expected, actual 475 | end 476 | 477 | def test_390 478 | expected = '
foo&bar>
' 479 | actual = tagz{ div_(:class => 'bar&foo>'){ 'foo&bar>' } } 480 | assert_equal expected, actual 481 | 482 | expected = %|
#{ expected }
| 483 | actual = tagz{ div_(:class => 'bar&foo>'){ actual } } 484 | assert_equal expected, actual 485 | end 486 | 487 | def test_400 488 | expected = '
foo&bar
' 489 | actual = tagz{ div_{ span_{ 'foo&bar' } } } 490 | assert_equal expected, actual 491 | end 492 | 493 | def test_410 494 | expected = '
false
' 495 | actual = tagz{ div_{ false } } 496 | assert_equal expected, actual 497 | end 498 | 499 | def test_420 500 | expected = "
\nfoobar\nfoobar\n
" 501 | actual = tagz{ div_{ __; span_{ :foobar }; ___('foobar'); } } 502 | assert_equal expected, actual 503 | end 504 | 505 | def test_430 506 | c = Class.new{ 507 | include Tagz.globally 508 | def foobar() div_{ 'foobar' } end 509 | }.new 510 | 511 | actual=nil 512 | assert_nothing_raised{ actual=c.foobar } 513 | expected = '
foobar
' 514 | assert_equal expected, actual 515 | 516 | =begin 517 | e = nil 518 | assert_raises(NoMethodError){ begin; c.missing; ensure; e=$!; end } 519 | assert e 520 | messages = e.backtrace.map{|line| line.split(%r/:/, 3).last} 521 | assert messages.all?{|message| message !~ /tagz/} 522 | =end 523 | end 524 | 525 | def test_440 526 | c = Class.new{ 527 | include Tagz.privately 528 | def foobar() tagz{ div_{ 'foobar' } } end 529 | def barfoo() div_{ 'barfoo' } end 530 | }.new 531 | 532 | actual=nil 533 | assert_nothing_raised{ actual=c.foobar } 534 | expected = '
foobar
' 535 | assert_equal expected, actual 536 | 537 | assert_raises(NoMethodError){ c.barfoo } 538 | end 539 | 540 | def test_450 541 | c = Class.new{ 542 | include Tagz.globally 543 | def a() tagz{ a_{ b(tagz); nil } } end 544 | def b(doc=nil) tagz(doc){ b_{ 'content' } } end 545 | }.new 546 | 547 | actual=nil 548 | assert_nothing_raised{ actual=c.a } 549 | expected = 'content' 550 | assert_equal expected, actual 551 | assert_nothing_raised{ c.b } 552 | end 553 | 554 | def test_460 555 | c = Class.new{ 556 | include Tagz.globally 557 | def a 558 | div_( 'a>b' => 'a>b' ){ 'content' } 559 | end 560 | }.new 561 | 562 | actual = nil 563 | assert_nothing_raised{ actual=c.a} 564 | expected = %(
content
) 565 | assert_equal expected, actual 566 | 567 | Tagz.escape_keys!(false) do 568 | Tagz.escape_values!(false) do 569 | actual = nil 570 | assert_nothing_raised{ actual=c.a} 571 | expected = %(
b="a>b">content
) 572 | assert_equal expected, actual 573 | end 574 | end 575 | 576 | upcased = lambda{|value| value.to_s.upcase} 577 | Tagz.escape_key!(upcased) do 578 | Tagz.escape_value!(upcased) do 579 | actual = nil 580 | assert_nothing_raised{ actual=c.a} 581 | expected = %(
B="A>B">content
) 582 | assert_equal expected, actual 583 | end 584 | end 585 | end 586 | 587 | def test_470 588 | c = Class.new{ 589 | include Tagz.globally 590 | def a 591 | div_( ){ 'a>b' } 592 | end 593 | }.new 594 | 595 | actual = nil 596 | assert_nothing_raised{ actual=c.a} 597 | expected = %(
a>b
) 598 | assert_equal expected, actual 599 | 600 | original = Tagz.escape_content!(true) 601 | assert original 602 | actual = nil 603 | assert_nothing_raised{ actual=c.a} 604 | expected = %(
a>b
) 605 | assert_equal expected, actual 606 | 607 | upcased = Tagz.escape_content!(lambda{|value| original.call(value).upcase}) 608 | assert upcased 609 | actual = nil 610 | assert_nothing_raised{ actual=c.a} 611 | expected = %(
A>B
) 612 | assert_equal expected, actual 613 | 614 | Tagz.escape_content!(original) 615 | actual = nil 616 | assert_nothing_raised{ actual=c.a} 617 | expected = %(
a>b
) 618 | assert_equal expected, actual 619 | ensure 620 | Tagz.escape_content!(original) 621 | end 622 | 623 | def test_480 624 | c = Class.new{ 625 | include Tagz.globally 626 | def a 627 | div_( 'a>b' => '<>'){ 'a>b' } 628 | end 629 | }.new 630 | 631 | Tagz.i_know_what_the_hell_i_am_doing! 632 | actual = nil 633 | assert_nothing_raised{ actual=c.a} 634 | expected = %(
b="<>">a>b
) 635 | assert_equal expected, actual 636 | ensure 637 | Tagz.i_do_not_know_what_the_hell_i_am_doing! 638 | end 639 | 640 | def test_490 641 | c = Class.new{ 642 | include Tagz.globally 643 | def a 644 | div_{ 645 | __ 646 | tagz.concat 'a>b' 647 | __ 648 | tagz.write 'c>d' 649 | __ 650 | tagz << 'e>f' 651 | __ 652 | tagz.push 'g>h' 653 | __ 654 | tagz.raw '' 655 | } 656 | end 657 | }.new 658 | 659 | actual = nil 660 | assert_nothing_raised{ actual=c.a} 661 | expected = "
\na>b\nc>d\ne>f\ng>h\n
" 662 | assert_equal expected, actual 663 | end 664 | 665 | def test_500 666 | expected = actual = nil 667 | Module.new do 668 | before = constants 669 | include Tagz 670 | after = constants 671 | expected = [] 672 | actual = after - before 673 | end 674 | assert_equal expected, actual 675 | end 676 | 677 | def test_510 678 | expected = actual = nil 679 | Module.new do 680 | before = constants 681 | include Tagz.globally 682 | after = constants 683 | expected = [] 684 | actual = after - before 685 | end 686 | assert_equal expected, actual 687 | end 688 | 689 | def test_520 690 | expected = actual = nil 691 | Module.new do 692 | before = constants 693 | include Tagz.privately 694 | after = constants 695 | expected = [] 696 | actual = after - before 697 | end 698 | assert_equal expected, actual 699 | end 700 | 701 | def test_530 702 | assert_nothing_raised{ 703 | code = <<-__ 704 | class C 705 | Element=NoEscape=Document=XChar=Privately=Escape=Globally=42 706 | include Tagz.globally 707 | def a() tagz{ 42 } end 708 | end 709 | C.new.a() 710 | __ 711 | assert_nothing_raised do 712 | assert eval(code), '42' 713 | end 714 | } 715 | end 716 | 717 | def test_540 718 | expected = 'foobar' 719 | actual = tagz{ 720 | foo_('checked' => true){ 721 | bar_(:selected => :selected){ 'foobar' } 722 | } 723 | } 724 | assert_equal expected, actual 725 | end 726 | 727 | 728 | def test_550 729 | assert_nothing_raised{ 730 | Tagz{ div_(:title => "foo' bar\""){ "foobar" } } 731 | } 732 | end 733 | 734 | def test_600 735 | value = '<>' 736 | html_safe = Tagz.html_safe(value) 737 | assert_equal(value, html_safe) 738 | assert_equal(false, value.respond_to?(:html_safe?)) 739 | assert_equal(false, value.respond_to?(:html_safe)) 740 | assert_equal(true, html_safe.respond_to?(:html_safe?)) 741 | assert_equal(true, html_safe.respond_to?(:html_safe)) 742 | end 743 | 744 | def test_610 745 | value = Tagz.html_safe.new('foobar') 746 | html_safe = Tagz.html_safe(value) 747 | assert_equal value.object_id, html_safe.object_id 748 | end 749 | 750 | def test_620 751 | expected = '
×
' 752 | actual = Tagz{ div_{ Tagz.html_safe('×') } } 753 | assert_equal expected, actual 754 | end 755 | end 756 | --------------------------------------------------------------------------------