hglib 0.1.pre20180129173049 → 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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +3 -0
- data.tar.gz.sig +0 -0
- data/ChangeLog +94 -11
- data/History.md +1 -1
- data/Manifest.txt +12 -0
- data/Rakefile +1 -1
- data/examples/clone.rb +13 -0
- data/integration/commands/clone_spec.rb +52 -0
- data/integration/spec_helper.rb +29 -0
- data/lib/hglib.rb +7 -7
- data/lib/hglib/repo.rb +129 -0
- data/lib/hglib/repo/id.rb +81 -0
- data/lib/hglib/repo/log_entry.rb +128 -0
- data/lib/hglib/server.rb +320 -0
- data/spec/.status +39 -0
- data/spec/hglib/repo/id_spec.rb +182 -0
- data/spec/hglib/repo/log_entry_spec.rb +69 -0
- data/spec/hglib/repo_spec.rb +106 -0
- data/spec/hglib/server_spec.rb +166 -0
- data/spec/spec_helper.rb +6 -1
- metadata +44 -34
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c5afd96a3fd5903f1ca7225d46a9db704abbe26faaddf1efb70e24dc6d08f2a6
|
4
|
+
data.tar.gz: 0f1bd5603a579a37dcef56c4e353550c10d0740a557524d515d5664ddd1c75be
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4f77a7e98c304184d863f4e19ed08eabfd05a13a9e2a87d368cbea07b3d6fccd456b3d50e775cd6c794a56831a9146f8062641f615f256fe8929ae30097eae36
|
7
|
+
data.tar.gz: c5892667e50469d93842abb09fccdc3f3ea328bc3f38ad0f4439b04a1ffd08e0152a2c21e88d86e420cfd797f49a62535247ff4ae84f67c5756b7dfcef4bf83b
|
checksums.yaml.gz.sig
ADDED
data.tar.gz.sig
ADDED
Binary file
|
data/ChangeLog
CHANGED
@@ -1,13 +1,96 @@
|
|
1
|
+
2019-04-03 Michael Granger <ged@FaerieMUD.org>
|
2
|
+
|
3
|
+
@ * Rakefile:
|
4
|
+
| Make the project public
|
5
|
+
| [847d7345d2eb] [tip]
|
6
|
+
|
|
7
|
+
o * History.md, lib/hglib.rb:
|
8
|
+
| Bump the minor version and update history.
|
9
|
+
| [e4dc675f47f3]
|
10
|
+
|
|
11
|
+
o * lib/hglib/repo/log_entry.rb, spec/hglib/repo/log_entry_spec.rb:
|
12
|
+
| Add a #files attribute to log entries.
|
13
|
+
|
|
14
|
+
| This will be populated if you run #log with `verbose: true`.
|
15
|
+
| [a66f9ba07f99]
|
16
|
+
|
|
17
|
+
o * lib/hglib/repo.rb:
|
18
|
+
| Add Repo#status -> #stat alias
|
19
|
+
| [f8308d34a0e1]
|
20
|
+
|
|
21
|
+
2019-01-16 Michael Granger <ged@FaerieMUD.org>
|
22
|
+
|
23
|
+
o * .hoerc, .ruby-version, lib/hglib/repo.rb, spec/hglib/repo_spec.rb:
|
24
|
+
| Use Ruby 2.6, add Repo#pull and #pull_update.
|
25
|
+
|
|
26
|
+
| Also fixes the Repo#log spec.
|
27
|
+
| [b84a356151a3]
|
28
|
+
|
|
29
|
+
2018-11-28 Michael Granger <ged@FaerieMUD.org>
|
30
|
+
|
31
|
+
o * lib/hglib/repo.rb, lib/hglib/repo/log_entry.rb,
|
32
|
+
| spec/hglib/repo/log_entry_spec.rb:
|
33
|
+
| Improve repo.log, add repo.commit
|
34
|
+
| [399d83897663]
|
35
|
+
|
|
36
|
+
o * lib/hglib/server.rb, spec/hglib/server_spec.rb:
|
37
|
+
| Improve server option-mangling
|
38
|
+
| [8ffd61bb3183]
|
39
|
+
|
|
40
|
+
o * Rakefile, certs/ged.pem, hglib.gemspec:
|
41
|
+
| =Update my gem-signing cert
|
42
|
+
| [05a8abeeec84]
|
43
|
+
|
|
44
|
+
2018-05-22 Michael Granger <ged@FaerieMUD.org>
|
45
|
+
|
46
|
+
o * .assemblies/commit:
|
47
|
+
| Add Assemblage commit script
|
48
|
+
| [4a1cbb9f8d56]
|
49
|
+
|
|
50
|
+
o * Manifest.txt, lib/hglib/repo/log_entry.rb, lib/hglib/server.rb,
|
51
|
+
| spec/hglib/repo/log_entry_spec.rb, spec/spec_helper.rb:
|
52
|
+
| Finish up log command/log entry class.
|
53
|
+
| [ac2b07cce0fc]
|
54
|
+
|
|
55
|
+
2018-05-15 Michael Granger <ged@FaerieMUD.org>
|
56
|
+
|
57
|
+
o * .gems, .hgignore, integration/commands/clone_spec.rb,
|
58
|
+
| integration/spec_helper.rb, lib/hglib.rb, lib/hglib/repo.rb,
|
59
|
+
| lib/hglib/repo/id.rb, lib/hglib/repo/log_entry.rb,
|
60
|
+
| lib/hglib/server.rb, spec/hglib/repo/id_spec.rb,
|
61
|
+
| spec/hglib/repo/log_entry_spec.rb, spec/hglib/repo_spec.rb,
|
62
|
+
| spec/hglib/server_spec.rb, spec/spec_helper.rb:
|
63
|
+
| Flesh out the features of Repo objects
|
64
|
+
| [d4af915821de]
|
65
|
+
|
|
66
|
+
2018-03-13 Michael Granger <ged@FaerieMUD.org>
|
67
|
+
|
68
|
+
o * .hgignore, .pryrc, README.md, Rakefile, examples/clone.rb,
|
69
|
+
| hglib.gemspec, lib/hglib.rb, lib/hglib/repo.rb,
|
70
|
+
| lib/hglib/repo/log_entry.rb, lib/hglib/server.rb,
|
71
|
+
| spec/hglib/server_spec.rb, spec/hglib_spec.rb, spec/spec_helper.rb:
|
72
|
+
| =Added repo class, fixed a bunch of stuff.
|
73
|
+
| [a366819bd05b]
|
74
|
+
|
|
1
75
|
2018-01-24 Michael Granger <ged@FaerieMUD.org>
|
2
76
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
77
|
+
o * .document, .hgignore, .hoerc, .rdoc_options, Manifest.txt,
|
78
|
+
| README.md, hglib.gemspec:
|
79
|
+
| Update project metadata, build files
|
80
|
+
| [5881275661a7]
|
81
|
+
|
|
82
|
+
o * lib/hglib.rb, spec/hglib_spec.rb:
|
83
|
+
| Add configurable binpath to the top-level module
|
84
|
+
| [da8322c8b033]
|
85
|
+
|
|
86
|
+
o * .ruby-version:
|
87
|
+
| Make ruby-version less specific
|
88
|
+
| [81f357f730d9]
|
89
|
+
|
|
90
|
+
o * .document, .editorconfig, .gems, .hg_archival.txt, .hgignore,
|
91
|
+
.pryrc, .rdoc_options, .ruby-gemset, .ruby-version, .simplecov,
|
92
|
+
Gemfile, History.md, LICENSE.txt, Manifest.txt, README.md, Rakefile,
|
93
|
+
certs/ged.pem, lib/hglib.rb, spec/hglib_spec.rb,
|
94
|
+
spec/spec_helper.rb:
|
95
|
+
Initial commit.
|
96
|
+
[d6c97f99b012]
|
data/History.md
CHANGED
data/Manifest.txt
CHANGED
@@ -5,6 +5,18 @@ LICENSE.txt
|
|
5
5
|
Manifest.txt
|
6
6
|
README.md
|
7
7
|
Rakefile
|
8
|
+
examples/clone.rb
|
9
|
+
integration/commands/clone_spec.rb
|
10
|
+
integration/spec_helper.rb
|
8
11
|
lib/hglib.rb
|
12
|
+
lib/hglib/repo.rb
|
13
|
+
lib/hglib/repo/id.rb
|
14
|
+
lib/hglib/repo/log_entry.rb
|
15
|
+
lib/hglib/server.rb
|
16
|
+
spec/.status
|
17
|
+
spec/hglib/repo/id_spec.rb
|
18
|
+
spec/hglib/repo/log_entry_spec.rb
|
19
|
+
spec/hglib/repo_spec.rb
|
20
|
+
spec/hglib/server_spec.rb
|
9
21
|
spec/hglib_spec.rb
|
10
22
|
spec/spec_helper.rb
|
data/Rakefile
CHANGED
@@ -14,7 +14,6 @@ Hoe.plugin :signing
|
|
14
14
|
Hoe.plugin :deveiate
|
15
15
|
|
16
16
|
Hoe.plugins.delete :rubyforge
|
17
|
-
Hoe.plugins.delete :gemcutter # Remove for public gems
|
18
17
|
|
19
18
|
hoespec = Hoe.spec 'hglib' do |spec|
|
20
19
|
spec.readme_file = 'README.md'
|
@@ -84,6 +83,7 @@ end
|
|
84
83
|
task :gemspec => GEMSPEC
|
85
84
|
file GEMSPEC => __FILE__
|
86
85
|
task GEMSPEC do |task|
|
86
|
+
Rake.application.trace "Rebuilding gemspec."
|
87
87
|
spec = $hoespec.spec
|
88
88
|
spec.files.delete( '.gemtest' )
|
89
89
|
spec.signing_key = nil
|
data/examples/clone.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
#!/usr/bin/env rspec -cfd
|
2
|
+
|
3
|
+
require_relative '../spec_helper'
|
4
|
+
|
5
|
+
RSpec.describe "cloning" do
|
6
|
+
|
7
|
+
let( :repo_dir ) do
|
8
|
+
dir = Dir.mktmpdir( ['hglib', 'repodir'] )
|
9
|
+
Pathname( dir )
|
10
|
+
end
|
11
|
+
|
12
|
+
|
13
|
+
after( :each ) do
|
14
|
+
repo_dir.rmtree if repo_dir.exist?
|
15
|
+
end
|
16
|
+
|
17
|
+
|
18
|
+
it "can clone a local repo with no options" do
|
19
|
+
repo = Hglib.clone( '.', repo_dir )
|
20
|
+
|
21
|
+
expect( repo ).to be_a( Hglib::Repo )
|
22
|
+
expect( repo.path ).to eq( repo_dir )
|
23
|
+
expect( repo.status ).to be_empty
|
24
|
+
expect( repo_dir.children(false) ).to include(
|
25
|
+
Pathname( '.hg' ),
|
26
|
+
Pathname( 'Rakefile' )
|
27
|
+
)
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
it "can clone without updating" do
|
32
|
+
repo = Hglib.clone( '.', repo_dir, noupdate: true )
|
33
|
+
|
34
|
+
expect( repo ).to be_a( Hglib::Repo )
|
35
|
+
expect( repo.path ).to eq( repo_dir )
|
36
|
+
expect( repo.status ).to be_empty
|
37
|
+
expect( repo.id ).to eq( '000000000000' )
|
38
|
+
expect( repo_dir.children(false) ).to contain_exactly( Pathname('.hg') )
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
it "can clone to a specific revision" do
|
43
|
+
repo = Hglib.clone( '.', repo_dir, rev: 'da8322c8b033' )
|
44
|
+
|
45
|
+
expect( repo ).to be_a( Hglib::Repo )
|
46
|
+
expect( repo.path ).to eq( repo_dir )
|
47
|
+
expect( repo.status ).to be_empty
|
48
|
+
expect( repo.id ).to eq( 'da8322c8b033' )
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
#encoding: utf-8
|
3
|
+
|
4
|
+
require 'tmpdir'
|
5
|
+
|
6
|
+
require 'rspec'
|
7
|
+
require 'hglib'
|
8
|
+
|
9
|
+
RSpec.configure do |config|
|
10
|
+
config.expect_with :rspec do |expectations|
|
11
|
+
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
|
12
|
+
end
|
13
|
+
|
14
|
+
config.mock_with :rspec do |mocks|
|
15
|
+
mocks.verify_partial_doubles = true
|
16
|
+
end
|
17
|
+
|
18
|
+
config.shared_context_metadata_behavior = :apply_to_host_groups
|
19
|
+
config.filter_run_when_matching :focus
|
20
|
+
config.example_status_persistence_file_path = "spec/.status"
|
21
|
+
config.disable_monkey_patching!
|
22
|
+
config.warnings = true
|
23
|
+
config.profile_examples = 10
|
24
|
+
config.order = :random
|
25
|
+
|
26
|
+
config.filter_run_excluding( :requires_binary ) unless Hglib.hg_path.executable?
|
27
|
+
|
28
|
+
Kernel.srand( config.seed )
|
29
|
+
end
|
data/lib/hglib.rb
CHANGED
@@ -12,10 +12,10 @@ module Hglib
|
|
12
12
|
Exception2MessageMapper
|
13
13
|
|
14
14
|
# Package version
|
15
|
-
VERSION = '0.0
|
15
|
+
VERSION = '0.1.0'
|
16
16
|
|
17
17
|
# Version control revision
|
18
|
-
REVISION = %q$Revision
|
18
|
+
REVISION = %q$Revision$
|
19
19
|
|
20
20
|
# The default path to the `hg` command
|
21
21
|
DEFAULT_HG_PATH = begin
|
@@ -66,14 +66,14 @@ module Hglib
|
|
66
66
|
end
|
67
67
|
|
68
68
|
|
69
|
-
### Clone the +
|
70
|
-
### directory with the basename of the +
|
69
|
+
### Clone the +source_repo+ to the specified +local_dir+, which defaults to a
|
70
|
+
### directory with the basename of the +source_repo+ in the current working
|
71
71
|
### directory.
|
72
|
-
def self::clone(
|
73
|
-
output = self.server( nil ).run( :clone,
|
72
|
+
def self::clone( source_repo, local_dir=nil, **options )
|
73
|
+
output = self.server( nil ).run( :clone, source_repo, local_dir, **options )
|
74
74
|
self.log.debug "Clone output: %s" % [ output ]
|
75
75
|
|
76
|
-
local_dir ||= Pathname.pwd + File.basename(
|
76
|
+
local_dir ||= Pathname.pwd + File.basename( source_repo )
|
77
77
|
return self.repo( local_dir )
|
78
78
|
end
|
79
79
|
|
data/lib/hglib/repo.rb
ADDED
@@ -0,0 +1,129 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'json'
|
5
|
+
require 'loggability'
|
6
|
+
require 'hglib' unless defined?( Hglib )
|
7
|
+
|
8
|
+
|
9
|
+
class Hglib::Repo
|
10
|
+
extend Loggability
|
11
|
+
|
12
|
+
# Loggability API -- log to the hglib logger
|
13
|
+
log_to :hglib
|
14
|
+
|
15
|
+
|
16
|
+
autoload :Id, 'hglib/repo/id'
|
17
|
+
autoload :LogEntry, 'hglib/repo/log_entry'
|
18
|
+
|
19
|
+
|
20
|
+
### Create a new Repo object that will operate on the Mercurial repo at the
|
21
|
+
### specified +path+.
|
22
|
+
def initialize( path )
|
23
|
+
@path = Pathname( path )
|
24
|
+
@server = nil
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
######
|
29
|
+
public
|
30
|
+
######
|
31
|
+
|
32
|
+
##
|
33
|
+
# The path to the repository
|
34
|
+
attr_reader :path
|
35
|
+
|
36
|
+
|
37
|
+
### Return the Hglib::Server started for this Repo, creating it if necessary.
|
38
|
+
def server
|
39
|
+
return @server ||= self.create_server
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
### Return a Hash of the status of the files in the repo, keyed by Pathname of
|
44
|
+
### the file. An empty Hash is returned if there are no files with one of the
|
45
|
+
### requested statuses.
|
46
|
+
def status( *args, **options )
|
47
|
+
response = self.server.run( :status, *args, **options )
|
48
|
+
self.logger.debug "Parsing status response: %p" % [ response ]
|
49
|
+
|
50
|
+
return {} if response.length == 1 && response.first.empty?
|
51
|
+
return response.each_slice( 2 ).inject({}) do |hash, (raw_status, path)|
|
52
|
+
path = Pathname( path.chomp )
|
53
|
+
hash[ path ] = raw_status.strip
|
54
|
+
hash
|
55
|
+
end
|
56
|
+
end
|
57
|
+
alias_method :stat, :status
|
58
|
+
|
59
|
+
|
60
|
+
### Return a Hglib::Repo::Id that identifies the repository state at the
|
61
|
+
### specified +revision+, or the current revision if unspecified. A +revision+
|
62
|
+
### of `.` identifies the working directory parent without uncommitted changes.
|
63
|
+
def id( revision=nil )
|
64
|
+
options = {}
|
65
|
+
options[:rev] = revision if revision
|
66
|
+
|
67
|
+
response = self.server.run( :id, **options )
|
68
|
+
self.logger.debug "Got ID response: %p" % [ response ]
|
69
|
+
|
70
|
+
return Hglib::Repo::Id.parse( response.first )
|
71
|
+
end
|
72
|
+
|
73
|
+
|
74
|
+
### Return an Array of Hglib::Repo::LogEntry objects that describes the revision
|
75
|
+
### history of the specified +files+ or the entire project.
|
76
|
+
def log( *files, **options )
|
77
|
+
options[:graph] = false
|
78
|
+
options[:T] = 'json'
|
79
|
+
|
80
|
+
jsonlog = self.server.run( :log, *files, **options )
|
81
|
+
entries = JSON.parse( jsonlog.join )
|
82
|
+
|
83
|
+
return entries.map {|entry| Hglib::Repo::LogEntry.new(entry) }
|
84
|
+
end
|
85
|
+
|
86
|
+
|
87
|
+
### Commit the specified +files+ with the given +options+.
|
88
|
+
def commit( *files, **options )
|
89
|
+
response = self.server.run( :commit, *files, **options )
|
90
|
+
self.logger.debug "Got COMMIT response: %p" % [ response ]
|
91
|
+
|
92
|
+
return true
|
93
|
+
end
|
94
|
+
|
95
|
+
|
96
|
+
### Pull changes from the specified +source+ (which defaults to the +default+
|
97
|
+
### path) into the local repository.
|
98
|
+
def pull( source=nil, **options )
|
99
|
+
response = self.server.run( :pull, source, **options )
|
100
|
+
self.logger.debug "Got PULL response: %p" % [ response ]
|
101
|
+
|
102
|
+
return true
|
103
|
+
end
|
104
|
+
|
105
|
+
|
106
|
+
### Pull changes from the specified +source+ into the local repository and update
|
107
|
+
### to the new branch head if new descendents were pulled.
|
108
|
+
def pull_update( source=nil, **options )
|
109
|
+
options[:update] = true
|
110
|
+
return self.pull( source, **options )
|
111
|
+
end
|
112
|
+
|
113
|
+
|
114
|
+
#########
|
115
|
+
protected
|
116
|
+
#########
|
117
|
+
|
118
|
+
### Create an Hglib::Server for this Repo.
|
119
|
+
def create_server
|
120
|
+
return Hglib::Server.new( self.path.to_s )
|
121
|
+
end
|
122
|
+
|
123
|
+
|
124
|
+
### Return the logger for this object; aliased to avoid the conflict with `hg log`.
|
125
|
+
def logger
|
126
|
+
return Loggability[ self ]
|
127
|
+
end
|
128
|
+
|
129
|
+
end # class Hglib::Repo
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'hglib/repo' unless defined?( Hglib::Repo )
|
5
|
+
|
6
|
+
|
7
|
+
# The identification of a particular revision of a repository.
|
8
|
+
class Hglib::Repo::Id
|
9
|
+
|
10
|
+
### Parse the given +raw_id+ and return a new Id that contains the parsed
|
11
|
+
### information.
|
12
|
+
def self::parse( raw_id )
|
13
|
+
global, tags, bookmarks = raw_id.chomp.split( ' ', 3 )
|
14
|
+
has_plus = global.chomp!( '+' ) ? true : false
|
15
|
+
|
16
|
+
tags ||= ''
|
17
|
+
tags = tags.split( '/' )
|
18
|
+
|
19
|
+
bookmarks ||= ''
|
20
|
+
bookmarks = bookmarks.split( '/' )
|
21
|
+
|
22
|
+
return self.new( global, *tags, uncommitted_changes: has_plus, bookmarks: bookmarks )
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
### Create a new repository ID with the given +global+ revision identifier, one
|
27
|
+
### or more +tags+, and other options.
|
28
|
+
def initialize( global, *tags, uncommitted_changes: false, bookmarks: [] )
|
29
|
+
@global = global
|
30
|
+
@tags = tags
|
31
|
+
@bookmarks = Array( bookmarks )
|
32
|
+
@uncommitted_changes = uncommitted_changes
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
######
|
37
|
+
public
|
38
|
+
######
|
39
|
+
|
40
|
+
##
|
41
|
+
# The repo's global (short-form) revision identifier.
|
42
|
+
attr_reader :global
|
43
|
+
|
44
|
+
##
|
45
|
+
# The tags belonging to the revision of the repo.
|
46
|
+
attr_reader :tags
|
47
|
+
|
48
|
+
##
|
49
|
+
# The bookmarks set on the revision of the repo.
|
50
|
+
attr_reader :bookmarks
|
51
|
+
|
52
|
+
|
53
|
+
### Returns +true+ if the repo's working directory has uncommitted changes.
|
54
|
+
def uncommitted_changes?
|
55
|
+
return @uncommitted_changes
|
56
|
+
end
|
57
|
+
alias_method :has_uncommitted_changes?, :uncommitted_changes?
|
58
|
+
|
59
|
+
|
60
|
+
### Return the ID as a String in the form used by the command line.
|
61
|
+
def to_s
|
62
|
+
str = self.global.dup
|
63
|
+
|
64
|
+
str << '+' if self.uncommitted_changes?
|
65
|
+
str << ' ' << self.tags.join( '/' ) unless self.tags.empty?
|
66
|
+
str << ' ' << self.bookmarks.join( '/' ) unless self.bookmarks.empty?
|
67
|
+
|
68
|
+
return str
|
69
|
+
end
|
70
|
+
|
71
|
+
|
72
|
+
### Comparison operator -- returns +true+ if the +other+ object is another
|
73
|
+
### Hglib::Repo::Id with the same values, or a String containing the #global
|
74
|
+
### revision identifier.
|
75
|
+
def ==( other )
|
76
|
+
return (other.is_a?( self.class ) && self.to_s == other.to_s) ||
|
77
|
+
self.global == other
|
78
|
+
end
|
79
|
+
|
80
|
+
end # class Hglib::Repo::Id
|
81
|
+
|