nanoc-rust 0.1
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.
- 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: []
|