Skip to content

Refactor native extension #4

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
152 changes: 98 additions & 54 deletions ext/case_transform/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,80 +2,124 @@
extern crate ruru;
extern crate inflector;

// // dash: kebab-case
use inflector::cases::kebabcase::to_kebab_case;
// // underscore: snake_case
use inflector::cases::snakecase::to_snake_case;
// // camel_lower: camelCase
use inflector::cases::camelcase::to_camel_case;
// // camel: ClassCase (PascalCase)
use inflector::cases::classcase::to_class_case;

use ruru::{Class, Object, RString, Hash, Array, Symbol, AnyObject, VM};
use ruru::types::ValueType;
use inflector::cases::{camelcase, classcase, kebabcase, snakecase};
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is handy!

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there a way to import the methods into the 'global' namespace of the file? so you don't have to do classcase::to_class_case?


class!(CaseTransform);
use ruru::{Class, Object, VerifiedObject, RString, Hash, Array, Symbol, AnyObject};
use ruru::types::ValueType;
use ruru::result::Error as RuruError;

methods! (
CaseTransform,
itself,
trait Transform: Object {
Copy link
Author

@d-unsed d-unsed Sep 21, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Transform describes an interface for transformation. It can be implemented only to Ruby objects due to Object constraint.

Then we make type-specific implementations for

  • AnyObject (no-op)
  • RString
  • Symbol
  • Hash
  • Array

by implementing transorm methods.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I need to read up on traits and impls

for myself:

fn transform(&self, transform_function: &Fn(String) -> String) -> AnyObject;
}

fn deepTransformKeys(hash: Hash, block: &Fn(String) -> String) -> Hash {
let result = Hash::new();
impl Transform for AnyObject {
fn transform(&self, _transform_function: &Fn(String) -> String) -> AnyObject {
self.clone()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why was this removed? I saw it one one of the example repos, though I don't know what its purpose was

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The line was moved to the bottom of the file

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

}
}

hash.unwrap().each(|key, value| {
let newValue = if value.ty() == ValueType::Hash { deepTransformKeys(value, block).to_any_object() } else { value };
let newKey = RString::new(block(key.unwrap().to_string()));
result.store(newKey, newValue);
});
impl Transform for RString {
fn transform(&self, transform_function: &Fn(String) -> String) -> AnyObject {
let result = transform_function(self.to_string();

result
RString::new(&result)).to_any_object()
}
}

impl Transform for Symbol {
fn transform(&self, transform_function: &Fn(String) -> String) -> AnyObject {
let result = transform_function(self.to_string());

fn transformArray(value: Array, transformMethod: &Fn(AnyObject) -> AnyObject) -> Array {
value.map(|item| transformMethod(item)).unwrap()
Symbol::new(&result).to_any_object()
}
}

impl Transform for Hash {
fn transform(&self, transform_function: &Fn(String) -> String) -> AnyObject {
let mut result = Hash::new();

self.each(|key, value| {
let new_key = transform(key, transform_function);
let new_value = match value.ty() {
ValueType::Hash => transform(value, transform_function),
_ => value,
};

fn transformHash(value: Hash, transformMethod: &Fn(AnyObject) -> AnyObject) -> Hash {
deepTransformKeys(value, |key| transformMethod(key))
result.store(new_key, new_value);
});

result.to_any_object()
}
}

impl Transform for Array {
fn transform(&self, transform_function: &Fn(String) -> String) -> AnyObject {
// Temp hack to consume &self for iterator
let result = unsafe { self.to_any_object().to::<Array>() };

fn transformSymbol(value: Symbol, transformMethod: &Fn(AnyObject) -> AnyObject) -> Symbol {
let transformed = transformMethod(value);
Symbol::new(transformed);
result.into_iter()
Copy link
Collaborator

@NullVoxPopuli NullVoxPopuli Sep 21, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

arrays are iterable by default?

edit: not*

Copy link
Author

@d-unsed d-unsed Sep 21, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They kinda are iterable, but after explicit conversion to iterators :) Same for slices, vectors etc

.map(|item| transform(item, transform_function))
.collect::<Array>()
.to_any_object()
}
}

fn transform(
value: AnyObject,
objectTransform: &Fn(AnyObject) -> AnyObject,
keyTransform: &Fn(String) -> String
) -> AnyObject {
match value.unwrap().ty() {
ValueType::Array => transformArray(value, objectTransform).to_any_object(),
ValueType::Hash => transformHash(value, objectTransform).to_any_object(),
ValueType::Symbol => transformSymbol(value, objectTransform).to_any_object(),
ValueType::RString => keyTransform(value).to_any_object(),
ValueType::Object => value
}
trait TryTransform: Object {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TryTransform trait itself is needed only to define try_transform method on AnyObject. Rust does not allow to change the implementation of types that is not owned by current crate.

fn try_transform<T>(&self,
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

try_transform<T> method:

  1. Tries to convert current AnyObject to the type T.
  2. :
    • If the conversion is successful it performs a transformation.
    • otherwise, returns an error from try_convert_to.

An example of usage of try_transform is

object.try_transform::<Hash>(key_transform) // if the object is a hash, try to do a hash transformation

The interesting part here is the constraints for T type: where T: VerifiedObject + Transform.

transform_function: &Fn(String) -> String)
-> Result<AnyObject, RuruError>
where T: VerifiedObject + Transform
{
self.try_convert_to::<T>().map(|object| object.transform(transform_function))
}
}

impl TryTransform for AnyObject {}

fn transform(object: AnyObject, key_transform: &Fn(String) -> String) -> AnyObject {
let result = object.try_transform::<RString>(key_transform)
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Try to transform the object as a RString
Otherwise try to transform the object as a Symbol
...
Otherwise try to transform the object as a AnyObject (it is any unkown class, do nothing)

.or_else(|_| object.try_transform::<Symbol>(key_transform))
.or_else(|_| object.try_transform::<Array>(key_transform))
.or_else(|_| object.try_transform::<Hash>(key_transform))
.or_else(|_| object.try_transform::<AnyObject>(key_transform));

result.unwrap()
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can safely unwrap here, because result has AnyObject type

}

fn to_pascal_case(key: String) -> String {
classcase::to_class_case(snakecase::to_snake_case(key))
}

fn to_camel_case(key: String) -> String {
camelcase::to_camel_case(snakecase::to_snake_case(key))
}

fn to_dashed_case(key: String) -> String {
kebabcase::to_kebab_case(snakecase::to_snake_case(key))
}

fn to_snake_case(key: String) -> String {
snakecase::to_snake_case(key)
}

fn toPascalCase(key: String) -> String { to_class_case(to_snake_case(key.unwrap())) }
fn toCamelCase(key: String) -> String { to_camel_case(to_snake_case(key.unwrap())) }
fn toDashedCase(key: String) -> String { to_kebab_case(to_snake_case(key.unwrap())) }
fn toSnakeCase(key: String) -> String { to_snake_case(key.unwrap()) }
class!(CaseTransform);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here it is!


methods! (
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All the functions which are not used directly in the CaseTransform class are moved outside methods!

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this makes sense!

CaseTransform,
_itself,

fn camel(value: AnyObject) -> AnyObject { transform(value.unwrap().to_any_object(), &camel, &toPascalCase) }
fn camelLower(value: AnyObject) -> AnyObject { transform(value.unwrap().to_any_object(), &camelLower, &toCamelCase) }
fn dash(value: AnyObject) -> AnyObject { transform(value.unwrap().to_any_object(), &dash, &toDashedCase) }
fn underscore(value: AnyObject) -> AnyObject { transform(value.unwrap(), &underscore, &toSnakeCase) }
fn unaltered(value: AnyObject) -> AnyObject { value.unwrap().to_any_object() }
fn camel(object: AnyObject) -> AnyObject { transform(value.unwrap(), &to_pascal_case) }
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And we can safely unwrap in this methods, because objects are AnyObjects

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

legit

fn camel_lower(object: AnyObject) -> AnyObject { transform(object.unwrap(), &to_camel_case) }
fn dash(object: AnyObject) -> AnyObject { transform(object.unwrap(), &to_dashed_case) }
fn underscore(object: AnyObject) -> AnyObject { transform(object.unwrap(), &to_snake_case) }
fn unaltered(object: AnyObject) -> AnyObject { object.unwrap() }
);

#[no_mangle]
pub extern fn initialize_case_transform() {
Class::new("CaseTransform", None).define(|itself| {
pub extern "C" fn initialize_case_transform() {
Class::from_existing("CaseTransform").define(|itself| {
itself.def_self("camel", camel);
itself.def_self("camel_lower", camelLower);
itself.def_self("camel_lower", camel_lower);
itself.def_self("dash", dash);
itself.def_self("underscore", underscore);
itself.def_self("unaltered", unaltered);
Expand Down