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,74 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'shellwords'
|
4
|
+
|
5
|
+
module Ellipses
|
6
|
+
module Client
|
7
|
+
class Parser
|
8
|
+
PATTERN = /
|
9
|
+
^
|
10
|
+
(?<prefix>\s*)
|
11
|
+
(?<directive>(?:[.]|>){3,}.*)
|
12
|
+
/x
|
13
|
+
|
14
|
+
Parsed = Struct.new :lexemes, :prefix, keyword_init: true do
|
15
|
+
def to_a
|
16
|
+
lexemes
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
Lexeme = Struct.new :token, :argv, keyword_init: true do
|
21
|
+
def self.from_strings(strings)
|
22
|
+
new token: strings.shift.downcase, argv: strings
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_s
|
26
|
+
token
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class << self
|
31
|
+
def parse(line)
|
32
|
+
return unless (match = line.match(PATTERN))
|
33
|
+
|
34
|
+
parser = new(match[:directive])
|
35
|
+
|
36
|
+
Parsed.new lexemes: parser.(), prefix: match[:prefix]
|
37
|
+
end
|
38
|
+
|
39
|
+
def match?(line)
|
40
|
+
!line.match(PATTERN).nil?
|
41
|
+
end
|
42
|
+
|
43
|
+
private_class_method :new
|
44
|
+
end
|
45
|
+
|
46
|
+
attr_reader :line
|
47
|
+
|
48
|
+
def initialize(line)
|
49
|
+
@line = line
|
50
|
+
end
|
51
|
+
|
52
|
+
def call
|
53
|
+
Shellwords.split(preprocess(line)).slice_after('|').map do |group|
|
54
|
+
group.pop if group.last == '|'
|
55
|
+
Lexeme.from_strings(group)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
SUBSTITUTIONS = {
|
62
|
+
/^[.]{3,}/ => 'include',
|
63
|
+
/^>{3,}/ => ''
|
64
|
+
}.freeze
|
65
|
+
|
66
|
+
def preprocess(rawline)
|
67
|
+
rawline.strip.tap do |line|
|
68
|
+
SUBSTITUTIONS.each { |pattern, replace| line.gsub!(pattern, replace) }
|
69
|
+
line.strip!
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ellipses
|
4
|
+
module Client
|
5
|
+
class Repository
|
6
|
+
File = Struct.new :path, :source, :digest, :registered, keyword_init: true do
|
7
|
+
def save(all: true)
|
8
|
+
return if !registered && !all
|
9
|
+
return if digest == (new_digest = Support.digest(*source.lines))
|
10
|
+
|
11
|
+
Support.writelines(path, source.lines)
|
12
|
+
Support::UI.notice(path)
|
13
|
+
|
14
|
+
self.digest = new_digest
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
private_constant :File
|
19
|
+
|
20
|
+
attr_reader :loader
|
21
|
+
|
22
|
+
def initialize(loader)
|
23
|
+
@loader = loader
|
24
|
+
@files = {}
|
25
|
+
@memo = {}
|
26
|
+
end
|
27
|
+
|
28
|
+
def [](path)
|
29
|
+
@files[memo(path)].source
|
30
|
+
end
|
31
|
+
|
32
|
+
def each_source
|
33
|
+
each { |_, file| yield(file.source) }
|
34
|
+
end
|
35
|
+
|
36
|
+
def load(loader)
|
37
|
+
Dir.chdir loader.directory do
|
38
|
+
loader.read.each { |meta| register_internal(meta.source, Source.from_file(meta.source, meta.series)) }
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def save(all: true)
|
43
|
+
n = 0
|
44
|
+
each { |_, file| file.save(all: all) and (n += 1) }
|
45
|
+
n.positive?
|
46
|
+
end
|
47
|
+
|
48
|
+
def dump
|
49
|
+
meta = Meta.new []
|
50
|
+
|
51
|
+
each do |path, file|
|
52
|
+
next unless file.registered
|
53
|
+
next unless (source = file.source).series
|
54
|
+
|
55
|
+
meta << Meta::Source.new(source: path, series: source.series)
|
56
|
+
end
|
57
|
+
|
58
|
+
meta
|
59
|
+
end
|
60
|
+
|
61
|
+
def register(path, *args, **kwargs)
|
62
|
+
if @files.key?(key = memo(path))
|
63
|
+
return @files[key].tap { |file| file.registered = true }.source
|
64
|
+
end
|
65
|
+
|
66
|
+
register_internal(key, Source.from_file(path, *args, **kwargs))
|
67
|
+
end
|
68
|
+
|
69
|
+
def unregister(path)
|
70
|
+
return unless @files.key?(key = memo(path))
|
71
|
+
|
72
|
+
@files[key].tap { |file| file.registered = false }.source
|
73
|
+
end
|
74
|
+
|
75
|
+
def registered?(path)
|
76
|
+
return false unless @files.key?(key = memo(path))
|
77
|
+
|
78
|
+
@files[key].registered
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
|
83
|
+
def memo(path)
|
84
|
+
@memo[path] ||= Support.deflate_path(path, loader.directory)
|
85
|
+
end
|
86
|
+
|
87
|
+
def each(&block)
|
88
|
+
Dir.chdir(loader.directory) { @files.each(&block) }
|
89
|
+
end
|
90
|
+
|
91
|
+
def register_internal(key, source)
|
92
|
+
@files[key] = File.new path: key,
|
93
|
+
source: source,
|
94
|
+
digest: Support.digest(*source.lines),
|
95
|
+
registered: true
|
96
|
+
|
97
|
+
source
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'ellipses/server'
|
4
|
+
|
5
|
+
module Ellipses
|
6
|
+
module Client
|
7
|
+
class Source
|
8
|
+
attr_reader :series
|
9
|
+
|
10
|
+
def initialize(lines, series)
|
11
|
+
@lines = lines
|
12
|
+
@series = series || []
|
13
|
+
end
|
14
|
+
|
15
|
+
def lines
|
16
|
+
@lines.is_a?(Lines) ? @lines : (@lines = Lines.new @lines.to_a)
|
17
|
+
end
|
18
|
+
|
19
|
+
def decompile
|
20
|
+
return if series.empty?
|
21
|
+
|
22
|
+
while (lock = series.shift)
|
23
|
+
lines.substitute_uniq_like_chunk!(lock.insertion, lock.directive)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def compile(server)
|
28
|
+
new_lines = Lines[]
|
29
|
+
|
30
|
+
lines.each do |line|
|
31
|
+
new_lines.append(*(Parser.match?(line) ? execute_and_record(line, server) : line))
|
32
|
+
end
|
33
|
+
|
34
|
+
@lines = new_lines
|
35
|
+
end
|
36
|
+
|
37
|
+
def recompile(server)
|
38
|
+
decompile
|
39
|
+
compile(server)
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def execute(line, server, **kwargs)
|
45
|
+
raise Error, "No directive found: #{line}" unless (parsed = Parser.parse(line))
|
46
|
+
|
47
|
+
directive = Directive.new(parsed.lexemes, server)
|
48
|
+
|
49
|
+
outlines = directive.execute(**kwargs)
|
50
|
+
outlines.map! { |outline| Support.prefixize_non_blank(outline, parsed.prefix, excludes: "\f\f\f") }
|
51
|
+
|
52
|
+
Lines[outlines]
|
53
|
+
end
|
54
|
+
|
55
|
+
def execute_and_record(line, server, **kwargs)
|
56
|
+
result = execute(line, server, **kwargs)
|
57
|
+
series << Meta::Lock.new(directive: line, insertion: result.insertion_by_entropy)
|
58
|
+
result
|
59
|
+
end
|
60
|
+
|
61
|
+
class << self
|
62
|
+
def from_file(file, series = nil)
|
63
|
+
lines = File.readlines(Support.file!(file, error: Error)).lazy
|
64
|
+
new lines, series
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'ellipses'
|
4
|
+
|
5
|
+
require_relative 'server/error'
|
6
|
+
require_relative 'server/meta'
|
7
|
+
require_relative 'server/meta_file'
|
8
|
+
require_relative 'server/symbols'
|
9
|
+
require_relative 'server/repository'
|
10
|
+
require_relative 'server/application'
|
@@ -0,0 +1,85 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ellipses
|
4
|
+
module Server
|
5
|
+
class Application
|
6
|
+
DEFAULT_PORT = 0
|
7
|
+
|
8
|
+
Instance = Struct.new :uri, :repository, :port, keyword_init: true do
|
9
|
+
def to_s
|
10
|
+
self.class.ident(uri, port)
|
11
|
+
end
|
12
|
+
|
13
|
+
class << self
|
14
|
+
def ident(uri, port)
|
15
|
+
"#{uri}:#{port || DEFAULT_PORT}"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
attr_reader :paths, :intersperse
|
21
|
+
|
22
|
+
def initialize(paths, intersperse: "\n")
|
23
|
+
@paths = setup_paths(paths)
|
24
|
+
@intersperse = intersperse
|
25
|
+
@instances = {}
|
26
|
+
end
|
27
|
+
|
28
|
+
def out(uri:, symbols:, port: nil)
|
29
|
+
[].tap { |chunks| symbols.each { |symbol| chunks.append(*instance(uri, port).repository[symbol]) } }
|
30
|
+
end
|
31
|
+
|
32
|
+
def dump(uri:, symbols:, port: nil)
|
33
|
+
Support.intersperse_arrays(out(uri: uri, symbols: symbols, port: port), intersperse).flatten
|
34
|
+
end
|
35
|
+
|
36
|
+
def validate(uri)
|
37
|
+
raise NotImplementedError
|
38
|
+
end
|
39
|
+
|
40
|
+
def available?(uri)
|
41
|
+
!scan(uri).nil?
|
42
|
+
end
|
43
|
+
|
44
|
+
def available!(uri)
|
45
|
+
return if available?(uri)
|
46
|
+
|
47
|
+
raise Error, "Repository not found in path: #{uri}"
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def instance(uri, port = nil)
|
53
|
+
ident = Instance.ident(uri, port)
|
54
|
+
return @instances[ident] if @instances.key? ident
|
55
|
+
|
56
|
+
directory = scan(uri)
|
57
|
+
raise Error, "No repository instance found: #{uri}" unless directory
|
58
|
+
|
59
|
+
instance = Instance.new uri: uri, repository: Repository.load(directory), port: port
|
60
|
+
@instances[ident] = instance
|
61
|
+
end
|
62
|
+
|
63
|
+
def setup_paths(paths)
|
64
|
+
paths = paths.filter_map { |path| Support.dir(path) }.uniq
|
65
|
+
raise Error, 'No valid path found' if paths.empty?
|
66
|
+
|
67
|
+
paths
|
68
|
+
end
|
69
|
+
|
70
|
+
def scan(uri)
|
71
|
+
paths.map { |path| ::File.join(path, uri) }.find { |path| MetaFile.valid?(path) }
|
72
|
+
end
|
73
|
+
|
74
|
+
class << self
|
75
|
+
def dump(path, *symbols, **kwargs)
|
76
|
+
new([Support.dir!(path, error: Error)], **kwargs).dump(uri: '.', symbols: symbols)
|
77
|
+
end
|
78
|
+
|
79
|
+
def validate(path, **kwargs)
|
80
|
+
new([Support.dir!(path, error: Error)], **kwargs).validate('.')
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'dry/cli'
|
4
|
+
|
5
|
+
require 'ellipses/server'
|
6
|
+
|
7
|
+
module Ellipses
|
8
|
+
module Server
|
9
|
+
module CLI
|
10
|
+
module Commands
|
11
|
+
extend Dry::CLI::Registry
|
12
|
+
|
13
|
+
Dir[File.join(__dir__, 'cli/*.rb')].each { |command| require command }
|
14
|
+
|
15
|
+
register 'version', Version, aliases: ['v', '-v', '--version']
|
16
|
+
register 'dump', Dump
|
17
|
+
register 'validate', Validate
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ellipses
|
4
|
+
module Server
|
5
|
+
module CLI
|
6
|
+
module Commands
|
7
|
+
class Dump < Dry::CLI::Command
|
8
|
+
desc 'Dump symbols'
|
9
|
+
|
10
|
+
argument :path, type: :string, required: true, desc: 'Repository path'
|
11
|
+
argument :symbols, type: :array, required: false, desc: 'Symbols'
|
12
|
+
|
13
|
+
def call(path:, symbols: [], **)
|
14
|
+
puts Ellipses::Server::Application.dump path, *symbols
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'dry/cli'
|
4
|
+
|
5
|
+
module Ellipses
|
6
|
+
module Server
|
7
|
+
module CLI
|
8
|
+
module Commands
|
9
|
+
class Validate < Dry::CLI::Command
|
10
|
+
desc 'Validate repository'
|
11
|
+
|
12
|
+
argument :path, type: :string, required: true, desc: 'Repository path'
|
13
|
+
|
14
|
+
def call(path:)
|
15
|
+
Ellipses::Server::Application.validate path
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|