docrb-html 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (104) hide show
  1. checksums.yaml +7 -0
  2. data/.editorconfig +21 -0
  3. data/.gitignore +2 -0
  4. data/.rubocop.yml +52 -0
  5. data/Gemfile +7 -0
  6. data/Gemfile.lock +37 -0
  7. data/assets/breadcrumb.scss +25 -0
  8. data/assets/checkbox.scss +34 -0
  9. data/assets/class_header.scss +75 -0
  10. data/assets/class_mod_name.scss +8 -0
  11. data/assets/component_list.scss +4 -0
  12. data/assets/container.scss +9 -0
  13. data/assets/doc_box.scss +79 -0
  14. data/assets/documentation_block.scss +5 -0
  15. data/assets/favicon.ico +0 -0
  16. data/assets/fonts.scss +105 -0
  17. data/assets/footer.scss +15 -0
  18. data/assets/images/balance.svg +9 -0
  19. data/assets/images/breadcrumb_separator.svg +1 -0
  20. data/assets/images/checkbox-off.svg +1 -0
  21. data/assets/images/checkbox-on.svg +1 -0
  22. data/assets/images/chevron.svg +1 -0
  23. data/assets/images/docrb-label.svg +20 -0
  24. data/assets/images/github.svg +11 -0
  25. data/assets/images/home.svg +1 -0
  26. data/assets/images/inherited.svg +1 -0
  27. data/assets/images/override.svg +1 -0
  28. data/assets/images/questionmark.svg +1 -0
  29. data/assets/images/rubygems.svg +9 -0
  30. data/assets/images/user.svg +12 -0
  31. data/assets/js/filtering.js +66 -0
  32. data/assets/links.scss +16 -0
  33. data/assets/markdown.scss +41 -0
  34. data/assets/method_argument.scss +75 -0
  35. data/assets/method_display.scss +27 -0
  36. data/assets/method_list.scss +51 -0
  37. data/assets/project_header.scss +55 -0
  38. data/assets/reference.scss +36 -0
  39. data/assets/shared.scss +23 -0
  40. data/assets/style.scss +43 -0
  41. data/assets/symbol.scss +5 -0
  42. data/assets/tab_bar.scss +22 -0
  43. data/assets/text_block.scss +23 -0
  44. data/assets/type_definition.scss +3 -0
  45. data/assets/typedef.scss +6 -0
  46. data/bin/console +15 -0
  47. data/bin/setup +8 -0
  48. data/exe/docrb-html +12 -0
  49. data/lib/renderer/component/breadcrumb.rb +14 -0
  50. data/lib/renderer/component/checkbox.rb +9 -0
  51. data/lib/renderer/component/class_header.rb +14 -0
  52. data/lib/renderer/component/class_mod_name.rb +9 -0
  53. data/lib/renderer/component/component_list.rb +15 -0
  54. data/lib/renderer/component/doc_box.rb +38 -0
  55. data/lib/renderer/component/documentation_block.rb +9 -0
  56. data/lib/renderer/component/footer.rb +9 -0
  57. data/lib/renderer/component/markdown.rb +9 -0
  58. data/lib/renderer/component/method_argument.rb +106 -0
  59. data/lib/renderer/component/method_display.rb +9 -0
  60. data/lib/renderer/component/method_list.rb +9 -0
  61. data/lib/renderer/component/project_header.rb +9 -0
  62. data/lib/renderer/component/reference.rb +24 -0
  63. data/lib/renderer/component/symbol.rb +9 -0
  64. data/lib/renderer/component/tab_bar.rb +9 -0
  65. data/lib/renderer/component/text_block.rb +15 -0
  66. data/lib/renderer/component/type_definition.rb +9 -0
  67. data/lib/renderer/component/typedef.rb +9 -0
  68. data/lib/renderer/component.rb +50 -0
  69. data/lib/renderer/core_extensions.rb +11 -0
  70. data/lib/renderer/defs/specialized_object.rb +172 -0
  71. data/lib/renderer/defs/specialized_projection.rb +31 -0
  72. data/lib/renderer/defs.rb +180 -0
  73. data/lib/renderer/helpers.rb +82 -0
  74. data/lib/renderer/metadata.rb +44 -0
  75. data/lib/renderer/page.rb +17 -0
  76. data/lib/renderer/template.rb +38 -0
  77. data/lib/renderer/version.rb +5 -0
  78. data/lib/renderer.rb +129 -0
  79. data/renderer.gemspec +31 -0
  80. data/script/makecomponent +23 -0
  81. data/script/reload.js +14 -0
  82. data/script/serve +2 -0
  83. data/script/watch +17 -0
  84. data/templates/base.erb +25 -0
  85. data/templates/breadcrumb.erb +28 -0
  86. data/templates/checkbox.erb +8 -0
  87. data/templates/class_header.erb +53 -0
  88. data/templates/class_mod_name.erb +3 -0
  89. data/templates/component_list.erb +21 -0
  90. data/templates/doc_box.erb +82 -0
  91. data/templates/documentation_block.erb +18 -0
  92. data/templates/footer.erb +9 -0
  93. data/templates/markdown.erb +3 -0
  94. data/templates/method_argument.erb +29 -0
  95. data/templates/method_display.erb +28 -0
  96. data/templates/method_list.erb +25 -0
  97. data/templates/project_header.erb +38 -0
  98. data/templates/reference.erb +14 -0
  99. data/templates/symbol.erb +3 -0
  100. data/templates/tab_bar.erb +7 -0
  101. data/templates/text_block.erb +16 -0
  102. data/templates/type_definition.erb +4 -0
  103. data/templates/typedef.erb +3 -0
  104. metadata +178 -0
@@ -0,0 +1,106 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Renderer
4
+ class Component
5
+ class MethodArgument < Component
6
+ prop :type, :name, :value, :value_type, :computed
7
+
8
+ def prepare
9
+ @computed = {
10
+ rest: rest_arg,
11
+ name:,
12
+ continuation: continuation_for_type,
13
+ value: value_for_argument
14
+ }
15
+ end
16
+
17
+ def continuation_for_type
18
+ if type == "kwarg" || type == "kwoptarg"
19
+ :colon
20
+ elsif type&.index("opt")&.zero?
21
+ :equal
22
+ end
23
+ end
24
+
25
+ REST_ARG_BY_TYPE = {
26
+ "kwrestarg" => :double,
27
+ "restarg" => :single,
28
+ "blockarg" => :block
29
+ }.freeze
30
+
31
+ def rest_arg
32
+ REST_ARG_BY_TYPE[type]
33
+ end
34
+
35
+ def value_for_argument
36
+ return if value_type.nil?
37
+
38
+ case value_type
39
+ when "sym"
40
+ { kind: :symbol, value: }
41
+ when "bool"
42
+ { kind: :symbol, value: value.inspect }
43
+ when "nil"
44
+ { kind: :symbol, value: "nil" }
45
+ when "int"
46
+ { kind: :number, value: }
47
+ when "str"
48
+ { kind: :string, value: }
49
+ when "send"
50
+ method_call_argument(value)
51
+ when "const"
52
+ const_value(value)
53
+ else
54
+ { kind: :plain, value: }
55
+ end
56
+ end
57
+
58
+ def method_call_argument(value)
59
+ class_path = value[:target].map do |i|
60
+ next { kind: :symbol, value: "self" } if i == "self"
61
+
62
+ # TODO: Is this plain?
63
+ [{ kind: :class_or_module, value: i }, { kind: :plain, value: "::" }]
64
+ end.flatten
65
+
66
+ class_path.pop
67
+
68
+ {
69
+ kind: :method_call_argument,
70
+ value: [
71
+ class_path,
72
+ { kind: :plain, value: "." },
73
+ { kind: :plain, value: value[:name] }
74
+ ].flatten
75
+ }
76
+ end
77
+
78
+ def const_value(value)
79
+ class_path = value[:target].map do |i|
80
+ # TODO: Is this plain?
81
+ [{ kind: :class_or_module, value: i }, { kind: :continuation, double: true }]
82
+ end.flatten
83
+
84
+ class_path.pop
85
+
86
+ if class_path.empty?
87
+ return {
88
+ kind: :const,
89
+ value: [
90
+ { kind: :class_or_module, value: value[:name] }
91
+ ].flatten
92
+ }
93
+ end
94
+
95
+ {
96
+ kind: :const,
97
+ value: [
98
+ class_path,
99
+ { kind: :continuation, double: true },
100
+ { kind: :class_or_module, value: value[:name] }
101
+ ].flatten
102
+ }
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Renderer
4
+ class Component
5
+ class MethodDisplay < Component
6
+ prop :visibility, :type, :name, :href, :args, :doc, :decoration, :short_type
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Renderer
4
+ class Component
5
+ class MethodList < Component
6
+ prop :list
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Renderer
4
+ class Component
5
+ class ProjectHeader < Component
6
+ prop :name, :description, :owner, :license, :links
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Renderer
4
+ class Component
5
+ class Reference < Component
6
+ prop :ref, :unresolved, :path
7
+
8
+ def prepare
9
+ @unresolved = !ref[:ref_path]
10
+ return if @unresolved
11
+
12
+ components = ref[:ref_path].dup
13
+ @path = if ref[:ref_type] == "method"
14
+ method_name = components.pop
15
+ parent_name = components.pop
16
+ components + ["#{parent_name}.html#{method_name}"]
17
+ else
18
+ last = components.pop
19
+ (components + ["#{last}.html"]).compact
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Renderer
4
+ class Component
5
+ class Symbol < Component
6
+ prop :name
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Renderer
4
+ class Component
5
+ class TabBar < Component
6
+ prop :items, :selected_index
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Renderer
4
+ class Component
5
+ class TextBlock < Component
6
+ prop :list
7
+
8
+ def prepare
9
+ return unless !@list.nil? && !@list.is_a?(Array)
10
+
11
+ @list = [{ type: "html", contents: @list }]
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Renderer
4
+ class Component
5
+ class TypeDefinition < Component
6
+ prop :type, :name, :href
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Renderer
4
+ class Component
5
+ class Typedef < Component
6
+ prop :name
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Renderer
4
+ class Component
5
+ attr_accessor :id
6
+
7
+ def self.prop(*names)
8
+ attr_accessor(*names)
9
+
10
+ @props ||= []
11
+ @props.append(*names)
12
+ end
13
+
14
+ class << self
15
+ attr_writer :template
16
+ end
17
+
18
+ def self.template
19
+ return @template_instance if @template_instance
20
+
21
+ t_name = @template || name.split("::").last.snakify
22
+ @template_instance = Template.new("templates/#{t_name}.erb")
23
+ end
24
+
25
+ def props = @props ||= self.class.instance_variable_get(:@props) || []
26
+ def template = self.class.template
27
+
28
+ def initialize(**kwargs)
29
+ @id = kwargs.delete(:id)
30
+ kwargs.each do |k, v|
31
+ raise ArgumentError, "Unknown property #{k} for #{self.class.name}" unless props.include? k
32
+
33
+ send("#{k}=", v)
34
+ end
35
+ end
36
+
37
+ def prepare; end
38
+
39
+ def render(&)
40
+ prepare
41
+ opts = props.map { [_1, send(_1)] }.to_h
42
+ opts[:id] = @id
43
+ template.render(HELPERS, **opts, &)
44
+ end
45
+ end
46
+ end
47
+
48
+ Dir[Pathname.new(__dir__).join("component/*.rb")].each do |file|
49
+ require_relative "component/#{Pathname.new(file).basename}"
50
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ class String
4
+ def snakify
5
+ gsub(/::/, "/")
6
+ .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
7
+ .gsub(/([a-z\d])([A-Z])/, '\1_\2')
8
+ .tr("-", "_")
9
+ .downcase
10
+ end
11
+ end
@@ -0,0 +1,172 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Renderer
4
+ class Defs
5
+ class SpecializedObject
6
+ def initialize(obj, parent, provider)
7
+ @obj = obj
8
+ @provider = provider
9
+ prepare(parent)
10
+ end
11
+
12
+ def self.specialize(obj, parent, provider)
13
+ return nil if obj.nil?
14
+
15
+ new(obj, parent, provider)
16
+ end
17
+
18
+ def [](key) = @obj[key]
19
+ def fetch(*, **, &) = @obj.fetch(*, **, &)
20
+
21
+ def resolve(name)
22
+ named = named_as(name)
23
+ modules.find(&named) \
24
+ || classes.find(&named) \
25
+ || attributes&.find(&named) \
26
+ || defs.find(&named) \
27
+ || sdefs.find(&named)
28
+ end
29
+
30
+ def resolve_inheritance(name)
31
+ named = named_as(name)
32
+ modules.find(&named) \
33
+ || classes.find(&named) \
34
+ || parent&.resolve_inheritance(name)
35
+ end
36
+
37
+ def resolve_parent(name)
38
+ parent = self[:parent]
39
+ return { type: "Unknown Ref", name: } unless parent
40
+
41
+ parent.resolve(name) || parent.resolve_parent(name)
42
+ end
43
+
44
+ def resolve_path(path)
45
+ p = path.dup
46
+ obj = self
47
+ until p.empty?
48
+ obj = obj.resolve(p[0])
49
+ return unless obj
50
+
51
+ p.shift
52
+ end
53
+ obj
54
+ end
55
+
56
+ def resolve_qualified(obj)
57
+ result = nil
58
+ query = nil
59
+
60
+ if obj[:class_path]&.length&.> 0
61
+ query = obj[:class_path] + [obj[:name]]
62
+ result = root.resolve_path(query)
63
+ else
64
+ query = obj[:name]
65
+ result = resolve(query)
66
+ result ||= resolve_inheritance(query)
67
+ end
68
+
69
+ puts "Qualified resolution of #{query.inspect} by #{name} failed." unless result
70
+
71
+ result
72
+ end
73
+
74
+ def method_missing(method_name, *args, **kwargs, &)
75
+ var = "@#{method_name}".to_sym
76
+ if instance_variables.include?(var)
77
+ instance_variable_get(var)
78
+ elsif @obj.key? method_name
79
+ @obj.fetch(method_name)
80
+ else
81
+ super
82
+ end
83
+ end
84
+
85
+ def respond_to_missing?(method_name, include_private = false)
86
+ instance_variables.include?("@#{method_name}".to_sym) || @obj.key?(method_name) || super
87
+ end
88
+
89
+ def to_s
90
+ "<SpecializedObject for #{@provider.path_of(@obj).join("::")}>"
91
+ end
92
+
93
+ def inspect = to_s
94
+
95
+ def prepare_inheritance
96
+ @inheritance_prepared = true
97
+ @inherits = coerce_inheritance_data(@obj[:inherits])
98
+ @extends = @obj[:extends]&.map { resolve_qualified _1 }
99
+ @includes = @obj[:includes]&.map { resolve_qualified _1 }
100
+ @classes.each(&:prepare_inheritance)
101
+ @modules.each(&:prepare_inheritance)
102
+ end
103
+
104
+ private
105
+
106
+ def make_path
107
+ r = [name]
108
+ p = parent
109
+ while p
110
+ r << p.name
111
+ p = p.parent
112
+ end
113
+
114
+ r.reverse
115
+ end
116
+
117
+ def parent_of(parent)
118
+ return unless parent
119
+
120
+ p = parent
121
+ while p
122
+ return p unless p.parent
123
+
124
+ p = p.parent
125
+ end
126
+
127
+ p
128
+ end
129
+
130
+ def specialize_defs(defs, _parent)
131
+ defs.values.map do |i|
132
+ decoration = if i[:source] == "inheritance"
133
+ "inherited"
134
+ elsif i[:overriding]
135
+ "override"
136
+ else
137
+ ""
138
+ end
139
+
140
+ origin = i[:source]
141
+
142
+ @provider.find_source(i)
143
+ .merge({ decoration:, origin: })
144
+ end
145
+ end
146
+
147
+ def coerce_inheritance_data(obj)
148
+ return obj if obj.nil?
149
+
150
+ resolve_inheritance(obj) || obj
151
+ end
152
+
153
+ def prepare(parent)
154
+ @inheritance_prepared = true
155
+ @parent = parent
156
+ @defs = specialize_defs(@obj[:defs], self)
157
+ @sdefs = specialize_defs(@obj[:sdefs], self)
158
+ @attributes = (@obj[:attributes] || {}).values.map do |v|
159
+ @provider.prepare_attr(v)
160
+ end
161
+
162
+ @root = parent_of(parent)
163
+ @path = make_path
164
+
165
+ @classes = @obj[:classes].map { SpecializedObject.specialize(_1, self, @provider) }
166
+ @modules = @obj[:modules].map { SpecializedObject.specialize(_1, self, @provider) }
167
+ end
168
+
169
+ def named_as(n) = ->(o) { o.name == n }
170
+ end
171
+ end
172
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Renderer
4
+ class Defs
5
+ class SpecializedProjection < Array
6
+ def initialize(provider)
7
+ super()
8
+ @provider = provider
9
+ replace((provider.modules + provider.classes)
10
+ .map { provider.specialize_object _1 }
11
+ .each(&:prepare_inheritance))
12
+ end
13
+
14
+ def find_path(path)
15
+ path = @provider.path_of(path) unless path.is_a? Array
16
+ p = path.dup
17
+ obj = find { _1.name == p.first }
18
+ p.shift
19
+ return unless obj
20
+
21
+ until p.empty?
22
+ obj = obj.resolve(p.first)
23
+ p.shift
24
+ return unless obj
25
+ end
26
+
27
+ obj
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,180 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "defs/specialized_object"
4
+ require_relative "defs/specialized_projection"
5
+
6
+ class Renderer
7
+ class Defs
8
+ class << self
9
+ attr_reader :singleton
10
+ end
11
+
12
+ class << self
13
+ attr_writer :singleton
14
+ end
15
+
16
+ def initialize(base, metadata)
17
+ @data = JSON.parse(File.read("#{base}/data.json"), symbolize_names: true)
18
+ @meta = metadata
19
+ Defs.singleton = self
20
+ end
21
+
22
+ attr_reader :meta
23
+
24
+ def classes = @classes ||= make_paths(@data[:classes])
25
+ def modules = @modules ||= make_paths(@data[:modules])
26
+
27
+ def make_paths(obj)
28
+ return [] unless obj
29
+
30
+ obj.each { set_parent(_1, nil) }
31
+ end
32
+
33
+ def set_parent(obj, parent)
34
+ obj[:parent] = parent
35
+ obj[:classes].each { set_parent(_1, obj) }
36
+ obj[:modules].each { set_parent(_1, obj) }
37
+ end
38
+
39
+ def document_outline
40
+ (classes + modules).map { outline(_1) }
41
+ end
42
+
43
+ def outline(object, level = 0)
44
+ defs = object[:defs].values.map { map_method(_1) }.sort_by { _1[:name] }
45
+ sdefs = object[:sdefs].values.map { map_method(_1) }.sort_by { _1[:name] }
46
+ attributes = object[:attributes]&.values&.map { prepare_attr(_1) }&.sort_by { _1[:name] }
47
+
48
+ {
49
+ level:,
50
+ name: object[:name],
51
+ type: object[:type],
52
+ classes: object[:classes].map { outline(_1, level + 1) },
53
+ modules: object[:modules].map { outline(_1, level + 1) },
54
+ defs: defs + sdefs,
55
+ attributes:
56
+ }
57
+ end
58
+
59
+ def map_method(met)
60
+ decoration = if met[:source] == "inheritance"
61
+ "inherited"
62
+ elsif met[:overriding]
63
+ "override"
64
+ end
65
+
66
+ obj = find_source(met)
67
+ type = [].tap do |arr|
68
+ arr << "Class" if obj[:type] == "defs"
69
+ arr << "Method"
70
+ end.join(" ")
71
+
72
+ {
73
+ name: obj[:name],
74
+ visibility: obj[:visibility],
75
+ args: obj[:args],
76
+ type:,
77
+ short_type: obj[:type],
78
+ doc: obj[:doc],
79
+ decoration:
80
+ }
81
+ end
82
+
83
+ def find_source(met)
84
+ obj = met
85
+ obj = obj[:definition] while obj[:source] != "source"
86
+ obj[:definition]
87
+ end
88
+
89
+ def prepare_attr(met)
90
+ decoration = if met[:source] == "inheritance"
91
+ "inherited"
92
+ elsif met[:overriding]
93
+ "override"
94
+ end
95
+ origin = met[:source]
96
+ att = find_source(met)
97
+ visibility = if att[:reader_visibility] == "public" && att[:writer_visibility] == "public"
98
+ "read/write"
99
+ elsif att[:reader_visibility] == "public" && att[:writer_visibility] != "public"
100
+ "read-only"
101
+ else
102
+ "write-only"
103
+ end
104
+
105
+ {
106
+ name: att[:name],
107
+ type: "Attribute",
108
+ visibility:,
109
+ decoration:,
110
+ origin:,
111
+ doc: att[:doc]
112
+ }
113
+ end
114
+
115
+ def path_of(item)
116
+ p = []
117
+ parent = item
118
+ until parent.nil?
119
+ p << parent[:name]
120
+ parent = parent[:parent]
121
+ end
122
+ p.reverse
123
+ end
124
+
125
+ def definitions_of(item)
126
+ item[:defined_by]&.map do |d|
127
+ path_components = d[:filename].split("/")
128
+ {
129
+ name: path_components.last,
130
+ href: git_url(d)
131
+ }
132
+ end
133
+ end
134
+
135
+ def git_url(definition)
136
+ "#{@meta.git_url}/blob/#{@meta.git_tip}#{definition[:filename].gsub(@meta.git_root,
137
+ "")}#L#{definition[:start_at]}"
138
+ end
139
+
140
+ def clean_file_path(definition) = definition[:filename].gsub(meta.git_root, "")
141
+
142
+ def specialized_projection
143
+ @specialized_projection ||= SpecializedProjection.new(self)
144
+ end
145
+
146
+ def specialize_data
147
+ @specialize_data ||= (data[:modules] + data[:classes])
148
+ .map { specialize_object _1 }
149
+ .each(&:prepare_inheritance)
150
+ end
151
+
152
+ def specialize_object(obj, parent = nil)
153
+ SpecializedObject.specialize(obj, parent, self)
154
+ end
155
+
156
+ def by_name(n) = ->(o) { o[:name] == n }
157
+
158
+ def doc_for(path)
159
+ path = path_of(path) if path.is_a? Hash
160
+ path = path.dup
161
+ named = by_name(path.shift)
162
+ root = classes.find(&named) || modules.find(&named)
163
+
164
+ return unless root
165
+
166
+ find_recursive(path, root)
167
+ end
168
+
169
+ def find_recursive(path, root)
170
+ return root if path.empty?
171
+
172
+ named = by_name(path.shift)
173
+ next_item = root.classes.find(&named) || root.modules.find(&named)
174
+
175
+ return nil unless next_item
176
+
177
+ find_recursive(path, next_item)
178
+ end
179
+ end
180
+ end