lkml 0.1.0 → 0.2.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.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +28 -0
- data/.gitignore +57 -0
- data/.rubocop.yml +25 -0
- data/.ruby-version +1 -0
- data/CONTRIBUTING.md +56 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +76 -0
- data/LICENSE.md +1 -2
- data/README.md +81 -7
- data/Rakefile +12 -0
- data/bin/lkml +6 -0
- data/lib/lkml/cli.rb +44 -0
- data/lib/lkml/keys.rb +64 -114
- data/lib/lkml/lexer.rb +45 -68
- data/lib/lkml/parser.rb +296 -191
- data/lib/lkml/simple.rb +244 -238
- data/lib/lkml/tokens.rb +44 -129
- data/lib/lkml/tree.rb +362 -236
- data/lib/lkml/utils.rb +32 -0
- data/lib/lkml/version.rb +1 -1
- data/lib/lkml/visitors.rb +69 -64
- data/lib/lkml.rb +49 -14
- data/lkml.gemspec +35 -0
- data/script/benchmark.rb +36 -0
- data/script/download_lookml.rb +70 -0
- metadata +88 -13
data/lib/lkml/utils.rb
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lkml
|
|
4
|
+
module Utils
|
|
5
|
+
module_function
|
|
6
|
+
|
|
7
|
+
def strip(str)
|
|
8
|
+
start = 0
|
|
9
|
+
end_idx = -1
|
|
10
|
+
nchars = str.length
|
|
11
|
+
|
|
12
|
+
start += 1 while start < nchars && str[start].match?(/\s/)
|
|
13
|
+
|
|
14
|
+
end_idx -= 1 while start != nchars && str[end_idx].match?(/\s/)
|
|
15
|
+
|
|
16
|
+
lspace = start.zero? ? "" : str[0, start]
|
|
17
|
+
rspace = if end_idx == -1
|
|
18
|
+
""
|
|
19
|
+
else
|
|
20
|
+
str[(end_idx + 1)..] || ""
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
core = if end_idx == -1
|
|
24
|
+
str[start..] || ""
|
|
25
|
+
else
|
|
26
|
+
str[start..end_idx] || ""
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
[lspace, core, rspace]
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
data/lib/lkml/version.rb
CHANGED
data/lib/lkml/visitors.rb
CHANGED
|
@@ -1,91 +1,96 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
require_relative 'tree'
|
|
3
|
+
require_relative "tree"
|
|
6
4
|
|
|
7
5
|
module Lkml
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
node
|
|
6
|
+
module Visitors
|
|
7
|
+
class BasicVisitor
|
|
8
|
+
include Tree::Visitor
|
|
9
|
+
|
|
10
|
+
def _visit(node)
|
|
11
|
+
case node
|
|
12
|
+
when Tree::SyntaxToken
|
|
13
|
+
nil
|
|
14
|
+
else
|
|
15
|
+
ch = node.respond_to?(:children) ? node.children : nil
|
|
16
|
+
ch&.each { |child| child.accept(self) }
|
|
17
|
+
end
|
|
14
18
|
end
|
|
15
|
-
end
|
|
16
19
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
+
def visit(document)
|
|
21
|
+
_visit(document)
|
|
22
|
+
end
|
|
20
23
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
+
def visit_container(node)
|
|
25
|
+
_visit(node)
|
|
26
|
+
end
|
|
24
27
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
+
def visit_block(node)
|
|
29
|
+
_visit(node)
|
|
30
|
+
end
|
|
28
31
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
+
def visit_list(node)
|
|
33
|
+
_visit(node)
|
|
34
|
+
end
|
|
32
35
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
+
def visit_pair(node)
|
|
37
|
+
_visit(node)
|
|
38
|
+
end
|
|
36
39
|
|
|
37
|
-
|
|
38
|
-
|
|
40
|
+
def visit_token(token)
|
|
41
|
+
_visit(token)
|
|
42
|
+
end
|
|
39
43
|
end
|
|
40
|
-
end
|
|
41
44
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
+
class LookMlVisitor < BasicVisitor
|
|
46
|
+
def _visit(node)
|
|
47
|
+
node.to_s
|
|
48
|
+
end
|
|
45
49
|
end
|
|
46
|
-
end
|
|
47
50
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
node.
|
|
53
|
-
|
|
54
|
-
|
|
51
|
+
class BasicTransformer
|
|
52
|
+
include Tree::Visitor
|
|
53
|
+
|
|
54
|
+
def _visit_items(node)
|
|
55
|
+
ch = node.children
|
|
56
|
+
return node unless ch && !ch.empty?
|
|
57
|
+
|
|
58
|
+
new_children = ch.map { |child| child.accept(self) }
|
|
59
|
+
case node
|
|
60
|
+
when Tree::ContainerNode, Tree::ListNode
|
|
61
|
+
node.with(items: new_children.freeze)
|
|
62
|
+
else
|
|
63
|
+
node
|
|
64
|
+
end
|
|
55
65
|
end
|
|
56
|
-
end
|
|
57
66
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
else
|
|
63
|
-
node
|
|
67
|
+
def visit(document)
|
|
68
|
+
return document unless document.container
|
|
69
|
+
|
|
70
|
+
document.with(container: document.container.accept(self))
|
|
64
71
|
end
|
|
65
|
-
end
|
|
66
72
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
73
|
+
def visit_container(node)
|
|
74
|
+
_visit_items(node)
|
|
75
|
+
end
|
|
70
76
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
77
|
+
def visit_list(node)
|
|
78
|
+
_visit_items(node)
|
|
79
|
+
end
|
|
74
80
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
end
|
|
81
|
+
def visit_block(node)
|
|
82
|
+
return node unless node.container
|
|
78
83
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
end
|
|
84
|
+
node.with(container: node.container.accept(self))
|
|
85
|
+
end
|
|
82
86
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
87
|
+
def visit_pair(node)
|
|
88
|
+
node
|
|
89
|
+
end
|
|
86
90
|
|
|
87
|
-
|
|
88
|
-
|
|
91
|
+
def visit_token(token)
|
|
92
|
+
token
|
|
93
|
+
end
|
|
89
94
|
end
|
|
90
95
|
end
|
|
91
96
|
end
|
data/lib/lkml.rb
CHANGED
|
@@ -1,26 +1,61 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative
|
|
4
|
-
require_relative
|
|
5
|
-
require_relative
|
|
6
|
-
require_relative
|
|
7
|
-
require_relative
|
|
8
|
-
require_relative
|
|
9
|
-
require_relative
|
|
3
|
+
require_relative "lkml/version"
|
|
4
|
+
require_relative "lkml/tokens"
|
|
5
|
+
require_relative "lkml/keys"
|
|
6
|
+
require_relative "lkml/utils"
|
|
7
|
+
require_relative "lkml/tree"
|
|
8
|
+
require_relative "lkml/visitors"
|
|
9
|
+
require_relative "lkml/lexer"
|
|
10
|
+
require_relative "lkml/parser"
|
|
11
|
+
require "stringio"
|
|
12
|
+
|
|
13
|
+
require_relative "lkml/simple"
|
|
10
14
|
|
|
11
15
|
module Lkml
|
|
12
|
-
|
|
16
|
+
module_function
|
|
17
|
+
|
|
18
|
+
def parse(text)
|
|
13
19
|
lexer = Lexer.new(text)
|
|
14
20
|
tokens = lexer.scan
|
|
15
21
|
Parser.new(tokens).parse
|
|
16
22
|
end
|
|
17
23
|
|
|
18
|
-
def
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
24
|
+
def load(stream)
|
|
25
|
+
text =
|
|
26
|
+
case stream
|
|
27
|
+
when String then stream
|
|
28
|
+
else
|
|
29
|
+
raise TypeError, "Input stream must be a string or file object." unless stream.respond_to?(:read)
|
|
30
|
+
|
|
31
|
+
stream.read
|
|
22
32
|
|
|
23
|
-
|
|
24
|
-
|
|
33
|
+
end
|
|
34
|
+
tree = parse(text)
|
|
35
|
+
Simple::DictVisitor.new.visit(tree)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def dump(obj, io: nil)
|
|
39
|
+
tree = Simple::DictParser.new.parse(obj)
|
|
40
|
+
result = tree.to_s
|
|
41
|
+
if io
|
|
42
|
+
io.write(result)
|
|
43
|
+
nil
|
|
44
|
+
else
|
|
45
|
+
result
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
alias generate dump
|
|
50
|
+
|
|
51
|
+
def dict_parser
|
|
52
|
+
Simple::DictParser
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def dict_visitor
|
|
56
|
+
Simple::DictVisitor
|
|
25
57
|
end
|
|
26
58
|
end
|
|
59
|
+
|
|
60
|
+
Lkml.const_set(:DictParser, Lkml::Simple::DictParser)
|
|
61
|
+
Lkml.const_set(:DictVisitor, Lkml::Simple::DictVisitor)
|
data/lkml.gemspec
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "lib/lkml/version"
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |spec|
|
|
6
|
+
spec.name = "lkml"
|
|
7
|
+
spec.version = Lkml::VERSION
|
|
8
|
+
spec.authors = ["Josh Temple", "Sylvain Utard", "Altertable"]
|
|
9
|
+
spec.email = ["contact@altertable.ai"]
|
|
10
|
+
|
|
11
|
+
spec.summary = "A speedy LookML parser and serializer implemented in pure Ruby."
|
|
12
|
+
spec.description = <<~DESC
|
|
13
|
+
Parse LookML strings into Ruby hashes and serialize Ruby hashes back to LookML.
|
|
14
|
+
Implementation uses the same algorithm as the original Python lkml package.
|
|
15
|
+
DESC
|
|
16
|
+
spec.homepage = "https://github.com/altertable-ai/lkml"
|
|
17
|
+
spec.license = "MIT"
|
|
18
|
+
spec.required_ruby_version = ">= 3.3.0"
|
|
19
|
+
|
|
20
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
|
21
|
+
spec.metadata["source_code_uri"] = spec.homepage
|
|
22
|
+
spec.metadata["rubygems_mfa_required"] = "true"
|
|
23
|
+
|
|
24
|
+
spec.files = Dir.chdir(__dir__) do
|
|
25
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|\.git)/}) }
|
|
26
|
+
end
|
|
27
|
+
spec.bindir = "bin"
|
|
28
|
+
spec.executables = ["lkml"]
|
|
29
|
+
spec.require_paths = ["lib"]
|
|
30
|
+
|
|
31
|
+
spec.add_development_dependency "logger"
|
|
32
|
+
spec.add_development_dependency "minitest", "~> 5.25"
|
|
33
|
+
spec.add_development_dependency "rake", "~> 13.2"
|
|
34
|
+
spec.add_development_dependency "rubocop", "~> 1.69"
|
|
35
|
+
end
|
data/script/benchmark.rb
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Rough CPU-time benchmark over bundled GitHub LookML samples (same idea as scripts/benchmark.py).
|
|
5
|
+
|
|
6
|
+
require_relative "../lib/lkml"
|
|
7
|
+
|
|
8
|
+
dir = File.expand_path("../test/resources/github", __dir__)
|
|
9
|
+
times = []
|
|
10
|
+
|
|
11
|
+
Dir.glob(File.join(dir, "*.lkml")).each do |path|
|
|
12
|
+
text = File.read(path, encoding: "UTF-8")
|
|
13
|
+
start = Process.clock_gettime(Process::CLOCK_PROCESS_CPUTIME_ID)
|
|
14
|
+
begin
|
|
15
|
+
Lkml.load(text)
|
|
16
|
+
rescue SyntaxError, KeyError
|
|
17
|
+
next
|
|
18
|
+
end
|
|
19
|
+
finish = Process.clock_gettime(Process::CLOCK_PROCESS_CPUTIME_ID)
|
|
20
|
+
times << (finish - start)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
if times.empty?
|
|
24
|
+
warn "No successful parses; nothing to measure."
|
|
25
|
+
exit 1
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
avg = times.sum / times.size
|
|
29
|
+
variance = times.sum { |t| (t - avg)**2 } / (times.size - 1)
|
|
30
|
+
stdev = Math.sqrt(variance)
|
|
31
|
+
|
|
32
|
+
printf(
|
|
33
|
+
"Average process execution time: %.1f +/- %.1f ms\n",
|
|
34
|
+
avg * 100,
|
|
35
|
+
stdev * 100
|
|
36
|
+
)
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Downloads sample LookML via GitHub Code Search API (same workflow as scripts/download_lookml.py).
|
|
5
|
+
# Requires GITHUB_USERNAME and GITHUB_PERSONAL_ACCESS_TOKEN in the environment.
|
|
6
|
+
# Writes files under test/resources/github/ (create the directory if needed).
|
|
7
|
+
|
|
8
|
+
require "base64"
|
|
9
|
+
require "fileutils"
|
|
10
|
+
require "json"
|
|
11
|
+
require "net/http"
|
|
12
|
+
require "uri"
|
|
13
|
+
|
|
14
|
+
def parse_next_link(link_header)
|
|
15
|
+
return nil if link_header.nil? || link_header.empty?
|
|
16
|
+
|
|
17
|
+
link_header.split(",").each do |segment|
|
|
18
|
+
next unless segment.include?('rel="next"')
|
|
19
|
+
|
|
20
|
+
return Regexp.last_match(1) if segment =~ /<([^>]+)>/
|
|
21
|
+
end
|
|
22
|
+
nil
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
username = ENV.fetch("GITHUB_USERNAME")
|
|
26
|
+
token = ENV.fetch("GITHUB_PERSONAL_ACCESS_TOKEN")
|
|
27
|
+
|
|
28
|
+
directory = File.expand_path("../test/resources/github", __dir__)
|
|
29
|
+
FileUtils.mkdir_p(directory)
|
|
30
|
+
|
|
31
|
+
start_url = "https://api.github.com/search/code?q=view+language:lookml"
|
|
32
|
+
next_url = nil
|
|
33
|
+
page = 1
|
|
34
|
+
|
|
35
|
+
loop do
|
|
36
|
+
uri = URI(next_url || start_url)
|
|
37
|
+
req = Net::HTTP::Get.new(uri)
|
|
38
|
+
req.basic_auth(username, token)
|
|
39
|
+
|
|
40
|
+
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |h| h.request(req) }
|
|
41
|
+
res.value
|
|
42
|
+
|
|
43
|
+
next_url = parse_next_link(res["Link"])
|
|
44
|
+
|
|
45
|
+
warn next_url.inspect
|
|
46
|
+
|
|
47
|
+
payload = JSON.parse(res.body)
|
|
48
|
+
urls = payload.fetch("items").map { |item| item.fetch("url") }
|
|
49
|
+
|
|
50
|
+
warn "Downloading all content from page #{page}"
|
|
51
|
+
urls.each do |url|
|
|
52
|
+
u = URI(url)
|
|
53
|
+
r = Net::HTTP::Get.new(u)
|
|
54
|
+
r.basic_auth(username, token)
|
|
55
|
+
file_res = Net::HTTP.start(u.host, u.port, use_ssl: true) { |h| h.request(r) }
|
|
56
|
+
file_res.value
|
|
57
|
+
body = JSON.parse(file_res.body)
|
|
58
|
+
name = body.fetch("name")
|
|
59
|
+
encoded = body.fetch("content")
|
|
60
|
+
content = Base64.decode64(encoded.gsub("\n", "")).force_encoding("UTF-8")
|
|
61
|
+
|
|
62
|
+
next if name.end_with?(".lookml") || content.start_with?("-") || content.include?("- view")
|
|
63
|
+
|
|
64
|
+
File.write(File.join(directory, name), content)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
break if next_url.nil?
|
|
68
|
+
|
|
69
|
+
page += 1
|
|
70
|
+
end
|
metadata
CHANGED
|
@@ -1,38 +1,114 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: lkml
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
|
+
- Josh Temple
|
|
7
8
|
- Sylvain Utard
|
|
8
|
-
|
|
9
|
+
- Altertable
|
|
9
10
|
bindir: bin
|
|
10
11
|
cert_chain: []
|
|
11
|
-
date:
|
|
12
|
-
dependencies:
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
13
|
+
dependencies:
|
|
14
|
+
- !ruby/object:Gem::Dependency
|
|
15
|
+
name: logger
|
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
|
17
|
+
requirements:
|
|
18
|
+
- - ">="
|
|
19
|
+
- !ruby/object:Gem::Version
|
|
20
|
+
version: '0'
|
|
21
|
+
type: :development
|
|
22
|
+
prerelease: false
|
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
24
|
+
requirements:
|
|
25
|
+
- - ">="
|
|
26
|
+
- !ruby/object:Gem::Version
|
|
27
|
+
version: '0'
|
|
28
|
+
- !ruby/object:Gem::Dependency
|
|
29
|
+
name: minitest
|
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
|
31
|
+
requirements:
|
|
32
|
+
- - "~>"
|
|
33
|
+
- !ruby/object:Gem::Version
|
|
34
|
+
version: '5.25'
|
|
35
|
+
type: :development
|
|
36
|
+
prerelease: false
|
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
38
|
+
requirements:
|
|
39
|
+
- - "~>"
|
|
40
|
+
- !ruby/object:Gem::Version
|
|
41
|
+
version: '5.25'
|
|
42
|
+
- !ruby/object:Gem::Dependency
|
|
43
|
+
name: rake
|
|
44
|
+
requirement: !ruby/object:Gem::Requirement
|
|
45
|
+
requirements:
|
|
46
|
+
- - "~>"
|
|
47
|
+
- !ruby/object:Gem::Version
|
|
48
|
+
version: '13.2'
|
|
49
|
+
type: :development
|
|
50
|
+
prerelease: false
|
|
51
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
52
|
+
requirements:
|
|
53
|
+
- - "~>"
|
|
54
|
+
- !ruby/object:Gem::Version
|
|
55
|
+
version: '13.2'
|
|
56
|
+
- !ruby/object:Gem::Dependency
|
|
57
|
+
name: rubocop
|
|
58
|
+
requirement: !ruby/object:Gem::Requirement
|
|
59
|
+
requirements:
|
|
60
|
+
- - "~>"
|
|
61
|
+
- !ruby/object:Gem::Version
|
|
62
|
+
version: '1.69'
|
|
63
|
+
type: :development
|
|
64
|
+
prerelease: false
|
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
66
|
+
requirements:
|
|
67
|
+
- - "~>"
|
|
68
|
+
- !ruby/object:Gem::Version
|
|
69
|
+
version: '1.69'
|
|
70
|
+
description: |
|
|
71
|
+
Parse LookML strings into Ruby hashes and serialize Ruby hashes back to LookML.
|
|
72
|
+
Implementation uses the same algorithm as the original Python lkml package.
|
|
73
|
+
email:
|
|
74
|
+
- contact@altertable.ai
|
|
75
|
+
executables:
|
|
76
|
+
- lkml
|
|
16
77
|
extensions: []
|
|
17
78
|
extra_rdoc_files: []
|
|
18
79
|
files:
|
|
80
|
+
- ".github/workflows/ci.yml"
|
|
81
|
+
- ".gitignore"
|
|
82
|
+
- ".rubocop.yml"
|
|
83
|
+
- ".ruby-version"
|
|
84
|
+
- CONTRIBUTING.md
|
|
85
|
+
- Gemfile
|
|
86
|
+
- Gemfile.lock
|
|
19
87
|
- LICENSE.md
|
|
20
88
|
- README.md
|
|
89
|
+
- Rakefile
|
|
90
|
+
- bin/lkml
|
|
21
91
|
- lib/lkml.rb
|
|
92
|
+
- lib/lkml/cli.rb
|
|
22
93
|
- lib/lkml/keys.rb
|
|
23
94
|
- lib/lkml/lexer.rb
|
|
24
95
|
- lib/lkml/parser.rb
|
|
25
96
|
- lib/lkml/simple.rb
|
|
26
97
|
- lib/lkml/tokens.rb
|
|
27
98
|
- lib/lkml/tree.rb
|
|
99
|
+
- lib/lkml/utils.rb
|
|
28
100
|
- lib/lkml/version.rb
|
|
29
101
|
- lib/lkml/visitors.rb
|
|
30
|
-
|
|
102
|
+
- lkml.gemspec
|
|
103
|
+
- script/benchmark.rb
|
|
104
|
+
- script/download_lookml.rb
|
|
105
|
+
homepage: https://github.com/altertable-ai/lkml
|
|
31
106
|
licenses:
|
|
32
107
|
- MIT
|
|
33
108
|
metadata:
|
|
109
|
+
homepage_uri: https://github.com/altertable-ai/lkml
|
|
110
|
+
source_code_uri: https://github.com/altertable-ai/lkml
|
|
34
111
|
rubygems_mfa_required: 'true'
|
|
35
|
-
post_install_message:
|
|
36
112
|
rdoc_options: []
|
|
37
113
|
require_paths:
|
|
38
114
|
- lib
|
|
@@ -40,15 +116,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
40
116
|
requirements:
|
|
41
117
|
- - ">="
|
|
42
118
|
- !ruby/object:Gem::Version
|
|
43
|
-
version:
|
|
119
|
+
version: 3.3.0
|
|
44
120
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
45
121
|
requirements:
|
|
46
122
|
- - ">="
|
|
47
123
|
- !ruby/object:Gem::Version
|
|
48
124
|
version: '0'
|
|
49
125
|
requirements: []
|
|
50
|
-
rubygems_version: 3.
|
|
51
|
-
signing_key:
|
|
126
|
+
rubygems_version: 3.6.7
|
|
52
127
|
specification_version: 4
|
|
53
|
-
summary: LookML Ruby
|
|
128
|
+
summary: A speedy LookML parser and serializer implemented in pure Ruby.
|
|
54
129
|
test_files: []
|