gloss 0.0.1 → 0.0.6
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/.gitattributes +3 -0
- data/.github/workflows/crystal_specs.yml +26 -0
- data/.github/workflows/ruby_specs.yml +33 -0
- data/.gitignore +2 -1
- data/.rspec +1 -0
- data/Gemfile +0 -1
- data/Gemfile.lock +26 -34
- data/LICENSE +21 -0
- data/README.md +59 -7
- data/Rakefile +4 -0
- data/exe/gloss +15 -1
- data/ext/gloss/Makefile +27 -5
- data/ext/gloss/extconf.rb +3 -25
- data/ext/gloss/{src/lib → lib}/cr_ruby.cr +0 -0
- data/ext/gloss/lib/rbs_types.cr +3 -0
- data/ext/gloss/spec/parser_spec.cr +124 -0
- data/ext/gloss/spec/spec_helper.cr +2 -0
- data/ext/gloss/src/cr_ast.cr +129 -31
- data/ext/gloss/src/gloss.cr +12 -18
- data/ext/gloss/src/lexer.cr +59 -1
- data/ext/gloss/src/parser.cr +548 -254
- data/ext/gloss/src/rb_ast.cr +153 -27
- data/gloss.gemspec +1 -0
- data/lib/gloss.rb +4 -1
- data/lib/gloss/builder.rb +607 -409
- data/lib/gloss/cli.rb +64 -24
- data/lib/gloss/config.rb +16 -10
- data/lib/gloss/errors.rb +13 -7
- data/lib/gloss/initializer.rb +11 -6
- data/lib/gloss/logger.rb +34 -0
- data/lib/gloss/parser.rb +35 -0
- data/lib/gloss/scope.rb +8 -3
- data/lib/gloss/source.rb +18 -15
- data/lib/gloss/type_checker.rb +96 -0
- data/lib/gloss/version.rb +6 -1
- data/lib/gloss/watcher.rb +63 -19
- data/lib/gloss/writer.rb +18 -12
- data/sig/gloss.rbs +3 -0
- data/sig/listen.rbs +1 -0
- data/src/lib/gloss/builder.gl +546 -0
- data/src/lib/gloss/cli.gl +55 -0
- data/src/lib/gloss/config.gl +21 -0
- data/src/lib/gloss/errors.gl +11 -0
- data/src/lib/gloss/initializer.gl +20 -0
- data/src/lib/gloss/logger.gl +26 -0
- data/src/lib/gloss/parser.gl +31 -0
- data/src/lib/gloss/scope.gl +9 -0
- data/src/lib/gloss/source.gl +32 -0
- data/src/lib/gloss/type_checker.gl +101 -0
- data/src/lib/gloss/version.gl +3 -0
- data/src/lib/gloss/watcher.gl +67 -0
- data/src/lib/gloss/writer.gl +33 -0
- metadata +42 -6
- data/lib/gloss.bundle.dwarf +0 -0
- data/src/lib/hrb/initializer.gl +0 -22
- data/src/lib/hrb/watcher.gl +0 -32
@@ -0,0 +1,55 @@
|
|
1
|
+
require "optparse"
|
2
|
+
|
3
|
+
module Gloss
|
4
|
+
class CLI
|
5
|
+
def initialize(argv)
|
6
|
+
@argv = argv
|
7
|
+
end
|
8
|
+
|
9
|
+
def run
|
10
|
+
# TODO: allow destructuring: command, *files = @argv
|
11
|
+
command = @argv.first
|
12
|
+
files = @argv[1..-1]
|
13
|
+
err_msg = catch :error do
|
14
|
+
case command
|
15
|
+
when "watch"
|
16
|
+
files = files.map do |f|
|
17
|
+
path = Pathname.new(f).absolute? ? f : File.join(Dir.pwd, f)
|
18
|
+
if Pathname.new(path).exist?
|
19
|
+
path
|
20
|
+
else
|
21
|
+
throw :error, "Pathname #{f} does not exist"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
Watcher.new(files).watch
|
25
|
+
when "build"
|
26
|
+
(files.empty? ? Dir.glob("#{Config.src_dir}/**/*.gl") : files).each do |fp|
|
27
|
+
Gloss.logger.info "Building #{fp}"
|
28
|
+
content = File.read(fp)
|
29
|
+
tree_hash = Parser.new(content).run
|
30
|
+
type_checker = TypeChecker.new
|
31
|
+
rb_output = Builder.new(tree_hash, type_checker).run
|
32
|
+
type_checker.run(rb_output)
|
33
|
+
|
34
|
+
Gloss.logger.info "Writing #{fp}"
|
35
|
+
Writer.new(rb_output, fp).run
|
36
|
+
end
|
37
|
+
when "init"
|
38
|
+
force = false
|
39
|
+
OptionParser.new do |opt|
|
40
|
+
opt.on("--force", "-f") { force = true }
|
41
|
+
end.parse(@argv)
|
42
|
+
Initializer.new(force).run
|
43
|
+
else
|
44
|
+
throw :error, "Gloss doesn't know how to #{command}"
|
45
|
+
end
|
46
|
+
nil
|
47
|
+
end
|
48
|
+
|
49
|
+
if err_msg
|
50
|
+
Gloss.logger.fatal err_msg
|
51
|
+
exit 1
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "ostruct"
|
4
|
+
require "yaml"
|
5
|
+
|
6
|
+
module Gloss
|
7
|
+
CONFIG_PATH = ".gloss.yml"
|
8
|
+
Config = OpenStruct.new(
|
9
|
+
default_config: {
|
10
|
+
frozen_string_literals: true,
|
11
|
+
src_dir: "src",
|
12
|
+
}
|
13
|
+
)
|
14
|
+
|
15
|
+
user_config = if File.exist?(CONFIG_PATH)
|
16
|
+
YAML.safe_load(File.read(CONFIG_PATH))
|
17
|
+
else
|
18
|
+
Config.default_config
|
19
|
+
end
|
20
|
+
Config.default_config.each { |k, v| Config.send(:"#{k}=", user_config[k.to_s] || v) }
|
21
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require "yaml"
|
2
|
+
|
3
|
+
module Gloss
|
4
|
+
class Initializer
|
5
|
+
def initialize(@force)
|
6
|
+
end
|
7
|
+
|
8
|
+
def run
|
9
|
+
if File.exist?(CONFIG_PATH) && !@force
|
10
|
+
throw :error, "#{CONFIG_PATH} file already exists - aborting. Use --force to override."
|
11
|
+
end
|
12
|
+
|
13
|
+
File.open(CONFIG_PATH, "wb") do |file|
|
14
|
+
file.puts Config.default_config.transform_keys(&:to_s).to_yaml
|
15
|
+
end
|
16
|
+
|
17
|
+
Gloss.logger.info "Created #{CONFIG_PATH} with default preferences"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Gloss
|
2
|
+
def self.logger
|
3
|
+
if @logger
|
4
|
+
@logger
|
5
|
+
else
|
6
|
+
env_log_level = ENV.fetch("LOG_LEVEL") { "INFO" }
|
7
|
+
real_log_level = {
|
8
|
+
"UNKNOWN" => Logger::UNKNOWN,
|
9
|
+
"FATAL" => Logger::FATAL,
|
10
|
+
"ERROR" => Logger::ERROR,
|
11
|
+
"WARN" => Logger::WARN,
|
12
|
+
"INFO" => Logger::INFO,
|
13
|
+
"DEBUG" => Logger::DEBUG,
|
14
|
+
"NIL" => nil,
|
15
|
+
nil => nil,
|
16
|
+
"" => nil
|
17
|
+
}.fetch env_log_level
|
18
|
+
@logger = Logger.new(real_log_level ? STDOUT : nil)
|
19
|
+
formatter = Logger::Formatter.new
|
20
|
+
@logger.formatter = proc do |severity, datetime, progname, msg|
|
21
|
+
formatter.call(severity, datetime, progname, msg)
|
22
|
+
end
|
23
|
+
@logger
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Gloss
|
2
|
+
class Parser
|
3
|
+
def initialize(@str : String)
|
4
|
+
end
|
5
|
+
|
6
|
+
def run : String
|
7
|
+
tree_json = Gloss.parse_buffer(@str)
|
8
|
+
begin
|
9
|
+
JSON.parse tree_json, symbolize_names: true
|
10
|
+
rescue JSON::ParserError
|
11
|
+
error_message = tree_json
|
12
|
+
error_message.match /.+\s:(\d+)$/
|
13
|
+
if $1
|
14
|
+
line_number = $1.to_i
|
15
|
+
# line numbers start at 1, but array index starts at 0; so this still gives one line
|
16
|
+
# either side of the offending line
|
17
|
+
context = @str.lines[(line_number - 2)..(line_number)].map.with_index { |line, index|
|
18
|
+
"#{index - 1 + line_number}| #{line}"
|
19
|
+
}.join
|
20
|
+
error_message = <<~MSG
|
21
|
+
#{context.rstrip}
|
22
|
+
|
23
|
+
#{error_message}
|
24
|
+
|
25
|
+
MSG
|
26
|
+
end
|
27
|
+
throw :error, error_message
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Gloss
|
4
|
+
class Source < String
|
5
|
+
def initialize(@indent_level)
|
6
|
+
super()
|
7
|
+
end
|
8
|
+
|
9
|
+
def write(*args : String)
|
10
|
+
args.each do |a|
|
11
|
+
self << a
|
12
|
+
end
|
13
|
+
self
|
14
|
+
end
|
15
|
+
|
16
|
+
def write_indnt(*args : String)
|
17
|
+
write(*args.map { |a| "#{(" " * @indent_level)}#{a}" })
|
18
|
+
end
|
19
|
+
|
20
|
+
def write_ln(*args : String)
|
21
|
+
write_indnt(*args.map { |a| a.strip << "\n" })
|
22
|
+
end
|
23
|
+
|
24
|
+
def increment_indent
|
25
|
+
@indent_level += 1
|
26
|
+
end
|
27
|
+
|
28
|
+
def decrement_indent
|
29
|
+
@indent_level -= 1
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require "pry-byebug"
|
3
|
+
|
4
|
+
module Gloss
|
5
|
+
class TypeChecker
|
6
|
+
Project = Struct.new :targets
|
7
|
+
|
8
|
+
attr_reader :steep_target, :top_level_decls
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@steep_target = Steep::Project::Target.new(
|
12
|
+
name: "gloss",
|
13
|
+
options: Steep::Project::Options.new.tap do |o|
|
14
|
+
o.allow_unknown_constant_assignment = true
|
15
|
+
end,
|
16
|
+
source_patterns: ["gloss.rb"],
|
17
|
+
ignore_patterns: Array.new,
|
18
|
+
signature_patterns: ["sig"]
|
19
|
+
)
|
20
|
+
@top_level_decls = {}
|
21
|
+
end
|
22
|
+
|
23
|
+
def run(rb_str)
|
24
|
+
begin
|
25
|
+
valid_types = check_types rb_str
|
26
|
+
rescue ParseError => e
|
27
|
+
throw :error, ""
|
28
|
+
rescue => e
|
29
|
+
throw :error, "Type checking Error: #{e.message} (#{e.class})"
|
30
|
+
end
|
31
|
+
|
32
|
+
unless valid_types
|
33
|
+
errors = @steep_target.errors.map { |e|
|
34
|
+
case e
|
35
|
+
when Steep::Diagnostic::Ruby::NoMethod
|
36
|
+
"Unknown method :#{e.method}, location: #{e.type.location.inspect}"
|
37
|
+
when Steep::Diagnostic::Ruby::MethodBodyTypeMismatch
|
38
|
+
"Invalid method body type - expected: #{e.expected}, actual: #{e.actual}"
|
39
|
+
when Steep::Diagnostic::Ruby::IncompatibleArguments
|
40
|
+
<<-ERR
|
41
|
+
Invalid argmuents - method type: #{e.method_type}
|
42
|
+
method name: #{e.method_type.method_decls.first.method_name}
|
43
|
+
ERR
|
44
|
+
when Steep::Diagnostic::Ruby::ReturnTypeMismatch
|
45
|
+
"Invalid return type - expected: #{e.expected}, actual: #{e.actual}"
|
46
|
+
when Steep::Diagnostic::Ruby::IncompatibleAssignment
|
47
|
+
"Invalid assignment - cannot assign #{e.rhs_type} to type #{e.lhs_type}"
|
48
|
+
when Steep::Diagnostic::Ruby::UnexpectedBlockGiven
|
49
|
+
"Unexpected block given"
|
50
|
+
else
|
51
|
+
e.inspect
|
52
|
+
end
|
53
|
+
}.join("\n")
|
54
|
+
throw :error, errors
|
55
|
+
end
|
56
|
+
|
57
|
+
true
|
58
|
+
end
|
59
|
+
|
60
|
+
def check_types(rb_str)
|
61
|
+
env_loader = RBS::EnvironmentLoader.new
|
62
|
+
env = RBS::Environment.from_loader(env_loader)
|
63
|
+
project = Steep::Project.new(steepfile_path: Pathname.new(Config.src_dir).realpath)
|
64
|
+
project.targets << @steep_target
|
65
|
+
loader = Steep::Project::FileLoader.new(project: project)
|
66
|
+
loader.load_signatures
|
67
|
+
|
68
|
+
@steep_target.add_source("gloss.rb", rb_str)
|
69
|
+
|
70
|
+
@top_level_decls.each do |_, decl|
|
71
|
+
env << decl
|
72
|
+
end
|
73
|
+
env = env.resolve_type_names
|
74
|
+
|
75
|
+
@steep_target.instance_variable_set("@environment", env)
|
76
|
+
|
77
|
+
@steep_target.type_check
|
78
|
+
|
79
|
+
if @steep_target.status.is_a? Steep::Project::Target::SignatureErrorStatus
|
80
|
+
throw :error, @steep_target.status.errors.map { |e|
|
81
|
+
<<~MSG
|
82
|
+
SignatureSyntaxError:
|
83
|
+
Location: #{e.location}
|
84
|
+
Message: "#{e.exception.error_value.value}"
|
85
|
+
MSG
|
86
|
+
}.join("\n")
|
87
|
+
end
|
88
|
+
|
89
|
+
@steep_target.source_files.each do |path, f|
|
90
|
+
if f.status.is_a? Steep::Project::SourceFile::ParseErrorStatus
|
91
|
+
e = f.status.error
|
92
|
+
throw :error, "#{e.class}: #{e.message}"
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
@steep_target.status.is_a?(Steep::Project::Target::TypeCheckStatus) &&
|
97
|
+
@steep_target.no_error? &&
|
98
|
+
@steep_target.errors.empty?
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "listen"
|
4
|
+
|
5
|
+
module Gloss
|
6
|
+
class Watcher
|
7
|
+
def initialize(@paths : Array[String])
|
8
|
+
if @paths.empty?
|
9
|
+
@paths = [File.join(Dir.pwd, Config.src_dir)]
|
10
|
+
@only = /\.gl$/
|
11
|
+
else
|
12
|
+
file_names = Array.new
|
13
|
+
paths = Array.new
|
14
|
+
@paths.each do |pa|
|
15
|
+
pn = Pathname.new(pa)
|
16
|
+
paths << pn.parent.to_s
|
17
|
+
file_names << (pn.file? ? pn.basename.to_s : pa)
|
18
|
+
end
|
19
|
+
@paths = paths.uniq
|
20
|
+
@only = /#{Regexp.union(file_names)}/
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def watch
|
25
|
+
Gloss.logger.info "Now listening for changes in #{@paths.join(', ')}"
|
26
|
+
listener = Listen.to(
|
27
|
+
*@paths,
|
28
|
+
latency: 2,
|
29
|
+
only: @only
|
30
|
+
) do |modified, added, removed|
|
31
|
+
(modified + added).each do |f|
|
32
|
+
Gloss.logger.info "Rewriting #{f}"
|
33
|
+
content = File.read(f)
|
34
|
+
err = catch :error do
|
35
|
+
Writer.new(
|
36
|
+
Builder.new(
|
37
|
+
Parser.new(
|
38
|
+
content
|
39
|
+
).run
|
40
|
+
).run, f
|
41
|
+
).run
|
42
|
+
nil
|
43
|
+
end
|
44
|
+
if err
|
45
|
+
Gloss.logger.error err
|
46
|
+
else
|
47
|
+
Gloss.logger.info "Done"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
removed.each do |f|
|
51
|
+
out_path = Utils.src_path_to_output_path(f)
|
52
|
+
Gloss.logger.info "Removing #{out_path}"
|
53
|
+
File.delete out_path if File.exist? out_path
|
54
|
+
|
55
|
+
Gloss.logger.info "Done"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
begin
|
59
|
+
listener.start
|
60
|
+
sleep
|
61
|
+
rescue Interrupt
|
62
|
+
Gloss.logger.info "Interrupt signal received, shutting down"
|
63
|
+
exit 0
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "pathname"
|
4
|
+
require "fileutils"
|
5
|
+
|
6
|
+
module Gloss
|
7
|
+
module Utils
|
8
|
+
module_function
|
9
|
+
|
10
|
+
def src_path_to_output_path(src_path : String) : String
|
11
|
+
src_path.sub("#{Config.src_dir}/", "")
|
12
|
+
.sub(/\.gl$/, ".rb")
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class Writer
|
17
|
+
include Utils
|
18
|
+
|
19
|
+
def initialize(
|
20
|
+
@content,
|
21
|
+
src_path : String,
|
22
|
+
@output_path : Pathname? = Pathname.new(src_path_to_output_path(src_path))
|
23
|
+
)
|
24
|
+
end
|
25
|
+
|
26
|
+
def run
|
27
|
+
FileUtils.mkdir_p(@output_path.parent) unless @output_path.parent.exist?
|
28
|
+
File.open(@output_path, "wb") do |file|
|
29
|
+
file.puts @content
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: gloss
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- johansenja
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-02-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: fast_blank
|
@@ -38,6 +38,20 @@ dependencies:
|
|
38
38
|
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rbs
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
41
55
|
- !ruby/object:Gem::Dependency
|
42
56
|
name: steep
|
43
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -117,40 +131,62 @@ extensions:
|
|
117
131
|
- ext/gloss/extconf.rb
|
118
132
|
extra_rdoc_files: []
|
119
133
|
files:
|
134
|
+
- ".gitattributes"
|
135
|
+
- ".github/workflows/crystal_specs.yml"
|
136
|
+
- ".github/workflows/ruby_specs.yml"
|
120
137
|
- ".gitignore"
|
121
138
|
- ".gloss.yml"
|
139
|
+
- ".rspec"
|
122
140
|
- ".rubocop.yml"
|
123
141
|
- Gemfile
|
124
142
|
- Gemfile.lock
|
143
|
+
- LICENSE
|
125
144
|
- README.md
|
126
145
|
- Rakefile
|
127
146
|
- bin/console
|
128
147
|
- exe/gloss
|
129
148
|
- ext/gloss/Makefile
|
130
149
|
- ext/gloss/extconf.rb
|
150
|
+
- ext/gloss/lib/cr_ruby.cr
|
151
|
+
- ext/gloss/lib/rbs_types.cr
|
131
152
|
- ext/gloss/shard.yml
|
153
|
+
- ext/gloss/spec/parser_spec.cr
|
154
|
+
- ext/gloss/spec/spec_helper.cr
|
132
155
|
- ext/gloss/src/cr_ast.cr
|
133
156
|
- ext/gloss/src/gloss.cr
|
134
157
|
- ext/gloss/src/lexer.cr
|
135
|
-
- ext/gloss/src/lib/cr_ruby.cr
|
136
158
|
- ext/gloss/src/parser.cr
|
137
159
|
- ext/gloss/src/rb_ast.cr
|
138
160
|
- gloss.gemspec
|
139
|
-
- lib/gloss.bundle.dwarf
|
140
161
|
- lib/gloss.rb
|
141
162
|
- lib/gloss/builder.rb
|
142
163
|
- lib/gloss/cli.rb
|
143
164
|
- lib/gloss/config.rb
|
144
165
|
- lib/gloss/errors.rb
|
145
166
|
- lib/gloss/initializer.rb
|
167
|
+
- lib/gloss/logger.rb
|
168
|
+
- lib/gloss/parser.rb
|
146
169
|
- lib/gloss/scope.rb
|
147
170
|
- lib/gloss/source.rb
|
171
|
+
- lib/gloss/type_checker.rb
|
148
172
|
- lib/gloss/version.rb
|
149
173
|
- lib/gloss/watcher.rb
|
150
174
|
- lib/gloss/writer.rb
|
175
|
+
- sig/gloss.rbs
|
151
176
|
- sig/listen.rbs
|
152
|
-
- src/lib/
|
153
|
-
- src/lib/
|
177
|
+
- src/lib/gloss/builder.gl
|
178
|
+
- src/lib/gloss/cli.gl
|
179
|
+
- src/lib/gloss/config.gl
|
180
|
+
- src/lib/gloss/errors.gl
|
181
|
+
- src/lib/gloss/initializer.gl
|
182
|
+
- src/lib/gloss/logger.gl
|
183
|
+
- src/lib/gloss/parser.gl
|
184
|
+
- src/lib/gloss/scope.gl
|
185
|
+
- src/lib/gloss/source.gl
|
186
|
+
- src/lib/gloss/type_checker.gl
|
187
|
+
- src/lib/gloss/version.gl
|
188
|
+
- src/lib/gloss/watcher.gl
|
189
|
+
- src/lib/gloss/writer.gl
|
154
190
|
homepage:
|
155
191
|
licenses:
|
156
192
|
- MIT
|