ellipses 0.0.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 +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
|