kariminf/aruudy

View on GitHub
aruudy/poetry/meter.py

Summary

Maintainability
F
5 days
Test Coverage
#!/usr/bin/env python
# -*- coding: utf-8 -*-

#  Copyright 2019 Abdelkrime Aries <kariminfo0@gmail.com>
#
#  ---- AUTHORS ----
# 2019    Abdelkrime Aries <kariminfo0@gmail.com>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

import re
from aruudy.poetry import foot
from aruudy.poetry.foot import TafiilaType as FT
from aruudy.poetry.foot import TafiilaComp

re_haraka = re.compile(u"[\u064E\u064F\u0650\u0653]")

def get_ameter (text):
    """Get the Arabic meter of a given text.
    Produces the Arabic meter of a given text in prosody form.
    The Arabic meter is composed of two letters:

    - "w" watad (peg) which are vocalized letters
    - "s" sabab (cord) which are vowels and unvocalized letters

    Parameters
    ----------
    text : str
        Arabic text in prosody form.

    Returns
    -------
    str
        Arabic meter of the input text.
        A string composed of "w" and "s".

    """
    ameter = ""
    parts = []
    buf = ""
    for c in text:
        buf += c
        if re_haraka.search(c):
            if buf[: -2].strip():
                ameter += "s" #sabab
                parts.append(buf[: -2])
                buf = buf[-2:]
            ameter += "w" #watad
            parts.append(buf)
            buf = ""
    if buf.strip():
        ameter += "s"
        parts.append(buf)

    return ameter, parts

def a2e_meter (ameter):
    """Transforms an Arabic meter to an English one.
    The Arabic meter uses vocalization as a basis:

    - "w" watad (peg) which are vocalized letters
    - "s" sabab (cord) which are vowels and unvocalized letters

    While English meter uses syllables:

    - "-" for long syllables, equivalent to "ws" in the Arabic one
    - "u" for short syllables, equivalent to "w" in the Arabic one.

    Parameters
    ----------
    ameter : str
        The Arabic meter using the two letters: "w" and "s".

    Returns
    -------
    str
        The English meter using the two characters: "-" and "u".

    """
    res = ameter
    res = res.replace("ws", "-")
    res = res.replace("w", "u")
    return res

def e2a_meter (emeter):
    """Transforms an English meter to an Arabic one.
    The English meter uses syllables as a basis:

    - "-" for long syllables, equivalent to "ws" in the Arabic one
    - "u" for short syllables, equivalent to "w" in the Arabic one.

    While the Arabic meter uses vocalization:

    - "w" watad (peg) which are vocalized letters
    - "s" sabab (cord) which are vowels and unvocalized letters

    Parameters
    ----------
    emeter : str
        The English meter using the two characters: "-" and "u".

    Returns
    -------
    str
        The Arabic meter using the two letters: "w" and "s".

    """
    res = emeter
    res = res.replace("-", "ws")
    res = res.replace("u", "w")
    return res


buhuur = []


class Part(TafiilaComp):
    """The text's part description.

    Parameters
    ----------
    tafiila_comp : TafiilaComp
        The description of the Foot which this part is based on.

    Attributes
    ----------
    ameter : str
        The Arabic meter.
    emeter : type
        The english meter.
    text : type
        The part of text following that meter.

    """
    def __init__(self, tafiila_comp):
        TafiilaComp.__init__(self, tafiila_comp.__dict__)
        self.ameter = e2a_meter(self.emeter)
        self.text = ""

    def extract(self, units=[]):
        """Extracts the part of text following the meter.

        Parameters
        ----------
        units : list(str)
            A list of vocallized and unvocalized elements,
            generated from the text.

        Returns
        -------
        str
            the text following the meter.

        """
        l = len(self.emeter)
        if not units or len(units) < l:
            return None
        self.text = "".join(units[:l])
        return units[l:]

    def to_dict(self):
        """Transforms this object to a dictionary.

        Parameters
        ----------


        Returns
        -------
        dict
            The dictionary will contin:

            - type (TafiilaComp): the type of the foot
            - emeter (str): the English meter
            - ameter (str): the Arabic meter
            - mnemonic (str): the mnemonic describing the meter
            - text (str): the text following the meter

        """
        return {
            "type": self.type,
            "emeter": self.emeter,
            "ameter": self.ameter,
            "mnemonic": self.mnemonic,
            "text": self.text
        }

class BahrForm(object):
    """The form of a Bahr (meter).

    For a given Arabic meter (Bahr), there may be multiple forms.

    Parameters
    ----------
    feet : list(Tafiila)
        A list of feet describing the meter.

    Attributes
    ----------
    feet: list(Tafiila)
        A list of feet describing the meter.

    """

    def __init__(self, feet):
        self.feet = feet

    def validate(self, emeter, units=[]):
        """Chacks if an emeter follows this meter's form.

        Parameters
        ----------
        emeter : str
            The English meter of the text.
        units : list(str)
            A list of vocallized and unvocalized elements,
            generated from the text.

        Returns
        -------
        Part
            The part object.

        """
        parts = []
        text_emeter = emeter
        units_cp = list(units)
        for foot in self.feet: # different feet of the variant
            text_foot, text_emeter = foot.process(text_emeter)
            if not text_foot:
                return None
            part = Part(text_foot)
            units_cp = part.extract(units_cp)
            parts.append(part)
        return parts

def extract_meter(bahrForm, used=True):
    """Extract the meter description from a list of :class:`~aruudy.poetry.foot.Tafiila` objects.

    Parameters
    ----------
    bahrForm : BahrForm
        An object describing the meter's form.
    used : bool
        Meters, in Arabic, can have used forms different than standard ones.
        if True: the result is used form.
        Otherwise, it is standard form

    Returns
    -------
    dict
        A dictionary object describing the meter represented by the feet.
        The dictionary contains these elements:

        - type: a string describing the type of each foot (tafiila)
        - mnemonic: a string describing the mnemonic of each foot.
        - emeter: a string describing the English meter of each foot.
        - ameter: a string describing the Arabic meter of each foot.

    """
    res = {
        "type": "",
        "mnemonic": "",
        "emeter": "",
        "ameter": ""
    }

    sep = ""
    for foot in bahrForm.feet:
        meter = foot.get_form(used)
        res["type"] += sep + meter.type.ar
        res["mnemonic"] += sep + meter.mnemonic
        res["emeter"] += sep + meter.emeter
        res["ameter"] += sep + e2a_meter(meter.emeter)
        if not sep:
            sep = " "
    return res

class Bahr(object):
    """Representation of the Arabic meter.

    Parameters
    ----------
    info : dict
        Description of parameter `info`.

    Attributes
    ----------
    name : dict
        Bahr's name, which is composed of:

        - arabic: its name in Arabic
        - english: its name in English
        - trans: its Arabic name's transliteration.
    used_scansion : dict
        The most used scansion.
        The dictionary contains these elements:

        - type: a string describing the type of each foot (tafiila)
        - mnemonic: a string describing the mnemonic of each foot.
        - emeter: a string describing the English meter of each foot.
        - ameter: a string describing the Arabic meter of each foot.

    meter : list(BahrForm)
        A list of meter's forms.
    std_scansion : dict
        the standard scansion.
        The dictionary contains these elements:

        - type: a string describing the type of each foot (tafiila)
        - mnemonic: a string describing the mnemonic of each foot.
        - emeter: a string describing the English meter of each foot.
        - ameter: a string describing the Arabic meter of each foot.

    """
    def __init__(self, info):
        buhuur.append(self)
        self.name = info["name"]
        self.meter = info["meter"]
        self.key = info["key"]

        self.used_scansion = extract_meter(self.meter[0])
        self.std_scansion = extract_meter(self.meter[0], used=False)

    def __eq__(self, other):
        return self.__dict__ == other.__dict__

    def __str__(self):
        return str(self.get_names())

    def get_names(self):
        """Get the names of the meter.

        Parameters
        ----------


        Returns
        -------
        dict
            Bahr's name, which is composed of:

            - arabic: its name in Arabic
            - english: its name in English
            - trans: its Arabic name's transliteration.

        """
        return self.name

    def test_name(self, key, value):
        """Test if .

        Parameters
        ----------
        key : str
            can be "arabic", "english" or "trans".
        value : str
            The name we are looking for.

        Returns
        -------
        bool
            True, if this meter have the name specified by "value"

        """
        return value == self.name[key]

    def to_dict(self):
        """Transform the bahr to a dictionary.

        Parameters
        ----------


        Returns
        -------
        dict
            The dictionary has three components "name", "used_scansion" and
            "std_scansion" which are dictionaries too.
            They are described in the attributes section.

        """
        dic = {
            "name": self.name,
            "used_scansion": self.used_scansion,
            "std_scansion": self.std_scansion
        }

        return dic

    def validate(self, emeter, units=[]):
        """Validate a given emeter into one of the forms.

        Search for a form which the given emeter follows.

        Parameters
        ----------
        emeter : str
            English meter.
        units : list(str)
            A list of vocallized and unvocalized elements,
            generated from the text.

        Returns
        -------
        Part
            the part object.

        """
        for form in self.meter: # different forms
            parts = form.validate(emeter, units)
            if parts:
                return parts
        return None

tawiil = Bahr({
    "name": {
        "arabic": u"طويل",
        "english": "long",
        "trans": u"ṭawīl"
    },
    "meter": [
        BahrForm([
        foot.WWSWS([FT.SALIM, FT.QABDH]),
        foot.WWSWSWS([FT.SALIM, FT.QABDH, FT.KAFF]),
        foot.WWSWS([FT.SALIM, FT.QABDH]),
        foot.WWSWSWS([FT.QABDH]),
        ])
    ],
    "key": u"طويلٌ له دون البحور فضائلٌ  فعولن مفاعيلن فعولن مفاعلن"
})

madiid = Bahr({
    "name": {
        "arabic": u"مديد",
        "english": "protracted",
        "trans": u"madīd"
    },
    "meter": [
        BahrForm([
        foot.WSWWSWS([FT.SALIM, FT.KHABN]),
        foot.WSWWS([FT.SALIM, FT.KHABN]),
        foot.WSWWSWS([FT.SALIM, FT.KHABN])
        ])
    ],
    "key": u"لمديد الشعر عندي صفاتُ  فاعلاتن فاعلن فاعلاتن"
})

basiit = Bahr({
    "name": {
        "arabic": u"بسيط",
        "english": "spread-out",
        "trans": u"basīṭ"
    },
    "meter": [
        BahrForm([
        foot.WSWSWWS([FT.SALIM, FT.KHABN, FT.TAI]),
        foot.WSWWS([FT.SALIM, FT.KHABN]),
        foot.WSWSWWS([FT.SALIM, FT.KHABN, FT.TAI]),
        foot.WSWWS([FT.KHABN, FT.QATE]),
        ]),
        BahrForm([
        foot.WSWSWWS([FT.SALIM, FT.KHABN, FT.TAI]),
        foot.WSWWS([FT.SALIM, FT.KHABN]),
        foot.WSWSWWS([FT.SALIM, FT.KHABN, FT.TAI, FT.QATE, FT.TADIIL]),
        ])
    ],
    "key": u"إن البسيط لديه يبسط الأملُ  مستفعلن فعلن مستفعلن فعلن"
})

wafir = Bahr({
    "name": {
        "arabic": u"وافر",
        "english": "abundant",
        "trans": u"wāfir"
    },
    "meter": [
        BahrForm([
        foot.WWSWWWS([FT.SALIM, FT.ASAB]),
        foot.WWSWWWS([FT.SALIM, FT.ASAB]),
        foot.WWSWS([FT.SALIM]),
        ])
    ],
    "key": u"بحور الشعر وافرها جميل  مفاعلتن مفاعلتن فعولن"
})

kaamil = Bahr({
    "name": {
        "arabic": u"كامل",
        "english": "complete",
        "trans": u"kāmil"
    },
    "meter": [
        BahrForm([
        foot.WWWSWWS([FT.SALIM, FT.IDHMAR]),
        foot.WWWSWWS([FT.SALIM, FT.IDHMAR]),
        foot.WWWSWWS([FT.SALIM, FT.IDHMAR])
        ]),
        BahrForm([
        foot.WWWSWWS([FT.SALIM, FT.IDHMAR]),
        foot.WWWSWWS([FT.SALIM, FT.IDHMAR])
        ])
    ],
    "key": u"كمل الجمال من البحور الكامل متفاعلن متفاعلن متفاعلن"
})

hazj = Bahr({
    "name": {
        "arabic": u"هزج",
        "english": "trilling",
        "trans": u"hazaj",
    },
    "meter": [
        BahrForm([
        foot.WWSWSWS([FT.SALIM, FT.KAFF]),
        foot.WWSWSWS([FT.SALIM, FT.KAFF])
        ])
    ],
    "key": u"على الأهزاج تسهيل      مفاعيلن مفاعيلن"
})

rajz = Bahr({
    "name": {
        "arabic": u"رجز",
        "english": "trembling",
        "trans": u"rajaz"
    },
    "meter": [
        BahrForm([
        foot.WSWSWWS([FT.SALIM, FT.KHABN]),
        foot.WSWSWWS([FT.SALIM, FT.KHABN]),
        foot.WSWSWWS([FT.SALIM, FT.KHABN])
        ])
    ],
    "key": u"في أبحر الأرجاز بحرٌ يسهل   مستفعلن مستفعلن مستفعلن"
})

raml = Bahr({
    "name": {
        "arabic": u"رمل",
        "english": "trotting",
        "trans": u"ramal",
    },
    "meter": [
        BahrForm([
        foot.WSWWSWS([FT.SALIM, FT.KHABN]),
        foot.WSWWSWS([FT.SALIM, FT.KHABN]),
        foot.WSWWSWS([FT.SALIM, FT.KHABN])
        ])
    ],
    "key": u"رمل الأبحر ترويه الثقات فاعلاتن فاعلاتن فاعلاتن"
})

sariie = Bahr({
    "name": {
        "arabic": u"سريع",
        "english": "swift",
        "trans": u"sarīʿ",
    },
    "meter": [
        BahrForm([
        foot.WSWSWWS([FT.SALIM, FT.KHABN, FT.TAI, FT.KHABL]),
        foot.WSWSWWS([FT.SALIM, FT.KHABN, FT.TAI, FT.KHABL]),
        foot.WSWWS([FT.SALIM])
        ])
    ],
    "key": u"بحرٌ سريع ماله ساحل مستفعلن مستفعلن فاعلن"
})

munsarih = Bahr({
    "name": {
        "arabic": u"منسرح",
        "english": "quick-paced",
        "trans": u"munsariħ"
    },
    "meter": [
        BahrForm([
        foot.WSWSWWS([FT.SALIM, FT.KHABN]),
        foot.WSWSWSW([FT.SALIM, FT.TAI]),
        foot.WSWSWWS([FT.TAI])
        ])
    ],
    "key": u"منسرح فيه يضرب المثل    مستفعلن مفعولات مفتعلن"
})

khafiif = Bahr({
    "name": {
        "arabic": u"خفيف",
        "english": "light",
        "trans": u"khafīf"
    },
    "meter": [
        BahrForm([
        foot.WSWWSWS([FT.SALIM, FT.KHABN, FT.KAFF]),
        foot.WSWSWWS([FT.SALIM]),
        foot.WSWWSWS([FT.SALIM, FT.KHABN, FT.SHAKL])
        ])
    ],
    "key": u"يا خفيفاً خفّت به الحركات   فاعلاتن مستفعلن فاعلاتن"
})

mudharie = Bahr({
    "name": {
        "arabic": u"مضارع",
        "english": "similar",
        "trans": u"muḍāriʿ"
    },
    "meter": [
        BahrForm([
        foot.WWSWSWS([FT.SALIM, FT.QABDH,FT.KAFF]),
        foot.WSWWSWS([FT.SALIM])
        ])
    ],
    "key": u"تعدّ المضارعات  مفاعيلُ فاعلاتن"
})

muqtadhib = Bahr({
    "name": {
        "arabic": u"مقتضب",
        "english": "untrained",
        "trans": u"muqtaḍab"
    },
    "meter": [
        BahrForm([
        foot.WSWSWSW([FT.SALIM]),# FT.KHABN
        foot.WSWSWWS([FT.TAI])
        ])
    ],
    "key": u"اقتضب كما سألوا مفعلات مفتعلن"
})

mujdath = Bahr({
    "name": {
        "arabic": u"مجتث",
        "english": "cut-off",
        "trans": u"mujtathth"
    },
    "meter": [
        BahrForm([
        foot.WSWSWWS([FT.SALIM, FT.KHABN]),
        foot.WSWWSWS([FT.SALIM, FT.KHABN])
        ])
    ],
    "key": u"أن جثت الحركات  مستفعلن فاعلاتن"
})

mutaqaarib = Bahr({
    "name": {
        "arabic": u"متقارب",
        "english": "nearing",
        "trans": u"mutaqārib"
    },
    "meter": [
        BahrForm([
        foot.WWSWS([FT.SALIM, FT.QABDH]),
        foot.WWSWS([FT.SALIM, FT.QABDH]),
        foot.WWSWS([FT.SALIM, FT.QABDH]),
        foot.WWSWS([FT.SALIM, FT.QABDH, FT.QASR])
        ])
    ],
    "key": u"عن المتقارب قال الخليل      فعولن فعولن فعولن فعول"
})


mutadaarik = Bahr({
    "name": {
        "arabic": u"متدارك",
        "english": "overtaking",
        "trans": u"mutadārik"
    },
    "meter": [
        BahrForm([
        foot.WSWWS([FT.SALIM, FT.KHABN, FT.QATE]),
        foot.WSWWS([FT.SALIM, FT.KHABN, FT.QATE]),
        foot.WSWWS([FT.SALIM, FT.KHABN, FT.QATE]),
        foot.WSWWS([FT.SALIM, FT.KHABN, FT.QATE])
        ])
    ],
    "key": u"حركات المحدث تنتقل  فعلن فعلن فعلن فعل"
})


def name_type(name):
    """decides if a name is in English or Arabic.

    Parameters
    ----------
    name : str
        The name we want to test.

    Returns
    -------
    str
        "english" or "arabic".

    """
    if re.match("^[a-zA-Z]", name):
        return "english"
    return "arabic"

def get_bahr(name, dic=True):
    """Search for poetry Bahr by name.

    Parameters
    ----------
    name : str
        name of the poetry Bahr (meter).
    dic : bool
        True(default): it returns a dict object with all information.
        If False, it returns an object of type Bahr

    Returns
    -------
    dict
        dict: containing the information.
        or a Bahr object.
        or None

    """
    label = name_type(name)
    for b in buhuur:
        if b.test_name(label, name):
            if dic:
                return b.to_dict()
            return b
    return None


def get_names(lang=None):
    """get a list of meters names.

    Parameters
    ----------
    lang : str
        If not specified: the result will be all available names.


    Returns
    -------
    list(Union[str, dict])
        A list of names.

    """
    names = []
    for bahr in buhuur:
        if lang:
            names.append(bahr.name[lang])
        else:
            names.append(bahr.name)
    return names

def search_bahr(emeter, units=[]):
    """Search for Bahr of a given English meter.

    Parameters
    ----------
    emeter : str
        English meter.
    units : list(str)
        A list of vocallized and unvocalized elements,
        generated from the text.

    Returns
    -------
    tuple(Bahr, Part)
        A tuple of the found meter and the part's description.
        If not found, it will return (None, None)

    """
    for b in buhuur:
        res = b.validate(emeter, units)
        if res:
            return b, res

    return None, None