crazy_ivan 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (32) hide show
  1. data/Rakefile +65 -45
  2. data/bin/crazy_ivan +2 -3
  3. data/crazy_ivan.gemspec +41 -1
  4. data/lib/crazy_ivan.rb +19 -11
  5. data/lib/crazy_ivan/report_assembler.rb +1 -2
  6. data/{templates → lib/crazy_ivan/templates}/css/ci.css +0 -0
  7. data/{templates → lib/crazy_ivan/templates}/index.html +0 -0
  8. data/{templates → lib/crazy_ivan/templates}/javascript/builder.js +0 -0
  9. data/{templates → lib/crazy_ivan/templates}/javascript/controls.js +0 -0
  10. data/{templates → lib/crazy_ivan/templates}/javascript/date.js +0 -0
  11. data/{templates → lib/crazy_ivan/templates}/javascript/dragdrop.js +0 -0
  12. data/{templates → lib/crazy_ivan/templates}/javascript/effects.js +0 -0
  13. data/{templates → lib/crazy_ivan/templates}/javascript/json-template.js +0 -0
  14. data/{templates → lib/crazy_ivan/templates}/javascript/prototype.js +0 -0
  15. data/{templates → lib/crazy_ivan/templates}/javascript/scriptaculous.js +0 -0
  16. data/{templates → lib/crazy_ivan/templates}/javascript/slider.js +0 -0
  17. data/lib/crazy_ivan/vendor/core_ext/tmpdir.rb +87 -0
  18. data/lib/crazy_ivan/vendor/json-1.1.7/core_ext/json/ext/generator/extconf.rb +11 -0
  19. data/lib/crazy_ivan/vendor/json-1.1.7/core_ext/json/ext/generator/generator.c +919 -0
  20. data/lib/crazy_ivan/vendor/json-1.1.7/core_ext/json/ext/generator/unicode.c +182 -0
  21. data/lib/crazy_ivan/vendor/json-1.1.7/core_ext/json/ext/generator/unicode.h +53 -0
  22. data/lib/crazy_ivan/vendor/json-1.1.7/core_ext/json/ext/parser/extconf.rb +11 -0
  23. data/lib/crazy_ivan/vendor/json-1.1.7/core_ext/json/ext/parser/parser.c +1829 -0
  24. data/lib/crazy_ivan/vendor/json-1.1.7/core_ext/json/ext/parser/parser.rl +686 -0
  25. data/lib/crazy_ivan/vendor/json-1.1.7/core_ext/json/ext/parser/unicode.c +154 -0
  26. data/lib/crazy_ivan/vendor/json-1.1.7/core_ext/json/ext/parser/unicode.h +58 -0
  27. data/lib/crazy_ivan/vendor/json.rb +4 -8
  28. data/lib/crazy_ivan/vendor/open4.rb +4 -8
  29. data/lib/crazy_ivan/vendor/tmpdir.rb +3 -0
  30. data/lib/crazy_ivan/version.rb +1 -7
  31. metadata +87 -59
  32. data/VERSION +0 -1
data/Rakefile CHANGED
@@ -1,56 +1,76 @@
1
- require 'rubygems'
2
1
  require 'rake'
2
+ require 'rake/clean'
3
+ require 'rake/gempackagetask'
4
+ require 'rake/testtask'
5
+ require 'fileutils'
6
+
7
+ $LOAD_PATH << 'lib'
8
+ require 'crazy_ivan'
9
+
10
+ include FileUtils
11
+
12
+ version = CrazyIvan::VERSION
13
+ name = "crazy_ivan"
14
+
15
+ spec = Gem::Specification.new do |s|
16
+ s.name = name
17
+ s.version = version
18
+ s.summary = 'Crazy Ivan (CI) is simplest possible continuous integration tool.'
19
+ s.description = "Continuous integration should really just be a script that captures the output of running your project update & test commands and presents recent results in a static html page.
20
+
21
+ By keeping test reports in json, per-project CI configuration in 3 probably-one-line scripts, things are kept simple, quick, and super extensible.
22
+
23
+ Want to use git, svn, or hg? No problem.
24
+ Need to fire off results to Campfire? It's built-in.
3
25
 
4
- begin
5
- require 'jeweler'
6
- Jeweler::Tasks.new do |gem|
7
- gem.name = "crazy_ivan"
8
- gem.summary = 'Crazy Ivan (CI) is simplest possible continuous integration tool.'
9
- gem.description = "Continuous integration should really just be a script that captures the output of running your project update & test commands and presents recent results in a static html page.
10
-
11
- By keeping test reports in json, per-project CI configuration in 3 probably-one-line scripts, things are kept simple, quick, and super extensible.
12
-
13
- Want to use git, svn, or hg? No problem.
14
- Need to fire off results to Campfire? It's built-in.
15
-
16
- CI depends on cron."
17
- gem.email = "edward@edwardog.net"
18
- gem.homepage = "http://github.com/edward/crazy_ivan"
19
- gem.authors = ["Edward Ocampo-Gooding"]
20
- gem.executables = ["crazy_ivan", "test_report2campfire"]
21
- gem.default_executable = "crazy_ivan"
22
- gem.files = FileList['.gitignore', '*.gemspec', 'lib/**/*', 'bin/*', 'templates/**/*', '[A-Z]*', 'test/**/*'].to_a
23
-
24
- # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
25
- end
26
+ CI depends on cron."
27
+ s.authors = "Edward Ocampo-Gooding"
26
28
 
27
- Jeweler::GemcutterTasks.new
29
+ s.email = 'edward@edwardog.net'
30
+ s.homepage = "http://github.com/edward/crazy_ivan"
31
+ s.executables = ["crazy_ivan", "test_report2campfire"]
32
+ s.default_executable = "crazy_ivan"
33
+ s.rubyforge_project = "crazy_ivan"
34
+
35
+ s.platform = Gem::Platform::RUBY
36
+ s.has_rdoc = false
37
+
38
+ # s.files = Dir.glob("{bin,lib}/**/*")
39
+ s.files = FileList['.gitignore', '*.gemspec', 'lib/**/*', 'bin/*', 'templates/**/*', '[A-Z]*', 'test/**/*'].to_a
40
+
41
+ s.require_path = "lib"
42
+ s.bindir = "bin"
28
43
 
29
- rescue LoadError
30
- puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
44
+ s.add_development_dependency('gemcutter', '>= 0.2.1')
45
+ s.add_development_dependency('mocha')
46
+
47
+ readme = File.read('README.rdoc')
48
+ s.post_install_message = '\n' + readme[0...readme.index('== How this works')]
31
49
  end
32
50
 
33
- require 'rake/testtask'
34
- Rake::TestTask.new(:test) do |test|
35
- test.libs << 'lib' << 'test'
36
- test.pattern = 'test/**/*_test.rb'
37
- test.verbose = true
51
+ Rake::GemPackageTask.new(spec) do |p|
52
+ p.need_tar = true if RUBY_PLATFORM !~ /mswin/
38
53
  end
39
54
 
40
- task :test => :check_dependencies
55
+ desc "Install #{name} gem (#{version})"
56
+ task :install => [ :test, :package ] do
57
+ sh %{gem install pkg/#{name}-#{version}.gem --no-rdoc --no-ri}
58
+ end
41
59
 
42
- task :default => :test
60
+ desc "Uninstall #{name} gem"
61
+ task :uninstall => [ :clean ] do
62
+ sh %{gem uninstall #{name}}
63
+ end
43
64
 
44
- require 'rake/rdoctask'
45
- Rake::RDocTask.new do |rdoc|
46
- if File.exist?('VERSION')
47
- version = File.read('VERSION')
48
- else
49
- version = ""
50
- end
51
-
52
- rdoc.rdoc_dir = 'rdoc'
53
- rdoc.title = "Crazy Ivan #{version}"
54
- rdoc.rdoc_files.include('README*')
55
- rdoc.rdoc_files.include('lib/**/*.rb')
65
+ desc "Release #{name} gem (#{version})"
66
+ task :release => [ :test, :package ] do
67
+ sh %{gem push pkg/#{name}-#{version}.gem}
56
68
  end
69
+
70
+ task :default => :test
71
+
72
+ Rake::TestTask.new(:test) do |test|
73
+ test.libs << 'lib' << 'test'
74
+ test.pattern = 'test/**/*_test.rb'
75
+ test.verbose = true
76
+ end
data/bin/crazy_ivan CHANGED
@@ -1,16 +1,15 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  begin
4
- gem 'crazy_ivan'
4
+ require 'crazy_ivan'
5
5
  rescue LoadError
6
6
  # If people are not using gems, the load path must still
7
7
  # be correct.
8
8
  # TODO: Remove the begin / rescue block somehow
9
- $:.unshift File.dirname(__FILE__) + '/../lib'
9
+ $LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
10
10
  retry
11
11
  end
12
12
 
13
- require 'crazy_ivan'
14
13
  require "optparse"
15
14
  require "logger"
16
15
 
data/crazy_ivan.gemspec CHANGED
@@ -9,7 +9,7 @@ Gem::Specification.new do |s|
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Edward Ocampo-Gooding"]
12
- s.date = %q{2010-01-10}
12
+ s.date = %q{2010-01-11}
13
13
  s.default_executable = %q{crazy_ivan}
14
14
  s.description = %q{Continuous integration should really just be a script that captures the output of running your project update & test commands and presents recent results in a static html page.
15
15
 
@@ -185,6 +185,46 @@ Gem::Specification.new do |s|
185
185
  "test/test_helper.rb"
186
186
  ]
187
187
  s.homepage = %q{http://github.com/edward/crazy_ivan}
188
+ s.post_install_message = %q{= Crazy Ivan
189
+
190
+ Crazy Ivan (CI) is simplest possible continuous integration tool.
191
+
192
+ == Usage
193
+
194
+ Create a directory where your projects will live
195
+ $ mkdir /var/continuous-integration
196
+
197
+ Place some project(s) in that directory
198
+ $ cd /var/continuous-integration
199
+ $ git clone git://github.com/edward/active_merchant.git
200
+
201
+ Set up continuous integration for each project
202
+ $ crazy_ivan setup # creates example ci scripts in
203
+ # each project (see How this works)
204
+
205
+
206
+
207
+ $ crazy_ivan setup # creates the ci directory, and
208
+ # creates a configuration file,
209
+ # sets a cron job to run crazy_ivan
210
+
211
+ Manually run it once to check everything is ok
212
+ $ cd /var/continuous-integration
213
+ $ crazy_ivan /var/www/ci # the test reports path should be
214
+ # accessible via your web server
215
+
216
+ $ open /var/www/ci/index.html # or check it through your browser
217
+
218
+ Set a cron job to run it every 15 minutes
219
+ $ echo "0,15,30,45 * * * * cd /var/continuous-integration; crazy_ivan /var/www/ci" > ci.cron
220
+ $ crontab ci.cron
221
+
222
+ Note that you don’t want this running too frequently; having overlapping
223
+ runs is possible and would be bad.
224
+
225
+ (Functionality to have this run as a web-hook is planned.)
226
+
227
+ }
188
228
  s.rdoc_options = ["--charset=UTF-8"]
189
229
  s.require_paths = ["lib"]
190
230
  s.rubygems_version = %q{1.3.5}
data/lib/crazy_ivan.rb CHANGED
@@ -6,8 +6,11 @@ require 'crazy_ivan/html_asset_crush'
6
6
  require 'crazy_ivan/version'
7
7
  require 'crazy_ivan/vendor/json'
8
8
  require 'crazy_ivan/vendor/open4'
9
+ require 'crazy_ivan/vendor/tmpdir'
9
10
 
10
11
  module CrazyIvan
12
+ # VERSION = '1.0.0'
13
+
11
14
  def self.setup
12
15
  puts
13
16
  puts "Preparing per-project continuous integration configuration scripts"
@@ -18,9 +21,9 @@ module CrazyIvan
18
21
  FileUtils.mkdir_p('.ci')
19
22
 
20
23
  Dir.chdir('.ci') do
21
- puts " #{dir}/.ci/update"
24
+ puts " #{dir}/.ci"
22
25
  if File.exists?('version')
23
- puts " #{' ' * (dir + "/.ci").size}/version already exists - skipping"
26
+ puts " #{' ' * (dir + "/.ci").size}/version already exists - skipping"
24
27
  else
25
28
  File.open('version', 'w+') do |f|
26
29
  f.puts "#!/usr/bin/env ruby"
@@ -31,23 +34,23 @@ module CrazyIvan
31
34
  f.puts
32
35
  f.puts "puts `git show`[/^commit (.+)$/, 1]"
33
36
  end
34
- puts " #{' ' * (dir + "/.ci").size}/version -- created"
37
+ puts " #{' ' * (dir + "/.ci").size}/version -- created"
35
38
  end
36
39
 
37
40
  if File.exists?('update')
38
- puts " #{' ' * (dir + "/.ci").size}/update already exists - skipping"
41
+ puts " #{' ' * (dir + "/.ci").size}/update already exists - skipping"
39
42
  else
40
43
  File.open('update', 'w+') do |f|
41
44
  f.puts "#!/usr/bin/env bash"
42
45
  f.puts
43
46
  f.puts "# This script updates your code"
44
47
  f.puts "#"
45
- f.puts "# If you cant use a version control system, this script could just do some"
48
+ f.puts "# If you can't use a version control system, this script could just do some"
46
49
  f.puts "# some basic copying commands."
47
50
  f.puts
48
51
  f.puts "git pull"
49
52
  end
50
- puts " #{' ' * (dir + "/.ci").size}/update -- created"
53
+ puts " #{' ' * (dir + "/.ci").size}/update -- created"
51
54
  end
52
55
 
53
56
  if File.exists?('test')
@@ -61,11 +64,11 @@ module CrazyIvan
61
64
  f.puts
62
65
  f.puts "rake"
63
66
  end
64
- puts " #{' ' * (dir + "/.ci").size}/test -- created"
67
+ puts " #{' ' * (dir + "/.ci").size}/test -- created"
65
68
  end
66
69
 
67
70
  if File.exists?('conclusion')
68
- puts " #{' ' * (dir + "/.ci").size}/conclusion already exists -- skipping"
71
+ puts " #{' ' * (dir + "/.ci").size}/conclusion already exists -- skipping"
69
72
  else
70
73
  File.open('conclusion', 'w+') do |f|
71
74
  f.puts "#!/usr/bin/env ruby"
@@ -82,7 +85,7 @@ module CrazyIvan
82
85
  f.puts "# IO.popen(\"test_report2campfire \#{CAMPFIRE_ROOM_URL} \#{CAMPFIRE_API_KEY} \#{CRAZY_IVAN_REPORTS_URL}\", 'w') {|f| f.puts STDIN.read }"
83
86
  f.puts
84
87
  end
85
- puts " #{' ' * (dir + "/.ci").size}/conclusion -- created"
88
+ puts " #{' ' * (dir + "/.ci").size}/conclusion -- created"
86
89
  end
87
90
  puts
88
91
 
@@ -93,15 +96,20 @@ module CrazyIvan
93
96
 
94
97
  puts "Take a look at those 4 scripts to make sure they each do the right thing."
95
98
  puts
96
- puts "When you're ready, run the following from the projects directory (here):"
99
+ puts "When you're ready, run crazy_ivan manually from the projects directory (here):"
97
100
  puts
98
101
  puts " crazy_ivan /path/to/directory/your/reports/go"
99
102
  puts
100
103
  puts "then look at index.html in that path to confirm that everything is ok."
101
104
  puts
102
- puts "If things look good, then set up a cron task or other script to run"
105
+ puts "If things look good, then set up a cron job or other script to run"
103
106
  puts "crazy_ivan on a periodic basis."
104
107
  puts
108
+ puts "To setup a cron job to run crazy_ivan every 15 minutes, do this:"
109
+ puts " $ echo \"0,15,30,45 * * * * cd /var/continuous-integration; crazy_ivan /var/www/ci\" > ci.cron"
110
+ puts " $ crontab ci.cron"
111
+ puts
112
+ puts
105
113
  end
106
114
 
107
115
  def self.generate_test_reports_in(output_directory)
@@ -1,7 +1,6 @@
1
1
  class ReportAssembler
2
2
  MAXIMUM_RECENTS = 10
3
- ROOT_PATH = File.expand_path(File.dirname(__FILE__))
4
- TEMPLATES_PATH = File.join(ROOT_PATH, *%w[.. .. templates])
3
+ TEMPLATES_PATH = File.expand_path(File.dirname(__FILE__)) + '/templates'
5
4
 
6
5
  attr_accessor :runners
7
6
 
File without changes
File without changes
@@ -0,0 +1,87 @@
1
+ # ree-1.8.6 does not support Dir.mktmpdir
2
+
3
+ require 'fileutils'
4
+
5
+ class Dir
6
+ # Dir.mktmpdir creates a temporary directory.
7
+ #
8
+ # The directory is created with 0700 permission.
9
+ #
10
+ # The prefix and suffix of the name of the directory is specified by
11
+ # the optional first argument, <i>prefix_suffix</i>.
12
+ # - If it is not specified or nil, "d" is used as the prefix and no suffix is used.
13
+ # - If it is a string, it is used as the prefix and no suffix is used.
14
+ # - If it is an array, first element is used as the prefix and second element is used as a suffix.
15
+ #
16
+ # Dir.mktmpdir {|dir| dir is ".../d..." }
17
+ # Dir.mktmpdir("foo") {|dir| dir is ".../foo..." }
18
+ # Dir.mktmpdir(["foo", "bar"]) {|dir| dir is ".../foo...bar" }
19
+ #
20
+ # The directory is created under Dir.tmpdir or
21
+ # the optional second argument <i>tmpdir</i> if non-nil value is given.
22
+ #
23
+ # Dir.mktmpdir {|dir| dir is "#{Dir.tmpdir}/d..." }
24
+ # Dir.mktmpdir(nil, "/var/tmp") {|dir| dir is "/var/tmp/d..." }
25
+ #
26
+ # If a block is given,
27
+ # it is yielded with the path of the directory.
28
+ # The directory and its contents are removed
29
+ # using FileUtils.remove_entry_secure before Dir.mktmpdir returns.
30
+ # The value of the block is returned.
31
+ #
32
+ # Dir.mktmpdir {|dir|
33
+ # # use the directory...
34
+ # open("#{dir}/foo", "w") { ... }
35
+ # }
36
+ #
37
+ # If a block is not given,
38
+ # The path of the directory is returned.
39
+ # In this case, Dir.mktmpdir doesn't remove the directory.
40
+ #
41
+ # dir = Dir.mktmpdir
42
+ # begin
43
+ # # use the directory...
44
+ # open("#{dir}/foo", "w") { ... }
45
+ # ensure
46
+ # # remove the directory.
47
+ # FileUtils.remove_entry_secure dir
48
+ # end
49
+ #
50
+ def Dir.mktmpdir(prefix_suffix=nil, tmpdir=nil)
51
+ case prefix_suffix
52
+ when nil
53
+ prefix = "d"
54
+ suffix = ""
55
+ when String
56
+ prefix = prefix_suffix
57
+ suffix = ""
58
+ when Array
59
+ prefix = prefix_suffix[0]
60
+ suffix = prefix_suffix[1]
61
+ else
62
+ raise ArgumentError, "unexpected prefix_suffix: #{prefix_suffix.inspect}"
63
+ end
64
+ tmpdir ||= Dir.tmpdir
65
+ t = Time.now.strftime("%Y%m%d")
66
+ n = nil
67
+ begin
68
+ path = "#{tmpdir}/#{prefix}#{t}-#{$$}-#{rand(0x100000000).to_s(36)}"
69
+ path << "-#{n}" if n
70
+ path << suffix
71
+ Dir.mkdir(path, 0700)
72
+ rescue Errno::EEXIST
73
+ n ||= 0
74
+ n += 1
75
+ retry
76
+ end
77
+ if block_given?
78
+ begin
79
+ yield path
80
+ ensure
81
+ FileUtils.remove_entry_secure path
82
+ end
83
+ else
84
+ path
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,11 @@
1
+ require 'mkmf'
2
+ require 'rbconfig'
3
+
4
+ if CONFIG['CC'] =~ /gcc/
5
+ $CFLAGS += ' -Wall'
6
+ #$CFLAGS += ' -O0 -ggdb'
7
+ end
8
+
9
+ have_header("ruby/st.h") || have_header("st.h")
10
+ have_header("ruby/encoding.h")
11
+ create_makefile 'generator'
@@ -0,0 +1,919 @@
1
+ #include <string.h>
2
+ #include "ruby.h"
3
+ #if HAVE_RUBY_ST_H
4
+ #include "ruby/st.h"
5
+ #endif
6
+ #if HAVE_ST_H
7
+ #include "st.h"
8
+ #endif
9
+ #include "unicode.h"
10
+ #include <math.h>
11
+
12
+ #ifndef RHASH_TBL
13
+ #define RHASH_TBL(hsh) (RHASH(hsh)->tbl)
14
+ #endif
15
+
16
+ #ifndef RHASH_SIZE
17
+ #define RHASH_SIZE(hsh) (RHASH(hsh)->tbl->num_entries)
18
+ #endif
19
+
20
+ #ifndef RFLOAT_VALUE
21
+ #define RFLOAT_VALUE(val) (RFLOAT(val)->value)
22
+ #endif
23
+
24
+ #ifdef HAVE_RUBY_ENCODING_H
25
+ #include "ruby/encoding.h"
26
+ #define FORCE_UTF8(obj) rb_enc_associate((obj), rb_utf8_encoding())
27
+ #else
28
+ #define FORCE_UTF8(obj)
29
+ #endif
30
+
31
+ #define check_max_nesting(state, depth) do { \
32
+ long current_nesting = 1 + depth; \
33
+ if (state->max_nesting != 0 && current_nesting > state->max_nesting) \
34
+ rb_raise(eNestingError, "nesting of %ld is too deep", current_nesting); \
35
+ } while (0);
36
+
37
+ static VALUE mJSON, mExt, mGenerator, cState, mGeneratorMethods, mObject,
38
+ mHash, mArray, mInteger, mFloat, mString, mString_Extend,
39
+ mTrueClass, mFalseClass, mNilClass, eGeneratorError,
40
+ eCircularDatastructure, eNestingError;
41
+
42
+ static ID i_to_s, i_to_json, i_new, i_indent, i_space, i_space_before,
43
+ i_object_nl, i_array_nl, i_check_circular, i_max_nesting,
44
+ i_allow_nan, i_pack, i_unpack, i_create_id, i_extend;
45
+
46
+ typedef struct JSON_Generator_StateStruct {
47
+ VALUE indent;
48
+ VALUE space;
49
+ VALUE space_before;
50
+ VALUE object_nl;
51
+ VALUE array_nl;
52
+ int check_circular;
53
+ VALUE seen;
54
+ VALUE memo;
55
+ VALUE depth;
56
+ long max_nesting;
57
+ int flag;
58
+ int allow_nan;
59
+ } JSON_Generator_State;
60
+
61
+ #define GET_STATE(self) \
62
+ JSON_Generator_State *state; \
63
+ Data_Get_Struct(self, JSON_Generator_State, state);
64
+
65
+ /*
66
+ * Document-module: JSON::Ext::Generator
67
+ *
68
+ * This is the JSON generator implemented as a C extension. It can be
69
+ * configured to be used by setting
70
+ *
71
+ * JSON.generator = JSON::Ext::Generator
72
+ *
73
+ * with the method generator= in JSON.
74
+ *
75
+ */
76
+
77
+ static int hash_to_json_state_i(VALUE key, VALUE value, VALUE Vstate)
78
+ {
79
+ VALUE json, buf, Vdepth;
80
+ GET_STATE(Vstate);
81
+ buf = state->memo;
82
+ Vdepth = state->depth;
83
+
84
+ if (key == Qundef) return ST_CONTINUE;
85
+ if (state->flag) {
86
+ state->flag = 0;
87
+ rb_str_buf_cat2(buf, ",");
88
+ if (RSTRING_LEN(state->object_nl)) rb_str_buf_append(buf, state->object_nl);
89
+ }
90
+ if (RSTRING_LEN(state->object_nl)) {
91
+ rb_str_buf_append(buf, rb_str_times(state->indent, Vdepth));
92
+ }
93
+ json = rb_funcall(rb_funcall(key, i_to_s, 0), i_to_json, 2, Vstate, Vdepth);
94
+ Check_Type(json, T_STRING);
95
+ rb_str_buf_append(buf, json);
96
+ OBJ_INFECT(buf, json);
97
+ if (RSTRING_LEN(state->space_before)) {
98
+ rb_str_buf_append(buf, state->space_before);
99
+ }
100
+ rb_str_buf_cat2(buf, ":");
101
+ if (RSTRING_LEN(state->space)) rb_str_buf_append(buf, state->space);
102
+ json = rb_funcall(value, i_to_json, 2, Vstate, Vdepth);
103
+ Check_Type(json, T_STRING);
104
+ state->flag = 1;
105
+ rb_str_buf_append(buf, json);
106
+ OBJ_INFECT(buf, json);
107
+ state->depth = Vdepth;
108
+ state->memo = buf;
109
+ return ST_CONTINUE;
110
+ }
111
+
112
+ inline static VALUE mHash_json_transfrom(VALUE self, VALUE Vstate, VALUE Vdepth) {
113
+ long depth, len = RHASH_SIZE(self);
114
+ VALUE result;
115
+ GET_STATE(Vstate);
116
+
117
+ depth = 1 + FIX2LONG(Vdepth);
118
+ result = rb_str_buf_new(len);
119
+ state->memo = result;
120
+ state->depth = LONG2FIX(depth);
121
+ state->flag = 0;
122
+ rb_str_buf_cat2(result, "{");
123
+ if (RSTRING_LEN(state->object_nl)) rb_str_buf_append(result, state->object_nl);
124
+ rb_hash_foreach(self, hash_to_json_state_i, Vstate);
125
+ if (RSTRING_LEN(state->object_nl)) rb_str_buf_append(result, state->object_nl);
126
+ if (RSTRING_LEN(state->object_nl)) {
127
+ rb_str_buf_append(result, rb_str_times(state->indent, Vdepth));
128
+ }
129
+ rb_str_buf_cat2(result, "}");
130
+ return result;
131
+ }
132
+
133
+ static int hash_to_json_i(VALUE key, VALUE value, VALUE buf)
134
+ {
135
+ VALUE tmp;
136
+
137
+ if (key == Qundef) return ST_CONTINUE;
138
+ if (RSTRING_LEN(buf) > 1) rb_str_buf_cat2(buf, ",");
139
+ tmp = rb_funcall(rb_funcall(key, i_to_s, 0), i_to_json, 0);
140
+ Check_Type(tmp, T_STRING);
141
+ rb_str_buf_append(buf, tmp);
142
+ OBJ_INFECT(buf, tmp);
143
+ rb_str_buf_cat2(buf, ":");
144
+ tmp = rb_funcall(value, i_to_json, 0);
145
+ Check_Type(tmp, T_STRING);
146
+ rb_str_buf_append(buf, tmp);
147
+ OBJ_INFECT(buf, tmp);
148
+
149
+ return ST_CONTINUE;
150
+ }
151
+
152
+ /*
153
+ * call-seq: to_json(state = nil, depth = 0)
154
+ *
155
+ * Returns a JSON string containing a JSON object, that is unparsed from
156
+ * this Hash instance.
157
+ * _state_ is a JSON::State object, that can also be used to configure the
158
+ * produced JSON string output further.
159
+ * _depth_ is used to find out nesting depth, to indent accordingly.
160
+ */
161
+ static VALUE mHash_to_json(int argc, VALUE *argv, VALUE self)
162
+ {
163
+ VALUE Vstate, Vdepth, result;
164
+ long depth;
165
+
166
+ rb_scan_args(argc, argv, "02", &Vstate, &Vdepth);
167
+ depth = NIL_P(Vdepth) ? 0 : FIX2LONG(Vdepth);
168
+ if (NIL_P(Vstate)) {
169
+ long len = RHASH_SIZE(self);
170
+ result = rb_str_buf_new(len);
171
+ rb_str_buf_cat2(result, "{");
172
+ rb_hash_foreach(self, hash_to_json_i, result);
173
+ rb_str_buf_cat2(result, "}");
174
+ } else {
175
+ GET_STATE(Vstate);
176
+ check_max_nesting(state, depth);
177
+ if (state->check_circular) {
178
+ VALUE self_id = rb_obj_id(self);
179
+ if (RTEST(rb_hash_aref(state->seen, self_id))) {
180
+ rb_raise(eCircularDatastructure,
181
+ "circular data structures not supported!");
182
+ }
183
+ rb_hash_aset(state->seen, self_id, Qtrue);
184
+ result = mHash_json_transfrom(self, Vstate, LONG2FIX(depth));
185
+ rb_hash_delete(state->seen, self_id);
186
+ } else {
187
+ result = mHash_json_transfrom(self, Vstate, LONG2FIX(depth));
188
+ }
189
+ }
190
+ OBJ_INFECT(result, self);
191
+ FORCE_UTF8(result);
192
+ return result;
193
+ }
194
+
195
+ inline static VALUE mArray_json_transfrom(VALUE self, VALUE Vstate, VALUE Vdepth) {
196
+ long i, len = RARRAY_LEN(self);
197
+ VALUE shift, result;
198
+ long depth = NIL_P(Vdepth) ? 0 : FIX2LONG(Vdepth);
199
+ VALUE delim = rb_str_new2(",");
200
+ GET_STATE(Vstate);
201
+
202
+ check_max_nesting(state, depth);
203
+ if (state->check_circular) {
204
+ VALUE self_id = rb_obj_id(self);
205
+ rb_hash_aset(state->seen, self_id, Qtrue);
206
+ result = rb_str_buf_new(len);
207
+ if (RSTRING_LEN(state->array_nl)) rb_str_append(delim, state->array_nl);
208
+ shift = rb_str_times(state->indent, LONG2FIX(depth + 1));
209
+
210
+ rb_str_buf_cat2(result, "[");
211
+ OBJ_INFECT(result, self);
212
+ rb_str_buf_append(result, state->array_nl);
213
+ for (i = 0; i < len; i++) {
214
+ VALUE element = RARRAY_PTR(self)[i];
215
+ if (RTEST(rb_hash_aref(state->seen, rb_obj_id(element)))) {
216
+ rb_raise(eCircularDatastructure,
217
+ "circular data structures not supported!");
218
+ }
219
+ OBJ_INFECT(result, element);
220
+ if (i > 0) rb_str_buf_append(result, delim);
221
+ rb_str_buf_append(result, shift);
222
+ element = rb_funcall(element, i_to_json, 2, Vstate, LONG2FIX(depth + 1));
223
+ Check_Type(element, T_STRING);
224
+ rb_str_buf_append(result, element);
225
+ }
226
+ if (RSTRING_LEN(state->array_nl)) {
227
+ rb_str_buf_append(result, state->array_nl);
228
+ rb_str_buf_append(result, rb_str_times(state->indent, LONG2FIX(depth)));
229
+ }
230
+ rb_str_buf_cat2(result, "]");
231
+ rb_hash_delete(state->seen, self_id);
232
+ } else {
233
+ result = rb_str_buf_new(len);
234
+ OBJ_INFECT(result, self);
235
+ if (RSTRING_LEN(state->array_nl)) rb_str_append(delim, state->array_nl);
236
+ shift = rb_str_times(state->indent, LONG2FIX(depth + 1));
237
+
238
+ rb_str_buf_cat2(result, "[");
239
+ rb_str_buf_append(result, state->array_nl);
240
+ for (i = 0; i < len; i++) {
241
+ VALUE element = RARRAY_PTR(self)[i];
242
+ OBJ_INFECT(result, element);
243
+ if (i > 0) rb_str_buf_append(result, delim);
244
+ rb_str_buf_append(result, shift);
245
+ element = rb_funcall(element, i_to_json, 2, Vstate, LONG2FIX(depth + 1));
246
+ Check_Type(element, T_STRING);
247
+ rb_str_buf_append(result, element);
248
+ }
249
+ rb_str_buf_append(result, state->array_nl);
250
+ if (RSTRING_LEN(state->array_nl)) {
251
+ rb_str_buf_append(result, rb_str_times(state->indent, LONG2FIX(depth)));
252
+ }
253
+ rb_str_buf_cat2(result, "]");
254
+ }
255
+ return result;
256
+ }
257
+
258
+ /*
259
+ * call-seq: to_json(state = nil, depth = 0)
260
+ *
261
+ * Returns a JSON string containing a JSON array, that is unparsed from
262
+ * this Array instance.
263
+ * _state_ is a JSON::State object, that can also be used to configure the
264
+ * produced JSON string output further.
265
+ * _depth_ is used to find out nesting depth, to indent accordingly.
266
+ */
267
+ static VALUE mArray_to_json(int argc, VALUE *argv, VALUE self) {
268
+ VALUE Vstate, Vdepth, result;
269
+
270
+ rb_scan_args(argc, argv, "02", &Vstate, &Vdepth);
271
+ if (NIL_P(Vstate)) {
272
+ long i, len = RARRAY_LEN(self);
273
+ result = rb_str_buf_new(2 + 2 * len);
274
+ rb_str_buf_cat2(result, "[");
275
+ OBJ_INFECT(result, self);
276
+ for (i = 0; i < len; i++) {
277
+ VALUE element = RARRAY_PTR(self)[i];
278
+ OBJ_INFECT(result, element);
279
+ if (i > 0) rb_str_buf_cat2(result, ",");
280
+ element = rb_funcall(element, i_to_json, 0);
281
+ Check_Type(element, T_STRING);
282
+ rb_str_buf_append(result, element);
283
+ }
284
+ rb_str_buf_cat2(result, "]");
285
+ } else {
286
+ result = mArray_json_transfrom(self, Vstate, Vdepth);
287
+ }
288
+ OBJ_INFECT(result, self);
289
+ FORCE_UTF8(result);
290
+ return result;
291
+ }
292
+
293
+ /*
294
+ * call-seq: to_json(*)
295
+ *
296
+ * Returns a JSON string representation for this Integer number.
297
+ */
298
+ static VALUE mInteger_to_json(int argc, VALUE *argv, VALUE self)
299
+ {
300
+ VALUE result = rb_funcall(self, i_to_s, 0);
301
+ FORCE_UTF8(result);
302
+ return result;
303
+ }
304
+
305
+ /*
306
+ * call-seq: to_json(*)
307
+ *
308
+ * Returns a JSON string representation for this Float number.
309
+ */
310
+ static VALUE mFloat_to_json(int argc, VALUE *argv, VALUE self)
311
+ {
312
+ JSON_Generator_State *state = NULL;
313
+ VALUE Vstate, rest, tmp, result;
314
+ double value = RFLOAT_VALUE(self);
315
+ rb_scan_args(argc, argv, "01*", &Vstate, &rest);
316
+ if (!NIL_P(Vstate)) Data_Get_Struct(Vstate, JSON_Generator_State, state);
317
+ if (isinf(value)) {
318
+ if (!state || state->allow_nan) {
319
+ result = rb_funcall(self, i_to_s, 0);
320
+ } else {
321
+ tmp = rb_funcall(self, i_to_s, 0);
322
+ rb_raise(eGeneratorError, "%u: %s not allowed in JSON", __LINE__, StringValueCStr(tmp));
323
+ }
324
+ } else if (isnan(value)) {
325
+ if (!state || state->allow_nan) {
326
+ result = rb_funcall(self, i_to_s, 0);
327
+ } else {
328
+ tmp = rb_funcall(self, i_to_s, 0);
329
+ rb_raise(eGeneratorError, "%u: %s not allowed in JSON", __LINE__, StringValueCStr(tmp));
330
+ }
331
+ } else {
332
+ result = rb_funcall(self, i_to_s, 0);
333
+ }
334
+ FORCE_UTF8(result);
335
+ return result;
336
+ }
337
+
338
+ /*
339
+ * call-seq: String.included(modul)
340
+ *
341
+ * Extends _modul_ with the String::Extend module.
342
+ */
343
+ static VALUE mString_included_s(VALUE self, VALUE modul) {
344
+ VALUE result = rb_funcall(modul, i_extend, 1, mString_Extend);
345
+ FORCE_UTF8(result);
346
+ return result;
347
+ }
348
+
349
+ /*
350
+ * call-seq: to_json(*)
351
+ *
352
+ * This string should be encoded with UTF-8 A call to this method
353
+ * returns a JSON string encoded with UTF16 big endian characters as
354
+ * \u????.
355
+ */
356
+ static VALUE mString_to_json(int argc, VALUE *argv, VALUE self)
357
+ {
358
+ VALUE result = rb_str_buf_new(RSTRING_LEN(self));
359
+ rb_str_buf_cat2(result, "\"");
360
+ JSON_convert_UTF8_to_JSON(result, self, strictConversion);
361
+ rb_str_buf_cat2(result, "\"");
362
+ FORCE_UTF8(result);
363
+ return result;
364
+ }
365
+
366
+ /*
367
+ * call-seq: to_json_raw_object()
368
+ *
369
+ * This method creates a raw object hash, that can be nested into
370
+ * other data structures and will be unparsed as a raw string. This
371
+ * method should be used, if you want to convert raw strings to JSON
372
+ * instead of UTF-8 strings, e. g. binary data.
373
+ */
374
+ static VALUE mString_to_json_raw_object(VALUE self) {
375
+ VALUE ary;
376
+ VALUE result = rb_hash_new();
377
+ rb_hash_aset(result, rb_funcall(mJSON, i_create_id, 0), rb_class_name(rb_obj_class(self)));
378
+ ary = rb_funcall(self, i_unpack, 1, rb_str_new2("C*"));
379
+ rb_hash_aset(result, rb_str_new2("raw"), ary);
380
+ FORCE_UTF8(result);
381
+ return result;
382
+ }
383
+
384
+ /*
385
+ * call-seq: to_json_raw(*args)
386
+ *
387
+ * This method creates a JSON text from the result of a call to
388
+ * to_json_raw_object of this String.
389
+ */
390
+ static VALUE mString_to_json_raw(int argc, VALUE *argv, VALUE self) {
391
+ VALUE result, obj = mString_to_json_raw_object(self);
392
+ Check_Type(obj, T_HASH);
393
+ result = mHash_to_json(argc, argv, obj);
394
+ FORCE_UTF8(result);
395
+ return result;
396
+ }
397
+
398
+ /*
399
+ * call-seq: json_create(o)
400
+ *
401
+ * Raw Strings are JSON Objects (the raw bytes are stored in an array for the
402
+ * key "raw"). The Ruby String can be created by this module method.
403
+ */
404
+ static VALUE mString_Extend_json_create(VALUE self, VALUE o) {
405
+ VALUE ary;
406
+ Check_Type(o, T_HASH);
407
+ ary = rb_hash_aref(o, rb_str_new2("raw"));
408
+ return rb_funcall(ary, i_pack, 1, rb_str_new2("C*"));
409
+ }
410
+
411
+ /*
412
+ * call-seq: to_json(state = nil, depth = 0)
413
+ *
414
+ * Returns a JSON string for true: 'true'.
415
+ */
416
+ static VALUE mTrueClass_to_json(int argc, VALUE *argv, VALUE self)
417
+ {
418
+ VALUE result = rb_str_new2("true");
419
+ FORCE_UTF8(result);
420
+ return result;
421
+ }
422
+
423
+ /*
424
+ * call-seq: to_json(state = nil, depth = 0)
425
+ *
426
+ * Returns a JSON string for false: 'false'.
427
+ */
428
+ static VALUE mFalseClass_to_json(int argc, VALUE *argv, VALUE self)
429
+ {
430
+ VALUE result = rb_str_new2("false");
431
+ FORCE_UTF8(result);
432
+ return result;
433
+ }
434
+
435
+ /*
436
+ * call-seq: to_json(state = nil, depth = 0)
437
+ *
438
+ */
439
+ static VALUE mNilClass_to_json(int argc, VALUE *argv, VALUE self)
440
+ {
441
+ VALUE result = rb_str_new2("null");
442
+ FORCE_UTF8(result);
443
+ return result;
444
+ }
445
+
446
+ /*
447
+ * call-seq: to_json(*)
448
+ *
449
+ * Converts this object to a string (calling #to_s), converts
450
+ * it to a JSON string, and returns the result. This is a fallback, if no
451
+ * special method #to_json was defined for some object.
452
+ */
453
+ static VALUE mObject_to_json(int argc, VALUE *argv, VALUE self)
454
+ {
455
+ VALUE result, string = rb_funcall(self, i_to_s, 0);
456
+ Check_Type(string, T_STRING);
457
+ result = mString_to_json(argc, argv, string);
458
+ FORCE_UTF8(result);
459
+ return result;
460
+ }
461
+
462
+ /*
463
+ * Document-class: JSON::Ext::Generator::State
464
+ *
465
+ * This class is used to create State instances, that are use to hold data
466
+ * while generating a JSON text from a a Ruby data structure.
467
+ */
468
+
469
+ static void State_mark(JSON_Generator_State *state)
470
+ {
471
+ rb_gc_mark_maybe(state->indent);
472
+ rb_gc_mark_maybe(state->space);
473
+ rb_gc_mark_maybe(state->space_before);
474
+ rb_gc_mark_maybe(state->object_nl);
475
+ rb_gc_mark_maybe(state->array_nl);
476
+ rb_gc_mark_maybe(state->seen);
477
+ rb_gc_mark_maybe(state->memo);
478
+ rb_gc_mark_maybe(state->depth);
479
+ }
480
+
481
+ static JSON_Generator_State *State_allocate()
482
+ {
483
+ JSON_Generator_State *state = ALLOC(JSON_Generator_State);
484
+ return state;
485
+ }
486
+
487
+ static VALUE cState_s_allocate(VALUE klass)
488
+ {
489
+ JSON_Generator_State *state = State_allocate();
490
+ return Data_Wrap_Struct(klass, State_mark, -1, state);
491
+ }
492
+
493
+ /*
494
+ * call-seq: configure(opts)
495
+ *
496
+ * Configure this State instance with the Hash _opts_, and return
497
+ * itself.
498
+ */
499
+ static VALUE cState_configure(VALUE self, VALUE opts)
500
+ {
501
+ VALUE tmp;
502
+ GET_STATE(self);
503
+ tmp = rb_convert_type(opts, T_HASH, "Hash", "to_hash");
504
+ if (NIL_P(tmp)) tmp = rb_convert_type(opts, T_HASH, "Hash", "to_h");
505
+ if (NIL_P(tmp)) {
506
+ rb_raise(rb_eArgError, "opts has to be hash like or convertable into a hash");
507
+ }
508
+ opts = tmp;
509
+ tmp = rb_hash_aref(opts, ID2SYM(i_indent));
510
+ if (RTEST(tmp)) {
511
+ Check_Type(tmp, T_STRING);
512
+ state->indent = tmp;
513
+ }
514
+ tmp = rb_hash_aref(opts, ID2SYM(i_space));
515
+ if (RTEST(tmp)) {
516
+ Check_Type(tmp, T_STRING);
517
+ state->space = tmp;
518
+ }
519
+ tmp = rb_hash_aref(opts, ID2SYM(i_space_before));
520
+ if (RTEST(tmp)) {
521
+ Check_Type(tmp, T_STRING);
522
+ state->space_before = tmp;
523
+ }
524
+ tmp = rb_hash_aref(opts, ID2SYM(i_array_nl));
525
+ if (RTEST(tmp)) {
526
+ Check_Type(tmp, T_STRING);
527
+ state->array_nl = tmp;
528
+ }
529
+ tmp = rb_hash_aref(opts, ID2SYM(i_object_nl));
530
+ if (RTEST(tmp)) {
531
+ Check_Type(tmp, T_STRING);
532
+ state->object_nl = tmp;
533
+ }
534
+ tmp = ID2SYM(i_check_circular);
535
+ if (st_lookup(RHASH_TBL(opts), tmp, 0)) {
536
+ tmp = rb_hash_aref(opts, ID2SYM(i_check_circular));
537
+ state->check_circular = RTEST(tmp);
538
+ } else {
539
+ state->check_circular = 1;
540
+ }
541
+ tmp = ID2SYM(i_max_nesting);
542
+ state->max_nesting = 19;
543
+ if (st_lookup(RHASH_TBL(opts), tmp, 0)) {
544
+ VALUE max_nesting = rb_hash_aref(opts, tmp);
545
+ if (RTEST(max_nesting)) {
546
+ Check_Type(max_nesting, T_FIXNUM);
547
+ state->max_nesting = FIX2LONG(max_nesting);
548
+ } else {
549
+ state->max_nesting = 0;
550
+ }
551
+ }
552
+ tmp = rb_hash_aref(opts, ID2SYM(i_allow_nan));
553
+ state->allow_nan = RTEST(tmp);
554
+ return self;
555
+ }
556
+
557
+ /*
558
+ * call-seq: to_h
559
+ *
560
+ * Returns the configuration instance variables as a hash, that can be
561
+ * passed to the configure method.
562
+ */
563
+ static VALUE cState_to_h(VALUE self)
564
+ {
565
+ VALUE result = rb_hash_new();
566
+ GET_STATE(self);
567
+ rb_hash_aset(result, ID2SYM(i_indent), state->indent);
568
+ rb_hash_aset(result, ID2SYM(i_space), state->space);
569
+ rb_hash_aset(result, ID2SYM(i_space_before), state->space_before);
570
+ rb_hash_aset(result, ID2SYM(i_object_nl), state->object_nl);
571
+ rb_hash_aset(result, ID2SYM(i_array_nl), state->array_nl);
572
+ rb_hash_aset(result, ID2SYM(i_check_circular), state->check_circular ? Qtrue : Qfalse);
573
+ rb_hash_aset(result, ID2SYM(i_allow_nan), state->allow_nan ? Qtrue : Qfalse);
574
+ rb_hash_aset(result, ID2SYM(i_max_nesting), LONG2FIX(state->max_nesting));
575
+ return result;
576
+ }
577
+
578
+
579
+ /*
580
+ * call-seq: new(opts = {})
581
+ *
582
+ * Instantiates a new State object, configured by _opts_.
583
+ *
584
+ * _opts_ can have the following keys:
585
+ *
586
+ * * *indent*: a string used to indent levels (default: ''),
587
+ * * *space*: a string that is put after, a : or , delimiter (default: ''),
588
+ * * *space_before*: a string that is put before a : pair delimiter (default: ''),
589
+ * * *object_nl*: a string that is put at the end of a JSON object (default: ''),
590
+ * * *array_nl*: a string that is put at the end of a JSON array (default: ''),
591
+ * * *check_circular*: true if checking for circular data structures
592
+ * should be done, false (the default) otherwise.
593
+ * * *allow_nan*: true if NaN, Infinity, and -Infinity should be
594
+ * generated, otherwise an exception is thrown, if these values are
595
+ * encountered. This options defaults to false.
596
+ */
597
+ static VALUE cState_initialize(int argc, VALUE *argv, VALUE self)
598
+ {
599
+ VALUE opts;
600
+ GET_STATE(self);
601
+
602
+ rb_scan_args(argc, argv, "01", &opts);
603
+ state->indent = rb_str_new2("");
604
+ state->space = rb_str_new2("");
605
+ state->space_before = rb_str_new2("");
606
+ state->array_nl = rb_str_new2("");
607
+ state->object_nl = rb_str_new2("");
608
+ if (NIL_P(opts)) {
609
+ state->check_circular = 1;
610
+ state->allow_nan = 0;
611
+ state->max_nesting = 19;
612
+ } else {
613
+ cState_configure(self, opts);
614
+ }
615
+ state->seen = rb_hash_new();
616
+ state->memo = Qnil;
617
+ state->depth = INT2FIX(0);
618
+ return self;
619
+ }
620
+
621
+ /*
622
+ * call-seq: from_state(opts)
623
+ *
624
+ * Creates a State object from _opts_, which ought to be Hash to create a
625
+ * new State instance configured by _opts_, something else to create an
626
+ * unconfigured instance. If _opts_ is a State object, it is just returned.
627
+ */
628
+ static VALUE cState_from_state_s(VALUE self, VALUE opts)
629
+ {
630
+ if (rb_obj_is_kind_of(opts, self)) {
631
+ return opts;
632
+ } else if (rb_obj_is_kind_of(opts, rb_cHash)) {
633
+ return rb_funcall(self, i_new, 1, opts);
634
+ } else {
635
+ return rb_funcall(self, i_new, 0);
636
+ }
637
+ }
638
+
639
+ /*
640
+ * call-seq: indent()
641
+ *
642
+ * This string is used to indent levels in the JSON text.
643
+ */
644
+ static VALUE cState_indent(VALUE self)
645
+ {
646
+ GET_STATE(self);
647
+ return state->indent;
648
+ }
649
+
650
+ /*
651
+ * call-seq: indent=(indent)
652
+ *
653
+ * This string is used to indent levels in the JSON text.
654
+ */
655
+ static VALUE cState_indent_set(VALUE self, VALUE indent)
656
+ {
657
+ GET_STATE(self);
658
+ Check_Type(indent, T_STRING);
659
+ return state->indent = indent;
660
+ }
661
+
662
+ /*
663
+ * call-seq: space()
664
+ *
665
+ * This string is used to insert a space between the tokens in a JSON
666
+ * string.
667
+ */
668
+ static VALUE cState_space(VALUE self)
669
+ {
670
+ GET_STATE(self);
671
+ return state->space;
672
+ }
673
+
674
+ /*
675
+ * call-seq: space=(space)
676
+ *
677
+ * This string is used to insert a space between the tokens in a JSON
678
+ * string.
679
+ */
680
+ static VALUE cState_space_set(VALUE self, VALUE space)
681
+ {
682
+ GET_STATE(self);
683
+ Check_Type(space, T_STRING);
684
+ return state->space = space;
685
+ }
686
+
687
+ /*
688
+ * call-seq: space_before()
689
+ *
690
+ * This string is used to insert a space before the ':' in JSON objects.
691
+ */
692
+ static VALUE cState_space_before(VALUE self)
693
+ {
694
+ GET_STATE(self);
695
+ return state->space_before;
696
+ }
697
+
698
+ /*
699
+ * call-seq: space_before=(space_before)
700
+ *
701
+ * This string is used to insert a space before the ':' in JSON objects.
702
+ */
703
+ static VALUE cState_space_before_set(VALUE self, VALUE space_before)
704
+ {
705
+ GET_STATE(self);
706
+ Check_Type(space_before, T_STRING);
707
+ return state->space_before = space_before;
708
+ }
709
+
710
+ /*
711
+ * call-seq: object_nl()
712
+ *
713
+ * This string is put at the end of a line that holds a JSON object (or
714
+ * Hash).
715
+ */
716
+ static VALUE cState_object_nl(VALUE self)
717
+ {
718
+ GET_STATE(self);
719
+ return state->object_nl;
720
+ }
721
+
722
+ /*
723
+ * call-seq: object_nl=(object_nl)
724
+ *
725
+ * This string is put at the end of a line that holds a JSON object (or
726
+ * Hash).
727
+ */
728
+ static VALUE cState_object_nl_set(VALUE self, VALUE object_nl)
729
+ {
730
+ GET_STATE(self);
731
+ Check_Type(object_nl, T_STRING);
732
+ return state->object_nl = object_nl;
733
+ }
734
+
735
+ /*
736
+ * call-seq: array_nl()
737
+ *
738
+ * This string is put at the end of a line that holds a JSON array.
739
+ */
740
+ static VALUE cState_array_nl(VALUE self)
741
+ {
742
+ GET_STATE(self);
743
+ return state->array_nl;
744
+ }
745
+
746
+ /*
747
+ * call-seq: array_nl=(array_nl)
748
+ *
749
+ * This string is put at the end of a line that holds a JSON array.
750
+ */
751
+ static VALUE cState_array_nl_set(VALUE self, VALUE array_nl)
752
+ {
753
+ GET_STATE(self);
754
+ Check_Type(array_nl, T_STRING);
755
+ return state->array_nl = array_nl;
756
+ }
757
+
758
+ /*
759
+ * call-seq: check_circular?
760
+ *
761
+ * Returns true, if circular data structures should be checked,
762
+ * otherwise returns false.
763
+ */
764
+ static VALUE cState_check_circular_p(VALUE self)
765
+ {
766
+ GET_STATE(self);
767
+ return state->check_circular ? Qtrue : Qfalse;
768
+ }
769
+
770
+ /*
771
+ * call-seq: max_nesting
772
+ *
773
+ * This integer returns the maximum level of data structure nesting in
774
+ * the generated JSON, max_nesting = 0 if no maximum is checked.
775
+ */
776
+ static VALUE cState_max_nesting(VALUE self)
777
+ {
778
+ GET_STATE(self);
779
+ return LONG2FIX(state->max_nesting);
780
+ }
781
+
782
+ /*
783
+ * call-seq: max_nesting=(depth)
784
+ *
785
+ * This sets the maximum level of data structure nesting in the generated JSON
786
+ * to the integer depth, max_nesting = 0 if no maximum should be checked.
787
+ */
788
+ static VALUE cState_max_nesting_set(VALUE self, VALUE depth)
789
+ {
790
+ GET_STATE(self);
791
+ Check_Type(depth, T_FIXNUM);
792
+ state->max_nesting = FIX2LONG(depth);
793
+ return Qnil;
794
+ }
795
+
796
+ /*
797
+ * call-seq: allow_nan?
798
+ *
799
+ * Returns true, if NaN, Infinity, and -Infinity should be generated, otherwise
800
+ * returns false.
801
+ */
802
+ static VALUE cState_allow_nan_p(VALUE self)
803
+ {
804
+ GET_STATE(self);
805
+ return state->allow_nan ? Qtrue : Qfalse;
806
+ }
807
+
808
+ /*
809
+ * call-seq: seen?(object)
810
+ *
811
+ * Returns _true_, if _object_ was already seen during this generating run.
812
+ */
813
+ static VALUE cState_seen_p(VALUE self, VALUE object)
814
+ {
815
+ GET_STATE(self);
816
+ return rb_hash_aref(state->seen, rb_obj_id(object));
817
+ }
818
+
819
+ /*
820
+ * call-seq: remember(object)
821
+ *
822
+ * Remember _object_, to find out if it was already encountered (if a cyclic
823
+ * data structure is rendered).
824
+ */
825
+ static VALUE cState_remember(VALUE self, VALUE object)
826
+ {
827
+ GET_STATE(self);
828
+ return rb_hash_aset(state->seen, rb_obj_id(object), Qtrue);
829
+ }
830
+
831
+ /*
832
+ * call-seq: forget(object)
833
+ *
834
+ * Forget _object_ for this generating run.
835
+ */
836
+ static VALUE cState_forget(VALUE self, VALUE object)
837
+ {
838
+ GET_STATE(self);
839
+ return rb_hash_delete(state->seen, rb_obj_id(object));
840
+ }
841
+
842
+ /*
843
+ *
844
+ */
845
+ void Init_generator()
846
+ {
847
+ rb_require("json/common");
848
+ mJSON = rb_define_module("JSON");
849
+ mExt = rb_define_module_under(mJSON, "Ext");
850
+ mGenerator = rb_define_module_under(mExt, "Generator");
851
+ eGeneratorError = rb_path2class("JSON::GeneratorError");
852
+ eCircularDatastructure = rb_path2class("JSON::CircularDatastructure");
853
+ eNestingError = rb_path2class("JSON::NestingError");
854
+ cState = rb_define_class_under(mGenerator, "State", rb_cObject);
855
+ rb_define_alloc_func(cState, cState_s_allocate);
856
+ rb_define_singleton_method(cState, "from_state", cState_from_state_s, 1);
857
+ rb_define_method(cState, "initialize", cState_initialize, -1);
858
+
859
+ rb_define_method(cState, "indent", cState_indent, 0);
860
+ rb_define_method(cState, "indent=", cState_indent_set, 1);
861
+ rb_define_method(cState, "space", cState_space, 0);
862
+ rb_define_method(cState, "space=", cState_space_set, 1);
863
+ rb_define_method(cState, "space_before", cState_space_before, 0);
864
+ rb_define_method(cState, "space_before=", cState_space_before_set, 1);
865
+ rb_define_method(cState, "object_nl", cState_object_nl, 0);
866
+ rb_define_method(cState, "object_nl=", cState_object_nl_set, 1);
867
+ rb_define_method(cState, "array_nl", cState_array_nl, 0);
868
+ rb_define_method(cState, "array_nl=", cState_array_nl_set, 1);
869
+ rb_define_method(cState, "check_circular?", cState_check_circular_p, 0);
870
+ rb_define_method(cState, "max_nesting", cState_max_nesting, 0);
871
+ rb_define_method(cState, "max_nesting=", cState_max_nesting_set, 1);
872
+ rb_define_method(cState, "allow_nan?", cState_allow_nan_p, 0);
873
+ rb_define_method(cState, "seen?", cState_seen_p, 1);
874
+ rb_define_method(cState, "remember", cState_remember, 1);
875
+ rb_define_method(cState, "forget", cState_forget, 1);
876
+ rb_define_method(cState, "configure", cState_configure, 1);
877
+ rb_define_method(cState, "to_h", cState_to_h, 0);
878
+
879
+ mGeneratorMethods = rb_define_module_under(mGenerator, "GeneratorMethods");
880
+ mObject = rb_define_module_under(mGeneratorMethods, "Object");
881
+ rb_define_method(mObject, "to_json", mObject_to_json, -1);
882
+ mHash = rb_define_module_under(mGeneratorMethods, "Hash");
883
+ rb_define_method(mHash, "to_json", mHash_to_json, -1);
884
+ mArray = rb_define_module_under(mGeneratorMethods, "Array");
885
+ rb_define_method(mArray, "to_json", mArray_to_json, -1);
886
+ mInteger = rb_define_module_under(mGeneratorMethods, "Integer");
887
+ rb_define_method(mInteger, "to_json", mInteger_to_json, -1);
888
+ mFloat = rb_define_module_under(mGeneratorMethods, "Float");
889
+ rb_define_method(mFloat, "to_json", mFloat_to_json, -1);
890
+ mString = rb_define_module_under(mGeneratorMethods, "String");
891
+ rb_define_singleton_method(mString, "included", mString_included_s, 1);
892
+ rb_define_method(mString, "to_json", mString_to_json, -1);
893
+ rb_define_method(mString, "to_json_raw", mString_to_json_raw, -1);
894
+ rb_define_method(mString, "to_json_raw_object", mString_to_json_raw_object, 0);
895
+ mString_Extend = rb_define_module_under(mString, "Extend");
896
+ rb_define_method(mString_Extend, "json_create", mString_Extend_json_create, 1);
897
+ mTrueClass = rb_define_module_under(mGeneratorMethods, "TrueClass");
898
+ rb_define_method(mTrueClass, "to_json", mTrueClass_to_json, -1);
899
+ mFalseClass = rb_define_module_under(mGeneratorMethods, "FalseClass");
900
+ rb_define_method(mFalseClass, "to_json", mFalseClass_to_json, -1);
901
+ mNilClass = rb_define_module_under(mGeneratorMethods, "NilClass");
902
+ rb_define_method(mNilClass, "to_json", mNilClass_to_json, -1);
903
+
904
+ i_to_s = rb_intern("to_s");
905
+ i_to_json = rb_intern("to_json");
906
+ i_new = rb_intern("new");
907
+ i_indent = rb_intern("indent");
908
+ i_space = rb_intern("space");
909
+ i_space_before = rb_intern("space_before");
910
+ i_object_nl = rb_intern("object_nl");
911
+ i_array_nl = rb_intern("array_nl");
912
+ i_check_circular = rb_intern("check_circular");
913
+ i_max_nesting = rb_intern("max_nesting");
914
+ i_allow_nan = rb_intern("allow_nan");
915
+ i_pack = rb_intern("pack");
916
+ i_unpack = rb_intern("unpack");
917
+ i_create_id = rb_intern("create_id");
918
+ i_extend = rb_intern("extend");
919
+ }