Skip to content

Commit c0598aa

Browse files
authored
added basic Datapackage, Resource and DataStream objects (+travis, coveralls, phpunit etc..) (#3)
1 parent 3bfa2ad commit c0598aa

18 files changed

Lines changed: 630 additions & 2 deletions

.coveralls.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
coverage_clover: coverage-clover.xml
2+
json_path: coveralls.json

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
/vendor/
2+
/.idea/
3+
/coverage-clover.xml
4+
/composer.lock

.travis.yml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
language: php
2+
php:
3+
- '5.4'
4+
- '5.5'
5+
- '5.6'
6+
- '7.0'
7+
- '7.1'
8+
- nightly
9+
- hhvm
10+
before_script:
11+
- composer install --prefer-dist
12+
script:
13+
- composer test
14+
after_success:
15+
- vendor/bin/coveralls

CONTRIBUTING.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Contributing
2+
3+
The project follows the [Open Knowledge International coding standards](https://github.com/okfn/coding-standards).
4+
5+
6+
## Getting Started
7+
8+
1. Clone the repo
9+
2. Run the tests
10+
```
11+
$ composer install
12+
$ composer test
13+
```
14+
15+
## Phpunit - for unit tests
16+
17+
Phpunit is used for unit tests, you can find the tests under tests directory
18+
19+
Running Phpunit directly: `vendor/bin/phunit`
20+
21+
## Coveralls - for coverage
22+
23+
when running `composer test` phpunit generates coverage report in coverage-clover.xml - this is then sent to Coveralls via Travis.

README.md

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,40 @@
22

33
[![Travis](https://travis-ci.org/frictionlessdata/datapackage-php.svg?branch=master)](https://travis-ci.org/frictionlessdata/datapackage-php)
44
[![Coveralls](http://img.shields.io/coveralls/frictionlessdata/datapackage-php.svg?branch=master)](https://coveralls.io/r/frictionlessdata/datapackage-php?branch=master)
5-
[![Packagist](https://img.shields.io/packagist/dm/oki/datapackage.svg)](https://packagist.org/packages/oki/datapackage)
5+
[![Packagist](https://img.shields.io/packagist/dm/frictionlessdata/datapackage.svg)](https://packagist.org/packages/frictionlessdata/datapackage)
66
[![SemVer](https://img.shields.io/badge/versions-SemVer-brightgreen.svg)](http://semver.org/)
77
[![Gitter](https://img.shields.io/gitter/room/frictionlessdata/chat.svg)](https://gitter.im/frictionlessdata/chat)
88

9-
A utility library for working with [Data Package](https://specs.frictionlessdata.io/data-package/) in php.
9+
A utility library for working with [Data Package](https://specs.frictionlessdata.io/data-package/) in PHP.
10+
11+
12+
## Getting Started
13+
14+
### Installation
15+
16+
```bash
17+
$ composer require frictionlessdata/datapackage
18+
```
19+
20+
### Usage
21+
22+
```php
23+
use frictionlessdata\datapackage;
24+
25+
$datapackage = new Datapackage("tests/fixtures/multi_data_datapackage.json");
26+
foreach ($datapackage as $resource) {
27+
print("-- ".$resource->name()." --");
28+
$i = 0;
29+
foreach ($resource as $dataStream) {
30+
print("-dataStream ".++$i);
31+
foreach ($dataStream as $line) {
32+
print($line);
33+
}
34+
}
35+
}
36+
```
37+
38+
39+
## Contributing
40+
41+
Please read the contribution guidelines: [How to Contribute](CONTRIBUTING.md)

composer.json

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"name": "frictionlessdata/datapackage",
3+
"description": "A utility library for working with Data Packages",
4+
"license": "MIT",
5+
"require": {
6+
"php": ">=5.4"
7+
},
8+
"require-dev": {
9+
"phpunit/phpunit": "^4.8.35",
10+
"satooshi/php-coveralls": "^1.0"
11+
},
12+
"autoload": {
13+
"psr-4": {
14+
"frictionlessdata\\datapackage\\": "src/"
15+
}
16+
},
17+
"scripts": {
18+
"test": "phpunit --debug tests/ --coverage-clover coverage-clover.xml"
19+
}
20+
}

src/DataStream.php

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<?php namespace frictionlessdata\datapackage;
2+
3+
4+
class DataStream implements \Iterator
5+
{
6+
protected $_currentLineNumber = 0;
7+
protected $_fopenResource;
8+
protected $_dataSource;
9+
10+
public function __construct($dataSource)
11+
{
12+
try {
13+
$this->_fopenResource = fopen($dataSource, "r");
14+
} catch (\Exception $e) {
15+
throw new DataStreamOpenException("Failed to open source ".json_encode($dataSource));
16+
}
17+
}
18+
19+
public function __destruct()
20+
{
21+
fclose($this->_fopenResource);
22+
}
23+
24+
public function rewind() {
25+
if ($this->_currentLineNumber != 0) {
26+
throw new \Exception("DataStream does not support rewind, sorry");
27+
}
28+
}
29+
30+
public function current() {
31+
$line = fgets($this->_fopenResource);
32+
if ($line === false) {
33+
return "";
34+
} else {
35+
return $line;
36+
}
37+
}
38+
39+
public function key() {
40+
return $this->_currentLineNumber;
41+
}
42+
43+
public function next() {
44+
$this->_currentLineNumber++;
45+
}
46+
47+
public function valid() {
48+
return (!feof($this->_fopenResource));
49+
}
50+
}
51+
52+
53+
class DataStreamOpenException extends \Exception {};

src/Datapackage.php

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
<?php namespace frictionlessdata\datapackage;
2+
3+
4+
/**
5+
* Datapackage representation, supports loading from the following sources:
6+
* - native PHP object containing the descriptor
7+
* - JSON encoded object
8+
* - URL (must be in either 'http' or 'https' schemes)
9+
* - local filesystem (POSIX) path
10+
*/
11+
class Datapackage implements \Iterator
12+
{
13+
protected $_descriptor;
14+
protected $_currentResourcePosition = 0;
15+
protected $_basePath;
16+
17+
public function __construct($source, $basePath=null)
18+
{
19+
if (is_object($source)) {
20+
$this->_descriptor = $source;
21+
$this->_basePath = $basePath;
22+
} elseif (is_string($source)) {
23+
if (Utils::is_json_string($source)) {
24+
try {
25+
$this->_descriptor = json_decode($source);
26+
} catch (\Exception $e) {
27+
throw new DatapackageInvalidSourceException("Failed to load source: ".json_encode($source).": ".$e->getMessage());
28+
}
29+
$this->_basePath = $basePath;
30+
} elseif ($this->_isHttpSource($source)) {
31+
try {
32+
$this->_descriptor = json_decode(file_get_contents($this->_normalizeHttpSource($source)));
33+
} catch (\Exception $e) {
34+
throw new DatapackageInvalidSourceException("Failed to load source: ".json_encode($source).": ".$e->getMessage());
35+
}
36+
// http sources don't allow relative paths, hence basePath should remain null
37+
$this->_basePath = null;
38+
} else {
39+
if (empty($basePath)) {
40+
$this->_basePath = dirname($source);
41+
} else {
42+
$this->_basePath = $basePath;
43+
$absPath = $this->_basePath.DIRECTORY_SEPARATOR.$source;
44+
if (file_exists($absPath)) {
45+
$source = $absPath;
46+
}
47+
}
48+
try {
49+
$this->_descriptor = json_decode(file_get_contents($source));
50+
} catch (\Exception $e) {
51+
throw new DatapackageInvalidSourceException("Failed to load source: ".json_encode($source).": ".$e->getMessage());
52+
}
53+
54+
}
55+
} else {
56+
throw new DatapackageInvalidSourceException("Invalid source: ".json_encode($source));
57+
}
58+
}
59+
60+
protected function _normalizeHttpSource($source)
61+
{
62+
return $source;
63+
}
64+
65+
protected function _isHttpSource($source)
66+
{
67+
return Utils::is_http_source($source);
68+
}
69+
70+
protected function _initResource($resourceDescriptor)
71+
{
72+
return new Resource($resourceDescriptor, $this->_basePath);
73+
}
74+
75+
public function descriptor()
76+
{
77+
return $this->_descriptor;
78+
}
79+
80+
// standard iterator functions - to iterate over the resources
81+
public function rewind() { $this->_currentResourcePosition = 0; }
82+
public function current() { return $this->_initResource($this->descriptor()->resources[$this->_currentResourcePosition]); }
83+
public function key() { return $this->_currentResourcePosition; }
84+
public function next() { $this->_currentResourcePosition++; }
85+
public function valid() { return isset($this->descriptor()->resources[$this->_currentResourcePosition]); }
86+
}
87+
88+
89+
class DatapackageInvalidSourceException extends \Exception {};

src/Resource.php

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php namespace frictionlessdata\datapackage;
2+
3+
4+
class Resource implements \Iterator
5+
{
6+
protected $_descriptor;
7+
protected $_basePath;
8+
protected $_currentDataPosition = 0;
9+
10+
public function __construct($descriptor, $basePath)
11+
{
12+
$this->_basePath = $basePath;
13+
$this->_descriptor = $descriptor;
14+
}
15+
16+
protected function _isHttpSource($dataSource)
17+
{
18+
return Utils::is_http_source($dataSource);
19+
}
20+
21+
protected function _normalizeDataSource($dataSource)
22+
{
23+
if (!empty($this->_basePath) && !Utils::is_http_source($dataSource)) {
24+
// TODO: support JSON pointers
25+
$absPath = $this->_basePath.DIRECTORY_SEPARATOR.$dataSource;
26+
if (file_exists($absPath)) {
27+
$dataSource = $absPath;
28+
}
29+
}
30+
return $dataSource;
31+
}
32+
33+
protected function _getDataStream($dataSource)
34+
{
35+
return new DataStream($this->_normalizeDataSource($dataSource));
36+
}
37+
38+
public function descriptor()
39+
{
40+
return $this->_descriptor;
41+
}
42+
43+
public function name()
44+
{
45+
return $this->descriptor()->name;
46+
}
47+
48+
// standard iterator functions - to iterate over the data sources
49+
public function rewind() { $this->_currentDataPosition = 0; }
50+
public function current() { return $this->_getDataStream($this->descriptor()->data[$this->_currentDataPosition]); }
51+
public function key() { return $this->_currentDataPosition; }
52+
public function next() { $this->_currentDataPosition++; }
53+
public function valid() { return isset($this->descriptor()->data[$this->_currentDataPosition]); }
54+
}

src/Utils.php

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php namespace frictionlessdata\datapackage;
2+
3+
4+
class Utils
5+
{
6+
7+
public static function is_json_string($json)
8+
{
9+
return (
10+
is_string($json)
11+
&& (strpos(ltrim($json), "{") === 0)
12+
);
13+
}
14+
15+
public static function is_http_source($source)
16+
{
17+
return (
18+
is_string($source)
19+
&& (
20+
strpos($source, "http:") === 0
21+
|| strpos($source, "https:") === 0
22+
)
23+
);
24+
}
25+
26+
}

0 commit comments

Comments
 (0)