Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add a ReleaseNotesReader service class
- Loading branch information
Showing
4 changed files
with
242 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |