Skip to content

DEV-667: get it working so we can stage an item #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Mar 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
name: Run Tests

on: push

jobs:
test:
runs-on: ubuntu-latest
name: Ruby ${{ matrix.ruby }}
strategy:
matrix:
ruby: [2.7, 3.0, 3.1, 3.2]
steps:
- uses: actions/checkout@v3
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: ${{ matrix.ruby }}
bundler-cache: true
- name: Run linter for Ruby
run: bundle exec standardrb
- name: Run tests
run: bundle exec rspec
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,5 @@

# rspec failure tracking
.rspec_status
Gemfile.lock
vendor
6 changes: 5 additions & 1 deletion Gemfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
source "https://rubygems.org"

git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }

# Specify your gem's dependencies in ht-pairtree.gemspec
gemspec

group :development do
gem "pry"
end
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# HathiTrust::Pairtree

[![Tests](https://github.com/hathitrust/ht-pairtree/actions/workflows/tests.yml/badge.svg)](https://github.com/hathitrust/ht-pairtree/actions/workflows/tests.yml)

Deal with a [Pairtree](https://github.com/mlibrary/pairtree) given an HTID.

Allows both reading and creation of the underlying pairtree directories
Expand Down Expand Up @@ -39,9 +41,9 @@ pairtree = pt[id]
### Create a new pairtree object (directory)

newthing = pt.create('one.two3four')
#=> Pairtree::PathError (because there's no pairtree at .../obj/one)
#=> HathiTrust::Pairtree::NamespaceDoesNotExist (because there's no pairtree at .../obj/one)

newthing = pt.create('one.two3four', create_new_namespace: true)
newthing = pt.create('one.two3four', new_namespace_allowed: true)
#=> Pairtree::Obj<blah blah blah>

```
Expand Down Expand Up @@ -69,7 +71,7 @@ function htcd() {
Add this line to your application's Gemfile:

```ruby
gem 'ht-rpairtree'
gem 'ht-pairtree'
```

And then execute:
Expand Down
2 changes: 1 addition & 1 deletion Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ require "rspec/core/rake_task"

RSpec::Core::RakeTask.new(:spec)

task :default => :spec
task default: :spec
31 changes: 13 additions & 18 deletions ht-pairtree.gemspec
Original file line number Diff line number Diff line change
@@ -1,46 +1,41 @@

lib = File.expand_path("../lib", __FILE__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require "ht/pairtree/version"

Gem::Specification.new do |spec|
spec.name = "ht-pairtree"
spec.version = HathiTrust::Pairtree::VERSION
spec.authors = ["Bill Dueber"]
spec.email = ["[email protected]"]
#
spec.summary = %q{Pairtree with extra sugar for the HathiTrust environment}
spec.name = "ht-pairtree"
spec.version = HathiTrust::Pairtree::VERSION
spec.authors = ["Bill Dueber"]
spec.email = ["[email protected]"]
spec.summary = "Pairtree with extra sugar for the HathiTrust environment"
# spec.description = %q{TODO: Write a longer description or delete this line.}
spec.homepage = "https://github.com/hathitrust/ht-pairtree"
spec.license = "MIT"
spec.homepage = "https://github.com/hathitrust/ht-pairtree"
spec.license = "MIT"
#
# # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
# # to allow pushing to a single host or delete this section to allow pushing to any host.
if spec.respond_to?(:metadata)
spec.metadata["allowed_push_host"] = "https://gems.www.lib.umich.edu"
spec.metadata["homepage_uri"] = spec.homepage
spec.metadata["source_code_uri"] = spec.homepage
spec.metadata["changelog_uri"] = spec.homepage + '/CHANGELOG.md'
spec.metadata["changelog_uri"] = spec.homepage + "/CHANGELOG.md"
else
raise "RubyGems 2.0 or newer is required to protect against " \
"public gem pushes."
end

# Specify which files should be added to the gem when it is released.
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
spec.files = Dir.chdir(File.expand_path("..", __FILE__)) do
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
end
spec.bindir = "exe"
spec.executables = %w[htdir]
spec.bindir = "exe"
spec.executables = %w[htdir]
spec.require_paths = ["lib"]

# The 'pairtree' gem seems to be abandoned. Use the mlibrary
# clone
spec.add_dependency 'rpairtree'


spec.add_dependency "pairtree", "~> 0.3"
spec.add_development_dependency "bundler", "~> 2.0"
spec.add_development_dependency "rake", "~> 12.0"
spec.add_development_dependency "rspec", "~> 3.0"
spec.add_development_dependency "standardrb"
end
57 changes: 21 additions & 36 deletions lib/ht/pairtree.rb
Original file line number Diff line number Diff line change
@@ -1,35 +1,31 @@
# coding: utf-8
require "ht/pairtree/version"

# Pairtree is ancient and throws warnings so we suppress them on require
oldverbose = $VERBOSE
$VERBOSE = nil
require 'pairtree'
require "pairtree"
$VERBOSE = oldverbose

require 'fileutils'
require "fileutils"

module HathiTrust
# A simple Pairtree implementation to make it easier to work with
# the HathiTrust pairtree structure.
class Pairtree

class PairtreeError < StandardError;
class PairtreeError < StandardError
end

class NamespaceDoesNotExist < PairtreeError
end

SDR_DATA_ROOT_DEFAULT = (ENV["SDRDATAROOT"] || '/sdr1') + '/obj'
SDR_DATA_ROOT_DEFAULT = (ENV["SDRDATAROOT"] || "/sdr1") + "/obj"

# Create a new pairtree object rooted at the given directory
# @param [String] root The root of the über-pairtree (takes `ENV['SDRDATAROOT'] + 'obj'` by default)
def initialize(root: SDR_DATA_ROOT_DEFAULT)
@root = Pathname.new(root)
end



# Get a Pathname object corresponding to the directory holding data for the given HTID
# @param [String] htid The HathiTrust ID for an object
# @return [Pathname] Path to the given object's directory
Expand All @@ -40,58 +36,47 @@ def path_for(htid)
alias_method :dir, :path_for
alias_method :path_to, :path_for


# Get the underlying pairtree for the given obj.
# @param [String] htid The HathiTrust ID for an object
# @param [Boolean] create Whether to create the pairtree if it doesn't exist
# @return [Pairtree] the pairtree for that object
def pairtree_for(htid)
pairtree_root(htid)
def pairtree_for(htid, create: false)
pairtree_root(htid, create: create)
end

# Create a pairtree for the given htid. Allow namespace creation
# only if told to.
# Create the pairtree directory for the given htid. Allow namespace
# creation only if told to.
# @param htid [String] The HTID
# @param new_namespace_allowed [Boolean] Whether or not to error if the namespace DNE
# @raise [NamespaceDoesNotExist] if namespace DNE and new namespace not allowed
# @return [Pairtree::Obj] the underlying pairtree object
def create(htid, new_namespace_allowed: false)
if !namespace_exists?(htid)
if new_namespace_allowed
create_namespace_dir(htid)
else
raise NamespaceDoesNotExist.new("Namespace #{namespace(htid)} does not exist")
end
unless namespace_exists?(htid) || new_namespace_allowed
raise NamespaceDoesNotExist.new("Namespace #{namespace(htid)} does not exist")
end
pairtree_for(htid).mk(htid)
end

def namespace_exists?(htid)
namespace_dir(htid).exists?
pairtree_for(htid, create: new_namespace_allowed).mk(htid)
end

private

def create_namespace_dir(htid)
ndir = namespace_dir(htid)
return self if Dir.exists?(ndir)
Dir.mkdir(ndir)
File.open(ndir + "pairtree_prefix", 'w:utf-8') {|f| f.print namespace(htid)}
File.open(ndir + "pairtree_version0_1", 'w:utf-8') {|f| }
Dir.mkdir(ndir + "pairtree_root")
return self
def namespace_exists?(htid)
namespace_dir(htid).exist?
end


def namespace_dir(htid)
@root + namespace(htid)
end

def namespace(htid)
htid.split('.', 2).first
htid.split(".", 2).first
end

def pairtree_root(htid)
::Pairtree.at(namespace_dir(htid))
def pairtree_root(htid, create: false)
::Pairtree.at(namespace_dir(htid), prefix: pairtree_prefix(htid), create: create)
end

def pairtree_prefix(htid)
namespace(htid) + "."
end
end
end
2 changes: 1 addition & 1 deletion lib/ht/pairtree/version.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
module HathiTrust
class Pairtree
class Pairtree
VERSION = "0.1.0"
end
end
81 changes: 46 additions & 35 deletions spec/ht/pairtree_spec.rb
Original file line number Diff line number Diff line change
@@ -1,58 +1,69 @@
require 'pairtree'
require 'fileutils'
require 'tmpdir'

require "pairtree"
require "fileutils"
require "tmpdir"

RSpec.describe HathiTrust::Pairtree do
TMPDIR = Pathname.new(Dir.mktmpdir)
puts TMPDIR

idmap = {
'uc1.c3292592' => 'sdr1/obj/uc1/pairtree_root/c3/29/25/92/c3292592',
'loc.ark:/13960/t9w09k00s' => 'sdr1/obj/loc/pairtree_root/ar/k+/=1/39/60/=t/9w/09/k0/0s/ark+=13960=t9w09k00s',
'ia.ark:/13960/t9w10cs7x' => 'sdr1/obj/ia/pairtree_root/ar/k+/=1/39/60/=t/9w/10/cs/7x/ark+=13960=t9w10cs7x'
}

let(:idmap) do
{
"uc1.c3292592" => "sdr1/obj/uc1/pairtree_root/c3/29/25/92/c3292592",
"loc.ark:/13960/t9w09k00s" => "sdr1/obj/loc/pairtree_root/ar/k+/=1/39/60/=t/9w/09/k0/0s/ark+=13960=t9w09k00s",
"ia.ark:/13960/t9w10cs7x" => "sdr1/obj/ia/pairtree_root/ar/k+/=1/39/60/=t/9w/10/cs/7x/ark+=13960=t9w10cs7x"
}
end

before(:context) do
def make_paths(idmap)
idmap.values.each do |subdir|
dir = TMPDIR + subdir
puts "Trying to make #{dir}"
dir = @tmpdir + subdir
FileUtils.mkdir_p(dir)
root = TMPDIR + Pathname.new(subdir.split('/')[0..2].join('/'))
prefix = subdir.split('/')[2]
File.open(root + 'pairtree_prefix', 'w:utf-8') do |pp|
pp.print(prefix + '.')
root = @tmpdir + Pathname.new(subdir.split("/")[0..2].join("/"))
prefix = subdir.split("/")[2]
File.open(root + "pairtree_prefix", "w:utf-8") do |pp|
pp.print(prefix + ".")
end
File.open(root + 'pairtree_version0_1', 'w:utf-8') {}
File.open(root + "pairtree_version0_1", "w:utf-8") {}
end
end

after(:context) do
FileUtils.rm_f(TMPDIR)
around(:each) do |example|
Dir.mktmpdir do |d|
@tmpdir = Pathname.new(d)
FileUtils.mkdir_p(@tmpdir + "sdr1/obj")
example.run
end
end


it "has a version number" do
expect(HathiTrust::Pairtree::VERSION).not_to be nil
end


describe "translates names into directories" do
pt = HathiTrust::Pairtree.new(root: TMPDIR + 'sdr1/obj')
it "translates names into directories" do
make_paths(idmap)
pt = HathiTrust::Pairtree.new(root: @tmpdir + "sdr1/obj")

idmap.each_pair do |id, dir|
it id do
expect(pt.path_for(id).to_s).to eq((TMPDIR + dir).to_s)
end
expect(pt.path_for(id).to_s).to eq((@tmpdir + dir).to_s)
end
end

it "Can create new object directories" do
pt = HathiTrust::Pairtree.new(root: TMPDIR + 'sdr1/obj')
expect {pt.create('bill.dueber', create_new_namespace: false)}.to raise_error(Pairtree::PathError)
expect(pt.create('bill.dueber', create_new_namespace: true).exists?('.')).to be true
end
describe "#create" do
let(:pt) { HathiTrust::Pairtree.new(root: @tmpdir + "sdr1/obj") }

it "can create new object directories in the expected location" do
expect { pt.create("test.something", new_namespace_allowed: false) }.to raise_error(HathiTrust::Pairtree::NamespaceDoesNotExist)

pt_obj = pt.create("test.something", new_namespace_allowed: true)
expect(pt_obj.exist?(".")).to be true
expect(pt_obj.to_path).to eq((@tmpdir + "sdr1/obj/test/pairtree_root/so/me/th/in/g/something").to_s)
end

it "create is idempotent" do
pt.create("test.something", new_namespace_allowed: true)
expect(pt.create("test.something").exist?(".")).to be true
end

it "new_namespace_allowed is idempotent" do
pt.create("test.something", new_namespace_allowed: true)
expect(pt.create("test.somethingelse", new_namespace_allowed: true).exist?(".")).to be true
end
end
end