ritsu 0.1.0
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.
- data/README.md +96 -0
- data/Thorfile +95 -0
- data/VERSION +1 -0
- data/bin/ritsu +32 -0
- data/lib/ritsu/block.rb +259 -0
- data/lib/ritsu/ext/test_case.rb +20 -0
- data/lib/ritsu/external_library.rb +47 -0
- data/lib/ritsu/project.rb +89 -0
- data/lib/ritsu/project_generator.rb +34 -0
- data/lib/ritsu/project_generators/default_generator.rb +73 -0
- data/lib/ritsu/project_generators/default_generator_files/Thorfile.erb +9 -0
- data/lib/ritsu/project_generators/default_generator_files/meta/project.rb.erb +9 -0
- data/lib/ritsu/project_generators.rb +1 -0
- data/lib/ritsu/src_file.rb +76 -0
- data/lib/ritsu/src_files/cpp_file.rb +33 -0
- data/lib/ritsu/src_files/cpp_file_mixin.rb +13 -0
- data/lib/ritsu/src_files/executable_cmake_lists.rb +40 -0
- data/lib/ritsu/src_files/header_file.rb +46 -0
- data/lib/ritsu/src_files/header_file_mixin.rb +20 -0
- data/lib/ritsu/src_files/project_cmake_lists.rb +110 -0
- data/lib/ritsu/src_files/project_config_header_file.rb +15 -0
- data/lib/ritsu/src_files/project_config_header_template_file.rb +46 -0
- data/lib/ritsu/src_files/shared_library_cmake_lists.rb +40 -0
- data/lib/ritsu/src_files/static_library_cmake_lists.rb +40 -0
- data/lib/ritsu/src_files/target_cmake_lists.rb +159 -0
- data/lib/ritsu/src_files/templated_src_file.rb +44 -0
- data/lib/ritsu/src_files.rb +14 -0
- data/lib/ritsu/target.rb +134 -0
- data/lib/ritsu/targets/executable.rb +45 -0
- data/lib/ritsu/targets/library.rb +29 -0
- data/lib/ritsu/targets/shared_library.rb +39 -0
- data/lib/ritsu/targets/static_library.rb +33 -0
- data/lib/ritsu/targets.rb +4 -0
- data/lib/ritsu/template.rb +69 -0
- data/lib/ritsu/template_policies.rb +133 -0
- data/lib/ritsu/test_helpers.rb +124 -0
- data/lib/ritsu/thors/default_thor.rb +57 -0
- data/lib/ritsu/thors.rb +1 -0
- data/lib/ritsu/utility/accessors.rb +30 -0
- data/lib/ritsu/utility/check_upon_add_set.rb +35 -0
- data/lib/ritsu/utility/file_robot.rb +129 -0
- data/lib/ritsu/utility/files.rb +13 -0
- data/lib/ritsu/utility/instance_dependencies.rb +113 -0
- data/lib/ritsu/utility/instance_set.rb +29 -0
- data/lib/ritsu/utility/platform.rb +21 -0
- data/lib/ritsu/utility/simple_io.rb +65 -0
- data/lib/ritsu/utility/single_instance.rb +34 -0
- data/lib/ritsu/utility/strings.rb +41 -0
- data/lib/ritsu/utility.rb +8 -0
- data/lib/ritsu.rb +17 -0
- data/test/ritsu/block_test.rb +197 -0
- data/test/ritsu/external_library_test.rb +42 -0
- data/test/ritsu/project_generators/default_generator_test.rb +34 -0
- data/test/ritsu/project_test.rb +128 -0
- data/test/ritsu/src_file_test.rb +70 -0
- data/test/ritsu/src_files/cpp_file_test.rb +43 -0
- data/test/ritsu/src_files/executable_cmake_lists_test.rb +52 -0
- data/test/ritsu/src_files/header_file_test.rb +58 -0
- data/test/ritsu/src_files/project_cmake_lists_test.rb +152 -0
- data/test/ritsu/src_files/shared_library_cmake_lists_test.rb +52 -0
- data/test/ritsu/src_files/static_library_cmake_lists_test.rb +52 -0
- data/test/ritsu/src_files/target_cmake_lists_test.rb +16 -0
- data/test/ritsu/target_test.rb +106 -0
- data/test/ritsu/targets/executable_test.rb +11 -0
- data/test/ritsu/targets/shared_library_test.rb +11 -0
- data/test/ritsu/targets/static_library_test.rb +11 -0
- data/test/ritsu/template_policies_test.rb +0 -0
- data/test/ritsu/utility/accessors_test.rb +15 -0
- data/test/ritsu/utility/check_upon_add_set_test.rb +32 -0
- data/test/ritsu/utility/file_robot_test.rb +176 -0
- data/test/ritsu/utility/strings_test.rb +29 -0
- data/test/test_helpers.rb +4 -0
- metadata +209 -0
data/README.md
ADDED
@@ -0,0 +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
|
96
|
+
tool to build the project.
|
data/Thorfile
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
# enconding: utf-8
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'thor/rake_compat'
|
5
|
+
require 'rake/testtask'
|
6
|
+
require 'rake'
|
7
|
+
begin
|
8
|
+
require 'yard'
|
9
|
+
rescue LoadError
|
10
|
+
end
|
11
|
+
|
12
|
+
GEM_NAME = 'ritsu'
|
13
|
+
EXTRA_RDOC_FILES = FileList.new('*') do |list|
|
14
|
+
list.exclude(/(^|[^.a-z])[a-z]+/)
|
15
|
+
list.exclude('TODO')
|
16
|
+
end.to_a + ['Thorfile']
|
17
|
+
|
18
|
+
class Default < Thor
|
19
|
+
include Thor::RakeCompat
|
20
|
+
|
21
|
+
Rake::TestTask.new do |t|
|
22
|
+
t.libs << 'lib'
|
23
|
+
test_files = FileList['test/**/*_test.rb']
|
24
|
+
t.test_files = test_files
|
25
|
+
t.verbose = true
|
26
|
+
end
|
27
|
+
|
28
|
+
if defined?(RDoc)
|
29
|
+
RDoc::Task.new do |rdoc|
|
30
|
+
rdoc.main = "README.rdoc"
|
31
|
+
rdoc.rdoc_dir = "rdoc"
|
32
|
+
rdoc.title = GEM_NAME
|
33
|
+
rdoc.rdoc_files.include(*EXTRA_RDOC_FILES)
|
34
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
35
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
YARD::Rake::YardocTask.new do |t|
|
40
|
+
t.files = FileList.new('lib/**/*.rb').to_a + EXTRA_RDOC_FILES
|
41
|
+
t.options << '--incremental' if Rake.application.top_level_tasks.include?('redoc')
|
42
|
+
#t.options += FileList.new(scope('yard/*.rb')).to_a.map {|f| ['-e', f]}.flatten
|
43
|
+
files = FileList.new('doc-src/*').to_a.sort_by {|s| s.size} + %w[VERSION]
|
44
|
+
t.options << '--files' << files.join(',')
|
45
|
+
#t.options << '--template-path' << scope('yard')
|
46
|
+
t.options << '--title' << ENV["YARD_TITLE"] if ENV["YARD_TITLE"]
|
47
|
+
end
|
48
|
+
|
49
|
+
desc "doc", "Generate YARD Documentation"
|
50
|
+
def doc
|
51
|
+
yard
|
52
|
+
end
|
53
|
+
|
54
|
+
begin
|
55
|
+
require 'jeweler'
|
56
|
+
Jeweler::Tasks.new do |s|
|
57
|
+
s.name = GEM_NAME
|
58
|
+
s.version = File.read(File.dirname(__FILE__) + '/VERSION').strip
|
59
|
+
s.rubyforge_project = "ritsu"
|
60
|
+
s.platform = Gem::Platform::RUBY
|
61
|
+
s.summary = "A code generation system that facilitates building C/C++ software with the help of CMake and Doxygen"
|
62
|
+
s.email = "dragonmeteor@gmail.com"
|
63
|
+
s.homepage = "http://github.com/dragonmeteor/ritsu"
|
64
|
+
s.description = "A code generation system that facilitates building C/C++ software with the help of CMake and Doxygen"
|
65
|
+
s.authors = ['dragonmeteor']
|
66
|
+
|
67
|
+
s.has_rdoc = true
|
68
|
+
s.extra_rdoc_files = EXTRA_RDOC_FILES
|
69
|
+
s.rdoc_options += [
|
70
|
+
'--title', 'Ritsu',
|
71
|
+
'--main', 'README.md',
|
72
|
+
'--line-numbers',
|
73
|
+
'--inline-source'
|
74
|
+
]
|
75
|
+
|
76
|
+
s.require_path = 'lib'
|
77
|
+
s.bindir = "bin"
|
78
|
+
s.executables = %w( ritsu )
|
79
|
+
s.files = s.extra_rdoc_files + Dir.glob("{bin,lib}/**/*")
|
80
|
+
s.test_files.include 'test/**/*'
|
81
|
+
s.test_files.exclude 'test/**/output/**'
|
82
|
+
|
83
|
+
s.add_runtime_dependency 'thor', '>= 0.13.4'
|
84
|
+
s.add_runtime_dependency 'activesupport', '>= 2.3.5'
|
85
|
+
|
86
|
+
s.add_development_dependency 'jeweler', '>= 1.4.0'
|
87
|
+
s.add_development_dependency 'yard', '>= 0.5.3'
|
88
|
+
s.add_development_dependency 'maruku', '>= 0.5.9'
|
89
|
+
end
|
90
|
+
|
91
|
+
Jeweler::GemcutterTasks.new
|
92
|
+
rescue LoadError
|
93
|
+
puts "Jeweler, or one of its dependencies, is not available. Install it with: gem install jeweler"
|
94
|
+
end
|
95
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
data/bin/ritsu
ADDED
@@ -0,0 +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
|
32
|
+
end
|
data/lib/ritsu/block.rb
ADDED
@@ -0,0 +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
|
259
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
|
3
|
+
module Test
|
4
|
+
module Unit
|
5
|
+
class TestCase
|
6
|
+
def self.must(name, &block)
|
7
|
+
test_name = "test_#{name.gsub(/\s+/,'_')}".to_sym
|
8
|
+
defined = instance_method(test_name) rescue false
|
9
|
+
raise "#{test_name} is already defined in #{self}" if defined
|
10
|
+
if block_given?
|
11
|
+
define_method(test_name, &block)
|
12
|
+
else
|
13
|
+
define_method(test_name) do
|
14
|
+
flunk "No implementation provided for #{name}"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'active_support'
|
3
|
+
require File.dirname(__FILE__) + '/utility/accessors'
|
4
|
+
require File.dirname(__FILE__) + '/utility/instance_set'
|
5
|
+
|
6
|
+
module Ritsu
|
7
|
+
class ExternalLibrary
|
8
|
+
include Ritsu::Utility::Accessors
|
9
|
+
include Ritsu::Utility::InstanceSet
|
10
|
+
|
11
|
+
attr_reader :name
|
12
|
+
attr_accessor :cmake_name
|
13
|
+
attr_accessor :cmake_find_script
|
14
|
+
attr_accessor :cmake_depend_script
|
15
|
+
|
16
|
+
attr_method :cmake_name
|
17
|
+
attr_method :cmake_find_script
|
18
|
+
attr_method :cmake_depend_script
|
19
|
+
|
20
|
+
def initialize(name, options={})
|
21
|
+
options = {
|
22
|
+
:cmake_name => '',
|
23
|
+
:cmake_find_script => '',
|
24
|
+
:cmake_depend_script => ''}.merge(options)
|
25
|
+
@name = name
|
26
|
+
@cmake_name = options[:cmake_name]
|
27
|
+
@cmake_find_script = options[:cmake_find_script]
|
28
|
+
@cmake_depend_script = options[:cmake_depend_script]
|
29
|
+
ExternalLibrary.instances << self
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.validate_instance(instance)
|
33
|
+
if instances.select { |x| x.name == instance.name }.length > 0
|
34
|
+
raise ArgumentError.new "external library with name '#{instance.name}' already exists"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.find_by_name(name)
|
39
|
+
instances.each do |instance|
|
40
|
+
if instance.name == name
|
41
|
+
return instance
|
42
|
+
end
|
43
|
+
end
|
44
|
+
return nil
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'active_support'
|
3
|
+
require File.dirname(__FILE__) + '/external_library'
|
4
|
+
require File.dirname(__FILE__) + '/utility/single_instance'
|
5
|
+
require File.dirname(__FILE__) + '/utility/strings'
|
6
|
+
require File.dirname(__FILE__) + '/src_files/project_cmake_lists'
|
7
|
+
require File.dirname(__FILE__) + '/src_files/project_config_header_file'
|
8
|
+
require File.dirname(__FILE__) + '/src_files/project_config_header_template_file'
|
9
|
+
|
10
|
+
module Ritsu
|
11
|
+
class Project
|
12
|
+
include Ritsu::Utility::SingleInstance
|
13
|
+
include Ritsu::Utility::Strings
|
14
|
+
|
15
|
+
attr_reader :name
|
16
|
+
attr_reader :targets
|
17
|
+
attr_reader :external_libraries
|
18
|
+
attr_reader :src_files
|
19
|
+
attr_accessor :project_dir
|
20
|
+
attr_reader :cmake_lists
|
21
|
+
attr_reader :config_header_file
|
22
|
+
attr_reader :config_header_template_file
|
23
|
+
|
24
|
+
def initialize_instance(name)
|
25
|
+
if !is_c_name?(name)
|
26
|
+
raise ArgumentError.new(
|
27
|
+
"the project name must be a valid C name")
|
28
|
+
end
|
29
|
+
|
30
|
+
@name = name
|
31
|
+
@targets = Set.new
|
32
|
+
@external_libraries = Set.new
|
33
|
+
@src_files = Set.new
|
34
|
+
@project_dir = File.expand_path('.')
|
35
|
+
|
36
|
+
@cmake_lists = Ritsu::SrcFiles::ProjectCmakeLists.new(self)
|
37
|
+
@config_header_file = Ritsu::SrcFiles::ProjectConfigHeaderFile.new(self)
|
38
|
+
@config_header_template_file = Ritsu::SrcFiles::ProjectConfigHeaderTemplateFile.new(self)
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.create(name)
|
42
|
+
project = Project.new(name)
|
43
|
+
yield project if block_given?
|
44
|
+
project
|
45
|
+
end
|
46
|
+
|
47
|
+
def add_external_library(name)
|
48
|
+
library = ExternalLibrary.new(name)
|
49
|
+
yield library if block_given?
|
50
|
+
@external_libraries << library
|
51
|
+
end
|
52
|
+
|
53
|
+
def project
|
54
|
+
self
|
55
|
+
end
|
56
|
+
|
57
|
+
def src_dir
|
58
|
+
project_dir + "/src"
|
59
|
+
end
|
60
|
+
|
61
|
+
def compute_src_path(path, options={})
|
62
|
+
options = {:relative_to => :src}.merge(options)
|
63
|
+
case options[:reltavie_to]
|
64
|
+
when :src
|
65
|
+
path
|
66
|
+
when :absolute
|
67
|
+
src_dir = Pathname.new(self.src_dir)
|
68
|
+
input_path = Pathname.new(path)
|
69
|
+
input_path.relative_path_from(src_dir).to_s
|
70
|
+
else
|
71
|
+
raise ArgumentError.new("option :relative_to must be either :src or :absolute")
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def include_file(filename, options={})
|
76
|
+
file_content = Ritsu::Utility::Files.read(filename)
|
77
|
+
instance_eval(file_content)
|
78
|
+
end
|
79
|
+
|
80
|
+
def update
|
81
|
+
src_files.each do |src_file|
|
82
|
+
src_file.update
|
83
|
+
end
|
84
|
+
targets.each do |target|
|
85
|
+
target.update
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/utility/file_robot'
|
2
|
+
require File.dirname(__FILE__) + '/utility/instance_set'
|
3
|
+
require File.dirname(__FILE__) + '/utility/strings'
|
4
|
+
|
5
|
+
module Ritsu
|
6
|
+
class ProjectGenerator
|
7
|
+
attr_reader :name
|
8
|
+
|
9
|
+
def self.generator_classes
|
10
|
+
@generator_classes ||= {}
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.is_valid_generator_name?(name)
|
14
|
+
Ritsu::Utility::Strings::is_c_name?(name)
|
15
|
+
end
|
16
|
+
|
17
|
+
def initialize(name)
|
18
|
+
if !ProjectGenerator.is_valid_generator_name?(name)
|
19
|
+
raise ArgumentError.new("'#{name}' is not a valid project name (i.e., a C name)")
|
20
|
+
end
|
21
|
+
@name = name
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.validate_instance(instance)
|
25
|
+
if instances.select { |x| x.name == instance.name }.length > 0
|
26
|
+
raise ArgumentError.new("project generator with name '#{instance.name}' already exists")
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def generate(*args)
|
31
|
+
raise NotImplementedError.new
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|