summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorOleg Pykhalov <go.wigust@gmail.com>2018-09-04 02:51:26 +0300
committerOleg Pykhalov <go.wigust@gmail.com>2018-09-20 13:13:25 +0300
commitd133eb0a34fdabe87be40cd908118fba6219c838 (patch)
tree547940e0d547f771f4c4e97fcb78ab1fbc097286
downloadguile-feed-d133eb0a34fdabe87be40cd908118fba6219c838.tar.gz
Initial commit.
-rw-r--r--.dir-locals.el88
-rw-r--r--Makefile.am87
-rw-r--r--README2
-rwxr-xr-xbootstrap5
-rwxr-xr-xbuild-aux/pre-inst-env.in28
-rw-r--r--configure.ac55
-rw-r--r--feed.scm3
-rw-r--r--feed/scripts/rss.scm107
-rw-r--r--feed/ui.scm118
-rw-r--r--guix.scm129
-rw-r--r--rss/feeds.scm177
-rw-r--r--rss/feeds/code.scm20
-rw-r--r--rss/feeds/emacs.scm14
-rw-r--r--rss/feeds/game.scm8
-rw-r--r--rss/feeds/hardware.scm8
-rw-r--r--rss/feeds/news.scm14
-rw-r--r--rss/feeds/planet.scm26
-rw-r--r--rss/feeds/video.ru.scm38
-rw-r--r--rss/feeds/video.scm92
-rwxr-xr-xscripts/feed.in27
-rw-r--r--tests/rss.scm40
21 files changed, 1086 insertions, 0 deletions
diff --git a/.dir-locals.el b/.dir-locals.el
new file mode 100644
index 0000000..cf0cbf5
--- /dev/null
+++ b/.dir-locals.el
@@ -0,0 +1,88 @@
+;; Per-directory local variables for GNU Emacs 23 and later.
+
+((nil
+ . ((fill-column . 78)
+ (tab-width . 8)
+ (sentence-end-double-space . t)))
+ (c-mode . ((c-file-style . "gnu")))
+ (scheme-mode
+ .
+ ((indent-tabs-mode . nil)
+ (eval . (put 'eval-when 'scheme-indent-function 1))
+ (eval . (put 'call-with-prompt 'scheme-indent-function 1))
+ (eval . (put 'test-assert 'scheme-indent-function 1))
+ (eval . (put 'test-assertm 'scheme-indent-function 1))
+ (eval . (put 'test-equalm 'scheme-indent-function 1))
+ (eval . (put 'test-equal 'scheme-indent-function 1))
+ (eval . (put 'test-eq 'scheme-indent-function 1))
+ (eval . (put 'call-with-input-string 'scheme-indent-function 1))
+ (eval . (put 'guard 'scheme-indent-function 1))
+ (eval . (put 'lambda* 'scheme-indent-function 1))
+ (eval . (put 'substitute* 'scheme-indent-function 1))
+ (eval . (put 'match-record 'scheme-indent-function 2))
+
+ ;; 'modify-phases' and its keywords.
+ (eval . (put 'modify-phases 'scheme-indent-function 1))
+ (eval . (put 'replace 'scheme-indent-function 1))
+ (eval . (put 'add-before 'scheme-indent-function 2))
+ (eval . (put 'add-after 'scheme-indent-function 2))
+
+ (eval . (put 'modify-services 'scheme-indent-function 1))
+ (eval . (put 'with-directory-excursion 'scheme-indent-function 1))
+ (eval . (put 'package 'scheme-indent-function 0))
+ (eval . (put 'origin 'scheme-indent-function 0))
+ (eval . (put 'build-system 'scheme-indent-function 0))
+ (eval . (put 'bag 'scheme-indent-function 0))
+ (eval . (put 'graft 'scheme-indent-function 0))
+ (eval . (put 'operating-system 'scheme-indent-function 0))
+ (eval . (put 'file-system 'scheme-indent-function 0))
+ (eval . (put 'manifest-entry 'scheme-indent-function 0))
+ (eval . (put 'manifest-pattern 'scheme-indent-function 0))
+ (eval . (put 'substitute-keyword-arguments 'scheme-indent-function 1))
+ (eval . (put 'with-store 'scheme-indent-function 1))
+ (eval . (put 'with-error-handling 'scheme-indent-function 0))
+ (eval . (put 'with-mutex 'scheme-indent-function 1))
+ (eval . (put 'with-atomic-file-output 'scheme-indent-function 1))
+ (eval . (put 'call-with-compressed-output-port 'scheme-indent-function 2))
+ (eval . (put 'call-with-decompressed-port 'scheme-indent-function 2))
+ (eval . (put 'call-with-gzip-input-port 'scheme-indent-function 1))
+ (eval . (put 'call-with-gzip-output-port 'scheme-indent-function 1))
+ (eval . (put 'signature-case 'scheme-indent-function 1))
+ (eval . (put 'emacs-batch-eval 'scheme-indent-function 0))
+ (eval . (put 'emacs-batch-edit-file 'scheme-indent-function 1))
+ (eval . (put 'emacs-substitute-sexps 'scheme-indent-function 1))
+ (eval . (put 'emacs-substitute-variables 'scheme-indent-function 1))
+ (eval . (put 'with-derivation-narinfo 'scheme-indent-function 1))
+ (eval . (put 'with-derivation-substitute 'scheme-indent-function 2))
+
+ (eval . (put 'mlambda 'scheme-indent-function 1))
+ (eval . (put 'mlambdaq 'scheme-indent-function 1))
+ (eval . (put 'syntax-parameterize 'scheme-indent-function 1))
+ (eval . (put 'with-monad 'scheme-indent-function 1))
+ (eval . (put 'mbegin 'scheme-indent-function 1))
+ (eval . (put 'mwhen 'scheme-indent-function 1))
+ (eval . (put 'munless 'scheme-indent-function 1))
+ (eval . (put 'mlet* 'scheme-indent-function 2))
+ (eval . (put 'mlet 'scheme-indent-function 2))
+ (eval . (put 'run-with-store 'scheme-indent-function 1))
+ (eval . (put 'run-with-state 'scheme-indent-function 1))
+ (eval . (put 'wrap-program 'scheme-indent-function 1))
+ (eval . (put 'with-imported-modules 'scheme-indent-function 1))
+ (eval . (put 'with-extensions 'scheme-indent-function 1))
+
+ (eval . (put 'with-database 'scheme-indent-function 2))
+
+ (eval . (put 'call-with-container 'scheme-indent-function 1))
+ (eval . (put 'container-excursion 'scheme-indent-function 1))
+ (eval . (put 'eventually 'scheme-indent-function 1))
+
+ (eval . (put 'call-with-progress-reporter 'scheme-indent-function 1))
+
+ ;; This notably allows '(' in Paredit to not insert a space when the
+ ;; preceding symbol is one of these.
+ (eval . (modify-syntax-entry ?~ "'"))
+ (eval . (modify-syntax-entry ?$ "'"))
+ (eval . (modify-syntax-entry ?+ "'"))))
+ (emacs-lisp-mode . ((indent-tabs-mode . nil)))
+ (texinfo-mode . ((indent-tabs-mode . nil)
+ (fill-column . 72))))
diff --git a/Makefile.am b/Makefile.am
new file mode 100644
index 0000000..c57d934
--- /dev/null
+++ b/Makefile.am
@@ -0,0 +1,87 @@
+# Guile Feed --- Feed command-line interface.
+# Copyright © 2018 Oleg Pykhalov <go.wigust@gmail.com>
+#
+# This file is part of Guile Feed.
+#
+# Guile Feed is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# Guile Feed 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
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Guile Feed. If not, see <http://www.gnu.org/licenses/>.
+
+bin_SCRIPTS = scripts/feed
+
+# Handle substitution of fully-expanded Autoconf variables.
+do_subst = $(SED) \
+ -e 's,[@]GUILE[@],$(GUILE),g'
+
+scripts/feed: scripts/feed.in Makefile
+ $(AM_V_at)rm -f $@ $@-t
+ $(AM_V_at)$(MKDIR_P) "$(@D)"
+ $(AM_V_GEN)$(do_subst) < "$(srcdir)/$@.in" > "$@-t"
+ $(AM_V_at)chmod a+x,a-w "$@-t" && mv -f "$@-t" "$@"
+
+AM_V_GUILEC = $(AM_V_GUILEC_$(V))
+AM_V_GUILEC_ = $(AM_V_GUILEC_$(AM_DEFAULT_VERBOSITY))
+AM_V_GUILEC_0 = @echo " GUILEC " $@;
+
+MODULES = \
+ feed.scm \
+ feed/scripts/rss.scm \
+ feed/ui.scm \
+ rss/feeds.scm \
+ rss/feeds/code.scm \
+ rss/feeds/emacs.scm \
+ rss/feeds/game.scm \
+ rss/feeds/hardware.scm \
+ rss/feeds/news.scm \
+ rss/feeds/planet.scm \
+ rss/feeds/video.ru.scm \
+ rss/feeds/video.scm
+
+GO_FILES = $(MODULES:%.scm=%.go)
+
+nobase_dist_guilemodule_DATA = $(MODULES)
+nobase_nodist_guileccache_DATA = $(GO_FILES)
+
+# Make sure source files are installed first, so that the mtime of
+# installed compiled files is greater than that of installed source
+# files. See
+# <http://lists.gnu.org/archive/html/guile-devel/2010-07/msg00125.html>.
+guile_install_go_files = install-nobase_nodist_guileccacheDATA
+$(guile_install_go_files): install-nobase_dist_guilemoduleDATA
+
+GUILEC_ENV = \
+ GUILE_AUTO_COMPILE=0
+
+GUILEC_OPTS = \
+ -Warity-mismatch \
+ -Wformat \
+ -Wunbound-variable
+
+if GUIX_DIR
+ guix_module_part=":$(guixmoduledir)"
+ guix_ccache_part=":$(guixccachedir)"
+else
+ guix_module_part=
+ guix_ccache_part=
+endif
+
+# Guile PATHs shouldn't be unset: some guix modules want to load
+# (gnutls) module, that's why 'guix' package propagates 'gnutls', i.e.
+# a directory with gnutls module is placed in GUILE_LOAD_PATH.
+
+GUILEC_ENV += \
+ GUILE_LOAD_PATH="$(abs_srcdir)$(guix_module_part):$$GUILE_LOAD_PATH" \
+ GUILE_LOAD_COMPILED_PATH="$(abs_builddir)$(guix_ccache_part):$$GUILE_LOAD_COMPILED_PATH"
+
+$(GO_FILES): %.go: %.scm
+ -$(AM_V_GUILEC) $(GUILEC_ENV) \
+ $(GUILD) compile $(GUILEC_OPTS) --output=$@ $<
diff --git a/README b/README
new file mode 100644
index 0000000..6882865
--- /dev/null
+++ b/README
@@ -0,0 +1,2 @@
+Guile program for feeds generation. E.g. it could generate
+elfeed-feeds for Elfeed Emacs package. \ No newline at end of file
diff --git a/bootstrap b/bootstrap
new file mode 100755
index 0000000..cb774bc
--- /dev/null
+++ b/bootstrap
@@ -0,0 +1,5 @@
+#!/bin/sh
+# Create the build system.
+
+set -e -x
+exec autoreconf -vfi
diff --git a/build-aux/pre-inst-env.in b/build-aux/pre-inst-env.in
new file mode 100755
index 0000000..c2aaf6d
--- /dev/null
+++ b/build-aux/pre-inst-env.in
@@ -0,0 +1,28 @@
+#!/bin/sh
+
+# Copyright © 2018 Oleg Pykhalov <go.wigust@gmail.com>
+#
+# Usage: ./pre-inst-env COMMAND ARG...
+#
+# Run COMMAND in a pre-installation environment. Typical use is
+# "./pre-inst-env feed build hello".
+
+# By default we may end up with absolute directory names that contain '..',
+# which get into $GUILE_LOAD_PATH, leading to '..' in the module file names
+# recorded by Guile. To avoid that, make sure we get a real absolute
+# directory name. Additionally, use '-P' to get the canonical directory name
+# so that Guile's 'relative' %file-port-name-canonicalization can actually
+# work (see <http://bugs.gnu.org/17935>.)
+abs_top_srcdir="`cd "@abs_top_srcdir@" > /dev/null; pwd -P`"
+abs_top_builddir="`cd "@abs_top_builddir@" > /dev/null; pwd -P`"
+
+GUILE_LOAD_COMPILED_PATH="$abs_top_builddir${GUILE_LOAD_COMPILED_PATH:+:}$GUILE_LOAD_COMPILED_PATH"
+GUILE_LOAD_PATH="$abs_top_builddir:$abs_top_srcdir${GUILE_LOAD_PATH:+:}$GUILE_LOAD_PATH"
+export GUILE_LOAD_COMPILED_PATH GUILE_LOAD_PATH
+
+# Define $PATH so that `feed' and friends are easily found.
+
+PATH="$abs_top_builddir/scripts:$abs_top_builddir:$PATH"
+export PATH
+
+exec "$@"
diff --git a/configure.ac b/configure.ac
new file mode 100644
index 0000000..1e836f3
--- /dev/null
+++ b/configure.ac
@@ -0,0 +1,55 @@
+AC_INIT([Guile-Feed], [0.0.1], [go.wigust@gmail.com], [guile-feed],
+ [https://gitlab.com/wigust/guile-feed])
+
+PKG_CHECK_MODULES([GUILE], [guile-2.2])
+GUILE_PKG([2.2])
+GUILE_PROGS
+AC_ARG_WITH([guile-site-dir],
+ [AS_HELP_STRING([--with-guile-site-dir],
+ [installation directory for Guile (*.scm) files])],
+ [guilemoduledir="$withval"],
+ [guilemoduledir="$datarootdir/guile/site/$GUILE_EFFECTIVE_VERSION"])
+AC_SUBST([guilemoduledir])
+AC_MSG_CHECKING([for the Guile module directory])
+AC_MSG_RESULT([$guilemoduledir])
+
+AC_ARG_WITH([guile-site-ccache-dir],
+ [AS_HELP_STRING([--with-guile-site-ccache-dir],
+ [installation directory for Guile compiled (*.go) files])],
+ [guileccachedir="$withval"],
+ [guileccachedir="$libdir/guile/$GUILE_EFFECTIVE_VERSION/site-ccache"])
+AC_SUBST([guileccachedir])
+AC_MSG_CHECKING([for the Guile ccache directory])
+AC_MSG_RESULT([$guileccachedir])
+
+AC_ARG_WITH([guix-site-dir],
+ [AS_HELP_STRING([--with-guix-site-dir],
+ [directory with Guix modules (*.scm files)])],
+ [guixmoduledir="$withval"],
+ [guixmoduledir=""])
+AC_SUBST([guixmoduledir])
+
+AC_ARG_WITH([guix-site-ccache-dir],
+ [AS_HELP_STRING([--with-guix-site-ccache-dir],
+ [directory with Guix compiled (*.go) files])],
+ [guixccachedir="$withval"],
+ [guixccachedir=""])
+
+dnl If ccache dir is not specified, set it to the module dir.
+AS_IF([test "x$guixccachedir" = "x" -a \
+ "x$guixmoduledir" != "x"],
+ [guixccachedir="$guixmoduledir"])
+
+AC_SUBST([guixccachedir])
+
+AM_CONDITIONAL([GUIX_DIR], [test "x$guixmoduledir" != "x"])
+
+AC_PROG_SED
+
+AM_INIT_AUTOMAKE([foreign])
+AC_CONFIG_FILES([Makefile])
+
+AC_CONFIG_FILES([pre-inst-env:build-aux/pre-inst-env.in],
+ [chmod +x pre-inst-env])
+
+AC_OUTPUT
diff --git a/feed.scm b/feed.scm
new file mode 100644
index 0000000..b1f204b
--- /dev/null
+++ b/feed.scm
@@ -0,0 +1,3 @@
+;;; Copyright © 2018 Oleg Pykhalov <go.wigust@gmail.com>
+
+(define-module (feed))
diff --git a/feed/scripts/rss.scm b/feed/scripts/rss.scm
new file mode 100644
index 0000000..0cce788
--- /dev/null
+++ b/feed/scripts/rss.scm
@@ -0,0 +1,107 @@
+;;; Guile Feed --- Feed command-line interface.
+;;; Copyright © 2018 Oleg Pykhalov <go.wigust@gmail.com>
+;;;
+;;; This file is part of Guile Feed.
+;;;
+;;; Guile Feed is free software; you can redistribute it and/or modify
+;;; it under the terms of the GNU General Public License as published
+;;; by the Free Software Foundation; either version 3 of the License,
+;;; or (at your option) any later version.
+;;;
+;;; Guile Feed 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
+;;; General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU General Public License
+;;; along with Guile Feed. If not, see
+;;; <http://www.gnu.org/licenses/>.
+
+(define-module (feed scripts rss)
+ #:use-module (guix scripts)
+ #:use-module (guix ui)
+ #:use-module (ice-9 match)
+ #:use-module (ice-9 pretty-print)
+ #:use-module (rss feeds)
+ #:use-module (srfi srfi-1)
+ #:use-module (srfi srfi-11)
+ #:use-module (srfi srfi-26)
+ #:use-module (srfi srfi-37)
+ #:export (feed-rss))
+
+
+;;;
+;;; Command-line options.
+;;;
+
+(define %default-options
+ ;; Alist of default option values.
+ '())
+
+(define %options
+ (list (option '(#\h "help") #f #f
+ (lambda args
+ (show-help)
+ (exit 0)))
+ (option '(#\o "output-file") #t #f
+ (lambda (opt name arg result)
+ (alist-cons 'output-file arg result)))
+ (option '(#\s "search") #t #f
+ (lambda (opt name arg result)
+ (alist-cons 'search arg result)))
+ (option '("show") #t #t
+ (lambda (opt name arg result)
+ (alist-cons 'show arg result)))))
+
+(define (show-help)
+ (display "Usage: guix rss -o FILE\n")
+ (newline)
+ (display "
+ -h, --help display this help and exit")
+ (display "
+ -V, --version display version information and exit")
+ (display "
+ -o, --output-file=FILE convert rss to LISP FILE")
+ (display "
+ -s, --search=REGEXP search in name and description using REGEXP")
+ (display "
+ --show=RSS show details about RSS")
+ (newline))
+
+
+;;;
+;;; Entry point.
+;;;
+
+(define (feed-rss . args)
+ (define (parse-options)
+ ;; Return the alist of option values.
+ (parse-command-line args %options (list %default-options)
+ #:build-options? #f))
+ (let* ((opts (parse-options))
+ (output (assoc-ref opts 'output-file))
+ (search (assoc-ref opts 'search))
+ (show (assoc-ref opts 'show)))
+ (cond (show
+ (leave-on-EPIPE
+ (for-each (cute feed->recutils <>)
+ (find-feeds-by-name show))))
+ (search
+ (let* ((patterns (filter-map (match-lambda
+ (('search . rx) rx)
+ (_ #f))
+ opts))
+ (regexps (map (cut make-regexp* <> regexp/icase) patterns)))
+ (leave-on-EPIPE
+ (let-values (((rss scores)
+ (find-feeds-by-description regexps)))
+ (for-each (lambda (rss score)
+ (feed->recutils rss
+ #:extra-fields
+ `((relevance . ,score))))
+ rss
+ scores)))
+ #t))
+ (output
+ (with-output-to-file output
+ (lambda _ (pretty-print (feeds->lisp))))))))
diff --git a/feed/ui.scm b/feed/ui.scm
new file mode 100644
index 0000000..3e1fdfe
--- /dev/null
+++ b/feed/ui.scm
@@ -0,0 +1,118 @@
+;;; Guile Feed --- Feed command-line interface.
+;;; Copyright © 2018 Oleg Pykhalov <go.wigust@gmail.com>
+;;;
+;;; This file is part of Guile Feed.
+;;;
+;;; Guile Feed is free software; you can redistribute it and/or modify
+;;; it under the terms of the GNU General Public License as published
+;;; by the Free Software Foundation; either version 3 of the License,
+;;; or (at your option) any later version.
+;;;
+;;; Guile Feed 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
+;;; General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU General Public License
+;;; along with Guile Feed. If not, see
+;;; <http://www.gnu.org/licenses/>.
+
+(define-module (feed ui)
+ #:use-module (guix ui)
+ #:use-module (srfi srfi-1)
+ #:use-module (srfi srfi-26)
+ #:use-module (ice-9 match)
+ #:use-module (ice-9 ftw)
+ #:export (feed-main))
+
+(define (command-files)
+ "Return the list of source files that define Feed sub-commands."
+ (define directory
+ (and=> (search-path %load-path "feed.scm")
+ (compose (cut string-append <> "/feed/scripts")
+ dirname)))
+
+ (define dot-scm?
+ (cut string-suffix? ".scm" <>))
+
+ (if directory
+ (scandir directory dot-scm?)
+ '()))
+
+(define (commands)
+ "Return the list of Feed command names."
+ (map (compose (cut string-drop-right <> 4)
+ basename)
+ (command-files)))
+
+(define (show-feed-usage)
+ (format (current-error-port)
+ "Try `feed --help' for more information.~%")
+ (exit 1))
+
+(define (show-feed-help)
+ (format #t "Usage: feed COMMAND ARGS...
+Run COMMAND with ARGS.\n")
+ (newline)
+ (format #t "COMMAND must be one of the sub-commands listed below:\n")
+ (newline)
+ ;; TODO: Display a synopsis of each command.
+ (format #t "~{ ~a~%~}" (sort (commands) string<?)))
+
+(define program-name
+ ;; Name of the command-line program currently executing, or #f.
+ (make-parameter #f))
+
+(define (run-feed-command command . args)
+ "Run COMMAND with the given ARGS. Report an error when COMMAND is not
+found."
+ (define module
+ (catch 'misc-error
+ (lambda ()
+ (resolve-interface `(feed scripts ,command)))
+ (lambda -
+ (format (current-error-port)
+ "feed: ~a: command not found~%" command)
+ (show-feed-usage))))
+
+ (let ((command-main (module-ref module
+ (symbol-append 'feed- command))))
+ (parameterize ((program-name command))
+ ;; Disable canonicalization so we don't don't stat unreasonably.
+ (with-fluids ((%file-port-name-canonicalization #f))
+ (dynamic-wind
+ (const #f)
+ (lambda ()
+ (apply command-main args))
+ (lambda ()
+ ;; Abuse 'exit-hook' (which is normally meant to be used by the
+ ;; REPL) to run things like profiling hooks upon completion.
+ (run-hook exit-hook)))))))
+
+(define (show-feed-usage)
+ (format (current-error-port)
+ "Try `feed --help' for more information.~%")
+ (exit 1))
+
+(define (run-feed . args)
+ "Run the 'feed' command defined by command line ARGS."
+ (define option? (cut string-prefix? "-" <>))
+
+ ;; The default %LOAD-EXTENSIONS includes the empty string, which doubles the
+ ;; number of 'stat' calls per entry in %LOAD-PATH. Shamelessly remove it.
+ (set! %load-extensions '(".scm"))
+
+ (match args
+ (()
+ (format (current-error-port)
+ "feed: missing command name~%")
+ (show-feed-usage))
+ ((or ("-h") ("--help"))
+ (show-feed-help))
+ ((command args ...)
+ (apply run-feed-command
+ (string->symbol command)
+ args))))
+
+(define (feed-main arg0 . args)
+ (apply run-feed args))
diff --git a/guix.scm b/guix.scm
new file mode 100644
index 0000000..731cd32
--- /dev/null
+++ b/guix.scm
@@ -0,0 +1,129 @@
+;;; Guile Feed --- Feed command-line interface.
+;;; Copyright © 2018 Oleg Pykhalov <go.wigust@gmail.com>
+;;;
+;;; This file is part of Guile Feed.
+;;;
+;;; Guile Feed is free software; you can redistribute it and/or modify
+;;; it under the terms of the GNU General Public License as published
+;;; by the Free Software Foundation; either version 3 of the License,
+;;; or (at your option) any later version.
+;;;
+;;; Guile Feed 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
+;;; General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU General Public License
+;;; along with Guile Feed. If not, see
+;;; <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This file contains Guix package for development version of Guile
+;; Feed. To build or install, run:
+;;
+;; guix build --file=guix.scm
+;; guix package --install-from-file=guix.scm
+
+;;; Code:
+
+(use-modules (gnu packages autotools)
+ (gnu packages gnupg)
+ (gnu packages guile)
+ (gnu packages package-management)
+ (gnu packages pkg-config)
+ (gnu packages tls)
+ (guix build utils)
+ (guix build-system gnu)
+ (guix gexp)
+ (guix git-download)
+ (guix packages)
+ (ice-9 popen)
+ (ice-9 rdelim)
+ ((guix licenses) #:prefix license:))
+
+(define %source-dir (dirname (current-filename)))
+
+(define (git-output . args)
+ "Execute 'git ARGS ...' command and return its output without trailing
+newspace."
+ (with-directory-excursion %source-dir
+ (let* ((port (apply open-pipe* OPEN_READ "git" args))
+ (output (read-string port)))
+ (close-port port)
+ (string-trim-right output #\newline))))
+
+(define (current-commit)
+ (git-output "log" "-n" "1" "--pretty=format:%H"))
+
+(define guile-feed
+ (let ((commit (current-commit)))
+ (package
+ (name "guile-feed")
+ (version (string-append "0.0.1" "-" (string-take commit 7)))
+ (source (local-file %source-dir
+ #:recursive? #t
+ #:select? (git-predicate %source-dir)))
+ (build-system gnu-build-system)
+ (arguments
+ `(#:modules ((guix build gnu-build-system)
+ (guix build utils)
+ (srfi srfi-26)
+ (ice-9 popen)
+ (ice-9 rdelim))
+ #:phases
+ (modify-phases %standard-phases
+ (add-after 'install 'wrap-program
+ (lambda* (#:key inputs outputs #:allow-other-keys)
+ ;; Make sure the 'guix' command finds GnuTLS,
+ ;; Guile-JSON, and Guile-Git automatically.
+ (let* ((out (assoc-ref outputs "out"))
+ (guile (assoc-ref inputs "guile"))
+ (gcrypt (assoc-ref inputs "guile-gcrypt"))
+ (gnutls (assoc-ref inputs "gnutls"))
+ (guix (assoc-ref inputs "guix"))
+ (json (assoc-ref inputs "guile-json"))
+ (deps (list gcrypt gnutls guix json out))
+ (effective
+ (read-line
+ (open-pipe* OPEN_READ
+ (string-append guile "/bin/guile")
+ "-c" "(display (effective-version))")))
+ (path (string-join
+ (map (cut string-append <>
+ "/share/guile/site/"
+ effective)
+ deps)
+ ":"))
+ (gopath (string-join
+ (map (cut string-append <>
+ "/lib/guile/" effective
+ "/site-ccache")
+ deps)
+ ":")))
+
+ (wrap-program (string-append out "/bin/feed")
+ `("GUILE_LOAD_PATH" ":" prefix (,path))
+ `("GUILE_LOAD_COMPILED_PATH" ":" prefix (,gopath)))
+
+ #t))))))
+ (native-inputs
+ `(("autoconf" ,autoconf)
+ ("automake" ,automake)
+ ("pkg-config" ,pkg-config)))
+ (inputs
+ `(("gnutls" ,gnutls)
+ ("guile" ,guile-2.2)
+ ("guile-gcrypt" ,guile-gcrypt)
+ ("guile-json" ,guile-json)
+ ("guix" ,guix)))
+ (home-page "https://gitlab.com/wigust/guile-feed")
+ (synopsis "Command-line RSS feeds manager")
+ (description
+ "This package provides a command-line program to manage RSS feeds
+written in Guile.")
+ (license license:gpl3+))))
+
+guile-feed
+
+;;; guix.scm ends here
diff --git a/rss/feeds.scm b/rss/feeds.scm
new file mode 100644
index 0000000..8127b48
--- /dev/null
+++ b/rss/feeds.scm
@@ -0,0 +1,177 @@
+;;; Guile Feed --- Feed command-line interface.
+;;; Copyright © 2018 Oleg Pykhalov <go.wigust@gmail.com>
+;;;
+;;; This file is part of Guile Feed.
+;;;
+;;; Guile Feed is free software; you can redistribute it and/or modify
+;;; it under the terms of the GNU General Public License as published
+;;; by the Free Software Foundation; either version 3 of the License,
+;;; or (at your option) any later version.
+;;;
+;;; Guile Feed 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
+;;; General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU General Public License
+;;; along with Guile Feed. If not, see
+;;; <http://www.gnu.org/licenses/>.
+
+(define-module (rss feeds)
+ #:use-module (guix discovery)
+ #:use-module (guix records)
+ #:use-module (guix ui)
+ #:use-module (guix utils)
+ #:use-module (srfi srfi-1)
+ #:use-module (srfi srfi-26)
+ #:use-module (ice-9 match)
+ #:use-module (ice-9 vlist)
+ #:export (%feed-module-path
+ find-feeds-by-name
+ find-feeds-by-description
+ fold-feeds
+
+ feeds->lisp
+ feed->recutils
+ feeds->recutils
+
+ feed
+ feed?
+ feed-name
+
+ reddit-rss
+ youtube-channel
+ youtube-user))
+
+(define-record-type* <feed>
+ feed make-feed
+ feed?
+ (name feed-name)
+ (description feed-description)
+ (uri feed-uri)
+ (location feed-location
+ (default (and=> (current-source-location)
+ source-properties->location))
+ (innate)))
+
+(define reddit-rss
+ (cut string-append "https://www.reddit.com/r/" <> "/.rss"))
+
+(define youtube-channel
+ (cut string-append
+ "https://www.youtube.com/feeds/videos.xml?channel_id=" <>))
+
+(define youtube-user
+ (cut string-append "https://www.youtube.com/feeds/videos.xml?user=" <>))
+
+
+;; Code from (gnu services) module.
+
+(define %distro-root-directory
+ ;; Absolute file name of the module hierarchy.
+ (dirname (search-path %load-path "feed.scm")))
+
+(define %feed-module-path
+ ;; Search path for service feeds.
+ (make-parameter `((,%distro-root-directory . "rss/feeds"))))
+
+(define* (fold-feeds proc init
+ #:optional
+ (modules (all-modules (%feed-module-path))))
+ "For each service type exported by one of MODULES, call (PROC RESULT). INIT
+is used as the initial value of RESULT."
+ (fold-module-public-variables (lambda (object result)
+ (if (feed? object)
+ (proc object result)
+ result))
+ init
+ modules))
+
+
+
+(define (feeds->lisp)
+ `(setq elfeed-feeds
+ (quote ,(fold-feeds (lambda (feed lst)
+ (cons (match feed
+ (($ <feed> name description uri)
+ (cond ((string-prefix? "emacs-" name)
+ `(,uri emacs))
+ ((string-prefix? "game-" name)
+ `(,uri game))
+ ((string-prefix? "planet-" name)
+ `(,uri planet))
+ ((string-prefix? "video-" name)
+ `(,uri video))
+ (else uri))))
+ lst))
+ '()))))
+
+(define* (feed->recutils feed #:key (extra-fields '()))
+ (match feed
+ (($ <feed> name description uri)
+ (format #t "name: ~a~%" name)
+ (format #t "description: ~a~%" description)
+ (format #t "link: ~a~%" uri)
+ (for-each (match-lambda
+ ((field . value)
+ (let ((field (symbol->string field)))
+ (format #t "~a: ~a~%" field value))))
+ extra-fields)
+ (newline))))
+
+(define (feeds->recutils)
+ (fold-feeds (lambda (feed lst)
+ (cons (feed->recutils feed) lst))
+ '()))
+
+
+;;;
+;;; Search
+;;;
+
+(define %feed-metrics
+ ;; Metrics used to compute the "relevance score" of a feed against a set
+ ;; of regexps.
+ `((,feed-name . 3)
+ (,feed-description . 2)
+ (,(lambda (type)
+ (match (and=> (feed-location type) location-file)
+ ((? string? file) (basename file ".scm"))
+ (#f "")))
+ . 1)))
+
+(define (feed-relevance feed regexps)
+ "Return a score denoting the relevance of FEED for REGEXPS. A score of
+zero means that FEED does not match any of REGEXPS."
+ (relevance feed regexps %feed-metrics))
+
+(define find-feeds-by-name
+ (let ((feeds (delay
+ (fold-feeds (lambda (p r)
+ (vhash-cons (feed-name p) p r))
+ vlist-null))))
+ (lambda* (name #:optional version)
+ "Return the list of feeds with the given NAME."
+ (vhash-fold* cons '() name (force feeds)))))
+
+(define (find-feeds-by-description regexps)
+ "Return two values: the list of feeds whose name or description matches at
+least one of REGEXPS sorted by relevance, and the list of relevance scores."
+ (let ((matches (fold-feeds (lambda (feed result)
+ (match (feed-relevance feed regexps)
+ ((? zero?)
+ result)
+ (score
+ (cons (list feed score)
+ result))))
+ '())))
+ (unzip2 (sort matches
+ (lambda (m1 m2)
+ (match m1
+ ((feed1 score1)
+ (match m2
+ ((feed2 score2)
+ (if (= score1 score2)
+ (string>? (feed-name feed1)
+ (feed-name feed2))
+ (> score1 score2)))))))))))
diff --git a/rss/feeds/code.scm b/rss/feeds/code.scm
new file mode 100644
index 0000000..96a7939
--- /dev/null
+++ b/rss/feeds/code.scm
@@ -0,0 +1,20 @@
+(define-module (rss feeds code)
+ #:use-module (rss feeds))
+
+(define-public abo-abo
+ (feed
+ (name "abo-abo")
+ (description "Oleh Krehel blog.")
+ (uri "https://oremacs.com/atom.xml")))
+
+(define-public nullprogram
+ (feed
+ (name "nullprogram")
+ (description "Chris Wellons blog.")
+ (uri "http://nullprogram.com/feed/")))
+
+(define-public steckerhalter
+ (feed
+ (name "steckerhalter")
+ (description "steckerhalter blog.")
+ (uri "http://steckerhalter.tk/index.xml")))
diff --git a/rss/feeds/emacs.scm b/rss/feeds/emacs.scm
new file mode 100644
index 0000000..20527ba
--- /dev/null
+++ b/rss/feeds/emacs.scm
@@ -0,0 +1,14 @@
+(define-module (rss feeds emacs)
+ #:use-module (rss feeds))
+
+(define-public emacs-bennee
+ (feed
+ (name "emacs-bennee")
+ (description "Alex Bennee blog.")
+ (uri "https://www.bennee.com/~alex/blog/feed/")))
+
+(define-public emacs-cestlaz
+ (feed
+ (name "emacs-cestlaz")
+ (description "Mike Zamansky blog.")
+ (uri "https://cestlaz.github.io/rss.xml")))
diff --git a/rss/feeds/game.scm b/rss/feeds/game.scm
new file mode 100644
index 0000000..92b71d8
--- /dev/null
+++ b/rss/feeds/game.scm
@@ -0,0 +1,8 @@
+(define-module (rss feeds game)
+ #:use-module (rss feeds))
+
+(define-public game-reddit-freegames
+ (feed
+ (name "game-reddit-freegames")
+ (description "Reddit free as beer games.")
+ (uri (reddit-rss "freegames"))))
diff --git a/rss/feeds/hardware.scm b/rss/feeds/hardware.scm
new file mode 100644
index 0000000..1d26f4e
--- /dev/null
+++ b/rss/feeds/hardware.scm
@@ -0,0 +1,8 @@
+(define-module (rss feeds hardware)
+ #:use-module (rss feeds))
+
+(define-public h-node
+ (feed
+ (name "h-node")
+ (description "Libre hardware list.")
+ (uri "https://h-node.org/rss/modifications/en")))
diff --git a/rss/feeds/news.scm b/rss/feeds/news.scm
new file mode 100644
index 0000000..3408edd
--- /dev/null
+++ b/rss/feeds/news.scm
@@ -0,0 +1,14 @@
+(define-module (rss feeds news)
+ #:use-module (rss feeds))
+
+(define-public fedoramagazine
+ (feed
+ (name "fedoramagazine")
+ (description "Fedora online magazine.")
+ (uri "https://fedoramagazine.org/feed/")))
+
+(define-public lwn
+ (feed
+ (name "lwn")
+ (description "GNU/Linux online magazine.")
+ (uri "https://lwn.net/headlines/newrss")))
diff --git a/rss/feeds/planet.scm b/rss/feeds/planet.scm
new file mode 100644
index 0000000..9e17fa4
--- /dev/null
+++ b/rss/feeds/planet.scm
@@ -0,0 +1,26 @@
+(define-module (rss feeds planet)
+ #:use-module (rss feeds))
+
+(define-public planet-emacs
+ (feed
+ (name "planet-emacs")
+ (description "Emacs planet.")
+ (uri "http://planet.emacsen.org/atom.xml")))
+
+(define-public planet-gnu
+ (feed
+ (name "planet-gnu")
+ (description "Gnu planet.")
+ (uri "http://planet.gnu.org/atom.xml")))
+
+(define-public planet-lisp
+ (feed
+ (name "planet-lisp")
+ (description "Lisp planet.")
+ (uri "http://planet.lisp.org/rss20.xml")))
+
+(define-public planet-scheme
+ (feed
+ (name "planet-scheme")
+ (description "Scheme planet.")
+ (uri "http://www.scheme.dk/planet/atom.xml")))
diff --git a/rss/feeds/video.ru.scm b/rss/feeds/video.ru.scm
new file mode 100644
index 0000000..d1dd6c0
--- /dev/null
+++ b/rss/feeds/video.ru.scm
@@ -0,0 +1,38 @@
+(define-module (rss feeds video)
+ #:use-module (rss feeds))
+
+(define-public video-game-one
+ (feed
+ (name "video-game-one")
+ (description "Рекомендованный канал Nitroxenus")
+ (uri (youtube-user "elementaller"))))
+
+(define-public video-cpp-prosto
+ (feed
+ (name "cpp-prosto")
+ (description "Язык программирования C")
+ (uri (youtube-channel "UC_ehNByPcItZU3pXL-4skUA"))))
+
+(define-public video-nitro-live
+ (feed
+ (name "video-nitro-live")
+ (description "")
+ (uri (youtube-channel "UC1RZz5_cdVQHhhYJVpCDqHA"))))
+
+(define-public video-nitroxsenys
+ (feed
+ (name "video-nitroxsenys")
+ (description "")
+ (uri (youtube-channel "UCF3d6ZcTRBhnrNC0-cvzicw"))))
+
+(define-public video-pashtet495
+ (feed
+ (name "video-pashtet495")
+ (description "Рекомендованный канал Nitroxenus")
+ (uri (youtube-user "Pashtet495"))))
+
+(define-public video-stalkash
+ (feed
+ (name "video-stalkash")
+ (description "Рекомендованный канал Nitroxenus")
+ (uri (youtube-channel "UCOpm7EqPBtznEwYNNZrz1FQ"))))
diff --git a/rss/feeds/video.scm b/rss/feeds/video.scm
new file mode 100644
index 0000000..660b411
--- /dev/null
+++ b/rss/feeds/video.scm
@@ -0,0 +1,92 @@
+(define-module (rss feeds video)
+ #:use-module (rss feeds))
+
+(define-public video-baggers
+ (feed
+ (name "video-baggers")
+ (description "2D and 3D graphics with Common Lisp programming tutorials.")
+ (uri (youtube-channel "UCMV8p6Lb-bd6UZtTc_QD4zA"))))
+
+(define-public video-brian-lunduke
+ (feed
+ (name "video-brian-lunduke")
+ (description "Free and opensource news.")
+ (uri (youtube-channel "UCkK9UDm_ZNrq_rIXCz3xCGA"))))
+
+(define-public video-bsdnow
+ (feed
+ (name "video-bsdnow")
+ (description "FreeBSD weekly news.")
+ (uri "https://bitlove.org/jupiterbroadcasting/bsdnowhd/feed")))
+
+(define-public video-computeremotion
+ (feed
+ (name "video-computeremotion")
+ (description "Emacs Lisp programming tutorials.")
+ (uri (youtube-channel "UCgU5tUdVPpfM7sLAMWBTsDg"))))
+
+(define-public video-distrotube
+ (feed
+ (name "video-distrotube")
+ (description "Operating system reviews.")
+ (uri (youtube-channel "UCVls1GmFKf6WlTraIb_IaJg"))))
+
+(define-public video-dubbeltumme
+ (feed
+ (name "video-dubbeltumme")
+ (description "Command-line workflow.")
+ (uri (youtube-user name))))
+
+(define-public video-gotbletu
+ (feed
+ (name "video-gotbletu")
+ (description "Command-line programs reviews.")
+ (uri (youtube-user name))))
+
+(define-public video-ibsd
+ (feed
+ (name "video-ibsd")
+ (description "FreeBSD operating system review.")
+ (uri (youtube-channel "UCVls1GmFKf6WlTraIb_IaJg"))))
+
+(define-public video-ldcnow
+ (feed
+ (name "video-ldcnow")
+ (description "Linux Distro Community.")
+ (uri (youtube-user "LDCNow"))))
+
+(define-public video-luke-smith
+ (feed
+ (name "video-luke-smith")
+ (description "GNU utilities and libre hardware review.")
+ (uri (youtube-channel "UC2eYFnH61tmytImy1mTYvhA"))))
+
+(define-public video-matt-hartley
+ (feed
+ (name "video-matt-hartley")
+ (description "Operating system and utilities reviews.")
+ (uri (youtube-channel "UCbHXJGd7c8Hy4z0-YX1Jf3Q"))))
+
+(define-public video-metalx1000
+ (feed
+ (name "video-metalx1000")
+ (description "Shell-scripting tutorials.")
+ (uri (youtube-user name))))
+
+(define-public video-ssethtzeentach
+ (feed
+ (name "video-ssethtzeentach")
+ (description "Game-trolling.")
+ (uri (youtube-user "SsethTzeentach"))))
+
+(define-public video-tripcode
+ (feed
+ (name "video-tripcode")
+ (description "Thinkpad reviews and Libreboot flashing.")
+ (uri (youtube-channel "UCZrrEuHiQjN2CUo84g5tk7w"))))
+
+(define-public video-tuxreviews
+ (feed
+ (name "video-tuxreviews")
+ (description "GNU/Linux weekly news.")
+ (uri (youtube-user name))))
diff --git a/scripts/feed.in b/scripts/feed.in
new file mode 100755
index 0000000..dfa866d
--- /dev/null
+++ b/scripts/feed.in
@@ -0,0 +1,27 @@
+#!@GUILE@ \
+--no-auto-compile -e main -s
+!#
+
+;;; Guile Feed --- Feed command-line interface.
+;;; Copyright © 2018 Oleg Pykhalov <go.wigust@gmail.com>
+;;;
+;;; This file is part of Guile Feed.
+;;;
+;;; Guile Feed is free software; you can redistribute it and/or modify
+;;; it under the terms of the GNU General Public License as published
+;;; by the Free Software Foundation; either version 3 of the License,
+;;; or (at your option) any later version.
+;;;
+;;; Guile Feed 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
+;;; General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU General Public License
+;;; along with Guile Feed. If not, see
+;;; <http://www.gnu.org/licenses/>.
+
+(define* (main #:optional (args (command-line)))
+ (let ((feed-main (module-ref (resolve-interface '(feed ui))
+ 'feed-main)))
+ (exit (apply feed-main args))))
diff --git a/tests/rss.scm b/tests/rss.scm
new file mode 100644
index 0000000..843368c
--- /dev/null
+++ b/tests/rss.scm
@@ -0,0 +1,40 @@
+;;; Guile Feed --- Feed command-line interface.
+;;; Copyright © 2018 Oleg Pykhalov <go.wigust@gmail.com>
+;;;
+;;; This file is part of Guile Feed.
+;;;
+;;; Guile Feed is free software; you can redistribute it and/or modify
+;;; it under the terms of the GNU General Public License as published
+;;; by the Free Software Foundation; either version 3 of the License,
+;;; or (at your option) any later version.
+;;;
+;;; Guile Feed 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
+;;; General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU General Public License
+;;; along with Guile Feed. If not, see
+;;; <http://www.gnu.org/licenses/>.
+
+(define-module (test-feed)
+ #:use-module (guix tests)
+ #:use-module (feed scripts rss)
+ #:use-module (ice-9 match)
+ #:use-module (srfi srfi-64))
+
+(test-begin "search")
+
+(test-assert "feed-rss-search"
+ (string=? (with-output-to-string
+ (lambda ()
+ (feed-rss "-s" "planet-gnu")))
+ "\
+name: planet-gnu
+description: Gnu planet.
+link: http://planet.gnu.org/atom.xml
+relevance: 15
+
+"))
+
+(test-end "search")