nanoc-rust 0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +3 -0
- data/.rspec +3 -0
- data/.rubocop.yml +174 -0
- data/Gemfile +14 -0
- data/Gemfile.lock +63 -0
- data/Rakefile +14 -0
- data/lib/nanoc-rust.rb +160 -0
- data/nanoc-rust.gemspec +23 -0
- data/rust/Cargo.lock +4 -0
- data/rust/Cargo.toml +6 -0
- data/rust/Makefile +15 -0
- data/rust/extconf.rb +3 -0
- data/rust/src/digraph.rs +336 -0
- data/rust/src/nanoc_rust.rs +191 -0
- data/spec/directed_graph_spec.rb +228 -0
- data/spec/spec_helper.rb +12 -0
- data/test.sh +9 -0
- metadata +89 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 8db56be993dae34ec1512f2cd9af7467c7600529
|
4
|
+
data.tar.gz: a0514e0c847376638155b4f8e6e46038ad3902fa
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: cd68879cd68f971b3d6295288f782d0a3392c716797df889159ccc0187ef24efa41effa961cbff1a172969e082dd00d439213dbc24a9a7ac09174c16b6d5e727
|
7
|
+
data.tar.gz: 756b079f316ef77d1b8c4c0abf85ff510f0a649cf7a15feb4947c93b8ebffb08b13fa789456d92096a7867017099bb44517c01696980bf83846ca3c7f35a6d6a
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.rubocop.yml
ADDED
@@ -0,0 +1,174 @@
|
|
1
|
+
# ----- CONFIGURED -----
|
2
|
+
|
3
|
+
AllCops:
|
4
|
+
TargetRubyVersion: 2.3
|
5
|
+
DisplayCopNames: true
|
6
|
+
|
7
|
+
# We use filenames such as “create-site.rb” that translate to method names.
|
8
|
+
FileName:
|
9
|
+
Exclude:
|
10
|
+
- 'lib/nanoc/cli/commands/*.rb'
|
11
|
+
- 'Appraisals'
|
12
|
+
|
13
|
+
# A common pattern in tests is to define anonymous classes in which methods are defined, which trips
|
14
|
+
# up Rubocop’s nested method definition cop.
|
15
|
+
Lint/NestedMethodDefinition:
|
16
|
+
Exclude:
|
17
|
+
- 'test/**/*.rb'
|
18
|
+
- 'spec/**/*.rb'
|
19
|
+
|
20
|
+
# This is used in tests, to verify the effect of state-changing functions.
|
21
|
+
Style/GlobalVars:
|
22
|
+
Exclude:
|
23
|
+
- 'test/**/*.rb'
|
24
|
+
|
25
|
+
Style/TrailingCommaInArguments:
|
26
|
+
EnforcedStyleForMultiline: comma
|
27
|
+
|
28
|
+
Style/TrailingCommaInLiteral:
|
29
|
+
EnforcedStyleForMultiline: comma
|
30
|
+
|
31
|
+
# `rescue nil` is useful in specs where the exception is not important, but
|
32
|
+
# the size effects are.
|
33
|
+
Style/RescueModifier:
|
34
|
+
Exclude:
|
35
|
+
- 'spec/**/*.rb'
|
36
|
+
|
37
|
+
Layout/IndentArray:
|
38
|
+
EnforcedStyle: consistent
|
39
|
+
|
40
|
+
Lint/DuplicateMethods:
|
41
|
+
Exclude:
|
42
|
+
- 'test/data_sources/test_filesystem.rb'
|
43
|
+
- 'spec/spec_helper.rb'
|
44
|
+
|
45
|
+
# This needs to be fixed in Ruby 2.4.
|
46
|
+
Lint/UnifiedInteger:
|
47
|
+
Enabled: false
|
48
|
+
|
49
|
+
Layout/IndentHeredoc:
|
50
|
+
EnforcedStyle: squiggly
|
51
|
+
|
52
|
+
# This breaks RSpec on occasion, e.g. `expect { subject }.not_to change { foo }`,
|
53
|
+
# and generally does not provide useful warnings
|
54
|
+
Lint/AmbiguousBlockAssociation:
|
55
|
+
Enabled: false
|
56
|
+
|
57
|
+
# ----- TO ENABLE LATER -----
|
58
|
+
|
59
|
+
# Valid cops, but fixing the offenses they report is non-trivial.
|
60
|
+
|
61
|
+
RegexpLiteral:
|
62
|
+
Enabled: false
|
63
|
+
|
64
|
+
ClassAndModuleChildren:
|
65
|
+
Enabled: false
|
66
|
+
|
67
|
+
Style/EmptyElse:
|
68
|
+
Enabled: false
|
69
|
+
|
70
|
+
Style/Next:
|
71
|
+
Enabled: false
|
72
|
+
|
73
|
+
|
74
|
+
|
75
|
+
# ----- DISABLED (hard) -----
|
76
|
+
|
77
|
+
# Rubocop trips up on this.
|
78
|
+
Layout/LeadingCommentSpace:
|
79
|
+
Enabled: false
|
80
|
+
|
81
|
+
|
82
|
+
|
83
|
+
# ----- DISABLED (security) -----
|
84
|
+
|
85
|
+
# Nanoc runs offline in a trusted environment, and these security checks are false positives.
|
86
|
+
|
87
|
+
Security/YAMLLoad:
|
88
|
+
Enabled: false
|
89
|
+
|
90
|
+
Security/MarshalLoad:
|
91
|
+
Enabled: false
|
92
|
+
|
93
|
+
Security/Eval:
|
94
|
+
Exclude:
|
95
|
+
- 'test/**/*.rb'
|
96
|
+
- 'spec/**/*.rb'
|
97
|
+
- 'lib/nanoc/base/entities/code_snippet.rb'
|
98
|
+
- 'lib/nanoc/filters/erubi.rb'
|
99
|
+
|
100
|
+
|
101
|
+
# ----- DISABLED (metrics) -----
|
102
|
+
|
103
|
+
# Cops for metrics are disabled because they should not cause tests to fail.
|
104
|
+
|
105
|
+
Metrics/AbcSize:
|
106
|
+
Enabled: false
|
107
|
+
|
108
|
+
Metrics/BlockLength:
|
109
|
+
Enabled: false
|
110
|
+
|
111
|
+
Metrics/BlockNesting:
|
112
|
+
Enabled: false
|
113
|
+
|
114
|
+
Metrics/ClassLength:
|
115
|
+
Enabled: false
|
116
|
+
|
117
|
+
Metrics/CyclomaticComplexity:
|
118
|
+
Enabled: false
|
119
|
+
|
120
|
+
Metrics/LineLength:
|
121
|
+
Enabled: false
|
122
|
+
|
123
|
+
Metrics/MethodLength:
|
124
|
+
Enabled: false
|
125
|
+
|
126
|
+
Metrics/ModuleLength:
|
127
|
+
Enabled: false
|
128
|
+
|
129
|
+
Metrics/ParameterLists:
|
130
|
+
Enabled: false
|
131
|
+
|
132
|
+
Metrics/PerceivedComplexity:
|
133
|
+
Enabled: false
|
134
|
+
|
135
|
+
|
136
|
+
|
137
|
+
# ----- DISABLED (opinionated) -----
|
138
|
+
|
139
|
+
# We should embrace UTF-8, not avoid it. Since the Encoding cop is enabled,
|
140
|
+
# there’s no point in enforcing ASCII comments.
|
141
|
+
AsciiComments:
|
142
|
+
Enabled: false
|
143
|
+
|
144
|
+
# It does not make sense to enforce everything to have documentation.
|
145
|
+
Documentation:
|
146
|
+
Enabled: false
|
147
|
+
|
148
|
+
# Nanoc suppresses exceptions for valid reasons in a few cases.
|
149
|
+
HandleExceptions:
|
150
|
+
Enabled: false
|
151
|
+
|
152
|
+
# if/unless at the end of the line makes it too easy to oversee.
|
153
|
+
IfUnlessModifier:
|
154
|
+
Enabled: false
|
155
|
+
|
156
|
+
# Personal preference is to have decent constructors for exceptions rather than
|
157
|
+
# just a class and a message.
|
158
|
+
RaiseArgs:
|
159
|
+
Enabled: false
|
160
|
+
|
161
|
+
# Personal preference is to use `raise` to signal exceptions (normal control
|
162
|
+
# flow should not use exceptions anyway).
|
163
|
+
SignalException:
|
164
|
+
Enabled: false
|
165
|
+
|
166
|
+
# Some methods that appear to be accessors (return a single value or set a
|
167
|
+
# single value) should still not be considered to be accessors. This is a purely
|
168
|
+
# semantic difference.
|
169
|
+
TrivialAccessors:
|
170
|
+
Enabled: false
|
171
|
+
|
172
|
+
# This does not always semantically make sense.
|
173
|
+
GuardClause:
|
174
|
+
Enabled: false
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
nanoc-rust (0.1)
|
5
|
+
ffi (~> 1.9)
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: https://rubygems.org/
|
9
|
+
specs:
|
10
|
+
ast (2.3.0)
|
11
|
+
diff-lcs (1.3)
|
12
|
+
ffi (1.9.18)
|
13
|
+
fuubar (2.2.0)
|
14
|
+
rspec-core (~> 3.0)
|
15
|
+
ruby-progressbar (~> 1.4)
|
16
|
+
parallel (1.11.2)
|
17
|
+
parser (2.4.0.0)
|
18
|
+
ast (~> 2.2)
|
19
|
+
powerpack (0.1.1)
|
20
|
+
rainbow (2.2.2)
|
21
|
+
rake
|
22
|
+
rake (12.0.0)
|
23
|
+
rspec (3.6.0)
|
24
|
+
rspec-core (~> 3.6.0)
|
25
|
+
rspec-expectations (~> 3.6.0)
|
26
|
+
rspec-mocks (~> 3.6.0)
|
27
|
+
rspec-core (3.6.0)
|
28
|
+
rspec-support (~> 3.6.0)
|
29
|
+
rspec-expectations (3.6.0)
|
30
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
31
|
+
rspec-support (~> 3.6.0)
|
32
|
+
rspec-its (1.2.0)
|
33
|
+
rspec-core (>= 3.0.0)
|
34
|
+
rspec-expectations (>= 3.0.0)
|
35
|
+
rspec-mocks (3.6.0)
|
36
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
37
|
+
rspec-support (~> 3.6.0)
|
38
|
+
rspec-support (3.6.0)
|
39
|
+
rubocop (0.49.1)
|
40
|
+
parallel (~> 1.10)
|
41
|
+
parser (>= 2.3.3.1, < 3.0)
|
42
|
+
powerpack (~> 0.1)
|
43
|
+
rainbow (>= 1.99.1, < 3.0)
|
44
|
+
ruby-progressbar (~> 1.7)
|
45
|
+
unicode-display_width (~> 1.0, >= 1.0.1)
|
46
|
+
ruby-progressbar (1.8.1)
|
47
|
+
unicode-display_width (1.3.0)
|
48
|
+
|
49
|
+
PLATFORMS
|
50
|
+
ruby
|
51
|
+
|
52
|
+
DEPENDENCIES
|
53
|
+
bundler (~> 1.15)
|
54
|
+
fuubar
|
55
|
+
nanoc-rust!
|
56
|
+
rake
|
57
|
+
rspec
|
58
|
+
rspec-its (~> 1.2)
|
59
|
+
rspec-mocks
|
60
|
+
rubocop (~> 0.49)
|
61
|
+
|
62
|
+
BUNDLED WITH
|
63
|
+
1.15.1
|
data/Rakefile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rubocop/rake_task'
|
4
|
+
require 'rspec/core/rake_task'
|
5
|
+
|
6
|
+
RuboCop::RakeTask.new(:rubocop)
|
7
|
+
|
8
|
+
RSpec::Core::RakeTask.new(:spec) do |t|
|
9
|
+
t.verbose = false
|
10
|
+
end
|
11
|
+
|
12
|
+
task test: %i[spec rubocop]
|
13
|
+
|
14
|
+
task default: :test
|
data/lib/nanoc-rust.rb
ADDED
@@ -0,0 +1,160 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'ffi'
|
4
|
+
|
5
|
+
module NanocRust
|
6
|
+
module FFI
|
7
|
+
class Edge < ::FFI::Struct
|
8
|
+
layout :from, :uint32,
|
9
|
+
:to, :uint32,
|
10
|
+
:idx, :uint32
|
11
|
+
|
12
|
+
def to_a(props)
|
13
|
+
[self[:from], self[:to], props[self[:idx]]]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
extend ::FFI::Library
|
18
|
+
|
19
|
+
root = File.expand_path(File.dirname(__FILE__) + '/..')
|
20
|
+
ffi_lib root + '/rust/dist/libnanoc_rust.' + ::FFI::Platform::LIBSUFFIX
|
21
|
+
|
22
|
+
attach_function :drop_string, %i[pointer], :void
|
23
|
+
attach_function :drop_array, %i[pointer uint32], :void
|
24
|
+
|
25
|
+
attach_function :digraph_create, [], :pointer
|
26
|
+
attach_function :digraph_destroy, [:pointer], :void
|
27
|
+
|
28
|
+
attach_function :digraph_inspect, [:pointer], :pointer
|
29
|
+
|
30
|
+
attach_function :digraph_add_vertex, %i[pointer string], :void
|
31
|
+
attach_function :digraph_add_edge, %i[pointer string string uint32], :void
|
32
|
+
attach_function :digraph_delete_edges_to, %i[pointer string], :void
|
33
|
+
|
34
|
+
attach_function :digraph_direct_predecessors_of, %i[pointer string pointer], :pointer
|
35
|
+
attach_function :digraph_predecessors_of, %i[pointer string pointer], :pointer
|
36
|
+
attach_function :digraph_direct_successors_of, %i[pointer string pointer], :pointer
|
37
|
+
attach_function :digraph_successors_of, %i[pointer string pointer], :pointer
|
38
|
+
attach_function :digraph_edges, %i[pointer pointer], :pointer
|
39
|
+
attach_function :digraph_vertices, %i[pointer pointer], :pointer
|
40
|
+
attach_function :digraph_props_for, %i[pointer pointer pointer], :uint32
|
41
|
+
end
|
42
|
+
|
43
|
+
class DirectedGraph
|
44
|
+
def initialize(vertices)
|
45
|
+
raw = NanocRust::FFI.digraph_create
|
46
|
+
@wrapped = ::FFI::AutoPointer.new(raw, NanocRust::FFI.method(:digraph_destroy))
|
47
|
+
|
48
|
+
@_props = {}
|
49
|
+
@_next_prop_idx = 1
|
50
|
+
|
51
|
+
vertices.each { |v| add_vertex(v) }
|
52
|
+
end
|
53
|
+
|
54
|
+
def inspect
|
55
|
+
ptr = NanocRust::FFI.digraph_inspect(@wrapped)
|
56
|
+
s = ptr.read_string
|
57
|
+
::NanocRust::FFI.drop_string(ptr)
|
58
|
+
s
|
59
|
+
end
|
60
|
+
|
61
|
+
# mutating
|
62
|
+
|
63
|
+
def add_vertex(v)
|
64
|
+
NanocRust::FFI.digraph_add_vertex(@wrapped, v)
|
65
|
+
end
|
66
|
+
|
67
|
+
def add_edge(v1, v2, props: nil)
|
68
|
+
@_props[@_next_prop_idx] = props
|
69
|
+
NanocRust::FFI.digraph_add_edge(@wrapped, v1, v2, @_next_prop_idx)
|
70
|
+
@_next_prop_idx += 1
|
71
|
+
end
|
72
|
+
|
73
|
+
def delete_edges_to(v)
|
74
|
+
NanocRust::FFI.digraph_delete_edges_to(@wrapped, v)
|
75
|
+
end
|
76
|
+
|
77
|
+
# querying
|
78
|
+
|
79
|
+
def direct_predecessors_of(v)
|
80
|
+
count_ptr = new_count_ptr
|
81
|
+
ptr = NanocRust::FFI.digraph_direct_predecessors_of(@wrapped, v, count_ptr)
|
82
|
+
ptr_to_array_of_strings(ptr, count_ptr)
|
83
|
+
end
|
84
|
+
|
85
|
+
def predecessors_of(v)
|
86
|
+
count_ptr = new_count_ptr
|
87
|
+
ptr = NanocRust::FFI.digraph_predecessors_of(@wrapped, v, count_ptr)
|
88
|
+
ptr_to_array_of_strings(ptr, count_ptr)
|
89
|
+
end
|
90
|
+
|
91
|
+
def direct_successors_of(v)
|
92
|
+
count_ptr = new_count_ptr
|
93
|
+
ptr = NanocRust::FFI.digraph_direct_successors_of(@wrapped, v, count_ptr)
|
94
|
+
ptr_to_array_of_strings(ptr, count_ptr)
|
95
|
+
end
|
96
|
+
|
97
|
+
def successors_of(v)
|
98
|
+
count_ptr = new_count_ptr
|
99
|
+
ptr = NanocRust::FFI.digraph_successors_of(@wrapped, v, count_ptr)
|
100
|
+
ptr_to_array_of_strings(ptr, count_ptr)
|
101
|
+
end
|
102
|
+
|
103
|
+
def vertices
|
104
|
+
count_ptr = new_count_ptr
|
105
|
+
ptr = NanocRust::FFI.digraph_vertices(@wrapped, count_ptr)
|
106
|
+
ptr_to_array_of_u32_tuples(ptr, count_ptr)
|
107
|
+
end
|
108
|
+
|
109
|
+
def edges
|
110
|
+
count_ptr = new_count_ptr
|
111
|
+
ptr = NanocRust::FFI.digraph_edges(@wrapped, count_ptr)
|
112
|
+
ptr_to_array_of_u32_tuples(ptr, count_ptr)
|
113
|
+
end
|
114
|
+
|
115
|
+
def props_for(v1, v2)
|
116
|
+
idx = NanocRust::FFI.digraph_props_for(@wrapped, v1, v2)
|
117
|
+
@_props[idx]
|
118
|
+
end
|
119
|
+
|
120
|
+
private
|
121
|
+
|
122
|
+
def new_count_ptr
|
123
|
+
::FFI::MemoryPointer.new(:size_t, 1)
|
124
|
+
end
|
125
|
+
|
126
|
+
def ptr_to_array_of_strings(ptr, count_ptr)
|
127
|
+
# Read count
|
128
|
+
count = count_ptr.read_uint32
|
129
|
+
|
130
|
+
# Read strings
|
131
|
+
string_ptrs = ptr.read_array_of_pointer(count)
|
132
|
+
strings = string_ptrs.map(&:read_string)
|
133
|
+
|
134
|
+
# Deallocate
|
135
|
+
string_ptrs.each { |s| NanocRust::FFI.drop_string(s) }
|
136
|
+
NanocRust::FFI.drop_array(ptr, count)
|
137
|
+
|
138
|
+
strings
|
139
|
+
end
|
140
|
+
|
141
|
+
def ptr_to_array_of_u32_tuples(ptr, count_ptr)
|
142
|
+
# Read count
|
143
|
+
count = count_ptr.read_uint32
|
144
|
+
|
145
|
+
# Read edges
|
146
|
+
edges_ptr = ::FFI::Pointer.new(NanocRust::FFI::Edge, ptr)
|
147
|
+
edges = (0...count).map { |i| NanocRust::FFI::Edge.new(edges_ptr[i]).to_a(@_props) }
|
148
|
+
|
149
|
+
# Deallocate
|
150
|
+
NanocRust::FFI.drop_array(ptr, count)
|
151
|
+
|
152
|
+
edges
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def self.activate!
|
157
|
+
Nanoc::Int.send(:remove_const, 'DirectedGraph') if Nanoc::Int.const_defined?('DirectedGraph')
|
158
|
+
Nanoc::Int.const_set('DirectedGraph', NanocRust::DirectedGraph)
|
159
|
+
end
|
160
|
+
end
|
data/nanoc-rust.gemspec
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = 'nanoc-rust'
|
5
|
+
s.version = '0.1'
|
6
|
+
s.homepage = 'http://nanoc.ws/'
|
7
|
+
s.summary = 'Faster bits for Nanoc'
|
8
|
+
s.description = 'Contains parts of Nanoc rewritten in Rust for speed'
|
9
|
+
|
10
|
+
s.author = 'Denis Defreyne'
|
11
|
+
s.email = 'denis@stoneship.org'
|
12
|
+
s.license = 'MIT'
|
13
|
+
|
14
|
+
s.files = `git ls-files -z`.split("\x0")
|
15
|
+
s.require_paths = ['lib']
|
16
|
+
|
17
|
+
s.required_ruby_version = '>= 2.3.0'
|
18
|
+
|
19
|
+
s.extensions = Dir['rust/extconf.rb']
|
20
|
+
|
21
|
+
s.add_runtime_dependency('ffi', '~> 1.9')
|
22
|
+
s.add_development_dependency('bundler', '~> 1.15')
|
23
|
+
end
|
data/rust/Cargo.lock
ADDED
data/rust/Cargo.toml
ADDED
data/rust/Makefile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
default: build
|
2
|
+
|
3
|
+
test:
|
4
|
+
cargo test
|
5
|
+
|
6
|
+
build:
|
7
|
+
cargo build --release
|
8
|
+
mkdir -p dist
|
9
|
+
[ -f target/release/libnanoc_rust.dylib ] && cp target/release/libnanoc_rust.dylib dist || true
|
10
|
+
[ -f target/release/libnanoc_rust.so ] && cp target/release/libnanoc_rust.so dist || true
|
11
|
+
|
12
|
+
clean:
|
13
|
+
rm -rf target dist
|
14
|
+
|
15
|
+
install:
|
data/rust/extconf.rb
ADDED
data/rust/src/digraph.rs
ADDED
@@ -0,0 +1,336 @@
|
|
1
|
+
use std::collections::HashMap;
|
2
|
+
use std::collections::hash_set;
|
3
|
+
use std::collections::HashSet;
|
4
|
+
use std::hash::Hash;
|
5
|
+
use std::fmt::Debug;
|
6
|
+
|
7
|
+
pub struct DirectedGraph<T: Eq + Hash> {
|
8
|
+
indices: HashMap<T, u32>,
|
9
|
+
next_index: u32,
|
10
|
+
|
11
|
+
vertices: HashSet<T>,
|
12
|
+
|
13
|
+
froms: HashMap<T, HashSet<T>>,
|
14
|
+
tos: HashMap<T, HashSet<T>>,
|
15
|
+
|
16
|
+
edge_props: HashMap<(T, T), u32>,
|
17
|
+
|
18
|
+
empty: HashSet<T>,
|
19
|
+
}
|
20
|
+
|
21
|
+
type VertexIter<'a, T> = hash_set::Iter<'a, T>;
|
22
|
+
|
23
|
+
// TODO: Move to nanoc_rust.rs
|
24
|
+
#[repr(C)]
|
25
|
+
pub struct Edge {
|
26
|
+
from: u32,
|
27
|
+
to: u32,
|
28
|
+
props: u32,
|
29
|
+
}
|
30
|
+
|
31
|
+
impl<T: Eq + Hash + Clone + Debug> DirectedGraph<T> {
|
32
|
+
pub fn new() -> Self {
|
33
|
+
DirectedGraph {
|
34
|
+
indices: HashMap::new(),
|
35
|
+
next_index: 0,
|
36
|
+
|
37
|
+
froms: HashMap::new(),
|
38
|
+
tos: HashMap::new(),
|
39
|
+
|
40
|
+
edge_props: HashMap::new(),
|
41
|
+
|
42
|
+
vertices: HashSet::new(),
|
43
|
+
|
44
|
+
empty: HashSet::new(),
|
45
|
+
}
|
46
|
+
}
|
47
|
+
|
48
|
+
// Modifying
|
49
|
+
|
50
|
+
pub fn add_vertex(&mut self, v: T) {
|
51
|
+
self.vertices.insert(v.clone());
|
52
|
+
self.indices.entry(v).or_insert(self.next_index);
|
53
|
+
self.next_index += 1;
|
54
|
+
}
|
55
|
+
|
56
|
+
pub fn add_edge(&mut self, v1: T, v2: T, props: u32) {
|
57
|
+
self.add_vertex(v1.clone());
|
58
|
+
self.add_vertex(v2.clone());
|
59
|
+
|
60
|
+
self.edge_props.insert((v1.clone(), v2.clone()), props);
|
61
|
+
|
62
|
+
self.tos.entry(v2.clone()).or_insert(HashSet::new()).insert(v1.clone());
|
63
|
+
self.froms.entry(v1).or_insert(HashSet::new()).insert(v2);
|
64
|
+
}
|
65
|
+
|
66
|
+
pub fn delete_edges_to(&mut self, v: T) {
|
67
|
+
if let Some(mut tos) = self.tos.get_mut(&v) {
|
68
|
+
for to in tos.iter() {
|
69
|
+
self.edge_props.remove(&(to.clone(), v.clone()));
|
70
|
+
}
|
71
|
+
|
72
|
+
tos.clear();
|
73
|
+
}
|
74
|
+
|
75
|
+
for (_, mut froms) in &mut self.froms {
|
76
|
+
froms.remove(&v);
|
77
|
+
}
|
78
|
+
}
|
79
|
+
|
80
|
+
// Querying
|
81
|
+
|
82
|
+
pub fn props_for(&self, v1: T, v2: T) -> u32 {
|
83
|
+
let p: Option<&u32> = self.edge_props.get(&(v1, v2));
|
84
|
+
if let Some(v) = p.cloned() {
|
85
|
+
v
|
86
|
+
} else {
|
87
|
+
0
|
88
|
+
}
|
89
|
+
}
|
90
|
+
|
91
|
+
pub fn vertices_iter(&self) -> VertexIter<T> {
|
92
|
+
self.vertices.iter()
|
93
|
+
}
|
94
|
+
|
95
|
+
pub fn edges(&self) -> Vec<Edge> {
|
96
|
+
let mut res: Vec<Edge> = Vec::new();
|
97
|
+
|
98
|
+
let vertices = self.vertices_iter().collect::<Vec<_>>();
|
99
|
+
for from in vertices {
|
100
|
+
for to in self.direct_successors_of(from.clone()) {
|
101
|
+
res.push(Edge {
|
102
|
+
from: *self.indices.get(&from).unwrap(),
|
103
|
+
to: *self.indices.get(&to).unwrap(),
|
104
|
+
props: self.props_for(from.clone(), to.clone()),
|
105
|
+
});
|
106
|
+
}
|
107
|
+
}
|
108
|
+
|
109
|
+
res
|
110
|
+
}
|
111
|
+
|
112
|
+
pub fn direct_successors_of(&self, v: T) -> VertexIter<T> {
|
113
|
+
let froms = self.froms.get(&v);
|
114
|
+
if let Some(froms) = froms {
|
115
|
+
froms.iter()
|
116
|
+
} else {
|
117
|
+
self.empty.iter()
|
118
|
+
}
|
119
|
+
}
|
120
|
+
|
121
|
+
pub fn direct_predecessors_of(&self, v: T) -> VertexIter<T> {
|
122
|
+
let tos = self.tos.get(&v);
|
123
|
+
if let Some(tos) = tos {
|
124
|
+
tos.iter()
|
125
|
+
} else {
|
126
|
+
self.empty.iter()
|
127
|
+
}
|
128
|
+
}
|
129
|
+
|
130
|
+
pub fn successors_of(&self, v: T) -> Vec<T> {
|
131
|
+
let mut all_vertices: HashSet<T> = HashSet::new();
|
132
|
+
|
133
|
+
let mut processed_vertices: HashSet<T> = HashSet::new();
|
134
|
+
let mut unprocessed_vertices: Vec<T> = vec![v];
|
135
|
+
|
136
|
+
while let Some(vertex) = unprocessed_vertices.pop() {
|
137
|
+
// Get next unprocessed vertex
|
138
|
+
if processed_vertices.contains(&vertex) {
|
139
|
+
continue;
|
140
|
+
}
|
141
|
+
processed_vertices.insert(vertex.clone());
|
142
|
+
|
143
|
+
// Add successors of this vertex
|
144
|
+
for v in self.direct_successors_of(vertex) {
|
145
|
+
all_vertices.insert(v.clone());
|
146
|
+
unprocessed_vertices.push(v.clone());
|
147
|
+
}
|
148
|
+
}
|
149
|
+
|
150
|
+
return all_vertices.iter().cloned().collect::<Vec<_>>();
|
151
|
+
}
|
152
|
+
|
153
|
+
pub fn predecessors_of(&self, v: T) -> Vec<T> {
|
154
|
+
let mut all_vertices: HashSet<T> = HashSet::new();
|
155
|
+
|
156
|
+
let mut processed_vertices: HashSet<T> = HashSet::new();
|
157
|
+
let mut unprocessed_vertices: Vec<T> = vec![v];
|
158
|
+
|
159
|
+
while let Some(vertex) = unprocessed_vertices.pop() {
|
160
|
+
// Get next unprocessed vertex
|
161
|
+
if processed_vertices.contains(&vertex) {
|
162
|
+
continue;
|
163
|
+
}
|
164
|
+
processed_vertices.insert(vertex.clone());
|
165
|
+
|
166
|
+
// Add predecessors of this vertex
|
167
|
+
for v in self.direct_predecessors_of(vertex) {
|
168
|
+
all_vertices.insert(v.clone());
|
169
|
+
unprocessed_vertices.push(v.clone());
|
170
|
+
}
|
171
|
+
}
|
172
|
+
|
173
|
+
return all_vertices.iter().cloned().collect::<Vec<_>>();
|
174
|
+
}
|
175
|
+
}
|
176
|
+
|
177
|
+
#[cfg(test)]
|
178
|
+
mod tests {
|
179
|
+
use super::*;
|
180
|
+
use std::fmt::Debug;
|
181
|
+
use std::iter::IntoIterator;
|
182
|
+
|
183
|
+
macro_rules! assert_iters_eq {
|
184
|
+
( $a:expr, $b:expr ) => {{
|
185
|
+
match ($a, $b) {
|
186
|
+
(a, b) => {
|
187
|
+
let mut v1 = a.collect::<Vec<_>>();
|
188
|
+
let mut v2 = b.collect::<Vec<_>>();
|
189
|
+
|
190
|
+
v1.sort();
|
191
|
+
v2.sort();
|
192
|
+
|
193
|
+
assert_eq!(v1, v2);
|
194
|
+
}
|
195
|
+
}
|
196
|
+
}}
|
197
|
+
}
|
198
|
+
|
199
|
+
fn assert_empty<I, T: Debug + Ord>(c: I)
|
200
|
+
where
|
201
|
+
I: IntoIterator<Item = T>,
|
202
|
+
{
|
203
|
+
assert_eq!(0, c.into_iter().collect::<Vec<_>>().len());
|
204
|
+
}
|
205
|
+
|
206
|
+
#[test]
|
207
|
+
fn new_digraph_is_empty() {
|
208
|
+
let dg: DirectedGraph<i32> = DirectedGraph::new();
|
209
|
+
|
210
|
+
assert_empty(dg.vertices_iter());
|
211
|
+
|
212
|
+
assert_empty(dg.direct_successors_of(0));
|
213
|
+
assert_empty(dg.direct_successors_of(1));
|
214
|
+
assert_empty(dg.direct_successors_of(2));
|
215
|
+
assert_empty(dg.direct_successors_of(3));
|
216
|
+
|
217
|
+
assert_empty(dg.direct_predecessors_of(0));
|
218
|
+
assert_empty(dg.direct_predecessors_of(1));
|
219
|
+
assert_empty(dg.direct_predecessors_of(2));
|
220
|
+
assert_empty(dg.direct_predecessors_of(3));
|
221
|
+
}
|
222
|
+
|
223
|
+
#[test]
|
224
|
+
fn add_new_vertex() {
|
225
|
+
let mut dg: DirectedGraph<i32> = DirectedGraph::new();
|
226
|
+
dg.add_vertex(1);
|
227
|
+
|
228
|
+
assert_eq!(1, dg.vertices_iter().len());
|
229
|
+
assert_iters_eq!(vec![1].iter(), dg.vertices_iter());
|
230
|
+
|
231
|
+
assert_empty(dg.direct_successors_of(0));
|
232
|
+
assert_empty(dg.direct_successors_of(1));
|
233
|
+
assert_empty(dg.direct_successors_of(2));
|
234
|
+
assert_empty(dg.direct_successors_of(3));
|
235
|
+
|
236
|
+
assert_empty(dg.direct_predecessors_of(0));
|
237
|
+
assert_empty(dg.direct_predecessors_of(1));
|
238
|
+
assert_empty(dg.direct_predecessors_of(2));
|
239
|
+
assert_empty(dg.direct_predecessors_of(3));
|
240
|
+
}
|
241
|
+
|
242
|
+
#[test]
|
243
|
+
fn add_existing_vertex() {
|
244
|
+
let mut dg: DirectedGraph<i32> = DirectedGraph::new();
|
245
|
+
dg.add_vertex(1);
|
246
|
+
dg.add_vertex(1);
|
247
|
+
|
248
|
+
assert_eq!(1, dg.vertices_iter().len());
|
249
|
+
assert_iters_eq!(vec![1].iter(), dg.vertices_iter());
|
250
|
+
|
251
|
+
assert_empty(dg.direct_successors_of(0));
|
252
|
+
assert_empty(dg.direct_successors_of(1));
|
253
|
+
assert_empty(dg.direct_successors_of(2));
|
254
|
+
assert_empty(dg.direct_successors_of(3));
|
255
|
+
|
256
|
+
assert_empty(dg.direct_predecessors_of(0));
|
257
|
+
assert_empty(dg.direct_predecessors_of(1));
|
258
|
+
assert_empty(dg.direct_predecessors_of(2));
|
259
|
+
assert_empty(dg.direct_predecessors_of(3));
|
260
|
+
}
|
261
|
+
|
262
|
+
#[test]
|
263
|
+
fn add_multiple_vertices() {
|
264
|
+
let mut dg: DirectedGraph<i32> = DirectedGraph::new();
|
265
|
+
dg.add_vertex(1);
|
266
|
+
dg.add_vertex(3);
|
267
|
+
|
268
|
+
assert_eq!(2, dg.vertices_iter().len());
|
269
|
+
assert_iters_eq!(vec![1, 3].iter(), dg.vertices_iter());
|
270
|
+
|
271
|
+
assert_empty(dg.direct_successors_of(0));
|
272
|
+
assert_empty(dg.direct_successors_of(1));
|
273
|
+
assert_empty(dg.direct_successors_of(2));
|
274
|
+
assert_empty(dg.direct_successors_of(3));
|
275
|
+
|
276
|
+
assert_empty(dg.direct_predecessors_of(0));
|
277
|
+
assert_empty(dg.direct_predecessors_of(1));
|
278
|
+
assert_empty(dg.direct_predecessors_of(2));
|
279
|
+
assert_empty(dg.direct_predecessors_of(3));
|
280
|
+
}
|
281
|
+
|
282
|
+
#[test]
|
283
|
+
fn add_edges_has_vertices() {
|
284
|
+
let mut dg: DirectedGraph<i32> = DirectedGraph::new();
|
285
|
+
dg.add_edge(1, 2);
|
286
|
+
dg.add_edge(1, 3);
|
287
|
+
|
288
|
+
assert_eq!(3, dg.vertices_iter().len());
|
289
|
+
assert_iters_eq!(vec![1, 2, 3].iter(), dg.vertices_iter());
|
290
|
+
}
|
291
|
+
|
292
|
+
#[test]
|
293
|
+
fn add_edges_has_successors_and_predecessors() {
|
294
|
+
let mut dg: DirectedGraph<i32> = DirectedGraph::new();
|
295
|
+
dg.add_edge(1, 2);
|
296
|
+
dg.add_edge(1, 3);
|
297
|
+
|
298
|
+
// 0
|
299
|
+
assert_empty(dg.direct_successors_of(0));
|
300
|
+
assert_empty(dg.direct_predecessors_of(0));
|
301
|
+
|
302
|
+
// 1
|
303
|
+
assert_iters_eq!(vec![2, 3].iter(), dg.direct_successors_of(1));
|
304
|
+
assert_empty(dg.direct_predecessors_of(1));
|
305
|
+
|
306
|
+
// 2
|
307
|
+
assert_empty(dg.direct_successors_of(2));
|
308
|
+
assert_iters_eq!(vec![1].iter(), dg.direct_predecessors_of(2));
|
309
|
+
|
310
|
+
// 3
|
311
|
+
assert_empty(dg.direct_successors_of(3));
|
312
|
+
assert_iters_eq!(vec![1].iter(), dg.direct_predecessors_of(3));
|
313
|
+
}
|
314
|
+
|
315
|
+
#[test]
|
316
|
+
fn delete_edges_to() {
|
317
|
+
let mut dg: DirectedGraph<i32> = DirectedGraph::new();
|
318
|
+
dg.add_edge(0, 1);
|
319
|
+
dg.add_edge(0, 2);
|
320
|
+
dg.add_edge(0, 3);
|
321
|
+
dg.add_edge(1, 2);
|
322
|
+
dg.add_edge(1, 3);
|
323
|
+
dg.add_edge(2, 3);
|
324
|
+
dg.delete_edges_to(1);
|
325
|
+
|
326
|
+
assert_iters_eq!(vec![2, 3].iter(), dg.direct_successors_of(0));
|
327
|
+
assert_iters_eq!(vec![2, 3].iter(), dg.direct_successors_of(1));
|
328
|
+
assert_iters_eq!(vec![3].iter(), dg.direct_successors_of(2));
|
329
|
+
assert_empty(dg.direct_successors_of(3));
|
330
|
+
|
331
|
+
assert_empty(dg.direct_predecessors_of(0));
|
332
|
+
assert_empty(dg.direct_predecessors_of(1));
|
333
|
+
assert_iters_eq!(vec![0, 1].iter(), dg.direct_predecessors_of(2));
|
334
|
+
assert_iters_eq!(vec![0, 1, 2].iter(), dg.direct_predecessors_of(3));
|
335
|
+
}
|
336
|
+
}
|
@@ -0,0 +1,191 @@
|
|
1
|
+
#![feature(libc)]
|
2
|
+
|
3
|
+
extern crate libc;
|
4
|
+
|
5
|
+
mod digraph;
|
6
|
+
|
7
|
+
pub mod ffi {
|
8
|
+
use digraph::{DirectedGraph, Edge};
|
9
|
+
|
10
|
+
use libc::c_char;
|
11
|
+
use std::ffi::{CStr, CString};
|
12
|
+
use std::fmt::{Debug, Error, Formatter};
|
13
|
+
use std::mem;
|
14
|
+
use std::ptr;
|
15
|
+
|
16
|
+
type R = *const c_char;
|
17
|
+
type T = Vec<u8>;
|
18
|
+
type D = DirectedGraph<T>;
|
19
|
+
|
20
|
+
impl Debug for D {
|
21
|
+
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
|
22
|
+
write!(f, "Nanoc::Int::DirectedGraph(")?;
|
23
|
+
|
24
|
+
let mut idx = 0;
|
25
|
+
let mut vertices = self.vertices_iter().collect::<Vec<_>>();
|
26
|
+
vertices.sort();
|
27
|
+
for from in vertices {
|
28
|
+
for to in self.direct_successors_of(from.clone()) {
|
29
|
+
if idx > 0 {
|
30
|
+
write!(f, ", ")?;
|
31
|
+
}
|
32
|
+
|
33
|
+
write!(
|
34
|
+
f,
|
35
|
+
"{:?} -> {:?} props=nil",
|
36
|
+
String::from_utf8_lossy(&*from.clone()),
|
37
|
+
String::from_utf8_lossy(&*to.clone()),
|
38
|
+
)?;
|
39
|
+
|
40
|
+
idx += 1;
|
41
|
+
}
|
42
|
+
}
|
43
|
+
|
44
|
+
write!(f, ")")?;
|
45
|
+
Ok(())
|
46
|
+
}
|
47
|
+
}
|
48
|
+
|
49
|
+
// Converting raw <-> actual
|
50
|
+
|
51
|
+
fn r2t(r: R) -> T {
|
52
|
+
unsafe {
|
53
|
+
if r.is_null() {
|
54
|
+
vec![]
|
55
|
+
} else {
|
56
|
+
CStr::from_ptr(r).to_bytes().to_vec()
|
57
|
+
}
|
58
|
+
}
|
59
|
+
}
|
60
|
+
|
61
|
+
fn t2r(t: T) -> R {
|
62
|
+
unsafe {
|
63
|
+
if t.is_empty() {
|
64
|
+
ptr::null()
|
65
|
+
} else {
|
66
|
+
let cs = CString::from_vec_unchecked(t);
|
67
|
+
let ptr = cs.as_ptr();
|
68
|
+
mem::forget(cs);
|
69
|
+
ptr
|
70
|
+
}
|
71
|
+
}
|
72
|
+
}
|
73
|
+
|
74
|
+
// drop
|
75
|
+
|
76
|
+
#[no_mangle]
|
77
|
+
pub extern "C" fn drop_string(ptr: *mut c_char) {
|
78
|
+
unsafe { CString::from_raw(ptr); }
|
79
|
+
}
|
80
|
+
|
81
|
+
#[no_mangle]
|
82
|
+
pub extern "C" fn drop_array(ptr: *mut *mut c_char, length: usize) {
|
83
|
+
unsafe { Vec::<*mut c_char>::from_raw_parts(ptr, length, length); };
|
84
|
+
}
|
85
|
+
|
86
|
+
// create+destroy
|
87
|
+
|
88
|
+
#[no_mangle]
|
89
|
+
pub extern "C" fn digraph_create() -> Box<D> {
|
90
|
+
Box::new(DirectedGraph::new())
|
91
|
+
}
|
92
|
+
|
93
|
+
#[no_mangle]
|
94
|
+
pub extern "C" fn digraph_destroy(dg: &mut D) {
|
95
|
+
mem::drop(dg);
|
96
|
+
}
|
97
|
+
|
98
|
+
// query
|
99
|
+
|
100
|
+
#[no_mangle]
|
101
|
+
pub extern "C" fn digraph_inspect(dg: &D) -> R {
|
102
|
+
let s = format!("{:?}", dg);
|
103
|
+
CString::new(s).unwrap().into_raw()
|
104
|
+
}
|
105
|
+
|
106
|
+
#[no_mangle]
|
107
|
+
pub extern "C" fn digraph_props_for(dg: &D, v1: R, v2: R) -> u32 {
|
108
|
+
dg.props_for(r2t(v1), r2t(v2))
|
109
|
+
}
|
110
|
+
|
111
|
+
#[no_mangle]
|
112
|
+
pub extern "C" fn digraph_edges(dg: &D, out_count: *mut usize) -> *const Edge {
|
113
|
+
let mut vec = dg.edges();
|
114
|
+
vec.shrink_to_fit();
|
115
|
+
let ptr = vec.as_ptr();
|
116
|
+
unsafe { ptr::write(out_count, vec.len()); };
|
117
|
+
mem::forget(vec);
|
118
|
+
ptr
|
119
|
+
}
|
120
|
+
|
121
|
+
#[no_mangle]
|
122
|
+
pub extern "C" fn digraph_vertices(dg: &D, out_count: *mut usize) -> *const R {
|
123
|
+
let mut vec: Vec<R> = dg.vertices_iter().cloned().map(|a| t2r(a)).collect();
|
124
|
+
vec.shrink_to_fit();
|
125
|
+
let ptr = vec.as_ptr();
|
126
|
+
unsafe { ptr::write(out_count, vec.len()); };
|
127
|
+
mem::forget(vec);
|
128
|
+
ptr
|
129
|
+
}
|
130
|
+
|
131
|
+
#[no_mangle]
|
132
|
+
pub extern "C" fn digraph_direct_predecessors_of(dg: &D, v: R, out_count: *mut usize) -> *const R {
|
133
|
+
let mut vec: Vec<R> = dg.direct_predecessors_of(r2t(v)).cloned().map(|a| t2r(a)).collect();
|
134
|
+
vec.shrink_to_fit();
|
135
|
+
|
136
|
+
let ptr = vec.as_ptr();
|
137
|
+
unsafe { ptr::write(out_count, vec.len()); };
|
138
|
+
mem::forget(vec);
|
139
|
+
ptr
|
140
|
+
}
|
141
|
+
|
142
|
+
#[no_mangle]
|
143
|
+
pub extern "C" fn digraph_predecessors_of(dg: &D, v: R, out_count: *mut usize) -> *const R {
|
144
|
+
let mut vec: Vec<R> = dg.predecessors_of(r2t(v)).iter().cloned().map(|a| t2r(a)).collect();
|
145
|
+
vec.shrink_to_fit();
|
146
|
+
|
147
|
+
let ptr = vec.as_ptr();
|
148
|
+
unsafe { ptr::write(out_count, vec.len()); };
|
149
|
+
mem::forget(vec);
|
150
|
+
ptr
|
151
|
+
}
|
152
|
+
|
153
|
+
#[no_mangle]
|
154
|
+
pub extern "C" fn digraph_direct_successors_of(dg: &D, v: R, out_count: *mut usize) -> *const R {
|
155
|
+
let mut vec: Vec<R> = dg.direct_successors_of(r2t(v)).cloned().map(|a| t2r(a)).collect();
|
156
|
+
vec.shrink_to_fit();
|
157
|
+
|
158
|
+
let ptr = vec.as_ptr();
|
159
|
+
unsafe { ptr::write(out_count, vec.len()); };
|
160
|
+
mem::forget(vec);
|
161
|
+
ptr
|
162
|
+
}
|
163
|
+
|
164
|
+
#[no_mangle]
|
165
|
+
pub extern "C" fn digraph_successors_of(dg: &D, v: R, out_count: *mut usize) -> *const R {
|
166
|
+
let mut vec: Vec<R> = dg.successors_of(r2t(v)).iter().cloned().map(|a| t2r(a)).collect();
|
167
|
+
vec.shrink_to_fit();
|
168
|
+
|
169
|
+
let ptr = vec.as_ptr();
|
170
|
+
unsafe { ptr::write(out_count, vec.len()); };
|
171
|
+
mem::forget(vec);
|
172
|
+
ptr
|
173
|
+
}
|
174
|
+
|
175
|
+
// mutate
|
176
|
+
|
177
|
+
#[no_mangle]
|
178
|
+
pub extern "C" fn digraph_add_vertex(dg: &mut D, v: R) {
|
179
|
+
dg.add_vertex(r2t(v))
|
180
|
+
}
|
181
|
+
|
182
|
+
#[no_mangle]
|
183
|
+
pub extern "C" fn digraph_add_edge(dg: &mut D, v1: R, v2: R, p: u32) {
|
184
|
+
dg.add_edge(r2t(v1), r2t(v2), p)
|
185
|
+
}
|
186
|
+
|
187
|
+
#[no_mangle]
|
188
|
+
pub extern "C" fn digraph_delete_edges_to(dg: &mut D, v: R) {
|
189
|
+
dg.delete_edges_to(r2t(v));
|
190
|
+
}
|
191
|
+
}
|
@@ -0,0 +1,228 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
describe NanocRust::DirectedGraph do
|
4
|
+
subject(:graph) { described_class.new(%w[1 2 3]) }
|
5
|
+
|
6
|
+
describe '#edges' do
|
7
|
+
subject { graph.edges }
|
8
|
+
|
9
|
+
context 'empty graph' do
|
10
|
+
it { is_expected.to be_empty }
|
11
|
+
end
|
12
|
+
|
13
|
+
context 'graph with vertices, but no edges' do
|
14
|
+
before do
|
15
|
+
graph.add_vertex('1')
|
16
|
+
graph.add_vertex('2')
|
17
|
+
end
|
18
|
+
|
19
|
+
it { is_expected.to be_empty }
|
20
|
+
end
|
21
|
+
|
22
|
+
context 'graph with edges from previously added vertices' do
|
23
|
+
before do
|
24
|
+
graph.add_vertex('1')
|
25
|
+
graph.add_vertex('2')
|
26
|
+
graph.add_vertex('3')
|
27
|
+
|
28
|
+
graph.add_edge('1', '2')
|
29
|
+
graph.add_edge('1', '3')
|
30
|
+
end
|
31
|
+
|
32
|
+
it { is_expected.to match_array([[0, 1, nil], [0, 2, nil]]) }
|
33
|
+
end
|
34
|
+
|
35
|
+
context 'graph with edges from new vertices' do
|
36
|
+
before do
|
37
|
+
graph.add_edge('1', '2')
|
38
|
+
graph.add_edge('1', '3')
|
39
|
+
end
|
40
|
+
|
41
|
+
it { is_expected.to match_array([[0, 1, nil], [0, 2, nil]]) }
|
42
|
+
end
|
43
|
+
|
44
|
+
context 'graph with edge props' do
|
45
|
+
before do
|
46
|
+
graph.add_edge('1', '2', props: { name: 'Mr. C' })
|
47
|
+
graph.add_edge('1', '3', props: { name: 'Cooper' })
|
48
|
+
end
|
49
|
+
|
50
|
+
it { is_expected.to match_array([[0, 1, { name: 'Mr. C' }], [0, 2, { name: 'Cooper' }]]) }
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
describe '#props_for' do
|
55
|
+
subject { graph.props_for('1', '2') }
|
56
|
+
|
57
|
+
context 'no edge' do
|
58
|
+
it { is_expected.to be_nil }
|
59
|
+
end
|
60
|
+
|
61
|
+
context 'edge, but no props' do
|
62
|
+
before { graph.add_edge('1', '2') }
|
63
|
+
it { is_expected.to be_nil }
|
64
|
+
end
|
65
|
+
|
66
|
+
context 'edge with props' do
|
67
|
+
before { graph.add_edge('1', '2', props: { name: 'Mr. C' }) }
|
68
|
+
it { is_expected.to eq(name: 'Mr. C') }
|
69
|
+
|
70
|
+
context 'deleted edge (#delete_edges_to)' do
|
71
|
+
before { graph.delete_edges_to('2') }
|
72
|
+
it { is_expected.to be_nil }
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
describe '#direct_predecessors_of' do
|
78
|
+
subject { graph.direct_predecessors_of('2') }
|
79
|
+
|
80
|
+
context 'no edges' do
|
81
|
+
it { is_expected.to be_empty }
|
82
|
+
end
|
83
|
+
|
84
|
+
context 'one edge to' do
|
85
|
+
before { graph.add_edge('1', '2') }
|
86
|
+
it { is_expected.to eq(['1']) }
|
87
|
+
end
|
88
|
+
|
89
|
+
context 'two edges to' do
|
90
|
+
before do
|
91
|
+
graph.add_edge('1', '2')
|
92
|
+
graph.add_edge('3', '2')
|
93
|
+
end
|
94
|
+
|
95
|
+
it { is_expected.to match_array(%w[1 3]) }
|
96
|
+
end
|
97
|
+
|
98
|
+
context 'edge from' do
|
99
|
+
before { graph.add_edge('2', '3') }
|
100
|
+
it { is_expected.to be_empty }
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
describe '#direct_successors_of' do
|
105
|
+
subject { graph.direct_successors_of('2') }
|
106
|
+
|
107
|
+
context 'no edges' do
|
108
|
+
it { is_expected.to be_empty }
|
109
|
+
end
|
110
|
+
|
111
|
+
context 'one edge to' do
|
112
|
+
before { graph.add_edge('1', '2') }
|
113
|
+
it { is_expected.to be_empty }
|
114
|
+
end
|
115
|
+
|
116
|
+
context 'one edge from' do
|
117
|
+
before { graph.add_edge('2', '3') }
|
118
|
+
it { is_expected.to eq(['3']) }
|
119
|
+
end
|
120
|
+
|
121
|
+
context 'two edges from' do
|
122
|
+
before do
|
123
|
+
graph.add_edge('2', '1')
|
124
|
+
graph.add_edge('2', '3')
|
125
|
+
end
|
126
|
+
|
127
|
+
it { is_expected.to match_array(%w[1 3]) }
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
describe '#predecessors_of' do
|
132
|
+
subject { graph.predecessors_of('2') }
|
133
|
+
|
134
|
+
context 'no predecessors' do
|
135
|
+
before do
|
136
|
+
graph.add_edge('2', '3')
|
137
|
+
end
|
138
|
+
|
139
|
+
it { is_expected.to be_empty }
|
140
|
+
end
|
141
|
+
|
142
|
+
context 'direct predecessor' do
|
143
|
+
before do
|
144
|
+
graph.add_edge('2', '3')
|
145
|
+
graph.add_edge('1', '2')
|
146
|
+
end
|
147
|
+
|
148
|
+
context 'no indirect predecessors' do
|
149
|
+
it { is_expected.to match_array(['1']) }
|
150
|
+
end
|
151
|
+
|
152
|
+
context 'indirect predecessors' do
|
153
|
+
before { graph.add_edge('3', '1') }
|
154
|
+
it { is_expected.to match_array(%w[1 2 3]) }
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
describe '#successors_of' do
|
160
|
+
subject { graph.successors_of('2') }
|
161
|
+
|
162
|
+
context 'no successors' do
|
163
|
+
before do
|
164
|
+
graph.add_edge('1', '2')
|
165
|
+
end
|
166
|
+
|
167
|
+
it { is_expected.to be_empty }
|
168
|
+
end
|
169
|
+
|
170
|
+
context 'direct predecessor' do
|
171
|
+
before do
|
172
|
+
graph.add_edge('1', '2')
|
173
|
+
graph.add_edge('2', '3')
|
174
|
+
end
|
175
|
+
|
176
|
+
context 'no indirect successors' do
|
177
|
+
it { is_expected.to match_array(['3']) }
|
178
|
+
end
|
179
|
+
|
180
|
+
context 'indirect successors' do
|
181
|
+
before { graph.add_edge('3', '1') }
|
182
|
+
it { is_expected.to match_array(%w[1 2 3]) }
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
describe '#inspect' do
|
188
|
+
subject { graph.inspect }
|
189
|
+
|
190
|
+
context 'empty graph' do
|
191
|
+
it { is_expected.to eq('Nanoc::Int::DirectedGraph()') }
|
192
|
+
end
|
193
|
+
|
194
|
+
context 'one edge, no props' do
|
195
|
+
before do
|
196
|
+
graph.add_edge('1', '2')
|
197
|
+
end
|
198
|
+
|
199
|
+
it { is_expected.to eq('Nanoc::Int::DirectedGraph("1" -> "2" props=nil)') }
|
200
|
+
end
|
201
|
+
|
202
|
+
context 'two edges, no props' do
|
203
|
+
before do
|
204
|
+
graph.add_edge('1', '2')
|
205
|
+
graph.add_edge('2', '3')
|
206
|
+
end
|
207
|
+
|
208
|
+
it { is_expected.to eq('Nanoc::Int::DirectedGraph("1" -> "2" props=nil, "2" -> "3" props=nil)') }
|
209
|
+
end
|
210
|
+
|
211
|
+
context 'one edge, props' do
|
212
|
+
before do
|
213
|
+
graph.add_edge('1', '2', props: 'giraffe')
|
214
|
+
end
|
215
|
+
|
216
|
+
it { is_expected.to eq('Nanoc::Int::DirectedGraph("1" -> "2" props="giraffe")') }
|
217
|
+
end
|
218
|
+
|
219
|
+
context 'two edges, props' do
|
220
|
+
before do
|
221
|
+
graph.add_edge('1', '2', props: 'donkey')
|
222
|
+
graph.add_edge('2', '3', props: 'zebra')
|
223
|
+
end
|
224
|
+
|
225
|
+
it { is_expected.to eq('Nanoc::Int::DirectedGraph("1" -> "2" props="donkey", "2" -> "3" props="zebra")') }
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
data/spec/spec_helper.rb
ADDED
data/test.sh
ADDED
metadata
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: nanoc-rust
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: '0.1'
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Denis Defreyne
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-07-16 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: ffi
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.9'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.9'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.15'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.15'
|
41
|
+
description: Contains parts of Nanoc rewritten in Rust for speed
|
42
|
+
email: denis@stoneship.org
|
43
|
+
executables: []
|
44
|
+
extensions:
|
45
|
+
- rust/extconf.rb
|
46
|
+
extra_rdoc_files: []
|
47
|
+
files:
|
48
|
+
- ".gitignore"
|
49
|
+
- ".rspec"
|
50
|
+
- ".rubocop.yml"
|
51
|
+
- Gemfile
|
52
|
+
- Gemfile.lock
|
53
|
+
- Rakefile
|
54
|
+
- lib/nanoc-rust.rb
|
55
|
+
- nanoc-rust.gemspec
|
56
|
+
- rust/Cargo.lock
|
57
|
+
- rust/Cargo.toml
|
58
|
+
- rust/Makefile
|
59
|
+
- rust/extconf.rb
|
60
|
+
- rust/src/digraph.rs
|
61
|
+
- rust/src/nanoc_rust.rs
|
62
|
+
- spec/directed_graph_spec.rb
|
63
|
+
- spec/spec_helper.rb
|
64
|
+
- test.sh
|
65
|
+
homepage: http://nanoc.ws/
|
66
|
+
licenses:
|
67
|
+
- MIT
|
68
|
+
metadata: {}
|
69
|
+
post_install_message:
|
70
|
+
rdoc_options: []
|
71
|
+
require_paths:
|
72
|
+
- lib
|
73
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
74
|
+
requirements:
|
75
|
+
- - ">="
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: 2.3.0
|
78
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
requirements: []
|
84
|
+
rubyforge_project:
|
85
|
+
rubygems_version: 2.6.12
|
86
|
+
signing_key:
|
87
|
+
specification_version: 4
|
88
|
+
summary: Faster bits for Nanoc
|
89
|
+
test_files: []
|