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,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ellipses
4
+ module Client
5
+ module Commands
6
+ class Reject < Command
7
+ command 'reject', argc: 1
8
+
9
+ include Command::Mixins::SetupPattern
10
+
11
+ def call(input)
12
+ input.grep_v param.pattern
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ellipses
4
+ module Client
5
+ module Commands
6
+ class Select < Command
7
+ command 'select', argc: 1
8
+
9
+ include Command::Mixins::SetupPattern
10
+
11
+ def call(input)
12
+ input.grep param.pattern
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ellipses
4
+ module Client
5
+ module Commands
6
+ class Substitute < Command
7
+ command 'sub', argc: 2
8
+
9
+ include Command::Mixins::SetupPattern
10
+
11
+ def setup
12
+ super
13
+
14
+ param.replace = argv.last
15
+ end
16
+
17
+ def call(input)
18
+ input.map { |line| line.gsub param.pattern, param.replace }
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ellipses
4
+ module Client
5
+ module Commands
6
+ class Translate < Command
7
+ command 'tr', argc: 2
8
+
9
+ def call(input)
10
+ input.map { |line| line.tr(*argv) }
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'forwardable'
4
+
5
+ module Ellipses
6
+ module Client
7
+ class Config
8
+ extend Forwardable
9
+
10
+ def_delegators :@config, *%i[paths]
11
+
12
+ def initialize(**options)
13
+ @config = build OpenStruct.new(options.compact)
14
+ end
15
+
16
+ private
17
+
18
+ def default_paths
19
+ environment = %w[ELLIPSES_PATH SRCPATH].find { |env| ENV.key? env }
20
+ paths = environment ? ENV[environment].split(':') : []
21
+
22
+ ['.', *paths]
23
+ end
24
+
25
+ def build(config)
26
+ config.paths = default_paths if !config.paths || config.paths.empty?
27
+ config.paths.compact!
28
+
29
+ config
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ellipses
4
+ module Client
5
+ class Directive
6
+ Error = Class.new Error
7
+
8
+ attr_reader :commands, :server
9
+
10
+ def initialize(lexemes, server)
11
+ @server = server
12
+ @commands = build(lexemes)
13
+ end
14
+
15
+ def execute(**kwargs)
16
+ output = []
17
+ commands.each do |command|
18
+ output = command.call(input = output, **kwargs) || input
19
+ end
20
+ output
21
+ end
22
+
23
+ private
24
+
25
+ def build(lexemes)
26
+ lexemes.map do |lexeme|
27
+ name = lexeme.token
28
+
29
+ raise Error, "No such command available: #{name}" unless Commands.available?(name)
30
+
31
+ proto = Commands.proto(name)
32
+ raise Error, "Wrong number of arguments: #{lexeme.argv}" unless proto.valid?(lexeme.argv)
33
+
34
+ proto.klass.new(lexeme.argv, server)
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ellipses
4
+ module Client
5
+ Error = Class.new Ellipses::Error
6
+
7
+ AmbiguousError = Class.new Error
8
+ UnfoundError = Class.new Error
9
+ end
10
+ end
@@ -0,0 +1,154 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'delegate'
4
+
5
+ module Ellipses
6
+ module Client
7
+ class Lines < SimpleDelegator
8
+ Surround = Struct.new :before, :after, keyword_init: true do
9
+ def range(i)
10
+ Range.new(i - before, i + after)
11
+ end
12
+
13
+ def center
14
+ before
15
+ end
16
+
17
+ def sensible?(i)
18
+ before <= i || after >= i
19
+ end
20
+
21
+ def sensible!(i)
22
+ return true if sensible?(i)
23
+
24
+ raise ArgumentError, "Insensible index #{i} for surround: #{before}, #{after}"
25
+ end
26
+
27
+ def length
28
+ @length ||= before + after + 1
29
+ end
30
+ end
31
+
32
+ private_constant :Surround
33
+
34
+ class Chunk
35
+ attr_reader :content, :center, :origin
36
+
37
+ def initialize(content, center:, origin:)
38
+ @content = content
39
+ @center = center
40
+ @origin = origin
41
+
42
+ raise ArgumentError, "Invalid center of array: #{center}" unless center < content.length
43
+ end
44
+
45
+ def insertion
46
+ @insertion ||= Meta::Insertion.new before: center,
47
+ after: content.length - center - 1,
48
+ signature: content[center],
49
+ digest: Support.digest(*content)
50
+ end
51
+
52
+ def size
53
+ content.size
54
+ end
55
+
56
+ def origin_range
57
+ (origin...(origin + size))
58
+ end
59
+
60
+ def to_a
61
+ content
62
+ end
63
+ end
64
+
65
+ private_constant :Chunk
66
+
67
+ def initialize(array_of_lines)
68
+ super
69
+
70
+ @delegate_sd_obj = array_of_lines
71
+ end
72
+
73
+ def surrounding_chunk(i, before: 0, after: 0)
74
+ return unless i < length
75
+
76
+ range = (surround = Surround.new(before: before, after: after)).range(i)
77
+ Chunk.new self[range], center: surround.center, origin: range.begin
78
+ end
79
+
80
+ def insertion_by_surround(i, before: 0, after: 0)
81
+ return unless i < length
82
+
83
+ signature = self[i]
84
+ digest = Support.digest(*self[Surround.new(before: before, after: after).range(i)])
85
+
86
+ Meta::Insertion.new before: before, after: after, signature: signature, digest: digest
87
+ end
88
+
89
+ def insertion_by_entropy
90
+ return if empty?
91
+
92
+ signature = self[center = index_of_maximum_entropy]
93
+ before = center
94
+ after = length - center - 1
95
+ digest = Support.digest(*self)
96
+
97
+ Meta::Insertion.new before: before, after: after, signature: signature, digest: digest
98
+ end
99
+
100
+ def likes(insertion)
101
+ likes = []
102
+
103
+ each_with_index do |line, i|
104
+ next unless line == insertion.signature
105
+ next unless (chunk = surrounding_chunk(i, before: insertion.before, after: insertion.after))
106
+ next unless chunk.insertion == insertion
107
+
108
+ likes << chunk
109
+ end
110
+
111
+ likes
112
+ end
113
+
114
+ def uniq_like_chunk!(insertion)
115
+ raise UnfoundError, "No chunks found like: #{insertion}" if likes(insertion).empty?
116
+ raise AmbiguousError, "Multiple chunks found like: #{insertion}" unless (likes = likes(insertion)).size == 1
117
+
118
+ likes.first
119
+ end
120
+
121
+ def substitute_uniq_like_chunk!(insertion, *lines)
122
+ uniq_like_chunk!(insertion).tap do |chunk|
123
+ substitute_within(chunk.origin_range, *lines)
124
+ end
125
+ end
126
+
127
+ private
128
+
129
+ def index_of_maximum_entropy
130
+ each_index.max_by { |i| Support.entropy(self[i]) }
131
+ end
132
+
133
+ def substitute_at(index, *lines)
134
+ slice!(index).tap { insert(index, *lines) }
135
+ end
136
+
137
+ def substitute_within(index_or_range, *lines)
138
+ slice!(range = Support.to_range(index_or_range)).tap { insert(range.begin, *lines) }
139
+ end
140
+
141
+ class << self
142
+ def [](object = nil)
143
+ case object
144
+ when self then object
145
+ when Array then new(object)
146
+ when Enumerator then new(object.to_a)
147
+ when NilClass then new([])
148
+ else new([*object])
149
+ end
150
+ end
151
+ end
152
+ end
153
+ end
154
+ end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'delegate'
4
+
5
+ module Ellipses
6
+ module Client
7
+ class Meta < SimpleDelegator
8
+ Error = Class.new Error
9
+
10
+ using Support::Refinements::Struct::FromHashWithoutBogusKeys
11
+
12
+ Source = Struct.new :source, :series, keyword_init: true do
13
+ def to_json(*args)
14
+ to_h.to_json(*args)
15
+ end
16
+
17
+ def self.from_hash(hash)
18
+ new source: hash[:source], series: Series.from_array(hash[:series])
19
+ end
20
+ end
21
+
22
+ class Series < SimpleDelegator
23
+ def initialize(array_of_locks)
24
+ super
25
+
26
+ @delegate_sd_obj = array_of_locks
27
+ end
28
+
29
+ def to_json(*args)
30
+ to_a.to_json(*args)
31
+ end
32
+
33
+ def self.from_array(array_of_hashes)
34
+ array_of_locks = array_of_hashes.map { |hash| Lock.from_hash hash }
35
+ new array_of_locks
36
+ end
37
+ end
38
+
39
+ Lock = Struct.new :directive, :insertion, keyword_init: true do
40
+ def to_json(*args)
41
+ to_h.to_json(*args)
42
+ end
43
+
44
+ def self.from_hash(hash)
45
+ new directive: hash[:directive], insertion: Insertion.from_hash(hash[:insertion])
46
+ end
47
+ end
48
+
49
+ Insertion = Struct.new :before, :after, :signature, :digest, keyword_init: true do
50
+ def to_s
51
+ signature
52
+ end
53
+
54
+ def to_json(*args)
55
+ to_h.to_json(*args)
56
+ end
57
+
58
+ def self.from_hash(hash)
59
+ new(**hash)
60
+ end
61
+ end
62
+
63
+ def initialize(array_of_sources)
64
+ super
65
+
66
+ @delegate_sd_obj = array_of_sources
67
+ end
68
+
69
+ def to_json(*args)
70
+ to_a.to_json(*args)
71
+ end
72
+
73
+ def self.from_array(array_of_hashes)
74
+ array_of_sources = array_of_hashes.map { |hash| Source.from_hash(hash) }
75
+ new array_of_sources
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ module Ellipses
6
+ module Client
7
+ class MetaFile
8
+ FILES = %w[.local/var/src.lock src.lock .src.lock].freeze
9
+ EMPTY = "[]\n"
10
+
11
+ def self.create(directory)
12
+ Support.dir!(directory)
13
+
14
+ selected = FILES.find { |path| ::Dir.exist? File.join(directory, ::File.dirname(path)) }
15
+ file = ::File.join(directory, selected)
16
+
17
+ ::File.write(file, EMPTY) unless ::File.exist? file
18
+
19
+ new
20
+ end
21
+
22
+ attr_reader :directory, :alternatives
23
+
24
+ def initialize
25
+ @directory, @file = Support.search_path FILES
26
+ end
27
+
28
+ def file
29
+ loaded!
30
+ @file
31
+ end
32
+
33
+ def loaded?
34
+ @file && ::File.exist?(@file)
35
+ end
36
+
37
+ def loaded!
38
+ raise 'Lockfile not located' if @file.nil?
39
+
40
+ raise "Lockfile not found: #{@file}" unless ::File.exist? @file
41
+ end
42
+
43
+ def read
44
+ Meta.from_array JSON.load_file(file, symbolize_names: true)
45
+ end
46
+
47
+ def write(meta)
48
+ Support.updatelines(file, meta.empty? ? EMPTY : JSON.pretty_generate(meta))
49
+ end
50
+
51
+ def to_s
52
+ file
53
+ end
54
+ end
55
+ end
56
+ end