summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSean Molenaar <SMillerDev@users.noreply.github.com>2019-03-06 13:10:37 +0100
committerBenjamin Brahmer <info@b-brahmer.de>2019-03-06 13:10:37 +0100
commit71ba5a3ad1a1c9d867af68e72a4a19acd9ffe08d (patch)
treedebd3e30226df8def9e8f0bb30136c91db84184f
parent6a4e56e7274d85bcbd0e2dcde7a61d8f7a4397ec (diff)
Fix generation commands and make them available in ./occ (#402)
-rw-r--r--.travis.yml19
-rw-r--r--Makefile2
-rw-r--r--appinfo/register_command.php2
-rwxr-xr-x[-rw-r--r--]bin/tools/generate_authors.php10
-rwxr-xr-x[-rw-r--r--]bin/tools/generate_explore.php114
-rw-r--r--composer.json3
-rw-r--r--docs/README.md2
-rw-r--r--docs/explore/README.md6
-rw-r--r--docs/plugins/README.md2
-rw-r--r--lib/AppInfo/Application.php2
-rw-r--r--lib/Command/ExploreGenerator.php94
-rw-r--r--tests/Unit/Command/ExploreGeneratorTest.php200
12 files changed, 392 insertions, 64 deletions
diff --git a/.travis.yml b/.travis.yml
index 4f1030a3c..597298a04 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -2,15 +2,15 @@ sudo: false
dist: trusty
language: php
php:
- - 7.0
- - 7.1
+ - 7.0.33
+ - 7.1.26
- 7.2
- 7.3
- nightly
env:
global:
- - CORE_BRANCH=stable14
+ - CORE_BRANCH=stable15
- MOZ_HEADLESS=1
matrix:
- DB=pgsql
@@ -20,16 +20,6 @@ matrix:
- env: DB=pgsql CORE_BRANCH=master
- php: nightly
include:
- - php: 7.1
- env: DB=sqlite
- - php: 7.2
- env: DB=sqlite
- - php: 7.1
- env: DB=mysql
- - php: 7.2
- env: DB=mysql
- - php: 7.2
- env: DB=pgsql CORE_BRANCH=master
- php: 7.3
env: DB=sqlite
- php: 7.3
@@ -59,9 +49,10 @@ before_script:
- ./occ app:check-code news
- ./occ background:cron # enable default cron
- php -S localhost:8080 &
- - cd apps/news
script:
+ - ./occ news:generate-explore --votes 100 "https://nextcloud.com/blogfeed"
+ - cd apps/news
- make test
after_failure:
diff --git a/Makefile b/Makefile
index e9aa3c1e2..f3c9b602c 100644
--- a/Makefile
+++ b/Makefile
@@ -150,6 +150,7 @@ endif
appstore:
rm -rf $(appstore_build_directory) $(appstore_artifact_directory)
mkdir -p $(appstore_build_directory) $(appstore_artifact_directory)
+ ./bin/tools/generate_authors.php
cp -r \
"appinfo" \
"css" \
@@ -189,3 +190,4 @@ test:
# \Test\TestCase is only allowed to access the db if TRAVIS environment variable is set
env TRAVIS=1 ./vendor/phpunit/phpunit/phpunit -c phpunit.integration.xml --coverage-clover build/php-unit.clover
$(MAKE) phpcs
+ ./bin/tools/generate_authors.php
diff --git a/appinfo/register_command.php b/appinfo/register_command.php
index 723733bf5..3ce99b2a1 100644
--- a/appinfo/register_command.php
+++ b/appinfo/register_command.php
@@ -15,6 +15,7 @@ use OCA\News\Command\Updater\UpdateFeed;
use OCA\News\Command\Updater\AllFeeds;
use OCA\News\Command\Updater\BeforeUpdate;
use OCA\News\Command\Updater\AfterUpdate;
+use OCA\News\Command\ExploreGenerator;
$app = new Application();
$container = $app->getContainer();
@@ -22,3 +23,4 @@ $application->add($container->query(AllFeeds::class));
$application->add($container->query(UpdateFeed::class));
$application->add($container->query(BeforeUpdate::class));
$application->add($container->query(AfterUpdate::class));
+$application->add($container->query(ExploreGenerator::class));
diff --git a/bin/tools/generate_authors.php b/bin/tools/generate_authors.php
index 5c181b031..53d2c8892 100644..100755
--- a/bin/tools/generate_authors.php
+++ b/bin/tools/generate_authors.php
@@ -1,3 +1,4 @@
+#!/usr/bin/env php
<?php
/**
* Nextcloud - News
@@ -15,13 +16,20 @@ exec($cmd, $contributors);
// extract data from git output into an array
$regex = '/^\s*(?P<commit_count>\d+)\s*(?P<name>.*\w)\s*<(?P<email>[^\s]+)>$/';
$contributors = array_map(function ($contributor) use ($regex) {
+ $result = [];
preg_match($regex, $contributor, $result);
return $result;
}, $contributors);
// filter out bots
$contributors = array_filter($contributors, function ($contributor) {
- return strpos($contributor['name'], 'Jenkins') !== 0;
+ if (empty($contributor['name']) || empty($contributor['email'])) {
+ return false;
+ }
+ if (strpos($contributor['email'], 'bot') || strpos($contributor['name'], 'bot')) {
+ return false;
+ }
+ return true;
});
// turn tuples into markdown
diff --git a/bin/tools/generate_explore.php b/bin/tools/generate_explore.php
index 3ad19cb81..766e82db7 100644..100755
--- a/bin/tools/generate_explore.php
+++ b/bin/tools/generate_explore.php
@@ -1,3 +1,4 @@
+#!/usr/bin/env php
<?php
/**
* Nextcloud - News
@@ -8,58 +9,89 @@
* @author Bernhard Posselt <dev@bernhard-posselt.com>
* @copyright Bernhard Posselt 2016
*/
+require_once __DIR__ . '/../../vendor/autoload.php';
+require_once __DIR__ . '/../../../../lib/base.php';
+
+use FeedIo\FeedIo;
+use Favicon\Favicon;
+use OCA\News\AppInfo\Application;
+
+$generator = new ExploreGenerator();
+$generator->parse_argv($argv);
+print(json_encode($generator->read(), JSON_PRETTY_PRINT));
+print("\n");
/**
* This is used for generating a JSON config section for a feed by executing:
* php -f generate_authors.php www.feed.com
+ * @deprecated Use ./occ news:generate-explore instead.
*/
+class ExploreGenerator
+{
+ /**
+ * Feed and favicon fetcher.
+ */
+ protected $reader;
+ protected $favicon;
-require_once __DIR__ . '/../../vendor/autoload.php';
-
-if (count($argv) < 2 || count($argv) > 3) {
- print('Usage: php -f generate_explore http://path.com/feed [vote_count]');
- print("\n");
- exit();
-} elseif (count($argv) === 3) {
- $votes = $argv[2];
-} else {
- $votes = 100;
-}
-
-$url = $argv[1];
-
-try {
- $config = new PicoFeed\Config\Config();
- $reader = new PicoFeed\Reader\Reader($config);
- $resource = $reader->discover($url);
+ /**
+ * Argument data
+ */
+ protected $url;
+ protected $votes;
- $location = $resource->getUrl();
- $content = $resource->getContent();
- $encoding = $resource->getEncoding();
+ /**
+ * Set up class.
+ */
+ public function __construct()
+ {
+ $app = new Application();
+ $container = $app->getContainer();
- $parser = $reader->getParser($location, $content, $encoding);
+ $this->reader = $container->query(FeedIo::class);
+ $this->favicon = new Favicon();
+ }
- $feed = $parser->execute();
+ /**
+ * Parse required arguments.
+ * @param array $argv Arguments to the script.
+ * @return void
+ */
+ public function parse_argv($argv = [])
+ {
+ if (count($argv) < 2 || count($argv) > 3)
+ {
+ print('Usage: php -f generate_explore http://path.com/feed [vote_count]');
+ print("\n");
+ exit(1);
+ }
- $favicon = new PicoFeed\Reader\Favicon($config);
+ $this->votes = (count($argv) === 3) ? $argv[2] : 100;
+ $this->url = $argv[1];
+ }
- $result = [
- "title" => $feed->getTitle(),
- "favicon" => $favicon->find($url),
- "url" => $feed->getSiteUrl(),
- "feed" => $feed->getFeedUrl(),
- "description" => $feed->getDescription(),
- "votes" => $votes
- ];
+ /**
+ * Read the provided feed and return the important data.
+ * @return array Object representation of the feed
+ */
+ public function read()
+ {
+ try {
+ $resource = $this->reader->read($this->url);
+ $feed = $resource->getFeed();
+ $result = [
+ 'title' => $feed->getTitle(),
+ 'favicon' => $this->favicon->get($feed->getLink()),
+ 'url' => $feed->getLink(),
+ 'feed' => $this->url,
+ 'description' => $feed->getDescription(),
+ 'votes' => $this->votes,
+ ];
- if ($feed->getLogo()) {
- $result["image"] = $feed->getLogo();
- }
+ return $result;
+ } catch (\Throwable $ex) {
+ return [ 'error' => $ex->getMessage() ];
+ }
+ }
- print(json_encode($result, JSON_PRETTY_PRINT));
-
-} catch (\Exception $ex) {
- print($ex->getMessage());
}
-
-print("\n"); \ No newline at end of file
diff --git a/composer.json b/composer.json
index de3b7a028..4f1e1690f 100644
--- a/composer.json
+++ b/composer.json
@@ -40,7 +40,8 @@
"pear/net_url2": "2.2.2",
"riimu/kit-pathjoin": "1.2.0",
"debril/feed-io": "^3.0",
- "arthurhoaro/favicon": "^1.2"
+ "arthurhoaro/favicon": "^1.2",
+ "ext-json": "*"
},
"require-dev": {
"phpunit/phpunit": "^6.5",
diff --git a/docs/README.md b/docs/README.md
index 63837701a..62c2dac07 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -9,5 +9,5 @@ As a developer you can interact with the News app in the following ways:
* [Customize the explore section](explore/)
-The News app uses [picoFeed](https://github.com/miniflux/picoFeed) for parsing feeds and full text feeds. picoFeed is a fantastic library so if you [add custom full text configurations](https://github.com/miniflux/picoFeed/blob/master/docs/grabber.markdown#how-to-write-a-grabber-rules-file) or fix bugs, please consider **contributing your changes** back to the library to help others :)
+The News app uses [FeedIO](https://github.com/alexdebril/feed-io) for parsing feeds and full text feeds. FeedIO is a fantastic library so if you contribute or fix bugs, please consider **contributing your changes** back to the library to help others :)
diff --git a/docs/explore/README.md b/docs/explore/README.md
index c377e343b..be77a2a85 100644
--- a/docs/explore/README.md
+++ b/docs/explore/README.md
@@ -22,13 +22,13 @@ The file has the following format:
}
```
-To ease the pain of constructing the JSON object, you can use a small script to automatically create it:
+To ease the pain of constructing the JSON object, you can use a nextcloud command to automatically create it:
- php -f bin/tools/generate_explore.php https://path.com/to/feed.rss
+ php ./occ news:generate-explore https://path.com/to/feed.rss
By passing a second parameter you can set the vote count which determines the sorting on the explore page:
- php -f bin/tools/generate_explore.php https://path.com/to/feed.rss 1000
+ php ./occ news:generate-explore https://path.com/to/feed.rss 1000
You can paste the output directly into the appropriate json file but you may need to add additional categories and commas
diff --git a/docs/plugins/README.md b/docs/plugins/README.md
index bc9e110d0..3d7f968cb 100644
--- a/docs/plugins/README.md
+++ b/docs/plugins/README.md
@@ -8,7 +8,7 @@ There are essentially three different use cases for plugins:
* Dropping in additional CSS or JavaScript
## The Basics
-Whatever plugin you want to create, you first need to create a basic structure. A plugin is basically just an app so you can take advantage of the full [Nextcloud app API](https://docs.nextcloud.org/server/9/developer_manual/app/index.html). If you want you can [take a look at the developer docs](https://docs.nextcloud.org/server/9/developer_manual/app/index.html) or [dig into the tutorial](https://docs.nextcloud.org/server/9/developer_manual/app/tutorial.html).
+Whatever plugin you want to create, you first need to create a basic structure. A plugin is basically just an app so you can take advantage of the full [Nextcloud app API](https://docs.nextcloud.org/server/latest/developer_manual/app/index.html). If you want you can [take a look at the developer docs](https://docs.nextcloud.org/server/latest/developer_manual/app/index.html) or [dig into the tutorial](https://docs.nextcloud.org/server/latest/developer_manual/app/tutorial.html).
However if you just want to start slow, the full process is described below.
diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php
index c70a2fb6f..755f3ea70 100644
--- a/lib/AppInfo/Application.php
+++ b/lib/AppInfo/Application.php
@@ -76,7 +76,6 @@ class Application extends App
return $c->query(MapperFactory::class)->build();
});
-
/**
* App config parser.
*/
@@ -123,7 +122,6 @@ class Application extends App
);
});
-
$container->registerService(Config::class, function (IContainer $c): Config {
$config = new Config(
$c->query('ConfigView'),
diff --git a/lib/Command/ExploreGenerator.php b/lib/Command/ExploreGenerator.php
new file mode 100644
index 000000000..2e1b38e91
--- /dev/null
+++ b/lib/Command/ExploreGenerator.php
@@ -0,0 +1,94 @@
+<?php
+/**
+ * Nextcloud - News
+ *
+ * This file is licensed under the Affero General Public License version 3 or
+ * later. See the COPYING file.
+ *
+ * @author Sean Molenaar <sean@seanmolenaar.eu>
+ * @copyright Sean Molenaar 2019
+ */
+namespace OCA\News\Command;
+
+use FeedIo\FeedIo;
+use Favicon\Favicon;
+
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\OutputInterface;
+
+/**
+ * This is used for generating a JSON config section for a feed by executing:
+ * ./occ news:generate-explore www.feed.com
+ */
+class ExploreGenerator extends Command
+{
+ /**
+ * Feed and favicon fetcher.
+ */
+ protected $reader;
+ protected $favicon;
+
+ /**
+ * Set up class.
+ *
+ * @param FeedIo $reader Feed reader
+ * @param Favicon $favicon Favicon fetcher
+ */
+ public function __construct(FeedIo $reader, Favicon $favicon)
+ {
+ $this->reader = $reader;
+ $this->favicon = $favicon;
+ parent::__construct();
+ }
+
+ protected function configure()
+ {
+ $result = [
+ 'title' => 'Feed - Title',
+ 'favicon' => 'www.web.com/favicon.ico',
+ 'url' => 'www.web.com',
+ 'feed' => 'www.web.com/rss.xml',
+ 'description' => 'description is here',
+ 'votes' => 100,
+ ];
+
+ $this->setName('news:generate-explore')
+ ->setDescription(
+ 'Prints a JSON string which represents the given ' .
+ 'feed URL and votes, e.g.: ' . json_encode($result)
+ )
+ ->addArgument('feed', InputArgument::REQUIRED, 'Feed to parse')
+ ->addOption('votes', null, InputOption::VALUE_OPTIONAL, 'Votes for the feed, defaults to 100');
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output)
+ {
+ $url = $input->getArgument('feed');
+ $votes = $input->getOption('votes');
+ if (!$votes) {
+ $votes = 100;
+ }
+
+ try {
+ $resource = $this->reader->read($url);
+ $feed = $resource->getFeed();
+ $result = [
+ 'title' => $feed->getTitle(),
+ 'favicon' => $this->favicon->get($feed->getLink()),
+ 'url' => $feed->getLink(),
+ 'feed' => $url,
+ 'description' => $feed->getDescription(),
+ 'votes' => $votes,
+ ];
+
+ $output->writeln(json_encode($result, JSON_PRETTY_PRINT));
+ } catch (\Throwable $ex) {
+ $output->writeln('<error>Failed to fetch feed info:</error>');
+ $output->writeln($ex->getMessage());
+ return 1;
+ }
+ }
+}
diff --git a/tests/Unit/Command/ExploreGeneratorTest.php b/tests/Unit/Command/ExploreGeneratorTest.php
new file mode 100644
index 000000000..ac1f2c3c8
--- /dev/null
+++ b/tests/Unit/Command/ExploreGeneratorTest.php
@@ -0,0 +1,200 @@
+<?php
+/**
+ * @author Sean Molenaar <sean@seanmolenaar.eu>
+ *
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OCA\News\Tests\Unit\Command;
+
+use FeedIo\Feed;
+use FeedIo\FeedIo;
+use Favicon\Favicon;
+use FeedIo\Reader\Result;
+use OCA\News\Command\ExploreGenerator;
+
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+use Test\TestCase;
+
+class ExploreGeneratorTest extends TestCase {
+ /** @var \PHPUnit_Framework_MockObject_MockObject */
+ protected $favicon;
+ /** @var \PHPUnit_Framework_MockObject_MockObject */
+ protected $feedio;
+ /** @var \PHPUnit_Framework_MockObject_MockObject */
+ protected $consoleInput;
+ /** @var \PHPUnit_Framework_MockObject_MockObject */
+ protected $consoleOutput;
+
+ /** @var \Symfony\Component\Console\Command\Command */
+ protected $command;
+
+ protected function setUp()
+ {
+ parent::setUp();
+
+ $feedio = $this->feedio = $this->getMockBuilder(FeedIo::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $favicon = $this->favicon = $this->getMockBuilder(Favicon::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $this->consoleInput = $this->getMockBuilder(InputInterface::class)->getMock();
+ $this->consoleOutput = $this->getMockBuilder(OutputInterface::class)->getMock();
+
+ /** @var \FeedIo\FeedIo $feedio, \Favicon\Favicon $favicon */
+ $this->command = new ExploreGenerator($feedio, $favicon);
+ }
+
+ /**
+ * Test a valid feed will write the data needed.
+ */
+ public function testValidFeed()
+ {
+ $result = $this->getMockBuilder(Result::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $feed = $this->getMockBuilder(Feed::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $feed->expects($this->once())
+ ->method('getTitle')
+ ->willReturn('Title');
+ $feed->expects($this->exactly(2))
+ ->method('getLink')
+ ->willReturn('Link');
+ $feed->expects($this->once())
+ ->method('getDescription')
+ ->willReturn('Description');
+
+ $result->expects($this->once())
+ ->method('getFeed')
+ ->willReturn($feed);
+
+ $this->favicon->expects($this->once())
+ ->method('get')
+ ->willReturn('https://feed.io/favicon.ico');
+
+ $this->feedio->expects($this->once())
+ ->method('read')
+ ->with('https://feed.io/rss.xml')
+ ->willReturn($result);
+
+ $this->consoleInput->expects($this->once())
+ ->method('getArgument')
+ ->with('feed')
+ ->willReturn('https://feed.io/rss.xml');
+
+ $this->consoleInput->expects($this->once())
+ ->method('getOption')
+ ->with('votes')
+ ->willReturn(100);
+
+ $this->consoleOutput->expects($this->once())
+ ->method('writeln')
+ ->with($this->stringContains('https:\/\/feed.io\/rss.xml'));
+
+ self::invokePrivate($this->command, 'execute', [$this->consoleInput, $this->consoleOutput]);
+ }
+
+ /**
+ * Test a valid feed will write the data needed.
+ */
+ public function testFailingFeed()
+ {
+
+ $this->favicon->expects($this->never())
+ ->method('get');
+
+ $this->feedio->expects($this->once())
+ ->method('read')
+ ->with('https://feed.io/rss.xml')
+ ->will($this->throwException(new \Exception('Failure')));
+
+ $this->consoleInput->expects($this->once())
+ ->method('getArgument')
+ ->with('feed')
+ ->willReturn('https://feed.io/rss.xml');
+
+ $this->consoleInput->expects($this->once())
+ ->method('getOption')
+ ->with('votes')
+ ->willReturn(100);
+
+ $this->consoleOutput->expects($this->at(0))
+ ->method('writeln')
+ ->with($this->stringContains('<error>'));
+
+ $this->consoleOutput->expects($this->at(1))
+ ->method('writeln')
+ ->with($this->stringContains('Failure'));
+
+ self::invokePrivate($this->command, 'execute', [$this->consoleInput, $this->consoleOutput]);
+ }
+
+ /**
+ * Test a valid feed and votes will write the data needed.
+ */
+ public function testFeedWithVotes()
+ {
+ $result = $this->getMockBuilder(Result::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $feed = $this->getMockBuilder(Feed::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $feed->expects($this->once())
+ ->method('getTitle')
+ ->willReturn('Title');
+ $feed->expects($this->exactly(2))
+ ->method('getLink')
+ ->willReturn('Link');
+ $feed->expects($this->once())
+ ->method('getDescription')
+ ->willReturn('Description');
+
+ $result->expects($this->once())
+ ->method('getFeed')
+ ->willReturn($feed);
+
+ $this->favicon->expects($this->once())
+ ->method('get')
+ ->willReturn('https://feed.io/favicon.ico');
+
+ $this->feedio->expects($this->once())
+ ->method('read')
+ ->with('https://feed.io/rss.xml')
+ ->willReturn($result);
+
+ $this->consoleInput->expects($this->once())
+ ->method('getArgument')
+ ->with('feed')
+ ->willReturn('https://feed.io/rss.xml');
+
+ $this->consoleInput->expects($this->once())
+ ->method('getOption')
+ ->with('votes')
+ ->willReturn(200);
+
+ $this->consoleOutput->expects($this->once())
+ ->method('writeln')
+ ->with($this->stringContains('200'));
+
+ self::invokePrivate($this->command, 'execute', [$this->consoleInput, $this->consoleOutput]);
+ }
+}