Testy do Client API.
authorŁukasz Rekucki <lrekucki@gmail.com>
Mon, 21 Sep 2009 09:09:57 +0000 (11:09 +0200)
committerŁukasz Rekucki <lrekucki@gmail.com>
Mon, 21 Sep 2009 09:09:57 +0000 (11:09 +0200)
106 files changed:
apps/api/__init__.py [new file with mode: 0644]
apps/api/forms.py [new file with mode: 0644]
apps/api/handlers.py [new file with mode: 0644]
apps/api/models.py [new file with mode: 0644]
apps/api/tests.py [new file with mode: 0644]
apps/api/tests/__init__.py [new file with mode: 0644]
apps/api/tests/data/empty/.hg/00changelog.i [new file with mode: 0644]
apps/api/tests/data/empty/.hg/branchheads.cache [new file with mode: 0644]
apps/api/tests/data/empty/.hg/dirstate [new file with mode: 0644]
apps/api/tests/data/empty/.hg/requires [new file with mode: 0644]
apps/api/tests/data/empty/.hg/store/00changelog.i [new file with mode: 0644]
apps/api/tests/data/empty/.hg/store/00manifest.i [new file with mode: 0644]
apps/api/tests/data/empty/.hg/store/data/.library.i [new file with mode: 0644]
apps/api/tests/data/empty/.hg/store/fncache [new file with mode: 0644]
apps/api/tests/data/empty/.hg/store/undo [new file with mode: 0644]
apps/api/tests/data/empty/.hg/undo.branch [new file with mode: 0644]
apps/api/tests/data/empty/.hg/undo.dirstate [new file with mode: 0644]
apps/api/tests/data/empty/.library [new file with mode: 0644]
apps/api/tests/data/test2/.hg/00changelog.i [new file with mode: 0644]
apps/api/tests/data/test2/.hg/branch [new file with mode: 0644]
apps/api/tests/data/test2/.hg/branchheads.cache [new file with mode: 0644]
apps/api/tests/data/test2/.hg/dirstate [new file with mode: 0644]
apps/api/tests/data/test2/.hg/requires [new file with mode: 0644]
apps/api/tests/data/test2/.hg/store/00changelog.i [new file with mode: 0644]
apps/api/tests/data/test2/.hg/store/00manifest.i [new file with mode: 0644]
apps/api/tests/data/test2/.hg/store/data/.library.i [new file with mode: 0644]
apps/api/tests/data/test2/.hg/store/data/pub__testfile.xml.i [new file with mode: 0644]
apps/api/tests/data/test2/.hg/store/fncache [new file with mode: 0644]
apps/api/tests/data/test2/.hg/store/undo [new file with mode: 0644]
apps/api/tests/data/test2/.hg/undo.branch [new file with mode: 0644]
apps/api/tests/data/test2/.hg/undo.dirstate [new file with mode: 0644]
apps/api/tests/data/test2/.library [new file with mode: 0644]
apps/api/tests/data/test2/pub_testfile.xml [new file with mode: 0644]
apps/api/tests/data/testone/.hg/00changelog.i [new file with mode: 0644]
apps/api/tests/data/testone/.hg/branchheads.cache [new file with mode: 0644]
apps/api/tests/data/testone/.hg/dirstate [new file with mode: 0644]
apps/api/tests/data/testone/.hg/requires [new file with mode: 0644]
apps/api/tests/data/testone/.hg/store/00changelog.i [new file with mode: 0644]
apps/api/tests/data/testone/.hg/store/00manifest.i [new file with mode: 0644]
apps/api/tests/data/testone/.hg/store/data/.library.i [new file with mode: 0644]
apps/api/tests/data/testone/.hg/store/data/pub__testfile.xml.i [new file with mode: 0644]
apps/api/tests/data/testone/.hg/store/fncache [new file with mode: 0644]
apps/api/tests/data/testone/.hg/store/undo [new file with mode: 0644]
apps/api/tests/data/testone/.hg/undo.branch [new file with mode: 0644]
apps/api/tests/data/testone/.hg/undo.dirstate [new file with mode: 0644]
apps/api/tests/data/testone/.library [new file with mode: 0644]
apps/api/tests/data/testone/pub_testfile.xml [new file with mode: 0644]
apps/api/urls.py [new file with mode: 0644]
apps/api/utils.py [new file with mode: 0644]
apps/api/views.py [new file with mode: 0644]
lib/run_tests.sh [new file with mode: 0755]
lib/wlrepo/__init__.py [new file with mode: 0644]
lib/wlrepo/backend_mercurial.py [new file with mode: 0644]
lib/wlrepo/tests/__init__.py [new file with mode: 0644]
lib/wlrepo/tests/data/repos/cleanrepo/.hg/00changelog.i [new file with mode: 0644]
lib/wlrepo/tests/data/repos/cleanrepo/.hg/dirstate [new file with mode: 0644]
lib/wlrepo/tests/data/repos/cleanrepo/.hg/requires [new file with mode: 0644]
lib/wlrepo/tests/data/repos/cleanrepo/.hg/store/00changelog.i [new file with mode: 0644]
lib/wlrepo/tests/data/repos/cleanrepo/.hg/store/00manifest.i [new file with mode: 0644]
lib/wlrepo/tests/data/repos/cleanrepo/.hg/store/data/.hgignore.i [new file with mode: 0644]
lib/wlrepo/tests/data/repos/cleanrepo/.hg/store/data/ignored__file.i [new file with mode: 0644]
lib/wlrepo/tests/data/repos/cleanrepo/.hg/store/data/pub__valid__file.xml.i [new file with mode: 0644]
lib/wlrepo/tests/data/repos/cleanrepo/.hg/store/fncache [new file with mode: 0644]
lib/wlrepo/tests/data/repos/cleanrepo/.hg/store/undo [new file with mode: 0644]
lib/wlrepo/tests/data/repos/cleanrepo/.hg/undo.branch [new file with mode: 0644]
lib/wlrepo/tests/data/repos/cleanrepo/.hg/undo.dirstate [new file with mode: 0644]
lib/wlrepo/tests/data/repos/cleanrepo/.hgignore [new file with mode: 0644]
lib/wlrepo/tests/data/repos/cleanrepo/ignored_file [new file with mode: 0644]
lib/wlrepo/tests/data/repos/cleanrepo/pub_valid_file.xml [new file with mode: 0644]
lib/wlrepo/tests/data/repos/testrepoI/.hg/00changelog.i [new file with mode: 0644]
lib/wlrepo/tests/data/repos/testrepoI/.hg/dirstate [new file with mode: 0644]
lib/wlrepo/tests/data/repos/testrepoI/.hg/requires [new file with mode: 0644]
lib/wlrepo/tests/data/repos/testrepoI/.hg/store/00changelog.i [new file with mode: 0644]
lib/wlrepo/tests/data/repos/testrepoI/.hg/store/00manifest.i [new file with mode: 0644]
lib/wlrepo/tests/data/repos/testrepoI/.hg/store/data/.hgignore.i [new file with mode: 0644]
lib/wlrepo/tests/data/repos/testrepoI/.hg/store/data/ignored__file.i [new file with mode: 0644]
lib/wlrepo/tests/data/repos/testrepoI/.hg/store/data/pub__polish__file.xml.i [new file with mode: 0644]
lib/wlrepo/tests/data/repos/testrepoI/.hg/store/data/pub__valid__file.xml.i [new file with mode: 0644]
lib/wlrepo/tests/data/repos/testrepoI/.hg/store/fncache [new file with mode: 0644]
lib/wlrepo/tests/data/repos/testrepoI/.hg/store/undo [new file with mode: 0644]
lib/wlrepo/tests/data/repos/testrepoI/.hg/undo.branch [new file with mode: 0644]
lib/wlrepo/tests/data/repos/testrepoI/.hg/undo.dirstate [new file with mode: 0644]
lib/wlrepo/tests/data/repos/testrepoI/.hgignore [new file with mode: 0644]
lib/wlrepo/tests/data/repos/testrepoI/ignored_file [new file with mode: 0644]
lib/wlrepo/tests/data/repos/testrepoI/pub_polish_file.xml [new file with mode: 0644]
lib/wlrepo/tests/data/repos/testrepoI/pub_valid_file.xml [new file with mode: 0644]
lib/wlrepo/tests/data/repos/testrepoII/.hg/00changelog.i [new file with mode: 0644]
lib/wlrepo/tests/data/repos/testrepoII/.hg/branch [new file with mode: 0644]
lib/wlrepo/tests/data/repos/testrepoII/.hg/branchheads.cache [new file with mode: 0644]
lib/wlrepo/tests/data/repos/testrepoII/.hg/dirstate [new file with mode: 0644]
lib/wlrepo/tests/data/repos/testrepoII/.hg/requires [new file with mode: 0644]
lib/wlrepo/tests/data/repos/testrepoII/.hg/store/00changelog.i [new file with mode: 0644]
lib/wlrepo/tests/data/repos/testrepoII/.hg/store/00manifest.i [new file with mode: 0644]
lib/wlrepo/tests/data/repos/testrepoII/.hg/store/data/.hgignore.i [new file with mode: 0644]
lib/wlrepo/tests/data/repos/testrepoII/.hg/store/data/ignored__file.i [new file with mode: 0644]
lib/wlrepo/tests/data/repos/testrepoII/.hg/store/data/pub__polish__file.xml.i [new file with mode: 0644]
lib/wlrepo/tests/data/repos/testrepoII/.hg/store/data/pub__valid__file.xml.i [new file with mode: 0644]
lib/wlrepo/tests/data/repos/testrepoII/.hg/store/fncache [new file with mode: 0644]
lib/wlrepo/tests/data/repos/testrepoII/.hg/store/undo [new file with mode: 0644]
lib/wlrepo/tests/data/repos/testrepoII/.hg/undo.branch [new file with mode: 0644]
lib/wlrepo/tests/data/repos/testrepoII/.hg/undo.dirstate [new file with mode: 0644]
lib/wlrepo/tests/data/repos/testrepoII/.hgignore [new file with mode: 0644]
lib/wlrepo/tests/data/repos/testrepoII/ignored_file [new file with mode: 0644]
lib/wlrepo/tests/data/repos/testrepoII/pub_polish_file.xml [new file with mode: 0644]
lib/wlrepo/tests/data/repos/testrepoII/pub_valid_file.xml [new file with mode: 0644]
lib/wlrepo/tests/test_mercurial.py [new file with mode: 0644]

diff --git a/apps/api/__init__.py b/apps/api/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/apps/api/forms.py b/apps/api/forms.py
new file mode 100644 (file)
index 0000000..2b09e17
--- /dev/null
@@ -0,0 +1,20 @@
+# -*- encoding: utf-8 -*-
+
+__author__= "Łukasz Rekucki"
+__date__ = "$2009-09-20 21:34:52$"
+__doc__ = "Micro-forms for the API."
+
+
+from django import forms
+
+class DocumentGetForm(forms.Form):
+    autocabinet = forms.BooleanField(required=False)
+
+
+class DocumentUploadForm(forms.Form):
+    ocr = forms.FileField(label='Source OCR file')
+    bookname = forms.RegexField(regex=r'[0-9\.\w_-]+',  \
+        label='Publication name', help_text='Example: slowacki-beniowski')
+    
+    generate_dc = forms.BooleanField(required=False, initial=True, label=u"Generate DublinCore template")
+
diff --git a/apps/api/handlers.py b/apps/api/handlers.py
new file mode 100644 (file)
index 0000000..352e35e
--- /dev/null
@@ -0,0 +1,157 @@
+from piston.handler import BaseHandler, AnonymousBaseHandler
+from piston.utils import rc, validate
+
+import settings
+import librarian
+import api.forms as forms
+from datetime import date
+
+from django.core.urlresolvers import reverse
+from wlrepo import MercurialLibrary, CabinetNotFound
+
+#
+# Document List Handlers
+#
+class BasicLibraryHandler(AnonymousBaseHandler):
+    allowed_methods = ('GET',)
+
+    def read(self, request):
+        """Return the list of documents."""
+        lib = MercurialLibrary(path=settings.REPOSITORY_PATH)
+        cab = lib.main_cabinet
+
+        document_list = [{
+            'url': reverse('document_view', args=[docid]),
+            'name': docid } for docid in cab.documents() ]
+
+        return {
+            'cabinet': cab.name,
+            'latest_rev': cab.shelf(),
+            'documents' : document_list }
+
+class LibraryHandler(BaseHandler):
+    allowed_methods = ('GET', 'POST')
+    anonymous = BasicLibraryHandler
+
+    def read(self, request):
+        """Return the list of documents."""
+        lib = MercurialLibrary(path=settings.REPOSITORY_PATH)
+        cab = lib.main_cabinet
+
+        document_list = [{
+            'url': reverse('document_view', args=[docid]),
+            'name': docid } for docid in cab.documents() ]
+
+        return {
+            'cabinet': cab.name,
+            'latest_rev': cab.shelf(),
+            'documents' : document_list }
+
+    def create(self, request):
+        """Create a new document."""
+        lib = MercurialLibrary(path=settings.REPOSITORY_PATH)
+        cab = lib.main_cabinet
+
+        form = forms.DocumentUploadForm(request.POST, request.FILES)
+        if not form.is_valid():
+            return rc.BAD_REQUEST
+
+        f = request.FILES['ocr']
+        data = f.read().decode('utf-8')
+
+        if form.cleaned_data['generate_dc']:
+            data = librarian.wrap_text(data, unicode(date.today()))
+
+        doc = cab.create(form.cleaned_data['bookname'], initial_data=data)
+        
+        return {
+            'url': reverse('document_view', args=[doc.name]),
+            'name': doc.name,
+            'size': doc.size,
+            'revision': doc.shelf() }
+
+#
+# Document Handlers
+#
+class BasicDocumentHandler(AnonymousBaseHandler):
+    allowed_methods = ('GET',)
+
+    def read(self, request, docid):
+        lib = MercurialLibrary(path=settings.REPOSITORY_PATH)
+
+        opts = forms.DocumentGetForm(request.GET)
+        if not opts.is_valid():
+            return rc.BAD_REQUEST
+
+        document = lib.main_cabinet.retrieve(docid)        
+
+        result = {
+            'name': document.name,
+            'size': document.size,
+            'text_url': reverse('doctext_view', args=[docid]),
+            #'dc_url': reverse('docdc_view', docid=document.name),
+            #'parts_url': reverse('docparts_view', docid=document.name),
+            'latest_rev': document.shelf(),          
+        }
+
+        if request.GET.get('with_part', 'no') == 'yes':
+            result['parts'] = document.parts()
+
+        return result   
+        
+class DocumentHandler(BaseHandler):
+    allowed_methods = ('GET', 'PUT')
+    anonymous = BasicDocumentHandler
+    
+    def read(self, request, docid):
+        """Read document's meta data"""        
+        lib = MercurialLibrary(path=settings.REPOSITORY_PATH)
+
+        opts = forms.DocumentGetForm(request.GET)
+        if not opts.is_valid():
+            return rc.BAD_REQUEST
+              
+        document = lib.cabinet(docid, request.user.username, \
+                create=opts.cleaned_data['autocabinet'] ).retrieve()
+                
+        shared = lib.main_cabinet.retrieve(docid)
+
+        result = {
+            'name': document.name,
+            'size': document.size,
+            'text_url': reverse('doctext_view', args=[docid]),
+            'dc_url': reverse('docdc_view', args=[docid]),
+            'parts_url': reverse('docparts_view', args=[docid]),
+            'latest_rev': document.shelf(),
+            'latest_shared_rev': shared.shelf(),
+            #'shared': lib.isparentof(document, shared),
+            #'up_to_date': lib.isparentof(shared, document),
+        }
+
+        if request.GET.get('with_part', 'no') == 'yes':
+            result['parts'] = document.parts()
+
+        return result        
+
+#
+# Document Text View
+#
+class DocumentTextHandler(BaseHandler):
+    allowed_methods = ('GET', 'PUT')
+
+    def read(self, request, docid):
+        """Read document as raw text"""
+        lib = MercurialLibrary(path=settings.REPOSITORY_PATH)
+        try:            
+            return lib.document(docid, request.user.username).read()
+        except CabinetNotFound:
+            return rc.NOT_HERE
+
+    def update(self, request, docid):
+        lib = MercurialLibrary(path=settings.REPOSITORY_PATH)
+        try:
+            data = request.PUT['contents']
+            lib.document(docid, request.user.username).write(data)
+            return rc.ALL_OK
+        except (CabinetNotFound, KeyError):
+            return rc.NOT_HERE
\ No newline at end of file
diff --git a/apps/api/models.py b/apps/api/models.py
new file mode 100644 (file)
index 0000000..71a8362
--- /dev/null
@@ -0,0 +1,3 @@
+from django.db import models
+
+# Create your models here.
diff --git a/apps/api/tests.py b/apps/api/tests.py
new file mode 100644 (file)
index 0000000..2247054
--- /dev/null
@@ -0,0 +1,23 @@
+"""
+This file demonstrates two different styles of tests (one doctest and one
+unittest). These will both pass when you run "manage.py test".
+
+Replace these with more appropriate tests for your application.
+"""
+
+from django.test import TestCase
+
+class SimpleTest(TestCase):
+    def test_basic_addition(self):
+        """
+        Tests that 1 + 1 always equals 2.
+        """
+        self.failUnlessEqual(1 + 1, 2)
+
+__test__ = {"doctest": """
+Another way to test that 1 + 1 is equal to 2.
+
+>>> 1 + 1 == 2
+True
+"""}
+
diff --git a/apps/api/tests/__init__.py b/apps/api/tests/__init__.py
new file mode 100644 (file)
index 0000000..671978a
--- /dev/null
@@ -0,0 +1,199 @@
+"""
+This file demonstrates two different styles of tests (one doctest and one
+unittest). These will both pass when you run "manage.py test".
+
+Replace these with more appropriate tests for your application.
+"""
+
+from django.test import TestCase
+from django.test.client import Client
+from django.core.urlresolvers import reverse
+
+from django.utils import simplejson as json
+
+from django.contrib.auth.models import User
+
+import settings
+from os.path import join, dirname
+from StringIO import StringIO
+import shutil
+import tempfile
+
+REPO_TEMPLATES = join(dirname(__file__), 'data')
+
+def temprepo(name):
+    def decorator(func):   
+        def decorated(self, *args, **kwargs):
+            clean = False
+            try:
+                temp = tempfile.mkdtemp("", "testdir_" )
+                shutil.copytree(join(REPO_TEMPLATES, name), join(temp, 'repo'), False)
+                settings.REPOSITORY_PATH = join(temp, 'repo')
+                func(self, *args, **kwargs)
+                clean = True
+            finally:
+                if not clean and self.response:
+                    print "RESULT", func.__name__, ">>>"
+                    print self.response
+                    print "<<<"
+
+                shutil.rmtree(temp, True)
+                settings.REPOSITORY_PATH = ''
+            
+        return decorated
+    return decorator   
+    
+
+class SimpleTest(TestCase):
+
+    def setUp(self):
+        self.response = None
+        u = User.objects.create_user('admin', 'test@localhost', 'admin')
+        u.save()        
+
+    @temprepo('empty')
+    def test_documents_get_anonymous(self):
+        self.response = self.client.get( reverse("document_list_view") )
+        self.assert_json_response({
+            u'latest_rev': u'e56b2a7e06a97d7c3697fc4295974e0f20a66190',
+            u'documents': [],
+            u'cabinet': u'default',
+        }, exclude=['latest_shared_rev'])
+
+    @temprepo('empty')
+    def test_documents_get_with_login(self):
+        self.assertTrue(self.client.login(username='admin', password='admin'))
+        
+        self.response = self.client.get( reverse("document_list_view") )
+        self.assert_json_response({            
+            u'latest_rev': u'e56b2a7e06a97d7c3697fc4295974e0f20a66190',
+            u'documents': [],
+            u'cabinet': u'default',
+        })    
+
+    @temprepo('empty')
+    def test_documents_post(self):
+        self.assertTrue(self.client.login(username='admin', password='admin'))
+
+        infile = tempfile.NamedTemporaryFile("w+")
+        infile.write('0123456789')
+        infile.flush()
+        infile.seek(0)
+        
+        self.response = self.client.post( reverse("document_list_view"),               
+        data = {
+             'bookname': 'testbook',
+             'ocr': infile,
+             'generate_dc': False,
+        })
+
+        infile.close()
+        
+        result = self.assert_json_response({
+            'url': reverse('document_view', args=['testbook']),
+            'name': 'testbook',
+            'size': 10,
+            # can't test revision number, 'cause it's random
+        },)
+
+    @temprepo('empty')        
+    def test_document_creation(self):
+        self.assertTrue(self.client.login(username='admin', password='admin'))
+
+        infile = tempfile.NamedTemporaryFile("w+")
+        infile.write('012340123456789')
+        infile.flush()
+        infile.seek(0)
+
+        self.response = self.client.post( reverse("document_list_view"),
+        data = {
+             'bookname': 'testbook',
+             'ocr': infile,
+             'generate_dc': False,
+        })
+        
+        r = self.assert_json_response({
+            'url': reverse('document_view', args=['testbook']),
+            'name': 'testbook',
+            'size': 15,
+            # can't test revision number, 'cause it's random
+        })
+
+        created_rev = r['revision']
+
+        self.response = self.client.get( \
+            reverse("document_view", args=["testbook"])+'?autocabinet=true' )
+
+        result = self.assert_json_response({
+            u'latest_shared_rev': created_rev,
+            u'size': 15,
+        })
+
+
+    @temprepo('testone')
+    def test_document_meta_get_with_login(self):
+        self.assertTrue(self.client.login(username='admin', password='admin'))
+
+        self.response = self.client.get( reverse("document_list_view") )
+        self.assert_json_response({
+            u'latest_rev': u'f94a263812dbe46a3a13d5209bb119988d0078d5',
+            u'documents': [{u'url': u'/api/documents/testfile', u'name': u'testfile'}],
+            u'cabinet': u'default',
+        })
+
+        self.response = self.client.get( \
+            reverse("document_view", args=['testfile'])+'?autocabinet=true' )
+
+        self.assert_json_response({
+            u'latest_shared_rev': u'f94a263812dbe46a3a13d5209bb119988d0078d5',
+            u'text_url': reverse("doctext_view", args=[u'testfile']),
+            u'dc_url': reverse("docdc_view", args=[u'testfile']),
+            u'parts_url': reverse("docparts_view", args=[u'testfile']),
+            u'name': u'testfile',
+            u'size': 20,
+        })
+        
+        
+    @temprepo('test2')
+    def test_document_text_with_login(self):
+        self.assertTrue(self.client.login(username='admin', password='admin'))
+        
+        self.response = self.client.get( \
+            reverse("doctext_view", args=['testfile']) )
+
+        self.assertEqual(self.response.status_code, 200)
+        self.assertEqual(self.response.content, "Test file contents.\n")
+
+
+    @temprepo('test2')
+    def test_document_text_update(self):
+        self.assertTrue(self.client.login(username='admin', password='admin'))
+        TEXT = u"Ala ma kota i psa"
+        
+        self.response = self.client.put( \
+            reverse("doctext_view", args=['testfile']), {'contents': TEXT })
+        self.assertEqual(self.response.status_code, 200)
+
+        self.response = self.client.get( \
+            reverse("doctext_view", args=['testfile']) )
+        self.assertEqual(self.response.status_code, 200)
+        self.assertEqual(self.response.content, TEXT)
+
+    def assert_json_response(self, must_have={}, exclude=[]):
+        self.assertEqual(self.response.status_code, 200)
+        result = json.loads(self.response.content)
+
+        for (k,v) in must_have.items():
+            self.assertTrue(result.has_key(k), "Required field '%s' missing in response." % k)
+            self.assertEqual(result[k], v)
+
+        if exclude is True:
+            for (k,v) in result.items():
+                self.assertTrue(must_have.has_key(k))
+                self.assertEqual(must_have[k], v)
+
+        for key in exclude:
+            self.assertFalse(result.has_key(key))
+            
+        return result   
+              
\ No newline at end of file
diff --git a/apps/api/tests/data/empty/.hg/00changelog.i b/apps/api/tests/data/empty/.hg/00changelog.i
new file mode 100644 (file)
index 0000000..d3a8311
Binary files /dev/null and b/apps/api/tests/data/empty/.hg/00changelog.i differ
diff --git a/apps/api/tests/data/empty/.hg/branchheads.cache b/apps/api/tests/data/empty/.hg/branchheads.cache
new file mode 100644 (file)
index 0000000..022ec46
--- /dev/null
@@ -0,0 +1,2 @@
+e56b2a7e06a97d7c3697fc4295974e0f20a66190 0
+e56b2a7e06a97d7c3697fc4295974e0f20a66190 default
diff --git a/apps/api/tests/data/empty/.hg/dirstate b/apps/api/tests/data/empty/.hg/dirstate
new file mode 100644 (file)
index 0000000..3996f89
Binary files /dev/null and b/apps/api/tests/data/empty/.hg/dirstate differ
diff --git a/apps/api/tests/data/empty/.hg/requires b/apps/api/tests/data/empty/.hg/requires
new file mode 100644 (file)
index 0000000..5175383
--- /dev/null
@@ -0,0 +1,3 @@
+revlogv1
+store
+fncache
diff --git a/apps/api/tests/data/empty/.hg/store/00changelog.i b/apps/api/tests/data/empty/.hg/store/00changelog.i
new file mode 100644 (file)
index 0000000..f636141
Binary files /dev/null and b/apps/api/tests/data/empty/.hg/store/00changelog.i differ
diff --git a/apps/api/tests/data/empty/.hg/store/00manifest.i b/apps/api/tests/data/empty/.hg/store/00manifest.i
new file mode 100644 (file)
index 0000000..267c71a
Binary files /dev/null and b/apps/api/tests/data/empty/.hg/store/00manifest.i differ
diff --git a/apps/api/tests/data/empty/.hg/store/data/.library.i b/apps/api/tests/data/empty/.hg/store/data/.library.i
new file mode 100644 (file)
index 0000000..2431023
Binary files /dev/null and b/apps/api/tests/data/empty/.hg/store/data/.library.i differ
diff --git a/apps/api/tests/data/empty/.hg/store/fncache b/apps/api/tests/data/empty/.hg/store/fncache
new file mode 100644 (file)
index 0000000..d4a556e
--- /dev/null
@@ -0,0 +1 @@
+data/.library.i
diff --git a/apps/api/tests/data/empty/.hg/store/undo b/apps/api/tests/data/empty/.hg/store/undo
new file mode 100644 (file)
index 0000000..33dec7f
Binary files /dev/null and b/apps/api/tests/data/empty/.hg/store/undo differ
diff --git a/apps/api/tests/data/empty/.hg/undo.branch b/apps/api/tests/data/empty/.hg/undo.branch
new file mode 100644 (file)
index 0000000..331d858
--- /dev/null
@@ -0,0 +1 @@
+default
\ No newline at end of file
diff --git a/apps/api/tests/data/empty/.hg/undo.dirstate b/apps/api/tests/data/empty/.hg/undo.dirstate
new file mode 100644 (file)
index 0000000..ebf59c6
Binary files /dev/null and b/apps/api/tests/data/empty/.hg/undo.dirstate differ
diff --git a/apps/api/tests/data/empty/.library b/apps/api/tests/data/empty/.library
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/apps/api/tests/data/test2/.hg/00changelog.i b/apps/api/tests/data/test2/.hg/00changelog.i
new file mode 100644 (file)
index 0000000..d3a8311
Binary files /dev/null and b/apps/api/tests/data/test2/.hg/00changelog.i differ
diff --git a/apps/api/tests/data/test2/.hg/branch b/apps/api/tests/data/test2/.hg/branch
new file mode 100644 (file)
index 0000000..4ad96d5
--- /dev/null
@@ -0,0 +1 @@
+default
diff --git a/apps/api/tests/data/test2/.hg/branchheads.cache b/apps/api/tests/data/test2/.hg/branchheads.cache
new file mode 100644 (file)
index 0000000..650c9b0
--- /dev/null
@@ -0,0 +1,3 @@
+93618f13c33aab5a3d049e0860cd3d21a457cd7d 2
+f94a263812dbe46a3a13d5209bb119988d0078d5 default
+93618f13c33aab5a3d049e0860cd3d21a457cd7d personal_admin_file_testfile
diff --git a/apps/api/tests/data/test2/.hg/dirstate b/apps/api/tests/data/test2/.hg/dirstate
new file mode 100644 (file)
index 0000000..cc53c86
Binary files /dev/null and b/apps/api/tests/data/test2/.hg/dirstate differ
diff --git a/apps/api/tests/data/test2/.hg/requires b/apps/api/tests/data/test2/.hg/requires
new file mode 100644 (file)
index 0000000..5175383
--- /dev/null
@@ -0,0 +1,3 @@
+revlogv1
+store
+fncache
diff --git a/apps/api/tests/data/test2/.hg/store/00changelog.i b/apps/api/tests/data/test2/.hg/store/00changelog.i
new file mode 100644 (file)
index 0000000..4d3d86a
Binary files /dev/null and b/apps/api/tests/data/test2/.hg/store/00changelog.i differ
diff --git a/apps/api/tests/data/test2/.hg/store/00manifest.i b/apps/api/tests/data/test2/.hg/store/00manifest.i
new file mode 100644 (file)
index 0000000..de39968
Binary files /dev/null and b/apps/api/tests/data/test2/.hg/store/00manifest.i differ
diff --git a/apps/api/tests/data/test2/.hg/store/data/.library.i b/apps/api/tests/data/test2/.hg/store/data/.library.i
new file mode 100644 (file)
index 0000000..2431023
Binary files /dev/null and b/apps/api/tests/data/test2/.hg/store/data/.library.i differ
diff --git a/apps/api/tests/data/test2/.hg/store/data/pub__testfile.xml.i b/apps/api/tests/data/test2/.hg/store/data/pub__testfile.xml.i
new file mode 100644 (file)
index 0000000..ca41781
Binary files /dev/null and b/apps/api/tests/data/test2/.hg/store/data/pub__testfile.xml.i differ
diff --git a/apps/api/tests/data/test2/.hg/store/fncache b/apps/api/tests/data/test2/.hg/store/fncache
new file mode 100644 (file)
index 0000000..d55636d
--- /dev/null
@@ -0,0 +1,2 @@
+data/.library.i
+data/pub_testfile.xml.i
diff --git a/apps/api/tests/data/test2/.hg/store/undo b/apps/api/tests/data/test2/.hg/store/undo
new file mode 100644 (file)
index 0000000..33e8207
Binary files /dev/null and b/apps/api/tests/data/test2/.hg/store/undo differ
diff --git a/apps/api/tests/data/test2/.hg/undo.branch b/apps/api/tests/data/test2/.hg/undo.branch
new file mode 100644 (file)
index 0000000..998eac4
--- /dev/null
@@ -0,0 +1 @@
+personal_admin_file_testfile
\ No newline at end of file
diff --git a/apps/api/tests/data/test2/.hg/undo.dirstate b/apps/api/tests/data/test2/.hg/undo.dirstate
new file mode 100644 (file)
index 0000000..431d58a
Binary files /dev/null and b/apps/api/tests/data/test2/.hg/undo.dirstate differ
diff --git a/apps/api/tests/data/test2/.library b/apps/api/tests/data/test2/.library
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/apps/api/tests/data/test2/pub_testfile.xml b/apps/api/tests/data/test2/pub_testfile.xml
new file mode 100644 (file)
index 0000000..57873f2
--- /dev/null
@@ -0,0 +1 @@
+Test file contents.
diff --git a/apps/api/tests/data/testone/.hg/00changelog.i b/apps/api/tests/data/testone/.hg/00changelog.i
new file mode 100644 (file)
index 0000000..d3a8311
Binary files /dev/null and b/apps/api/tests/data/testone/.hg/00changelog.i differ
diff --git a/apps/api/tests/data/testone/.hg/branchheads.cache b/apps/api/tests/data/testone/.hg/branchheads.cache
new file mode 100644 (file)
index 0000000..6087023
--- /dev/null
@@ -0,0 +1,2 @@
+f94a263812dbe46a3a13d5209bb119988d0078d5 1
+f94a263812dbe46a3a13d5209bb119988d0078d5 default
diff --git a/apps/api/tests/data/testone/.hg/dirstate b/apps/api/tests/data/testone/.hg/dirstate
new file mode 100644 (file)
index 0000000..121c271
Binary files /dev/null and b/apps/api/tests/data/testone/.hg/dirstate differ
diff --git a/apps/api/tests/data/testone/.hg/requires b/apps/api/tests/data/testone/.hg/requires
new file mode 100644 (file)
index 0000000..5175383
--- /dev/null
@@ -0,0 +1,3 @@
+revlogv1
+store
+fncache
diff --git a/apps/api/tests/data/testone/.hg/store/00changelog.i b/apps/api/tests/data/testone/.hg/store/00changelog.i
new file mode 100644 (file)
index 0000000..c466ccb
Binary files /dev/null and b/apps/api/tests/data/testone/.hg/store/00changelog.i differ
diff --git a/apps/api/tests/data/testone/.hg/store/00manifest.i b/apps/api/tests/data/testone/.hg/store/00manifest.i
new file mode 100644 (file)
index 0000000..0239a8f
Binary files /dev/null and b/apps/api/tests/data/testone/.hg/store/00manifest.i differ
diff --git a/apps/api/tests/data/testone/.hg/store/data/.library.i b/apps/api/tests/data/testone/.hg/store/data/.library.i
new file mode 100644 (file)
index 0000000..2431023
Binary files /dev/null and b/apps/api/tests/data/testone/.hg/store/data/.library.i differ
diff --git a/apps/api/tests/data/testone/.hg/store/data/pub__testfile.xml.i b/apps/api/tests/data/testone/.hg/store/data/pub__testfile.xml.i
new file mode 100644 (file)
index 0000000..ca41781
Binary files /dev/null and b/apps/api/tests/data/testone/.hg/store/data/pub__testfile.xml.i differ
diff --git a/apps/api/tests/data/testone/.hg/store/fncache b/apps/api/tests/data/testone/.hg/store/fncache
new file mode 100644 (file)
index 0000000..d55636d
--- /dev/null
@@ -0,0 +1,2 @@
+data/.library.i
+data/pub_testfile.xml.i
diff --git a/apps/api/tests/data/testone/.hg/store/undo b/apps/api/tests/data/testone/.hg/store/undo
new file mode 100644 (file)
index 0000000..0dbe30a
Binary files /dev/null and b/apps/api/tests/data/testone/.hg/store/undo differ
diff --git a/apps/api/tests/data/testone/.hg/undo.branch b/apps/api/tests/data/testone/.hg/undo.branch
new file mode 100644 (file)
index 0000000..331d858
--- /dev/null
@@ -0,0 +1 @@
+default
\ No newline at end of file
diff --git a/apps/api/tests/data/testone/.hg/undo.dirstate b/apps/api/tests/data/testone/.hg/undo.dirstate
new file mode 100644 (file)
index 0000000..a2fe683
Binary files /dev/null and b/apps/api/tests/data/testone/.hg/undo.dirstate differ
diff --git a/apps/api/tests/data/testone/.library b/apps/api/tests/data/testone/.library
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/apps/api/tests/data/testone/pub_testfile.xml b/apps/api/tests/data/testone/pub_testfile.xml
new file mode 100644 (file)
index 0000000..57873f2
--- /dev/null
@@ -0,0 +1 @@
+Test file contents.
diff --git a/apps/api/urls.py b/apps/api/urls.py
new file mode 100644 (file)
index 0000000..ada879c
--- /dev/null
@@ -0,0 +1,48 @@
+__author__="lreqc"
+__date__ ="$2009-09-17 16:16:54$"
+
+from django.conf.urls.defaults import *
+from piston.resource import Resource
+
+from api.handlers import *
+from api.utils import TextEmitter, DjangoAuth
+
+authdata = {'authentication': DjangoAuth()}
+
+FORMAT_EXT = r"\.(?P<emitter_format>xml|json|yaml|django)$"
+
+library_resource = Resource(LibraryHandler, **authdata)
+document_resource = Resource(DocumentHandler, **authdata)
+document_text_resource = Resource(DocumentTextHandler, **authdata)
+
+urlpatterns = patterns('',
+#    url(r'^hello$', hello_resource, {'emitter_format': 'json'}),
+#    url(r'^hello\.(?P<emitter_format>.+)$', hello_resource),
+
+    # Documents
+    url(r'^documents$', library_resource, {'emitter_format': 'json'},
+        name="document_list_view"),
+
+    url(r'^documents/(?P<docid>[^/]+)'+FORMAT_EXT,
+        document_resource, name="document_view_withformat"),
+
+    url(r'^documents/(?P<docid>[^/]+)$',
+        document_resource, {'emitter_format': 'json'},
+        name="document_view"),
+    
+    url(r'^documents/(?P<docid>[^/]+)/text$',
+        document_text_resource, {'emitter_format': 'rawxml'},
+        name="doctext_view"),
+
+    url(r'^documents/(?P<docid>[^/]+)/dc$',
+        document_resource, {'emitter_format': 'json'},
+        name="docdc_view"),
+
+    url(r'^documents/(?P<docid>[^/]+)/parts$',
+        document_resource, {'emitter_format': 'json'},
+        name="docparts_view"),
+        
+  #  url(r'^posts/(?P<post_slug>[^/]+)/$', blogpost_resource),
+  #  url(r'^other/(?P<username>[^/]+)/(?P<data>.+)/$', arbitrary_resource),
+)
+
diff --git a/apps/api/utils.py b/apps/api/utils.py
new file mode 100644 (file)
index 0000000..67e8ea5
--- /dev/null
@@ -0,0 +1,25 @@
+# -*- encoding: utf-8 -*-
+
+__author__= "Łukasz Rekucki"
+__date__ = "$2009-09-20 21:48:03$"
+__doc__ = "Module documentation."
+
+
+from piston.emitters import Emitter
+from piston.utils import rc
+
+class TextEmitter(Emitter):
+    def render(self, request):
+        return unicode(self.construct())
+
+Emitter.register('text', TextEmitter, 'text/plain; charset=utf-8')
+Emitter.register('rawxml', TextEmitter, 'application/xml; charset=utf-8')
+
+
+class DjangoAuth(object):
+
+    def is_authenticated(self, request):
+        return request.user.is_authenticated()
+
+    def challenge(self):
+        return rc.FORBIDDEN
\ No newline at end of file
diff --git a/apps/api/views.py b/apps/api/views.py
new file mode 100644 (file)
index 0000000..60f00ef
--- /dev/null
@@ -0,0 +1 @@
+# Create your views here.
diff --git a/lib/run_tests.sh b/lib/run_tests.sh
new file mode 100755 (executable)
index 0000000..0b086ef
--- /dev/null
@@ -0,0 +1 @@
+PYTYONPATH=./lib:$PYTHONPATH nosetests --detailed-errors --with-doctest --with-coverage --cover-package=wlrepo
diff --git a/lib/wlrepo/__init__.py b/lib/wlrepo/__init__.py
new file mode 100644 (file)
index 0000000..5c27dc9
--- /dev/null
@@ -0,0 +1,111 @@
+# -*- encoding: utf-8 -*-
+__author__="Łukasz Rekucki"
+__date__ ="$2009-09-18 10:49:24$"
+
+__doc__ = """Main module for the Repository Abstraction Layer"""
+
+class Library(object):
+
+    def __init__(self, create=False):
+        """Open an existing library, or create a new one. By default, fails if
+        the library doesn't exist."""
+        self.create = create     
+        
+    def main_cabinet(self):
+        """Return the "main" cabinet of the library."""
+        pass
+
+    def cabinets(self):
+        """List all cabinets in the library."""
+        pass
+
+    def cabinet(self, document, user, create=False):
+        """Open a cabinet belonging to the _user_ for a given _document_.
+        If the _document_ is actually a sub-document, it's parent's cabinet is
+        opened istead.
+        
+        If the cabinet doesn't exists and create is False (the default), a 
+        CabinetNotFound exception is raised.
+        
+        If create is True, a new cabinet is created if it doesn't exist yet."""
+        pass
+
+
+class Cabinet(object):
+
+    def __init__(self, library, name=None, doc=None, user=None):
+        self._library = library
+        if name:
+            self._name = name
+            self._maindoc = ''
+        elif doc and user:
+            self._name = user + ':' + doc
+            self._maindoc = doc
+        else:
+            raise ValueError("You must provide either name or doc and user.")
+
+    def documents(self):
+        """Lists all documents and sub-documents in this cabinet."""
+        pass
+    
+    def retrieve(self, parts=None, shelve=None):
+        """Retrieve a document from a given shelve in the cabinet. If no
+        part is given, the main document is retrieved. If no shelve is given,
+        the top-most shelve is used.
+
+        If parts is a list, all the given parts are retrieved atomicly. Use None
+        as the name for the main document"""
+        pass
+
+    def create(self, name):
+        """Create a new sub-document in the cabinet with the given name."""
+        pass
+    
+    def maindoc_name(self):
+        return self._maindoc
+
+
+    @property
+    def library(self):
+        return self._library
+
+    @property
+    def name(self):
+        return self._name
+
+class Document(object):
+    def __init__(self, cabinet, name):
+        self._cabinet = cabinet
+        self._name = name
+
+    def read(self):
+        pass
+
+    def write(self, data):
+        pass
+
+    @property
+    def cabinet(self):
+        return self._cabinet
+
+    @property
+    def library(self):
+        return self._cabinet.library
+
+
+#
+# Exception classes
+#
+
+class LibraryException(Exception):
+    
+    def __init__(self, msg, cause=None):
+        Exception.__init__(self, msg)
+        self.cause = cause
+
+class CabinetNotFound(LibraryException):
+    pass
+
+
+# import backends to local namespace
+from backend_mercurial import MercurialLibrary
\ No newline at end of file
diff --git a/lib/wlrepo/backend_mercurial.py b/lib/wlrepo/backend_mercurial.py
new file mode 100644 (file)
index 0000000..38f01da
--- /dev/null
@@ -0,0 +1,339 @@
+# -*- encoding: utf-8 -*-
+__author__ = "Łukasz Rekucki"
+__date__ = "$2009-09-18 10:49:24$"
+
+__doc__ = """RAL implementation over Mercurial"""
+
+import mercurial
+from mercurial import localrepo as hglrepo
+from mercurial import ui as hgui
+import re
+import wlrepo
+
+FILTER = re.compile(r"^pub_(.+)\.xml$", re.UNICODE)
+
+def default_filter(name):
+    m = FILTER.match(name)    
+    if m is not None:
+        return name, m.group(1)
+    return None
+
+class MercurialLibrary(wlrepo.Library):
+
+    def __init__(self, path, maincabinet="default", ** kwargs):
+        super(wlrepo.Library, self).__init__( ** kwargs)
+
+        self._hgui = hgui.ui()
+        self._hgui.config('ui', 'quiet', 'true')
+        self._hgui.config('ui', 'interactive', 'false')
+
+        import os.path        
+        self._ospath = self._sanitize_string(os.path.realpath(path))
+        
+        maincabinet = self._sanitize_string(maincabinet)
+
+        if os.path.isdir(path):
+            try:
+                self._hgrepo = hglrepo.localrepository(self._hgui, path)
+            except mercurial.error.RepoError:
+                raise wlrepo.LibraryException("[HGLibrary]Not a valid repository at path '%s'." % path)
+        elif kwargs['create']:
+            os.makedirs(path)
+            try:
+                self._hgrepo = hglrepo.localrepository(self._hgui, path, create=1)
+            except mercurial.error.RepoError:
+                raise wlrepo.LibraryException("[HGLibrary] Can't create a repository on path '%s'." % path)
+        else:
+            raise wlrepo.LibraryException("[HGLibrary] Can't open a library on path '%s'." % path)
+
+        # fetch the main cabinet
+        lock = self._hgrepo.lock()
+        try:
+            btags = self._hgrepo.branchtags()
+            
+            if not self._has_branch(maincabinet):
+                raise wlrepo.LibraryException("[HGLibrary] No branch named '%s' to init main cabinet" % maincabinet)
+        
+            self._maincab = MercurialCabinet(self, maincabinet)
+        finally:
+            lock.release()
+
+    @property
+    def main_cabinet(self):
+        return self._maincab
+
+    def cabinet(self, docid, user, create=False):
+        bname = self._bname(user, docid)
+
+        lock = self._lock(True)
+        try:
+            if self._has_branch(bname):
+                return MercurialCabinet(self, bname, doc=docid, user=user)
+
+            if not create:
+                raise wlrepo.CabinetNotFound(docid, user)
+
+            # check if the docid exists in the main cabinet
+            needs_touch = not self._maincab.exists(docid)
+            print "Creating branch: ", needs_touch
+            cab = MercurialCabinet(self, bname, doc=docid, user=user)
+
+            fileid = cab._fileid(None)
+
+            def cleanup_action(l):
+                if needs_touch:
+                    print "Touch for file", docid
+                    l._fileopener()(fileid, "w").write('')
+                    l._fileadd(fileid)
+                
+                garbage = [fid for (fid, did) in l._filelist() if not did.startswith(docid)]
+                print "Garbage: ", garbage
+                l._filesrm(garbage)
+
+            # create the branch
+            self._create_branch(bname, before_commit=cleanup_action)
+            return MercurialCabinet(self, bname, doc=docid, user=user)
+        finally:
+            lock.release()
+            
+    #
+    # Private methods
+    #
+
+    #
+    # Locking
+    #
+    def _lock(self, write_mode=False):
+        return self._hgrepo.wlock() # no support for read/write mode yet
+
+    def _transaction(self, write_mode, action):
+        lock = self._lock(write_mode)
+        try:
+            return action(self)
+        finally:
+            lock.release()
+            
+    #
+    # Basic repo manipulation
+    #   
+
+    def _checkout(self, rev, force=True):
+        return MergeStatus(mercurial.merge.update(self._hgrepo, rev, False, force, None))
+
+    def _merge(self, rev):
+        """ Merge the revision into current working directory """
+        return MergeStatus(mercurial.merge.update(self._hgrepo, rev, True, False, None))
+    
+    def _common_ancestor(self, revA, revB):
+        return self._hgrepo[revA].ancestor(self.repo[revB])
+
+    def _commit(self, message, user="library"):
+        return self._hgrepo.commit(\
+                                   text=self._sanitize_string(message), \
+                                   user=self._sanitize_string(user))
+
+
+    def _fileexists(self, fileid):
+        return (fileid in self._hgrepo[None])
+
+    def _fileadd(self, fileid):
+        return self._hgrepo.add([fileid])
+    
+    def _filesadd(self, fileid_list):
+        return self._hgrepo.add(fileid_list)
+
+    def _filerm(self, fileid):
+        return self._hgrepo.remove([fileid])
+
+    def _filesrm(self, fileid_list):
+        return self._hgrepo.remove(fileid_list)
+
+    def _filelist(self, filter=default_filter):
+        for name in  self._hgrepo[None]:
+            result = filter(name)
+            if result is None: continue
+            
+            yield result
+
+    def _fileopener(self):
+        return self._hgrepo.wopener
+    
+    #
+    # BASIC BRANCH routines
+    #
+
+    def _bname(self, user, docid):
+        """Returns a branch name for a given document and user."""
+        docid = self._sanitize_string(docid)
+        uname = self._sanitize_string(user)
+        return "personal_" + uname + "_file_" + docid;
+
+    def _has_branch(self, name):
+        return self._hgrepo.branchmap().has_key(self._sanitize_string(name))
+
+    def _branch_tip(self, name):
+        name = self._sanitize_string(name)
+        return self._hgrepo.branchtags()[name]
+
+    def _create_branch(self, name, parent=None, before_commit=None):        
+        name = self._sanitize_string(name)
+
+        if self._has_branch(name): return # just exit
+
+        if parent is None:
+            parent = self._maincab
+
+        parentrev = parent._hgtip()
+
+        self._checkout(parentrev)
+        self._hgrepo.dirstate.setbranch(name)
+
+        if before_commit: before_commit(self)
+
+        print "commiting"
+        self._commit("[AUTO] Initial commit for branch '%s'." % name, user='library')
+        
+        # revert back to main
+        self._checkout(self._maincab._hgtip())
+        return self._branch_tip(name)
+
+    def _switch_to_branch(self, branchname):
+        current = self._hgrepo[None].branch()
+
+        if current == branchname:
+            return current # quick exit
+        
+        self._checkout(self._branch_tip(branchname))
+        return branchname        
+
+    #
+    # Merges
+    #
+    
+
+
+    #
+    # Utils
+    #
+
+    @staticmethod
+    def _sanitize_string(s):
+        if isinstance(s, unicode): #
+            return s.encode('utf-8')
+        else: # it's a string, so we have no idea what encoding it is
+            return s
+
+class MercurialCabinet(wlrepo.Cabinet):
+    
+    def __init__(self, library, branchname, doc=None, user=None):
+        if doc and user:
+            super(MercurialCabinet, self).__init__(library, doc=doc, user=user)
+        else:
+            super(MercurialCabinet, self).__init__(library, name=branchname)
+            
+        self._branchname = branchname
+
+    def documents(self):        
+        return self._execute_in_branch(action=lambda l, c: ( e[1] for e in l._filelist()) )
+
+    def retrieve(self, part=None, shelve=None):
+        fileid = self._fileid(part)
+
+        if fileid is None:
+            raise wlrepo.LibraryException("Can't retrieve main document from main cabinet.")
+                
+        return self._execute_in_branch(lambda l, c: MercurialDocument(c, fileid))
+
+    def create(self, name, initial_data=''):
+        fileid = self._fileid(name)
+
+        if name is None:
+            raise ValueError("Can't create main doc for maincabinet.")
+
+        def create_action(l, c):
+            if l._fileexists(fileid):
+                raise wlrepo.LibraryException("Can't create document '%s' in cabinet '%s' - it already exists" % (fileid, c.name))
+
+            fd = l._fileopener()(fileid, "w")
+            fd.write(initial_data)
+            l._fileadd(fileid)
+            l._commit("File '%d' created.")
+
+            return MercurialDocument(c, fileid)
+
+        return self._execute_in_branch(create_action)
+
+    def exists(self, part=None, shelve=None):
+        fileid = self._fileid(part)
+
+        if fileid is None: return false
+        return self._execute_in_branch(lambda l, c: l._fileexists(fileid))
+    
+    def _execute_in_branch(self, action, write=False):
+        def switch_action(library):
+            old = library._switch_to_branch(self._branchname)
+            try:
+                return action(library, self)
+            finally:
+                library._switch_to_branch(old)
+
+        return self._library._transaction(write_mode=write, action=switch_action)
+
+    def _fileid(self, part):
+        fileid = None
+
+        if self._maindoc == '':
+            if part is None: return None              
+            fileid = part
+        else:
+            fileid = self._maindoc + (('$' + part) if part else '')
+
+        return 'pub_' + fileid + '.xml'        
+
+    def _fileopener(self):
+        return self._library._fileopener()
+
+    def _hgtip(self):
+        return self._library._branch_tip(self._branchname)
+
+class MercurialDocument(wlrepo.Document):
+
+    def __init__(self, cabinet, fileid):
+        super(MercurialDocument, self).__init__(cabinet, fileid)
+        self._opener = self._cabinet._fileopener()        
+
+    def read(self):
+        return self._opener(self._name, "r").read()
+
+    def write(self, data):
+        return self._opener(self._name, "w").write(data)
+
+
+class MergeStatus(object):
+    def __init__(self, mstatus):
+        self.updated = mstatus[0]
+        self.merged = mstatus[1]
+        self.removed = mstatus[2]
+        self.unresolved = mstatus[3]
+
+    def isclean(self):
+        return self.unresolved == 0
+
+class UpdateStatus(object):
+
+    def __init__(self, mstatus):
+        self.modified = mstatus[0]
+        self.added = mstatus[1]
+        self.removed = mstatus[2]
+        self.deleted = mstatus[3]
+        self.untracked = mstatus[4]
+        self.ignored = mstatus[5]
+        self.clean = mstatus[6]
+
+    def has_changes(self):
+        return bool(len(self.modified) + len(self.added) + \
+                    len(self.removed) + len(self.deleted))
+
+
+__all__ = ["MercurialLibrary", "MercurialCabinet", "MercurialDocument"]
\ No newline at end of file
diff --git a/lib/wlrepo/tests/__init__.py b/lib/wlrepo/tests/__init__.py
new file mode 100644 (file)
index 0000000..c9e2f1d
--- /dev/null
@@ -0,0 +1,7 @@
+# -*- encoding: utf-8 -*-
+
+__author__= "Łukasz Rekucki"
+__date__ = "$2009-09-18 14:42:53$"
+__doc__ = "Test package for WL-RAL"
+
+
diff --git a/lib/wlrepo/tests/data/repos/cleanrepo/.hg/00changelog.i b/lib/wlrepo/tests/data/repos/cleanrepo/.hg/00changelog.i
new file mode 100644 (file)
index 0000000..d3a8311
Binary files /dev/null and b/lib/wlrepo/tests/data/repos/cleanrepo/.hg/00changelog.i differ
diff --git a/lib/wlrepo/tests/data/repos/cleanrepo/.hg/dirstate b/lib/wlrepo/tests/data/repos/cleanrepo/.hg/dirstate
new file mode 100644 (file)
index 0000000..b0aa1e6
Binary files /dev/null and b/lib/wlrepo/tests/data/repos/cleanrepo/.hg/dirstate differ
diff --git a/lib/wlrepo/tests/data/repos/cleanrepo/.hg/requires b/lib/wlrepo/tests/data/repos/cleanrepo/.hg/requires
new file mode 100644 (file)
index 0000000..5175383
--- /dev/null
@@ -0,0 +1,3 @@
+revlogv1
+store
+fncache
diff --git a/lib/wlrepo/tests/data/repos/cleanrepo/.hg/store/00changelog.i b/lib/wlrepo/tests/data/repos/cleanrepo/.hg/store/00changelog.i
new file mode 100644 (file)
index 0000000..45c1c46
Binary files /dev/null and b/lib/wlrepo/tests/data/repos/cleanrepo/.hg/store/00changelog.i differ
diff --git a/lib/wlrepo/tests/data/repos/cleanrepo/.hg/store/00manifest.i b/lib/wlrepo/tests/data/repos/cleanrepo/.hg/store/00manifest.i
new file mode 100644 (file)
index 0000000..ed99f61
Binary files /dev/null and b/lib/wlrepo/tests/data/repos/cleanrepo/.hg/store/00manifest.i differ
diff --git a/lib/wlrepo/tests/data/repos/cleanrepo/.hg/store/data/.hgignore.i b/lib/wlrepo/tests/data/repos/cleanrepo/.hg/store/data/.hgignore.i
new file mode 100644 (file)
index 0000000..2431023
Binary files /dev/null and b/lib/wlrepo/tests/data/repos/cleanrepo/.hg/store/data/.hgignore.i differ
diff --git a/lib/wlrepo/tests/data/repos/cleanrepo/.hg/store/data/ignored__file.i b/lib/wlrepo/tests/data/repos/cleanrepo/.hg/store/data/ignored__file.i
new file mode 100644 (file)
index 0000000..a6bdf46
Binary files /dev/null and b/lib/wlrepo/tests/data/repos/cleanrepo/.hg/store/data/ignored__file.i differ
diff --git a/lib/wlrepo/tests/data/repos/cleanrepo/.hg/store/data/pub__valid__file.xml.i b/lib/wlrepo/tests/data/repos/cleanrepo/.hg/store/data/pub__valid__file.xml.i
new file mode 100644 (file)
index 0000000..a6bdf46
Binary files /dev/null and b/lib/wlrepo/tests/data/repos/cleanrepo/.hg/store/data/pub__valid__file.xml.i differ
diff --git a/lib/wlrepo/tests/data/repos/cleanrepo/.hg/store/fncache b/lib/wlrepo/tests/data/repos/cleanrepo/.hg/store/fncache
new file mode 100644 (file)
index 0000000..a1607dd
--- /dev/null
@@ -0,0 +1,3 @@
+data/.hgignore.i
+data/ignored_file.i
+data/pub_valid_file.xml.i
diff --git a/lib/wlrepo/tests/data/repos/cleanrepo/.hg/store/undo b/lib/wlrepo/tests/data/repos/cleanrepo/.hg/store/undo
new file mode 100644 (file)
index 0000000..90d4cb1
Binary files /dev/null and b/lib/wlrepo/tests/data/repos/cleanrepo/.hg/store/undo differ
diff --git a/lib/wlrepo/tests/data/repos/cleanrepo/.hg/undo.branch b/lib/wlrepo/tests/data/repos/cleanrepo/.hg/undo.branch
new file mode 100644 (file)
index 0000000..331d858
--- /dev/null
@@ -0,0 +1 @@
+default
\ No newline at end of file
diff --git a/lib/wlrepo/tests/data/repos/cleanrepo/.hg/undo.dirstate b/lib/wlrepo/tests/data/repos/cleanrepo/.hg/undo.dirstate
new file mode 100644 (file)
index 0000000..1d0af27
Binary files /dev/null and b/lib/wlrepo/tests/data/repos/cleanrepo/.hg/undo.dirstate differ
diff --git a/lib/wlrepo/tests/data/repos/cleanrepo/.hgignore b/lib/wlrepo/tests/data/repos/cleanrepo/.hgignore
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/lib/wlrepo/tests/data/repos/cleanrepo/ignored_file b/lib/wlrepo/tests/data/repos/cleanrepo/ignored_file
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/lib/wlrepo/tests/data/repos/cleanrepo/pub_valid_file.xml b/lib/wlrepo/tests/data/repos/cleanrepo/pub_valid_file.xml
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/lib/wlrepo/tests/data/repos/testrepoI/.hg/00changelog.i b/lib/wlrepo/tests/data/repos/testrepoI/.hg/00changelog.i
new file mode 100644 (file)
index 0000000..d3a8311
Binary files /dev/null and b/lib/wlrepo/tests/data/repos/testrepoI/.hg/00changelog.i differ
diff --git a/lib/wlrepo/tests/data/repos/testrepoI/.hg/dirstate b/lib/wlrepo/tests/data/repos/testrepoI/.hg/dirstate
new file mode 100644 (file)
index 0000000..c13e1c1
Binary files /dev/null and b/lib/wlrepo/tests/data/repos/testrepoI/.hg/dirstate differ
diff --git a/lib/wlrepo/tests/data/repos/testrepoI/.hg/requires b/lib/wlrepo/tests/data/repos/testrepoI/.hg/requires
new file mode 100644 (file)
index 0000000..5175383
--- /dev/null
@@ -0,0 +1,3 @@
+revlogv1
+store
+fncache
diff --git a/lib/wlrepo/tests/data/repos/testrepoI/.hg/store/00changelog.i b/lib/wlrepo/tests/data/repos/testrepoI/.hg/store/00changelog.i
new file mode 100644 (file)
index 0000000..35b7cda
Binary files /dev/null and b/lib/wlrepo/tests/data/repos/testrepoI/.hg/store/00changelog.i differ
diff --git a/lib/wlrepo/tests/data/repos/testrepoI/.hg/store/00manifest.i b/lib/wlrepo/tests/data/repos/testrepoI/.hg/store/00manifest.i
new file mode 100644 (file)
index 0000000..7eb40cb
Binary files /dev/null and b/lib/wlrepo/tests/data/repos/testrepoI/.hg/store/00manifest.i differ
diff --git a/lib/wlrepo/tests/data/repos/testrepoI/.hg/store/data/.hgignore.i b/lib/wlrepo/tests/data/repos/testrepoI/.hg/store/data/.hgignore.i
new file mode 100644 (file)
index 0000000..2431023
Binary files /dev/null and b/lib/wlrepo/tests/data/repos/testrepoI/.hg/store/data/.hgignore.i differ
diff --git a/lib/wlrepo/tests/data/repos/testrepoI/.hg/store/data/ignored__file.i b/lib/wlrepo/tests/data/repos/testrepoI/.hg/store/data/ignored__file.i
new file mode 100644 (file)
index 0000000..a6bdf46
Binary files /dev/null and b/lib/wlrepo/tests/data/repos/testrepoI/.hg/store/data/ignored__file.i differ
diff --git a/lib/wlrepo/tests/data/repos/testrepoI/.hg/store/data/pub__polish__file.xml.i b/lib/wlrepo/tests/data/repos/testrepoI/.hg/store/data/pub__polish__file.xml.i
new file mode 100644 (file)
index 0000000..3488bcc
Binary files /dev/null and b/lib/wlrepo/tests/data/repos/testrepoI/.hg/store/data/pub__polish__file.xml.i differ
diff --git a/lib/wlrepo/tests/data/repos/testrepoI/.hg/store/data/pub__valid__file.xml.i b/lib/wlrepo/tests/data/repos/testrepoI/.hg/store/data/pub__valid__file.xml.i
new file mode 100644 (file)
index 0000000..fd3c7f7
Binary files /dev/null and b/lib/wlrepo/tests/data/repos/testrepoI/.hg/store/data/pub__valid__file.xml.i differ
diff --git a/lib/wlrepo/tests/data/repos/testrepoI/.hg/store/fncache b/lib/wlrepo/tests/data/repos/testrepoI/.hg/store/fncache
new file mode 100644 (file)
index 0000000..8730267
--- /dev/null
@@ -0,0 +1,4 @@
+data/.hgignore.i
+data/ignored_file.i
+data/pub_valid_file.xml.i
+data/pub_polish_file.xml.i
diff --git a/lib/wlrepo/tests/data/repos/testrepoI/.hg/store/undo b/lib/wlrepo/tests/data/repos/testrepoI/.hg/store/undo
new file mode 100644 (file)
index 0000000..62ca7e4
Binary files /dev/null and b/lib/wlrepo/tests/data/repos/testrepoI/.hg/store/undo differ
diff --git a/lib/wlrepo/tests/data/repos/testrepoI/.hg/undo.branch b/lib/wlrepo/tests/data/repos/testrepoI/.hg/undo.branch
new file mode 100644 (file)
index 0000000..331d858
--- /dev/null
@@ -0,0 +1 @@
+default
\ No newline at end of file
diff --git a/lib/wlrepo/tests/data/repos/testrepoI/.hg/undo.dirstate b/lib/wlrepo/tests/data/repos/testrepoI/.hg/undo.dirstate
new file mode 100644 (file)
index 0000000..fb1041a
Binary files /dev/null and b/lib/wlrepo/tests/data/repos/testrepoI/.hg/undo.dirstate differ
diff --git a/lib/wlrepo/tests/data/repos/testrepoI/.hgignore b/lib/wlrepo/tests/data/repos/testrepoI/.hgignore
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/lib/wlrepo/tests/data/repos/testrepoI/ignored_file b/lib/wlrepo/tests/data/repos/testrepoI/ignored_file
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/lib/wlrepo/tests/data/repos/testrepoI/pub_polish_file.xml b/lib/wlrepo/tests/data/repos/testrepoI/pub_polish_file.xml
new file mode 100644 (file)
index 0000000..c7ada58
--- /dev/null
@@ -0,0 +1 @@
+Gąska!
diff --git a/lib/wlrepo/tests/data/repos/testrepoI/pub_valid_file.xml b/lib/wlrepo/tests/data/repos/testrepoI/pub_valid_file.xml
new file mode 100644 (file)
index 0000000..8432188
--- /dev/null
@@ -0,0 +1 @@
+Ala ma kota
diff --git a/lib/wlrepo/tests/data/repos/testrepoII/.hg/00changelog.i b/lib/wlrepo/tests/data/repos/testrepoII/.hg/00changelog.i
new file mode 100644 (file)
index 0000000..d3a8311
Binary files /dev/null and b/lib/wlrepo/tests/data/repos/testrepoII/.hg/00changelog.i differ
diff --git a/lib/wlrepo/tests/data/repos/testrepoII/.hg/branch b/lib/wlrepo/tests/data/repos/testrepoII/.hg/branch
new file mode 100644 (file)
index 0000000..4ad96d5
--- /dev/null
@@ -0,0 +1 @@
+default
diff --git a/lib/wlrepo/tests/data/repos/testrepoII/.hg/branchheads.cache b/lib/wlrepo/tests/data/repos/testrepoII/.hg/branchheads.cache
new file mode 100644 (file)
index 0000000..6baf99d
--- /dev/null
@@ -0,0 +1,4 @@
+e0fbb8dc3f54c34581848d59a215d1d222a9e891 4
+26ecd403388cce326f6ca1e32625db0651900eb7 default
+34d4aa807563b28774bcbe07f6acbb984aa4dc8d personal_tester_valid_file
+e0fbb8dc3f54c34581848d59a215d1d222a9e891 personal_tester_file_valid_file
diff --git a/lib/wlrepo/tests/data/repos/testrepoII/.hg/dirstate b/lib/wlrepo/tests/data/repos/testrepoII/.hg/dirstate
new file mode 100644 (file)
index 0000000..9c1a27d
Binary files /dev/null and b/lib/wlrepo/tests/data/repos/testrepoII/.hg/dirstate differ
diff --git a/lib/wlrepo/tests/data/repos/testrepoII/.hg/requires b/lib/wlrepo/tests/data/repos/testrepoII/.hg/requires
new file mode 100644 (file)
index 0000000..5175383
--- /dev/null
@@ -0,0 +1,3 @@
+revlogv1
+store
+fncache
diff --git a/lib/wlrepo/tests/data/repos/testrepoII/.hg/store/00changelog.i b/lib/wlrepo/tests/data/repos/testrepoII/.hg/store/00changelog.i
new file mode 100644 (file)
index 0000000..f7ca408
Binary files /dev/null and b/lib/wlrepo/tests/data/repos/testrepoII/.hg/store/00changelog.i differ
diff --git a/lib/wlrepo/tests/data/repos/testrepoII/.hg/store/00manifest.i b/lib/wlrepo/tests/data/repos/testrepoII/.hg/store/00manifest.i
new file mode 100644 (file)
index 0000000..997a181
Binary files /dev/null and b/lib/wlrepo/tests/data/repos/testrepoII/.hg/store/00manifest.i differ
diff --git a/lib/wlrepo/tests/data/repos/testrepoII/.hg/store/data/.hgignore.i b/lib/wlrepo/tests/data/repos/testrepoII/.hg/store/data/.hgignore.i
new file mode 100644 (file)
index 0000000..2431023
Binary files /dev/null and b/lib/wlrepo/tests/data/repos/testrepoII/.hg/store/data/.hgignore.i differ
diff --git a/lib/wlrepo/tests/data/repos/testrepoII/.hg/store/data/ignored__file.i b/lib/wlrepo/tests/data/repos/testrepoII/.hg/store/data/ignored__file.i
new file mode 100644 (file)
index 0000000..a6bdf46
Binary files /dev/null and b/lib/wlrepo/tests/data/repos/testrepoII/.hg/store/data/ignored__file.i differ
diff --git a/lib/wlrepo/tests/data/repos/testrepoII/.hg/store/data/pub__polish__file.xml.i b/lib/wlrepo/tests/data/repos/testrepoII/.hg/store/data/pub__polish__file.xml.i
new file mode 100644 (file)
index 0000000..3488bcc
Binary files /dev/null and b/lib/wlrepo/tests/data/repos/testrepoII/.hg/store/data/pub__polish__file.xml.i differ
diff --git a/lib/wlrepo/tests/data/repos/testrepoII/.hg/store/data/pub__valid__file.xml.i b/lib/wlrepo/tests/data/repos/testrepoII/.hg/store/data/pub__valid__file.xml.i
new file mode 100644 (file)
index 0000000..fd3c7f7
Binary files /dev/null and b/lib/wlrepo/tests/data/repos/testrepoII/.hg/store/data/pub__valid__file.xml.i differ
diff --git a/lib/wlrepo/tests/data/repos/testrepoII/.hg/store/fncache b/lib/wlrepo/tests/data/repos/testrepoII/.hg/store/fncache
new file mode 100644 (file)
index 0000000..8730267
--- /dev/null
@@ -0,0 +1,4 @@
+data/.hgignore.i
+data/ignored_file.i
+data/pub_valid_file.xml.i
+data/pub_polish_file.xml.i
diff --git a/lib/wlrepo/tests/data/repos/testrepoII/.hg/store/undo b/lib/wlrepo/tests/data/repos/testrepoII/.hg/store/undo
new file mode 100644 (file)
index 0000000..fb22969
Binary files /dev/null and b/lib/wlrepo/tests/data/repos/testrepoII/.hg/store/undo differ
diff --git a/lib/wlrepo/tests/data/repos/testrepoII/.hg/undo.branch b/lib/wlrepo/tests/data/repos/testrepoII/.hg/undo.branch
new file mode 100644 (file)
index 0000000..620a92c
--- /dev/null
@@ -0,0 +1 @@
+personal_tester_file_valid_file
\ No newline at end of file
diff --git a/lib/wlrepo/tests/data/repos/testrepoII/.hg/undo.dirstate b/lib/wlrepo/tests/data/repos/testrepoII/.hg/undo.dirstate
new file mode 100644 (file)
index 0000000..2c1df6a
Binary files /dev/null and b/lib/wlrepo/tests/data/repos/testrepoII/.hg/undo.dirstate differ
diff --git a/lib/wlrepo/tests/data/repos/testrepoII/.hgignore b/lib/wlrepo/tests/data/repos/testrepoII/.hgignore
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/lib/wlrepo/tests/data/repos/testrepoII/ignored_file b/lib/wlrepo/tests/data/repos/testrepoII/ignored_file
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/lib/wlrepo/tests/data/repos/testrepoII/pub_polish_file.xml b/lib/wlrepo/tests/data/repos/testrepoII/pub_polish_file.xml
new file mode 100644 (file)
index 0000000..c7ada58
--- /dev/null
@@ -0,0 +1 @@
+Gąska!
diff --git a/lib/wlrepo/tests/data/repos/testrepoII/pub_valid_file.xml b/lib/wlrepo/tests/data/repos/testrepoII/pub_valid_file.xml
new file mode 100644 (file)
index 0000000..8432188
--- /dev/null
@@ -0,0 +1 @@
+Ala ma kota
diff --git a/lib/wlrepo/tests/test_mercurial.py b/lib/wlrepo/tests/test_mercurial.py
new file mode 100644 (file)
index 0000000..a4e1cfb
--- /dev/null
@@ -0,0 +1,99 @@
+# -*- encoding: utf-8 -*-
+
+__author__= "Łukasz Rekucki"
+__date__ = "$2009-09-18 14:43:27$"
+__doc__ = "Tests for RAL mercurial backend."
+
+from nose.tools import *
+
+import wlrepo
+from wlrepo import MercurialLibrary
+from wlrepo.backend_mercurial import *
+
+import os, os.path, tempfile
+import shutil
+
+REPO_TEMPLATES = os.path.join( os.path.dirname(__file__), 'data/repos')
+ROOT_PATH = None
+
+class testBasicLibrary(object):
+
+    def setUp(self):
+        self.path = tempfile.mkdtemp("", "testdir_" )
+        print self.path
+        for subdir in os.listdir(REPO_TEMPLATES):
+            shutil.copytree(REPO_TEMPLATES + '/' + subdir, self.path + '/' + subdir, False)
+
+    def tearDown(self):        
+        if self.path is not None:
+            shutil.rmtree(self.path, True)
+        pass
+   
+    def testOpening(self):
+        library = MercurialLibrary(self.path + '/cleanrepo')
+
+    def testMainCabinet(self):
+        library = MercurialLibrary(self.path + '/cleanrepo')
+
+        mcab = library.main_cabinet
+        assert_equal(mcab.maindoc_name(), '')
+
+        # @type mcab MercurialCabinet
+        doclist = mcab.documents()
+        assert_equal( list(doclist), ['valid_file'])
+
+
+    def testReadDocument(self):
+        library = MercurialLibrary(self.path + '/testrepoI')
+        doc = library.main_cabinet.retrieve('valid_file')
+        
+        assert_equal(doc.read().strip(), 'Ala ma kota')
+
+    def testReadUTF8Document(self):
+        library = MercurialLibrary(self.path + '/testrepoI')
+        doc = library.main_cabinet.retrieve('polish_file')
+
+        assert_equal(doc.read().strip(), u'Gąska!'.encode('utf-8'))
+
+    def testWriteDocument(self):
+        library = MercurialLibrary(self.path + '/testrepoI')
+        doc = library.main_cabinet.retrieve('valid_file')
+
+        assert_equal(doc.read().strip(), 'Ala ma kota')
+        
+        STRING = u'Gąski lubią pływać!\n'.encode('utf-8')
+        doc.write(STRING)       
+        
+        assert_equal(doc.read(), STRING)
+
+    def testCreateDocument(self):
+        repopath = os.path.join(self.path, 'testrepoI')
+
+        library = MercurialLibrary(repopath)
+        doc = library.main_cabinet.create("another_file")
+        doc.write("Some text")
+        assert_equal( doc.read(), "Some text")
+        assert_true( os.path.isfile( os.path.join(repopath, "pub_another_file.xml")) )
+        
+    def testSwitchBranch(self):
+        library = MercurialLibrary(self.path + '/testrepoII')
+
+        tester_cab = library.cabinet("valid_file", "tester", create=False)
+        assert_equal( list(tester_cab.documents()), ['valid_file'])
+
+    @raises(wlrepo.CabinetNotFound)
+    def testNoBranch(self):
+        library = MercurialLibrary(self.path + '/testrepoII')
+        tester_cab = library.cabinet("ugh", "tester", create=False)
+
+
+    def testCreateBranch(self):
+        repopath = os.path.join(self.path, 'testrepoII')
+        library = MercurialLibrary(repopath)
+
+        tester_cab = library.cabinet("anotherone", "tester", create=True)       
+        assert_equal( list(tester_cab.documents()), ['anotherone'])
+
+        
+
+        
\ No newline at end of file