Skip to content

Commit

Permalink
Add a ReleaseNotesReader service class
Browse files Browse the repository at this point in the history
  • Loading branch information
imobachgs committed Sep 14, 2017
1 parent a1378f1 commit 8c14206
Show file tree
Hide file tree
Showing 4 changed files with 242 additions and 1 deletion.
1 change: 0 additions & 1 deletion src/lib/y2packager/product_reader.rb
Expand Up @@ -22,7 +22,6 @@ class ProductReader
include Yast::Logger

class << self

# Installation packages map
#
# This map contains the correspondence between products and the
Expand Down
133 changes: 133 additions & 0 deletions src/lib/y2packager/release_notes_reader.rb
@@ -0,0 +1,133 @@
require "yast"
require "fileutils"
require "y2packager/package"
require "pathname"

Yast.import "Directory"
Yast.import "Pkg"

module Y2Packager
# This class is able to read release notes for a given product
#
# Release notes for a product are available in a specific package which provides
# "release-notes()" for the given product. For instance, a package which provides
# "release-notes() = SLES" will provide release notes for the SLES product.
#
# This reader takes care of downloading the release notes package (if any),
# extracting its content and returning release notes for a given language/format.
class ReleaseNotesReader
include Yast::Logger

# @return [Pathname] Place to store all release notes
attr_reader :work_dir

# Constructor
#
# @param work_dir [Pathname] Temporal directory to work
def initialize(work_dir = nil)
@work_dir = work_dir || Pathname(Yast::Directory.tmpdir).join("release-notes")
end

# Get release notes for a given product
#
# Release notes are downloaded and extracted to work directory. When
# release notes for a language "xx_XX" are not found, it will fallback to
# "xx".
#
# @param product [Y2Packager::Product] Product
# @param lang [String] Release notes language (falling back to "en")
# @param format [Symbol] Release notes format (:txt or :rtf)
# @return [String,nil] Release notes or nil if a release notes were not found
# (no package providing release notes or notes not found in the package)
def for(product, lang: "en_US", format: :txt)
package = release_notes_package_for(product)
return nil if package.nil?
download_and_extract(package)
content = release_notes_content(package, lang, format)
cleanup
content
end

private

def cleanup
::FileUtils.rm_r(work_dir) if work_dir.directory?
end

# Return the release notes package for a given product
#
# This method queries libzypp asking for the package which contains release
# notes for the given product. It relies on the `release-notes()` tag.
#
# @param product [Product] Product
# @return [Package,nil] Package containing the release notes; nil if not found
def release_notes_package_for(product)
provides = Yast::Pkg.PkgQueryProvides("release-notes()")
release_notes_packages = provides.map(&:first).uniq
package_name = release_notes_packages.find do |name|
dependencies = Yast::Pkg.ResolvableDependencies(name, :package, "").first["deps"]
dependencies.any? do |dep|
dep["provides"].to_s.match(/release-notes\(\)\s*=\s*#{product.name}\s*/)
end
end
return nil if package_name.nil?
# FIXME: make sure we get the latest version
Y2Packager::Package.find(package_name).find { |s| s.status == :available }
end

# Return release notes content for a package, language and format
#
# Release notes are downloaded and extracted to work directory. When
# release notes for a language "xx_XX" are not found, it will fallback to
# "xx".
#
# @param package [String] Release notes package name
# @param lang [String] Language code ("en_US", "en", etc.)
# @param format [Symbol] Content format (:txt, :rtf, etc.).
# @see release_notes_file
def release_notes_content(package, lang, format)
file = release_notes_file(package, lang, format)
file ? File.read(file) : nil
end

FALLBACK_LANGS = ["en_US", "en"].freeze
# Return release notes file path for a given package, language and format
#
# Release notes are downloaded and extracted to work directory. When
# release notes for a language "xx_XX" are not found, it will fallback to
# "xx".
#
# @param package [String] Release notes package name
# @param lang [String] Language code ("en_US", "en", etc.)
# @param format [Symbol] Content format (:txt, :rtf, etc.).
def release_notes_file(package, lang, format)
langs = [lang]
langs << lang[0..1] if lang.size > 2
langs.concat(FALLBACK_LANGS)

Dir.glob(
File.join(
release_notes_path(package), "**", "RELEASE-NOTES.{#{langs.join(",")}}.#{format}"
)
).first
end

# Download and extract package
#
# @return [Boolean]
def download_and_extract(package)
target = release_notes_path(package)
return true if ::File.directory?(target)
::FileUtils.mkdir_p(target)
package.extract_to(target)
end

# Return the location of the extracted release notes package
#
# @param [Package] Release notes package
# @return [String] Path to extracted release notes
def release_notes_path(package)
work_dir.join(package.name)
end
end
end
Binary file modified test/data/release-notes-dummy.rpm
Binary file not shown.
109 changes: 109 additions & 0 deletions test/lib/release_notes_reader_test.rb
@@ -0,0 +1,109 @@
#!/usr/bin/env rspec

require_relative "../test_helper"
require "y2packager/release_notes_reader"
require "y2packager/product"

describe Y2Packager::ReleaseNotesReader do
subject(:reader) { described_class.new(work_dir) }

let(:work_dir) { FIXTURES_PATH.join("release-notes") }

let(:product) do
instance_double(Y2Packager::Product, name: "dummy")
end

let(:package) do
Y2Packager::Package.new("release-notes-dummy", 2, :available)
end

let(:dependencies) do
[
{ "deps" => [{ "provides" => "release-notes() = dummy" }] }
]
end

let(:provides) do
[["release-notes-dummy", :CAND, :NONE]]
end

before do
allow(Yast::Pkg).to receive(:PkgQueryProvides).with("release-notes()")
.and_return(provides)
allow(Yast::Pkg).to receive(:ResolvableDependencies)
.with("release-notes-dummy", :package, "").and_return(dependencies)
allow(Y2Packager::Package).to receive(:find).with(package.name).and_return([package])
allow(package).to receive(:download_to) do |path|
::FileUtils.mkdir_p(path) unless work_dir.directory?
::FileUtils.cp(FIXTURES_PATH.join("release-notes-dummy.rpm"), path)
true
end
end

describe "#for" do
let(:download) { true }

it "returns product release notes in english" do
expect(reader.for(product)).to eq("Release Notes\n")
end

it "cleans up temporary files" do
reader.for(product)
expect(File).to_not be_directory(work_dir)
end

context "when a full language code is given (xx_XX)" do
it "returns product release notes for the given language" do
expect(reader.for(product, lang: "en_US")).to eq("Release Notes\n")
end

context "and release notes are not available" do
it "returns product release notes for the short language code (xx)" do
expect(reader.for(product, lang: "de_DE")).to eq("Versionshinweise\n")
end
end
end

context "when a format is given" do
it "returns product release notes in the given format" do
expect(reader.for(product, format: :html))
.to eq("<h1>Release Notes</h1>\n")
end

context "and release notes are not available in the given format" do
it "returns the english version" do
expect(reader.for(product, lang: "de_DE", format: :html))
.to eq("<h1>Release Notes</h1>\n")
end
end
end

context "when release notes are not available" do
it "returns the english version" do
expect(reader.for(product, lang: "es")).to eq("Release Notes\n")
end
end

context "when package could not be retrieved" do
before do
allow(package).to receive(:extract_to).and_return(false)
end

it "returns nil" do
expect(reader.for(product)).to be_nil
end
end

context "when no package containing release notes was found" do
let(:provides) { [] }

it "returns nil" do
expect(reader.for(product)).to be_nil
end
end
end

after do
::FileUtils.rm_rf(work_dir) if work_dir.exist?
end
end

0 comments on commit 8c14206

Please sign in to comment.