Repository: kmyk/online-judge-tools
Pull request #665 (2020-01-26 09:26:39)

Support Sphere online judge.
#651 

大分性能が低い。

できるやつ
https://www.spoj.com/problems/ACARGO/
無理なやつ
https://www.spoj.com/problems/MKLABELS/


Patch:
diff --git a/onlinejudge/service/__init__.py b/onlinejudge/service/__init__.py
index 02b3159..5dc884e 100644
--- a/onlinejudge/service/__init__.py
+++ b/onlinejudge/service/__init__.py
@@ -10,6 +10,7 @@ import onlinejudge.service.hackerrank
 import onlinejudge.service.kattis
 import onlinejudge.service.library_checker
 import onlinejudge.service.poj
+import onlinejudge.service.spoj
 import onlinejudge.service.topcoder
 import onlinejudge.service.toph
 import onlinejudge.service.yukicoder
diff --git a/onlinejudge/service/spoj.py b/onlinejudge/service/spoj.py
new file mode 100644
index 0000000..83a4c80
--- /dev/null
+++ b/onlinejudge/service/spoj.py
@@ -0,0 +1,102 @@
+# Python Version: 3.x
+# -*- coding: utf-8 -*-
+"""
+the module for Sphere Online Judge (https://www.spoj.com/)
+
+"""
+
+import re
+import urllib.parse
+from typing import *
+
+import bs4
+import requests
+
+import onlinejudge._implementation.logging as log
+import onlinejudge._implementation.testcase_zipper
+import onlinejudge._implementation.utils as utils
+import onlinejudge.type
+from onlinejudge.type import TestCase
+
+
+class SPOJService(onlinejudge.type.Service):
+    def get_url(self) -> str:
+        return 'https://www.spoj.com/'
+
+    def get_name(self) -> str:
+        return 'Sphere Online Judge'
+
+    @classmethod
+    def from_url(cls, url: str) -> Optional['SPOJService']:
+        # example: https://www.spoj.com/
+        result = urllib.parse.urlparse(url)
+        if result.scheme in ('', 'http', 'https') \
+                and result.netloc in ('www.spoj.com',):
+            return cls()
+        return None
+
+
+class SPOJProblem(onlinejudge.type.Problem):
+    """
+    :ivar problem_id: :py:class:`str` like `TEST` or `PRIME1`
+    """
+    def __init__(self, *, problem_id):
+        self.problem_id = problem_id
+
+    def download_sample_cases(self, *, session: Optional[requests.Session] = None) -> List[TestCase]:
+        """
+        :raises SampleParseError:
+        """
+        session = session or utils.get_default_session()
+
+        url = 'https://www.spoj.com/problems/{}'.format(self.problem_id)
+        resp = utils.request('GET', url, session=session)
+        soup = bs4.BeautifulSoup(resp.content.decode(resp.encoding), utils.html_parser)
+        samples = onlinejudge._implementation.testcase_zipper.SampleZipper()
+        for pre, h3 in self._find_sample_tags(soup):
+            s = utils.textfile(utils.dos2unix(utils.parse_content(pre).lstrip()))
+            name = h3.string
+            samples.add(s.encode(), name)
+        return samples.get()
+
+    def _find_sample_tags(cls, soup: bs4.BeautifulSoup) -> Iterator[Tuple[bs4.Tag, bs4.Tag]]:
+        expected_strings = ('Sample Input:', 'Sample Output:')
+
+        def get_header(pre, tag, expected_tag_name):
+            if tag and tag.name == expected_tag_name and tag.string and any(s in tag.string for s in expected_strings):
+                return tag
+            return None
+
+        for pre in soup.find(id='problem-body').find_all('pre'):
+            log.debug('pre tag: %s', str(pre))
+            tag = pre.find_previous_sibling().find_previous_sibling()
+            h3 = get_header(pre, tag=tag, expected_tag_name='b')
+
+            if h3:
+                yield (pre, h3)
+                continue
+        samples = []  # type: List[TestCase]
+
+        return samples
+
+    def get_url(self) -> str:
+        return 'https://www.spoj.com/problems/{}'.format(self.problem_id)
+
+    @classmethod
+    def from_url(cls, url: str) -> Optional['SPOJProblem']:
+        # example: https://www.spoj.com/problems/PRIME1/
+        result = urllib.parse.urlparse(url)
+        if result.scheme in ('', 'http', 'https') \
+                and result.netloc == 'www.spoj.com':
+            m = re.match(r'/(?:problems)/([0-9A-Z_a-z-]+)/?', result.path)
+            if m:
+                problem_id = m.group(1)
+                return cls(problem_id=problem_id)
+        return None
+
+    def get_service(self) -> SPOJService:
+        return SPOJService()
+
+
+onlinejudge.dispatch.services += [SPOJService]
+onlinejudge.dispatch.problems += [SPOJProblem]
diff --git a/readme.md b/readme.md
index 1a65f5b..86f1b23 100644
--- a/readme.md
+++ b/readme.md
@@ -28,6 +28,7 @@ Tools for online judge services. Downloading sample cases, Testing/Submitting yo
     -   Kattis
     -   Toph (Problem Archive)
     -   CodeChef
+    -   Sphere online judge
     -   Facebook Hacker Cup
     -   Library Checker (<https://judge.yosupo.jp>)
 -   Download system test cases


Test patch:
diff --git a/tests/service_spoj.py b/tests/service_spoj.py
new file mode 100644
index 0000000..a3a6272
--- /dev/null
+++ b/tests/service_spoj.py
@@ -0,0 +1,24 @@
+import unittest
+
+from onlinejudge.service.spoj import SPOJProblem, SPOJService
+from onlinejudge.type import TestCase
+
+
+class CodeChefSerivceTest(unittest.TestCase):
+    def test_from_url(self):
+        self.assertIsInstance(SPOJService.from_url('https://www.spoj.com/'), SPOJService)
+        self.assertIsNone(SPOJService.from_url('https://www.facebook.com/'))
+
+
+class CodeChefProblemTest(unittest.TestCase):
+    def test_from_url(self):
+        self.assertEqual(SPOJProblem.from_url('https://www.spoj.com/problems/ACARGO/').problem_id, 'ACARGO')
+
+    def test_download_samples(self):
+        self.assertEqual(SPOJProblem.from_url('https://www.spoj.com/problems/ACARGO/').download_sample_cases(), [
+            TestCase(name='sample-1', input_name='Sample Input:', input_data=b'3 5\n0\n1\n3\n2 3\n0\n1\n5 20\n2\n7\n12\n9\n13\n0 0\n', output_name='Sample Output:', output_data=b'1\n0\n10\n'),
+        ])
+
+    def test_download_samples_todo(self):
+        # No samples found.
+        self.assertFalse(SPOJProblem.from_url('https://www.spoj.com/problems/MKLABELS/').download_sample_cases())