Skip to content

Commit 245d2a1

Browse files
committed
add code
1 parent f3e6f86 commit 245d2a1

14 files changed

Lines changed: 449 additions & 0 deletions

File tree

.coveragerc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
[run]
2+
branch = True
3+
source = atwiki
4+
omit =
5+
atwiki/test/*

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
*.pyc
2+
/dist/
3+
/build/
4+
/*.egg-info
5+
/.tox/
6+
.coverage

.travis.yml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
language: python
2+
3+
python:
4+
- 2.6
5+
- 2.7
6+
- 3.3
7+
- 3.4
8+
- 3.5
9+
10+
before_install:
11+
- env
12+
- uname -a
13+
14+
install:
15+
# Show current environment
16+
- python -V
17+
- pip --version
18+
19+
# Install Coveralls
20+
- pip install coveralls
21+
22+
script:
23+
# Unit Test
24+
- coverage run -p setup.py test
25+
26+
after_success:
27+
- coverage combine
28+
- coveralls

LICENSE

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Copyright (C) 2016 Preferred Networks and Nippon Telegraph and Telephone Corporation
2+
3+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4+
5+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6+
7+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

README.rst

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
atwiki-python
2+
=============
3+
4+
``atwiki-python`` is a client library to access [@wiki](https://atwiki.jp) from Python.
5+
6+
Install
7+
-------
8+
9+
..
10+
11+
pip install .
12+
13+
Requirements
14+
------------
15+
16+
* Python 2.6, 2.7, 3.3, 3.4 or 3.5.
17+
18+
Usage
19+
-----
20+
21+
.. code:: python
22+
23+
from atwiki import *
24+
25+
api = AtWikiAPI(AtWikiURI('http://www65.atwiki.jp/python-client/'))
26+
27+
# List of pages.
28+
for page in api.get_list():
29+
print(page['name'])
30+
31+
# List of pages tagged with 'tag01'.
32+
for page in api.get_list('tag01'):
33+
print(page['name'])
34+
35+
# Source of page ID 14
36+
print(api.get_source(14))
37+
38+
License
39+
-------
40+
41+
MIT License

atwiki/__init__.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
from ._version import VERSION
2+
3+
from .core import AtWikiAPI
4+
from .uri import AtWikiURI
5+
6+
__version__ = '.'.join(map(str, VERSION))
7+
8+
__all__ = ['AtWikiAPI', 'AtWikiURI']

atwiki/_version.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
VERSION = (0, 0, 1)

atwiki/core.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
# -*- coding: utf-8 -*-
2+
3+
from __future__ import absolute_import, division, print_function, unicode_literals
4+
5+
try:
6+
# Python 3
7+
from urllib.request import urlopen, Request
8+
except ImportError:
9+
# Python 2
10+
from urllib2 import urlopen, Request
11+
12+
import re
13+
import time
14+
from collections import namedtuple
15+
16+
import bs4
17+
import bs4.element
18+
from bs4 import BeautifulSoup
19+
20+
from .uri import AtWikiURI
21+
22+
class AtWikiAPI(object):
23+
_PAGER_PATTERN = re.compile(r'.+?(\d+).+?(\d+).+?(\d+).+?') # "計 110 ページ / 1 から 100 を表示"
24+
25+
def __init__(self, uri, **kwargs):
26+
self._uri = uri
27+
self._user_agent = kwargs.get('user_agent', 'Mozilla/5.0')
28+
self._sleep = kwargs.get('sleep', 1)
29+
30+
def get_list(self, tag=None):
31+
index = 0
32+
while True:
33+
count = 0
34+
is_end = True
35+
if tag:
36+
soup = self._request(self._uri.tag(tag, index))
37+
links = soup.find('div', attrs={'class': 'cmd_tag'}).findAll('a', href=True)
38+
is_end = False
39+
else:
40+
soup = self._request(self._uri.list('create', index))
41+
links = soup.find('table', attrs={'class': 'pagelist'}).findAll('a', href=True, title=True)
42+
pager = soup.find('div', attrs={'class': 'pagelist'}).findAll('p')[2].text
43+
m = self._PAGER_PATTERN.search(pager)
44+
if m:
45+
(total, cursor_begin, cursor_end) = (int(m.group(1)), int(m.group(2)), int(m.group(3)))
46+
is_end = (total == cursor_end)
47+
for link in links:
48+
page_id = self._uri.get_page_id_from_uri(link.attrs['href'])
49+
page_name = link.text.strip()
50+
if page_id:
51+
count += 1
52+
yield {'id': page_id, 'name': page_name}
53+
if count == 0 or is_end: break
54+
index += 1
55+
time.sleep(self._sleep)
56+
57+
def get_source(self, page_id, generation=0):
58+
soup = self._request(self._uri.backup_source(page_id, generation))
59+
return soup.find('pre', attrs={'class': 'cmd_backup'}).text.replace('\r', '')
60+
61+
def search(self, keyword, is_and=True):
62+
soup = self._request(self._uri.search(keyword, is_and))
63+
lis = soup.find('div', id='wikibody').findAll('li')[:-1] # drop last item (link to http://atwiki.jp/wiki/keyword)
64+
for li in lis:
65+
a = li.find('a')
66+
name = a.text
67+
snippet = None
68+
for sib in a.next_siblings:
69+
if snippet is None:
70+
if sib.name == 'br': snippet = ''
71+
continue
72+
if isinstance(sib, bs4.element.Tag):
73+
snippet += sib.text
74+
else:
75+
snippet += str(sib)
76+
snippet = '' if snippet is None else snippet.strip()
77+
yield {'name': name, 'snippet': snippet}
78+
79+
def _request(self, url, data=None):
80+
req = Request(url, headers={'User-Agent': self._user_agent}, data=data)
81+
return BeautifulSoup(urlopen(req).read(), 'html.parser')

atwiki/test/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
TEST_BASE_URI = 'http://www65.atwiki.jp/python-client'

atwiki/test/test_core.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# -*- coding: utf-8 -*-
2+
3+
from __future__ import absolute_import, division, print_function, unicode_literals
4+
5+
from unittest import TestCase
6+
7+
from atwiki.core import AtWikiAPI
8+
from atwiki.uri import AtWikiURI
9+
10+
from . import TEST_BASE_URI
11+
12+
class AtWikiAPITest(TestCase):
13+
def setUp(self):
14+
self._api = AtWikiAPI(AtWikiURI(TEST_BASE_URI))
15+
16+
def test_get_list(self):
17+
results = list(self._api.get_list())
18+
self.assertTrue(1 < len(results))
19+
20+
def test_get_list_tag(self):
21+
results = list(self._api.get_list('tag01'))
22+
self.assertEqual(len(results), 1)
23+
self.assertEqual(results[0]['id'], 18)
24+
self.assertEqual(results[0]['name'], 'Test_atwiki.test.test_core:AtWikiAPITest:test_get_list_tag')
25+
26+
def test_get_source(self):
27+
self.assertEqual(self._api.get_source(14, 0),
28+
'テスト1\nテスト2\n\nテスト3\nテスト4\n\n\nテスト5')
29+
self.assertEqual(self._api.get_source(14, 1),
30+
'テスト1\nテスト2\n\nテスト3\nテスト4')
31+
32+
def test_get_source_special(self):
33+
self.assertEqual(self._api.get_source(15, 0),
34+
'"テスト1"<br>&\n"テスト2"<br>&')
35+
36+
def test_search(self):
37+
results = list(self._api.search('SearchKeyword01 SearchKeyword02'))
38+
self.assertEqual(len(results), 1)
39+
self.assertEqual(results[0]['name'], 'Test_atwiki.test.test_core:AtWikiAPITest:test_search')
40+
self.assertEqual(results[0]['snippet'], 'SearchKeyword01\nSearchKeyword02')
41+
42+
def test_search_or(self):
43+
results = list(self._api.search('SearchKeyword01 SearchKeyword02', False))
44+
self.assertEqual(len(results), 2)
45+
self.assertEqual(results[0]['name'], 'Test_atwiki.test.test_core:AtWikiAPITest:test_search')
46+
self.assertEqual(results[0]['snippet'], 'SearchKeyword01\nSearchKeyword02')
47+
self.assertEqual(results[1]['name'], 'Test_atwiki.test.test_core:AtWikiAPITest:test_search_or')
48+
self.assertEqual(results[1]['snippet'], 'SearchKeyword02')

0 commit comments

Comments
 (0)