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.
Files changed (65) hide show
  1. checksums.yaml +7 -0
  2. data/BEN/304/260OKU.md +137 -0
  3. data/CHANGELOG.md +9 -0
  4. data/LICENSE.md +675 -0
  5. data/README.md +1 -0
  6. data/bin/src +12 -0
  7. data/bin/srv +12 -0
  8. data/ellipses.gemspec +39 -0
  9. data/lib/ellipses-client.rb +4 -0
  10. data/lib/ellipses-server.rb +4 -0
  11. data/lib/ellipses.rb +5 -0
  12. data/lib/ellipses/client.rb +17 -0
  13. data/lib/ellipses/client/application.rb +77 -0
  14. data/lib/ellipses/client/cli.rb +24 -0
  15. data/lib/ellipses/client/cli/compile.rb +21 -0
  16. data/lib/ellipses/client/cli/decompile.rb +21 -0
  17. data/lib/ellipses/client/cli/init.rb +23 -0
  18. data/lib/ellipses/client/cli/update.rb +19 -0
  19. data/lib/ellipses/client/cli/validate.rb +21 -0
  20. data/lib/ellipses/client/cli/version.rb +17 -0
  21. data/lib/ellipses/client/command.rb +69 -0
  22. data/lib/ellipses/client/commands.rb +23 -0
  23. data/lib/ellipses/client/commands/include.rb +22 -0
  24. data/lib/ellipses/client/commands/reject.rb +17 -0
  25. data/lib/ellipses/client/commands/select.rb +17 -0
  26. data/lib/ellipses/client/commands/substitute.rb +23 -0
  27. data/lib/ellipses/client/commands/translate.rb +15 -0
  28. data/lib/ellipses/client/config.rb +33 -0
  29. data/lib/ellipses/client/directive.rb +39 -0
  30. data/lib/ellipses/client/error.rb +10 -0
  31. data/lib/ellipses/client/lines.rb +154 -0
  32. data/lib/ellipses/client/meta.rb +79 -0
  33. data/lib/ellipses/client/meta_file.rb +56 -0
  34. data/lib/ellipses/client/parser.rb +74 -0
  35. data/lib/ellipses/client/repository.rb +101 -0
  36. data/lib/ellipses/client/source.rb +69 -0
  37. data/lib/ellipses/error.rb +5 -0
  38. data/lib/ellipses/server.rb +10 -0
  39. data/lib/ellipses/server/application.rb +85 -0
  40. data/lib/ellipses/server/cli.rb +21 -0
  41. data/lib/ellipses/server/cli/dump.rb +20 -0
  42. data/lib/ellipses/server/cli/validate.rb +21 -0
  43. data/lib/ellipses/server/cli/version.rb +17 -0
  44. data/lib/ellipses/server/error.rb +7 -0
  45. data/lib/ellipses/server/meta.rb +69 -0
  46. data/lib/ellipses/server/meta_file.rb +39 -0
  47. data/lib/ellipses/server/repository.rb +47 -0
  48. data/lib/ellipses/server/symbols.rb +146 -0
  49. data/lib/ellipses/support.rb +3 -0
  50. data/lib/ellipses/support/deflate_path.rb +14 -0
  51. data/lib/ellipses/support/digest.rb +13 -0
  52. data/lib/ellipses/support/entropy.rb +17 -0
  53. data/lib/ellipses/support/expand_path.rb +13 -0
  54. data/lib/ellipses/support/intersperse_arrays.rb +14 -0
  55. data/lib/ellipses/support/prefixize_non_blank.rb +16 -0
  56. data/lib/ellipses/support/refinements.rb +20 -0
  57. data/lib/ellipses/support/sanitize_path.rb +101 -0
  58. data/lib/ellipses/support/search_path.rb +19 -0
  59. data/lib/ellipses/support/shell.rb +85 -0
  60. data/lib/ellipses/support/to_range.rb +15 -0
  61. data/lib/ellipses/support/ui.rb +55 -0
  62. data/lib/ellipses/support/updatelines.rb +19 -0
  63. data/lib/ellipses/support/writelines.rb +11 -0
  64. data/lib/ellipses/version.rb +5 -0
  65. 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,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ellipses
4
+ Error = Class.new StandardError
5
+ 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
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ellipses
4
+ module Server
5
+ module CLI
6
+ module Commands
7
+ class Version < Dry::CLI::Command
8
+ desc 'Print version'
9
+
10
+ def call(*)
11
+ puts '1.0.0'
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ellipses
4
+ module Server
5
+ Error = Class.new Ellipses::Error
6
+ end
7
+ end