exa 0.1.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: f27cfd470e0649009f1628ac416129e508b9f892
4
+ data.tar.gz: de0b8c876be5db9d1aa5e51f139f675d0fef6fbf
5
+ SHA512:
6
+ metadata.gz: 84260a3a82086953e2e99ff908f370609ddbe5a60bf7567f646939ae71137477c7e00b5c29359d8339a458c1cb527dbc1bc14c891b376604c4a5bd78e2bb0737
7
+ data.tar.gz: 85cca81bd881342d01aa2878127d9469a1f154258285dcefd43794f1b948fdc8398e20a0d3c44d780b3b9069ecf9792842b75491ce43ae655c9337568096b9db
@@ -0,0 +1,3 @@
1
+ -
2
+ ChangeLog.md
3
+ LICENSE.txt
@@ -0,0 +1,6 @@
1
+ /.bundle
2
+ /.yardoc/
3
+ /Gemfile.lock
4
+ /doc/
5
+ /pkg/
6
+ /vendor/cache/*.gem
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --colour --format documentation
@@ -0,0 +1 @@
1
+ --markup markdown --title "exa Documentation" --protected
@@ -0,0 +1,4 @@
1
+ ### 0.1.0 / 2016-12-20
2
+
3
+ * Initial release:
4
+
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ gem 'pastel'
6
+
7
+ group :development do
8
+ gem 'kramdown'
9
+ gem 'pry'
10
+ end
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2016 Joseph Weissman
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,33 @@
1
+ # exa
2
+
3
+ * [Homepage](https://rubygems.org/gems/exa)
4
+ * [Documentation](http://rubydoc.info/gems/exa/frames)
5
+ * [Email](mailto:jweissman1986 at gmail.com)
6
+
7
+ [![Code Climate GPA](https://codeclimate.com/github//exa/badges/gpa.svg)](https://codeclimate.com/github//exa)
8
+
9
+ ## Description
10
+
11
+ TODO: Description
12
+
13
+ ## Features
14
+
15
+ ## Examples
16
+
17
+ require 'exa'
18
+
19
+ ## Requirements
20
+
21
+ ## Install
22
+
23
+ $ gem install exa
24
+
25
+ ## Synopsis
26
+
27
+ $ exa
28
+
29
+ ## Copyright
30
+
31
+ Copyright (c) 2016 Joseph Weissman
32
+
33
+ See {file:LICENSE.txt} for details.
@@ -0,0 +1,31 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+
5
+ begin
6
+ require 'bundler/setup'
7
+ rescue LoadError => e
8
+ abort e.message
9
+ end
10
+
11
+ require 'rake'
12
+
13
+
14
+ require 'rubygems/tasks'
15
+ Gem::Tasks.new
16
+
17
+ require 'rspec/core/rake_task'
18
+ RSpec::Core::RakeTask.new
19
+
20
+ task :test => :spec
21
+ task :default => :spec
22
+
23
+ require 'yard'
24
+ YARD::Rake::YardocTask.new
25
+ task :doc => :yard
26
+
27
+ require 'cucumber/rake/task'
28
+
29
+ Cucumber::Rake::Task.new do |t|
30
+ t.cucumber_opts = %w[--format pretty]
31
+ end
data/bin/exa ADDED
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ root = File.expand_path(File.join(File.dirname(__FILE__),'..'))
4
+ if File.directory?(File.join(root,'.git'))
5
+ Dir.chdir(root) do
6
+ begin
7
+ require 'bundler/setup'
8
+ require 'exa'
9
+ require 'exa/shell'
10
+
11
+ Exa::Shell.repl! do |config|
12
+ config.prompt = -> shell { shell.pastel.bold(" #{shell.pwd.path} $ ") }
13
+ end
14
+ rescue LoadError => e
15
+ warn e.message
16
+ warn "Run `gem install bundler` to install Bundler"
17
+ exit(-1)
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,60 @@
1
+ # encoding: utf-8
2
+
3
+ require 'yaml'
4
+
5
+ Gem::Specification.new do |gem|
6
+ gemspec = YAML.load_file('gemspec.yml')
7
+
8
+ gem.name = gemspec.fetch('name')
9
+ gem.version = gemspec.fetch('version') do
10
+ lib_dir = File.join(File.dirname(__FILE__),'lib')
11
+ $LOAD_PATH << lib_dir unless $LOAD_PATH.include?(lib_dir)
12
+
13
+ require 'exa/version'
14
+ Exa::VERSION
15
+ end
16
+
17
+ gem.summary = gemspec['summary']
18
+ gem.description = gemspec['description']
19
+ gem.licenses = Array(gemspec['license'])
20
+ gem.authors = Array(gemspec['authors'])
21
+ gem.email = gemspec['email']
22
+ gem.homepage = gemspec['homepage']
23
+
24
+ glob = lambda { |patterns| gem.files & Dir[*patterns] }
25
+
26
+ gem.files = `git ls-files`.split($/)
27
+ gem.files = glob[gemspec['files']] if gemspec['files']
28
+
29
+ gem.executables = gemspec.fetch('executables') do
30
+ glob['bin/*'].map { |path| File.basename(path) }
31
+ end
32
+ gem.default_executable = gem.executables.first if Gem::VERSION < '1.7.'
33
+
34
+ gem.extensions = glob[gemspec['extensions'] || 'ext/**/extconf.rb']
35
+ gem.test_files = glob[gemspec['test_files'] || '{test/{**/}*_test.rb']
36
+ gem.extra_rdoc_files = glob[gemspec['extra_doc_files'] || '*.{txt,md}']
37
+
38
+ gem.require_paths = Array(gemspec.fetch('require_paths') {
39
+ %w[ext lib].select { |dir| File.directory?(dir) }
40
+ })
41
+
42
+ gem.requirements = Array(gemspec['requirements'])
43
+ gem.required_ruby_version = gemspec['required_ruby_version']
44
+ gem.required_rubygems_version = gemspec['required_rubygems_version']
45
+ gem.post_install_message = gemspec['post_install_message']
46
+
47
+ split = lambda { |string| string.split(/,\s*/) }
48
+
49
+ if gemspec['dependencies']
50
+ gemspec['dependencies'].each do |name,versions|
51
+ gem.add_dependency(name,split[versions])
52
+ end
53
+ end
54
+
55
+ if gemspec['development_dependencies']
56
+ gemspec['development_dependencies'].each do |name,versions|
57
+ gem.add_development_dependency(name,split[versions])
58
+ end
59
+ end
60
+ end
File without changes
@@ -0,0 +1 @@
1
+ Feature: Blah blah blah
File without changes
@@ -0,0 +1,16 @@
1
+ name: exa
2
+ summary: "recursive in-memory algos and data structures"
3
+ description: "recursive in-memory structures"
4
+ license: MIT
5
+ authors: Joseph Weissman
6
+ email: jweissman1986@gmail.com
7
+ homepage: https://rubygems.org/gems/exa
8
+
9
+ development_dependencies:
10
+ bundler: ~> 1.10
11
+ codeclimate-test-reporter: ~> 0.1
12
+ cucumber: ~> 0.10.2
13
+ rake: ~> 10.0
14
+ rspec: ~> 3.0
15
+ rubygems-tasks: ~> 0.2
16
+ yard: ~> 0.8
@@ -0,0 +1,85 @@
1
+ require 'exa/version'
2
+ require 'exa/tree_node'
3
+ require 'exa/visitor'
4
+
5
+ module Exa
6
+ class Process
7
+ def initialize(title)
8
+ @title = title
9
+ end
10
+
11
+ def register
12
+ Process.table += [ self ]
13
+ end
14
+
15
+ def unregister
16
+ Process.table -= [ self ]
17
+ end
18
+
19
+ def run!
20
+ register
21
+ perform!
22
+ unregister
23
+ end
24
+
25
+ def self.table
26
+ @table ||= []
27
+ end
28
+ end
29
+
30
+ class Copier < Process
31
+ def initialize(source, target)
32
+ @source = source
33
+ @target = target
34
+ super("copy #{source} -> #{target}")
35
+ end
36
+
37
+ def perform!
38
+ @target.update @source.value
39
+ end
40
+ end
41
+
42
+ class Deleter < Process
43
+ def initialize(target)
44
+ @target = target
45
+ super("delete #{target}")
46
+ end
47
+
48
+ def perform!
49
+ @target.parent.remove_child(child_name: @target.name)
50
+ end
51
+ end
52
+
53
+ class << self
54
+ def remember(path, value)
55
+ p [ :remember, path: path, value: value ]
56
+ recall(path).update(value)
57
+ end
58
+ alias :[]= :remember
59
+
60
+ def recall(path)
61
+ p [ :recall, path: path ]
62
+ visitor.seek(path)
63
+ end
64
+ alias :[] :recall
65
+
66
+ def expand(path)
67
+ visitor.query(path)
68
+ end
69
+ alias :call :expand
70
+
71
+ def clean_slate!
72
+ @root = TreeNode.new(name: '', value: '(root)')
73
+ # @visitor = Visitor.new(@root)
74
+ end
75
+
76
+ private
77
+ def visitor
78
+ Visitor.new(root)
79
+ end
80
+
81
+ def root
82
+ @root ||= TreeNode.new(name: '', value: '(system root)')
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,107 @@
1
+ require 'pry'
2
+ require 'pastel'
3
+
4
+ module Exa
5
+ class ShellConfig
6
+ attr_accessor :prompt
7
+ end
8
+
9
+ class ShellCommand
10
+ def initialize(title:,args:)
11
+ @title = title
12
+ @args = args
13
+ end
14
+
15
+ def evaluate(shell)
16
+ case @title
17
+ when "ls" then
18
+ if shell.pwd.children.any?
19
+ shell.print_collection shell.pwd.children.map(&:name)
20
+ else
21
+ shell.print_warning "(no children of #{shell.pwd.path})"
22
+ end
23
+ when "cd" then
24
+ target = Exa.expand(@args.first)
25
+ if target && !target.empty?
26
+ shell.change_directory target.first
27
+ else
28
+ shell.print_warning "Invalid path for cd: #{@args.first}"
29
+ end
30
+ when "mkdir" then
31
+ target = Exa.recall(@args.first)
32
+ when "pwd" then
33
+ shell.print_info shell.pwd.path
34
+ else
35
+ shell.print_warning "Unknown command: '#@title'"
36
+ end
37
+ end
38
+
39
+ def self.extract(string)
40
+ tokens = string.split(' ')
41
+ # p [ :extract, tokens: tokens ]
42
+ cmd,*args = *tokens
43
+ # p [ :extract, cmd: cmd, args: args ]
44
+ new(title: cmd, args: args)
45
+ end
46
+ end
47
+
48
+ class Shell
49
+ attr_reader :pwd
50
+
51
+ def initialize(pwd)
52
+ @pwd = pwd
53
+ end
54
+
55
+ def configuration
56
+ @config ||= ShellConfig.new
57
+ end
58
+
59
+ def kickstart!
60
+ loop do
61
+ print configuration.prompt.call(self)
62
+ inp = gets.chomp
63
+ outp = shell_eval(inp)
64
+ p outp unless outp.nil? || (outp.respond_to?(:empty?) && outp.empty?)
65
+ end
66
+ end
67
+
68
+ def shell_eval(cmd_str)
69
+ cmd = ShellCommand.extract(cmd_str)
70
+ cmd.evaluate(self)
71
+ end
72
+
73
+ def change_directory(target)
74
+ @pwd = target
75
+ end
76
+
77
+ def print_collection(elements)
78
+ puts
79
+ elements.each do |element|
80
+ puts " - " + pastel.blue(" #{element}")
81
+ end
82
+ puts
83
+ end
84
+
85
+ def print_warning(message)
86
+ puts
87
+ puts pastel.red(message)
88
+ puts
89
+ end
90
+
91
+ def print_info(message)
92
+ puts
93
+ puts pastel.green(message)
94
+ puts
95
+ end
96
+
97
+ def pastel
98
+ @pastel ||= Pastel.new
99
+ end
100
+
101
+ def self.repl!
102
+ shell = new(Exa['/'])
103
+ yield shell.configuration
104
+ shell.kickstart!
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,159 @@
1
+ module Exa
2
+ class TreeNode
3
+ attr_reader :name, :parent, :overlays, :links
4
+ def initialize(name:, value: nil, parent: nil, virtual: false, symbolic: false)
5
+ p [ :tree_node, name: name ]
6
+ @name = name
7
+ @value = value
8
+ @parent = parent
9
+ @virtual = virtual
10
+ @symbolic = symbolic
11
+ @children = []
12
+ @overlays = []
13
+ @links = []
14
+ end
15
+
16
+ def value
17
+ if virtual?
18
+ constituents.first.value
19
+ elsif symbolic?
20
+ dereference_symbolic_link.value
21
+ else
22
+ @value
23
+ end
24
+ end
25
+
26
+ def virtual?
27
+ @virtual
28
+ end
29
+
30
+ def symbolic?
31
+ @symbolic
32
+ end
33
+
34
+ def inspect
35
+ "<#{@name}>"
36
+ end
37
+
38
+ def path
39
+ if symbolic?
40
+ dereference_symbolic_link.path
41
+ else
42
+ if @parent
43
+ slash_name = "/#@name"
44
+ if @parent.path == '/'
45
+ slash_name
46
+ else
47
+ @parent.path + slash_name
48
+ end
49
+ else
50
+ '/' #@name
51
+ end
52
+ end
53
+ end
54
+
55
+ def update(val)
56
+ @value = val
57
+ self
58
+ end
59
+
60
+ def create_child(child_name:)
61
+ child = TreeNode.new(name: child_name, parent: self)
62
+ @children << child
63
+ child
64
+ end
65
+
66
+ def remove_child(child_name:)
67
+ child = @children.detect { |c| c.name == child_name }
68
+ @children.delete(child)
69
+ self
70
+ end
71
+
72
+ def unify(overlay:)
73
+ if overlay.virtual?
74
+ raise "Won't union mount virtual paths! (Try `link(source: ...)` instead.)"
75
+ end
76
+
77
+ @overlays << overlay
78
+ self
79
+ end
80
+
81
+ def link(source:)
82
+ @links << source
83
+ self
84
+ end
85
+
86
+ def children
87
+ if virtual?
88
+ virtualize(constituents.flat_map(&:children))
89
+ else
90
+ @children + symbolize(symbolic_children) + virtualize(virtual_children)
91
+ end
92
+ end
93
+
94
+ def recall(path)
95
+ Visitor.new(self).seek(path)
96
+ end
97
+ alias :[] :recall
98
+
99
+ def query(path)
100
+ Visitor.new(self).query(path)
101
+ end
102
+
103
+ def copy(target)
104
+ Copier.new(self, target).perform!
105
+ end
106
+
107
+ def delete
108
+ Deleter.new(self).perform!
109
+ end
110
+
111
+ protected
112
+ def constituents
113
+ sources = if parent.virtual?
114
+ parent.constituents.flat_map(&:children)
115
+ else
116
+ parent.overlays.flat_map(&:children)
117
+ end
118
+
119
+ sources.select do |candidate|
120
+ candidate.name == @name
121
+ end
122
+ end
123
+
124
+ def virtual_children
125
+ @overlays.flat_map(&:children)
126
+ end
127
+
128
+ def symbolic_children
129
+ # this is really a reference
130
+ @links.flat_map(&:children)
131
+ end
132
+
133
+ def dereference_symbolic_link
134
+ # okay, we're symbolic... so our parents created us
135
+ # and have a link
136
+ parent.links.flat_map(&:children).detect do |linked_child|
137
+ linked_child.name == @name
138
+ end
139
+ end
140
+
141
+ private
142
+ def symbolize(schildren)
143
+ schildren.map(&method(:symbolize_one))
144
+ end
145
+
146
+ def symbolize_one(schild)
147
+ TreeNode.new(name: schild.name, value: schild.value, parent: self, symbolic: true)
148
+ end
149
+
150
+ def virtualize(vchildren)
151
+ vchildren.map(&method(:virtualize_one))
152
+ end
153
+
154
+ def virtualize_one(vchild)
155
+ TreeNode.new(name: vchild.name, value: vchild.value, parent: self, virtual: true)
156
+ end
157
+ end
158
+ end
159
+
@@ -0,0 +1,4 @@
1
+ module Exa
2
+ # exa version
3
+ VERSION = "0.1.0"
4
+ end
@@ -0,0 +1,100 @@
1
+ module Exa
2
+ class Visitor
3
+ def initialize(root)
4
+ @root = root
5
+ end
6
+
7
+ def gather_leaves(branch=@root, depth: 10)
8
+ return [] if depth < 0
9
+ if branch.children.any?
10
+ branch.children.flat_map do |child|
11
+ gather_leaves(child, depth: depth-1)
12
+ end.uniq
13
+ else # we are a leaf!
14
+ [ branch ]
15
+ end
16
+ end
17
+
18
+ def gather_branches(branch=@root, depth: 10)
19
+ return [] if depth < 0
20
+ if branch.children.any?
21
+ [ branch ] + branch.children.flat_map do |child|
22
+ gather_branches(child, depth: depth-1)
23
+ end.uniq
24
+ else # we are a leaf!
25
+ [ ]
26
+ end
27
+ end
28
+
29
+ def seek(path, create_missing: true)
30
+ path = '/' + path unless path.start_with?('/')
31
+ _root, *path_segments = path.split('/')
32
+ current = @root
33
+ path_segments.each do |segment|
34
+ next_child = current.children.detect do |child|
35
+ child.name == segment
36
+ end
37
+
38
+ current = if next_child
39
+ next_child
40
+ else
41
+ return nil unless create_missing
42
+ current.create_child(child_name: segment)
43
+ end
44
+ end
45
+ current
46
+ end
47
+
48
+ def query(path)
49
+ path = '/' + path unless path.start_with?('/')
50
+ _root, next_segment, *remaining_segments = path.split('/')
51
+ remaining_path = '/' + remaining_segments.join('/')
52
+ current = @root
53
+ return [current] unless next_segment
54
+ if next_segment == '*'
55
+ # need to multiplex remaining query across *all* children
56
+ next_children = current.children
57
+ if remaining_segments.any?
58
+ next_children.flat_map do |child|
59
+ child.query(remaining_path).uniq
60
+ end
61
+ else
62
+ next_children
63
+ end
64
+ elsif next_segment == '**'
65
+ # this is more subtle, and really points to:
66
+ # do we need to be treating the path query as a regular expression?
67
+ # and matching against *all* filenames?
68
+ if remaining_segments.any?
69
+ gather_branches(current).flat_map do |folder|
70
+ folder.query(remaining_path).uniq
71
+ end
72
+ else
73
+ gather_leaves(current)
74
+ end
75
+ elsif next_segment == '..'
76
+ # need to back up...
77
+ parent = current.parent
78
+ if remaining_segments.any?
79
+ parent.children.flat_map do |child|
80
+ child.query(remaining_path).uniq
81
+ end.uniq
82
+ else
83
+ [ parent ]
84
+ end
85
+ else
86
+ # need to find just child matching *this* segment
87
+ next_child = current.children.detect { |c| c.name == next_segment }
88
+ if next_child
89
+ if remaining_segments.any?
90
+ next_child.query(remaining_path).uniq
91
+ else
92
+ [ next_child ]
93
+ end
94
+ else
95
+ []
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,121 @@
1
+ require 'spec_helper'
2
+ require 'pry'
3
+ require 'exa'
4
+
5
+ describe Exa do
6
+ before do
7
+ Exa.clean_slate!
8
+ end
9
+
10
+ it 'should store a value' do
11
+ Exa.remember('x', 5)
12
+ Exa.remember('y', 7)
13
+ expect(Exa.recall('x').value).to eq(5)
14
+ expect(Exa.recall('y').value).to eq(7)
15
+ end
16
+
17
+ it 'has shorthand' do
18
+ Exa['hi'] = 'hello'
19
+ expect( Exa['hi'].value ).to eq('hello')
20
+ end
21
+
22
+ it 'should store a structured value' do
23
+ Exa.remember('/hello/world', 5)
24
+ expect(Exa.recall('/hello/world').value).to eq(5)
25
+
26
+ # shorthand
27
+ expect(Exa['/hello/world'].value).to eq(5)
28
+ end
29
+
30
+ it 'should navigate' do
31
+ Exa.remember("/a/b/c", 'd')
32
+ expect(Exa["/a/b/c"].value).to eq('d')
33
+ expect(Exa["/a/b/c"].path).to eq('/a/b/c')
34
+ expect(Exa["/a"].children).to eq([Exa["/a/b"]])
35
+ expect(Exa["/a"].parent).to eq(Exa["/"])
36
+
37
+ expect(Exa['/'].path).to eq('/')
38
+ end
39
+
40
+ describe 'filesystem-like structures' do
41
+ before do
42
+ Exa["/usr/joe/minutes/alpha"] = "hello"
43
+ Exa["/usr/joe/books/ch01/sec01/intro"] = "welcome"
44
+ Exa["/usr/mal/minutes/beta"] = "world"
45
+ end
46
+
47
+ it 'should unify/virtualize' do
48
+ Exa["/usr/mal/friends"].unify(overlay: Exa["/usr/joe"])
49
+
50
+ expect( Exa["/usr/mal/friends/minutes"] ).to be_virtual
51
+ expect( Exa["/usr/mal/friends/minutes/alpha"] ).to be_virtual
52
+ expect( Exa["/usr/mal/friends/minutes/alpha"].value ).to eq('hello')
53
+ expect( Exa["/usr/mal/friends/books/ch01/sec01/intro"].value ).to eq('welcome')
54
+ end
55
+
56
+ it 'should symlink' do
57
+ # okay, make a union mount so we have virtual paths...
58
+ Exa["/usr/joe/friends"].unify(overlay: Exa["/usr/mal"])
59
+
60
+ expect( Exa['/usr/joe/friends/minutes'] ).to be_virtual
61
+
62
+ # we should be able to *symlink* to virtual paths
63
+ # which we can't otherwise mount directly (since virtual)
64
+ Exa['/news'].link(source: Exa['/usr/joe/friends/minutes'])
65
+
66
+ expect( Exa['/news/beta'] ).to be_symbolic
67
+ expect( Exa['/news/beta'].value ).to eq('world')
68
+ expect( Exa['/news/beta'].path ).to eq('/usr/joe/friends/minutes/beta')
69
+ end
70
+
71
+ describe 'path expansion' do
72
+ it 'should expand root path' do
73
+ expect( Exa.expand('/') ).to eq([ Exa['/'] ])
74
+ end
75
+
76
+ it 'should expand user path without a slash' do
77
+ expect( Exa.expand('usr')).to eq([ Exa['/usr'] ])
78
+ end
79
+
80
+ it 'should expand double stars' do
81
+ expect( Exa.expand('**/joe') ).to eq([ Exa['/usr/joe'] ])
82
+ end
83
+
84
+ it 'should expand single stars' do
85
+ expect( Exa.expand('/usr/*') ).to eq([ Exa['/usr/joe'], Exa['/usr/mal'] ])
86
+ end
87
+
88
+ it 'should expand nested single stars' do
89
+ expect( Exa.expand('/usr/joe/*/*') ).to eq([ Exa['/usr/joe/minutes/alpha'], Exa['/usr/joe/books/ch01'] ])
90
+ end
91
+
92
+ it 'should expand double dots' do
93
+ expect( Exa.expand('/usr/..') ).to eq([ Exa['/'] ])
94
+ expect( Exa.expand('/usr/joe/..') ).to eq([ Exa['/usr'] ])
95
+ end
96
+
97
+ it 'should not find expansions for inexistent paths' do
98
+ expect( Exa.expand('/does_not_exist') ).to eq([])
99
+ expect( Exa.expand('/also/does/not/exist') ).to eq([])
100
+ end
101
+ end
102
+
103
+ it 'should create/nav arbitrary structures' do
104
+ abel_route = '/universes/ea/regions/blessed_isles/cities/numenor/people/abel'
105
+ cain_route = '/universes/ea/regions/blessed_isles/cities/numenor/people/cain'
106
+ Exa[abel_route] = 'hi from abel!'
107
+ Exa[cain_route] = 'hi from cain!'
108
+ expect( Exa.expand('/**/people/*') ).to eq([ Exa[abel_route], Exa[cain_route] ])
109
+ end
110
+
111
+ it 'should perform some fs-like operations' do
112
+ Exa['/tmp/hello'] = 'world'
113
+
114
+ Exa['/tmp/hello'].copy(Exa['/tmp/there'])
115
+ Exa['/tmp/hello'].delete
116
+
117
+ expect( Exa['/tmp/there'].value ).to eq('world')
118
+ expect( Exa['/tmp/hello'].value ).to eq(nil)
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,4 @@
1
+ require 'rspec'
2
+ require 'exa/version'
3
+
4
+ include Exa
metadata ADDED
@@ -0,0 +1,168 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: exa
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Joseph Weissman
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-12-26 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.10'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.10'
27
+ - !ruby/object:Gem::Dependency
28
+ name: codeclimate-test-reporter
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.1'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.1'
41
+ - !ruby/object:Gem::Dependency
42
+ name: cucumber
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 0.10.2
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 0.10.2
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '10.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '10.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '3.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rubygems-tasks
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '0.2'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '0.2'
97
+ - !ruby/object:Gem::Dependency
98
+ name: yard
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '0.8'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '0.8'
111
+ description: recursive in-memory structures
112
+ email: jweissman1986@gmail.com
113
+ executables:
114
+ - exa
115
+ extensions: []
116
+ extra_rdoc_files:
117
+ - ChangeLog.md
118
+ - LICENSE.txt
119
+ - README.md
120
+ files:
121
+ - ".document"
122
+ - ".gitignore"
123
+ - ".rspec"
124
+ - ".yardopts"
125
+ - ChangeLog.md
126
+ - Gemfile
127
+ - LICENSE.txt
128
+ - README.md
129
+ - Rakefile
130
+ - bin/exa
131
+ - exa.gemspec
132
+ - features/.gitkeep
133
+ - features/exa.feature
134
+ - features/step_definitions/.gitkeep
135
+ - features/step_definitions/exa_steps.rb
136
+ - gemspec.yml
137
+ - lib/exa.rb
138
+ - lib/exa/shell.rb
139
+ - lib/exa/tree_node.rb
140
+ - lib/exa/version.rb
141
+ - lib/exa/visitor.rb
142
+ - spec/exa_spec.rb
143
+ - spec/spec_helper.rb
144
+ homepage: https://rubygems.org/gems/exa
145
+ licenses:
146
+ - MIT
147
+ metadata: {}
148
+ post_install_message:
149
+ rdoc_options: []
150
+ require_paths:
151
+ - lib
152
+ required_ruby_version: !ruby/object:Gem::Requirement
153
+ requirements:
154
+ - - ">="
155
+ - !ruby/object:Gem::Version
156
+ version: '0'
157
+ required_rubygems_version: !ruby/object:Gem::Requirement
158
+ requirements:
159
+ - - ">="
160
+ - !ruby/object:Gem::Version
161
+ version: '0'
162
+ requirements: []
163
+ rubyforge_project:
164
+ rubygems_version: 2.5.1
165
+ signing_key:
166
+ specification_version: 4
167
+ summary: recursive in-memory algos and data structures
168
+ test_files: []