platformos-check 0.0.3 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +13 -0
  3. data/data/platformos_liquid/documentation/filters.json +1 -1
  4. data/data/platformos_liquid/documentation/objects.json +1 -1
  5. data/data/platformos_liquid/documentation/tags.json +1 -1
  6. data/lib/platformos_check/app.rb +21 -18
  7. data/lib/platformos_check/checks/invalid_args.rb +7 -39
  8. data/lib/platformos_check/cli.rb +3 -4
  9. data/lib/platformos_check/file_system_storage.rb +3 -7
  10. data/lib/platformos_check/graphql_file.rb +25 -1
  11. data/lib/platformos_check/in_memory_storage.rb +2 -24
  12. data/lib/platformos_check/language_server/completion_providers/background_partial_completion_provider.rb +19 -0
  13. data/lib/platformos_check/language_server/completion_providers/function_partial_completion_provider.rb +19 -0
  14. data/lib/platformos_check/language_server/completion_providers/graphql_partial_completion_provider.rb +19 -0
  15. data/lib/platformos_check/language_server/completion_providers/include_partial_completion_provider.rb +19 -0
  16. data/lib/platformos_check/language_server/completion_providers/object_completion_provider.rb +1 -0
  17. data/lib/platformos_check/language_server/completion_providers/render_partial_completion_provider.rb +19 -0
  18. data/lib/platformos_check/language_server/completion_providers/tag_completion_provider.rb +10 -15
  19. data/lib/platformos_check/language_server/constants.rb +1 -1
  20. data/lib/platformos_check/language_server/diagnostics_engine.rb +2 -3
  21. data/lib/platformos_check/language_server/document_link_engine.rb +1 -3
  22. data/lib/platformos_check/language_server/handler.rb +1 -1
  23. data/lib/platformos_check/language_server/hover_providers/filter_hover_provider.rb +9 -15
  24. data/lib/platformos_check/language_server/hover_providers/tag_hover_provider.rb +36 -0
  25. data/lib/platformos_check/language_server/partial_completion_provider.rb +71 -0
  26. data/lib/platformos_check/language_server.rb +1 -0
  27. data/lib/platformos_check/liquid_visitor.rb +1 -1
  28. data/lib/platformos_check/platformos_liquid/source_index/filter_entry.rb +35 -2
  29. data/lib/platformos_check/platformos_liquid/source_index/object_entry.rb +4 -0
  30. data/lib/platformos_check/platformos_liquid/source_index/parameter_entry.rb +4 -0
  31. data/lib/platformos_check/platformos_liquid/source_index/tag_entry.rb +28 -0
  32. data/lib/platformos_check/storage.rb +2 -2
  33. data/lib/platformos_check/version.rb +1 -1
  34. metadata +9 -3
  35. data/lib/platformos_check/language_server/completion_providers/render_snippet_completion_provider.rb +0 -50
@@ -31,10 +31,13 @@ module PlatformosCheck
31
31
 
32
32
  file(relative_path).dirname.mkpath unless file(relative_path).dirname.directory?
33
33
  file(relative_path).write(content, mode: 'w+b', encoding: 'UTF-8')
34
+ @platformos_app&.update([relative_path])
34
35
  end
35
36
 
36
37
  def remove(relative_path)
37
38
  file(relative_path).delete
39
+
40
+ @platformos_app&.update([relative_path], remove: true)
38
41
  reset_memoizers
39
42
  end
40
43
 
@@ -60,12 +63,6 @@ module PlatformosCheck
60
63
  .map { |path| path.relative_path_from(@root).to_s }
61
64
  end
62
65
 
63
- def directories
64
- @directories ||= glob('**/')
65
- .select { |f| File.directory?(f) }
66
- .map { |f| f.relative_path_from(@root).to_s }
67
- end
68
-
69
66
  private
70
67
 
71
68
  def file_exists?(relative_path)
@@ -74,7 +71,6 @@ module PlatformosCheck
74
71
 
75
72
  def reset_memoizers
76
73
  @file_array = nil
77
- @directories = nil
78
74
  end
79
75
 
80
76
  def glob(pattern)
@@ -13,6 +13,12 @@ module PlatformosCheck
13
13
  @storage.write(@relative_path, content.gsub("\n", @eol))
14
14
  @source = content
15
15
  @rewriter = nil
16
+ @ast = nil
17
+ @variables = nil
18
+ @definition = nil
19
+ @parse = nil
20
+ @required_arguments = nil
21
+ @defined_arguments = nil
16
22
  end
17
23
 
18
24
  def rewriter
@@ -48,7 +54,7 @@ module PlatformosCheck
48
54
  end
49
55
 
50
56
  def warnings
51
- @ast.warnings
57
+ parse.warnings
52
58
  end
53
59
 
54
60
  def root
@@ -59,8 +65,26 @@ module PlatformosCheck
59
65
  Struct.new(:warnings, :root)
60
66
  end
61
67
 
68
+ def required_arguments
69
+ @required_arguments ||= variables.each_with_object([]) do |v, vars|
70
+ vars << v.name if v.type.is_a?(GraphQL::Language::Nodes::NonNullType)
71
+ end
72
+ end
73
+
74
+ def defined_arguments
75
+ @defined_arguments ||= variables.map(&:name)
76
+ end
77
+
62
78
  private
63
79
 
80
+ def variables
81
+ @variables ||= definition&.variables || []
82
+ end
83
+
84
+ def definition
85
+ @definition ||= parse.definitions.detect { |d| d.is_a?(GraphQL::Language::Nodes::OperationDefinition) }
86
+ end
87
+
64
88
  def bounded(lower, x, upper)
65
89
  [lower, [x, upper].min].max
66
90
  end
@@ -22,47 +22,25 @@ module PlatformosCheck
22
22
  end
23
23
 
24
24
  def write(relative_path, content)
25
+ @platformos_app&.update([relative_path])
25
26
  @files[relative_path] = content
26
27
  end
27
28
 
28
29
  def remove(relative_path)
30
+ @platformos_app&.update([relative_path], remove: true)
29
31
  @files.delete(relative_path)
30
32
  end
31
33
 
32
34
  def mkdir(relative_path)
33
35
  @files[relative_path] = nil
34
- reset_memoizers
35
36
  end
36
37
 
37
- # TODO: Fix corrector
38
- # def rename(old_path, new_path)
39
- # old_path += '/' if old_path[-1] != '/'
40
- # new_path += '/' if new_path[-1] != '/'
41
- # @files.transform_keys! { |k| k.sub(/\A#{old_path}/, new_path) }
42
- #
43
- # reset_memoizers
44
- # end
45
-
46
38
  def files
47
39
  @files.keys
48
40
  end
49
41
 
50
- def directories
51
- @directories ||= @files
52
- .keys
53
- .flat_map { |relative_path| Pathname.new(relative_path).ascend.to_a }
54
- .map(&:to_s)
55
- .uniq
56
- end
57
-
58
42
  def relative_path(absolute_path)
59
43
  Pathname.new(absolute_path).relative_path_from(@root).to_s
60
44
  end
61
-
62
- private
63
-
64
- def reset_memoizers
65
- @directories = nil
66
- end
67
45
  end
68
46
  end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PlatformosCheck
4
+ module LanguageServer
5
+ class BackgroundPartialCompletionProvider < CompletionProvider
6
+ include PartialCompletionProvider
7
+
8
+ private
9
+
10
+ def files
11
+ @storage.platformos_app.partials
12
+ end
13
+
14
+ def regexp
15
+ PARTIAL_BACKGROUND
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PlatformosCheck
4
+ module LanguageServer
5
+ class FunctionPartialCompletionProvider < CompletionProvider
6
+ include PartialCompletionProvider
7
+
8
+ private
9
+
10
+ def files
11
+ @storage.platformos_app.partials
12
+ end
13
+
14
+ def regexp
15
+ PARTIAL_FUNCTION
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PlatformosCheck
4
+ module LanguageServer
5
+ class GraphqlPartialCompletionProvider < CompletionProvider
6
+ include PartialCompletionProvider
7
+
8
+ private
9
+
10
+ def files
11
+ @storage.platformos_app.graphqls
12
+ end
13
+
14
+ def regexp
15
+ PARTIAL_GRAPHQL
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PlatformosCheck
4
+ module LanguageServer
5
+ class IncludePartialCompletionProvider < CompletionProvider
6
+ include PartialCompletionProvider
7
+
8
+ private
9
+
10
+ def files
11
+ @storage.platformos_app.partials
12
+ end
13
+
14
+ def regexp
15
+ PARTIAL_INCLUDE
16
+ end
17
+ end
18
+ end
19
+ end
@@ -13,6 +13,7 @@ module PlatformosCheck
13
13
 
14
14
  PlatformosLiquid::SourceIndex
15
15
  .objects
16
+ .select(&:global?)
16
17
  .select { |object| object.name.start_with?(partial(variable_lookup)) }
17
18
  .map { |object| object_to_completion(object) }
18
19
  end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PlatformosCheck
4
+ module LanguageServer
5
+ class RenderPartialCompletionProvider < CompletionProvider
6
+ include PartialCompletionProvider
7
+
8
+ private
9
+
10
+ def files
11
+ @storage.platformos_app.partials
12
+ end
13
+
14
+ def regexp
15
+ PARTIAL_RENDER
16
+ end
17
+ end
18
+ end
19
+ end
@@ -5,34 +5,29 @@ module PlatformosCheck
5
5
  class TagCompletionProvider < CompletionProvider
6
6
  def completions(context)
7
7
  content = context.content
8
- cursor = context.cursor
9
8
 
10
9
  return [] if content.nil?
11
- return [] unless can_complete?(content, cursor)
10
+ return [] unless can_complete?(context)
12
11
 
13
- partial = first_word(content) || ''
14
- labels = PlatformosLiquid::Tag.labels
15
- labels += PlatformosLiquid::Tag.end_labels
16
- labels
17
- .select { |w| w.start_with?(partial) }
18
- .map { |tag| tag_to_completion(tag) }
12
+ partial = first_word(context.buffer.lines[context.line]) || ''
13
+ PlatformosLiquid::SourceIndex.tags.select { |tag| tag.name.start_with?(partial) }
14
+ .map { |tag| tag_to_completion(tag) }
19
15
  end
20
16
 
21
- def can_complete?(content, cursor)
22
- content.start_with?(Liquid::TagStart) && (
23
- cursor_on_first_word?(content, cursor) ||
24
- cursor_on_start_content?(content, cursor, Liquid::TagStart)
25
- )
17
+ def can_complete?(context)
18
+ context.content.start_with?(Liquid::TagStart) && (cursor_on_first_word?(context.buffer.lines[context.line], context.col) || cursor_on_start_content?(context.buffer.lines[context.line], context.col, Liquid::TagStart))
26
19
  end
27
20
 
28
21
  private
29
22
 
30
23
  def tag_to_completion(tag)
31
- content = PlatformosLiquid::Documentation.tag_doc(tag)
24
+ content = PlatformosLiquid::Documentation.tag_doc(tag.name)
32
25
 
33
26
  {
34
- label: tag,
27
+ contents: content,
28
+ label: tag.name,
35
29
  kind: CompletionItemKinds::KEYWORD,
30
+ **format_hash(tag),
36
31
  **doc_hash(content)
37
32
  }
38
33
  end
@@ -15,7 +15,7 @@ module PlatformosCheck
15
15
 
16
16
  def self.partial_tag_with_result(tag)
17
17
  /
18
- \{%-?\s*#{tag}\s+(?<var>[\w]+)+\s*=\s*'(?<partial>[^']*)'|
18
+ \{%-?\s*#{tag}\s+(?<var>[\w]+)\s*=\s*'(?<partial>[^']*)'|
19
19
  \{%-?\s*#{tag}\s+(?<var>[\w]+)\s*=\s*"(?<partial>[^"]*)"|
20
20
 
21
21
  # in liquid tags the whole line is white space until the tag
@@ -21,13 +21,12 @@ module PlatformosCheck
21
21
  def analyze_and_send_offenses(absolute_path_or_paths, config, force: false, only_single_file: false)
22
22
  return unless @diagnostics_lock.try_lock
23
23
 
24
- platformos_app = PlatformosCheck::App.new(storage)
25
- analyzer = PlatformosCheck::Analyzer.new(platformos_app, config.enabled_checks)
24
+ analyzer = PlatformosCheck::Analyzer.new(storage.platformos_app, config.enabled_checks)
26
25
 
27
26
  if !only_single_file && (@diagnostics_manager.first_run? || force)
28
27
  run_full_platformos_check(analyzer)
29
28
  else
30
- run_partial_platformos_check(absolute_path_or_paths, platformos_app, analyzer, only_single_file)
29
+ run_partial_platformos_check(absolute_path_or_paths, storage.platformos_app, analyzer, only_single_file)
31
30
  end
32
31
 
33
32
  @diagnostics_lock.unlock
@@ -12,10 +12,8 @@ module PlatformosCheck
12
12
  buffer = @storage.read(relative_path)
13
13
  return [] unless buffer
14
14
 
15
- platformos_app = PlatformosCheck::App.new(@storage)
16
-
17
15
  @providers.flat_map do |p|
18
- p.document_links(buffer, platformos_app)
16
+ p.document_links(buffer, @storage.platformos_app)
19
17
  end
20
18
  end
21
19
  end
@@ -24,7 +24,7 @@ module PlatformosCheck
24
24
 
25
25
  CAPABILITIES = {
26
26
  completionProvider: {
27
- triggerCharacters: ['.', '{{ ', '{% '],
27
+ triggerCharacters: ['.', '{{ ', '{% ', '/'],
28
28
  context: true
29
29
  },
30
30
  codeActionProvider: {
@@ -18,7 +18,10 @@ module PlatformosCheck
18
18
  variable_lookup = VariableLookupFinder.lookup(context)
19
19
  denied_filters = denied_filters_for(variable_lookup)
20
20
  available_filters_for(determine_input_type(variable_lookup))
21
- .select { |filter| (filter.name == partial(content, cursor)) && denied_filters.none?(filter.name) }
21
+ .select do |filter|
22
+ partial_name = partial(content, cursor)
23
+ (filter.name == partial_name || filter.aliases.any? { |a| a == partial_name }) && denied_filters.none?(filter.name)
24
+ end
22
25
  .map { |filter| filter_to_completion(filter) }
23
26
  .first
24
27
  end
@@ -88,23 +91,14 @@ module PlatformosCheck
88
91
  partial_match[1]
89
92
  end
90
93
 
91
- def param_to_doc(param)
92
- "#{param&.name || 'object'}:#{param&.return_type&.downcase || 'untyped'}"
93
- end
94
-
95
94
  def filter_to_completion(filter)
96
95
  content = PlatformosLiquid::Documentation.render_doc(filter)
97
- first_param, *other_params = filter.parameters
98
- other_params = other_params.map { |param| param_to_doc(param) }
99
- other_params = other_params.any? ? ": #{other_params.join(', ')}" : ""
100
- content += " \n\n#{param_to_doc(first_param)} | #{filter.name}#{other_params} => #{filter.return_type}"
101
-
102
96
  {
103
- contents: content
104
- # label: filter.name,
105
- # kind: CompletionItemKinds::FUNCTION,
106
- # **format_hash(filter),
107
- # **doc_hash(content)
97
+ contents: content,
98
+ label: filter.name,
99
+ kind: CompletionItemKinds::FUNCTION,
100
+ **format_hash(filter),
101
+ **doc_hash(content)
108
102
  }
109
103
  end
110
104
  end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PlatformosCheck
4
+ module LanguageServer
5
+ class TagHoverProvider < HoverProvider
6
+ def completions(context)
7
+ content = context.content
8
+
9
+ return [] if content.nil?
10
+ return [] unless can_complete?(context)
11
+
12
+ partial = first_word(context.buffer.lines[context.line]) || ''
13
+ PlatformosLiquid::SourceIndex.tags.select { |tag| tag.name.start_with?(partial) }
14
+ .map { |tag| tag_to_completion(tag) }.first
15
+ end
16
+
17
+ def can_complete?(context)
18
+ context.content.start_with?(Liquid::TagStart) && (cursor_on_first_word?(context.buffer.lines[context.line], context.col) || cursor_on_start_content?(context.buffer.lines[context.line], context.col, Liquid::TagStart))
19
+ end
20
+
21
+ private
22
+
23
+ def tag_to_completion(tag)
24
+ content = PlatformosLiquid::Documentation.tag_doc(tag.name)
25
+
26
+ {
27
+ contents: content,
28
+ label: tag.name,
29
+ kind: CompletionItemKinds::KEYWORD,
30
+ **format_hash(tag),
31
+ **doc_hash(content)
32
+ }
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PlatformosCheck
4
+ module LanguageServer
5
+ module PartialCompletionProvider
6
+ def completions(context)
7
+ content = context.buffer.lines[context.line]
8
+ cursor = context.col
9
+ @file_name = nil
10
+
11
+ return [] if content.nil?
12
+ return [] unless cursor_on_quoted_argument?(content, cursor)
13
+
14
+ files
15
+ .select { |x| x.name.start_with?(@file_name) }
16
+ .map { |x| file_to_completion(x, context) }
17
+ end
18
+
19
+ private
20
+
21
+ def cursor_on_quoted_argument?(content, cursor)
22
+ @match = content.match(regexp)
23
+ return false if @match.nil?
24
+
25
+ return false unless @match.begin(:partial) <= cursor && cursor <= @match.end(:partial)
26
+
27
+ @file_name = @match[:partial][0, cursor - @match.begin(:partial)]
28
+ true
29
+ end
30
+
31
+ def files
32
+ raise NotImplementedError
33
+ end
34
+
35
+ def regexp
36
+ raise NotImplementedError
37
+ end
38
+
39
+ def file_to_completion(file, context)
40
+ {
41
+ label: file.name,
42
+ kind: CompletionItemKinds::TEXT,
43
+ detail: file.source,
44
+ textEdit: {
45
+ newText: file.name,
46
+ insert: {
47
+ start: {
48
+ line: context.line,
49
+ character: @match.begin(:partial)
50
+ },
51
+ end: {
52
+ line: context.line,
53
+ character: @match.end(:partial)
54
+ }
55
+ },
56
+ replace: {
57
+ start: {
58
+ line: context.line,
59
+ character: @match.begin(:partial)
60
+ },
61
+ end: {
62
+ line: context.line,
63
+ character: @match.end(:partial)
64
+ }
65
+ }
66
+ }
67
+ }
68
+ end
69
+ end
70
+ end
71
+ end
@@ -32,6 +32,7 @@ require_relative "language_server/completion_context"
32
32
  require_relative "language_server/completion_helper"
33
33
  require_relative "language_server/completion_provider"
34
34
  require_relative "language_server/completion_engine"
35
+ require_relative "language_server/partial_completion_provider"
35
36
  Dir[__dir__ + "/language_server/completion_providers/*.rb"].each do |file|
36
37
  require file
37
38
  end
@@ -12,7 +12,7 @@ module PlatformosCheck
12
12
  def visit_liquid_file(liquid_file)
13
13
  visit(LiquidNode.new(liquid_file.root, nil, liquid_file))
14
14
  rescue Liquid::Error => e
15
- e.template_name = liquid_file.name
15
+ e.template_name = liquid_file.relative_path
16
16
  call_checks(:on_error, e)
17
17
  end
18
18
 
@@ -1,12 +1,45 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'cgi'
4
+
3
5
  module PlatformosCheck
4
6
  module PlatformosLiquid
5
7
  class SourceIndex
6
8
  class FilterEntry < BaseEntry
7
9
  def parameters
8
- (hash['parameters'] || [])
9
- .map { |hash| ParameterEntry.new(hash) }
10
+ @parameters ||= (hash['parameters'] || []).map { |hash| ParameterEntry.new(hash) }
11
+ end
12
+
13
+ def summary
14
+ hash['summary']&.strip == 'returns' ? nil : hash['summary']
15
+ end
16
+
17
+ def description
18
+ @descritpion = begin
19
+ desc = hash['description']&.strip || ''
20
+ desc = '' if desc == 'returns'
21
+ if parameters.any?
22
+ desc += "Parameters:"
23
+ parameters.each { |p| desc += "\n- #{p.full_summary}" }
24
+ end
25
+ if hash['return_type']&.any?
26
+ rt = hash['return_type'].first
27
+ rt['description'] = nil if rt['description']&.strip == ''
28
+ desc += "\n\nReturns:"
29
+ desc += "\n- #{[rt['type'], rt['description']].compact.join(': ')}\n"
30
+ end
31
+ if hash['examples']
32
+ desc += "\n\n---\n\n"
33
+ hash['examples'].each_with_index do |e, i|
34
+ example = e['raw_liquid'].gsub(/[\n]+/, "\n").strip.split('=>')
35
+ input = example[0].strip
36
+ output = example[1]&.strip
37
+ desc += "\n - Example #{i}:\n\n```liquid\n#{input}\n```"
38
+ desc += "\n##\nOutput: #{output}" if output
39
+ end
40
+ end
41
+ end
42
+ desc
10
43
  end
11
44
 
12
45
  def aliases
@@ -14,6 +14,10 @@ module PlatformosCheck
14
14
  def platformos_documentation_url
15
15
  "#{PLATFORMOS_DOCUMENTATION_URL}/developer-guide/variables/context-variable##{hash['name']}"
16
16
  end
17
+
18
+ def global?
19
+ hash.dig('access', 'global')
20
+ end
17
21
  end
18
22
  end
19
23
  end
@@ -12,6 +12,10 @@ module PlatformosCheck
12
12
  "#{PLATFORMOS_DOCUMENTATION_URL}/api-reference/liquid/filters/#{hash['name']}"
13
13
  end
14
14
 
15
+ def full_summary
16
+ "#{hash['name']} - #{hash['description']}"
17
+ end
18
+
15
19
  private
16
20
 
17
21
  def return_type_hash
@@ -15,6 +15,34 @@ module PlatformosCheck
15
15
  }
16
16
  end
17
17
 
18
+ def description
19
+ @descritpion = begin
20
+ desc = hash['description']&.strip || ''
21
+ desc = '' if desc == 'returns'
22
+ if parameters.any?
23
+ desc += "\n\n---\n\nParameters:"
24
+ parameters.each { |p| desc += "\n- #{p.full_summary}" }
25
+ end
26
+ if hash['return_type']&.any?
27
+ rt = hash['return_type'].first
28
+ rt['description'] = nil if rt['description']&.strip == ''
29
+ desc += "\n\nReturns:"
30
+ desc += "\n- #{[rt['type'], rt['description']].compact.join(': ')}\n"
31
+ end
32
+ if hash['examples']
33
+ desc += "\n\n---\n\n"
34
+ hash['examples'].each_with_index do |e, i|
35
+ example = e['raw_liquid'].gsub(/[\n]+/, "\n").strip.split('=>')
36
+ input = example[0]&.strip
37
+ output = example[1]&.strip
38
+ desc += "\n - Example #{i}:\n\n```liquid\n#{input}\n```"
39
+ desc += "\n##\nOutput: #{output}" if output
40
+ end
41
+ end
42
+ end
43
+ desc
44
+ end
45
+
18
46
  def platformos_documentation_url
19
47
  "#{PLATFORMOS_DOCUMENTATION_URL}/api-reference/liquid/tags/#{hash['name']}"
20
48
  end
@@ -18,8 +18,8 @@ module PlatformosCheck
18
18
  raise NotImplementedError
19
19
  end
20
20
 
21
- def directories
22
- raise NotImplementedError
21
+ def platformos_app
22
+ @platformos_app ||= PlatformosCheck::App.new(self)
23
23
  end
24
24
 
25
25
  def versioned?
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PlatformosCheck
4
- VERSION = "0.0.3"
4
+ VERSION = "0.2.0"
5
5
  end