Skip to content
This repository was archived by the owner on Oct 24, 2025. It is now read-only.

Commit efc38d9

Browse files
committed
Initial commit
1 parent b3e8d77 commit efc38d9

File tree

5 files changed

+298
-0
lines changed

5 files changed

+298
-0
lines changed

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,9 @@ build/*
99
a.out
1010
bin/*
1111
*.gem
12+
13+
dist/
14+
.*.swp
15+
*.egg
16+
*.egg-info
17+
*.so

MANIFEST.in

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
include *.h
2+
include *.hpp
3+
include *.cpp

sass.c

Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
#include <Python.h>
2+
#include "sass_interface.h"
3+
4+
typedef struct {
5+
PyObject_HEAD
6+
struct sass_options options;
7+
} sass_OptionsObject;
8+
9+
static int
10+
sass_Options_init(sass_OptionsObject *self, PyObject *args, PyObject *kwds) {
11+
static char *sig[] = {"include_paths", "image_path", NULL};
12+
PyObject *include_paths, *image_path, *item;
13+
char *include_paths_cstr, *item_buffer, *image_path_cstr;
14+
size_t include_paths_len, include_paths_size, i, offset, item_size,
15+
image_path_size;
16+
if (!PyArg_ParseTupleAndKeywords(args, kwds, "OS", sig,
17+
&include_paths, &image_path)) {
18+
return -1;
19+
}
20+
21+
self->options.output_style = SASS_STYLE_NESTED;
22+
23+
if (include_paths == Py_None) {
24+
PyErr_SetString(PyExc_TypeError, "include_paths must not be None");
25+
return -1;
26+
}
27+
if (PyString_Check(include_paths)) {
28+
if (PyString_AsStringAndSize(include_paths,
29+
&include_paths_cstr, &include_paths_size)
30+
!= 0) {
31+
return -1;
32+
}
33+
self->options.include_paths = malloc(sizeof(char) *
34+
(include_paths_size + 1));
35+
strncpy(self->options.include_paths,
36+
include_paths_cstr,
37+
include_paths_size + 1);
38+
self->options.include_paths[include_paths_size] = '\0';
39+
}
40+
else if (PySequence_Check(include_paths)) {
41+
include_paths_len = PySequence_Size(include_paths);
42+
if (include_paths_len < 0) {
43+
return -1;
44+
}
45+
include_paths_size = 0;
46+
for (i = 0; i < include_paths_len; ++i) {
47+
item = PySequence_GetItem(include_paths, i);
48+
if (item == NULL) {
49+
return -1;
50+
}
51+
if (!PyString_Check(item)) {
52+
PyErr_Format(PyExc_TypeError,
53+
"include_paths must consists of only strings, "
54+
"but #%d is not a string", i);
55+
return -1;
56+
}
57+
include_paths_size += PyString_Size(item);
58+
}
59+
// add glue chars
60+
if (include_paths_len > 1) {
61+
include_paths_size += include_paths_len - 1;
62+
}
63+
self->options.include_paths = malloc(sizeof(char) *
64+
(include_paths_size + 1));
65+
// join
66+
offset = 0;
67+
for (i = 0; i < include_paths_len; ++i) {
68+
if (i) {
69+
self->options.include_paths[offset] = ':';
70+
++offset;
71+
}
72+
item = PySequence_GetItem(include_paths, i);
73+
PyString_AsStringAndSize(item, &item_buffer, &item_size);
74+
strncpy(self->options.include_paths + offset,
75+
item_buffer, item_size);
76+
offset += item_size;
77+
}
78+
self->options.include_paths[include_paths_size] = '\0';
79+
}
80+
else {
81+
PyErr_SetString(PyExc_TypeError,
82+
"include_paths must be a string or a sequence of "
83+
"strings");
84+
return -1;
85+
}
86+
87+
if (PyString_AsStringAndSize(image_path,
88+
&image_path_cstr, &image_path_size) != 0) {
89+
return -1;
90+
}
91+
self->options.image_path = malloc(sizeof(char) * (image_path_size + 1));
92+
strncpy(self->options.image_path, image_path_cstr, image_path_size + 1);
93+
self->options.image_path[image_path_size] = '\0';
94+
95+
return 0;
96+
}
97+
98+
static void
99+
sass_Options_dealloc(sass_OptionsObject *self) {
100+
free(self->options.include_paths);
101+
free(self->options.image_path);
102+
self->ob_type->tp_free((PyObject *) self);
103+
}
104+
105+
static PyObject *
106+
sass_Options_get_include_paths(sass_OptionsObject *self, void *closure)
107+
{
108+
size_t i, j;
109+
char *paths;
110+
PyObject *list;
111+
int tmp;
112+
113+
paths = self->options.include_paths;
114+
list = PyList_New(0);
115+
if (list == NULL) {
116+
return NULL;
117+
}
118+
Py_INCREF(list);
119+
120+
i = j = 0;
121+
do {
122+
if (i > j && (paths[i] == ':' || paths[i] == '\0')) {
123+
tmp = PyList_Append(list,
124+
PyString_FromStringAndSize(paths + j, i - j));
125+
if (tmp != 0) {
126+
return NULL;
127+
}
128+
j = i + 1;
129+
}
130+
}
131+
while (paths[i++]);
132+
133+
return list;
134+
}
135+
136+
static PyObject *
137+
sass_Options_get_image_path(sass_OptionsObject *self, void *closure)
138+
{
139+
PyObject *string;
140+
string = PyString_FromString(self->options.image_path);
141+
Py_INCREF(string);
142+
return string;
143+
}
144+
145+
static PyGetSetDef sass_Options_getset[] = {
146+
{"include_paths", (getter) sass_Options_get_include_paths, NULL,
147+
"The list of paths to include."},
148+
{"image_path", (getter) sass_Options_get_image_path, NULL,
149+
"The path to find images."},
150+
{NULL}
151+
};
152+
153+
static PyTypeObject sass_OptionsType = {
154+
PyObject_HEAD_INIT(NULL)
155+
.ob_size = 0,
156+
.tp_name = "sass.Options",
157+
.tp_basicsize = sizeof(sass_OptionsObject),
158+
.tp_itemsize = 0,
159+
.tp_dealloc = (destructor) sass_Options_dealloc,
160+
.tp_print = 0,
161+
.tp_getattr = 0,
162+
.tp_setattr = 0,
163+
.tp_compare = 0,
164+
.tp_repr = 0,
165+
.tp_as_number = 0,
166+
.tp_as_sequence = 0,
167+
.tp_as_mapping = 0,
168+
.tp_hash = 0,
169+
.tp_call = 0,
170+
.tp_str = 0,
171+
.tp_getattro = 0,
172+
.tp_setattro = 0,
173+
.tp_as_buffer = 0,
174+
.tp_flags = Py_TPFLAGS_DEFAULT,
175+
.tp_doc = "The object which contains compilation options.",
176+
.tp_traverse = 0,
177+
.tp_clear = 0,
178+
.tp_richcompare = 0,
179+
.tp_weaklistoffset = 0,
180+
.tp_iter = 0,
181+
.tp_iternext = 0,
182+
.tp_methods = 0,
183+
.tp_members = 0,
184+
.tp_getset = sass_Options_getset,
185+
.tp_base = 0,
186+
.tp_dict = 0,
187+
.tp_descr_get = 0,
188+
.tp_descr_set = 0,
189+
.tp_dictoffset = 0,
190+
.tp_init = (initproc) sass_Options_init,
191+
.tp_alloc = 0,
192+
.tp_new = 0
193+
};
194+
195+
static PyMethodDef sass_methods[] = {
196+
{NULL}
197+
};
198+
199+
PyMODINIT_FUNC
200+
initsass()
201+
{
202+
PyObject *module;
203+
204+
sass_OptionsType.tp_new = PyType_GenericNew;
205+
if (PyType_Ready(&sass_OptionsType) < 0) {
206+
return;
207+
}
208+
209+
module = Py_InitModule3("sass", sass_methods,
210+
"The thin binding of libsass for Python.");
211+
if (module == NULL) {
212+
return;
213+
}
214+
Py_INCREF(&sass_OptionsType);
215+
PyModule_AddObject(module, "Options", (PyObject *) &sass_OptionsType);
216+
}

sasstests.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
from attest import Tests, assert_hook, raises
2+
3+
from sass import Options
4+
5+
6+
suite = Tests()
7+
8+
9+
@suite.test
10+
def options_include_paths():
11+
o = Options(include_paths='ab/cd:de/fg', image_path='')
12+
assert o.include_paths == ['ab/cd', 'de/fg']
13+
o = Options(include_paths=['li/st', 'te/st'], image_path='')
14+
assert o.include_paths == ['li/st', 'te/st']
15+
o = Options(include_paths=('tup/le', 'te/st'), image_path='')
16+
assert o.include_paths == ['tup/le', 'te/st']
17+
with raises(TypeError):
18+
Options(include_paths=None, image_path='a/b')
19+
with raises(TypeError):
20+
Options(include_paths=123, image_path='a/b')
21+
22+
23+
@suite.test
24+
def options_image_path():
25+
o = Options(include_paths='a:b', image_path='image/path')
26+
assert o.image_path == 'image/path'
27+
with raises(TypeError):
28+
Options(include_paths='a:b', image_path=None)
29+
with raises(TypeError):
30+
Options(include_paths='a:b', image_path=123)
31+
with raises(TypeError):
32+
Options(include_paths='a:b', image_path=['a/b', 'c/d'])

setup.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
try:
2+
from setuptools import Extension, setup
3+
except ImportError:
4+
from distutils.core import Extension, setup
5+
6+
7+
libsass_sources = [
8+
'context.cpp', 'functions.cpp', 'document.cpp',
9+
'document_parser.cpp', 'eval_apply.cpp', 'node.cpp',
10+
'node_factory.cpp', 'node_emitters.cpp', 'prelexer.cpp',
11+
'sass_interface.cpp',
12+
]
13+
14+
libsass_headers = [
15+
'color_names.hpp', 'error.hpp', 'node.hpp',
16+
'context.hpp', 'eval_apply.hpp', 'node_factory.hpp',
17+
'document.hpp', 'functions.hpp', 'prelexer.hpp',
18+
'sass_interface.h'
19+
]
20+
21+
sass_extension = Extension(
22+
'sass',
23+
['sass.c'] + libsass_sources,
24+
depends=libsass_headers,
25+
extra_compile_args=['-c', '-Wall', '-O2', '-fPIC'],
26+
extra_link_args=['-fPIC'],
27+
)
28+
29+
setup(
30+
name='libsass',
31+
version='0.1.0',
32+
ext_modules=[sass_extension],
33+
py_modules=['sasstests'],
34+
license='MIT License',
35+
author='Hong Minhee',
36+
author_email='minhee' '@' 'dahlia.kr',
37+
url='https://github.com/dahlia/libsass-python',
38+
tests_require=['Attest'],
39+
test_loader='attest:auto_reporter.test_loader',
40+
test_suite='sasstests.suite'
41+
)

0 commit comments

Comments
 (0)