ellipses 0.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/BEN/304/260OKU.md +137 -0
- data/CHANGELOG.md +9 -0
- data/LICENSE.md +675 -0
- data/README.md +1 -0
- data/bin/src +12 -0
- data/bin/srv +12 -0
- data/ellipses.gemspec +39 -0
- data/lib/ellipses-client.rb +4 -0
- data/lib/ellipses-server.rb +4 -0
- data/lib/ellipses.rb +5 -0
- data/lib/ellipses/client.rb +17 -0
- data/lib/ellipses/client/application.rb +77 -0
- data/lib/ellipses/client/cli.rb +24 -0
- data/lib/ellipses/client/cli/compile.rb +21 -0
- data/lib/ellipses/client/cli/decompile.rb +21 -0
- data/lib/ellipses/client/cli/init.rb +23 -0
- data/lib/ellipses/client/cli/update.rb +19 -0
- data/lib/ellipses/client/cli/validate.rb +21 -0
- data/lib/ellipses/client/cli/version.rb +17 -0
- data/lib/ellipses/client/command.rb +69 -0
- data/lib/ellipses/client/commands.rb +23 -0
- data/lib/ellipses/client/commands/include.rb +22 -0
- data/lib/ellipses/client/commands/reject.rb +17 -0
- data/lib/ellipses/client/commands/select.rb +17 -0
- data/lib/ellipses/client/commands/substitute.rb +23 -0
- data/lib/ellipses/client/commands/translate.rb +15 -0
- data/lib/ellipses/client/config.rb +33 -0
- data/lib/ellipses/client/directive.rb +39 -0
- data/lib/ellipses/client/error.rb +10 -0
- data/lib/ellipses/client/lines.rb +154 -0
- data/lib/ellipses/client/meta.rb +79 -0
- data/lib/ellipses/client/meta_file.rb +56 -0
- data/lib/ellipses/client/parser.rb +74 -0
- data/lib/ellipses/client/repository.rb +101 -0
- data/lib/ellipses/client/source.rb +69 -0
- data/lib/ellipses/error.rb +5 -0
- data/lib/ellipses/server.rb +10 -0
- data/lib/ellipses/server/application.rb +85 -0
- data/lib/ellipses/server/cli.rb +21 -0
- data/lib/ellipses/server/cli/dump.rb +20 -0
- data/lib/ellipses/server/cli/validate.rb +21 -0
- data/lib/ellipses/server/cli/version.rb +17 -0
- data/lib/ellipses/server/error.rb +7 -0
- data/lib/ellipses/server/meta.rb +69 -0
- data/lib/ellipses/server/meta_file.rb +39 -0
- data/lib/ellipses/server/repository.rb +47 -0
- data/lib/ellipses/server/symbols.rb +146 -0
- data/lib/ellipses/support.rb +3 -0
- data/lib/ellipses/support/deflate_path.rb +14 -0
- data/lib/ellipses/support/digest.rb +13 -0
- data/lib/ellipses/support/entropy.rb +17 -0
- data/lib/ellipses/support/expand_path.rb +13 -0
- data/lib/ellipses/support/intersperse_arrays.rb +14 -0
- data/lib/ellipses/support/prefixize_non_blank.rb +16 -0
- data/lib/ellipses/support/refinements.rb +20 -0
- data/lib/ellipses/support/sanitize_path.rb +101 -0
- data/lib/ellipses/support/search_path.rb +19 -0
- data/lib/ellipses/support/shell.rb +85 -0
- data/lib/ellipses/support/to_range.rb +15 -0
- data/lib/ellipses/support/ui.rb +55 -0
- data/lib/ellipses/support/updatelines.rb +19 -0
- data/lib/ellipses/support/writelines.rb +11 -0
- data/lib/ellipses/version.rb +5 -0
- metadata +264 -0
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'forwardable'
|
4
|
+
|
5
|
+
module Ellipses
|
6
|
+
module Server
|
7
|
+
class Meta
|
8
|
+
Error = Class.new Error
|
9
|
+
|
10
|
+
using Support::Refinements::Struct::FromHashWithoutBogusKeys
|
11
|
+
|
12
|
+
Global = Struct.new(:depends, :extension, :root, keyword_init: true) do
|
13
|
+
def self.from_hash(hash)
|
14
|
+
from_hash_without_bogus_keys!(hash, error: Error)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
Symbol = Struct.new(:symbol, :description, :path, :depends, keyword_init: true) do
|
19
|
+
def to_s
|
20
|
+
symbol
|
21
|
+
end
|
22
|
+
|
23
|
+
def add_depends(*depends)
|
24
|
+
return if depends&.empty?
|
25
|
+
|
26
|
+
self.depends ||= []
|
27
|
+
depends.reverse_each { |member| self.depends.unshift(member) }
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.from_hash(hash)
|
31
|
+
from_hash_without_bogus_keys!(hash, error: Error)
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.from_hash_with_depends(hash, *depends)
|
35
|
+
from_hash_without_bogus_keys!(hash, error: Error).tap { |symbol| symbol.add_depends(*depends) }
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class Symbols
|
40
|
+
extend Forwardable
|
41
|
+
|
42
|
+
def_delegators :@symbols, :each, :length
|
43
|
+
|
44
|
+
attr_reader :symbols
|
45
|
+
|
46
|
+
def initialize(symbols, *depends)
|
47
|
+
hash = {}
|
48
|
+
|
49
|
+
(symbols || []).each do |meta|
|
50
|
+
symbol = Symbol.from_hash_with_depends(meta, *depends)
|
51
|
+
hash[symbol.to_s] = symbol
|
52
|
+
end
|
53
|
+
|
54
|
+
@symbols = hash.values
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
attr_reader :symbols, :global
|
59
|
+
|
60
|
+
def initialize(hash)
|
61
|
+
symbols_array = hash.delete('symbols') || []
|
62
|
+
global_hash = hash || {}
|
63
|
+
|
64
|
+
@global = Global.from_hash(global_hash)
|
65
|
+
@symbols = Symbols.new(symbols_array, *(@global.depends || []))
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'tomlrb'
|
4
|
+
|
5
|
+
module Ellipses
|
6
|
+
module Server
|
7
|
+
class MetaFile
|
8
|
+
FILE = 'src.toml'
|
9
|
+
|
10
|
+
Error = Class.new(Error)
|
11
|
+
|
12
|
+
attr_reader :directory
|
13
|
+
|
14
|
+
def initialize(directory)
|
15
|
+
@directory = Support.dir!(directory, error: Error)
|
16
|
+
@file = Support.file!(FILE, base: @directory, error: Error)
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_s
|
20
|
+
@file
|
21
|
+
end
|
22
|
+
|
23
|
+
def read
|
24
|
+
Meta.new Tomlrb.load_file(@file)
|
25
|
+
rescue StandardError => e
|
26
|
+
raise Error, "Error while loading meta file: #{file}: #{e.message}"
|
27
|
+
end
|
28
|
+
|
29
|
+
class << self
|
30
|
+
def valid?(directory)
|
31
|
+
new(directory)
|
32
|
+
true
|
33
|
+
rescue Error
|
34
|
+
false
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'set'
|
4
|
+
|
5
|
+
module Ellipses
|
6
|
+
module Server
|
7
|
+
class Repository
|
8
|
+
Error = Class.new Error
|
9
|
+
|
10
|
+
attr_reader :symbols, :global, :root
|
11
|
+
|
12
|
+
def initialize(symbols:, global:, directory:)
|
13
|
+
@symbols = symbols
|
14
|
+
@global = global
|
15
|
+
@root = global.root ? ::File.join(directory, global.root) : directory
|
16
|
+
@consumed = Set.new
|
17
|
+
end
|
18
|
+
|
19
|
+
def [](string)
|
20
|
+
(chunks = []).tap do
|
21
|
+
symbols.resolve(string).each { |symbol| yield_symbol(chunks, symbol) }
|
22
|
+
next unless chunks.empty?
|
23
|
+
|
24
|
+
raise Error, "No chunks resolved for symbol: #{string}, which may already have been consumed"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def yield_symbol(chunks, symbol)
|
31
|
+
return if @consumed.include? symbol
|
32
|
+
|
33
|
+
@consumed << symbol
|
34
|
+
return if (lines = symbol.payload(root, global.extension)).empty?
|
35
|
+
|
36
|
+
chunks << lines
|
37
|
+
end
|
38
|
+
|
39
|
+
class << self
|
40
|
+
def load(directory)
|
41
|
+
meta = (meta_file = MetaFile.new(directory)).read
|
42
|
+
new symbols: Symbols.new(meta.symbols), global: meta.global, directory: meta_file.directory
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,146 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'set'
|
4
|
+
|
5
|
+
module Ellipses
|
6
|
+
module Server
|
7
|
+
class Symbols
|
8
|
+
CircularReferenceError = Class.new Error
|
9
|
+
MissingSymbolError = Class.new Error
|
10
|
+
BogusLeafError = Class.new Error
|
11
|
+
EmptyPayloadError = Class.new Error
|
12
|
+
|
13
|
+
class Symbol
|
14
|
+
attr_reader :depends
|
15
|
+
|
16
|
+
def initialize(meta)
|
17
|
+
@meta = meta
|
18
|
+
@depends = []
|
19
|
+
end
|
20
|
+
|
21
|
+
def build(symbols)
|
22
|
+
return if @meta.depends.nil?
|
23
|
+
|
24
|
+
# To avoid a circular reference, skip dependency if it is this symbol
|
25
|
+
@meta.depends.uniq.reject { |depend| depend == to_s }.each { |depend| depends << symbols.register(depend) }
|
26
|
+
end
|
27
|
+
|
28
|
+
def to_s
|
29
|
+
@meta.to_s
|
30
|
+
end
|
31
|
+
|
32
|
+
def leaf?
|
33
|
+
depends.empty?
|
34
|
+
end
|
35
|
+
|
36
|
+
def path
|
37
|
+
@path ||= @meta.path
|
38
|
+
end
|
39
|
+
|
40
|
+
def payload(rootdir, extension = nil)
|
41
|
+
unless (file = where(rootdir, extension)) && !::File.directory?(file)
|
42
|
+
raise BogusLeafError, "No source found for leaf symbol: #{self}" if leaf?
|
43
|
+
|
44
|
+
return []
|
45
|
+
end
|
46
|
+
|
47
|
+
::File.readlines(file).tap do |lines|
|
48
|
+
raise EmptyPayloadError, "Empty source for symbol: #{self}" if lines.empty?
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def where(rootdir, extension = nil)
|
55
|
+
return ::File.exist?(path) ? ::File.join(rootdir, path) : nil if path
|
56
|
+
|
57
|
+
paths = [base = ::File.join(rootdir, to_s)]
|
58
|
+
paths.prepend("#{base}#{extension}") if extension
|
59
|
+
|
60
|
+
paths.find { |path| ::File.exist?(path) }
|
61
|
+
end
|
62
|
+
|
63
|
+
class << self
|
64
|
+
def from_string(string)
|
65
|
+
new Meta::Symbol.from_hash symbol: string
|
66
|
+
end
|
67
|
+
|
68
|
+
def from_meta(meta)
|
69
|
+
new meta
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
private_class_method :new
|
74
|
+
end
|
75
|
+
|
76
|
+
private_constant :Symbol
|
77
|
+
|
78
|
+
def initialize(meta)
|
79
|
+
@meta = meta
|
80
|
+
@registry = {}
|
81
|
+
|
82
|
+
build
|
83
|
+
end
|
84
|
+
|
85
|
+
def [](string)
|
86
|
+
raise MissingSymbolError, "Missing symbol: #{string}" unless @registry.key? string
|
87
|
+
|
88
|
+
@registry[string]
|
89
|
+
end
|
90
|
+
|
91
|
+
def resolve(string)
|
92
|
+
resolved = []
|
93
|
+
walk(self[string]) { |symbol| resolved << symbol }
|
94
|
+
resolved
|
95
|
+
end
|
96
|
+
|
97
|
+
def register(string)
|
98
|
+
_register Symbol.from_string(string)
|
99
|
+
end
|
100
|
+
|
101
|
+
def walk(symbol, &block)
|
102
|
+
_walk(symbol, Set[symbol], &block)
|
103
|
+
end
|
104
|
+
|
105
|
+
private
|
106
|
+
|
107
|
+
def build
|
108
|
+
@meta.each { |symbol_meta| _register_by_meta(symbol_meta) }
|
109
|
+
@registry.dup.each_value { |symbol| symbol.build(self) }
|
110
|
+
|
111
|
+
sanitize
|
112
|
+
end
|
113
|
+
|
114
|
+
def _walk(symbol, stack, &block)
|
115
|
+
return if symbol.depends.nil?
|
116
|
+
|
117
|
+
symbol.depends.each do |depend|
|
118
|
+
raise CircularReferenceError, "Circular reference from #{symbol} to #{depend}" if stack.first == depend
|
119
|
+
|
120
|
+
next if stack.include? depend
|
121
|
+
|
122
|
+
stack << depend
|
123
|
+
_walk(depend, stack, &block)
|
124
|
+
rescue ::StopIteration
|
125
|
+
return nil
|
126
|
+
end
|
127
|
+
|
128
|
+
yield(symbol) if block
|
129
|
+
end
|
130
|
+
|
131
|
+
def _register(symbol)
|
132
|
+
@registry[symbol.to_s] || (@registry[symbol.to_s] = symbol)
|
133
|
+
end
|
134
|
+
|
135
|
+
def _register_by_meta(symbol_meta)
|
136
|
+
_register Symbol.from_meta(symbol_meta)
|
137
|
+
end
|
138
|
+
|
139
|
+
def sanitize
|
140
|
+
tap do
|
141
|
+
@registry.each_value { |symbol| walk(symbol) }
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'pathname'
|
4
|
+
|
5
|
+
module Ellipses
|
6
|
+
module Support
|
7
|
+
module_function
|
8
|
+
|
9
|
+
def deflate_path(path, rootdir = nil)
|
10
|
+
base = Pathname.new(rootdir || '.').expand_path
|
11
|
+
Pathname.new(path).cleanpath.expand_path.relative_path_from(base).to_s
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ellipses
|
4
|
+
module Support
|
5
|
+
module_function
|
6
|
+
|
7
|
+
# https://rosettacode.org/wiki/Entropy#Ruby
|
8
|
+
def entropy(string)
|
9
|
+
string
|
10
|
+
.each_char
|
11
|
+
.group_by(&:to_s)
|
12
|
+
.values
|
13
|
+
.map { |x| x.length / string.length.to_f }
|
14
|
+
.reduce(0) { |e, x| e - x * Math.log2(x) }
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ellipses
|
4
|
+
module Support
|
5
|
+
module_function
|
6
|
+
|
7
|
+
def intersperse_arrays(arrays, intersperse)
|
8
|
+
[].tap do |interspersed|
|
9
|
+
arrays.each { |array| interspersed.append(array, intersperse) }
|
10
|
+
interspersed.pop
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ellipses
|
4
|
+
module Support
|
5
|
+
module_function
|
6
|
+
|
7
|
+
def prefixize_non_blank(string, prefix, excludes: nil)
|
8
|
+
stripped = string.strip
|
9
|
+
|
10
|
+
return string if prefix.empty? || stripped.empty?
|
11
|
+
return string if excludes && [*excludes].include?(stripped)
|
12
|
+
|
13
|
+
"#{prefix}#{string}"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ellipses
|
4
|
+
module Support
|
5
|
+
module Refinements
|
6
|
+
module Struct
|
7
|
+
module FromHashWithoutBogusKeys
|
8
|
+
refine ::Struct.singleton_class do
|
9
|
+
def from_hash_without_bogus_keys!(hash, error:)
|
10
|
+
bogus = (hash = hash.transform_keys(&:to_sym)).keys.reject { |key| members.include?(key) }
|
11
|
+
raise error, "Bogus keys found: #{bogus}" unless bogus.empty?
|
12
|
+
|
13
|
+
new hash.slice(*members)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ellipses
|
4
|
+
module Support
|
5
|
+
module SanitizePath
|
6
|
+
class Sanitizer
|
7
|
+
def self.sanitize(*args, **kwargs)
|
8
|
+
new(*args, **kwargs).sanitize
|
9
|
+
end
|
10
|
+
|
11
|
+
class Error < StandardError
|
12
|
+
def initialize(path)
|
13
|
+
super("#{kind}: #{path}")
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.kind(message)
|
17
|
+
Class.new self do
|
18
|
+
define_method(:kind) { message }
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
MissingBase = Error.kind 'Directory not found'
|
24
|
+
WrongBase = Error.kind 'Not a directory'
|
25
|
+
MissingPath = Error.kind 'File or directory not found'
|
26
|
+
NotFile = Error.kind 'Not a file'
|
27
|
+
NotDir = Error.kind 'Not a directory'
|
28
|
+
NotExecutable = Error.kind 'Not an executable'
|
29
|
+
|
30
|
+
attr_reader :path, :base, :fullpath
|
31
|
+
|
32
|
+
def initialize(path:, base: nil)
|
33
|
+
@path = ::File.join(*path)
|
34
|
+
@base = ::File.join(*base) if base
|
35
|
+
@fullpath = ::File.expand_path(@path, @base)
|
36
|
+
end
|
37
|
+
|
38
|
+
def sanitize
|
39
|
+
if base
|
40
|
+
raise MissingBase, base unless ::File.exist?(base)
|
41
|
+
raise WrongBase, base unless ::File.directory?(base)
|
42
|
+
end
|
43
|
+
|
44
|
+
raise MissingPath, fullpath unless ::File.exist?(fullpath)
|
45
|
+
|
46
|
+
call
|
47
|
+
|
48
|
+
fullpath
|
49
|
+
end
|
50
|
+
|
51
|
+
class Dir < Sanitizer
|
52
|
+
def call
|
53
|
+
raise NotDir, fullpath unless ::File.directory?(fullpath)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
private_constant :Dir
|
58
|
+
|
59
|
+
class File < Sanitizer
|
60
|
+
def call
|
61
|
+
raise NotFile, fullpath unless ::File.file?(fullpath)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
private_constant :File
|
66
|
+
|
67
|
+
class Executable < Sanitizer
|
68
|
+
def call
|
69
|
+
raise NotExecutable, fullpath unless ::File.file?(fullpath) || ::File.executable?(fullpath)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
private_constant :Executable
|
74
|
+
end
|
75
|
+
|
76
|
+
private_constant :Sanitizer
|
77
|
+
|
78
|
+
def self.extended(consumer) # rubocop:disable Metrics/MethodLength
|
79
|
+
super
|
80
|
+
|
81
|
+
%i[file dir executable].each do |meth|
|
82
|
+
klass = Sanitizer.const_get meth.capitalize
|
83
|
+
|
84
|
+
consumer.define_singleton_method(meth) do |path, base: nil|
|
85
|
+
klass.sanitize(path: path, base: base)
|
86
|
+
rescue Sanitizer::Error
|
87
|
+
nil
|
88
|
+
end
|
89
|
+
|
90
|
+
consumer.define_singleton_method("#{meth}!") do |path, error: nil, base: nil|
|
91
|
+
klass.sanitize(path: path, base: base)
|
92
|
+
rescue Sanitizer::Error => e
|
93
|
+
error ? raise(error, e.message) : raise
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
extend SanitizePath
|
100
|
+
end
|
101
|
+
end
|