ritsu 0.7.0 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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