openstax_content 1.3.0 → 1.5.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 82eddc2890b74eeea7b59646cda86001dbb0385ddc00ad874ae5f69573441d57
4
- data.tar.gz: a8f83e6c1a5bb7c0e5cda4e6171da21da8cfce91e1e89c74ecc954a8299015eb
3
+ metadata.gz: 640f5b30f23af9597543e0e6239b3def597b456ae05b85c9657e6380d5ad6623
4
+ data.tar.gz: ba19461ea0e3700f2f8c752c4dcf2fe5808bf8a798b22707fec47d9857f081c8
5
5
  SHA512:
6
- metadata.gz: 34fe275a54727deb10d3636fbc82cf3c9a86461d67f6695ea157209b71ae0ded2660d4c0e4f100af332c5db9bb5b8b54769ce22f060ff96d13e2b3be9a3f98cc
7
- data.tar.gz: 05ec447981510aac02c672ba6606aaf111ffe785ffdc5b7a8d1a0223f7e8c3807da7168b61551e1acc23d48cf31134a6d080e2a65ee4864a7a2491787b85eac3
6
+ metadata.gz: a5589b01cfa98a740916cd47274171ada0265429960ca68657124f1d0c6c98e551542b3027db5278845e3ef10b9191d54b74d84cf4fd8db7355c28f2e3722d5e
7
+ data.tar.gz: 0e02160d69f9789030a33bd0f84da38eb0922bf3bb06c54fcc6740bdb7649ae1bbd72c62f130ce3e49947f73a711d477380c3e6eb36fc2b40c6a10ba0b344d58
data/README.md CHANGED
@@ -26,45 +26,11 @@ s3_access_key_id and s3_secret_access_key are optional (you can use AWS instance
26
26
 
27
27
  ## Usage
28
28
 
29
- ### Approved Book List (to get approved books and approved versions)
29
+ ### Approved Book List (to get books and page slugs)
30
30
  ```rb
31
31
  abl = OpenStax::Content::Abl.new
32
- approved_books = abl.approved_books
33
- approved_versions = abl.approved_versions
34
- ```
35
-
36
- ### S3 Bucket Listing (to get latest archive and book versions)
37
- ```rb
38
- s3 = OpenStax::Content::S3.new
39
- if s3.bucket_configured?
40
- latest_archive_version = s3.ls.last
41
- latest_book_ids = s3.ls latest_archive_version
42
- chosen_book = latest_book_ids.sample
43
- book_uuid, book_version = chosen_book.split('@', 2)
44
- book = OpenStax::Content::Book.new(
45
- archive_version: latest_archive_version, uuid: book_uuid, version: book_version
46
- )
47
- end
48
- ```
49
-
50
- ### Archive (to create archive links, load content and get book and page slugs)
51
- ```rb
52
- archive = OpenStax::Content::Archive.new latest_archive_version
53
-
54
- book_id = "#{book_uuid}@#{book_version}"
55
- page_id = "#{book_id}:#{page_uuid}"
56
-
57
- book_url = archive.url_for book_id
58
- page_url = archive.url_for page_id
59
-
60
- book_json = archive.fetch book_id
61
- page_json = archive.fetch page_id
62
-
63
- book_hash = archive.json book_id
64
- page_hash = archive.json page_id
65
-
66
- book_slug = archive.slug book_id # or book_uuid
67
- page_slug = archive.slug page_id # or "#{book_uuid}:#{page_uuid}"
32
+ books = abl.books
33
+ slugs = abl.slugs_by_page_uuid
68
34
  ```
69
35
 
70
36
  ### Fragment Splitter (to split pages and create interactive readings)
@@ -2,6 +2,10 @@ require_relative 'archive'
2
2
  require_relative 'book'
3
3
 
4
4
  class OpenStax::Content::Abl
5
+ # Check back this many archive versions
6
+ # If there are more than this number of archive versions still building, errors will happen
7
+ DEFAULT_MAX_ARCHIVE_ATTEMPTS = 5
8
+
5
9
  def initialize(url: nil)
6
10
  @url = url
7
11
  end
@@ -14,70 +18,85 @@ class OpenStax::Content::Abl
14
18
  @body_string ||= Faraday.get(url).body
15
19
  end
16
20
 
17
- def body_hash
18
- @body_hash ||= JSON.parse(body_string, symbolize_names: true)
21
+ def body_array
22
+ @body_array ||= JSON.parse(body_string, symbolize_names: true)
19
23
  end
20
24
 
21
25
  def digest
22
26
  Digest::SHA256.hexdigest body_string
23
27
  end
24
28
 
25
- def latest_approved_version_by_collection_id(archive: OpenStax::Content::Archive.new)
26
- {}.tap do |hash|
27
- body_hash[:approved_versions].each do |version|
28
- next if version[:min_code_version] > archive.version
29
+ def books(archive: OpenStax::Content::Archive.new)
30
+ body_array.filter { |book| book[:code_version] <= archive.version }.map do |book|
31
+ OpenStax::Content::Book.new(
32
+ archive: archive,
33
+ uuid: book[:uuid],
34
+ version: book[:commit_sha][0..6],
35
+ min_code_version: book[:code_version],
36
+ slug: book[:slug],
37
+ committed_at: book[:committed_at]
38
+ )
39
+ end
40
+ end
41
+
42
+ def each_book_with_previous_archive_version_fallback(max_attempts: DEFAULT_MAX_ARCHIVE_ATTEMPTS, &block)
43
+ raise ArgumentError, 'no block given' if block.nil?
44
+ raise ArgumentError, 'given block must accept the book as its first argument' if block.arity == 0
45
+
46
+ books = OpenStax::Content::Abl.new.books
47
+ attempt = 1
48
+
49
+ until books.empty?
50
+ previous_version = nil
51
+ previous_archive = nil
52
+ retry_books = []
53
+
54
+ books.each do |book|
55
+ begin
56
+ block.call book
57
+ rescue StandardError => exception
58
+ raise exception if attempt >= max_attempts
59
+
60
+ # Sometimes books in the latest archive fails to load (when the new version is still building)
61
+ # Retry with an earlier version of archive, if possible
62
+ previous_version ||= book.archive.previous_version
29
63
 
30
- existing_version = hash[version[:collection_id]]
64
+ if previous_version.nil?
65
+ # There are no more earlier archive versions
66
+ raise exception
67
+ else
68
+ previous_archive ||= OpenStax::Content::Archive.new version: previous_version
31
69
 
32
- next if !existing_version.nil? &&
33
- (existing_version[:content_version].split('.').map(&:to_i) <=>
34
- version[:content_version].split('.').map(&:to_i)) >= 0
70
+ retry_book = OpenStax::Content::Book.new(
71
+ archive: previous_archive,
72
+ uuid: book.uuid,
73
+ version: book.version,
74
+ slug: book.slug,
75
+ min_code_version: book.min_code_version,
76
+ committed_at: book.committed_at
77
+ )
35
78
 
36
- hash[version[:collection_id]] = version
79
+ # If the book requires an archive version that hasn't finished building yet, don't include it
80
+ retry_books << retry_book if retry_book.valid?
81
+ end
82
+ end
37
83
  end
84
+
85
+ books = retry_books
86
+ attempt += 1
38
87
  end
39
88
  end
40
89
 
41
- def approved_books(archive: OpenStax::Content::Archive.new)
42
- # Can be removed once we have no more CNX books
43
- version_by_collection_id = latest_approved_version_by_collection_id(archive: archive)
44
-
45
- body_hash[:approved_books].flat_map do |approved_book|
46
- if approved_book[:versions].nil?
47
- # CNX-hosted book
48
- version = version_by_collection_id[approved_book[:collection_id]]
49
-
50
- next [] if version.nil?
51
-
52
- approved_book[:books].map do |book|
53
- OpenStax::Content::Book.new(
54
- archive: archive,
55
- uuid: book[:uuid],
56
- version: version[:content_version].sub('1.', ''),
57
- slug: book[:slug],
58
- style: approved_book[:style]
59
- )
60
- end
61
- else
62
- # Git-hosted book
63
- approved_book[:versions].flat_map do |version|
64
- next [] if version[:min_code_version] > archive.version
65
-
66
- commit_metadata = version[:commit_metadata]
67
-
68
- commit_metadata[:books].map do |book|
69
- OpenStax::Content::Book.new(
70
- archive: archive,
71
- uuid: book[:uuid],
72
- version: version[:commit_sha][0..6],
73
- slug: book[:slug],
74
- style: book[:style],
75
- min_code_version: version[:min_code_version],
76
- committed_at: commit_metadata[:committed_at]
77
- )
78
- end
90
+ def slugs_by_page_uuid(max_attempts: DEFAULT_MAX_ARCHIVE_ATTEMPTS)
91
+ @slugs_by_page_uuid ||= {}.tap do |hash|
92
+ each_book_with_previous_archive_version_fallback(max_attempts: max_attempts) do |book|
93
+ book.all_pages.each do |page|
94
+ hash[page.uuid] ||= []
95
+ hash[page.uuid] << { book: book.slug, page: page.slug }
79
96
  end
80
97
  end
98
+
99
+ hash.each { |uuid, slugs| slugs.uniq! }
81
100
  end
82
101
  end
83
102
  end
@@ -4,20 +4,16 @@ require_relative 'book_part'
4
4
  class OpenStax::Content::Book
5
5
  extend Forwardable
6
6
 
7
- attr_reader :archive, :uuid, :version, :slug, :style, :min_code_version, :committed_at
7
+ attr_reader :archive, :uuid, :version, :slug, :min_code_version, :committed_at
8
8
 
9
- def initialize(
10
- archive:, uuid:, version:,
11
- url: nil, hash: nil, slug: nil, style: nil, min_code_version: nil, committed_at: nil
12
- )
9
+ def initialize(archive:, uuid:, version:, url: nil, hash: nil, min_code_version: nil, slug: nil, committed_at: nil)
13
10
  @archive = archive
14
11
  @uuid = uuid
15
12
  @version = version
16
13
  @url = url
17
14
  @hash = hash
18
- @slug = slug
19
- @style = style
20
15
  @min_code_version = min_code_version
16
+ @slug = slug
21
17
  @committed_at = committed_at
22
18
  end
23
19
 
@@ -61,5 +57,5 @@ class OpenStax::Content::Book
61
57
  @root_book_part ||= OpenStax::Content::BookPart.new(hash: tree, is_root: true, book: self)
62
58
  end
63
59
 
64
- def_delegator :root_book_part, :all_pages
60
+ def_delegators :root_book_part, :all_book_parts, :all_pages
65
61
  end
@@ -45,6 +45,12 @@ class OpenStax::Content::BookPart
45
45
  end
46
46
  end
47
47
 
48
+ def all_book_parts
49
+ @all_book_parts ||= parts.flat_map do |part|
50
+ part.is_a?(OpenStax::Content::BookPart) ? [ part ] + part.all_book_parts : []
51
+ end
52
+ end
53
+
48
54
  def all_pages
49
55
  @all_pages ||= parts.flat_map do |part|
50
56
  part.is_a?(OpenStax::Content::Page) ? [ part ] : part.all_pages
@@ -1,4 +1,5 @@
1
1
  require 'nokogiri'
2
+ require 'ostruct'
2
3
  require_relative 'fragment/reading'
3
4
  require_relative 'custom_css'
4
5
 
@@ -76,7 +77,7 @@ class OpenStax::Content::FragmentSplitter
76
77
  args = { node: node, labels: labels }
77
78
  args[:reference_view_url] = reference_view_url \
78
79
  if fragment_class.is_a? OpenStax::Content::Fragment::Reading
79
- fragment = fragment_class.new args
80
+ fragment = fragment_class.new **args
80
81
  fragment unless fragment.blank?
81
82
  end
82
83
 
@@ -1,5 +1,5 @@
1
1
  module OpenStax
2
2
  module Content
3
- VERSION = '1.3.0'
3
+ VERSION = '1.5.0'
4
4
  end
5
5
  end
@@ -1,8 +1,8 @@
1
1
  module OpenStax
2
2
  module Content
3
3
  class << self
4
- attr_accessor :abl_url, :archive_path, :bucket_name, :domain, :exercises_search_api_url,
5
- :logger, :s3_region, :s3_access_key_id, :s3_secret_access_key
4
+ attr_accessor :abl_url, :archive_path, :bucket_name, :domain, :exercises_search_api_url, :logger, :s3_region,
5
+ :s3_access_key_id, :s3_secret_access_key
6
6
 
7
7
  def configure
8
8
  yield self
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: openstax_content
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.0
4
+ version: 1.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dante Soares
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-08-05 00:00:00.000000000 Z
11
+ date: 2025-05-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: aws-sdk-s3
@@ -183,7 +183,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
183
183
  - !ruby/object:Gem::Version
184
184
  version: '0'
185
185
  requirements: []
186
- rubygems_version: 3.1.4
186
+ rubygems_version: 3.5.21
187
187
  signing_key:
188
188
  specification_version: 4
189
189
  summary: Ruby bindings to read and parse the OpenStax ABL and the content archive