wrapture 0.2.2 → 0.5.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.
@@ -0,0 +1,107 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+
3
+ # frozen_string_literal: true
4
+
5
+ #--
6
+ # Copyright 2020 Joel E. Anderson
7
+ #
8
+ # Licensed under the Apache License, Version 2.0 (the "License");
9
+ # you may not use this file except in compliance with the License.
10
+ # You may obtain a copy of the License at
11
+ #
12
+ # http://www.apache.org/licenses/LICENSE-2.0
13
+ #
14
+ # Unless required by applicable law or agreed to in writing, software
15
+ # distributed under the License is distributed on an "AS IS" BASIS,
16
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
+ # See the License for the specific language governing permissions and
18
+ # limitations under the License.
19
+ #++
20
+
21
+ module Wrapture
22
+ # A comment that can be inserted in generated source code.
23
+ #
24
+ # Comments are primarily used to insert documentation about generated code for
25
+ # documentation generation tools such as Doxygen.
26
+ class Comment
27
+ # Validates a doc string.
28
+ def self.validate_doc(doc)
29
+ raise InvalidDoc, 'a doc must be a string' unless doc.is_a?(String)
30
+ end
31
+
32
+ # The raw text of the comment.
33
+ attr_reader :text
34
+
35
+ # Creates a comment from a string. If the provided string is nil, then an
36
+ # empty string is used.
37
+ def initialize(comment = '')
38
+ @text = comment.nil? ? '' : comment
39
+ end
40
+
41
+ # True if this comment is empty, false otherwise.
42
+ def empty?
43
+ @text.empty?
44
+ end
45
+
46
+ # Yields each line of the comment formatted as specified.
47
+ def format(line_prefix: '// ', first_line: nil, last_line: nil,
48
+ max_line_length: 80)
49
+ return if @text.empty?
50
+
51
+ yield first_line if first_line
52
+
53
+ paragraphs(max_line_length - line_prefix.length) do |line|
54
+ yield "#{line_prefix}#{line}".rstrip
55
+ end
56
+
57
+ yield last_line if last_line
58
+ end
59
+
60
+ # Calls the given block for each line of the comment formatted using Doxygen
61
+ # style.
62
+ def format_as_doxygen(max_line_length: 80, &block)
63
+ format(line_prefix: ' * ', first_line: '/**',
64
+ last_line: ' */', max_line_length: max_line_length) do |line|
65
+ block.call(line)
66
+ end
67
+ end
68
+
69
+ private
70
+
71
+ # Yields the comment converted into paragraph-style blocks.
72
+ #
73
+ # Consecutive lines with text are concatenated together to the maximum line
74
+ # length, regardless of the original line length in the comment. One or more
75
+ # empty lines are written as a single empty line, separating paragraphs.
76
+ #
77
+ # Yielded lines may have trailing spaces, which are not considered part of
78
+ # the maximum length. The caller must strip these off.
79
+ def paragraphs(line_length)
80
+ running_line = String.new
81
+ newline_mode = true
82
+ @text.each_line do |line|
83
+ if line.strip.empty?
84
+ unless newline_mode
85
+ yield running_line
86
+ yield ''
87
+ running_line.clear
88
+ newline_mode = true
89
+ end
90
+ else
91
+ newline_mode = false
92
+ end
93
+
94
+ line.scan(/\S+/) do |word|
95
+ if running_line.length + word.length > line_length
96
+ yield running_line
97
+ running_line = String.new("#{word} ")
98
+ else
99
+ running_line << word << ' '
100
+ end
101
+ end
102
+ end
103
+
104
+ yield running_line
105
+ end
106
+ end
107
+ end
@@ -1,34 +1,76 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+
1
3
  # frozen_string_literal: true
2
4
 
5
+ #--
6
+ # Copyright 2019-2020 Joel E. Anderson
7
+ #
8
+ # Licensed under the Apache License, Version 2.0 (the "License");
9
+ # you may not use this file except in compliance with the License.
10
+ # You may obtain a copy of the License at
11
+ #
12
+ # http://www.apache.org/licenses/LICENSE-2.0
13
+ #
14
+ # Unless required by applicable law or agreed to in writing, software
15
+ # distributed under the License is distributed on an "AS IS" BASIS,
16
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
+ # See the License for the specific language governing permissions and
18
+ # limitations under the License.
19
+ #++
20
+
3
21
  module Wrapture
4
- ##
5
22
  # A description of a constant.
6
23
  class ConstantSpec
24
+ # Normalizes a hash specification of a constant. Normalization will check
25
+ # for things like invalid keys, duplicate entries in include lists, and
26
+ # will set missing keys to their default value (for example, an empty list
27
+ # if no includes are given).
7
28
  def self.normalize_spec_hash(spec)
8
- normalized_spec = spec.dup
29
+ Comment.validate_doc(spec['doc']) if spec.key?('doc')
30
+
31
+ normalized = spec.dup
9
32
 
10
- normalized_spec['includes'] ||= []
11
- normalized_spec['includes'].uniq!
33
+ normalized['version'] = Wrapture.spec_version(spec)
34
+ normalized['includes'] = Wrapture.normalize_includes spec['includes']
12
35
 
13
- normalized_spec
36
+ normalized
14
37
  end
15
38
 
39
+ # Creates a constant spec based on the provided hash spec
40
+ #
41
+ # The hash must have the following keys:
42
+ # name:: the name of the constant
43
+ # type:: the type of the constant
44
+ # value:: the value to assign to the constant
45
+ # includes:: a list of includes that need to be added in order for this
46
+ # constant to be valid (for example, includes for the type and value).
47
+ #
48
+ # The following keys are optional:
49
+ # doc:: a string containing the documentation for this constant
16
50
  def initialize(spec)
17
- @spec = ConstantSpec.normalize_spec_hash spec
51
+ @spec = ConstantSpec.normalize_spec_hash(spec)
52
+ @doc = @spec.key?('doc') ? Comment.new(@spec['doc']) : nil
53
+ @type = TypeSpec.new(@spec['type'])
18
54
  end
19
55
 
56
+ # A list of includes needed for the declaration of this constant.
20
57
  def declaration_includes
21
58
  @spec['includes'].dup
22
59
  end
23
60
 
61
+ # A list of includes needed for the definition of this constant.
24
62
  def definition_includes
25
63
  @spec['includes'].dup
26
64
  end
27
65
 
28
- def declaration
29
- "static const #{ClassSpec.typed_variable(@spec['type'], @spec['name'])}"
66
+ # Calls the given block once for each line of the declaration of this
67
+ # constant, including any documentation.
68
+ def declaration(&block)
69
+ @doc&.format_as_doxygen(max_line_length: 76) { |line| block.call(line) }
70
+ block.call("static const #{@type.variable(@spec['name'])};")
30
71
  end
31
72
 
73
+ # The definition of this constant.
32
74
  def definition(class_name)
33
75
  expanded_name = "#{class_name}::#{@spec['name']}"
34
76
  "const #{@spec['type']} #{expanded_name} = #{@spec['value']}"
@@ -0,0 +1,41 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+
3
+ # frozen_string_literal: true
4
+
5
+ #--
6
+ # Copyright 2019-2020 Joel E. Anderson
7
+ #
8
+ # Licensed under the Apache License, Version 2.0 (the "License");
9
+ # you may not use this file except in compliance with the License.
10
+ # You may obtain a copy of the License at
11
+ #
12
+ # http://www.apache.org/licenses/LICENSE-2.0
13
+ #
14
+ # Unless required by applicable law or agreed to in writing, software
15
+ # distributed under the License is distributed on an "AS IS" BASIS,
16
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
+ # See the License for the specific language governing permissions and
18
+ # limitations under the License.
19
+ #++
20
+
21
+ module Wrapture
22
+ # A string denoting an equivalent struct type or value.
23
+ EQUIVALENT_STRUCT_KEYWORD = 'equivalent-struct'
24
+
25
+ # A string denoting a pointer to an equivalent struct type or value.
26
+ EQUIVALENT_POINTER_KEYWORD = 'equivalent-struct-pointer'
27
+
28
+ # A string denoting the return value of a wrapped function call.
29
+ RETURN_VALUE_KEYWORD = 'return-value'
30
+
31
+ # A string denoting a reference to the object a method is called on.
32
+ SELF_REFERENCE_KEYWORD = 'self-reference'
33
+
34
+ # A string denoting a reference to a template.
35
+ TEMPLATE_USE_KEYWORD = 'use-template'
36
+
37
+ # A list of all keywords.
38
+ KEYWORDS = [EQUIVALENT_STRUCT_KEYWORD, EQUIVALENT_POINTER_KEYWORD,
39
+ SELF_REFERENCE_KEYWORD, RETURN_VALUE_KEYWORD,
40
+ TEMPLATE_USE_KEYWORD].freeze
41
+ end
@@ -0,0 +1,155 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+
3
+ # frozen_string_literal: true
4
+
5
+ #--
6
+ # Copyright 2020 Joel E. Anderson
7
+ #
8
+ # Licensed under the Apache License, Version 2.0 (the "License");
9
+ # you may not use this file except in compliance with the License.
10
+ # You may obtain a copy of the License at
11
+ #
12
+ # http://www.apache.org/licenses/LICENSE-2.0
13
+ #
14
+ # Unless required by applicable law or agreed to in writing, software
15
+ # distributed under the License is distributed on an "AS IS" BASIS,
16
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
+ # See the License for the specific language governing permissions and
18
+ # limitations under the License.
19
+ #++
20
+
21
+ module Wrapture
22
+ # A description of an enumeration.
23
+ class EnumSpec
24
+ # Returns a normalized copy of a hash specification of an enumeration in
25
+ # place. See normalize_spec_hash! for details.
26
+ def self.normalize_spec_hash(spec)
27
+ normalize_spec_hash!(Marshal.load(Marshal.dump(spec)))
28
+ end
29
+
30
+ # Normalizes a hash specification of an enumeration in place. Normalization
31
+ # will remove duplicate entries in include lists and check for a name key.
32
+ def self.normalize_spec_hash!(spec)
33
+ unless spec.key?('name')
34
+ raise MissingSpecKey, 'a name is required for enumerations'
35
+ end
36
+
37
+ if spec.key?('elements')
38
+ unless spec['elements'].is_a?(Array)
39
+ raise InvalidSpecKey, 'the elements key must be an array'
40
+ end
41
+ else
42
+ raise MissingSpecKey, 'elements are required for enumerations'
43
+ end
44
+
45
+ spec['includes'] = Wrapture.normalize_includes(spec['includes'])
46
+ spec['elements'].each do |element|
47
+ element['includes'] = Wrapture.normalize_includes(element['includes'])
48
+ end
49
+
50
+ spec
51
+ end
52
+
53
+ # Creates an enumeration specification based on the provided hash spec.
54
+ def initialize(spec)
55
+ @spec = EnumSpec.normalize_spec_hash(spec)
56
+
57
+ @doc = Comment.new(@spec.fetch('doc', nil))
58
+ end
59
+
60
+ # Generates the wrapper definition file.
61
+ def generate_wrapper
62
+ filename = "#{@spec['name']}.hpp"
63
+
64
+ File.open(filename, 'w') do |file|
65
+ definition_contents do |line|
66
+ file.puts(line)
67
+ end
68
+ end
69
+
70
+ [filename]
71
+ end
72
+
73
+ # The name of the enumeration.
74
+ def name
75
+ @spec['name']
76
+ end
77
+
78
+ private
79
+
80
+ # Yields each line of the definition of the wrapper for this enum.
81
+ def definition_contents
82
+ indent = 0
83
+
84
+ yield "#ifndef #{header_guard}"
85
+ yield "#define #{header_guard}"
86
+ yield
87
+
88
+ definition_includes.each { |filename| yield "#include <#{filename}>" }
89
+ yield
90
+
91
+ if @spec.key?('namespace')
92
+ yield "namespace #{@spec['namespace']} {"
93
+ yield
94
+ indent += 2
95
+ end
96
+
97
+ @doc.format_as_doxygen(max_line_length: 76) do |line|
98
+ yield "#{' ' * indent}#{line}"
99
+ end
100
+
101
+ yield "#{' ' * indent}enum class #{name} {"
102
+ indent += 2
103
+
104
+ elements = @spec['elements']
105
+ elements[0...-1].each do |element|
106
+ element_doc(element) { |line| yield "#{' ' * indent}#{line}" }
107
+ element_definition(element) { |line| yield "#{' ' * indent}#{line}," }
108
+ end
109
+
110
+ element_doc(elements.last) { |line| yield "#{' ' * indent}#{line}" }
111
+ element_definition(elements.last) do |line|
112
+ yield "#{' ' * indent}#{line}"
113
+ end
114
+
115
+ indent -= 2
116
+ yield "#{' ' * indent}};"
117
+ yield
118
+ yield '}' if @spec.key?('namespace')
119
+ yield
120
+ yield "#endif /* #{header_guard} */"
121
+ end
122
+
123
+ # A list of the includes needed for the definition of the enumeration.
124
+ def definition_includes
125
+ includes = @spec['includes'].dup
126
+
127
+ @spec['elements'].each do |element|
128
+ includes.concat(element['includes'])
129
+ end
130
+
131
+ includes.uniq
132
+ end
133
+
134
+ # Yields each line of the definition of an element.
135
+ def element_definition(element)
136
+ if element.key?('value')
137
+ yield "#{element['name']} = #{element['value']}"
138
+ else
139
+ yield element['name']
140
+ end
141
+ end
142
+
143
+ # Calls the given block once for each line of the documentation for an
144
+ # element.
145
+ def element_doc(element, &block)
146
+ doc = Comment.new(element.fetch('doc', nil))
147
+ doc.format_as_doxygen(max_line_length: 74) { |line| block.call(line) }
148
+ end
149
+
150
+ # The header guard for the enumeration.
151
+ def header_guard
152
+ "__#{@spec['name'].upcase}_HPP"
153
+ end
154
+ end
155
+ end
@@ -0,0 +1,67 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+
3
+ # frozen_string_literal: true
4
+
5
+ #--
6
+ # Copyright 2019-2020 Joel E. Anderson
7
+ #
8
+ # Licensed under the Apache License, Version 2.0 (the "License");
9
+ # you may not use this file except in compliance with the License.
10
+ # You may obtain a copy of the License at
11
+ #
12
+ # http://www.apache.org/licenses/LICENSE-2.0
13
+ #
14
+ # Unless required by applicable law or agreed to in writing, software
15
+ # distributed under the License is distributed on an "AS IS" BASIS,
16
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
+ # See the License for the specific language governing permissions and
18
+ # limitations under the License.
19
+ #++
20
+
21
+ module Wrapture
22
+ # An error from the Wrapture library
23
+ class WraptureError < StandardError
24
+ end
25
+
26
+ # A documentation string is invalid.
27
+ class InvalidDoc < WraptureError
28
+ end
29
+
30
+ # A template has been invoked in an unsupported way.
31
+ class InvalidTemplateUsage < WraptureError
32
+ end
33
+
34
+ # The spec has a key that is not valid.
35
+ class InvalidSpecKey < WraptureError
36
+ # Creates an InvalidSpecKey with the given message. A list of valid values
37
+ # may optionally be passed to +valid_keys+ which will be added to the end
38
+ # of the message.
39
+ def initialize(message, valid_keys: [])
40
+ complete_message = message.dup
41
+
42
+ unless valid_keys.empty?
43
+ complete_message << ' (valid values are \''
44
+ complete_message << valid_keys.join('\', \'')
45
+ complete_message << '\')'
46
+ end
47
+
48
+ super(complete_message)
49
+ end
50
+ end
51
+
52
+ # The spec is missing a key that is required.
53
+ class MissingSpecKey < WraptureError
54
+ end
55
+
56
+ # Missing a namespace in the class spec
57
+ class NoNamespace < WraptureError
58
+ end
59
+
60
+ # The spec cannot be defined due to missing information.
61
+ class UndefinableSpec < WraptureError
62
+ end
63
+
64
+ # The spec version is not supported by this version of Wrapture.
65
+ class UnsupportedSpecVersion < WraptureError
66
+ end
67
+ end