Skip to content

Instantly share code, notes, and snippets.

@sgoguen
Last active April 16, 2017 20:54
Show Gist options
  • Save sgoguen/7e44aa43538da427611e1d96721c906b to your computer and use it in GitHub Desktop.
Save sgoguen/7e44aa43538da427611e1d96721c906b to your computer and use it in GitHub Desktop.
public class Nav {
public static HelperResult NavAndPrint(object obj, string path) {
var printer = Nav.GetPrinter().SetValue(obj);
return printer.NavigateTo(path).TryToShowObject();
}
private static UiContext GetPrinter() {
return new UiContext(
displayRules: new Func<UiContext, Maybe<HelperResult>>[] {
TryBasicTypes, TryPrintMethod, TrySeqDefault, TryDefault
},
navRules: new Func<string, object, Maybe<object>>[] {
TryGetProperty, TryIndexer, TryDictionary,
TryGetQueryable, TryGetArray, TryGetEnumerable
}
);
}
private static object CatchError<T>(Func<T> f) {
try {
return f();
} catch (Exception ex) {
return ex;
}
}
private static Maybe<object> TryGetProperty(string name, object input) {
var getter = GetMemberGetters(input).FirstOrDefault(g => g.Name == name);
if (getter == null)
return None<object>();
return Some(getter.GetPropValue(input));
}
private static Maybe<object> TryGetArray(string index, object input) {
try {
int rowIndex;
if (!int.TryParse(index, out rowIndex))
return None<object>();
var arr = input as object[];
if (arr == null || rowIndex > arr.Length) return None<object>();
return Some(arr[rowIndex]);
} catch (Exception ex) {
return None<object>();
}
}
private static Maybe<object> TryGetEnumerable(string index, object input) {
try {
int rowIndex;
if (!int.TryParse(index, out rowIndex))
return None<object>();
var arr = (input as IEnumerable).OfType<object>();
return Some(arr.Skip(rowIndex).First());
} catch (Exception ex) {
return None<object>();
}
}
private static Maybe<object> TryGetQueryable(string index, object input) {
try {
int rowIndex;
if (!int.TryParse(index, out rowIndex))
return None<object>();
var arr = (input as IQueryable).OfType<object>();
return Some(arr.Skip(rowIndex).First());
} catch (Exception) {
return None<object>();
}
}
private static Maybe<object> TryIndexer(string index, object input) {
if (input == null) return None<object>();
var indexer = GetIndexer(input.GetType());
if (!indexer.HasValue) return None<object>();
return Some(indexer.Value(index, input));
}
private static Maybe<object> TryDictionary(string index, object input) {
try {
var dict = input as IDictionary;
if (dict == null || !dict.Contains(index))
return None<object>();
return Some(dict[index]);
} catch (Exception) {
return None<object>();
}
}
private static bool IsBasicProperty(PropertyInfo p) {
return p.IsSpecialName == false && p.GetIndexParameters().Length == 0
&& p.CanRead && p.IsSpecialName == false;
}
private static bool IsPrimitive(object o) => (o == null) || IsPrimitive(o.GetType());
private static bool IsPrimitive(Type t) {
var list = new[] {
typeof(int), typeof(string), typeof(DateTime), typeof(long), typeof(uint), typeof(char),
typeof(double), typeof(decimal), typeof(uint), typeof(byte), typeof(float), typeof(sbyte), typeof(short),
typeof(ulong), typeof(ushort), typeof(bool)
};
return list.Contains(t);
}
private static bool IsList(Type t) {
if (IsPrimitive(t))
return false;
return t.IsArray || t.GetInterfaces().Contains(typeof(IEnumerable));
}
private static MemberGetter[] GetMemberGetters(object o) {
if (o == null)
return new MemberGetter[] { };
return GetMemberGetters(o.GetType());
}
private static MemberGetter[] GetMemberGetters(Type objectType) {
var flags = BindingFlags.Public | BindingFlags.Instance;
var properties = from p in objectType.GetProperties(flags)
where IsBasicProperty(p)
let param = Expression.Parameter(typeof(object), "x")
let cparam = Expression.Convert(param, objectType)
let isType = Expression.TypeIs(param, objectType)
let getProp = Expression.Convert(Expression.Property(cparam, p), typeof(object))
let check = Expression.Lambda<Func<object, object>>(Expression.Condition(isType, getProp, Expression.Constant(null)), param).Compile()
select new MemberGetter {
Name = p.Name,
Type = p.PropertyType,
GetPropValue = check
};
var fields = from f in objectType.GetFields(flags)
select new MemberGetter {
Name = f.Name,
Type = f.FieldType,
GetPropValue = new Func<object, object>(o => f.GetValue(o))
};
return properties.Concat(fields).ToArray();
}
private static Maybe<Func<string, object, object>> GetIndexer(Type t) {
if (t == null)
return None<Func<string, object, object>>();
var method = (from m in t.GetMethods()
where m.IsSpecialName && m.Name == "get_Item"
let parameters = m.GetParameters()
where parameters.Length == 1
where parameters[0].ParameterType == typeof(string)
let indexParam = Expression.Parameter(typeof(string), "index")
let objectParam = Expression.Parameter(typeof(object), "obj")
let objToType = Expression.Convert(objectParam, t)
let isType = Expression.TypeIs(objectParam, t)
let call = Expression.Call(objToType, m, indexParam)
let convertCall = Expression.Convert(call, typeof(object))
let getIndex = Expression.Lambda<Func<string, object, object>>(
Expression.Condition(isType, convertCall, Expression.Constant(null)),
indexParam, objectParam).Compile()
select getIndex
).FirstOrDefault();
if (method == null)
return None<Func<string, object, object>>();
return Some(method);
}
public class MemberGetter {
public string Name { get; set; }
public Type Type { get; set; }
public bool IsPrimitive => IsPrimitive(Type);
public bool IsList => IsList(Type);
public Func<object, object> GetPropValue { get; set; }
}
private static Maybe<T> Some<T>(T value) => new Maybe<T>(value);
private static Maybe<T> None<T>() => new Maybe<T>(false);
public struct Maybe<T> {
public bool HasValue { get; private set; }
public T Value { get; private set; }
public Maybe(bool ignore) {
HasValue = false;
Value = default(T);
}
public Maybe(T value) {
HasValue = true;
Value = value;
}
}
public struct UiContext {
private Func<UiContext, Maybe<HelperResult>>[] DisplayRules;
private Func<string, object, Maybe<object>>[] NavRules;
public readonly string[] Path;
public readonly object Value;
public readonly Type ObjectType;
public UiContext(Func<UiContext, Maybe<HelperResult>>[] displayRules, Func<string, object, Maybe<object>>[] navRules) {
DisplayRules = displayRules;
NavRules = navRules;
Path = new string[] { };
Value = null;
ObjectType = null;
}
private UiContext(object value, string[] path, Func<UiContext, Maybe<HelperResult>>[] displayRules, Func<string, object, Maybe<object>>[] navRules) {
DisplayRules = displayRules;
NavRules = navRules;
Path = path;
Value = value;
ObjectType = value?.GetType();
}
public UiContext SetValue(object value) {
return new UiContext(value, this.Path, this.DisplayRules, this.NavRules);
}
public UiContext SetDisplayRules(params Func<UiContext, Maybe<HelperResult>>[] displayRules) {
DisplayRules = displayRules;
return this;
}
public UiContext SetNavRules(params Func<string, object, Maybe<object>>[] navRules) {
NavRules = navRules;
return this;
}
public UiContext AddPath(string path, object value) {
var newPath = Path.Concat(new string[] { path }).ToArray();
return new UiContext(value, newPath, DisplayRules, NavRules);
}
public HelperResult TryToShowObject() {
foreach (var rule in DisplayRules) {
var result = rule(this);
if (result.HasValue) {
return result.Value;
}
}
throw new Exception(String.Format("Error displaying object with {0} rules", DisplayRules.Length));
}
private Maybe<UiContext> TryNavRules(string path) {
foreach (var f in NavRules) {
var r = f(path, this.Value);
if (r.HasValue)
return Some(this.AddPath(path, r.Value));
}
return None<UiContext>();
}
public UiContext NavigateTo(string path) {
path = path ?? "";
var paths = path.Split('/').Where(p => !String.IsNullOrWhiteSpace(p)).ToArray();
return NavigatePath(paths);
}
public UiContext NavigatePath(string[] path) {
var obj = this.Value;
if (obj == null || path == null) return this.SetValue(null);
var currentPath = new List<string>();
var result = this;
foreach (var p in path) {
if (result.Value == null)
return result;
currentPath.Add(p);
//var resultType = result.GetType();
// We want to try fetching the next object by checking things in this order:
// Property,dictionary,array,list,iqueryable,ienumerable
try {
var r = result.TryNavRules(p);
if (r.HasValue) {
result = r.Value;
continue;
}
} catch (Exception ex) {
throw new Exception($"Member not found {String.Join("/", currentPath.ToArray())}");
}
throw new Exception($"Member not found {String.Join("/", currentPath.ToArray())}");
}
return result;
}
}
private static Maybe<HelperResult> TryPrintMethod(UiContext context) {
var method = context.ObjectType.GetMethods().FirstOrDefault(m => m.Name == "Print");
if (method == null || method.IsSpecialName) return None<HelperResult>();
if (method.ReturnType != typeof(HelperResult) && method.ReturnType != typeof(IHtmlString)) return None<HelperResult>();
if (method.GetParameters().Length != 0) return None<HelperResult>();
var result = method.Invoke(context.Value, new object[] { });
if (result == null) return None<HelperResult>();
if (result is IHtmlString) {
var htmlString = (IHtmlString)result;
return Some(new HelperResult(a => a.Write(htmlString.ToHtmlString())));
}
return Some((HelperResult)result);
}
private static Maybe<HelperResult> TryBasicTypes(UiContext context) {
if (context.ObjectType == null || IsPrimitive(context.ObjectType)) {
return Some(ShowValue(context));
} else if (context.Value is HelperResult) {
return Some((HelperResult)context.Value);
} else if (context.Value is IHtmlString) {
var htmlString = (IHtmlString)context.Value;
return Some(new HelperResult(tw => tw.Write(htmlString.ToHtmlString())));
} else {
return None<HelperResult>();
}
}
private static HelperResult ShowValue(UiContext context) {
if (context.Value == null) {
return new HelperResult(tw => tw.Write("<span > (null) </ span >"));
} else if (IsPrimitive(context.Value)) {
return new HelperResult(tw => tw.Write($"<span>{context.Value}</span>"));
} else {
var url = $"/Dev/{String.Join("/", context.Path)}";
return new HelperResult(tw => tw.Write($"<a href=\"{url}\">{context.Value.GetType().Name}...</a>"));
}
}
private static Maybe<HelperResult> TryTable(UiContext context) {
if (context.Value is System.Data.Linq.ITable) {
var table = (System.Data.Linq.ITable)context.Value;
var metaType = table.Context.Mapping.GetMetaType(table.ElementType);
return Some(DumpToResult(metaType));
}
return None<HelperResult>();
}
private static Maybe<HelperResult> TrySeqDefault(UiContext context) {
if (context.Value is IQueryable) {
//var list = ((IQueryable) context.Value).OfType<object>().Take(100);
return Some(DumpToResult(context.Value));
}
return None<HelperResult>();
}
private static Maybe<HelperResult> TryDefault(UiContext context) {
return Some(DefaultObject(context));
}
private static HelperResult DefaultObject(UiContext context) {
return DumpToResult(from p in GetMemberGetters(context.Value)
let newContext = context.AddPath(p.Name, p.GetPropValue(context.Value))
let value = ShowValue(newContext)
select new { p.Name, Value = value });
}
private static HelperResult DumpToResult<T>(T obj) {
return new HelperResult(tw => tw.Write(Util.ToHtmlString(obj)));
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment