diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1d1ebf34..10f4c0d1 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -13,13 +13,13 @@ jobs: steps: - uses: actions/checkout@ee0669bd1cc54295c223e0bb666b733df41de1c5 # v2.7.0 - name: Set up Ruby - uses: ruby/setup-ruby@540484a3c0f308b08619664ec40bf6c371d172c3 # v1.205.0 + uses: ruby/setup-ruby@32110d4e311bd8996b2a82bf2a43b714ccc91777 # v1.221.0 with: bundler-cache: true # runs 'bundle install' and caches installed gems automatically - name: Run tests run: bundle exec rake - name: Deploy to Heroku - uses: akhileshns/heroku-deploy@581dd286c962b6972d427fcf8980f60755c15520 # v3.13.15 + uses: akhileshns/heroku-deploy@e3eb99d45a8e2ec5dca08735e089607befa4bf28 # v3.14.15 with: heroku_api_key: ${{secrets.HEROKU_API_KEY}} heroku_app_name: "devdocs" diff --git a/.github/workflows/schedule-doc-report.yml b/.github/workflows/schedule-doc-report.yml index 2fd07e3d..137cec66 100644 --- a/.github/workflows/schedule-doc-report.yml +++ b/.github/workflows/schedule-doc-report.yml @@ -11,7 +11,7 @@ jobs: steps: - uses: actions/checkout@ee0669bd1cc54295c223e0bb666b733df41de1c5 # v2.7.0 - name: Set up Ruby - uses: ruby/setup-ruby@540484a3c0f308b08619664ec40bf6c371d172c3 # v1.205.0 + uses: ruby/setup-ruby@32110d4e311bd8996b2a82bf2a43b714ccc91777 # v1.221.0 with: bundler-cache: true # runs 'bundle install' and caches installed gems automatically - name: Generate report diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9caecfd7..70c3d4d2 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -11,7 +11,7 @@ jobs: steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Set up Ruby - uses: ruby/setup-ruby@540484a3c0f308b08619664ec40bf6c371d172c3 # v1.205.0 + uses: ruby/setup-ruby@32110d4e311bd8996b2a82bf2a43b714ccc91777 # v1.221.0 with: bundler-cache: true # runs 'bundle install' and caches installed gems automatically - name: Run tests diff --git a/.ruby-version b/.ruby-version index 9c25013d..4d9d11cf 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -3.3.6 +3.4.2 diff --git a/.tool-versions b/.tool-versions index 5aa8e0c3..ae5ecdb2 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1 +1 @@ -ruby 3.3.6 +ruby 3.4.2 diff --git a/COPYRIGHT b/COPYRIGHT index 9c520b87..374054bd 100644 --- a/COPYRIGHT +++ b/COPYRIGHT @@ -1,4 +1,4 @@ -Copyright 2013-2024 Thibaut Courouble and other contributors +Copyright 2013-2025 Thibaut Courouble and other contributors This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/Dockerfile b/Dockerfile index 7ec33c21..92c59642 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM ruby:3.3.6 +FROM ruby:3.4.2 ENV LANG=C.UTF-8 ENV ENABLE_SERVICE_WORKER=true diff --git a/Dockerfile-alpine b/Dockerfile-alpine index 1d258221..ce605eaa 100644 --- a/Dockerfile-alpine +++ b/Dockerfile-alpine @@ -1,4 +1,4 @@ -FROM ruby:3.3.6-alpine +FROM ruby:3.4.2-alpine ENV LANG=C.UTF-8 ENV ENABLE_SERVICE_WORKER=true diff --git a/Gemfile b/Gemfile index 9079b2c9..be8dcb0b 100644 --- a/Gemfile +++ b/Gemfile @@ -1,5 +1,5 @@ source 'https://rubygems.org' -ruby '3.3.6' +ruby '3.4.2' gem 'activesupport', require: false gem 'html-pipeline' diff --git a/Gemfile.lock b/Gemfile.lock index df868a37..9ccdd742 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -36,7 +36,8 @@ GEM exifr (1.4.0) ffi (1.15.5) fspath (3.1.2) - highline (2.0.3) + highline (3.1.2) + reline html-pipeline (2.14.3) activesupport (>= 2) nokogiri (>= 1.4) @@ -53,6 +54,7 @@ GEM image_optim (~> 0.19) image_size (3.3.0) in_threads (1.6.0) + io-console (0.8.0) logger (1.6.2) method_source (1.0.0) mini_portile2 (2.8.8) @@ -61,7 +63,7 @@ GEM mustermann (3.0.3) ruby2_keywords (~> 0.0.1) newrelic_rpm (8.16.0) - nokogiri (1.17.2) + nokogiri (1.18.3) mini_portile2 (~> 2.8.2) racc (~> 1.4) options (2.3.2) @@ -76,7 +78,7 @@ GEM byebug (~> 11.0) pry (>= 0.13, < 0.15) racc (1.8.1) - rack (2.2.10) + rack (2.2.11) rack-protection (3.2.0) base64 (>= 0.1.0) rack (~> 2.2, >= 2.2.4) @@ -88,6 +90,8 @@ GEM rb-inotify (0.10.1) ffi (~> 1.0) redcarpet (3.6.0) + reline (0.6.0) + io-console (~> 0.5) rexml (3.3.9) rouge (1.11.1) rr (3.1.1) @@ -126,14 +130,14 @@ GEM strings-ansi (0.2.0) terminal-table (3.0.2) unicode-display_width (>= 1.1.1, < 3) - terser (1.2.4) + terser (1.2.5) execjs (>= 0.3.0, < 3) thin (1.8.2) daemons (~> 1.0, >= 1.0.9) eventmachine (~> 1.0, >= 1.0.4) rack (>= 1, < 3) thor (1.3.2) - tilt (2.4.0) + tilt (2.6.0) tty-pager (0.14.0) strings (~> 0.2.0) tty-screen (~> 0.8) @@ -187,7 +191,7 @@ DEPENDENCIES yajl-ruby RUBY VERSION - ruby 3.3.6p108 + ruby 3.4.2p28 BUNDLED WITH 2.4.6 diff --git a/README.md b/README.md index 97df20a1..d4b66959 100644 --- a/README.md +++ b/README.md @@ -25,12 +25,12 @@ Unless you wish to contribute to the project, we recommend using the hosted vers The easiest way to run DevDocs locally is using Docker: ```sh -docker run --name devdocs -d -p 9292:9292 ghcr.io/freecodcamp/devdocs:latest +docker run --name devdocs -d -p 9292:9292 ghcr.io/freecodecamp/devdocs:latest ``` This will start DevDocs at [localhost:9292](http://localhost:9292). We provide both regular and Alpine-based images: -- `ghcr.io/freecodcamp/devdocs:latest` - Standard image -- `ghcr.io/freecodcamp/devdocs:latest-alpine` - Alpine-based (smaller size) +- `ghcr.io/freecodecamp/devdocs:latest` - Standard image +- `ghcr.io/freecodecamp/devdocs:latest-alpine` - Alpine-based (smaller size) Images are automatically built and updated monthly with the latest documentation. @@ -195,7 +195,7 @@ Made something cool? Feel free to open a PR to add a new row to this table! You ## Copyright / License -Copyright 2013–2024 Thibaut Courouble and [other contributors](https://github.com/freeCodeCamp/devdocs/graphs/contributors) +Copyright 2013–2025 Thibaut Courouble and [other contributors](https://github.com/freeCodeCamp/devdocs/graphs/contributors) This software is licensed under the terms of the Mozilla Public License v2.0. See the [COPYRIGHT](./COPYRIGHT) and [LICENSE](./LICENSE) files. diff --git a/assets/javascripts/lib/license.js b/assets/javascripts/lib/license.js index 5c0d5fa4..15b42c98 100644 --- a/assets/javascripts/lib/license.js +++ b/assets/javascripts/lib/license.js @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 Thibaut Courouble and other contributors + * Copyright 2013-2025 Thibaut Courouble and other contributors * * This source code is licensed under the terms of the Mozilla * Public License, v. 2.0, a copy of which may be obtained at: diff --git a/assets/javascripts/lib/util.js b/assets/javascripts/lib/util.js index 3aa301bc..5226262d 100644 --- a/assets/javascripts/lib/util.js +++ b/assets/javascripts/lib/util.js @@ -457,13 +457,13 @@ $.noop = function () {}; $.popup = function (value) { try { + window.open(value.href || value, "_blank", "noopener"); + } catch (error) { const win = window.open(); if (win.opener) { win.opener = null; } win.location = value.href || value; - } catch (error) { - window.open(value.href || value, "_blank"); } }; @@ -526,21 +526,3 @@ $.highlight = function (el, options) { el.classList.add(options.className); setTimeout(() => el.classList.remove(options.className), options.delay); }; - -$.copyToClipboard = function (string) { - let result; - const textarea = document.createElement("textarea"); - textarea.style.position = "fixed"; - textarea.style.opacity = 0; - textarea.value = string; - document.body.appendChild(textarea); - try { - textarea.select(); - result = !!document.execCommand("copy"); - } catch (error) { - result = false; - } finally { - document.body.removeChild(textarea); - } - return result; -}; diff --git a/assets/javascripts/news.json b/assets/javascripts/news.json index 052f4918..2c393d97 100644 --- a/assets/javascripts/news.json +++ b/assets/javascripts/news.json @@ -1,4 +1,8 @@ [ + [ + "2025-02-16", + "New documentation: OpenLayers" + ], [ "2024-11-23", "New documentation: DuckDB" diff --git a/assets/javascripts/templates/pages/about_tmpl.js b/assets/javascripts/templates/pages/about_tmpl.js index 683bb546..e3142da5 100644 --- a/assets/javascripts/templates/pages/about_tmpl.js +++ b/assets/javascripts/templates/pages/about_tmpl.js @@ -32,7 +32,7 @@ app.templates.aboutPage = function () {

- Copyright 2013–2024 Thibaut Courouble and other contributors
+ Copyright 2013–2025 Thibaut Courouble and other contributors
This software is licensed under the terms of the Mozilla Public License v2.0.
You may obtain a copy of the source code at github.com/freeCodeCamp/devdocs.
For more information, see the COPYRIGHT diff --git a/assets/javascripts/views/content/entry_page.js b/assets/javascripts/views/content/entry_page.js index 95920878..961d90e1 100644 --- a/assets/javascripts/views/content/entry_page.js +++ b/assets/javascripts/views/content/entry_page.js @@ -96,16 +96,9 @@ app.views.EntryPage = class EntryPage extends app.View { return content; } - const links = (() => { - const result = []; - for (var link in this.entry.doc.links) { - var url = this.entry.doc.links[link]; - result.push( - `${EntryPage.LINKS[link]}`, - ); - } - return result; - })(); + const links = Object.entries(this.entry.doc.links).map(([link, url]) => { + return `${EntryPage.LINKS[link]}`; + }); return `

${content}`; } @@ -203,8 +196,8 @@ app.views.EntryPage = class EntryPage extends app.View { } restore() { - let path; - if (this.cacheMap[(path = this.entry.filePath())]) { + const path = this.entry.filePath(); + if (this.cacheMap[[path]]) { this.render(this.cacheMap[path], true); return true; } @@ -217,18 +210,17 @@ app.views.EntryPage = class EntryPage extends app.View { this.load(); } else if (target.classList.contains("_pre-clip")) { $.stopEvent(event); - target.classList.add( - $.copyToClipboard(target.parentNode.textContent) - ? "_pre-clip-success" - : "_pre-clip-error", + navigator.clipboard.writeText(target.parentNode.textContent).then( + () => target.classList.add("_pre-clip-success"), + () => target.classList.add("_pre-clip-error"), ); setTimeout(() => (target.className = "_pre-clip"), 2000); } } onAltC() { - let link; - if (!(link = this.find("._attribution:last-child ._attribution-link"))) { + const link = this.find("._attribution:last-child ._attribution-link"); + if (!link) { return; } console.log(link.href + location.hash); @@ -236,8 +228,8 @@ app.views.EntryPage = class EntryPage extends app.View { } onAltO() { - let link; - if (!(link = this.find("._attribution:last-child ._attribution-link"))) { + const link = this.find("._attribution:last-child ._attribution-link"); + if (!link) { return; } this.delay(() => $.popup(link.href + location.hash)); diff --git a/assets/stylesheets/application.css.scss b/assets/stylesheets/application.css.scss index 3823cdc3..b45e7b60 100644 --- a/assets/stylesheets/application.css.scss +++ b/assets/stylesheets/application.css.scss @@ -3,7 +3,7 @@ //= depend_on sprites/docs.json /*! - * Copyright 2013-2024 Thibaut Courouble and other contributors + * Copyright 2013-2025 Thibaut Courouble and other contributors * * This source code is licensed under the terms of the Mozilla * Public License, v. 2.0, a copy of which may be obtained at: @@ -97,6 +97,7 @@ 'pages/nushell', 'pages/octave', 'pages/openjdk', + 'pages/openlayers', 'pages/perl', 'pages/phalcon', 'pages/phaser', diff --git a/assets/stylesheets/pages/_mdn.scss b/assets/stylesheets/pages/_mdn.scss index 51f2b6c5..9155df45 100644 --- a/assets/stylesheets/pages/_mdn.scss +++ b/assets/stylesheets/pages/_mdn.scss @@ -28,6 +28,9 @@ p > code, li > code { @extend %label; } + details { @extend %box; } + summary > div { display: inline; } + > .note, .notecard, // MDN 2021 .notice, diff --git a/assets/stylesheets/pages/_openlayers.scss b/assets/stylesheets/pages/_openlayers.scss new file mode 100644 index 00000000..50628b2f --- /dev/null +++ b/assets/stylesheets/pages/_openlayers.scss @@ -0,0 +1,10 @@ +._openlayers { + @extend %simple; + .nameContainer { + @extend %block-label; + > * { display: inline-block; margin: 0; } + > .tag-source { float: right; } + } + .card { @extend %box; margin-bottom: 1rem; padding: 1rem; } + .signature, .type-signature { @extend %code; } +} diff --git a/docs/file-scrapers.md b/docs/file-scrapers.md index 55ba5552..7a6e0a5f 100644 --- a/docs/file-scrapers.md +++ b/docs/file-scrapers.md @@ -254,6 +254,17 @@ done ### Nokogiri ### Ruby / Minitest + +```sh +git clone https://github.com/seattlerb/minitest +cd minitest/ +bundle install +bundle add rdoc hoe +bundle exec rak docs +cd .. +cp -r minitest/docs $DEVDOCS/docs/minitest +``` + ### Ruby on Rails * Download a release at https://github.com/rails/rails/releases or clone https://github.com/rails/rails.git (checkout to the branch of the rails' version that is going to be scraped) * Open `railties/lib/rails/api/task.rb` and comment out any code related to sdoc (`configure_sdoc`) diff --git a/lib/docs/filters/bazel/clean_html.rb b/lib/docs/filters/bazel/clean_html.rb index d772908e..ca827b36 100644 --- a/lib/docs/filters/bazel/clean_html.rb +++ b/lib/docs/filters/bazel/clean_html.rb @@ -7,8 +7,10 @@ module Docs css('devsite-feature-tooltip').remove css('devsite-thumb-rating').remove css('devsite-toc').remove + css('devsite-feedback').remove css('a.button-with-icon').remove css('button.devsite-heading-link').remove + css('.devsite-article-body > span:first-child[style="float: right; line-height: 36px"]').remove doc end diff --git a/lib/docs/filters/core/normalize_urls.rb b/lib/docs/filters/core/normalize_urls.rb index cd1888a8..9834b32d 100644 --- a/lib/docs/filters/core/normalize_urls.rb +++ b/lib/docs/filters/core/normalize_urls.rb @@ -42,7 +42,7 @@ module Docs def fix_url(url) if context[:redirections] url = URL.parse(url) - path = url.path.downcase + path = url.path ? url.path.downcase : nil if context[:redirections].key?(path) url.path = context[:redirections][path] diff --git a/lib/docs/filters/crystal/entries.rb b/lib/docs/filters/crystal/entries.rb index 3727bdf2..151efbdc 100644 --- a/lib/docs/filters/crystal/entries.rb +++ b/lib/docs/filters/crystal/entries.rb @@ -16,8 +16,12 @@ module Docs name else return at_css('h1').content.strip unless at_css('.type-name') - name = at_css('.type-name').children.last.content.strip + name = at_css('.type-name').children.reject { |n| n.matches?('.kind') } + name.map! { |n| n.text.strip } + name.reject! &:empty? + name = name.join name.remove! %r{\(.*\)} + name.strip! name end end diff --git a/lib/docs/filters/elixir/clean_html.rb b/lib/docs/filters/elixir/clean_html.rb index 6783afb2..2d656ff6 100644 --- a/lib/docs/filters/elixir/clean_html.rb +++ b/lib/docs/filters/elixir/clean_html.rb @@ -34,7 +34,9 @@ module Docs node.name = 'h3' node['id'] = id - source_href = node.at_css('a.icon-action[title="View Source"]').attr('href') + a = node.at_css('a.icon-action[title="View Source"]') + a ||= node.at_css('a.icon-action[aria-label="View Source"]') + source_href = a.attr('href') node.content = node.at_css('.signature').inner_text node << %(Source) diff --git a/lib/docs/filters/express/clean_html.rb b/lib/docs/filters/express/clean_html.rb index 0d9c5127..01a63909 100644 --- a/lib/docs/filters/express/clean_html.rb +++ b/lib/docs/filters/express/clean_html.rb @@ -3,7 +3,7 @@ module Docs class CleanHtmlFilter < Filter def call i = 1 - n = at_css("#navmenu a[href='#{result[:path].split('/').last}']").parent + n = at_css("#navmenu .submenu-content a[href='#{result[:path].split('/').last}']").parent i += 1 while n && n = n.previous_element at_css('h1')['data-level'] = i diff --git a/lib/docs/filters/openlayers/clean_html.rb b/lib/docs/filters/openlayers/clean_html.rb new file mode 100644 index 00000000..713306c2 --- /dev/null +++ b/lib/docs/filters/openlayers/clean_html.rb @@ -0,0 +1,18 @@ +module Docs + class Openlayers + class CleanHtmlFilter < Filter + def call + @doc = at_css('section') + + at_css('h2').name = 'h1' if at_css('h2') + + css('pre.prettyprint').each do |node| + node['data-language'] = node['class'].include?('html') ? 'html' : 'js' + node.content = node.content + end + + doc + end + end + end +end diff --git a/lib/docs/filters/openlayers/entries.rb b/lib/docs/filters/openlayers/entries.rb new file mode 100644 index 00000000..a325bdcd --- /dev/null +++ b/lib/docs/filters/openlayers/entries.rb @@ -0,0 +1,23 @@ +module Docs + class Openlayers + class EntriesFilter < Docs::EntriesFilter + def get_name + at_css('h2').text.split('~').last.strip + end + + def get_type + slug[/ol_([^_]+)_/, 1] or 'ol' + end + + def additional_entries + css('h4.name').each_with_object [] do |node, entries| + node['id'] = node.previous_element['id'] + next if node.at_css('.inherited') + name = node.children.find {|n| n.text? }.text.strip + name.prepend "#{self.name}." + entries << [name, node['id']] + end + end + end + end +end diff --git a/lib/docs/filters/phpunit/clean_html.rb b/lib/docs/filters/phpunit/clean_html.rb index 5e36704c..dacb1587 100644 --- a/lib/docs/filters/phpunit/clean_html.rb +++ b/lib/docs/filters/phpunit/clean_html.rb @@ -2,7 +2,7 @@ module Docs class Phpunit class CleanHtmlFilter < Filter def call - @doc = at_css('.section') if not root_page? + @doc = at_css('section') if not root_page? css('pre').each do |node| node['class'] = 'highlight' diff --git a/lib/docs/filters/rdoc/container.rb b/lib/docs/filters/rdoc/container.rb index aea2b0f5..4732b02a 100644 --- a/lib/docs/filters/rdoc/container.rb +++ b/lib/docs/filters/rdoc/container.rb @@ -13,8 +13,11 @@ module Docs meta = Nokogiri::XML::Node.new 'dl', doc.document meta['class'] = 'meta' - if parent = at_css('#parent-class-section') - meta << %(
Parent:
#{parent.at_css('.link').inner_html.strip}
) + parent = at_css('#parent-class-section') + if parent && link = parent.at_css('.link') + meta << %(
Parent:
#{link.inner_html.strip}
) + elsif parent && link = parent.at_css('a') + meta << %(
Parent:
#{link.to_html}
) end if includes = at_css('#includes-section') diff --git a/lib/docs/filters/scala/entries_v2.rb b/lib/docs/filters/scala/entries_v2.rb index ca3b6d12..6f044644 100644 --- a/lib/docs/filters/scala/entries_v2.rb +++ b/lib/docs/filters/scala/entries_v2.rb @@ -71,7 +71,7 @@ module Docs # name from the HTML because companion object classes may be broken out into # their own entries (by the source documentation). When that happens, # we want to group these classes (like `scala.reflect.api.Annotations.Annotation`) - # under the package name, and not the fully-qualfied name which would + # under the package name, and not the fully-qualified name which would # include the companion object. def package_name name = package_drop_last(slug_parts) diff --git a/lib/docs/filters/scikit_image/entries.rb b/lib/docs/filters/scikit_image/entries.rb index 108eb2ce..f3ec975b 100644 --- a/lib/docs/filters/scikit_image/entries.rb +++ b/lib/docs/filters/scikit_image/entries.rb @@ -3,7 +3,8 @@ module Docs class EntriesFilter < Docs::EntriesFilter def get_name name = at_css('h1').content.strip - name.remove! "\u{00b6}" + name.delete_suffix! "¶" + name.delete_suffix! "#" if slug.start_with?('api') name.remove! 'Module: ' diff --git a/lib/docs/filters/scikit_learn/clean_html.rb b/lib/docs/filters/scikit_learn/clean_html.rb index 530df12d..afcd2c2a 100644 --- a/lib/docs/filters/scikit_learn/clean_html.rb +++ b/lib/docs/filters/scikit_learn/clean_html.rb @@ -2,6 +2,7 @@ module Docs class ScikitLearn class CleanHtmlFilter < Filter def call + @doc = at_css('main article', 'main') if root_page? css('.row').each do |node| html = '
' diff --git a/lib/docs/filters/tensorflow/clean_html.rb b/lib/docs/filters/tensorflow/clean_html.rb index 6d594d70..cc153320 100644 --- a/lib/docs/filters/tensorflow/clean_html.rb +++ b/lib/docs/filters/tensorflow/clean_html.rb @@ -46,6 +46,9 @@ module Docs node.replace("

#{node.to_html}

") end + css('span[slot="popout-heading"]').remove + css('span[slot="popout-contents"]').remove + doc end end diff --git a/lib/docs/filters/vueuse/entries.rb b/lib/docs/filters/vueuse/entries.rb index a3d0af56..2bd0e05d 100644 --- a/lib/docs/filters/vueuse/entries.rb +++ b/lib/docs/filters/vueuse/entries.rb @@ -248,6 +248,10 @@ module Docs "text": "usePreferredReducedMotion", "link": "/core/usePreferredReducedMotion/" }, + { + "text": "usePreferredReducedTransparency", + "link": "/core/usePreferredReducedTransparency/" + }, { "text": "useScreenOrientation", "link": "/core/useScreenOrientation/" @@ -264,6 +268,10 @@ module Docs "text": "useShare", "link": "/core/useShare/" }, + { + "text": "useSSRWidth", + "link": "/core/useSSRWidth/" + }, { "text": "useStyleTag", "link": "/core/useStyleTag/" @@ -313,6 +321,10 @@ module Docs "text": "onClickOutside", "link": "/core/onClickOutside/" }, + { + "text": "onElementRemoval", + "link": "/core/onElementRemoval/" + }, { "text": "onKeyStroke", "link": "/core/onKeyStroke/" @@ -788,6 +800,10 @@ module Docs { "text": "Time", "items": [ + { + "text": "useCountdown", + "link": "/core/useCountdown/" + }, { "text": "useDateFormat", "link": "/shared/useDateFormat/" diff --git a/lib/docs/scrapers/bazel.rb b/lib/docs/scrapers/bazel.rb index 3f2f9447..fe37137a 100644 --- a/lib/docs/scrapers/bazel.rb +++ b/lib/docs/scrapers/bazel.rb @@ -12,6 +12,11 @@ module Docs Licensed under the Creative Commons Attribution 4.0 License, and code samples are licensed under the Apache 2.0 License. HTML + version '8.0' do + self.release = '8.0.0' + self.base_url = 'https://bazel.build/versions/8.0.0/reference/be/' + end + version '7.0' do self.release = '7.0.0' self.base_url = 'https://bazel.build/versions/7.0.0/reference/be/' diff --git a/lib/docs/scrapers/crystal.rb b/lib/docs/scrapers/crystal.rb index c06d2fa7..0ca20ab4 100644 --- a/lib/docs/scrapers/crystal.rb +++ b/lib/docs/scrapers/crystal.rb @@ -2,7 +2,7 @@ module Docs class Crystal < UrlScraper include MultipleBaseUrls self.type = 'crystal' - self.release = '1.14.0' + self.release = '1.15.1' self.base_urls = [ "https://crystal-lang.org/api/#{release}/", "https://crystal-lang.org/reference/#{release[0..2]}/", @@ -21,6 +21,7 @@ module Docs options[:skip_patterns] = [ %r{\ACrystal/System/}, + %r{\ACrystal/PointerPairingHeap/}, %r{\AIO/Evented.html\z}, %r{\ARegex/PCRE2.html\z} ] @@ -34,7 +35,7 @@ module Docs HTML else <<-HTML - © 2012–2024 Manas Technology Solutions.
+ © 2012–2025 Manas Technology Solutions.
Licensed under the Apache License, Version 2.0. HTML end diff --git a/lib/docs/scrapers/docker.rb b/lib/docs/scrapers/docker.rb index 070796ae..bfc420a3 100644 --- a/lib/docs/scrapers/docker.rb +++ b/lib/docs/scrapers/docker.rb @@ -96,7 +96,7 @@ module Docs def get_latest_version(opts) doc = fetch_doc('https://docs.docker.com/engine/release-notes/', opts) - latest_version = doc.at_css('.DocSearch-content > h2 > a').content.strip + latest_version = doc.at_css('h2.scroll-mt-20 > a').content.strip latest_version.rpartition(' ')[-1] end end diff --git a/lib/docs/scrapers/elixir.rb b/lib/docs/scrapers/elixir.rb index 47f361e6..14b3c164 100644 --- a/lib/docs/scrapers/elixir.rb +++ b/lib/docs/scrapers/elixir.rb @@ -30,6 +30,18 @@ module Docs "https://hexdocs.pm/mix/#{self.class.release}/Mix.html" ] end + version '1.18' do + self.release = '1.18.1' + self.base_urls = [ + "https://hexdocs.pm/elixir/#{release}/", + "https://hexdocs.pm/eex/#{release}/", + "https://hexdocs.pm/ex_unit/#{release}/", + "https://hexdocs.pm/iex/#{release}/", + "https://hexdocs.pm/logger/#{release}/", + "https://hexdocs.pm/mix/#{release}/" + ] + end + version '1.17' do self.release = '1.17.2' self.base_urls = [ diff --git a/lib/docs/scrapers/express.rb b/lib/docs/scrapers/express.rb index 2f0a93ce..724b771f 100644 --- a/lib/docs/scrapers/express.rb +++ b/lib/docs/scrapers/express.rb @@ -2,7 +2,7 @@ module Docs class Express < UrlScraper self.name = 'Express' self.type = 'express' - self.release = '4.18.1' + self.release = '4.21.2' self.base_url = 'https://expressjs.com/en/' self.root_path = '4x/api.html' self.initial_paths = %w( diff --git a/lib/docs/scrapers/fastapi.rb b/lib/docs/scrapers/fastapi.rb index ed857934..d5b3f4e5 100644 --- a/lib/docs/scrapers/fastapi.rb +++ b/lib/docs/scrapers/fastapi.rb @@ -2,7 +2,7 @@ module Docs class Fastapi < UrlScraper self.name = 'FastAPI' self.type = 'fastapi' - self.release = '0.111.1' + self.release = '0.115.6' self.base_url = 'https://fastapi.tiangolo.com/' self.root_path = '/' self.links = { diff --git a/lib/docs/scrapers/flask.rb b/lib/docs/scrapers/flask.rb index 08655e7a..11229a25 100755 --- a/lib/docs/scrapers/flask.rb +++ b/lib/docs/scrapers/flask.rb @@ -18,6 +18,11 @@ module Docs Licensed under the BSD 3-clause License. HTML + version do + self.release = '3.1.1' + self.base_url = "https://flask.palletsprojects.com/en/stable/" + end + version '3.0' do self.release = '3.0.x' self.base_url = "https://flask.palletsprojects.com/en/#{self.release}/" diff --git a/lib/docs/scrapers/git.rb b/lib/docs/scrapers/git.rb index d8a289c1..a3a0fff8 100644 --- a/lib/docs/scrapers/git.rb +++ b/lib/docs/scrapers/git.rb @@ -1,7 +1,7 @@ module Docs class Git < UrlScraper self.type = 'git' - self.release = '2.47.1' + self.release = '2.48.1' self.base_url = 'https://git-scm.com/docs' self.initial_paths = %w(/git.html) self.links = { diff --git a/lib/docs/scrapers/go.rb b/lib/docs/scrapers/go.rb index 767db080..2b795a8c 100644 --- a/lib/docs/scrapers/go.rb +++ b/lib/docs/scrapers/go.rb @@ -1,7 +1,7 @@ module Docs class Go < UrlScraper self.type = 'go' - self.release = '1.23.0' + self.release = '1.24.0' self.base_url = 'https://golang.org/pkg/' self.links = { home: 'https://golang.org/', @@ -10,10 +10,19 @@ module Docs # Run godoc locally, since https://golang.org/pkg/ redirects to https://pkg.go.dev/std with rate limiting / scraping protection. - # podman run --net host --rm -it docker.io/golang:1.23.0 + # podman run --net host --rm -it docker.io/golang:1.24.0 #podman# go install golang.org/x/tools/cmd/godoc@latest #podman# rm -r /usr/local/go/test/ #podman# godoc -http 0.0.0.0:6060 -v + + # or using alpine + # podman run --net host --rm -it alpine:latest + #podman# apk add curl + #podman# curl -LO https://go.dev/dl/go1.24.0.linux-amd64.tar.gz + #podman# tar xf go1.24.0.linux-amd64.tar.gz + #podman# go/bin/go install golang.org/x/tools/cmd/godoc@latest + #podman# /root/go/bin/godoc -http 0.0.0.0:6060 -v + self.base_url = 'http://localhost:6060/pkg/' html_filters.push 'clean_local_urls' diff --git a/lib/docs/scrapers/haskell.rb b/lib/docs/scrapers/haskell.rb index 9cfe7f66..8d822ece 100755 --- a/lib/docs/scrapers/haskell.rb +++ b/lib/docs/scrapers/haskell.rb @@ -59,7 +59,7 @@ module Docs end version '9' do - self.release = '9.4.2' + self.release = '9.12.1' self.base_url = "https://downloads.haskell.org/~ghc/#{release}/docs/" options[:container] = ->(filter) {filter.subpath.start_with?('users_guide') ? '.document' : '#content'} end diff --git a/lib/docs/scrapers/jquery/jquery_ui.rb b/lib/docs/scrapers/jquery/jquery_ui.rb index d4f66fef..eca46fe1 100644 --- a/lib/docs/scrapers/jquery/jquery_ui.rb +++ b/lib/docs/scrapers/jquery/jquery_ui.rb @@ -2,7 +2,7 @@ module Docs class JqueryUi < Jquery self.name = 'jQuery UI' self.slug = 'jqueryui' - self.release = '1.13.0' + self.release = '1.14.1' self.base_url = 'https://api.jqueryui.com' self.root_path = '/category/all' diff --git a/lib/docs/scrapers/mdn/dom.rb b/lib/docs/scrapers/mdn/dom.rb index 3023e20c..322d65d0 100644 --- a/lib/docs/scrapers/mdn/dom.rb +++ b/lib/docs/scrapers/mdn/dom.rb @@ -1,6 +1,6 @@ module Docs class Dom < Mdn - # release = '2023-08-20' + # release = '2025-01-30' self.name = 'Web APIs' self.slug = 'dom' self.base_url = 'https://developer.mozilla.org/en-US/docs/Web/API' diff --git a/lib/docs/scrapers/mdn/javascript.rb b/lib/docs/scrapers/mdn/javascript.rb index e6aabdb1..5c9f4d0c 100644 --- a/lib/docs/scrapers/mdn/javascript.rb +++ b/lib/docs/scrapers/mdn/javascript.rb @@ -3,7 +3,7 @@ module Docs prepend FixInternalUrlsBehavior prepend FixRedirectionsBehavior - # release = '2024-11-18' + # release = '2025-01-30' self.name = 'JavaScript' self.base_url = 'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference' self.links = { diff --git a/lib/docs/scrapers/meteor.rb b/lib/docs/scrapers/meteor.rb index a758d154..04cb929e 100644 --- a/lib/docs/scrapers/meteor.rb +++ b/lib/docs/scrapers/meteor.rb @@ -47,8 +47,7 @@ module Docs end def get_latest_version(opts) - doc = fetch_doc('https://docs.meteor.com/#/full/', opts) - doc.at_css('select.version-select > option').content + get_npm_version('meteor', opts) end end end diff --git a/lib/docs/scrapers/nix.rb b/lib/docs/scrapers/nix.rb index 5be27ffa..b2ee5c87 100644 --- a/lib/docs/scrapers/nix.rb +++ b/lib/docs/scrapers/nix.rb @@ -23,9 +23,7 @@ module Docs def get_latest_version(opts) doc = fetch_doc('https://nixos.org/manual/nix/stable/', opts) - json = JSON.parse(doc.at_css('body')['data-nix-channels']) - channel = json.find { |c| c['channel'] == 'stable' } - channel['version'] + doc.at_css('a.active')['href'].scan(/([0-9.]+)/)[0][0] end end end diff --git a/lib/docs/scrapers/openlayers.rb b/lib/docs/scrapers/openlayers.rb new file mode 100644 index 00000000..1c957fbd --- /dev/null +++ b/lib/docs/scrapers/openlayers.rb @@ -0,0 +1,24 @@ +module Docs + class Openlayers < UrlScraper + self.name = 'OpenLayers' + self.type = 'openlayers' + self.slug = 'openlayers' + self.release = '10.4.0' + self.base_url = "https://openlayers.org/en/latest/apidoc/" + self.links = { + home: 'https://openlayers.org/', + code: 'https://github.com/openlayers/openlayers' + } + + html_filters.push 'openlayers/entries', 'openlayers/clean_html' + + options[:attribution] = <<-HTML + © 2005-present, OpenLayers Contributors All rights reserved. + Licensed under the BSD 2-Clause License. + HTML + + def get_latest_version(opts) + get_npm_version('ol', opts) + end + end +end diff --git a/lib/docs/scrapers/phpunit.rb b/lib/docs/scrapers/phpunit.rb index 7bd1c603..12efbbfc 100644 --- a/lib/docs/scrapers/phpunit.rb +++ b/lib/docs/scrapers/phpunit.rb @@ -17,12 +17,21 @@ module Docs options[:title] = false options[:attribution] = <<-HTML - © 2005–2020 Sebastian Bergmann
+ © 2005–2025 Sebastian Bergmann
Licensed under the Creative Commons Attribution 3.0 Unported License. HTML FILTERS = %w(phpunit/clean_html phpunit/entries title) + version do + self.release = '12.0' + self.base_url = "https://docs.phpunit.de/en/#{release}/" + + html_filters.push FILTERS + + options[:container] = '.document' + end + version '9' do self.release = '9.5' self.base_url = "https://phpunit.readthedocs.io/en/#{release}/" @@ -77,8 +86,8 @@ module Docs def get_latest_version(opts) doc = fetch_doc('https://phpunit.readthedocs.io/', opts) - label = doc.at_css('.rst-current-version').content.strip - label.scan(/v: ([0-9.]+)/)[0][0] + label = doc.at_css('meta[name="readthedocs-version-slug"]')["content"] + label end end diff --git a/lib/docs/scrapers/rdoc/minitest.rb b/lib/docs/scrapers/rdoc/minitest.rb index 2ff12cf4..a5023435 100644 --- a/lib/docs/scrapers/rdoc/minitest.rb +++ b/lib/docs/scrapers/rdoc/minitest.rb @@ -1,14 +1,9 @@ module Docs class Minitest < Rdoc - # Instructions: - # 1. Run "gem update rdoc hoe" - # 2. Download the source code at https://github.com/seattlerb/minitest - # 3. Run "rake docs" (in the Minitest directory) - # 4. Copy the "docs" directory to "docs/minitest" self.name = 'Ruby / Minitest' self.slug = 'minitest' - self.release = '5.20.0' + self.release = '5.25.4' self.links = { code: 'https://github.com/minitest/minitest' } diff --git a/lib/docs/scrapers/rdoc/rails.rb b/lib/docs/scrapers/rdoc/rails.rb index 414bd4a5..03aad4a3 100644 --- a/lib/docs/scrapers/rdoc/rails.rb +++ b/lib/docs/scrapers/rdoc/rails.rb @@ -75,6 +75,10 @@ module Docs end end + version '8.0' do + self.release = '8.0.1' + end + version '7.2' do self.release = '7.2.1' end diff --git a/lib/docs/scrapers/rdoc/ruby.rb b/lib/docs/scrapers/rdoc/ruby.rb index eef26f41..630cb81e 100644 --- a/lib/docs/scrapers/rdoc/ruby.rb +++ b/lib/docs/scrapers/rdoc/ruby.rb @@ -63,12 +63,16 @@ module Docs /\AXMP/] options[:attribution] = <<-HTML - Ruby Core © 1993–2022 Yukihiro Matsumoto
+ Ruby Core © 1993–2024 Yukihiro Matsumoto
Licensed under the Ruby License.
Ruby Standard Library © contributors
Licensed under their own licenses. HTML + version '3.4' do + self.release = '3.4.1' + end + version '3.3' do self.release = '3.3.0' end diff --git a/lib/docs/scrapers/redis.rb b/lib/docs/scrapers/redis.rb index d4c62b42..9c8e03b1 100644 --- a/lib/docs/scrapers/redis.rb +++ b/lib/docs/scrapers/redis.rb @@ -22,8 +22,7 @@ module Docs def get_latest_version(opts) body = fetch('http://download.redis.io/redis-stable/00-RELEASENOTES', opts) - body = body.lines[1..-1].join - body.scan(/Redis ([0-9.]+)/)[0][0] + body.scan(/Redis Community Edition ([0-9.]+)/)[0][0] end private diff --git a/lib/docs/scrapers/rust.rb b/lib/docs/scrapers/rust.rb index 23419f58..fbbb1415 100644 --- a/lib/docs/scrapers/rust.rb +++ b/lib/docs/scrapers/rust.rb @@ -3,7 +3,7 @@ module Docs class Rust < UrlScraper self.type = 'rust' - self.release = '1.83.0' + self.release = '1.84.1' self.base_url = 'https://doc.rust-lang.org/' self.root_path = 'book/index.html' self.initial_paths = %w( diff --git a/lib/docs/scrapers/sass.rb b/lib/docs/scrapers/sass.rb index d7b5954a..d0c0a935 100644 --- a/lib/docs/scrapers/sass.rb +++ b/lib/docs/scrapers/sass.rb @@ -1,7 +1,7 @@ module Docs class Sass < UrlScraper self.type = 'yard' - self.release = '1.82.9' + self.release = '1.85.0' self.base_url = 'https://sass-lang.com/documentation' self.root_path = 'index.html' self.links = { @@ -17,16 +17,16 @@ module Docs options[:trailing_slash] = false options[:attribution] = <<-HTML - © 2006–2024 the Sass team, and numerous contributors
+ © 2006–2025 the Sass team, and numerous contributors
Licensed under the MIT License. HTML - - private def get_latest_version(opts) get_npm_version('sass', opts) end + private + def parse(response) response.body.gsub! ' ', ' ' end diff --git a/lib/docs/scrapers/scikit_image.rb b/lib/docs/scrapers/scikit_image.rb index 368e03f7..66014c0f 100644 --- a/lib/docs/scrapers/scikit_image.rb +++ b/lib/docs/scrapers/scikit_image.rb @@ -3,8 +3,10 @@ module Docs self.name = 'scikit-image' self.slug = 'scikit_image' self.type = 'sphinx' - self.release = '0.18.1' - self.base_url = 'https://scikit-image.org/docs/0.18.x/' + self.release = '0.25.0' + v = self.release[/\d+\.\d+/] + self.base_url = "https://scikit-image.org/docs/#{v}.x/" + self.initial_paths = %w(/ /api/ /user_guide/) self.links = { home: 'https://scikit-image.org/', code: 'https://github.com/scikit-image/scikit-image' @@ -12,7 +14,7 @@ module Docs html_filters.push 'scikit_image/entries', 'sphinx/clean_html' - options[:container] = '.span9' + options[:container] = 'main article' options[:skip] = %w(api_changes.html) options[:only_patterns] = [/\Aapi/, /\Auser_guide/] diff --git a/lib/docs/scrapers/scikit_learn.rb b/lib/docs/scrapers/scikit_learn.rb index 45268c4b..564b5a30 100644 --- a/lib/docs/scrapers/scikit_learn.rb +++ b/lib/docs/scrapers/scikit_learn.rb @@ -3,8 +3,9 @@ module Docs self.name = 'scikit-learn' self.slug = 'scikit_learn' self.type = 'sphinx' - self.release = '1.1.3' - self.base_url = "https://scikit-learn.org/1.1/" + self.release = '1.6.1' + v = self.release[/\d+\.\d+/] + self.base_url = "https://scikit-learn.org/#{v}/" self.root_path = 'index.html' self.force_gzip = true self.links = { @@ -14,7 +15,6 @@ module Docs html_filters.push 'scikit_learn/entries', 'scikit_learn/clean_html', 'sphinx/clean_html', 'title' - options[:container] = ->(filter) { filter.root_page? ? 'body > .container' : '#sk-page-content-wrapper > .body' } options[:skip] = %w(modules/generated/sklearn.experimental.enable_iterative_imputer.html modules/generated/sklearn.experimental.enable_hist_gradient_boosting.html) options[:only_patterns] = [/\Amodules/, /\Adatasets/, /\Atutorial/, /\Aauto_examples/] @@ -24,7 +24,7 @@ module Docs options[:max_image_size] = 256_000 options[:attribution] = <<-HTML - © 2007–2022 The scikit-learn developers
+ © 2007–2025 The scikit-learn developers
Licensed under the 3-clause BSD License. HTML diff --git a/lib/docs/scrapers/tensorflow/tensorflow.rb b/lib/docs/scrapers/tensorflow/tensorflow.rb index fbd68abc..9d3cdb58 100644 --- a/lib/docs/scrapers/tensorflow/tensorflow.rb +++ b/lib/docs/scrapers/tensorflow/tensorflow.rb @@ -19,6 +19,11 @@ module Docs Code samples licensed under the Apache 2.0 License. HTML + version do + self.release = "2.18.0" + self.base_url = "https://www.tensorflow.org/api_docs/python/tf" + end + version '2.9' do self.release = "2.9.1" self.base_url = "https://www.tensorflow.org/versions/r#{version}/api_docs/python/tf" diff --git a/lib/docs/scrapers/tensorflow/tensorflow_cpp.rb b/lib/docs/scrapers/tensorflow/tensorflow_cpp.rb index e7dfbe9e..6fe3c86a 100644 --- a/lib/docs/scrapers/tensorflow/tensorflow_cpp.rb +++ b/lib/docs/scrapers/tensorflow/tensorflow_cpp.rb @@ -3,6 +3,11 @@ module Docs self.name = 'TensorFlow C++' self.slug = 'tensorflow_cpp' + version do + self.release = "2.18.0" + self.base_url = "https://www.tensorflow.org/api_docs/cc" + end + version '2.9' do self.release = "2.9.1" self.base_url = "https://www.tensorflow.org/versions/r#{version}/api_docs/cc" diff --git a/lib/docs/scrapers/trio.rb b/lib/docs/scrapers/trio.rb index 141a408e..701f9c23 100644 --- a/lib/docs/scrapers/trio.rb +++ b/lib/docs/scrapers/trio.rb @@ -1,7 +1,7 @@ module Docs class Trio < UrlScraper self.type = 'simple' - self.release = '0.22.2' + self.release = '0.29.0' self.base_url = "https://trio.readthedocs.io/en/v#{self.release}/" self.root_path = 'index.html' self.links = { @@ -25,7 +25,7 @@ module Docs def get_latest_version(opts) doc = fetch_doc('https://trio.readthedocs.io/en/stable/', opts) - doc.at_css('.rst-other-versions a[href^="/en/v"]').content[1..-1] + doc.at_css('div.trio-version').content[0..-1] end end end diff --git a/lib/docs/scrapers/vite.rb b/lib/docs/scrapers/vite.rb index 937d0916..4ca7b9d3 100644 --- a/lib/docs/scrapers/vite.rb +++ b/lib/docs/scrapers/vite.rb @@ -22,7 +22,7 @@ module Docs html_filters.push 'vite/entries', 'vite/clean_html' version do - self.release = '6.0.1' + self.release = '6.1.0' self.base_url = 'https://vite.dev/' end diff --git a/lib/docs/scrapers/vueuse.rb b/lib/docs/scrapers/vueuse.rb index 3933b60c..d9e6748c 100644 --- a/lib/docs/scrapers/vueuse.rb +++ b/lib/docs/scrapers/vueuse.rb @@ -22,7 +22,7 @@ module Docs Licensed under the MIT License. HTML - self.release = '12.0.0' + self.release = '12.5.0' self.base_url = 'https://vueuse.org/' self.initial_paths = %w(functions.html) html_filters.push 'vueuse/entries', 'vite/clean_html', 'vueuse/clean_html' diff --git a/lib/docs/scrapers/werkzeug.rb b/lib/docs/scrapers/werkzeug.rb index 70379744..d331ecd6 100755 --- a/lib/docs/scrapers/werkzeug.rb +++ b/lib/docs/scrapers/werkzeug.rb @@ -17,6 +17,11 @@ module Docs Licensed under the BSD 3-clause License. HTML + version do + self.release = '3.1.1' + self.base_url = "https://werkzeug.palletsprojects.com/en/latest/" + end + version '3.0' do self.release = '3.0.x' self.base_url = "https://werkzeug.palletsprojects.com/en/#{self.release}/" diff --git a/lib/tasks/docs.thor b/lib/tasks/docs.thor index 5b85a8b0..6e953e4f 100644 --- a/lib/tasks/docs.thor +++ b/lib/tasks/docs.thor @@ -239,7 +239,7 @@ class DocsCLI < Thor ['index.json', 'meta.json'].each do |filename| json = "https://documents.devdocs.io/#{doc.path}/#{filename}?#{time}" begin - URI.open(json) do |file| + URI.open(json, "Accept-Encoding" => "identity") do |file| mutex.synchronize do path = File.join(dir, filename) File.write(path, file.read) diff --git a/public/icons/docs/openlayers/16.png b/public/icons/docs/openlayers/16.png new file mode 100644 index 00000000..fe90ebfb Binary files /dev/null and b/public/icons/docs/openlayers/16.png differ diff --git a/public/icons/docs/openlayers/16@2x.png b/public/icons/docs/openlayers/16@2x.png new file mode 100644 index 00000000..c39658a1 Binary files /dev/null and b/public/icons/docs/openlayers/16@2x.png differ diff --git a/public/icons/docs/openlayers/SOURCE b/public/icons/docs/openlayers/SOURCE new file mode 100644 index 00000000..74be1eaa --- /dev/null +++ b/public/icons/docs/openlayers/SOURCE @@ -0,0 +1,2 @@ +https://github.com/openlayers +https://avatars.githubusercontent.com/u/240579?s=64 diff --git a/test/lib/docs/core/manifest_test.rb b/test/lib/docs/core/manifest_test.rb index 60d5ba90..1a06cf28 100644 --- a/test/lib/docs/core/manifest_test.rb +++ b/test/lib/docs/core/manifest_test.rb @@ -64,7 +64,7 @@ class ManifestTest < Minitest::Spec it "includes the doc's meta representation" do json = manifest.as_json assert_equal 1, json.length - assert_equal "{\"name\"=>\"Test\", \"db_size\"=>776533, :attribution=>\"foo\", :alias=>nil}", json[0].to_s + assert_equal "{\"name\" => \"Test\", \"db_size\" => 776533, attribution: \"foo\", alias: nil}", json[0].to_s end end