ritsu 0.7.0 → 0.7.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (89) hide show
  1. data/README.md +95 -95
  2. data/bin/define_cpp_string +54 -54
  3. data/bin/ritsu +31 -31
  4. data/lib/ritsu.rb +16 -16
  5. data/lib/ritsu/block.rb +258 -258
  6. data/lib/ritsu/ext/cuda.rb +5 -5
  7. data/lib/ritsu/ext/cuda/external_library.rb +15 -15
  8. data/lib/ritsu/ext/cuda/project.rb +31 -31
  9. data/lib/ritsu/ext/cuda/src_files/cu_file.rb +46 -46
  10. data/lib/ritsu/ext/cuda/src_files/target_cmake_lists.rb +110 -110
  11. data/lib/ritsu/ext/cuda/target.rb +16 -16
  12. data/lib/ritsu/ext/cuda/targets/library.rb +16 -16
  13. data/lib/ritsu/ext/fake_install.rb +2 -2
  14. data/lib/ritsu/ext/fake_install/project.rb +16 -16
  15. data/lib/ritsu/ext/fake_install/src_files/project_cmake_lists.rb +45 -45
  16. data/lib/ritsu/ext/glsl.rb +1 -1
  17. data/lib/ritsu/ext/glsl/src_files/frag_file.rb +70 -70
  18. data/lib/ritsu/ext/glsl/src_files/vert_file.rb +70 -70
  19. data/lib/ritsu/ext/qt.rb +4 -4
  20. data/lib/ritsu/ext/qt/project.rb +47 -47
  21. data/lib/ritsu/ext/qt/src_files/header_file.rb +60 -60
  22. data/lib/ritsu/ext/qt/src_files/target_cmake_lists.rb +106 -106
  23. data/lib/ritsu/ext/qt/src_files/ui_file.rb +46 -46
  24. data/lib/ritsu/ext/test_case.rb +19 -19
  25. data/lib/ritsu/external_library.rb +46 -46
  26. data/lib/ritsu/project.rb +93 -93
  27. data/lib/ritsu/project_generator.rb +33 -33
  28. data/lib/ritsu/project_generators/default_generator.rb +72 -72
  29. data/lib/ritsu/project_generators/default_generator_files/Thorfile.erb +8 -8
  30. data/lib/ritsu/project_generators/default_generator_files/meta/project.rb.erb +10 -10
  31. data/lib/ritsu/src_file.rb +79 -79
  32. data/lib/ritsu/src_files.rb +12 -12
  33. data/lib/ritsu/src_files/cpp_file.rb +43 -43
  34. data/lib/ritsu/src_files/executable_cmake_lists.rb +39 -39
  35. data/lib/ritsu/src_files/header_file.rb +60 -60
  36. data/lib/ritsu/src_files/project_cmake_lists.rb +133 -133
  37. data/lib/ritsu/src_files/project_config_header_file.rb +14 -14
  38. data/lib/ritsu/src_files/project_config_header_template_file.rb +44 -44
  39. data/lib/ritsu/src_files/shared_library_cmake_lists.rb +39 -39
  40. data/lib/ritsu/src_files/static_library_cmake_lists.rb +39 -39
  41. data/lib/ritsu/src_files/target_cmake_lists.rb +189 -189
  42. data/lib/ritsu/src_files/templated_src_file.rb +47 -47
  43. data/lib/ritsu/src_files/unit.rb +32 -32
  44. data/lib/ritsu/target.rb +154 -154
  45. data/lib/ritsu/targets.rb +3 -3
  46. data/lib/ritsu/targets/executable.rb +44 -44
  47. data/lib/ritsu/targets/library.rb +29 -29
  48. data/lib/ritsu/targets/shared_library.rb +38 -38
  49. data/lib/ritsu/targets/static_library.rb +32 -32
  50. data/lib/ritsu/template.rb +68 -68
  51. data/lib/ritsu/template_policies.rb +132 -132
  52. data/lib/ritsu/test_helpers.rb +123 -123
  53. data/lib/ritsu/thors/default_thor.rb +1 -1
  54. data/lib/ritsu/utility.rb +7 -7
  55. data/lib/ritsu/utility/accessors.rb +29 -29
  56. data/lib/ritsu/utility/check_upon_add_set.rb +34 -34
  57. data/lib/ritsu/utility/file_robot.rb +128 -128
  58. data/lib/ritsu/utility/files.rb +12 -12
  59. data/lib/ritsu/utility/instance_dependencies.rb +112 -112
  60. data/lib/ritsu/utility/instance_set.rb +28 -28
  61. data/lib/ritsu/utility/platform.rb +20 -20
  62. data/lib/ritsu/utility/simple_io.rb +64 -64
  63. data/lib/ritsu/utility/single_instance.rb +33 -33
  64. data/lib/ritsu/utility/strings.rb +40 -40
  65. data/test/ritsu/block_test.rb +196 -196
  66. data/test/ritsu/ext/cuda/src_files/cuda_static_library_cmake_lists_test.rb +63 -63
  67. data/test/ritsu/external_library_test.rb +41 -41
  68. data/test/ritsu/project_generators/default_generator_test.rb +34 -34
  69. data/test/ritsu/project_test.rb +127 -127
  70. data/test/ritsu/src_file_test.rb +69 -69
  71. data/test/ritsu/src_files/cpp_file_test.rb +42 -42
  72. data/test/ritsu/src_files/executable_cmake_lists_test.rb +92 -92
  73. data/test/ritsu/src_files/header_file_test.rb +57 -57
  74. data/test/ritsu/src_files/project_cmake_lists_test.rb +159 -159
  75. data/test/ritsu/src_files/shared_library_cmake_lists_test.rb +54 -54
  76. data/test/ritsu/src_files/static_library_cmake_lists_test.rb +54 -54
  77. data/test/ritsu/src_files/target_cmake_lists_test.rb +15 -15
  78. data/test/ritsu/target_test.rb +105 -105
  79. data/test/ritsu/targets/executable_test.rb +10 -10
  80. data/test/ritsu/targets/shared_library_test.rb +10 -10
  81. data/test/ritsu/targets/static_library_test.rb +10 -10
  82. data/test/ritsu/utility/accessors_test.rb +14 -14
  83. data/test/ritsu/utility/check_upon_add_set_test.rb +31 -31
  84. data/test/ritsu/utility/file_robot_test.rb +175 -175
  85. data/test/ritsu/utility/strings_test.rb +28 -28
  86. data/test/test_helpers.rb +3 -3
  87. metadata +72 -124
  88. data/Thorfile +0 -104
  89. data/VERSION +0 -1
data/README.md CHANGED
@@ -1,96 +1,96 @@
1
- # Ritsu
2
-
3
- Ritsu helps you with C/C++ software development. It provides you with a
4
- domain specific language to specify a C/C++ project, and then automates
5
- build project generation with the help of CMake.
6
-
7
- ## Using
8
-
9
- ### Generate a Project
10
-
11
- Run
12
-
13
- ritsu my_project
14
-
15
- in command prompt to generate a project.
16
- This will generate directory with the following contents:
17
-
18
- my_project
19
- build
20
- meta
21
- project.rb
22
- src
23
- cmake_modules
24
- --empty--
25
- Thorfile
26
-
27
- ### Specifying the Project
28
-
29
- The file <tt>meta/project.rb</tt> specifies the project, and its
30
- content is initially as follows.
31
-
32
- require 'ritsu'
33
-
34
- Ritsu::Project.create('my_project') do |p|
35
-
36
- ##################
37
- # YOUR CODE HERE #
38
- ##################
39
-
40
- end
41
-
42
- You now can populate the <tt>src</tt> directory with your C/C++ source
43
- and header files. For examples:
44
-
45
- my_project
46
- build
47
- meta
48
- project.rb
49
- src
50
- cmake_modules
51
- my_program
52
- lib.cpp
53
- lib.h
54
- main.cpp
55
- Thorfile
56
-
57
-
58
- Then, you can specify targets to build as follows:
59
-
60
- require 'ritsu'
61
-
62
- Ritsu::Project.create('my_project') do |p|
63
- p.add_executable('my_program') do |e|
64
- e.add_cpp_file 'main.cpp'
65
- e.add_header_file 'lib.h'
66
- e.add_cpp_file 'lib.cpp'
67
- end
68
- end
69
-
70
- As you can see, for each target (an executable or a library),
71
- you have to create a directory under <tt>src</tt>, which bears the
72
- same name as the project. The file names used in the <tt>add_cpp_file</tt>
73
- and <tt>add_header_file</tt> commands are relative to the directory of the target.
74
- You can bypass this behavior though.
75
-
76
- ### Building
77
-
78
- To build the project, you first need to update the source files and
79
- the <tt>CMakeLists.txt</tt> files for the project and for its targets.
80
- This is done by running a Thor command in the project directory.
81
-
82
- thor my_project:update_src
83
-
84
- The source files and <tt>CMakeList</tt> files are generated if they are
85
- not present. From this point, you can either run cmake to
86
- generate platform-specific build scripts, or run
87
-
88
- thor my_project:cmake
89
-
90
- which will create an out-of-source build scripts in the <tt>build</tt> directory.
91
- The action
92
-
93
- thor my_project:update
94
-
95
- combines the first two steps in one shot. Now, use your platform-specific
1
+ # Ritsu
2
+
3
+ Ritsu helps you with C/C++ software development. It provides you with a
4
+ domain specific language to specify a C/C++ project, and then automates
5
+ build project generation with the help of CMake.
6
+
7
+ ## Using
8
+
9
+ ### Generate a Project
10
+
11
+ Run
12
+
13
+ ritsu my_project
14
+
15
+ in command prompt to generate a project.
16
+ This will generate directory with the following contents:
17
+
18
+ my_project
19
+ build
20
+ meta
21
+ project.rb
22
+ src
23
+ cmake_modules
24
+ --empty--
25
+ Thorfile
26
+
27
+ ### Specifying the Project
28
+
29
+ The file <tt>meta/project.rb</tt> specifies the project, and its
30
+ content is initially as follows.
31
+
32
+ require 'ritsu'
33
+
34
+ Ritsu::Project.create('my_project') do |p|
35
+
36
+ ##################
37
+ # YOUR CODE HERE #
38
+ ##################
39
+
40
+ end
41
+
42
+ You now can populate the <tt>src</tt> directory with your C/C++ source
43
+ and header files. For examples:
44
+
45
+ my_project
46
+ build
47
+ meta
48
+ project.rb
49
+ src
50
+ cmake_modules
51
+ my_program
52
+ lib.cpp
53
+ lib.h
54
+ main.cpp
55
+ Thorfile
56
+
57
+
58
+ Then, you can specify targets to build as follows:
59
+
60
+ require 'ritsu'
61
+
62
+ Ritsu::Project.create('my_project') do |p|
63
+ p.add_executable('my_program') do |e|
64
+ e.add_cpp_file 'main.cpp'
65
+ e.add_header_file 'lib.h'
66
+ e.add_cpp_file 'lib.cpp'
67
+ end
68
+ end
69
+
70
+ As you can see, for each target (an executable or a library),
71
+ you have to create a directory under <tt>src</tt>, which bears the
72
+ same name as the project. The file names used in the <tt>add_cpp_file</tt>
73
+ and <tt>add_header_file</tt> commands are relative to the directory of the target.
74
+ You can bypass this behavior though.
75
+
76
+ ### Building
77
+
78
+ To build the project, you first need to update the source files and
79
+ the <tt>CMakeLists.txt</tt> files for the project and for its targets.
80
+ This is done by running a Thor command in the project directory.
81
+
82
+ thor my_project:update_src
83
+
84
+ The source files and <tt>CMakeList</tt> files are generated if they are
85
+ not present. From this point, you can either run cmake to
86
+ generate platform-specific build scripts, or run
87
+
88
+ thor my_project:cmake
89
+
90
+ which will create an out-of-source build scripts in the <tt>build</tt> directory.
91
+ The action
92
+
93
+ thor my_project:update
94
+
95
+ combines the first two steps in one shot. Now, use your platform-specific
96
96
  tool to build the project.
@@ -1,54 +1,54 @@
1
- #!/usr/bin/env ruby
2
- # Convert a text file's content into a C++ const string.
3
-
4
- # Turn the given string to
5
- # a C string literal that
6
- # parses to the original string.
7
- def string_literal(s)
8
- result = "\"\\\n"
9
- s.each_byte do |b|
10
- c = b.chr
11
- if c == "\n"
12
- result += "\\n\\\n"
13
- elsif c == "\r"
14
- next
15
- elsif c == "\\"
16
- result += "\\\\"
17
- elsif c == "\""
18
- result += "\\\""
19
- else
20
- result += c
21
- end
22
- end
23
- result += "\""
24
- result
25
- end
26
-
27
- # Declare a constant string
28
- # variable whose value
29
- # is the given string.
30
- def const_string(name, s)
31
- result = "const char * #{name} = " + string_literal(s) + ";"
32
- end
33
-
34
- require 'optparse'
35
-
36
- parser = OptionParser.new do |parser|
37
- parser.banner = "Usage: define_cpp_string variable_name"
38
-
39
- parser.on_tail("-h", "--help", "Show this message") do
40
- puts parser
41
- exit
42
- end
43
- end
44
-
45
- parser.parse(ARGV)
46
- var_name = ARGV.shift
47
- if not var_name
48
- puts parser
49
- exit
50
- end
51
-
52
- s = STDIN.read
53
-
54
- puts const_string(var_name, s)
1
+ #!/usr/bin/env ruby
2
+ # Convert a text file's content into a C++ const string.
3
+
4
+ # Turn the given string to
5
+ # a C string literal that
6
+ # parses to the original string.
7
+ def string_literal(s)
8
+ result = "\"\\\n"
9
+ s.each_byte do |b|
10
+ c = b.chr
11
+ if c == "\n"
12
+ result += "\\n\\\n"
13
+ elsif c == "\r"
14
+ next
15
+ elsif c == "\\"
16
+ result += "\\\\"
17
+ elsif c == "\""
18
+ result += "\\\""
19
+ else
20
+ result += c
21
+ end
22
+ end
23
+ result += "\""
24
+ result
25
+ end
26
+
27
+ # Declare a constant string
28
+ # variable whose value
29
+ # is the given string.
30
+ def const_string(name, s)
31
+ result = "const char * #{name} = " + string_literal(s) + ";"
32
+ end
33
+
34
+ require 'optparse'
35
+
36
+ parser = OptionParser.new do |parser|
37
+ parser.banner = "Usage: define_cpp_string variable_name"
38
+
39
+ parser.on_tail("-h", "--help", "Show this message") do
40
+ puts parser
41
+ exit
42
+ end
43
+ end
44
+
45
+ parser.parse(ARGV)
46
+ var_name = ARGV.shift
47
+ if not var_name
48
+ puts parser
49
+ exit
50
+ end
51
+
52
+ s = STDIN.read
53
+
54
+ puts const_string(var_name, s)
data/bin/ritsu CHANGED
@@ -1,32 +1,32 @@
1
- #!/usr/bin/env ruby
2
- # The command line Ritsu generator.
3
-
4
- $LOAD_PATH.unshift File.dirname(__FILE__) + "/../lib"
5
- require 'ritsu'
6
- require 'optparse'
7
-
8
- options = {:generator => "default"}
9
- OptionParser.new do |opts|
10
- opts.banner = "Usage: ritsu PROJECT_NAME [OPTIONS]"
11
- opts.on("-g", "--generator GENERATOR", "name of the generator to use") do |generator|
12
- options[:generator] = generator
13
- end
14
-
15
- opts.parse!
16
- if ARGV.length < 1
17
- puts opts
18
- exit
19
- end
20
- end
21
-
22
- generator_class = Ritsu::ProjectGenerator.generator_classes[options[:generator]]
23
-
24
- if generator_class.nil?
25
- puts "there is no generator with name '#{options[:generator]}'"
26
- else
27
- begin
28
- generator_class.new.generate(ARGV[0], '.', options)
29
- rescue Exception => e
30
- puts e
31
- end
1
+ #!/usr/bin/env ruby
2
+ # The command line Ritsu generator.
3
+
4
+ $LOAD_PATH.unshift File.dirname(__FILE__) + "/../lib"
5
+ require 'ritsu'
6
+ require 'optparse'
7
+
8
+ options = {:generator => "default"}
9
+ OptionParser.new do |opts|
10
+ opts.banner = "Usage: ritsu PROJECT_NAME [OPTIONS]"
11
+ opts.on("-g", "--generator GENERATOR", "name of the generator to use") do |generator|
12
+ options[:generator] = generator
13
+ end
14
+
15
+ opts.parse!
16
+ if ARGV.length < 1
17
+ puts opts
18
+ exit
19
+ end
20
+ end
21
+
22
+ generator_class = Ritsu::ProjectGenerator.generator_classes[options[:generator]]
23
+
24
+ if generator_class.nil?
25
+ puts "there is no generator with name '#{options[:generator]}'"
26
+ else
27
+ begin
28
+ generator_class.new.generate(ARGV[0], '.', options)
29
+ rescue Exception => e
30
+ puts e
31
+ end
32
32
  end
@@ -1,17 +1,17 @@
1
- require 'rubygems'
2
- require 'active_support'
3
-
4
- require File.expand_path(File.dirname(__FILE__) + '/ritsu/external_library')
5
- require File.expand_path(File.dirname(__FILE__) + '/ritsu/project')
6
- require File.expand_path(File.dirname(__FILE__) + '/ritsu/target')
7
- require File.expand_path(File.dirname(__FILE__) + '/ritsu/src_file')
8
- require File.expand_path(File.dirname(__FILE__) + '/ritsu/block')
9
- require File.expand_path(File.dirname(__FILE__) + '/ritsu/template')
10
- require File.expand_path(File.dirname(__FILE__) + '/ritsu/project_generator')
11
-
12
- require File.expand_path(File.dirname(__FILE__) + '/ritsu/template_policies')
13
- require File.expand_path(File.dirname(__FILE__) + '/ritsu/targets')
14
- require File.expand_path(File.dirname(__FILE__) + '/ritsu/src_files')
15
- require File.expand_path(File.dirname(__FILE__) + '/ritsu/utility')
16
- require File.expand_path(File.dirname(__FILE__) + '/ritsu/project_generators')
1
+ require 'rubygems'
2
+ require 'active_support'
3
+
4
+ require File.expand_path(File.dirname(__FILE__) + '/ritsu/external_library')
5
+ require File.expand_path(File.dirname(__FILE__) + '/ritsu/project')
6
+ require File.expand_path(File.dirname(__FILE__) + '/ritsu/target')
7
+ require File.expand_path(File.dirname(__FILE__) + '/ritsu/src_file')
8
+ require File.expand_path(File.dirname(__FILE__) + '/ritsu/block')
9
+ require File.expand_path(File.dirname(__FILE__) + '/ritsu/template')
10
+ require File.expand_path(File.dirname(__FILE__) + '/ritsu/project_generator')
11
+
12
+ require File.expand_path(File.dirname(__FILE__) + '/ritsu/template_policies')
13
+ require File.expand_path(File.dirname(__FILE__) + '/ritsu/targets')
14
+ require File.expand_path(File.dirname(__FILE__) + '/ritsu/src_files')
15
+ require File.expand_path(File.dirname(__FILE__) + '/ritsu/utility')
16
+ require File.expand_path(File.dirname(__FILE__) + '/ritsu/project_generators')
17
17
  require File.expand_path(File.dirname(__FILE__) + '/ritsu/thors')
@@ -1,259 +1,259 @@
1
- require 'rubygems'
2
- require 'active_support/core_ext/string/starts_ends_with'
3
- require File.dirname(__FILE__) + '/utility/check_upon_add_set'
4
- require File.dirname(__FILE__) + '/utility/files'
5
- require File.dirname(__FILE__) + '/utility/strings'
6
-
7
- module Ritsu
8
- module BlockMixin
9
- attr_reader :id
10
- attr_accessor :contents
11
- attr_accessor :local_indentation
12
- attr_accessor :indent_level
13
- attr_accessor :indent_length
14
-
15
- def initialize_block_mixin(id = nil, options={})
16
- options = {
17
- :contents => [],
18
- :local_indentation => "",
19
- :indent_length=>4
20
- }.merge(options)
21
-
22
- @id = id
23
- @contents = options[:contents]
24
- @local_indentation = options[:local_indentation]
25
- @indent_length = options[:indent_length]
26
- @indent_level = 0
27
- end
28
-
29
- def add_line(line)
30
- contents << " " * (indent_level * indent_length) + line
31
- end
32
-
33
- def add_new_line
34
- add_line("")
35
- end
36
-
37
- protected
38
- def add_block_structure(block)
39
- block.local_indentation = block.local_indentation + " " * (indent_level * indent_length)
40
- contents << block
41
- end
42
-
43
- def add_line_or_other_content(content)
44
- if content.kind_of?(String)
45
- add_line(content)
46
- else
47
- contents << content
48
- end
49
- end
50
-
51
- public
52
- def clear_contents
53
- contents.clear
54
- end
55
-
56
- def indent
57
- @indent_level += 1
58
- end
59
-
60
- def outdent
61
- @indent_level -= 1
62
- end
63
-
64
- def block_structure?
65
- true
66
- end
67
- end
68
-
69
- class Block
70
- include BlockMixin
71
-
72
- attr_accessor :block_start_prefix
73
- attr_accessor :block_end_prefix
74
-
75
- def initialize(id = nil, options={})
76
- options = {
77
- :block_start_prefix => "//<<",
78
- :block_end_prefix => "//>>"
79
- }.merge(options)
80
-
81
- @block_start_prefix = options[:block_start_prefix]
82
- @block_end_prefix = options[:block_end_prefix]
83
-
84
- initialize_block_mixin(id, options)
85
- end
86
-
87
- def self.extract_block_id(str, prefix)
88
- str.strip.slice((prefix.length)..-1).strip
89
- end
90
-
91
- def extract_block_id(str, prefix)
92
- Block.extract_block_id(str, prefix)
93
- end
94
-
95
- def parse_lines(lines, options={})
96
- options = {
97
- :block_start_prefix => block_start_prefix,
98
- :block_end_prefix => block_end_prefix
99
- }.merge(options)
100
- @block_start_prefix = options[:block_start_prefix]
101
- @block_end_prefix = options[:block_end_prefix]
102
-
103
- block_stack = [self]
104
- global_indentation_length = 0
105
-
106
- get_local_indentation = Proc.new do |line|
107
- leading_spaces_length = Ritsu::Utility::Strings.leading_spaces(line, options).length
108
- remaining_space_length = leading_spaces_length - global_indentation_length
109
- if remaining_space_length < 0 then remaining_space_length = 0 end
110
- " " * remaining_space_length
111
- end
112
-
113
- append_line = Proc.new do |line|
114
- block_stack.last.contents << (get_local_indentation.call(line) + line.lstrip)
115
- end
116
-
117
- lines.each do |line|
118
- if line.strip.starts_with?(block_start_prefix)
119
- id = extract_block_id(line, block_start_prefix)
120
- local_indentation = get_local_indentation.call(line)
121
-
122
- options[:local_indentation] = local_indentation
123
- block = Block.new(id, options)
124
- block_stack.last.contents << block
125
- block_stack.push(block)
126
- global_indentation_length += block.local_indentation.length
127
- elsif line.strip.starts_with?(block_end_prefix)
128
- id = extract_block_id(line, block_end_prefix)
129
- if block_stack.last.id == id
130
- block = block_stack.pop()
131
- global_indentation_length -= block.local_indentation.length
132
- else
133
- append_line.call(line)
134
- end
135
- else
136
- append_line.call(line)
137
- end
138
- end
139
-
140
- if block_stack.length != 1
141
- raise ArgumentError.new("error in input. some blocks are malformed")
142
- end
143
- end
144
-
145
- def parse_string(string, options={})
146
- lines = string.rstrip.split("\n")
147
- parse_lines(lines, options)
148
- end
149
-
150
- def parse_file(filename, options={})
151
- text = Ritsu::Utility::Files.read(filename)
152
- parse_string(text, options)
153
- end
154
-
155
- ##
156
- # In all case, the generated string shall have no trailing whitespaces.
157
- def to_s(options={})
158
- options = {:no_delimiter => false, :indentation => ""}.merge(options)
159
- no_delimiter = options.delete(:no_delimiter)
160
- indentation = options.delete(:indentation)
161
-
162
- io = StringIO.new
163
- io << indentation + local_indentation + block_start_prefix + " " + id + "\n" unless no_delimiter
164
- contents.each do |content|
165
- if content.kind_of?(Block)
166
- io << content.to_s({:indentation=>indentation+local_indentation}.merge(options)) + "\n"
167
- else
168
- io << indentation + local_indentation + content.to_s + "\n"
169
- end
170
- end
171
- io << indentation + local_indentation + block_end_prefix + " " + id unless no_delimiter
172
-
173
- io.string.rstrip
174
- end
175
-
176
- def write_to_file(filename, options={})
177
- options = {:no_delimiter => true}.merge(options)
178
-
179
- File.open(filename, "w") do |f|
180
- f.write(to_s(options))
181
- end
182
- end
183
-
184
- def self.parse(*args)
185
- if args.length > 2
186
- raise ArgumentError.new("only at most 2 arguments, the second one is a hash, are accepted")
187
- end
188
- options = {}
189
-
190
- prepare_options_from_kth_arg = Proc.new do |k|
191
- options = options.merge(args[k]) if (args.length > k)
192
- end
193
-
194
- block = Block.new
195
- result = case args[0]
196
- when Array
197
- prepare_options_from_kth_arg.call(1)
198
- block.parse_lines(args[0], options)
199
- when String
200
- prepare_options_from_kth_arg.call(1)
201
- block.parse_string(args[0], options)
202
- when Hash
203
- if filename = args[0].delete(:file)
204
- prepare_options_from_kth_arg.call(0)
205
- block.parse_file(filename, options)
206
- else
207
- raise ArgumentError.new("you must specify :file option if the first argument is a hash")
208
- end
209
- end
210
- return block
211
- end
212
-
213
- def child_block_count
214
- count = 0
215
- contents.each do |content|
216
- count += 1 if content.kind_of?(Block)
217
- end
218
- end
219
-
220
- ##
221
- # @return (Block) the first child block with the given ID. nil if there is no such child block.
222
- def child_block_with_id(id)
223
- contents.each do |content|
224
- if content.kind_of?(Block) and content.id == id
225
- return content
226
- end
227
- end
228
- return nil
229
- end
230
-
231
- def child_blocks
232
- contents.select {|x| x.kind_of?(Block)}
233
- end
234
-
235
- ##
236
- # @return (Integer) the position of the child block with the given ID in the contents array.
237
- # nil if there is no such child block.
238
- def child_block_with_id_position(id)
239
- contents.length.times do |i|
240
- if contents[i].kind_of?(Block) and contents[i].id == id
241
- return i
242
- end
243
- end
244
- return nil
245
- end
246
-
247
- def add_block(block)
248
- add_block_structure(block)
249
- end
250
-
251
- def add_content(content)
252
- if content.kind_of?(Block)
253
- add_block(content)
254
- else
255
- add_line_or_other_content(content)
256
- end
257
- end
258
- end
1
+ require 'rubygems'
2
+ require 'active_support/core_ext/string/starts_ends_with'
3
+ require File.dirname(__FILE__) + '/utility/check_upon_add_set'
4
+ require File.dirname(__FILE__) + '/utility/files'
5
+ require File.dirname(__FILE__) + '/utility/strings'
6
+
7
+ module Ritsu
8
+ module BlockMixin
9
+ attr_reader :id
10
+ attr_accessor :contents
11
+ attr_accessor :local_indentation
12
+ attr_accessor :indent_level
13
+ attr_accessor :indent_length
14
+
15
+ def initialize_block_mixin(id = nil, options={})
16
+ options = {
17
+ :contents => [],
18
+ :local_indentation => "",
19
+ :indent_length=>4
20
+ }.merge(options)
21
+
22
+ @id = id
23
+ @contents = options[:contents]
24
+ @local_indentation = options[:local_indentation]
25
+ @indent_length = options[:indent_length]
26
+ @indent_level = 0
27
+ end
28
+
29
+ def add_line(line)
30
+ contents << " " * (indent_level * indent_length) + line
31
+ end
32
+
33
+ def add_new_line
34
+ add_line("")
35
+ end
36
+
37
+ protected
38
+ def add_block_structure(block)
39
+ block.local_indentation = block.local_indentation + " " * (indent_level * indent_length)
40
+ contents << block
41
+ end
42
+
43
+ def add_line_or_other_content(content)
44
+ if content.kind_of?(String)
45
+ add_line(content)
46
+ else
47
+ contents << content
48
+ end
49
+ end
50
+
51
+ public
52
+ def clear_contents
53
+ contents.clear
54
+ end
55
+
56
+ def indent
57
+ @indent_level += 1
58
+ end
59
+
60
+ def outdent
61
+ @indent_level -= 1
62
+ end
63
+
64
+ def block_structure?
65
+ true
66
+ end
67
+ end
68
+
69
+ class Block
70
+ include BlockMixin
71
+
72
+ attr_accessor :block_start_prefix
73
+ attr_accessor :block_end_prefix
74
+
75
+ def initialize(id = nil, options={})
76
+ options = {
77
+ :block_start_prefix => "//<<",
78
+ :block_end_prefix => "//>>"
79
+ }.merge(options)
80
+
81
+ @block_start_prefix = options[:block_start_prefix]
82
+ @block_end_prefix = options[:block_end_prefix]
83
+
84
+ initialize_block_mixin(id, options)
85
+ end
86
+
87
+ def self.extract_block_id(str, prefix)
88
+ str.strip.slice((prefix.length)..-1).strip
89
+ end
90
+
91
+ def extract_block_id(str, prefix)
92
+ Block.extract_block_id(str, prefix)
93
+ end
94
+
95
+ def parse_lines(lines, options={})
96
+ options = {
97
+ :block_start_prefix => block_start_prefix,
98
+ :block_end_prefix => block_end_prefix
99
+ }.merge(options)
100
+ @block_start_prefix = options[:block_start_prefix]
101
+ @block_end_prefix = options[:block_end_prefix]
102
+
103
+ block_stack = [self]
104
+ global_indentation_length = 0
105
+
106
+ get_local_indentation = Proc.new do |line|
107
+ leading_spaces_length = Ritsu::Utility::Strings.leading_spaces(line, options).length
108
+ remaining_space_length = leading_spaces_length - global_indentation_length
109
+ if remaining_space_length < 0 then remaining_space_length = 0 end
110
+ " " * remaining_space_length
111
+ end
112
+
113
+ append_line = Proc.new do |line|
114
+ block_stack.last.contents << (get_local_indentation.call(line) + line.lstrip)
115
+ end
116
+
117
+ lines.each do |line|
118
+ if line.strip.starts_with?(block_start_prefix)
119
+ id = extract_block_id(line, block_start_prefix)
120
+ local_indentation = get_local_indentation.call(line)
121
+
122
+ options[:local_indentation] = local_indentation
123
+ block = Block.new(id, options)
124
+ block_stack.last.contents << block
125
+ block_stack.push(block)
126
+ global_indentation_length += block.local_indentation.length
127
+ elsif line.strip.starts_with?(block_end_prefix)
128
+ id = extract_block_id(line, block_end_prefix)
129
+ if block_stack.last.id == id
130
+ block = block_stack.pop()
131
+ global_indentation_length -= block.local_indentation.length
132
+ else
133
+ append_line.call(line)
134
+ end
135
+ else
136
+ append_line.call(line)
137
+ end
138
+ end
139
+
140
+ if block_stack.length != 1
141
+ raise ArgumentError.new("error in input. some blocks are malformed")
142
+ end
143
+ end
144
+
145
+ def parse_string(string, options={})
146
+ lines = string.rstrip.split("\n")
147
+ parse_lines(lines, options)
148
+ end
149
+
150
+ def parse_file(filename, options={})
151
+ text = Ritsu::Utility::Files.read(filename)
152
+ parse_string(text, options)
153
+ end
154
+
155
+ ##
156
+ # In all case, the generated string shall have no trailing whitespaces.
157
+ def to_s(options={})
158
+ options = {:no_delimiter => false, :indentation => ""}.merge(options)
159
+ no_delimiter = options.delete(:no_delimiter)
160
+ indentation = options.delete(:indentation)
161
+
162
+ io = StringIO.new
163
+ io << indentation + local_indentation + block_start_prefix + " " + id + "\n" unless no_delimiter
164
+ contents.each do |content|
165
+ if content.kind_of?(Block)
166
+ io << content.to_s({:indentation=>indentation+local_indentation}.merge(options)) + "\n"
167
+ else
168
+ io << indentation + local_indentation + content.to_s + "\n"
169
+ end
170
+ end
171
+ io << indentation + local_indentation + block_end_prefix + " " + id unless no_delimiter
172
+
173
+ io.string.rstrip
174
+ end
175
+
176
+ def write_to_file(filename, options={})
177
+ options = {:no_delimiter => true}.merge(options)
178
+
179
+ File.open(filename, "w") do |f|
180
+ f.write(to_s(options))
181
+ end
182
+ end
183
+
184
+ def self.parse(*args)
185
+ if args.length > 2
186
+ raise ArgumentError.new("only at most 2 arguments, the second one is a hash, are accepted")
187
+ end
188
+ options = {}
189
+
190
+ prepare_options_from_kth_arg = Proc.new do |k|
191
+ options = options.merge(args[k]) if (args.length > k)
192
+ end
193
+
194
+ block = Block.new
195
+ result = case args[0]
196
+ when Array
197
+ prepare_options_from_kth_arg.call(1)
198
+ block.parse_lines(args[0], options)
199
+ when String
200
+ prepare_options_from_kth_arg.call(1)
201
+ block.parse_string(args[0], options)
202
+ when Hash
203
+ if filename = args[0].delete(:file)
204
+ prepare_options_from_kth_arg.call(0)
205
+ block.parse_file(filename, options)
206
+ else
207
+ raise ArgumentError.new("you must specify :file option if the first argument is a hash")
208
+ end
209
+ end
210
+ return block
211
+ end
212
+
213
+ def child_block_count
214
+ count = 0
215
+ contents.each do |content|
216
+ count += 1 if content.kind_of?(Block)
217
+ end
218
+ end
219
+
220
+ ##
221
+ # @return (Block) the first child block with the given ID. nil if there is no such child block.
222
+ def child_block_with_id(id)
223
+ contents.each do |content|
224
+ if content.kind_of?(Block) and content.id == id
225
+ return content
226
+ end
227
+ end
228
+ return nil
229
+ end
230
+
231
+ def child_blocks
232
+ contents.select {|x| x.kind_of?(Block)}
233
+ end
234
+
235
+ ##
236
+ # @return (Integer) the position of the child block with the given ID in the contents array.
237
+ # nil if there is no such child block.
238
+ def child_block_with_id_position(id)
239
+ contents.length.times do |i|
240
+ if contents[i].kind_of?(Block) and contents[i].id == id
241
+ return i
242
+ end
243
+ end
244
+ return nil
245
+ end
246
+
247
+ def add_block(block)
248
+ add_block_structure(block)
249
+ end
250
+
251
+ def add_content(content)
252
+ if content.kind_of?(Block)
253
+ add_block(content)
254
+ else
255
+ add_line_or_other_content(content)
256
+ end
257
+ end
258
+ end
259
259
  end